Skip to content

Commit 0e07b7e

Browse files
committed
add tests for publicKey
1 parent 50e99cd commit 0e07b7e

File tree

6 files changed

+206
-49
lines changed

6 files changed

+206
-49
lines changed

README.md

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -108,16 +108,34 @@ server.route([
108108
## API
109109
#### Plugin Options
110110

111-
- `client {Object}` — The configuration of [`keycloak-auth-utils`][keycloak-auth-utils] its [`GrantManager`][keycloak-auth-utils-gm]. The configuration requires at least:
112-
- `realmUrl {string}`: The absolute uri of the Keycloak realm<br/>
113-
Example: `https://localhost:8080/auth/realms/testme`
114-
- `clientId {string}` The identifier of the Keycloak client<br/>
115-
Example: `foobar`
116-
- `secret {string}` The related secret of the Keycloak client<br/>
117-
Example: `1234-bar-4321-foo`
111+
- `realmUrl {string}`: The absolute uri of the Keycloak realm<br/>
112+
Required. Example: `https://localhost:8080/auth/realms/testme`<br/>
113+
114+
- `clientId {string}` The identifier of the Keycloak client<br/>
115+
Required. Example: `foobar`<br/>
116+
117+
118+
- `secret {string}` The related secret of the Keycloak client<br/>
119+
Required (or `publicKey`). Example: `1234-bar-4321-foo`<br/>
120+
118121

119-
Furthermore it may be necessary to reduce `minTimeBetweenJwksRequests`.<br/>
120-
Required.
122+
- `publicKey {string}` The related public key of the Keycloak client<br/>
123+
Required (or `secret`). Example:
124+
```
125+
-----BEGIN RSA PUBLIC KEY-----
126+
MIICCgKCAgEAur96MoQa/blg5eJFqmN//V4oQjKaBJl6KEvWSGAVgRm2PsnFKzCJ
127+
K9aJrUjETS473/x6fAHXyF5QKun6avNxpWs2VzwlO4t8Bi2EpSW2w0led2OzR/MF
128+
CYUX1Bg00OXLmdh0kvemABHXSOL4Zs4nN95rr77JsdG6ntylWmtnZlofySwLjFrX
129+
B2JFpunl4n7eF7y8vD4Zyycvi+xl+OuMA6qomLQmfIDF0qJh0QMt/xh6CB4ooK5a
130+
Fy6VHORU5gS6MoSgJ78ZPhqmKayRd6U0rhDOq7bWiVykktTPdB1X/YlMbQHonyqZ
131+
0z2sh7e7olroUdE1FsDi0SdxV2Zzvff8IIPSGT74gQuaU4buoVUpMjSdXDfJw9m4
132+
i/mhsZwX1/MUU8Oq1AsBHdgTkNVaVwFc6Z7AqJSGW/mxMKQqspOr+/BAM8pnw5BC
133+
R/RnZBJfMjAYiSh6rVYuEWUBBLgZWIRNAMyRwS8OtWxADfOBtVwn8fmw8schro9X
134+
D17VcqTdjlS1Mkr73ZoD1GagWZvuSOaz2P0PhnfapRUF1KkbjjnbyzLpfT8Mgovv
135+
xBQJDIcRYL5oetXu//V5rNAr0na4zMBkPOi3ArpFp+Z4YKulYmSEG//216rLmzjl
136+
WKEkH1OK39jddDrMTidlxDKY+THheyQBPZ6pFfbKEM5281glYjkeQpECAwEAAQ==
137+
-----END RSA PUBLIC KEY-----
138+
```
121139

122140
- `cache {Object|false}` — The configuration of the [hapi.js cache](https://hapijs.com/api#servercacheoptions) powered by [catbox][catbox].<br/>
123141
If `false` the cache is disabled. Use an empty object (`{}`) to use the built-in default cache.<br/>
@@ -127,8 +145,6 @@ Optional. Default: `false`.
127145
Optional. Default: `[]`.<br/>
128146

129147
#### `server.kjwt.validate(field {string}, done {Function})`
130-
Uses internally [`GrantManager.prototype.validateAccessToken()`][keycloak-auth-utils-gm-validate].
131-
132148
- `field {string}` — The `Bearer` field, including the scheme (`bearer`) itself.<br/>
133149
Example: `bearer 12345.abcde.67890`.<br/>
134150
Required.
@@ -227,7 +243,3 @@ For further information read the [contributing guideline](CONTRIBUTING.md).
227243
[catbox]: https://github.com/hapijs/catbox
228244
[bearer]: https://tools.ietf.org/html/rfc6750
229245
[hapi-route-options]: https://hapijs.com/api#route-options
230-
[keycloak-auth-utils]: http://www.keycloak.org/keycloak-nodejs-auth-utils/
231-
[keycloak-auth-utils-gm]: http://www.keycloak.org/keycloak-nodejs-auth-utils/grant-manager.js.html
232-
[keycloak-auth-utils-gm-obtain]: http://www.keycloak.org/keycloak-nodejs-auth-utils/grant-manager.js.html#obtainDirectly
233-
[keycloak-auth-utils-gm-validate]: http://www.keycloak.org/keycloak-nodejs-auth-utils/grant-manager.js.html#validateAccessToken

src/index.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,10 @@ let options
2525
* @returns {Promise} The error-handled promise
2626
*/
2727
function validateOffline (token) {
28-
const { publicKey, verifyOpts = {} } = options
28+
const {
29+
publicKey,
30+
verifyOpts = { algorithms: ['RS256', 'RS384', 'RS512'] }
31+
} = options
2932

3033
return new Promise((resolve, reject) => {
3134
jwt.verify(token, publicKey, verifyOpts, (err, decoded) => {

test.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
const fs = require('fs')
2+
const jwt = require('jsonwebtoken')
3+
const fixtures = require('./test/fixtures')
4+
5+
const priv = fs.readFileSync('./test/fixtures/private.pem')
6+
7+
const dd = jwt.sign(fixtures.content.userData, priv, {
8+
algorithm: 'RS256'
9+
})
10+
console.log(dd)

test/fixtures/index.js

Lines changed: 47 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
const fs = require('fs')
2+
const jsonwebtoken = require('jsonwebtoken')
3+
14
const token = 'abc.def.ghi'
25
const baseUrl = 'http://localhost:8080'
36
const realmPath = '/auth/realms/testme'
@@ -45,46 +48,53 @@ const common = Object.assign({}, clientConfig, {
4548
introspectPath
4649
})
4750

51+
const contentBase = {
52+
'sub': '1234567890',
53+
'name': 'John Doe',
54+
'email': '[email protected]',
55+
'admin': true,
56+
'active': true,
57+
'realm_access': {
58+
'roles': [
59+
'admin'
60+
]
61+
},
62+
'resource_access': {
63+
'account': {
64+
'roles': [
65+
'manage-account',
66+
'manage-account-links',
67+
'view-profile'
68+
]
69+
},
70+
'same': {
71+
'roles': [
72+
'editor'
73+
]
74+
},
75+
'other-app': {
76+
'roles': [
77+
'other-app:creator'
78+
]
79+
}
80+
}
81+
}
82+
4883
/**
4984
* @type Object
5085
* @public
5186
*
5287
* Content Parts of JWTs
5388
*/
5489
const content = {
55-
userData: {
90+
userData: Object.assign({
5691
'exp': 5,
57-
'iat': 1,
58-
'sub': '1234567890',
59-
'name': 'John Doe',
60-
'email': '[email protected]',
61-
'admin': true,
62-
'active': true,
63-
'realm_access': {
64-
'roles': [
65-
'admin'
66-
]
67-
},
68-
'resource_access': {
69-
'account': {
70-
'roles': [
71-
'manage-account',
72-
'manage-account-links',
73-
'view-profile'
74-
]
75-
},
76-
'same': {
77-
'roles': [
78-
'editor'
79-
]
80-
},
81-
'other-app': {
82-
'roles': [
83-
'other-app:creator'
84-
]
85-
}
86-
}
87-
}
92+
'iat': 1
93+
}, contentBase),
94+
userDataNoExp: Object.assign({
95+
'exp': (Date.now() / 1000) + 60 * 60,
96+
'iat': (Date.now() / 1000) + 60 * 15
97+
}, contentBase)
8898
}
8999

90100
/**
@@ -96,7 +106,11 @@ const content = {
96106
const jwt = {
97107
userData: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjUsImFjdGl2ZSI6dHJ1ZSwiaWF0IjoxLCJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiZW1haWwiOiJqb2huLmRvZUBtYWlsLmNvbSIsImFkbWluIjp0cnVlLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsiYWRtaW4iXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX0sInNhbWUiOnsicm9sZXMiOlsiZWRpdG9yIl19LCJvdGhlci1hcHAiOnsicm9sZXMiOlsib3RoZXItYXBwOmNyZWF0b3IiXX19fQ.rjQkSo132lPSVUEsuz42oB5u_lW9zCBpvnOyngDYa_0',
98108
userDataExp: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY3RpdmUiOnRydWUsInN1YiI6IjEyMzQ1Njc4OTAiLCJuYW1lIjoiSm9obiBEb2UiLCJlbWFpbCI6ImpvaG4uZG9lQG1haWwuY29tIiwiYWRtaW4iOnRydWUsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJhZG1pbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfSwic2FtZSI6eyJyb2xlcyI6WyJlZGl0b3IiXX0sIm90aGVyLWFwcCI6eyJyb2xlcyI6WyJvdGhlci1hcHA6Y3JlYXRvciJdfX19.0_g0X4cOpOEJ37qwRQzBpouQDn2aEyg0-0jnnWECCsk',
99-
userDataScope: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY3RpdmUiOnRydWUsImV4cCI6NSwiaWF0IjoxLCJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiZW1haWwiOiJqb2huLmRvZUBtYWlsLmNvbSIsImFkbWluIjp0cnVlfQ.V7lYgZKjnDJcPtUnIBHA9f-8bn8XXB6uH8bXElH-aOU'
109+
userDataScope: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY3RpdmUiOnRydWUsImV4cCI6NSwiaWF0IjoxLCJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiZW1haWwiOiJqb2huLmRvZUBtYWlsLmNvbSIsImFkbWluIjp0cnVlfQ.V7lYgZKjnDJcPtUnIBHA9f-8bn8XXB6uH8bXElH-aOU',
110+
userDataPublicKey: () => jsonwebtoken.sign(content.userDataNoExp, fs.readFileSync('./test/fixtures/private.pem'), {
111+
algorithm: 'RS256'
112+
}),
113+
userDataPublicKeyExp: 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjUsImlhdCI6MSwic3ViIjoiMTIzNDU2Nzg5MCIsIm5hbWUiOiJKb2huIERvZSIsImVtYWlsIjoiam9obi5kb2VAbWFpbC5jb20iLCJhZG1pbiI6dHJ1ZSwiYWN0aXZlIjp0cnVlLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsiYWRtaW4iXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX0sInNhbWUiOnsicm9sZXMiOlsiZWRpdG9yIl19LCJvdGhlci1hcHAiOnsicm9sZXMiOlsib3RoZXItYXBwOmNyZWF0b3IiXX19fQ.IgNqnLBKCox3b6KLewV5U_CLfQ79upmT-xkz5XreQ1Jdv1HcAuWcDYeraFnHU29aeU-JJGa7k3VvdbVSwSbFPkkMvjg_lLnqUUcHtuoPqisdbyty-4VRfZp18JnbAUnxlY4R7d-ZEQKPiXp-WBkHOPo5c8FrqATJ7HI7-MkrrKLBgOCmAFrVXto2lp7xmQOnoTy-31Suoj-0hOaIuP2jDRuIwGGigIgtCxxriE0Lzpt2R_wlEfq-0kmMZrgq9F91x-rB6Vg0nbhdtcv4c61nETHdRSqSIuAQzi9lm1mq9Kg67qU7tzaC3pPLkZRd6hMX6fwittVhm9G9SrXLX4rN6QoArEvglfuNX9sIYgEFoD9vqCx1rRCiyx1m55IGgFRLSn_pjOfTBx5Gb5GPlDcCdsNjW0BXLMSzhuPXDsslFFcBHzSYdOFz6hKEU1CVDYjxalq7wDPzMyCORDSSrgZJn_-oirDFaOe3H3ioZzH2x_lfXfbgga7lc5hkU0ZYZfq-1PUrEuqKKFWtDyPhzwcbX-uCno4HiJOuBlRsnJJFnWI0-GNASHUQD-eO4c9a0c2azG7dhH9lnhsHyA_ptLlIqeoPebkl_E88GXmjbHs3qa6anw1ZWn6sEXdlHAZ1ARt8Gc6r_2oOU8VOusgOnnMQdLYHONfbS0sKFNV65uvd01M'
100114
}
101115

102116
module.exports = {

test/publicKey.spec.js

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
const test = require('ava')
2+
const helpers = require('./_helpers')
3+
const fixtures = require('./fixtures')
4+
const cache = require('../src/cache')
5+
6+
test.afterEach('reset instances and prototypes', () => {
7+
cache.reset()
8+
})
9+
10+
const publicKeyConfig = {
11+
realmUrl: fixtures.common.realmUrl,
12+
clientId: fixtures.common.clientId,
13+
publicKey: fixtures.common.publicKey
14+
}
15+
16+
test.cb.serial('authentication does succeed', (t) => {
17+
helpers.getServer(publicKeyConfig, (server) => {
18+
server.inject({
19+
method: 'GET',
20+
url: '/',
21+
headers: {
22+
authorization: `bearer ${fixtures.jwt.userDataPublicKey()}`
23+
}
24+
}, (res) => {
25+
t.truthy(res)
26+
t.is(res.statusCode, 200)
27+
t.end()
28+
})
29+
})
30+
})
31+
32+
test.cb.serial('authentication does succeed – cached', (t) => {
33+
const mockReq = {
34+
method: 'GET',
35+
url: '/',
36+
headers: {
37+
authorization: `bearer ${fixtures.jwt.userDataPublicKey()}`
38+
}
39+
}
40+
41+
helpers.getServer(Object.assign({
42+
cache: true
43+
}, publicKeyConfig), (server) => {
44+
server.inject(mockReq, () => {
45+
server.inject(mockReq, (res) => {
46+
t.truthy(res)
47+
t.is(res.statusCode, 200)
48+
t.end()
49+
})
50+
})
51+
})
52+
})
53+
54+
test.cb.serial('authentication does success – valid roles', (t) => {
55+
helpers.getServer(publicKeyConfig, (server) => {
56+
server.inject({
57+
method: 'GET',
58+
url: '/role',
59+
headers: {
60+
authorization: `bearer ${fixtures.jwt.userDataPublicKey()}`
61+
}
62+
}, (res) => {
63+
t.truthy(res)
64+
t.is(res.statusCode, 200)
65+
t.end()
66+
})
67+
})
68+
})
69+
70+
test.cb.serial('authentication does fail – invalid roles', (t) => {
71+
helpers.getServer(publicKeyConfig, (server) => {
72+
server.inject({
73+
method: 'GET',
74+
url: '/role/guest',
75+
headers: {
76+
authorization: `bearer ${fixtures.jwt.userDataPublicKey()}`
77+
}
78+
}, (res) => {
79+
t.truthy(res)
80+
t.is(res.statusCode, 403)
81+
t.end()
82+
})
83+
})
84+
})
85+
86+
test.cb.serial('authentication does fail – expired token', (t) => {
87+
helpers.getServer(publicKeyConfig, (server) => {
88+
server.inject({
89+
method: 'GET',
90+
url: '/',
91+
headers: {
92+
authorization: `bearer ${fixtures.jwt.userDataPublicKeyExp}`
93+
}
94+
}, (res) => {
95+
t.truthy(res)
96+
t.is(res.statusCode, 401)
97+
t.is(res.headers['www-authenticate'], 'Bearer error="jwt expired"')
98+
t.end()
99+
})
100+
})
101+
})
102+
103+
test.cb.serial('authentication does fail – invalid header', (t) => {
104+
helpers.getServer(publicKeyConfig, (server) => {
105+
server.inject({
106+
method: 'GET',
107+
url: '/',
108+
headers: {
109+
authorization: fixtures.common.token
110+
}
111+
}, (res) => {
112+
t.truthy(res)
113+
t.is(res.statusCode, 401)
114+
t.is(res.headers['www-authenticate'], 'Bearer error="Missing or invalid authorization header"')
115+
t.end()
116+
})
117+
})
118+
})
File renamed without changes.

0 commit comments

Comments
 (0)