Skip to content
Source
Sign inSign up

dpop.testkit.ts

39 lines1.4 KB

1// @effect-diagnostics-next-line nodeBuiltinImport:off -- Effect's Crypto has no generateKeyPairSync or sign.
2import * as NodeCrypto from "node:crypto";
3​
4import {
5 computeDpopAccessTokenHash,
6 computeDpopJwkThumbprint,
7 type DpopPublicJwk,
8} from "@t3tools/shared/dpop";
9​
10/** A client's DPoP key, signing proofs the way a browser or the mobile app does. */
11export const makeDpopKey = () => {
12 const { privateKey, publicKey } = NodeCrypto.generateKeyPairSync("ec", { namedCurve: "P-256" });
13 const publicJwk = publicKey.export({ format: "jwk" }) as DpopPublicJwk;
14 let proofCount = 0;
15 return {
16 thumbprint: computeDpopJwkThumbprint(publicJwk),
17 sign: (input: {
18 readonly method: string;
19 readonly url: string;
20 readonly token: string;
21 readonly iat: number;
22 }) => {
23 const encode = (value: unknown) => Buffer.from(JSON.stringify(value)).toString("base64url");
24 const header = encode({ typ: "dpop+jwt", alg: "ES256", jwk: publicJwk });
25 const payload = encode({
26 htm: input.method,
27 htu: input.url,
28 jti: `proof-${++proofCount}`,
29 iat: input.iat,
30 ath: computeDpopAccessTokenHash(input.token),
31 });
32 const signature = NodeCrypto.sign("sha256", Buffer.from(`${header}.${payload}`), {
33 key: privateKey,
34 dsaEncoding: "ieee-p1363",
35 }).toString("base64url");
36 return `${header}.${payload}.${signature}`;
37 },
38 };
39};