Skip to content

Commit

Permalink
feat(types): add optional Generics for JWT verify and decrypt
Browse files Browse the repository at this point in the history
BREAKING CHANGE: jwtVerify and jwtDecrypt type argument for the resolved
KeyLike type is now a second optional type argument following a type
for the JWT Claims Set (aka payload)

Resolves #568
  • Loading branch information
panva committed Oct 25, 2023
1 parent eddd400 commit 61bd2a0
Show file tree
Hide file tree
Showing 5 changed files with 68 additions and 20 deletions.
9 changes: 5 additions & 4 deletions src/jwt/decrypt.ts
@@ -1,5 +1,6 @@
import { compactDecrypt } from '../jwe/compact/decrypt.js'
import type {
JWTPayload,
KeyLike,
DecryptOptions,
JWTClaimVerificationOptions,
Expand Down Expand Up @@ -47,22 +48,22 @@ export interface JWTDecryptGetKey
* {@link https://github.com/panva/jose/issues/210#jwe-alg Algorithm Key Requirements}.
* @param options JWT Decryption and JWT Claims Set validation options.
*/
export async function jwtDecrypt(
export async function jwtDecrypt<PayloadType = JWTPayload>(
jwt: string | Uint8Array,
key: KeyLike | Uint8Array,
options?: JWTDecryptOptions,
): Promise<JWTDecryptResult>
): Promise<JWTDecryptResult<PayloadType>>
/**
* @param jwt JSON Web Token value (encoded as JWE).
* @param getKey Function resolving Private Key or Secret to decrypt and verify the JWT with. See
* {@link https://github.com/panva/jose/issues/210#jwe-alg Algorithm Key Requirements}.
* @param options JWT Decryption and JWT Claims Set validation options.
*/
export async function jwtDecrypt<KeyLikeType extends KeyLike = KeyLike>(
export async function jwtDecrypt<PayloadType = JWTPayload, KeyLikeType extends KeyLike = KeyLike>(
jwt: string | Uint8Array,
getKey: JWTDecryptGetKey,
options?: JWTDecryptOptions,
): Promise<JWTDecryptResult & ResolvedKey<KeyLikeType>>
): Promise<JWTDecryptResult<PayloadType> & ResolvedKey<KeyLikeType>>
export async function jwtDecrypt(
jwt: string | Uint8Array,
key: KeyLike | Uint8Array | JWTDecryptGetKey,
Expand Down
15 changes: 11 additions & 4 deletions src/jwt/unsecured.ts
Expand Up @@ -6,8 +6,8 @@ import { JWTInvalid } from '../util/errors.js'
import jwtPayload from '../lib/jwt_claims_set.js'
import { ProduceJWT } from './produce.js'

export interface UnsecuredResult {
payload: JWTPayload
export interface UnsecuredResult<PayloadType = JWTPayload> {
payload: PayloadType & JWTPayload
header: JWSHeaderParameters
}

Expand Down Expand Up @@ -53,7 +53,10 @@ export class UnsecuredJWT extends ProduceJWT {
* @param jwt Unsecured JWT to decode the payload of.
* @param options JWT Claims Set validation options.
*/
static decode(jwt: string, options?: JWTClaimVerificationOptions): UnsecuredResult {
static decode<PayloadType = JWTPayload>(
jwt: string,
options?: JWTClaimVerificationOptions,
): UnsecuredResult<PayloadType> {
if (typeof jwt !== 'string') {
throw new JWTInvalid('Unsecured JWT must be a string')
}
Expand All @@ -71,7 +74,11 @@ export class UnsecuredJWT extends ProduceJWT {
throw new JWTInvalid('Invalid Unsecured JWT')
}

const payload = jwtPayload(header, base64url.decode(encodedPayload), options)
const payload = jwtPayload(
header,
base64url.decode(encodedPayload),
options,
) as UnsecuredResult<PayloadType>['payload']

return { payload, header }
}
Expand Down
9 changes: 5 additions & 4 deletions src/jwt/verify.ts
@@ -1,5 +1,6 @@
import { compactVerify } from '../jws/compact/verify.js'
import type {
JWTPayload,
KeyLike,
VerifyOptions,
JWTClaimVerificationOptions,
Expand Down Expand Up @@ -98,11 +99,11 @@ export interface JWTVerifyGetKey extends GetKeyFunction<JWTHeaderParameters, Fla
* {@link https://github.com/panva/jose/issues/210#jws-alg Algorithm Key Requirements}.
* @param options JWT Decryption and JWT Claims Set validation options.
*/
export async function jwtVerify(
export async function jwtVerify<PayloadType = JWTPayload>(
jwt: string | Uint8Array,
key: KeyLike | Uint8Array,
options?: JWTVerifyOptions,
): Promise<JWTVerifyResult>
): Promise<JWTVerifyResult<PayloadType>>

/**
* @example Usage with a public JSON Web Key Set hosted on a remote URL
Expand All @@ -123,11 +124,11 @@ export async function jwtVerify(
* {@link https://github.com/panva/jose/issues/210#jws-alg Algorithm Key Requirements}.
* @param options JWT Decryption and JWT Claims Set validation options.
*/
export async function jwtVerify<KeyLikeType extends KeyLike = KeyLike>(
export async function jwtVerify<PayloadType = JWTPayload, KeyLikeType extends KeyLike = KeyLike>(
jwt: string | Uint8Array,
getKey: JWTVerifyGetKey,
options?: JWTVerifyOptions,
): Promise<JWTVerifyResult & ResolvedKey<KeyLikeType>>
): Promise<JWTVerifyResult<PayloadType> & ResolvedKey<KeyLikeType>>

export async function jwtVerify(
jwt: string | Uint8Array,
Expand Down
8 changes: 4 additions & 4 deletions src/types.d.ts
Expand Up @@ -590,17 +590,17 @@ export interface CompactVerifyResult {
protectedHeader: CompactJWSHeaderParameters
}

export interface JWTVerifyResult {
export interface JWTVerifyResult<PayloadType = JWTPayload> {
/** JWT Claims Set. */
payload: JWTPayload
payload: PayloadType & JWTPayload

/** JWS Protected Header. */
protectedHeader: JWTHeaderParameters
}

export interface JWTDecryptResult {
export interface JWTDecryptResult<PayloadType = JWTPayload> {
/** JWT Claims Set. */
payload: JWTPayload
payload: PayloadType & JWTPayload

/** JWE Protected Header. */
protectedHeader: CompactJWEHeaderParameters
Expand Down
47 changes: 43 additions & 4 deletions test/types/index.test-d.ts
Expand Up @@ -50,12 +50,12 @@ expectType<KeyObject | Uint8Array>(await lib.importJWK<KeyObject>({}))
}

{
const result = await lib.jwtVerify<CryptoKey>('', lib.createLocalJWKSet({ keys: [] }))
const result = await lib.jwtVerify<{}, CryptoKey>('', lib.createLocalJWKSet({ keys: [] }))
expectType<CryptoKey | Uint8Array>(result.key)
}

{
const result = await lib.jwtVerify<KeyObject>('', lib.createLocalJWKSet({ keys: [] }))
const result = await lib.jwtVerify<{}, KeyObject>('', lib.createLocalJWKSet({ keys: [] }))
expectType<KeyObject | Uint8Array>(result.key)
}

Expand Down Expand Up @@ -128,12 +128,12 @@ expectType<KeyObject | Uint8Array>(await lib.importJWK<KeyObject>({}))
}

{
const result = await lib.jwtDecrypt<CryptoKey>('', () => <any>{})
const result = await lib.jwtDecrypt<{}, CryptoKey>('', () => <any>{})
expectType<CryptoKey | Uint8Array>(result.key)
}

{
const result = await lib.jwtDecrypt<KeyObject>('', () => <any>{})
const result = await lib.jwtDecrypt<{}, KeyObject>('', () => <any>{})
expectType<KeyObject | Uint8Array>(result.key)
}

Expand Down Expand Up @@ -208,3 +208,42 @@ expectType<KeyObject>(await lib.createRemoteJWKSet<KeyObject>(new URL(''))())
expectType<lib.KeyLike>(await lib.EmbeddedJWK())
expectType<CryptoKey>(await lib.EmbeddedJWK())
expectType<KeyObject>(await lib.EmbeddedJWK())

{
const result = await lib.jwtVerify<{ foo: 'string' }>('', new Uint8Array())
expectType<string | undefined>(result.payload.iss)
expectType<unknown>(result.payload.unknown)
expectType<'string'>(result.payload.foo)
}

{
const result = await lib.jwtDecrypt<{ foo: 'string' }>('', new Uint8Array())
expectType<string | undefined>(result.payload.iss)
expectType<unknown>(result.payload.unknown)
expectType<'string'>(result.payload.foo)
}

{
const result = lib.UnsecuredJWT.decode<{ foo: 'string' }>('')
expectType<string | undefined>(result.payload.iss)
expectType<unknown>(result.payload.unknown)
expectType<'string'>(result.payload.foo)
}

{
const result = await lib.jwtVerify('', new Uint8Array())
expectType<string | undefined>(result.payload.iss)
expectType<unknown>(result.payload.unknown)
}

{
const result = await lib.jwtDecrypt('', new Uint8Array())
expectType<string | undefined>(result.payload.iss)
expectType<unknown>(result.payload.unknown)
}

{
const result = lib.UnsecuredJWT.decode('')
expectType<string | undefined>(result.payload.iss)
expectType<unknown>(result.payload.unknown)
}

0 comments on commit 61bd2a0

Please sign in to comment.