UNPKG

anvil-connect-nodejs

Version:
184 lines (165 loc) 5.07 kB
/** * Module dependencies */ var JWT = require('anvil-connect-jwt') var async = require('async') var request = require('superagent') var UnauthorizedError = require('../errors/UnauthorizedError') var nowSeconds = require('./time-utils.js').nowSeconds /** * AccessToken */ var AccessToken = JWT.define({ // default header header: { alg: 'RS256' }, headers: [ 'alg' ], // modify header schema registeredHeaders: { alg: { format: 'StringOrURI', required: true, enum: ['RS256'] } }, // permitted claims claims: ['jti', 'iss', 'sub', 'aud', 'exp', 'iat', 'scope'], // modify payload schema registeredClaims: { jti: { format: 'String', required: true }, iss: { format: 'URI', required: true }, iat: { format: 'IntDate', required: true }, exp: { format: 'IntDate', required: true }, sub: { format: 'String', required: true }, aud: { format: 'String', required: true }, scope: { format: 'String', required: true } } }) /** * Sends an OIDC refresh token request. * @method refresh */ AccessToken.refresh = function (refreshToken, options, callback) { if (!options.client_id || !options.client_secret || !refreshToken) { return callback(new UnauthorizedError('Missing client credentials')) } request .post(options.issuer + '/token') .auth(options.client_id, options.client_secret) .set('Content-Type', 'application/json') .send({ refresh_token: refreshToken, grant_type: 'refresh_token' }) .end(function (err, response) { // superagent error if (err) { return callback(err) } // Forbidden client or invalid access token if (response.body && response.body.error) { return callback(new UnauthorizedError(response.body)) } else { return callback(null, response.body) } }) } /** * Verifies an OIDC access token (makes calls to /token/verify endpoint, etc). * Used by `client.token()`, `client.refresh()` and `client.verify()`. * @method verify * @param token {String} JWT AccessToken for OpenID Connect (base64 encoded) * @param options {Object} Options hashmap * @param options.client_id {String} * @param options.client_secret {String} * @param options.clients * @param options.issuer {String} * @param options.key {String|JWK} Token secret. Either a string signature (in * which case, use JWA to verify), or a JWK object (use JWS to verify) * @param options.scope * @param callback {Function} * @throws {UnauthorizedError} HTTP 401 or 403 errors */ AccessToken.verify = function (token, options, callback) { async.parallel({ jwt: function (done) { if (token.indexOf('.') !== -1) { var at = AccessToken.decode(token, options.key) if (!at || at instanceof Error) { done(new UnauthorizedError({ realm: 'user', error: 'invalid_token', error_description: 'Invalid access token', statusCode: 401 })) } else { done(null, at) } } else { done() } }, random: function (done) { if (token.indexOf('.') === -1) { request .post(options.issuer + '/token/verify') .auth(options.client_id, options.client_secret) .set('Content-Type', 'application/json') .send({ access_token: token }) .end(function (err, response) { // superagent error if (err) { return done(err) } // Forbidden client or invalid access token if (response.body && response.body.error) { done(new UnauthorizedError(response.body)) } else { done(null, response.body) } }) } else { done() } } }, function (err, result) { if (err) { return callback(err) } var claims = result.random || result.jwt.payload var issuer = options.issuer var clients = options.clients var scope = options.scope // mismatching issuer if (claims.iss !== issuer) { return callback(new UnauthorizedError({ error: 'invalid_token', error_description: 'Mismatching issuer', statusCode: 403 })) } // mismatching audience if (clients && clients.indexOf(claims.aud) === -1) { return callback(new UnauthorizedError({ error: 'invalid_token', error_description: 'Mismatching audience', statusCode: 403 })) } // expired token if (claims.exp < nowSeconds()) { return callback(new UnauthorizedError({ error: 'invalid_token', error_description: 'Expired access token', statusCode: 403 })) } // insufficient scope if (scope && claims.scope.indexOf(scope) === -1) { return callback(new UnauthorizedError({ error: 'insufficient_scope', error_description: 'Insufficient scope', statusCode: 403 })) } callback(null, claims) }) } /** * Export */ module.exports = AccessToken