From fa333e26a19d8a5fc4251145786414c3907a157c Mon Sep 17 00:00:00 2001 From: Hernan Alvarado Date: Tue, 28 Apr 2026 14:25:11 -0500 Subject: [PATCH 1/2] feat(core,jose): add JWK support across core and jose packages --- packages/core/src/@types/session.ts | 12 ++++-- packages/core/src/jose.ts | 3 +- packages/core/src/shared/assert.ts | 10 +++++ packages/core/src/shared/crypto.ts | 19 ++++++++- packages/core/test/jose.test.ts | 65 ++++++++++++++++++++++++++++- packages/jose/CHANGELOG.md | 2 + packages/jose/src/assert.ts | 7 +++- packages/jose/src/deriveKey.ts | 5 ++- packages/jose/src/encrypt.ts | 16 +++---- packages/jose/src/index.ts | 11 +++-- packages/jose/src/secret.ts | 10 ++--- packages/jose/src/sign.ts | 10 ++--- packages/jose/test/index.test.ts | 51 ++++++++++++++++++++++ 13 files changed, 191 insertions(+), 30 deletions(-) diff --git a/packages/core/src/@types/session.ts b/packages/core/src/@types/session.ts index 05cb25b..0381988 100644 --- a/packages/core/src/@types/session.ts +++ b/packages/core/src/@types/session.ts @@ -3,6 +3,7 @@ import type { TypedJWTPayload } from "@aura-stack/jose" import type { UserIdentity, UserShape } from "@/shared/identity.ts" import type { DeepPartial, EditableShape, Prettify, ZodShapeToObject } from "@/@types/utility.ts" import type { CookieStoreConfig, IdentityConfig, InternalLogger, JoseInstance } from "@/@types/config.ts" +import { JWK } from "@aura-stack/jose/jose" /** Application user type, inferred from the configured identity schema (defaults to the built-in user shape). */ export type User = Infer @@ -18,8 +19,8 @@ export interface Session { } export interface CryptoSecret { - sign: CryptoKey | CryptoKeyPair - encrypt: CryptoKey | CryptoKeyPair + sign: CryptoKey | CryptoKeyPair | JWK | AsymmetricKeyPair + encrypt: CryptoKey | CryptoKeyPair | JWK | AsymmetricKeyPair } export interface AsymmetricKeyPairFromEnv { @@ -27,6 +28,11 @@ export interface AsymmetricKeyPairFromEnv { privateKey: string } +export interface AsymmetricKeyPair { + publicKey: CryptoKey | JWK + privateKey: CryptoKey | JWK +} + /** * A symmetric secret or asymmetric key pair used for JWT operations. * @@ -34,7 +40,7 @@ export interface AsymmetricKeyPairFromEnv { * - CryptoKey: Web Crypto API key, for environments that support it * - CryptoKeyPair: asymmetric signing/encryption (RS256, ES256, EdDSA, RSA-OAEP, etc.) */ -export type SecretKey = string | Uint8Array | CryptoKey | CryptoKeyPair | CryptoSecret +export type SecretKey = string | Uint8Array | CryptoKey | CryptoKeyPair | CryptoSecret | JWK | AsymmetricKeyPair /** * @todo: add key rotation support for "SecretKey | CryptoKeyPair | [SecretKey | CryptoKeyPair, ...(SecretKey | CryptoKeyPair)[]]" diff --git a/packages/core/src/jose.ts b/packages/core/src/jose.ts index 53bad07..dfbeb12 100644 --- a/packages/core/src/jose.ts +++ b/packages/core/src/jose.ts @@ -21,6 +21,7 @@ import { isCryptoSecret, isEncryptedMode, isJWTPEMFormattedKeyPair, + isKeyPair, isPEMFormattedKeyPairFromEnv, isSealedMode, isSignedMode, @@ -187,7 +188,7 @@ const getSecrets = async ( }, } } - if (isCryptoKey(secret) || isCryptoKeyPair(secret)) { + if (isCryptoKey(secret) || isCryptoKeyPair(secret) || isKeyPair(secret)) { return { jwsSecret: secret, jweSecret: secret, diff --git a/packages/core/src/shared/assert.ts b/packages/core/src/shared/assert.ts index 046e63e..6bd193a 100644 --- a/packages/core/src/shared/assert.ts +++ b/packages/core/src/shared/assert.ts @@ -1,5 +1,6 @@ import { equals, patternToRegex } from "@/shared/utils.ts" import type { + AsymmetricKeyPair, AsymmetricKeyPairFromEnv, CryptoSecret, JWTConfig, @@ -7,6 +8,7 @@ import type { JWTPayloadWithToken, SessionConfig, } from "@/@types/index.ts" +import { JWK } from "@aura-stack/jose/jose" export const isFalsy = (value: unknown): boolean => { return value === false || value === 0 || value === "" || value === null || value === undefined || Number.isNaN(value) @@ -125,6 +127,10 @@ export const isCryptoKey = (value: unknown): value is CryptoKey => { return typeof value === "object" && value !== null && "algorithm" in value && "extractable" in value } +export const isKeyPair = (value: unknown): value is AsymmetricKeyPair => { + return typeof value === "object" && value !== null && "publicKey" in value && "privateKey" in value +} + export const isCryptoSecret = (value: unknown): value is CryptoSecret => { return ( typeof value === "object" && @@ -163,3 +169,7 @@ export const isJWTPEMFormattedKeyPair = ( isPEMFormattedKeyPairFromEnv((value as any).encrypt) ) } + +export const isJWKFormattedKey = (value: unknown): value is JWK => { + return typeof value === "object" && value !== null && "kty" in value && typeof (value as any).kty === "string" +} diff --git a/packages/core/src/shared/crypto.ts b/packages/core/src/shared/crypto.ts index d509104..c4dca6e 100644 --- a/packages/core/src/shared/crypto.ts +++ b/packages/core/src/shared/crypto.ts @@ -1,7 +1,7 @@ import { AuthSecurityError } from "@/shared/errors.ts" import { isJWTPayloadWithToken } from "@/shared/assert.ts" import { equals, timingSafeEqual } from "@/shared/utils.ts" -import { importPKCS8, importSPKI } from "@aura-stack/jose/jose" +import { exportJWK, generateKeyPair, GenerateKeyPairOptions, importPKCS8, importSPKI } from "@aura-stack/jose/jose" import { base64url, encoder, getRandomBytes, getSubtleCrypto } from "@/jose.ts" import type { AsymmetricKeyPairFromEnv, AuthRuntimeConfig, JoseInstance, User } from "@/@types/index.ts" @@ -151,3 +151,20 @@ export const importPEMKeyPair = async (key: AsymmetricKeyPairFromEnv, algorithm: privateKey: importedPrivateKey, } } + +/** + * Generates a new asymmetric key pair and exports it in JWK format. + * + * @param alg - The intended algorithm for the keys (e.g. "RS256" for RSA signing, "RSA-OAEP" for RSA encryption) + * @param options - Optional parameters for key generation (e.g. modulusLength for RSA) + * @returns A Promise that resolves to an object containing the public and private keys in JWK format + */ +export const exportJWKKeyPair = async (alg: string, options?: GenerateKeyPairOptions) => { + const { publicKey, privateKey } = await generateKeyPair(alg, options) + const jwkPublicKey = await exportJWK(publicKey) + const jwkPrivateKey = await exportJWK(privateKey) + return { + publicKey: jwkPublicKey, + privateKey: jwkPrivateKey, + } +} diff --git a/packages/core/test/jose.test.ts b/packages/core/test/jose.test.ts index 5bf4eab..bb7d891 100644 --- a/packages/core/test/jose.test.ts +++ b/packages/core/test/jose.test.ts @@ -1,6 +1,6 @@ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest" import { createJoseInstance, encoder } from "@/jose.ts" -import { createSecretValue } from "@/shared/crypto.ts" +import { createSecretValue, exportJWKKeyPair } from "@/shared/crypto.ts" import { createAuth } from "@/createAuth.ts" import { generateKeyPair } from "@aura-stack/jose/jose" import { RS256PEMFormat, RSAOAEP256PEMFormat } from "./presets.ts" @@ -618,4 +618,67 @@ describe("createJoseInstance", () => { /Multiples PEM Key Pairs from environment variables require 'sealed' JWT mode. For 'signed' or 'encrypted' modes, provide a single PEM key pair or a combined key object./ ) }) + + test("JWS (signed) with JWK formatted keys", async () => { + vi.stubEnv("AURA_AUTH_SALT", createSecretValue(32)) + + const secret = await exportJWKKeyPair("RS256", { extractable: true }) + const jose = createJoseInstance(secret, { + jwt: { + signingAlgorithm: "RS256", + }, + }) + + const signed = await jose.signJWS(payload) + const verified = await jose.verifyJWS(signed) + expect(verified).toMatchObject(payload) + }) + + test("JWE (encrypted) with JWK formatted keys", async () => { + vi.stubEnv("AURA_AUTH_SALT", createSecretValue(32)) + + const secret = await exportJWKKeyPair("RSA-OAEP-256", { extractable: true }) + const jose = createJoseInstance(secret, { + jwt: { + mode: "encrypted", + keyAlgorithm: "RSA-OAEP-256", + encryptionAlgorithm: "A256GCM", + }, + }) + const encrypted = await jose.encryptJWE(payload) + const decrypted = await jose.decryptJWE(encrypted) + expect(decrypted).toMatchObject(payload) + }) + + test("JWT (sealed) with JWK formatted keys", async () => { + vi.stubEnv("AURA_AUTH_SALT", createSecretValue(32)) + + const signingKeyPair = await exportJWKKeyPair("RS256", { extractable: true }) + const encryptionKeyPair = await exportJWKKeyPair("RSA-OAEP-256", { extractable: true }) + const jose = createJoseInstance( + { + sign: signingKeyPair, + encrypt: encryptionKeyPair, + }, + { + jwt: { + signingAlgorithm: "RS256", + keyAlgorithm: "RSA-OAEP-256", + encryptionAlgorithm: "A256GCM", + }, + } + ) + + const encoded = await jose.encodeJWT(payload) + const decoded = await jose.decodeJWT(encoded) + expect(decoded).toMatchObject(payload) + + const signed = await jose.signJWS(payload) + const verified = await jose.verifyJWS(signed) + expect(verified).toMatchObject(payload) + + const encrypted = await jose.encryptJWE(payload) + const decrypted = await jose.decryptJWE(encrypted) + expect(decrypted).toMatchObject(payload) + }) }) diff --git a/packages/jose/CHANGELOG.md b/packages/jose/CHANGELOG.md index 443218e..79d1ea9 100644 --- a/packages/jose/CHANGELOG.md +++ b/packages/jose/CHANGELOG.md @@ -10,6 +10,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), ### Added +- Added support to asymmetric cryptography using JWK key pairs across JOSE functions, including the dedicated `signJWS`, `verifyJWS`, `encryptJWE`, `decryptJWE`, `encryptCompactJWE`, `decryptCompactJWE`, `encodeJWT`, and `decodeJWT` functions, as well as the factory functions `createJWS`, `createJWE`, and `createJWT`. [#159](https://github.com/aura-stack-ts/auth/pull/159) + - Added support for asymmetric cryptography using `public/private` key pairs via `CryptoKeyPair` across JOSE functions, including the dedicated `signJWS`, `verifyJWS`, `encryptJWE`, `decryptJWE`, `encryptCompactJWE`, `decryptCompactJWE`, `encodeJWT`, and `decodeJWT` functions, as well as the factory functions `createJWS`, `createJWE`, and `createJWT`. [#157](https://github.com/aura-stack-ts/auth/pull/157) ## [0.5.0] - 2026-04-21 diff --git a/packages/jose/src/assert.ts b/packages/jose/src/assert.ts index 4812832..0132000 100644 --- a/packages/jose/src/assert.ts +++ b/packages/jose/src/assert.ts @@ -1,4 +1,5 @@ import { AuraJoseError, InvalidSecretError } from "@/errors.ts" +import type { AsymmetricKeyPair } from "@/index.ts" export const isAuraJoseError = (error: unknown): error is AuraJoseError => { return error instanceof AuraJoseError @@ -24,6 +25,10 @@ export const isInvalidPayload = (payload: unknown): boolean => { ) } -export const isCryptoKeyPair = (value: unknown): value is CryptoKeyPair => { +export const isAsymmetricKeyPair = (value: unknown): value is AsymmetricKeyPair => { return typeof value === "object" && value !== null && "publicKey" in value && "privateKey" in value } + +export const isJWKKey = (value: unknown): value is JsonWebKey => { + return typeof value === "object" && value !== null && "kty" in value +} diff --git a/packages/jose/src/deriveKey.ts b/packages/jose/src/deriveKey.ts index 6cc036e..f3c5633 100644 --- a/packages/jose/src/deriveKey.ts +++ b/packages/jose/src/deriveKey.ts @@ -2,6 +2,7 @@ import { createSecret } from "@/secret.ts" import { KeyDerivationError } from "@/errors.ts" import { encoder, getSubtleCrypto } from "@/crypto.ts" import { SecretInput } from "./index.ts" +import { isJWKKey } from "./assert.ts" /** * Generate a derived key using HKDF (HMAC-based Extract-and-Expand Key Derivation Function) @@ -62,8 +63,8 @@ export const createDeriveKey = async ( info?: string | Uint8Array, length: number = 32 ) => { - const secretKey = createSecret(secret) - if (secretKey instanceof CryptoKey) { + const secretKey = createSecret(secret) as Uint8Array + if (secretKey instanceof CryptoKey || isJWKKey(secretKey)) { throw new KeyDerivationError("Cannot derive key from CryptoKey. Use Uint8Array or string secret instead.") } const key = await deriveKey(secretKey, salt ?? "Aura Jose secret salt", info ?? "Aura Jose secret derivation", length) diff --git a/packages/jose/src/encrypt.ts b/packages/jose/src/encrypt.ts index efb546d..b7c4650 100644 --- a/packages/jose/src/encrypt.ts +++ b/packages/jose/src/encrypt.ts @@ -11,9 +11,9 @@ import { } from "jose" import { createSecret } from "@/secret.ts" import { decoder, encoder, getRandomBytes } from "@/crypto.ts" -import { isAuraJoseError, isCryptoKeyPair, isFalsy } from "@/assert.ts" +import { isAuraJoseError, isAsymmetricKeyPair, isFalsy } from "@/assert.ts" import { InvalidPayloadError, JWEDecryptionError, JWEEncryptionError } from "@/errors.ts" -import type { SecretInput, TypedJWTPayload } from "@/index.ts" +import type { JWTSecretInput, SecretInput, TypedJWTPayload } from "@/index.ts" export type { JWTDecryptOptions, JWEHeaderParameters, DecryptOptions } from "jose" @@ -148,9 +148,9 @@ export const decryptCompactJWE = async (token: string, secret: SecretInput, opti * @param secret - Secret key used for encrypting and decrypting the JWE * @returns encryptJWE and decryptJWE functions */ -export const createJWE = (secret: SecretInput | CryptoKeyPair) => { - const encryptSecret = isCryptoKeyPair(secret) ? secret.publicKey : secret - const decryptSecret = isCryptoKeyPair(secret) ? secret.privateKey : secret +export const createJWE = (secret: JWTSecretInput) => { + const encryptSecret = isAsymmetricKeyPair(secret) ? secret.publicKey : secret + const decryptSecret = isAsymmetricKeyPair(secret) ? secret.privateKey : secret return { encryptJWE: ( @@ -168,9 +168,9 @@ export const createJWE = (secret: SecretInput | Cryp * @param secret - Secret key used for encrypting and decrypting the JWE * @returns compactEncryptJWE and decryptCompactJWE functions */ -export const createCompactJWE = (secret: SecretInput | CryptoKeyPair) => { - const encryptSecret = isCryptoKeyPair(secret) ? secret.publicKey : secret - const decryptSecret = isCryptoKeyPair(secret) ? secret.privateKey : secret +export const createCompactJWE = (secret: JWTSecretInput) => { + const encryptSecret = isAsymmetricKeyPair(secret) ? secret.publicKey : secret + const decryptSecret = isAsymmetricKeyPair(secret) ? secret.privateKey : secret return { compactEncryptJWE: (payload: string, options?: JWEHeaderParameters) => compactEncryptJWE(payload, encryptSecret, options), diff --git a/packages/jose/src/index.ts b/packages/jose/src/index.ts index 651dac4..0171bd2 100644 --- a/packages/jose/src/index.ts +++ b/packages/jose/src/index.ts @@ -1,7 +1,7 @@ /** * @module @aura-stack/jose */ -import type { DecryptOptions, JWEHeaderParameters, JWTHeaderParameters, JWTPayload, JWTVerifyOptions } from "jose" +import type { DecryptOptions, JWEHeaderParameters, JWK, JWTHeaderParameters, JWTPayload, JWTVerifyOptions } from "jose" import { getSecrets } from "@/secret.ts" import { signJWS, verifyJWS } from "@/sign.ts" import { isAuraJoseError } from "@/assert.ts" @@ -19,14 +19,19 @@ export type * from "@/secret.ts" export * from "@/crypto.ts" export type * from "@/crypto.ts" +export interface AsymmetricKeyPair { + publicKey: CryptoKey | JWK | JsonWebKey + privateKey: CryptoKey | JWK | JsonWebKey +} + /** * Secret input can be: * - CryptoKey: W3C standard key object (works across all runtimes) * - Uint8Array: Raw bytes * - string: String that will be encoded to UTF-8 */ -export type SecretInput = Uint8Array | string | CryptoKey -export type JWTSecretInput = SecretInput | CryptoKeyPair +export type SecretInput = Uint8Array | string | CryptoKey | JWK +export type JWTSecretInput = SecretInput | AsymmetricKeyPair export type DerivedKeyInput = { sign: JWTSecretInput; encrypt: JWTSecretInput } export type Prettify = { [K in keyof T]: T[K] } & {} export type TypedJWTPayload = JWTPayload & Payload diff --git a/packages/jose/src/secret.ts b/packages/jose/src/secret.ts index f7534a6..28e0319 100644 --- a/packages/jose/src/secret.ts +++ b/packages/jose/src/secret.ts @@ -1,4 +1,4 @@ -import { isCryptoKeyPair, isObject } from "@/assert.ts" +import { isAsymmetricKeyPair, isJWKKey, isObject } from "@/assert.ts" import { InvalidSecretError } from "@/errors.ts" import { encoder } from "@/crypto.ts" import type { DerivedKeyInput, JWTSecretInput, SecretInput } from "@/index.ts" @@ -45,17 +45,17 @@ export const createSecret = (secret: SecretInput, length: number = 32) => { } return encoded } - if (secret instanceof CryptoKey || secret instanceof Uint8Array) { + if (secret instanceof CryptoKey || secret instanceof Uint8Array || isJWKKey(secret)) { if (secret instanceof Uint8Array && secret.byteLength < length) { throw new InvalidSecretError(`Secret must be at least ${length} bytes long`) } return secret } - throw new InvalidSecretError("Secret must be a string, Uint8Array, or CryptoKey") + throw new InvalidSecretError("Secret must be a string, Uint8Array, CryptoKey or AsymmetricKeyPair") } const getJWSSecrets = (secret: JWTSecretInput) => { - if (!isCryptoKeyPair(secret)) { + if (!isAsymmetricKeyPair(secret)) { return { encode: secret, decode: secret, @@ -69,7 +69,7 @@ const getJWSSecrets = (secret: JWTSecretInput) => { } const getJWESecrets = (secret: JWTSecretInput) => { - if (!isCryptoKeyPair(secret)) { + if (!isAsymmetricKeyPair(secret)) { return { encode: secret, decode: secret, diff --git a/packages/jose/src/sign.ts b/packages/jose/src/sign.ts index b10035e..84ca28f 100644 --- a/packages/jose/src/sign.ts +++ b/packages/jose/src/sign.ts @@ -1,9 +1,9 @@ import { base64url, jwtVerify, SignJWT, type JWTPayload, type JWTVerifyOptions, type JWTHeaderParameters } from "jose" import { createSecret } from "@/secret.ts" import { getRandomBytes } from "@/crypto.ts" -import { isAuraJoseError, isCryptoKeyPair, isFalsy, isInvalidPayload } from "@/assert.ts" +import { isAsymmetricKeyPair, isAuraJoseError, isFalsy, isInvalidPayload } from "@/assert.ts" import { JWSSigningError, JWSVerificationError, InvalidPayloadError } from "@/errors.ts" -import type { SecretInput, TypedJWTPayload } from "@/index.ts" +import type { JWTSecretInput, SecretInput, TypedJWTPayload } from "@/index.ts" export type { JWTVerifyOptions, JWTHeaderParameters } from "jose" @@ -90,9 +90,9 @@ export const verifyJWS = async ( * @param options - Optional signing options (e.g. algorithm) * @returns signJWS and verifyJWS functions */ -export const createJWS = (secret: SecretInput | CryptoKeyPair) => { - const signSecret = isCryptoKeyPair(secret) ? secret.privateKey : secret - const verifySecret = isCryptoKeyPair(secret) ? secret.publicKey : secret +export const createJWS = (secret: JWTSecretInput) => { + const signSecret = isAsymmetricKeyPair(secret) ? secret.privateKey : secret + const verifySecret = isAsymmetricKeyPair(secret) ? secret.publicKey : secret return { signJWS: ( payload: TypedJWTPayload>, diff --git a/packages/jose/test/index.test.ts b/packages/jose/test/index.test.ts index bb43060..c5bdec4 100644 --- a/packages/jose/test/index.test.ts +++ b/packages/jose/test/index.test.ts @@ -209,6 +209,19 @@ describe("JWSs", () => { expect(decoded.name).toBe(payload.name) expect(decoded.email).toBe(payload.email) }) + + test("verify JWK with RSA algorithm", async () => { + const { publicKey, privateKey } = await generateKeyPair("RS256", { extractable: true }) + const publicJWK = await crypto.subtle.exportKey("jwk", publicKey) + const privateJWK = await crypto.subtle.exportKey("jwk", privateKey) + const { signJWS, verifyJWS } = createJWS({ + publicKey: publicJWK, + privateKey: privateJWK, + }) + const signed = await signJWS(payload, { alg: "RS256" }) + const verified = await verifyJWS(signed, { algorithms: ["RS256"] }) + expect(verified).toMatchObject(payload) + }) }) describe("JWEs", () => { @@ -311,6 +324,19 @@ describe("JWEs", () => { }) expect(JSON.parse(decryptedPayload)).toMatchObject(payload) }) + + test("verify JWK with RSA algorithm", async () => { + const { publicKey, privateKey } = await generateKeyPair("RSA-OAEP-256", { extractable: true }) + const publicJWK = await crypto.subtle.exportKey("jwk", publicKey) + const privateJWK = await crypto.subtle.exportKey("jwk", privateKey) + const { encryptJWE, decryptJWE } = createJWE({ + publicKey: publicJWK, + privateKey: privateJWK, + }) + const signed = await encryptJWE(payload, { alg: "RSA-OAEP-256" }) + const verified = await decryptJWE(signed, { keyManagementAlgorithms: ["RSA-OAEP-256"] }) + expect(verified).toMatchObject(payload) + }) }) describe("JWTs", () => { @@ -427,6 +453,31 @@ describe("JWTs", () => { expect(decoded.name).toBe(payload.name) expect(decoded.email).toBe(payload.email) }) + + test("createJWT with JWK keys", async () => { + const { publicKey: signPublicKey, privateKey: signPrivateKey } = await generateKeyPair("RS256", { extractable: true }) + const { publicKey: encryptPublicKey, privateKey: encryptPrivateKey } = await generateKeyPair("RSA-OAEP-256", { + extractable: true, + }) + const signPublicJWK = await crypto.subtle.exportKey("jwk", signPublicKey) + const signPrivateJWK = await crypto.subtle.exportKey("jwk", signPrivateKey) + const encryptPublicJWK = await crypto.subtle.exportKey("jwk", encryptPublicKey) + const encryptPrivateJWK = await crypto.subtle.exportKey("jwk", encryptPrivateKey) + const jwt = createJWT({ + sign: { publicKey: signPublicJWK, privateKey: signPrivateJWK }, + encrypt: { publicKey: encryptPublicJWK, privateKey: encryptPrivateJWK }, + }) + + const encoded = await jwt.encodeJWT(payload, { + sign: { alg: "RS256" }, + encrypt: { alg: "RSA-OAEP-256", enc: "A256GCM" }, + }) + const decoded = await jwt.decodeJWT(encoded, { + verify: { algorithms: ["RS256"] }, + decrypt: { keyManagementAlgorithms: ["RSA-OAEP-256"], contentEncryptionAlgorithms: ["A256GCM"] }, + }) + expect(decoded).toMatchObject(payload) + }) }) describe("createSecret", () => { From 4a1e6d5d8f86e2896914efa01a355039bf3cf0ac Mon Sep 17 00:00:00 2001 From: Hernan Alvarado Date: Tue, 28 Apr 2026 15:10:16 -0500 Subject: [PATCH 2/2] chore: apply coderabbit --- packages/core/CHANGELOG.md | 2 ++ packages/core/src/@types/session.ts | 6 +++--- packages/core/src/shared/assert.ts | 2 +- packages/jose/CHANGELOG.md | 2 +- packages/jose/src/assert.ts | 2 +- packages/jose/src/deriveKey.ts | 2 +- packages/jose/src/secret.ts | 2 +- 7 files changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 0daf7a8..3a024da 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -10,6 +10,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), ### Added +- Extended asymmetric cryptography support to accept JWK (JSON Web Key) format keys in addition to `CryptoKeyPair` across JOSE functions exposed by `createAuth.jose`, including the dedicated `signJWS`, `verifyJWS`, `encryptJWE`, `decryptJWE`, `encodeJWT`, and `decodeJWT` functions. [#159](https://github.com/aura-stack-ts/auth/pull/159) + - Added support for asymmetric cryptography using `public/private` key pairs via `CryptoKeyPair` across JOSE functions exposed by `createAuth.jose`, including the dedicated `signJWS`, `verifyJWS`, `encryptJWE`, `decryptJWE`, `encodeJWT`, and `decodeJWT` functions. [#157](https://github.com/aura-stack-ts/auth/pull/157) - Added the `Dribbble` OAuth provider to the supported integrations in Aura Auth. [#153](https://github.com/aura-stack-ts/auth/pull/153) diff --git a/packages/core/src/@types/session.ts b/packages/core/src/@types/session.ts index 0381988..cd5e27e 100644 --- a/packages/core/src/@types/session.ts +++ b/packages/core/src/@types/session.ts @@ -3,7 +3,7 @@ import type { TypedJWTPayload } from "@aura-stack/jose" import type { UserIdentity, UserShape } from "@/shared/identity.ts" import type { DeepPartial, EditableShape, Prettify, ZodShapeToObject } from "@/@types/utility.ts" import type { CookieStoreConfig, IdentityConfig, InternalLogger, JoseInstance } from "@/@types/config.ts" -import { JWK } from "@aura-stack/jose/jose" +import type { JWK } from "@aura-stack/jose/jose" /** Application user type, inferred from the configured identity schema (defaults to the built-in user shape). */ export type User = Infer @@ -19,8 +19,8 @@ export interface Session { } export interface CryptoSecret { - sign: CryptoKey | CryptoKeyPair | JWK | AsymmetricKeyPair - encrypt: CryptoKey | CryptoKeyPair | JWK | AsymmetricKeyPair + sign: CryptoKey | CryptoKeyPair | JWK | JsonWebKey | AsymmetricKeyPair + encrypt: CryptoKey | CryptoKeyPair | JWK | JsonWebKey | AsymmetricKeyPair } export interface AsymmetricKeyPairFromEnv { diff --git a/packages/core/src/shared/assert.ts b/packages/core/src/shared/assert.ts index 6bd193a..c1bbbd4 100644 --- a/packages/core/src/shared/assert.ts +++ b/packages/core/src/shared/assert.ts @@ -8,7 +8,7 @@ import type { JWTPayloadWithToken, SessionConfig, } from "@/@types/index.ts" -import { JWK } from "@aura-stack/jose/jose" +import type { JWK } from "@aura-stack/jose/jose" export const isFalsy = (value: unknown): boolean => { return value === false || value === 0 || value === "" || value === null || value === undefined || Number.isNaN(value) diff --git a/packages/jose/CHANGELOG.md b/packages/jose/CHANGELOG.md index 79d1ea9..f6dae0c 100644 --- a/packages/jose/CHANGELOG.md +++ b/packages/jose/CHANGELOG.md @@ -10,7 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), ### Added -- Added support to asymmetric cryptography using JWK key pairs across JOSE functions, including the dedicated `signJWS`, `verifyJWS`, `encryptJWE`, `decryptJWE`, `encryptCompactJWE`, `decryptCompactJWE`, `encodeJWT`, and `decodeJWT` functions, as well as the factory functions `createJWS`, `createJWE`, and `createJWT`. [#159](https://github.com/aura-stack-ts/auth/pull/159) +- Extended asymmetric cryptography support to accept JWK (JSON Web Key) format keys in addition to `CryptoKeyPair` across JOSE functions, including the dedicated `signJWS`, `verifyJWS`, `encryptJWE`, `decryptJWE`, `encryptCompactJWE`, `decryptCompactJWE`, `encodeJWT`, and `decodeJWT` functions, as well as the factory functions `createJWS`, `createJWE`, and `createJWT`. [#159](https://github.com/aura-stack-ts/auth/pull/159) - Added support for asymmetric cryptography using `public/private` key pairs via `CryptoKeyPair` across JOSE functions, including the dedicated `signJWS`, `verifyJWS`, `encryptJWE`, `decryptJWE`, `encryptCompactJWE`, `decryptCompactJWE`, `encodeJWT`, and `decodeJWT` functions, as well as the factory functions `createJWS`, `createJWE`, and `createJWT`. [#157](https://github.com/aura-stack-ts/auth/pull/157) diff --git a/packages/jose/src/assert.ts b/packages/jose/src/assert.ts index 0132000..5c1282e 100644 --- a/packages/jose/src/assert.ts +++ b/packages/jose/src/assert.ts @@ -30,5 +30,5 @@ export const isAsymmetricKeyPair = (value: unknown): value is AsymmetricKeyPair } export const isJWKKey = (value: unknown): value is JsonWebKey => { - return typeof value === "object" && value !== null && "kty" in value + return typeof value === "object" && value !== null && "kty" in value && typeof (value as JsonWebKey).kty === "string" } diff --git a/packages/jose/src/deriveKey.ts b/packages/jose/src/deriveKey.ts index f3c5633..372078b 100644 --- a/packages/jose/src/deriveKey.ts +++ b/packages/jose/src/deriveKey.ts @@ -65,7 +65,7 @@ export const createDeriveKey = async ( ) => { const secretKey = createSecret(secret) as Uint8Array if (secretKey instanceof CryptoKey || isJWKKey(secretKey)) { - throw new KeyDerivationError("Cannot derive key from CryptoKey. Use Uint8Array or string secret instead.") + throw new KeyDerivationError("Cannot derive key from CryptoKey or JWK. Use Uint8Array or string secret instead.") } const key = await deriveKey(secretKey, salt ?? "Aura Jose secret salt", info ?? "Aura Jose secret derivation", length) return key diff --git a/packages/jose/src/secret.ts b/packages/jose/src/secret.ts index 28e0319..26fb6b8 100644 --- a/packages/jose/src/secret.ts +++ b/packages/jose/src/secret.ts @@ -51,7 +51,7 @@ export const createSecret = (secret: SecretInput, length: number = 32) => { } return secret } - throw new InvalidSecretError("Secret must be a string, Uint8Array, CryptoKey or AsymmetricKeyPair") + throw new InvalidSecretError("Secret must be a string, Uint8Array, CryptoKey or JWK") } const getJWSSecrets = (secret: JWTSecretInput) => {