From 3a64b86fc3c11b1ebfa521cef8e3a3dc61a9653c Mon Sep 17 00:00:00 2001 From: Samuel Kopp <62482066+boywithkeyboard@users.noreply.github.com> Date: Fri, 18 Aug 2023 16:15:57 +0200 Subject: [PATCH] refactor(jwt): accept secret as first arg --- jwt.ts | 39 +++++++++++++++++++++++---------------- test/jwt.test.ts | 37 +++++++++++++++++++++++++++++++++++++ x/x.test.tsx | 2 ++ 3 files changed, 62 insertions(+), 16 deletions(-) create mode 100644 test/jwt.test.ts diff --git a/jwt.ts b/jwt.ts index 1bebdb8..4265e3a 100644 --- a/jwt.ts +++ b/jwt.ts @@ -3,13 +3,8 @@ import { decode, encode, } from 'https://deno.land/std@0.198.0/encoding/base64.ts' -import { - create, - getNumericDate, - Payload as JwtPayload, - verify as _verify, - VerifyOptions, -} from 'https://deno.land/x/djwt@v2.8/mod.ts' +import * as Jwt from 'https://deno.land/x/djwt@v2.8/mod.ts' +import { Context } from './mod.ts' interface Payload { iss?: string @@ -55,16 +50,22 @@ export function importKey(key: string) { */ // deno-lint-ignore ban-types export async function sign = {}>( + secret: string | CryptoKey | Context, payload: T & Payload, - secret: string | CryptoKey, ) { - const key = typeof secret === 'string' ? await importKey(secret) : secret + const key = typeof secret === 'string' + ? await importKey(secret) + : secret instanceof Context + ? await importKey( + (secret.env('jwt_secret') ?? secret.env('JWT_SECRET')) as string, + ) + : secret const { exp, nbf, ...rest } = payload - return await create({ alg: 'HS512', typ: 'JWT' }, { - ...(exp && { exp: getNumericDate(exp) }), - ...(nbf && { nbf: getNumericDate(nbf) }), + return await Jwt.create({ alg: 'HS512', typ: 'JWT' }, { + ...(exp && { exp: Jwt.getNumericDate(exp) }), + ...(nbf && { nbf: Jwt.getNumericDate(nbf) }), ...rest, }, key) } @@ -73,14 +74,20 @@ export async function sign = {}>( * Verify the validity of a JWT. */ export async function verify = Payload>( + secret: string | CryptoKey | Context, token: string, - secret: string | CryptoKey, - options?: VerifyOptions, + options?: Jwt.VerifyOptions, ) { try { - const key = typeof secret === 'string' ? await importKey(secret) : secret + const key = typeof secret === 'string' + ? await importKey(secret) + : secret instanceof Context + ? await importKey( + (secret.env('jwt_secret') ?? secret.env('JWT_SECRET')) as string, + ) + : secret - return await _verify(token, key, options) as JwtPayload & T + return await Jwt.verify(token, key, options) as Jwt.Payload & T } catch (_err) { return } diff --git a/test/jwt.test.ts b/test/jwt.test.ts new file mode 100644 index 0000000..e60145b --- /dev/null +++ b/test/jwt.test.ts @@ -0,0 +1,37 @@ +// Copyright 2023 Samuel Kopp. All rights reserved. Apache-2.0 license. +import { cheetah } from '../cheetah.ts' +import { createKey, importKey } from '../jwt.ts' +import { jwt } from '../mod.ts' +import { assertEquals, assertInstanceOf } from '../test/deps.ts' + +Deno.test('jwt', async () => { + const key = await createKey() + const cryptoKey = await importKey(key) + + assertInstanceOf(cryptoKey, CryptoKey) + + const token = await jwt.sign(key, { example: 'object' }) + + assertEquals(await jwt.verify(await createKey(), token) === undefined, true) + assertEquals(await jwt.verify(key, token) !== undefined, true) + assertEquals(await jwt.verify(cryptoKey, token) !== undefined, true) + + Deno.env.set('jwt_secret', key) + + const app = new cheetah() + + app.get('/one', async (c) => { + assertEquals(await jwt.verify(c, token) !== undefined, true) + }) + + await app.fetch(new Request('http://localhost/one')) + + Deno.env.delete('jwt_secret') + Deno.env.set('JWT_SECRET', key) + + app.get('/two', async (c) => { + assertEquals(await jwt.verify(c, token) !== undefined, true) + }) + + await app.fetch(new Request('http://localhost/two')) +}) diff --git a/x/x.test.tsx b/x/x.test.tsx index b595ad8..7fed67c 100644 --- a/x/x.test.tsx +++ b/x/x.test.tsx @@ -5,6 +5,8 @@ import { assertEquals, assertInstanceOf } from '../test/deps.ts' import { h, jsx } from './jsx.tsx' import { createKey, importKey, sign, verify } from './jwt.ts' +// TODO delete at v2.0 + Deno.test('x', async (t) => { await t.step('jwt', async () => { const key = await createKey()