UNPKG

5.02 kBJavaScriptView Raw
1/**
2 * Module dependencies
3 */
4
5var JWT = require('anvil-connect-jwt')
6var async = require('async')
7var request = require('superagent')
8var UnauthorizedError = require('../errors/UnauthorizedError')
9var nowSeconds = require('./time-utils.js').nowSeconds
10
11/**
12 * AccessToken
13 */
14var AccessToken = JWT.define({
15 // default header
16 header: {
17 alg: 'RS256'
18 },
19
20 headers: [
21 'alg'
22 ],
23
24 // modify header schema
25 registeredHeaders: {
26 alg: { format: 'StringOrURI', required: true, enum: ['RS256'] }
27 },
28
29 // permitted claims
30 claims: ['jti', 'iss', 'sub', 'aud', 'exp', 'iat', 'scope'],
31
32 // modify payload schema
33 registeredClaims: {
34 jti: { format: 'String', required: true },
35 iss: { format: 'URI', required: true },
36 iat: { format: 'IntDate', required: true },
37 exp: { format: 'IntDate', required: true },
38 sub: { format: 'String', required: true },
39 aud: { format: 'String', required: true },
40 scope: { format: 'String', required: true }
41 }
42
43})
44
45/**
46 * Refreshes
47 */
48AccessToken.refresh = function (refreshToken, options, callback) {
49 if (!options.client_id || !options.client_secret || !refreshToken) {
50 return callback(new UnauthorizedError('Missing client credentials'))
51 }
52 request
53 .post(options.issuer + '/token')
54 .auth(options.client_id, options.client_secret)
55 .set('Content-Type', 'application/json')
56 .send({ refresh_token: refreshToken, grant_type: 'refresh_token' })
57 .end(function (err, response) {
58 // superagent error
59 if (err) {
60 return callback(err)
61 }
62
63 // Forbidden client or invalid access token
64 if (response.body && response.body.error) {
65 return callback(new UnauthorizedError(response.body))
66 } else {
67 return callback(null, response.body)
68 }
69 })
70}
71
72/**
73 * Verifies an OIDC access token (makes calls to /token/verify endpoint, etc).
74 * Used by `client.token()`, `client.refresh()` and `client.verify()`.
75 * @method verify
76 * @param token {String} JWT AccessToken for OpenID Connect (base64 encoded)
77 * @param options {Object} Options hashmap
78 * @param options.client_id {String}
79 * @param options.client_secret {String}
80 * @param options.clients
81 * @param options.issuer {String}
82 * @param options.key {String|JWK} Token secret. Either a string signature (in
83 * which case, use JWA to verify), or a JWK object (use JWS to verify)
84 * @param options.scope
85 * @param callback {Function}
86 * @throws {UnauthorizedError} HTTP 401 or 403 errors
87 */
88AccessToken.verify = function (token, options, callback) {
89 async.parallel({
90 jwt: function (done) {
91 if (token.indexOf('.') !== -1) {
92 var at = AccessToken.decode(token, options.key)
93 if (!at || at instanceof Error) {
94 done(new UnauthorizedError({
95 realm: 'user',
96 error: 'invalid_token',
97 error_description: 'Invalid access token',
98 statusCode: 401
99 }))
100 } else {
101 done(null, at)
102 }
103 } else {
104 done()
105 }
106 },
107 random: function (done) {
108 if (token.indexOf('.') === -1) {
109 request
110 .post(options.issuer + '/token/verify')
111 .auth(options.client_id, options.client_secret)
112 .set('Content-Type', 'application/json')
113 .send({ access_token: token })
114 .end(function (err, response) {
115 // superagent error
116 if (err) {
117 return done(err)
118 }
119
120 // Forbidden client or invalid access token
121 if (response.body && response.body.error) {
122 done(new UnauthorizedError(response.body))
123 } else {
124 done(null, response.body)
125 }
126 })
127 } else {
128 done()
129 }
130 }
131 }, function (err, result) {
132 if (err) { return callback(err) }
133
134 var claims = result.random || result.jwt.payload
135 var issuer = options.issuer
136 var clients = options.clients
137 var scope = options.scope
138
139 // mismatching issuer
140 if (claims.iss !== issuer) {
141 return callback(new UnauthorizedError({
142 error: 'invalid_token',
143 error_description: 'Mismatching issuer',
144 statusCode: 403
145 }))
146 }
147
148 // mismatching audience
149 if (clients && clients.indexOf(claims.aud) === -1) {
150 return callback(new UnauthorizedError({
151 error: 'invalid_token',
152 error_description: 'Mismatching audience',
153 statusCode: 403
154 }))
155 }
156
157 // expired token
158 if (claims.exp < nowSeconds()) {
159 return callback(new UnauthorizedError({
160 error: 'invalid_token',
161 error_description: 'Expired access token',
162 statusCode: 403
163 }))
164 }
165
166 // insufficient scope
167 if (scope && claims.scope.indexOf(scope) === -1) {
168 return callback(new UnauthorizedError({
169 error: 'insufficient_scope',
170 error_description: 'Insufficient scope',
171 statusCode: 403
172 }))
173 }
174
175 callback(null, claims)
176 })
177}
178
179/**
180 * Export
181 */
182module.exports = AccessToken