import { Eltro as t, assert} from 'eltro' import encode from '../../api/jwt/encode.mjs' import decode from '../../api/jwt/decode.mjs' import defaults from '../../api/defaults.mjs' const unixNow = Math.floor(Date.now() / 1000) const jwtHeader = { typ: 'JWT', alg: 'HS256', kid: '2' } const jwtBody = { aud: 'https://host/oauth/token', iss: 'test@test.com', iat: unixNow, exp: unixNow + 600, scope: ['http://stuff', 'http://stuff2'] } const pubKeys = { 'test@test.com': { 'default@HS256': 'sharedkey', '2@HS256': 'sharedkey', '2@HS384': 'sharedkey', '2@HS512': 'sharedkey', '5@HS256': 'wrongkey' }, 'test@custom.com': { '2@HS256': { key: 'sharedkey', expiresSkew: 600, expiresMax: 86400 }, '3@HS256': { }, '4@HS256': [], '5@HS256': { key: 'sharedkey' }, }, } t.describe('encode/decode', function() { ['HS256', 'HS384', 'HS512'].forEach(function(algo) { t.test('success with ' + algo, function() { let customJwtHeader = defaults(jwtHeader) customJwtHeader.kid = '2' customJwtHeader.alg = algo let jwt = encode(customJwtHeader, jwtBody, 'sharedkey') let decodedJwtBody = decode(jwt, pubKeys, [ 'https://host/oauth/token' ]) assert.deepStrictEqual(jwtBody, decodedJwtBody) }) }) t.test('success without kid', function() { let customJwtHeader = defaults(jwtHeader) delete customJwtHeader.kid let jwt = encode(customJwtHeader, jwtBody, 'sharedkey') let decodedJwtBody = decode(jwt, pubKeys, [ 'https://host/oauth/token' ]) assert.deepStrictEqual(jwtBody, decodedJwtBody) }) t.test('success without iat or iss', function() { let customJwtBody = defaults(jwtBody) delete customJwtBody.iat delete customJwtBody.exp let jwt = encode(jwtHeader, customJwtBody, 'sharedkey') let decodedJwtBody = decode(jwt, pubKeys, [ 'https://host/oauth/token' ]) assert.ok(decodedJwtBody.iat) assert.ok(decodedJwtBody.iat >= unixNow) assert.ok(decodedJwtBody.iat < unixNow + 2) assert.ok(decodedJwtBody.exp) assert.ok(decodedJwtBody.exp >= unixNow) assert.ok(decodedJwtBody.exp > unixNow + 200) assert.ok(decodedJwtBody.exp < unixNow + 400) }) t.test('success with object key', function() { let customJwtBody = defaults(jwtBody) customJwtBody.kid = '5' customJwtBody.iss = 'test@custom.com' let jwt = encode(jwtHeader, jwtBody, 'sharedkey') let decodedJwtBody = decode(jwt, pubKeys, [ 'https://host/oauth/token' ]) assert.deepStrictEqual(jwtBody, decodedJwtBody) }) t.test('success with array aud', function() { let customJwtBody = defaults(jwtBody) customJwtBody.aud = [ 'https://myhost/oauth/token', 'https://host/oauth/token' ] let jwt = encode(jwtHeader, customJwtBody, 'sharedkey') let decodedJwtBody = decode(jwt, pubKeys, [ 'https://host/oauth/token' ]) assert.deepStrictEqual(customJwtBody, decodedJwtBody) }) t.test('success with no aud and empty audience list', function() { let customJwtBody = defaults(jwtBody) delete customJwtBody.aud let jwt = encode(jwtHeader, customJwtBody, 'sharedkey') let decodedJwtBody = decode(jwt, pubKeys, []) assert.deepStrictEqual(customJwtBody, decodedJwtBody) }) t.test('success with expired token', function() { let customJwtBody = defaults(jwtBody) customJwtBody.iss = 'test@custom.com' customJwtBody.exp -= 600 let jwt = encode(jwtHeader, customJwtBody, 'sharedkey') let decodedJwtBody = decode(jwt, pubKeys, [ 'https://host/oauth/token' ]) assert.deepStrictEqual(customJwtBody, decodedJwtBody) }) t.test('token outside maximum expires', function() { let customJwtBody = defaults(jwtBody) customJwtBody.iss = 'test@custom.com' customJwtBody.exp += 172800 let jwt = encode(jwtHeader, customJwtBody, 'sharedkey') assert.throws( function() { decode(jwt, pubKeys, ['https://host/oauth/token']) }, /Expires in the future by more than 86400 seconds/ ) }) t.test('token outside maximum expires using decode options', function() { let customJwtBody = defaults(jwtBody) customJwtBody.exp += 172800 let jwt = encode(jwtHeader, customJwtBody, 'sharedkey') assert.throws( function() { decode(jwt, pubKeys, ['https://host/oauth/token'], { expiresMax: 600 }) }, /Expires in the future by more than 600 seconds/ ) }) t.test('token outside maximum expires using nbf', function() { let customJwtBody = defaults(jwtBody) customJwtBody.exp += 172800 customJwtBody.nbf = customJwtBody.iat delete customJwtBody.iat let jwt = encode(jwtHeader, customJwtBody, 'sharedkey') assert.throws( function() { decode(jwt, pubKeys, ['https://host/oauth/token'], { expiresMax: 600 }) }, /Expires in the future by more than 600 seconds/ ) }) t.test('token outside maximum expires using unixNow', function() { let customJwtBody = defaults(jwtBody) customJwtBody.exp += 172800 delete customJwtBody.iat let jwt = encode(jwtHeader, customJwtBody, 'sharedkey') assert.throws( function() { decode(jwt, pubKeys, ['https://host/oauth/token'], { expiresMax: 600 }) }, /Expires in the future by more than 600 seconds/ ) }) t.test('unknown aud', function() { let jwt = encode(jwtHeader, jwtBody, 'sharedkey') assert.throws( function() { decode(jwt, pubKeys, ['https://myhost/oauth/token']) }, /Unknown audience 'https:\/\/host\/oauth\/token'/ ) }) t.test('expired', function() { let customJwtBody = defaults(jwtBody) customJwtBody.iat -= 1200 customJwtBody.exp -= 800 let jwt = encode(jwtHeader, customJwtBody, 'sharedkey') assert.throws( function() { decode(jwt, pubKeys, ['https://host/oauth/token']) }, /Token has expired/ ) }) t.test('invalid', function() { assert.throws( function() { decode('asdfasdgassdga.asdfasdg.sadfsadfas', pubKeys, ['https://host/oauth/token']) }, /Token was invalid/ ) }) t.test('missing exp', function() { let customJwtBody = defaults(jwtBody) delete customJwtBody.exp let jwt = encode(jwtHeader, customJwtBody, 'sharedkey', true) assert.throws( function() { decode(jwt, pubKeys, ['https://host/oauth/token']) }, /No expires set on token/ ) }) t.test('missing iss', function() { let customJwtBody = defaults(jwtBody) delete customJwtBody.iss let jwt = encode(jwtHeader, customJwtBody, 'sharedkey') assert.throws( function() { decode(jwt, pubKeys, ['https://host/oauth/token']) }, /No issuer set/ ) }) t.test('iat invalid', function() { let customJwtBody = defaults(jwtBody) customJwtBody.iat += 1200 let jwt = encode(jwtHeader, customJwtBody, 'sharedkey') assert.throws( function() { decode(jwt, pubKeys, ['https://host/oauth/token']) }, /Issued at in the future by more than 300 seconds/ ) }) t.test('nbf invalid', function() { let customJwtBody = defaults(jwtBody) customJwtBody.nbf = customJwtBody.iat + 1200 let jwt = encode(jwtHeader, customJwtBody, 'sharedkey') assert.throws( function() { decode(jwt, pubKeys, ['https://host/oauth/token']) }, /Not before in the future by more than 300 seconds/ ) }) t.test('unknown issuer', function() { let customJwtBody = defaults(jwtBody) customJwtBody.iss = 'unknown@test.com' let jwt = encode(jwtHeader, customJwtBody, 'sharedkey') assert.throws( function() { decode(jwt, pubKeys, ['https://host/oauth/token']) }, /Unknown issuer 'unknown@test.com'/ ) }) t.test('wrong alg', function() { let customJwtHeader = defaults(jwtHeader) customJwtHeader.alg = 'HS128' assert.throws( function() { encode(customJwtHeader, jwtBody, 'sharedkey') }, /Only alg HS256, HS384 and HS512 are supported/ ) }) t.test('unknown kid', function() { let customJwtHeader = defaults(jwtHeader) customJwtHeader.kid = '3' let jwt = encode(customJwtHeader, jwtBody, 'sharedkey') assert.throws( function() { decode(jwt, pubKeys, ['https://host/oauth/token']) }, /Unknown pubkey id '3'/ ) }) t.test('invalid signature', function() { let customJwtHeader = defaults(jwtHeader) customJwtHeader.kid = '2' let jwt = encode(customJwtHeader, jwtBody, 'sharedkey') let backup = jwt if (jwt[jwt.length - 2] === 'A') { jwt = jwt.slice(0, -2) + 'BB' } else { jwt = jwt.slice(0, -2) + 'AA' } try { assert.throws( function() { decode(jwt, pubKeys, ['https://host/oauth/token']) }, /Verification failed with alg 'HS256'/ ) } catch (err) { console.log('-----') console.log(backup) console.log(jwt) throw err } jwt = encode(customJwtHeader, jwtBody, 'sharedkey') jwt += 'a' assert.throws( function() { decode(jwt, pubKeys, ['https://host/oauth/token']) }, /Verification failed with alg 'HS256'/ ) }) t.test('invalid shared key', function() { let customJwtHeader = defaults(jwtHeader) customJwtHeader.kid = '5' customJwtHeader.alg = 'HS256' let jwt = encode(customJwtHeader, jwtBody, 'sharedkey') assert.throws( function() { decode(jwt, pubKeys, ['https://host/oauth/token']) }, /Verification failed with alg 'HS256'/ ) }) t.test('invalid pubkey', function() { let customJwtHeader = defaults(jwtHeader) customJwtHeader.kid = '4' let jwt = encode(customJwtHeader, jwtBody, 'sharedkey') assert.throws( function() { decode(jwt, pubKeys, ['https://host/oauth/token']) } ) let customJwtBody = defaults(jwtBody) customJwtBody.iss = 'test@custom.com' customJwtHeader.kid = '3' jwt = encode(customJwtHeader, customJwtBody, 'sharedkey') assert.throws( function() { decode(jwt, pubKeys, ['https://host/oauth/token']) }, /'3'.+misconfigured/ ) customJwtHeader.kid = '4' jwt = encode(customJwtHeader, customJwtBody, 'sharedkey') assert.throws( function() { decode(jwt, pubKeys, ['https://host/oauth/token']) }, /'4'.+misconfigured/ ) }) t.test('success with broken token', function() { let expectedJwtBody = { id: 1, exp: 1519802991, iat: 1519802691, iss: 'test@test.com', aud: 'https://host/oauth/token' } let decodedJwtBody = decode( 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiaWF0IjoxNTE5ODAyNjkxfQ.p6t378Ri2JpOCm9WtC36ttyH8ILzG9-OWT_kgMrrRfo', pubKeys, ['https://host/oauth/token'], { fixup: (header, body) => { header.kid = '2' body.iss = 'test@test.com' body.aud = 'https://host/oauth/token' body.exp = body.iat + 300 }, expiresSkew: 307584000 } ) assert.deepStrictEqual(decodedJwtBody, expectedJwtBody) }) })