Skip to content

Commit

Permalink
Optional generic types for Payload (#84)
Browse files Browse the repository at this point in the history
  • Loading branch information
timonson authored Nov 5, 2023
1 parent c3f280b commit 1860a27
Show file tree
Hide file tree
Showing 9 changed files with 61 additions and 34 deletions.
2 changes: 1 addition & 1 deletion algorithm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ export type Algorithm =
| "RS512"
| "ES256"
| "ES384"
// | "ES512" //is not yet supported.
// https://github.com/denoland/deno/blob/main/ext/crypto/00_crypto.js
// | "ES512" //is not yet supported.
| "none";

// Still needs an 'any' type! Does anyone have an idea?
Expand Down
2 changes: 1 addition & 1 deletion deps.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export * as base64url from "https://deno.land/std@0.204.0/encoding/base64url.ts";
export * as base64url from "https://deno.land/std@0.205.0/encoding/base64url.ts";
14 changes: 9 additions & 5 deletions dist/mod.js
Original file line number Diff line number Diff line change
Expand Up @@ -396,12 +396,16 @@ function createSigningInput(header, payload) {
return `${mod.encode(encoder1.encode(JSON.stringify(header)))}.${mod.encode(encoder1.encode(JSON.stringify(payload)))}`;
}
async function create1(header, payload, key) {
if (verify(header.alg, key)) {
const signingInput = createSigningInput(header, payload);
const signature = await create(header.alg, key, signingInput);
return `${signingInput}.${signature}`;
if (isObject(payload)) {
if (verify(header.alg, key)) {
const signingInput = createSigningInput(header, payload);
const signature = await create(header.alg, key, signingInput);
return `${signingInput}.${signature}`;
} else {
throw new Error(`The jwt's alg '${header.alg}' does not match the key's algorithm.`);
}
} else {
throw new Error(`The jwt's alg '${header.alg}' does not match the key's algorithm.`);
throw new Error(`The jwt claims set is not a JSON object.`);
}
}
function getNumericDate(exp) {
Expand Down
3 changes: 1 addition & 2 deletions examples/deps.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
export { serve } from "https://deno.land/std@0.140.0/http/server.ts";
export * as base64 from "https://deno.land/std@0.140.0/encoding/base64.ts";
export * as base64 from "https://deno.land/std@0.205.0/encoding/base64.ts";
13 changes: 8 additions & 5 deletions examples/hs512.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { create, getNumericDate, verify } from "../mod.ts";
import { serve } from "./deps.ts";

import type { Header, Payload } from "../mod.ts";
import {
create,
getNumericDate,
type Header,
type Payload,
verify,
} from "../mod.ts";

const key = await crypto.subtle.generateKey(
{ name: "HMAC", hash: "SHA-512" },
Expand Down Expand Up @@ -31,4 +34,4 @@ async function handleRequest(request: Request) {
}
}

await serve(handleRequest);
Deno.serve(handleRequest);
7 changes: 2 additions & 5 deletions examples/rs384.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
import { create, verify } from "../mod.ts";
import { serve } from "./deps.ts";

import type { Header, Payload } from "../mod.ts";
import { create, type Header, type Payload, verify } from "../mod.ts";

const { privateKey, publicKey } = await window.crypto.subtle.generateKey(
{
Expand Down Expand Up @@ -39,4 +36,4 @@ async function handleRequest(request: Request) {
}
}

await serve(handleRequest);
Deno.serve(handleRequest);
30 changes: 17 additions & 13 deletions mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,9 +142,9 @@ function validateAudClaim(
* jwt has a valid _serialization_. Otherwise it throws an `Error`. This function
* does **not** verify the digital signature.
*/
export function decode(
export function decode<PayloadType extends Payload | unknown = unknown>(
jwt: string,
): [header: unknown, payload: unknown, signature: Uint8Array] {
): [unknown, PayloadType, Uint8Array] {
try {
const arr = jwt
.split(".")
Expand All @@ -154,7 +154,7 @@ export function decode(
? JSON.parse(decoder.decode(uint8Array))
: uint8Array
);
if (is3Tuple(arr)) return arr;
if (is3Tuple(arr)) return arr as [unknown, PayloadType, Uint8Array];
else throw new Error();
} catch {
throw Error("The serialization of the jwt is invalid.");
Expand Down Expand Up @@ -200,11 +200,11 @@ export function validate(
* Takes jwt, `CryptoKey` and `VerifyOptions` and returns the `Payload` of the
* jwt if the jwt is valid. Otherwise it throws an `Error`.
*/
export async function verify(
export async function verify<PayloadType extends Payload>(
jwt: string,
key: CryptoKey | null,
options?: VerifyOptions,
): Promise<Payload> {
): Promise<PayloadType> {
const { header, payload, signature } = validate(decode(jwt), options);
if (verifyAlgorithm(header.alg, key)) {
if (
Expand All @@ -223,7 +223,7 @@ export async function verify(
throw new Error("The payload does not satisfy all passed predicates.");
}

return payload;
return payload as PayloadType;
} else {
throw new Error(
`The jwt's alg '${header.alg}' does not match the key's algorithm.`,
Expand Down Expand Up @@ -255,15 +255,19 @@ export async function create(
payload: Payload,
key: CryptoKey | null,
): Promise<string> {
if (verifyAlgorithm(header.alg, key)) {
const signingInput = createSigningInput(header, payload);
const signature = await createSignature(header.alg, key, signingInput);
if (isObject(payload)) {
if (verifyAlgorithm(header.alg, key)) {
const signingInput = createSigningInput(header, payload);
const signature = await createSignature(header.alg, key, signingInput);

return `${signingInput}.${signature}`;
return `${signingInput}.${signature}`;
} else {
throw new Error(
`The jwt's alg '${header.alg}' does not match the key's algorithm.`,
);
}
} else {
throw new Error(
`The jwt's alg '${header.alg}' does not match the key's algorithm.`,
);
throw new Error(`The jwt claims set is not a JSON object.`);
}
}

Expand Down
20 changes: 20 additions & 0 deletions tests/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,13 @@ Deno.test({
Error,
"The jwt's alg 'HS256' does not match the key's algorithm.",
);
await assertRejects(
async () => {
await create(header, "invalid payload" as unknown as Payload, keyHS512);
},
Error,
"The jwt claims set is not a JSON object.",
);
},
});

Expand Down Expand Up @@ -222,6 +229,19 @@ Deno.test({
{ exp: 0 },
);

await assertEquals(
(await verify<{ email: string }>(
await create(
{ alg: "HS512", typ: "JWT" },
{ email: "joe@example.com" },
keyHS512,
),
keyHS512,
{ ignoreExp: true },
)).email,
"joe@example.com",
);

await assertEquals(
await verify(
await create(
Expand Down
4 changes: 2 additions & 2 deletions tests/test_deps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ export {
assertEquals,
assertRejects,
assertThrows,
} from "https://deno.land/std@0.204.0/testing/asserts.ts";
} from "https://deno.land/std@0.205.0/testing/asserts.ts";

export {
decode as decodeHex,
} from "https://deno.land/std@0.204.0/encoding/hex.ts";
} from "https://deno.land/std@0.205.0/encoding/hex.ts";

0 comments on commit 1860a27

Please sign in to comment.