unjwt 0.7.3 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +4 -4
  2. package/dist/THIRD-PARTY-LICENSES.md.gz +0 -0
  3. package/dist/_chunks/_codec-jwe.d.mts +170 -0
  4. package/dist/_chunks/_codec-jws.mjs +878 -0
  5. package/dist/_chunks/_internal.d.mts +7 -0
  6. package/dist/_chunks/_jwt-internal.mjs +388 -0
  7. package/dist/_chunks/algorithms.mjs +186 -0
  8. package/dist/_chunks/index.d.mts +500 -808
  9. package/dist/_chunks/index2.d.mts +11 -0
  10. package/dist/_chunks/index3.d.mts +108 -0
  11. package/dist/_chunks/jwe.d.mts +43 -66
  12. package/dist/_chunks/jwe.mjs +384 -252
  13. package/dist/_chunks/jwk.mjs +1374 -607
  14. package/dist/_chunks/jws.d.mts +48 -103
  15. package/dist/_chunks/jws.mjs +372 -184
  16. package/dist/_chunks/jwt.d.mts +56 -0
  17. package/dist/_chunks/libs/unsecure.d.mts +164 -85
  18. package/dist/_chunks/libs/unsecure.mjs +390 -123
  19. package/dist/adapters/elysia/index.d.mts +33 -171
  20. package/dist/adapters/elysia/index.mjs +106 -575
  21. package/dist/adapters/h3v1/index.d.mts +59 -344
  22. package/dist/adapters/h3v1/index.mjs +114 -619
  23. package/dist/adapters/h3v2/index.d.mts +58 -326
  24. package/dist/adapters/h3v2/index.mjs +108 -611
  25. package/dist/core/internal.d.mts +2 -0
  26. package/dist/core/internal.mjs +1 -0
  27. package/dist/core/jwe.d.mts +4 -2
  28. package/dist/core/jwe.mjs +4 -2
  29. package/dist/core/jwk.d.mts +183 -371
  30. package/dist/core/jwk.mjs +4 -3
  31. package/dist/core/jws.d.mts +4 -2
  32. package/dist/core/jws.mjs +4 -2
  33. package/dist/core/utils/index.d.mts +5 -158
  34. package/dist/core/utils/index.mjs +12 -3
  35. package/dist/index.d.mts +8 -4
  36. package/dist/index.mjs +6 -3
  37. package/package.json +29 -30
  38. package/dist/_chunks/_internal.mjs +0 -88
  39. package/dist/_chunks/utils.mjs +0 -392
package/README.md CHANGED
@@ -12,10 +12,10 @@ Low-level JWT toolkit built on the Web Crypto API. Sign, verify, encrypt, decryp
12
12
 
13
13
  - **JWS** ([RFC 7515](https://datatracker.ietf.org/doc/html/rfc7515)) — HMAC, RSA, RSA-PSS, ECDSA, EdDSA. Compact + General/Flattened JSON Serialization (multi-signature).
14
14
  - **JWE** ([RFC 7516](https://datatracker.ietf.org/doc/html/rfc7516)) — `dir`, AES-KW, AES-GCM-KW, RSA-OAEP, PBES2, ECDH-ES. AES-GCM and AES-CBC-HMAC-SHA2 content encryption. Compact + General/Flattened JSON Serialization (multi-recipient).
15
- - **JWK** ([RFC 7517](https://datatracker.ietf.org/doc/html/rfc7517)) — generate, import, export, wrap, unwrap. `CryptoKey` ↔ JWK ↔ PEM conversion. ECDH-ES shared-secret derivation. JWKS fetching, lookup, and rotation.
16
- - **Framework adapters** — cookie-based JWT sessions for [H3 v1](https://unjwt.s94.dev/adapters) (Nuxt v4 / Nitro v2) and [H3 v2](https://unjwt.s94.dev/adapters) (Nuxt v5 / Nitro v3).
15
+ - **JWK** ([RFC 7517](https://datatracker.ietf.org/doc/html/rfc7517)) — generate, import, export, wrap, unwrap. `CryptoKey` ↔ JWK ↔ PEM conversion. ECDH-ES shared-secret derivation. JWK Set lookup and rotation over a set your application fetches.
16
+ - **Framework adapters** — cookie-based JWT sessions for [H3 v1](https://unjwt.s94.dev/adapters) (Nuxt v4 / Nitro v2), [H3 v2](https://unjwt.s94.dev/adapters) (Nuxt v5 / Nitro v3) and [Elysia](https://unjwt.s94.dev/adapters/elysia).
17
17
  - **Runtime-agnostic** — Node 22+, Deno, Bun, Cloudflare Workers, browsers. Anywhere Web Crypto runs.
18
- - **Zero runtime dependencies** for core; optional peer deps for adapters (`h3`, `cookie-es`, `rou3`).
18
+ - **Zero runtime dependencies** for core; optional peer deps for adapters (`h3`, `cookie-es`, `elysia`).
19
19
 
20
20
  ## Install
21
21
 
@@ -55,7 +55,7 @@ Full walkthroughs, recipes, and the complete API reference live at **[unjwt.s94.
55
55
  - [JWS — signing and verifying →](https://unjwt.s94.dev/jwt/jws)
56
56
  - [JWE — encrypting and decrypting →](https://unjwt.s94.dev/jwt/jwe)
57
57
  - [JWK — key management →](https://unjwt.s94.dev/jwk)
58
- - [H3 session adapters →](https://unjwt.s94.dev/adapters)
58
+ - [Session adapters →](https://unjwt.s94.dev/adapters)
59
59
  - [Examples →](https://unjwt.s94.dev/examples) — authentication, JWKS endpoints, refresh tokens, end-to-end encryption, signed receipts.
60
60
 
61
61
  ## Development
Binary file
@@ -0,0 +1,170 @@
1
+ import { ExpiresIn, JWEAsymmetricPrivateJWK, JWEAsymmetricPublicJWK, JWEDecryptJWK, JWEDecryptOptions, JWEEncryptJWK, JWEEncryptOptions, JWEHeaderParameters, JWKLookupFunctionHeader, JWKSet, JWK_HMAC, JWK_oct, JWSAsymmetricPrivateJWK, JWSAsymmetricPublicJWK, JWSSignOptions, JWSVerifyJWK, JWSVerifyOptions, JWTClaims, StrictOmit } from "./index.mjs";
2
+ /** Per-issue overrides for a single `update()` call. */
3
+ export interface SessionIssueOptions {
4
+ /** Lifetime for this issue only, overriding the config's `maxAge`. */
5
+ maxAge?: ExpiresIn;
6
+ }
7
+ /** Session data shape; `any` values let an `interface` (no index signature) satisfy it. */
8
+ export type SessionRecord = Record<string, any>;
9
+ export interface SessionClaims extends Required<Pick<JWTClaims, "jti" | "iat">>, StrictOmit<JWTClaims, "jti" | "iat"> {}
10
+ /** Session data without the adapter-owned `jti`, `iat`, `exp` and `nbf`. */
11
+ export type SessionData<T extends SessionRecord = SessionClaims> = StrictOmit<T, "jti" | "iat" | "exp" | "nbf">;
12
+ /** Why `onUpdate` fired: an `update()` call, or `rolling` reissuing a session that was read. */
13
+ export type SessionUpdateTrigger = "update" | "rolling";
14
+ export type SessionUpdate<T extends SessionRecord = SessionClaims> = Partial<SessionData<T>> | ((oldData: SessionData<T>) => Partial<SessionData<T>> | undefined);
15
+ export interface SessionManager<T extends SessionRecord = SessionClaims, ConfigMaxAge extends ExpiresIn | undefined = ExpiresIn | undefined> {
16
+ readonly id: string | undefined;
17
+ readonly createdAt: number;
18
+ /** Expiry in ms — always a number when the session has a `maxAge`. */
19
+ readonly expiresAt: ConfigMaxAge extends ExpiresIn ? number : number | undefined;
20
+ readonly data: SessionData<T>;
21
+ readonly token: string | undefined;
22
+ /**
23
+ * Issues a new token after merging `update`; `options.maxAge` overrides this token's lifetime.
24
+ * Later reads see the data's JSON round trip. Rolls back if issuing or the cookie write fails;
25
+ * a throwing `onUpdate` rejects but keeps the new session.
26
+ */
27
+ update: (update?: SessionUpdate<T>, options?: SessionIssueOptions) => Promise<SessionManager<T, ConfigMaxAge>>;
28
+ clear: () => Promise<SessionManager<T, ConfigMaxAge>>;
29
+ }
30
+ /** The config members every session shares, whether it signs or seals its token. */
31
+ interface SessionBaseConfig<MaxAge extends ExpiresIn | undefined = ExpiresIn | undefined> {
32
+ /** Sets `exp = iat + maxAge`, requires `exp` on incoming tokens, drives cookie expiry; unset never expires. */
33
+ maxAge?: MaxAge;
34
+ /** Reissues a session on read, so `exp` slides: `true` on every read, a duration once less than that remains. Needs `maxAge`. */
35
+ rolling?: true | ExpiresIn;
36
+ /** Drives the cookie name and default header; prefer `__Host-`; a trailing `.<digits>` is refused. */
37
+ name?: string;
38
+ /** Header carrying a bare or `Bearer` token, or `false`. Default `x-<name>-session` (cookie prefix stripped). */
39
+ sessionHeader?: false | string;
40
+ /** Session ID (`jti`) generator. Default `crypto.randomUUID`. */
41
+ generateId?: () => string;
42
+ }
43
+ /**
44
+ * Lifecycle hooks; `onRead`, `onExpire` and `onError` are mutually exclusive per incoming token.
45
+ * `session` is the live manager, so a hook can `update()` or `clear()` in place; `oldSession` is a snapshot.
46
+ */
47
+ interface SessionHooks<TSession, TManager, TCarrier extends Record<string, unknown>, TConfig> {
48
+ /** Fires once per request, when the session is read from its token. */
49
+ onRead?: (args: TCarrier & {
50
+ session: TManager & {
51
+ id: string;
52
+ token: string;
53
+ };
54
+ config: TConfig;
55
+ }) => void | Promise<void>;
56
+ /** Fires after a token is issued and its cookie written, by `update()` or by `rolling`; a throw rejects without rollback. */
57
+ onUpdate?: (args: TCarrier & {
58
+ session: TManager & {
59
+ id: string;
60
+ token: string;
61
+ };
62
+ /** Deep-copied snapshot of the session before the update. */
63
+ oldSession: TSession;
64
+ trigger: SessionUpdateTrigger;
65
+ config: TConfig;
66
+ }) => void | Promise<void>;
67
+ /** Fires when the session is cleared. */
68
+ onClear?: (args: TCarrier & {
69
+ oldSession: TSession | undefined;
70
+ config: TConfig;
71
+ }) => void | Promise<void>;
72
+ /** Fires when the incoming token is expired. Receives the expired token's decoded claims. */
73
+ onExpire?: (args: TCarrier & {
74
+ session: {
75
+ id: string | undefined;
76
+ createdAt: number | undefined;
77
+ expiresAt: number | undefined;
78
+ token: string;
79
+ };
80
+ error: Error;
81
+ config: TConfig;
82
+ }) => void | Promise<void>;
83
+ /** Fires on read-path failures other than expiry, and on write-path issue failures. */
84
+ onError?: (args: TCarrier & {
85
+ session: TManager;
86
+ error: any;
87
+ config: TConfig;
88
+ }) => void | Promise<void>;
89
+ }
90
+ /** In-flight read of a session, awaited by a second reader on the same request. */
91
+ declare const kSessionPending: unique symbol;
92
+ /** In-request session state, mirroring the claims of the underlying token. */
93
+ export interface SessionState<T extends SessionRecord, MaxAge extends ExpiresIn | undefined> {
94
+ /** JWT `jti`; `undefined` until the session is persisted. */
95
+ id: string | undefined;
96
+ /** In ms; mirrors the JWT `iat` claim (seconds). */
97
+ createdAt: number;
98
+ /** In ms; mirrors the JWT `exp` claim (seconds). */
99
+ expiresAt: MaxAge extends ExpiresIn ? number : number | undefined;
100
+ data: SessionData<T>;
101
+ token: string | undefined;
102
+ [kSessionPending]?: Promise<SessionState<T, MaxAge>>;
103
+ }
104
+ /** HS* `oct` JWK, `CryptoKey` (requires pinned `alg`), or a pair: private signs, public JWK/list/set verifies. */
105
+ type JWSSessionKey = CryptoKey | JWK_oct<JWK_HMAC> | {
106
+ privateKey: JWSAsymmetricPrivateJWK | CryptoKey;
107
+ publicKey: JWSAsymmetricPublicJWK | JWSAsymmetricPublicJWK[] | JWKSet | CryptoKey;
108
+ };
109
+ /** JWS verify overrides, minus the options the adapter owns. */
110
+ export type SessionVerifyOptions = StrictOmit<JWSVerifyOptions, "algorithms" | "typ" | "maxTokenAge" | "validateClaims" | "forceUint8Array" | "payload">;
111
+ /** JWS sign options, minus those setting `exp`/`nbf` (the adapter mints `exp` from `maxAge`). */
112
+ export type SessionSignOptions = StrictOmit<JWSSignOptions, "expiresIn" | "expiresAt" | "notBeforeIn" | "notBeforeAt">;
113
+ /** JWS sign/verify overrides a session accepts. */
114
+ interface JWSSessionOptions {
115
+ signOptions?: SessionSignOptions;
116
+ verifyOptions?: SessionVerifyOptions;
117
+ }
118
+ /** The config of a JWS-backed (signed, not encrypted) session, minus its hooks. */
119
+ export interface JWSSessionConfig<MaxAge extends ExpiresIn | undefined = ExpiresIn | undefined, TCookie extends object = object> extends SessionBaseConfig<MaxAge> {
120
+ /** Key used for signing — see {@link JWSSessionKey}. */
121
+ key: JWSSessionKey;
122
+ /** `false` to disable. Defaults: `path="/"`, `secure`, `sameSite: "lax"`, `httpOnly: false` (payload is client-readable). */
123
+ cookie?: false | (TCookie & {
124
+ chunkMaxLength?: number;
125
+ });
126
+ /** Pinned `signOptions.alg` restricts verification; `protectedHeader.typ` defaults to `"session+jwt"` (RFC 8725 §3.11). */
127
+ jws?: JWSSessionOptions;
128
+ }
129
+ /** JWS session hooks, plus the verify key lookup. */
130
+ export interface JWSSessionHooks<TSession, TManager, TCarrier extends Record<string, unknown>, TConfig> extends SessionHooks<TSession, TManager, TCarrier, TConfig> {
131
+ /** Resolve the verification key(s) for the incoming token from its protected header (e.g. by `kid`). */
132
+ onVerifyKeyLookup?: (args: TCarrier & {
133
+ header: JWKLookupFunctionHeader;
134
+ config: TConfig;
135
+ }) => JWKSet | JWSVerifyJWK | Promise<JWKSet | JWSVerifyJWK>;
136
+ }
137
+ /** Symmetric JWK, `CryptoKey` (requires pinned `alg` and `enc`), or a pair: public seals, private unseals. */
138
+ type JWESessionKey = CryptoKey | JWEEncryptJWK | {
139
+ privateKey: JWEAsymmetricPrivateJWK | CryptoKey;
140
+ publicKey?: JWEAsymmetricPublicJWK | CryptoKey;
141
+ };
142
+ /** JWE decrypt overrides, minus the options the adapter owns. */
143
+ export type SessionDecryptOptions = StrictOmit<JWEDecryptOptions, "algorithms" | "encryptionAlgorithms" | "typ" | "maxTokenAge" | "validateClaims" | "forceUint8Array" | "payload">;
144
+ /** JWE encrypt options, minus those setting `exp`/`nbf` (the adapter mints `exp` from `maxAge`). */
145
+ export type SessionEncryptOptions = StrictOmit<JWEEncryptOptions, "expiresIn" | "expiresAt" | "notBeforeIn" | "notBeforeAt">;
146
+ /** JWE encrypt/decrypt overrides a session accepts. */
147
+ interface JWESessionOptions {
148
+ encryptOptions?: SessionEncryptOptions;
149
+ decryptOptions?: SessionDecryptOptions;
150
+ }
151
+ /** The config of a JWE-backed (encrypted) session, minus its hooks. */
152
+ export interface JWESessionConfig<MaxAge extends ExpiresIn | undefined = ExpiresIn | undefined, TCookie extends object = object> extends SessionBaseConfig<MaxAge> {
153
+ /** Key used for sealing — see {@link JWESessionKey}. */
154
+ key: JWESessionKey;
155
+ /** `false` to disable. Defaults: `path="/"`, `secure`, `sameSite: "lax"`, `httpOnly: true`. */
156
+ cookie?: false | (TCookie & {
157
+ chunkMaxLength?: number;
158
+ });
159
+ /** Pinned `encryptOptions.alg`/`enc` restrict unsealing; `protectedHeader.typ` defaults to `"session+jwt"` (RFC 8725 §3.11). */
160
+ jwe?: JWESessionOptions;
161
+ }
162
+ /** JWE session hooks, plus the unseal key lookup. */
163
+ export interface JWESessionHooks<TSession, TManager, TCarrier extends Record<string, unknown>, TConfig> extends SessionHooks<TSession, TManager, TCarrier, TConfig> {
164
+ /** Resolve the decryption key for the incoming token from its protected header (e.g. by `kid`). */
165
+ onUnsealKeyLookup?: (args: TCarrier & {
166
+ header: JWEHeaderParameters;
167
+ config: TConfig;
168
+ }) => JWEDecryptJWK | Promise<JWEDecryptJWK>;
169
+ }
170
+ export {};