diff --git a/.changeset/createjwt-curve-alias.md b/.changeset/createjwt-curve-alias.md new file mode 100644 index 0000000..17904e2 --- /dev/null +++ b/.changeset/createjwt-curve-alias.md @@ -0,0 +1,9 @@ +--- +"@agentcommercekit/jwt": minor +--- + +`createJwt` now accepts key-curve names (`secp256k1`, `secp256r1`, `Ed25519`) +as `alg` aliases and resolves them to their JWT algorithms (`ES256K`, `ES256`, +`EdDSA`), matching the documented behavior. The alias was documented but never +wired up, so previously only JWT algorithm names worked. Passing a JWT +algorithm directly is unchanged. diff --git a/packages/jwt/src/create-jwt.test.ts b/packages/jwt/src/create-jwt.test.ts index 5ae2cf8..9221239 100644 --- a/packages/jwt/src/create-jwt.test.ts +++ b/packages/jwt/src/create-jwt.test.ts @@ -55,4 +55,34 @@ describe("createJWT", () => { "Failed to create JWT", ) }) + + const validJwt = + "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJpc3MiOiJkaWQ6ZXhhbXBsZTo0NTYifQ.sig" + + it.each([ + { alg: "secp256k1", expected: "ES256K" }, + { alg: "secp256r1", expected: "ES256" }, + { alg: "Ed25519", expected: "EdDSA" }, + ] as const)( + "resolves the key-curve alias $alg to $expected", + async ({ alg, expected }) => { + vi.mocked(baseCreateJWT).mockResolvedValueOnce(validJwt) + + await createJwt(mockPayload, mockOptions, { alg }) + + expect(baseCreateJWT).toHaveBeenCalledWith(mockPayload, mockOptions, { + alg: expected, + }) + }, + ) + + it("passes a JWT algorithm through unchanged", async () => { + vi.mocked(baseCreateJWT).mockResolvedValueOnce(validJwt) + + await createJwt(mockPayload, mockOptions, { alg: "EdDSA" }) + + expect(baseCreateJWT).toHaveBeenCalledWith(mockPayload, mockOptions, { + alg: "EdDSA", + }) + }) }) diff --git a/packages/jwt/src/create-jwt.ts b/packages/jwt/src/create-jwt.ts index e99d50d..4edd379 100644 --- a/packages/jwt/src/create-jwt.ts +++ b/packages/jwt/src/create-jwt.ts @@ -1,3 +1,4 @@ +import type { KeyCurve } from "@agentcommercekit/keys" import { createJWT as baseCreateJWT, type JWTHeader, @@ -5,7 +6,11 @@ import { type JWTPayload, } from "did-jwt" -import type { JwtAlgorithm } from "./jwt-algorithm" +import { + curveToJwtAlgorithm, + isJwtAlgorithm, + type JwtAlgorithm, +} from "./jwt-algorithm" import { isJwtString, type JwtString } from "./jwt-string" export type JwtPayload = JWTPayload @@ -25,19 +30,23 @@ export interface JwtHeader extends Omit { * @param payload - The payload to create the JWT from * @param options - The options to create the JWT from * @param header - Optional header overrides - * @param header.alg - The algorithm to use for the JWT. Accepts `secp256k1` and - * `Ed25519` as aliases for `ES256K` and `EdDSA` respectively. Defaults to + * @param header.alg - The algorithm to use for the JWT. Accepts a JWT + * algorithm (`ES256`, `ES256K`, `EdDSA`) or a key-curve alias (`secp256k1`, + * `secp256r1`, `Ed25519`) that is resolved to its JWT algorithm. Defaults to * `ES256K`. * @returns The JWT */ export async function createJwt( payload: Partial, options: JwtOptions, - { alg = "ES256K", ...header }: Partial = {}, + { + alg = "ES256K", + ...header + }: Partial> & { alg?: JwtAlgorithm | KeyCurve } = {}, ): Promise { const result = await baseCreateJWT(payload, options, { ...header, - alg, + alg: isJwtAlgorithm(alg) ? alg : curveToJwtAlgorithm(alg), }) if (!isJwtString(result)) {