diff --git a/src/JWT.ts b/src/JWT.ts index 39c0e1a8..8467e176 100644 --- a/src/JWT.ts +++ b/src/JWT.ts @@ -43,6 +43,7 @@ interface JWTPayload { iss?: string sub?: string aud?: string + iat?: number nbf?: number type?: string exp?: number @@ -152,19 +153,19 @@ export function decodeJWT(jwt: string): JWTDecoded { */ // export async function createJWT(payload, { issuer, signer, alg, expiresIn }) { export async function createJWT( - payload: object, + payload: any, { issuer, signer, alg, expiresIn }: JWTOptions ): Promise { if (!signer) throw new Error('No Signer functionality has been configured') if (!issuer) throw new Error('No issuing DID has been configured') const header: JWTHeader = { typ: 'JWT', alg: alg || defaultAlg } const timestamps: Partial = { - nbf: Math.floor(Date.now() / 1000), + iat: Math.floor(Date.now() / 1000), exp: undefined } - if (expiresIn) { + if (expiresIn && payload.nbf) { if (typeof expiresIn === 'number') { - timestamps.exp = timestamps.nbf + Math.floor(expiresIn) + timestamps.exp = payload.nbf + Math.floor(expiresIn) } else { throw new Error('JWT expiresIn is not a number') } diff --git a/src/__tests__/JWT-test.ts b/src/__tests__/JWT-test.ts index 7e417876..2a6a154a 100644 --- a/src/__tests__/JWT-test.ts +++ b/src/__tests__/JWT-test.ts @@ -107,7 +107,7 @@ describe('createJWT()', () => { it('creates a JWT with expiry in 10000 seconds', () => { return createJWT( - { requested: ['name', 'phone'] }, + { requested: ['name', 'phone'], nbf: Math.floor(new Date().getTime() / 1000) }, { issuer: did, signer, expiresIn: 10000 } ).then(jwt => { const { payload } = decodeJWT(jwt) @@ -115,6 +115,40 @@ describe('createJWT()', () => { }) }) + it('ignores expiresIn if nbf is not set', async () => { + const { payload } = decodeJWT(await createJWT( + { requested: ['name', 'phone'] }, + { issuer: did, signer, expiresIn: 10000 } + )) + return expect(payload.exp).toBeUndefined() + }) + + it('sets iat to the current time by default', async () => { + const timestamp = Math.floor(Date.now() / 1000) + const { payload } = decodeJWT(await createJWT( + { requested: ['name', 'phone'] }, + { issuer: did, signer } + )) + return expect(payload.iat).toEqual(timestamp) + }) + + it('sets iat to the value passed in payload', async () => { + const timestamp = 2000000 + const { payload } = decodeJWT(await createJWT( + { requested: ['name', 'phone'], iat: timestamp }, + { issuer: did, signer } + )) + return expect(payload.iat).toEqual(timestamp) + }) + + it('does not set iat if value in payload is undefined', async () => { + const { payload } = decodeJWT(await createJWT( + { requested: ['name', 'phone'], iat: undefined }, + { issuer: did, signer } + )) + return expect(payload.iat).toBeUndefined() + }) + it('throws an error if unsupported algorithm is passed in', () => { return createJWT( { requested: ['name', 'phone'] }, @@ -152,7 +186,7 @@ describe('createJWT()', () => { it('creates a JWT with expiry in 10000 seconds', () => { return createJWT( - { requested: ['name', 'phone'] }, + { requested: ['name', 'phone'], nbf: Math.floor(new Date().getTime() / 1000) }, { alg, issuer: did, signer, expiresIn: 10000 } ).then(jwt => { const { payload } = decodeJWT(jwt) diff --git a/src/__tests__/__snapshots__/JWT-test.ts.snap b/src/__tests__/__snapshots__/JWT-test.ts.snap index 7f1f567f..39c6e4d6 100644 --- a/src/__tests__/__snapshots__/JWT-test.ts.snap +++ b/src/__tests__/__snapshots__/JWT-test.ts.snap @@ -2,95 +2,96 @@ exports[`createJWT() ES256K creates a JWT with correct format 1`] = ` Object { - "data": "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJuYmYiOjE0ODUzMjExMzMsInJlcXVlc3RlZCI6WyJuYW1lIiwicGhvbmUiXSwiaXNzIjoiZGlkOnVwb3J0OjJuUXRpUUc2Q2dtMUdZVEJhYUtBZ3I3NnVZN2lTZXhVa3FYIn0", + "data": "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJpYXQiOjE0ODUzMjExMzMsInJlcXVlc3RlZCI6WyJuYW1lIiwicGhvbmUiXSwiaXNzIjoiZGlkOnVwb3J0OjJuUXRpUUc2Q2dtMUdZVEJhYUtBZ3I3NnVZN2lTZXhVa3FYIn0", "header": Object { "alg": "ES256K", "typ": "JWT", }, "payload": Object { + "iat": 1485321133, "iss": "did:uport:2nQtiQG6Cgm1GYTBaaKAgr76uY7iSexUkqX", - "nbf": 1485321133, "requested": Array [ "name", "phone", ], }, - "signature": "a0u5aiRRsxTj0-bVN_pfCl95izb2bDuCsMiODbwH_wrFBH6IcLCQRclByepu0Yu__0CWyw-TQfYuxxdx-wExYw", + "signature": "4zZKylwG3_1WPPd0HEpeOt1uUplyXhIGQ7G26wx0T1fnwovYcXQUA-FY3Cp13iA0XwMT8kIlxwsuMFCYMdw1sw", } `; exports[`createJWT() ES256K creates a JWT with correct legacy format 1`] = ` Object { - "data": "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJuYmYiOjE0ODUzMjExMzMsInJlcXVlc3RlZCI6WyJuYW1lIiwicGhvbmUiXSwiaXNzIjoiMm5RdGlRRzZDZ20xR1lUQmFhS0Fncjc2dVk3aVNleFVrcVgifQ", + "data": "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJpYXQiOjE0ODUzMjExMzMsInJlcXVlc3RlZCI6WyJuYW1lIiwicGhvbmUiXSwiaXNzIjoiMm5RdGlRRzZDZ20xR1lUQmFhS0Fncjc2dVk3aVNleFVrcVgifQ", "header": Object { "alg": "ES256K", "typ": "JWT", }, "payload": Object { + "iat": 1485321133, "iss": "2nQtiQG6Cgm1GYTBaaKAgr76uY7iSexUkqX", - "nbf": 1485321133, "requested": Array [ "name", "phone", ], }, - "signature": "YPk7tICPNhJwm251UAqLR9iskfKKVGloiAks1ndtdZv2SzRk2X9jN2LOxusJQUNoLlH1TkP0yiJb2nU4Rqrjag", + "signature": "D1Tx-R9g-pyGAb4C486fx_1Scwf74JcoUC2xYRJ6cZFW9zwBs_NZEZg3bptrMfmNRao_2A2cbCncRKPpV7TycQ", } `; exports[`createJWT() Ed25519 creates a JWT with correct format 1`] = ` Object { - "data": "eyJ0eXAiOiJKV1QiLCJhbGciOiJFZDI1NTE5In0.eyJuYmYiOjE0ODUzMjExMzMsInJlcXVlc3RlZCI6WyJuYW1lIiwicGhvbmUiXSwiaXNzIjoiZGlkOm5hY2w6QnZyQjhpSkF6XzFqZnExbVJ4aUVLZnI5cWNuTGZxNURPR3JCZjJFUlVIVSJ9", + "data": "eyJ0eXAiOiJKV1QiLCJhbGciOiJFZDI1NTE5In0.eyJpYXQiOjE0ODUzMjExMzMsInJlcXVlc3RlZCI6WyJuYW1lIiwicGhvbmUiXSwiaXNzIjoiZGlkOm5hY2w6QnZyQjhpSkF6XzFqZnExbVJ4aUVLZnI5cWNuTGZxNURPR3JCZjJFUlVIVSJ9", "header": Object { "alg": "Ed25519", "typ": "JWT", }, "payload": Object { + "iat": 1485321133, "iss": "did:nacl:BvrB8iJAz_1jfq1mRxiEKfr9qcnLfq5DOGrBf2ERUHU", - "nbf": 1485321133, "requested": Array [ "name", "phone", ], }, - "signature": "TVkTb7Copwq40dj9a8iorOi0yk_akAAW0VcsUk-XpzxGJiWMas8nXDwnxUvyPBSQLNGHu3teI0HSRBbzAiZAAg", + "signature": "ZoPf01SxW2n5zngunI942FpviEMP6jBZZb9NJ27M_K7AcmjPeeLH8bm2lv0INmJ2u98JVSzELF8YLWQvPYB1Bw", } `; exports[`verifyJWT() accepts a valid MNID audience 1`] = ` Object { "aud": "did:uport:2nQtiQG6Cgm1GYTBaaKAgr76uY7iSexUkqY", + "iat": 1485321133, "iss": "did:uport:2nQtiQG6Cgm1GYTBaaKAgr76uY7iSexUkqX", - "nbf": 1485321133, } `; exports[`verifyJWT() accepts a valid audience 1`] = ` Object { "aud": "did:uport:2nQtiQG6Cgm1GYTBaaKAgr76uY7iSexUkqY", + "iat": 1485321133, "iss": "did:uport:2nQtiQG6Cgm1GYTBaaKAgr76uY7iSexUkqX", - "nbf": 1485321133, } `; exports[`verifyJWT() accepts a valid audience using callback_url 1`] = ` Object { "aud": "http://pututu.uport.me/unique", + "iat": 1485321133, "iss": "did:uport:2nQtiQG6Cgm1GYTBaaKAgr76uY7iSexUkqX", - "nbf": 1485321133, } `; exports[`verifyJWT() accepts a valid exp 1`] = ` Object { "exp": 1485320834, + "iat": 1485321133, "iss": "did:uport:2nQtiQG6Cgm1GYTBaaKAgr76uY7iSexUkqX", - "nbf": 1485321133, } `; exports[`verifyJWT() accepts a valid nbf 1`] = ` Object { + "iat": 1485321133, "iss": "did:uport:2nQtiQG6Cgm1GYTBaaKAgr76uY7iSexUkqX", "nbf": 1485321433, } @@ -99,16 +100,16 @@ Object { exports[`verifyJWT() handles ES256K-R algorithm 1`] = ` Object { "hello": "world", + "iat": 1485321133, "iss": "did:uport:2nQtiQG6Cgm1GYTBaaKAgr76uY7iSexUkqX", - "nbf": 1485321133, } `; exports[`verifyJWT() handles ES256K-R algorithm with ethereum address 1`] = ` Object { "hello": "world", + "iat": 1485321133, "iss": "did:uport:2nQtiQG6Cgm1GYTBaaKAgr76uY7iSexUkqY", - "nbf": 1485321133, } `;