UNPKG

anvil-connect-nodejs

Version:
687 lines (496 loc) 17.6 kB
# Test dependencies fs = require 'fs' cwd = process.cwd() path = require 'path' chai = require 'chai' sinon = require 'sinon' sinonChai = require 'sinon-chai' nock = require 'nock' expect = chai.expect # Assertions chai.use sinonChai chai.should() # Code under test AccessToken = require path.join cwd, 'lib/AccessToken' JWT = require 'anvil-connect-jwt' base64url = require 'base64url' nowSeconds = require(path.join cwd, 'lib/time-utils').nowSeconds pem2jwk = require('pem-jwk').pem2jwk # Keypair publicFile = path.join cwd, 'test/lib/keys/public.pem' publicKey = fs.readFileSync(publicFile).toString('ascii') privateFile = path.join cwd, 'test/lib/keys/private.pem' privateKey = fs.readFileSync(privateFile).toString('ascii') jwk = pem2jwk(publicKey) describe 'Access Token', -> it 'should be a subclass of JWT', -> AccessToken.super.should.equal JWT describe 'header', -> it 'must not use "none" as "alg" value', -> expect(-> new AccessToken({}, { alg: 'none' })).to.throw Error it 'should not use "x5u", "x5c", "jku", or "jwk" header parameter fields', -> header = alg: 'RS256' x5u: 'x5u' x5c: 'x5c' jku: 'jku' jwk: 'jwk' payload = jti: 'random' iss: 'http://anvil.io' sub: 'uuid' aud: 'uuid' exp: Date.now() iat: Date.now() scope: 'openid profile' token = new AccessToken payload, header expect(token.header.x5u).to.be.undefined expect(token.header.x5c).to.be.undefined expect(token.header.jku).to.be.undefined expect(token.header.jwk).to.be.undefined describe 'claims', -> it 'should require "jti" property', -> AccessToken.registeredClaims.jti.required.should.be.true it 'should require "iss" Issuer Identifier', -> AccessToken.registeredClaims.iss.required.should.be.true it 'should require "iat" Issued time', -> AccessToken.registeredClaims.iat.required.should.be.true it 'should require "exp" Expiration time', -> AccessToken.registeredClaims.exp.required.should.be.true it 'should require "sub" Subject Identifier', -> AccessToken.registeredClaims.sub.required.should.be.true it 'should require "aud" Audience Identifier', -> AccessToken.registeredClaims.aud.required.should.be.true it 'should require "scope" property', -> AccessToken.registeredClaims.scope.required.should.be.true describe 'refresh', -> {err,token} = {} describe 'without refresh_token', -> it 'should provide an error', -> AccessToken.refresh null, {client_id:'fakeid', client_secret:'fakesecret'}, (err) -> err.should.be.instanceof Error describe 'without client_id', -> it 'should provide an error', -> AccessToken.refresh 'faketoken', {client_secret:'fakesecret'}, (err) -> err.should.be.instanceof Error describe 'without client_secret', -> it 'should provide an error', -> AccessToken.refresh 'faketoken', {client_id:'fakeid'}, (err) -> err.should.be.instanceof Error describe 'with all params and response ko', -> before (done) -> issuer = 'https://connect.anvil.io' options = issuer: issuer client_id: 'uuid2' client_secret: 'secret' headers = reqHeaders: 'Content-Type': 'application/json' nock(issuer, headers) .post('/token', { refresh_token: 'faketoken', grant_type:'refresh_token'}) .basicAuth({ user: options.client_id pass: options.client_secret }) .reply(400, { error: 'invalid_request' }) AccessToken.refresh 'faketoken', options, (error, accesstoken) -> err = error token = accesstoken done() after () -> nock.cleanAll() it 'should not provide an error', -> expect(err).to.be.ok it 'should provide a decoded token', -> expect(token).not.to.be.ok describe 'with all params and response ok', -> tokens = access_token: 'foo.bar.baz' refresh_token: 'foo' before (done) -> issuer = 'https://connect.anvil.io' options = issuer: issuer client_id: 'uuid2' client_secret: 'secret' headers = reqHeaders: 'Content-Type': 'application/json' nock(issuer, headers) .post('/token', { refresh_token: 'faketoken', grant_type:'refresh_token'}) .basicAuth({ user: options.client_id pass: options.client_secret }) .reply(200, tokens) AccessToken.refresh 'faketoken', options, (error, accesstoken) -> err = error token = accesstoken done() after () -> nock.cleanAll() it 'should not provide an error', -> expect(err).to.not.be.ok it 'should provide a decoded token', -> token.should.eql tokens describe 'verify', -> {err,token} = {} describe 'with unverifiable JWT', -> it 'should provide an error', -> AccessToken.verify 'mal.formed.jwt', { key: jwk }, (err) -> err.should.be.instanceof Error describe 'with verifiable JWT and mismatching issuer', -> before (done) -> payload = jti: 'random' iss: 'https://WRONG.authorization.server' sub: 'uuid' aud: 'https://some.client.app' exp: Date.now() iat: Date.now() scope: 'openid profile' jwt = (new AccessToken payload).encode(privateKey) options = issuer: 'https://your.authorization.server' key: publicKey AccessToken.verify jwt, options, (error, accesstoken) -> err = error token = accesstoken done() it 'should provide an error', -> expect(err).to.be.instanceof Error it 'should provide an error description', -> err.error_description.should.equal 'Mismatching issuer' it 'should not provide a decoded token', -> expect(token).to.be.undefined describe 'with verifiable JWT and mismatching audience', -> before (done) -> payload = jti: 'random' iss: 'http://your.authorization.server' sub: 'uuid' aud: 'https://WRONG.client.app' exp: Date.now() iat: Date.now() scope: 'openid profile' jwt = (new AccessToken payload).encode(privateKey) options = jti: 'random' issuer: payload.iss clients: ['https://client.app'] key: jwk AccessToken.verify jwt, options, (error, accesstoken) -> err = error token = accesstoken done() it 'should provide an error', -> expect(err).to.be.instanceof Error it 'should provide an error description', -> err.error_description.should.equal 'Mismatching audience' it 'should not provide a decoded token', -> expect(token).to.be.undefined describe 'with verifiable JWT and expired token', -> before (done) -> payload = jti: 'random' iss: 'https://your.authorization.server' sub: 'uuid' exp: nowSeconds(-1000) iat: Date.now() aud: 'https://your.client.app' scope: 'openid profile' jwt = (new AccessToken payload).encode(privateKey) options = issuer: payload.iss aud: payload.aud key: publicKey AccessToken.verify jwt, options, (error, accesstoken) -> err = error token = accesstoken done() it 'should provide an error', -> expect(err).to.be.instanceof Error it 'should provide an error description', -> err.error_description.should.equal 'Expired access token' it 'should not provide a decoded token', -> expect(token).to.be.undefined describe 'with verifiable JWT and insufficient scope', -> before (done) -> payload = jti: 'random' iss: 'https://your.authorization.server' sub: 'uuid' exp: Date.now() iat: Date.now() aud: 'https://your.client.app' scope: 'openid profile' jwt = (new AccessToken payload).encode(privateKey) options = issuer: payload.iss aud: payload.aud key: publicKey scope: 'realm' AccessToken.verify jwt, options, (error, accesstoken) -> err = error token = accesstoken done() it 'should provide an error', -> expect(err).to.be.instanceof Error it 'should provide an error description', -> err.error_description.should.equal 'Insufficient scope' it 'should not provide a decoded token', -> expect(token).to.be.undefined describe 'with verifiable JWT and valid payload', -> {payload,claims,err} = {} before (done) -> payload = jti: 'random' iss: 'https://your.authorization.server' sub: 'uuid' exp: Date.now() iat: Date.now() aud: 'https://your.client.app' scope: 'openid profile' jwt = (new AccessToken payload).encode(privateKey) options = issuer: payload.iss aud: payload.aud key: publicKey AccessToken.verify jwt, options, (error, accesstoken) -> err = error token = accesstoken done() it 'should not provide an error', -> expect(err).not.to.be.ok it 'should provide a decoded token', -> token.jti.should.equal payload.jti token.iss.should.equal payload.iss token.sub.should.equal payload.sub token.exp.should.be.a.number token.iat.should.be.a.number token.aud.should.equal payload.aud token.scope.should.equal payload.scope describe 'with unverifiable random token', -> before (done) -> issuer = 'https://connect.anvil.io' claims = jti: 'random' iss: issuer sub: 'uuid' exp: Date.now() iat: Date.now() aud: 'https://your.client.app' scope: 'openid profile' options = issuer: issuer client_id: 'uuid2' client_secret: 'secret' headers = reqHeaders: 'Content-Type': 'application/json' nock(issuer, headers) .post('/token/verify', { access_token: claims.jti }) .basicAuth({ user: options.client_id pass: options.client_secret }) .reply(401, 'Unauthorized') AccessToken.verify claims.jti, options, (error, accesstoken) -> err = error token = accesstoken done() after -> nock.cleanAll() it 'should provide an error', -> expect(err).to.be.instanceof Error it 'should provide an error description', -> err.message.should.equal 'Unauthorized' it 'should not provide a decoded token', -> expect(token).to.not.be.ok describe 'with verifiable random token and mismatching issuer', -> before (done) -> issuer = 'https://connect.anvil.io' claims = jti: 'random' iss: 'https://WRONG.anvil.io' sub: 'uuid' exp: Date.now() iat: Date.now() aud: 'https://your.client.app' scope: 'openid profile' options = issuer: issuer client_id: 'uuid2' client_secret: 'secret' headers = reqHeaders: 'Content-Type': 'application/json' nock(issuer, headers) .post('/token/verify', { access_token: claims.jti }) .basicAuth({ user: options.client_id pass: options.client_secret }) .reply(200, claims) AccessToken.verify claims.jti, options, (error, accesstoken) -> err = error token = accesstoken done() after -> nock.cleanAll() it 'should provide an error', -> expect(err).to.be.instanceof Error it 'should provide an error description', -> err.error_description.should.equal 'Mismatching issuer' it 'should not provide a decoded token', -> expect(token).to.not.be.ok describe 'with verifiable random token and mismatching audience', -> before (done) -> issuer = 'https://connect.anvil.io' claims = jti: 'random' iss: issuer sub: 'uuid' exp: Date.now() iat: Date.now() aud: 'https://your.client.app' scope: 'openid profile' options = issuer: issuer client_id: 'uuid2' client_secret: 'secret' clients: 'https://not.client.app' headers = reqHeaders: 'Content-Type': 'application/json' nock(issuer, headers) .post('/token/verify', { access_token: claims.jti }) .basicAuth({ user: options.client_id pass: options.client_secret }) .reply(200, claims) AccessToken.verify claims.jti, options, (error, accesstoken) -> err = error token = accesstoken done() after -> nock.cleanAll() it 'should provide an error', -> expect(err).to.be.instanceof Error it 'should provide an error description', -> err.error_description.should.equal 'Mismatching audience' it 'should not provide a decoded token', -> expect(token).to.not.be.ok describe 'with verifiable random token and expired token', -> before (done) -> issuer = 'https://connect.anvil.io' claims = jti: 'random' iss: issuer sub: 'uuid' exp: nowSeconds(-1000) iat: Date.now() aud: 'https://your.client.app' scope: 'openid profile' options = issuer: issuer client_id: 'uuid2' client_secret: 'secret' headers = reqHeaders: 'Content-Type': 'application/json' nock(issuer, headers) .post('/token/verify', { access_token: claims.jti }) .basicAuth({ user: options.client_id pass: options.client_secret }) .reply(200, claims) AccessToken.verify claims.jti, options, (error, accesstoken) -> err = error token = accesstoken done() after -> nock.cleanAll() it 'should provide an error', -> expect(err).to.be.instanceof Error it 'should provide an error description', -> err.error_description.should.equal 'Expired access token' it 'should not provide a decoded token', -> expect(token).to.not.be.ok describe 'with verifiable random token and insufficient scope', -> before (done) -> issuer = 'https://connect.anvil.io' claims = jti: 'random' iss: issuer sub: 'uuid' exp: Date.now() iat: Date.now() aud: 'https://your.client.app' scope: 'openid profile' options = issuer: issuer client_id: 'uuid2' client_secret: 'secret' scope: 'realm' headers = reqHeaders: 'Content-Type': 'application/json' nock(issuer, headers) .post('/token/verify', { access_token: claims.jti }) .basicAuth({ user: options.client_id pass: options.client_secret }) .reply(200, claims) AccessToken.verify claims.jti, options, (error, accesstoken) -> err = error token = accesstoken done() after -> nock.cleanAll() it 'should provide an error', -> expect(err).to.be.instanceof Error it 'should provide an error description', -> err.error_description.should.equal 'Insufficient scope' it 'should not provide a decoded token', -> expect(token).to.not.be.ok describe 'with verifiable random token and valid payload', -> {claims} = {} before (done) -> issuer = 'https://connect.anvil.io' claims = jti: 'random' iss: issuer sub: 'uuid' exp: Date.now() iat: Date.now() aud: 'https://your.client.app' scope: 'openid profile' options = issuer: issuer client_id: 'uuid2' client_secret: 'secret' headers = reqHeaders: 'Content-Type': 'application/json' nock(issuer, headers) .post('/token/verify', { access_token: claims.jti }) .basicAuth({ user: options.client_id pass: options.client_secret }) .reply(200, claims) AccessToken.verify claims.jti, options, (error, accesstoken) -> err = error token = accesstoken done() it 'should not provide an error', -> expect(err).to.not.be.ok it 'should provide a decoded token', -> token.should.eql claims