unjwt 0.8.5 → 0.9.1
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.
- package/README.md +34 -21
- package/dist/_chunks/algorithms.mjs +44 -20
- package/dist/_chunks/digest.d.mts +4 -4
- package/dist/_chunks/digest.mjs +23 -15
- package/dist/_chunks/error.d.mts +29 -42
- package/dist/_chunks/export.mjs +1 -4
- package/dist/_chunks/index.d.mts +270 -234
- package/dist/_chunks/index2.d.mts +82 -189
- package/dist/_chunks/index4.d.mts +223 -60
- package/dist/_chunks/index5.d.mts +68 -31
- package/dist/_chunks/index6.d.mts +45 -64
- package/dist/_chunks/jwe.mjs +53 -48
- package/dist/_chunks/jwk.mjs +9 -3
- package/dist/_chunks/jws.mjs +55 -50
- package/dist/_chunks/jwt.d.mts +13 -49
- package/dist/_chunks/jwt.mjs +4 -158
- package/dist/_chunks/jwt2.mjs +162 -0
- package/dist/_chunks/libs/unsecure.d.mts +0 -6
- package/dist/_chunks/limits.d.mts +3 -2
- package/dist/_chunks/payload.mjs +13 -7
- package/dist/_chunks/wrap.mjs +3 -1
- package/dist/core/internal.d.mts +50 -2
- package/dist/core/internal.mjs +3 -1
- package/dist/core/jwe/index.d.mts +1 -1
- package/dist/core/jwk/index.d.mts +2 -2
- package/dist/core/jwk/index.mjs +2 -2
- package/dist/dpop/index.d.mts +77 -18
- package/dist/index.d.mts +6 -6
- package/dist/index.mjs +4 -4
- package/dist/session/index.d.mts +568 -22
- package/dist/session/index.mjs +1386 -34
- package/dist/utils/index.d.mts +4 -4
- package/dist/utils/index.mjs +2 -10
- package/package.json +4 -32
- package/dist/_chunks/codec-jwe.d.mts +0 -280
- package/dist/_chunks/codec-jws.mjs +0 -1005
- package/dist/_chunks/cookie-attributes.d.mts +0 -12
- package/dist/_chunks/cookie.mjs +0 -50
- package/dist/adapters/elysia/index.d.mts +0 -123
- package/dist/adapters/elysia/index.mjs +0 -120
- package/dist/adapters/h3/index.d.mts +0 -135
- package/dist/adapters/h3/index.mjs +0 -117
- package/dist/adapters/h3v1/index.d.mts +0 -148
- package/dist/adapters/h3v1/index.mjs +0 -128
package/README.md
CHANGED
|
@@ -8,13 +8,14 @@ A collection of JWT utilities based on the Web Crypto API, in a single zero-depe
|
|
|
8
8
|
|
|
9
9
|
## Features
|
|
10
10
|
|
|
11
|
-
- **JWS** ([RFC 7515](https://
|
|
12
|
-
- **JWE** ([RFC 7516](https://
|
|
13
|
-
- **JWK** ([RFC 7517](https://
|
|
14
|
-
- **DPoP** ([RFC 9449](https://www.rfc-editor.org/rfc/rfc9449))
|
|
15
|
-
- **Sessions
|
|
16
|
-
- **
|
|
17
|
-
- **
|
|
11
|
+
- **JWS** ([RFC 7515](https://www.rfc-editor.org/rfc/rfc7515)): HMAC, RSA, RSA-PSS, ECDSA, EdDSA. Compact and General/Flattened JSON serialization (several signatures over one payload).
|
|
12
|
+
- **JWE** ([RFC 7516](https://www.rfc-editor.org/rfc/rfc7516)): `dir`, AES-KW, AES-GCM-KW, RSA-OAEP, PBES2, ECDH-ES, with AES-GCM and AES-CBC-HMAC-SHA2 content encryption. Compact and General/Flattened JSON serialization (several recipients for one ciphertext).
|
|
13
|
+
- **JWK** ([RFC 7517](https://www.rfc-editor.org/rfc/rfc7517)): generate, import, export, wrap and unwrap keys; `CryptoKey`, JWK and PEM conversion; thumbprints as key IDs; JWK Set lookup and rotation.
|
|
14
|
+
- **DPoP** ([RFC 9449](https://www.rfc-editor.org/rfc/rfc9449)): create and verify proofs with [`unjwt/dpop`](https://unjwt.s94.dev/dpop). Your application owns HTTP and replay storage.
|
|
15
|
+
- **Sessions**: signed or encrypted cookie sessions with [`unjwt/session`](https://unjwt.s94.dev/sessions) over a Web `Request`, on [h3](https://unjwt.s94.dev/examples/h3), [Nitro](https://unjwt.s94.dev/examples/nitro), [Nuxt](https://unjwt.s94.dev/examples/nuxt), [Hono and Workers](https://unjwt.s94.dev/examples/hono), Bun and Deno, or on a token in hand; a [driver](https://unjwt.s94.dev/sessions/drivers) for any other framework.
|
|
16
|
+
- **One typed error**: every failure is a `JWTError` with a `code` to branch on.
|
|
17
|
+
- **Runtime-agnostic**: Node.js 22+, Bun, Deno, Cloudflare Workers and browsers, anywhere Web Crypto runs.
|
|
18
|
+
- **Zero runtime dependencies**, no peer dependencies.
|
|
18
19
|
|
|
19
20
|
## Install
|
|
20
21
|
|
|
@@ -32,30 +33,42 @@ npx skills add sandros94/unjwt
|
|
|
32
33
|
## Quickstart
|
|
33
34
|
|
|
34
35
|
```ts
|
|
35
|
-
// Sign
|
|
36
|
+
// Sign with the private half, verify with the public one (JWS)
|
|
36
37
|
import { sign, verify } from "unjwt/jws";
|
|
37
38
|
import { generateJWK } from "unjwt/jwk";
|
|
38
39
|
|
|
39
|
-
const
|
|
40
|
-
const token = await sign({ sub: "
|
|
41
|
-
const { payload } = await verify(token,
|
|
42
|
-
// { sub: "
|
|
40
|
+
const { privateKey, publicKey } = await generateJWK("ES256");
|
|
41
|
+
const token = await sign({ sub: "user_1a2b" }, privateKey, { expiresIn: "15m" });
|
|
42
|
+
const { payload } = await verify(token, publicKey);
|
|
43
|
+
// { sub: "user_1a2b", iat: …, exp: … }
|
|
43
44
|
|
|
44
|
-
// Encrypt
|
|
45
|
+
// Encrypt for a recipient's public key, decrypt with its private key (JWE)
|
|
45
46
|
import { encrypt, decrypt } from "unjwt/jwe";
|
|
46
47
|
|
|
47
|
-
const
|
|
48
|
-
const
|
|
48
|
+
const recipient = await generateJWK("ECDH-ES+A256KW");
|
|
49
|
+
const jwe = await encrypt({ invoice: "inv_2026_0042" }, recipient.publicKey);
|
|
50
|
+
const opened = await decrypt(jwe, recipient.privateKey);
|
|
51
|
+
|
|
52
|
+
// Keep a session in a cookie on any Web Request
|
|
53
|
+
import { defineJWESession } from "unjwt/session";
|
|
54
|
+
|
|
55
|
+
const appSession = defineJWESession({ key: await generateJWK("A256GCM"), maxAge: "7D" });
|
|
56
|
+
const headers = new Headers();
|
|
57
|
+
const session = await appSession.use({ req: request, res: { headers } });
|
|
58
|
+
await session.update({ user: "user_1a2b" }); // Set-Cookie lands on `headers`
|
|
49
59
|
```
|
|
50
60
|
|
|
51
|
-
|
|
61
|
+
Walkthroughs, recipes and the full API live at **[unjwt.s94.dev](https://unjwt.s94.dev)**:
|
|
52
62
|
|
|
53
63
|
- [Getting started →](https://unjwt.s94.dev/getting-started)
|
|
54
|
-
- [
|
|
55
|
-
- [
|
|
56
|
-
- [JWK
|
|
57
|
-
- [Sessions
|
|
58
|
-
- [
|
|
64
|
+
- [Sign and verify (JWS) →](https://unjwt.s94.dev/jws)
|
|
65
|
+
- [Encrypt and decrypt (JWE) →](https://unjwt.s94.dev/jwe)
|
|
66
|
+
- [Keys (JWK) →](https://unjwt.s94.dev/jwk)
|
|
67
|
+
- [Sessions →](https://unjwt.s94.dev/sessions)
|
|
68
|
+
- [Safety: errors, claims and limits →](https://unjwt.s94.dev/safety)
|
|
69
|
+
- [DPoP proofs →](https://unjwt.s94.dev/dpop)
|
|
70
|
+
- [Recipes →](https://unjwt.s94.dev/examples): sessions on h3, Nitro, Nuxt, Hono and Workers, CDN imports, custom drivers.
|
|
71
|
+
- [Migration guides →](https://unjwt.s94.dev/getting-started/migration)
|
|
59
72
|
|
|
60
73
|
## Development
|
|
61
74
|
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { safeJsonParse, sanitizeObjectCopy, textEncoder } from "./libs/unsecure.mjs";
|
|
2
2
|
import { JWTError } from "../error.mjs";
|
|
3
|
-
import { assertClaimValidationOptions,
|
|
4
|
-
import {
|
|
3
|
+
import { assertClaimValidationOptions, assertCryptoKey, assertPlainObject, assertStringList, base64Parse, base64Stringify, base64UrlParse, base64UrlStringify, concatUint8Arrays, hasMisplacedSecret, isCryptoKey, isCryptoKeyPair, isJWK, isJWKSet, isPlainObject, isPublicJWK, uint32be } from "./jwt.mjs";
|
|
4
|
+
import { assertCritShape, quoteTokenValue } from "./jwt2.mjs";
|
|
5
|
+
import { ECDSA_CURVES, aesBitsOf, checkKeyUsage, cryptoKeyCurve, digestUrl, hashBitsOf, inferJWSAllowedAlgorithms, innerKeyWrapAlg, isAlgorithm, isContentEncryptionAlgorithm, isECDHESAlgorithm, isPBES2Algorithm, isSameAlgorithm, jwkAlgServes, jwkKtyServes, jwsHashOf, lengthOf, modulusLengthOf, namedCurveOf, pbes2HashOf, rsaOaepHashOf, shaBitsOf, unusableKey } from "./digest.mjs";
|
|
5
6
|
function serializeProtectedHeader(header) {
|
|
6
7
|
try {
|
|
7
8
|
return JSON.stringify(header);
|
|
@@ -1242,6 +1243,7 @@ const DEFAULT_JOSE_LIMITS = /* @__PURE__ */ Object.freeze({
|
|
|
1242
1243
|
signatures: 16,
|
|
1243
1244
|
recipients: 16,
|
|
1244
1245
|
keyCandidates: 8,
|
|
1246
|
+
keyAttempts: 16,
|
|
1245
1247
|
pbes2Iterations: 4 * DEFAULT_PBES2_ITERATIONS
|
|
1246
1248
|
});
|
|
1247
1249
|
function resolveLimit(limits, name) {
|
|
@@ -1258,6 +1260,23 @@ function assertCandidateLimit(count, limit) {
|
|
|
1258
1260
|
if (count <= limit) return;
|
|
1259
1261
|
throw new JWTError(`${count} candidate keys exceed the limit of ${limit}, narrow them with a "kid".`, "ERR_JWK_KEY_NOT_FOUND");
|
|
1260
1262
|
}
|
|
1263
|
+
function createKeyAttemptBudget(limit) {
|
|
1264
|
+
let remaining = limit;
|
|
1265
|
+
let exhausted;
|
|
1266
|
+
return {
|
|
1267
|
+
get exhausted() {
|
|
1268
|
+
return exhausted;
|
|
1269
|
+
},
|
|
1270
|
+
spend() {
|
|
1271
|
+
if (remaining > 0) {
|
|
1272
|
+
remaining--;
|
|
1273
|
+
return;
|
|
1274
|
+
}
|
|
1275
|
+
exhausted ??= new JWTError(`Key attempt budget of ${limit} ("limits.keyAttempts") exhausted, narrow the keys with a "kid".`, "ERR_JWK_KEY_NOT_FOUND");
|
|
1276
|
+
throw exhausted;
|
|
1277
|
+
}
|
|
1278
|
+
};
|
|
1279
|
+
}
|
|
1261
1280
|
function assertJWKSetCandidates(candidates, options) {
|
|
1262
1281
|
if (candidates.length === 0) {
|
|
1263
1282
|
const { kid } = options;
|
|
@@ -1304,12 +1323,11 @@ const JWK_SET_KEY_OPS = /* @__PURE__ */ Object.freeze({
|
|
|
1304
1323
|
function buildJWKSetFilter(header, options) {
|
|
1305
1324
|
const kid = header?.kid;
|
|
1306
1325
|
const alg = header?.alg;
|
|
1307
|
-
const { op
|
|
1326
|
+
const { op } = options;
|
|
1308
1327
|
const use = op;
|
|
1309
1328
|
const keyOps = JWK_SET_KEY_OPS[op];
|
|
1310
1329
|
return (k) => {
|
|
1311
|
-
if (!isJWK(k)) return false;
|
|
1312
|
-
if (!allowSymmetric && hasSymmetricSecret(k)) return false;
|
|
1330
|
+
if (!isJWK(k) || hasMisplacedSecret(k)) return false;
|
|
1313
1331
|
if (kid && k.kid !== kid) return false;
|
|
1314
1332
|
if (k.alg && !(alg && jwkAlgServes({
|
|
1315
1333
|
kty: k.kty,
|
|
@@ -1381,7 +1399,10 @@ async function resolveKeyInput(key, options) {
|
|
|
1381
1399
|
const { header, token, what } = options;
|
|
1382
1400
|
let resolved;
|
|
1383
1401
|
try {
|
|
1384
|
-
resolved = await key(
|
|
1402
|
+
resolved = await key({
|
|
1403
|
+
header,
|
|
1404
|
+
token
|
|
1405
|
+
});
|
|
1385
1406
|
} catch (error) {
|
|
1386
1407
|
throw asKeyLookupError(error, what);
|
|
1387
1408
|
}
|
|
@@ -1422,7 +1443,7 @@ async function importKey(key, algOrOptions) {
|
|
|
1422
1443
|
return key;
|
|
1423
1444
|
}
|
|
1424
1445
|
if (isJWK(key)) {
|
|
1425
|
-
if (
|
|
1446
|
+
if (hasMisplacedSecret(key)) throw new JWTError(`JWK "k" is defined only for "oct" keys, not "${key.kty}".`, "ERR_JWK_INVALID");
|
|
1426
1447
|
assertJWKAlgAuthority(key, alg);
|
|
1427
1448
|
if (key.kty === "oct" && typeof key.k === "string") {
|
|
1428
1449
|
const rawBytes = decodeSegment(key.k, {
|
|
@@ -1462,11 +1483,16 @@ async function importKey(key, algOrOptions) {
|
|
|
1462
1483
|
throw new JWTError("Invalid key type provided to importKey", "ERR_JWK_INVALID");
|
|
1463
1484
|
}
|
|
1464
1485
|
function assertJWKAlgAuthority(jwk, alg) {
|
|
1465
|
-
if (!alg
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1486
|
+
if (!alg) return;
|
|
1487
|
+
if (jwk.alg) {
|
|
1488
|
+
if (jwkAlgServes({
|
|
1489
|
+
kty: jwk.kty,
|
|
1490
|
+
alg: jwk.alg
|
|
1491
|
+
}, alg)) return;
|
|
1492
|
+
throw new JWTError(`JWK "alg" is "${jwk.alg}" but the operation requires "${alg}".`, "ERR_JWK_INVALID");
|
|
1493
|
+
}
|
|
1494
|
+
if (jwkKtyServes(jwk.kty, alg)) return;
|
|
1495
|
+
throw new JWTError(`JWK "kty" is "${jwk.kty}", which cannot serve "${alg}".`, "ERR_JWK_INVALID");
|
|
1470
1496
|
}
|
|
1471
1497
|
function assertIntentOnJWK(jwk, expect) {
|
|
1472
1498
|
if (expect === void 0 || jwk.kty === "oct") return;
|
|
@@ -1492,10 +1518,11 @@ const JWS_ALG_CTX = /* @__PURE__ */ Object.freeze({
|
|
|
1492
1518
|
op: "sig"
|
|
1493
1519
|
});
|
|
1494
1520
|
function checkAlgAllowed(alg, key, options) {
|
|
1495
|
-
const { ctx, algorithms
|
|
1521
|
+
const { ctx, algorithms } = options;
|
|
1496
1522
|
if (algorithms !== void 0) return algorithms.includes(alg) ? void 0 : notAllowed(alg, ctx);
|
|
1497
1523
|
if (isJWK(key) && typeof key.alg === "string" && (!ctx.octRoles || key.kty !== "oct")) return isSameAlgorithm(key.alg, alg) ? void 0 : notAllowed(alg, ctx);
|
|
1498
|
-
|
|
1524
|
+
if (key === void 0 || key === null) return new JWTError("A key is required.", "ERR_JWK_INVALID");
|
|
1525
|
+
const inferred = ctx.infer(key);
|
|
1499
1526
|
if (!inferred) return new JWTError("Cannot infer allowed algorithms from this key, pass \"algorithms\".", ctx.errorCode);
|
|
1500
1527
|
return inferred.includes(alg) ? void 0 : notAllowed(alg, ctx);
|
|
1501
1528
|
}
|
|
@@ -1503,12 +1530,9 @@ function notAllowed(alg, ctx) {
|
|
|
1503
1530
|
return new JWTError(`${ctx.label} not allowed: ${alg}`, ctx.errorCode);
|
|
1504
1531
|
}
|
|
1505
1532
|
function keyMatchesHeader(header, keyMaterial, options) {
|
|
1506
|
-
const { ctx
|
|
1533
|
+
const { ctx } = options;
|
|
1507
1534
|
if (isJWKSet(keyMaterial)) {
|
|
1508
|
-
const candidates = keyMaterial.keys.filter(buildJWKSetFilter(header, {
|
|
1509
|
-
op: ctx.op,
|
|
1510
|
-
allowSymmetric
|
|
1511
|
-
}));
|
|
1535
|
+
const candidates = keyMaterial.keys.filter(buildJWKSetFilter(header, { op: ctx.op }));
|
|
1512
1536
|
return candidates.length === 0 || candidates.some((k) => keyServesHeaderAlg(header, k, ctx));
|
|
1513
1537
|
}
|
|
1514
1538
|
if (isJWK(keyMaterial)) return keyServesHeaderAlg(header, keyMaterial, ctx);
|
|
@@ -1538,4 +1562,4 @@ function keyServesHeaderAlg(header, key, ctx) {
|
|
|
1538
1562
|
const keyCurve = isCryptoKey(key) ? cryptoKeyCurve(key) : "crv" in key ? key.crv : void 0;
|
|
1539
1563
|
return !epkCurve || !keyCurve || epkCurve === keyCurve;
|
|
1540
1564
|
}
|
|
1541
|
-
export { DEFAULT_JOSE_LIMITS, DEFAULT_PBES2_ITERATIONS, JWS_ALG_CTX, MAX_PARTY_INFO_BYTES, WeakMapJWKCache, aesKwUnwrap, aesKwWrap, asDecryptionError, asJWTError, asKeyLookupError, assertCompactToken, assertConsumerOptions, assertDirectKeyRole, assertECDHKeyAllowed, assertEntryLimit, assertJWEEncSupported, assertJWKSetCandidates, assertOctKeyLength, assertOptionEntry, assertPBES2Count, assertPBES2Iterations, assertPBES2Params, assertPBES2Salt, assertProducerConfirmation, assertProducerCrit, assertProducerKeyHeaders, assertProtectedOnlyOption, assertSerializationObject, bitLengthCEK, bitLengthIV, bitLengthTag, buildECDHAgreementParams, buildJWKSetFilter, checkAlgAllowed, checkAlgSupported, checkJWEEncSupported, checkProtectedTyp, checkSerializationEntry, clearJWKCache, configureJWKCache, copyJsonObject, decodeProtectedHeader, decodeSegment, decrypt, decryptRSAES, deriveECDHESKey, deriveKey, ecdhDerivedBits, ecdhesWrap, encrypt, encryptKey, encryptRSAES, fromPKCS8, fromSPKI, fromX509, gcmkwDecrypt, gcmkwEncrypt, generateCEK, generateIV, getJWKsFromSet, importKey, jwkTokey, keyMatchesHeader, keyToJWK, normalizeEpk, pbes2Unwrap, pbes2Wrap, resolveKeyInput, resolveLimit, resolvePasswordKey, serializeProtectedHeader, sign, stringKeyError, thumbprint, toPKCS8, toSPKI, verify };
|
|
1565
|
+
export { DEFAULT_JOSE_LIMITS, DEFAULT_PBES2_ITERATIONS, JWS_ALG_CTX, MAX_PARTY_INFO_BYTES, WeakMapJWKCache, aesKwUnwrap, aesKwWrap, asDecryptionError, asJWTError, asKeyLookupError, assertCompactToken, assertConsumerOptions, assertDirectKeyRole, assertECDHKeyAllowed, assertEntryLimit, assertJWEEncSupported, assertJWKSetCandidates, assertOctKeyLength, assertOptionEntry, assertPBES2Count, assertPBES2Iterations, assertPBES2Params, assertPBES2Salt, assertProducerConfirmation, assertProducerCrit, assertProducerKeyHeaders, assertProtectedOnlyOption, assertSerializationObject, bitLengthCEK, bitLengthIV, bitLengthTag, buildECDHAgreementParams, buildJWKSetFilter, checkAlgAllowed, checkAlgSupported, checkJWEEncSupported, checkProtectedTyp, checkSerializationEntry, clearJWKCache, configureJWKCache, copyJsonObject, createKeyAttemptBudget, decodeProtectedHeader, decodeSegment, decrypt, decryptRSAES, deriveECDHESKey, deriveKey, ecdhDerivedBits, ecdhesWrap, encrypt, encryptKey, encryptRSAES, fromPKCS8, fromSPKI, fromX509, gcmkwDecrypt, gcmkwEncrypt, generateCEK, generateIV, getJWKsFromSet, importKey, jwkTokey, keyMatchesHeader, keyToJWK, normalizeEpk, pbes2Unwrap, pbes2Wrap, resolveKeyInput, resolveLimit, resolvePasswordKey, serializeProtectedHeader, sign, stringKeyError, thumbprint, toPKCS8, toSPKI, verify };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/** The hash functions the JOSE specs name for digests in token members. */
|
|
2
2
|
export type DigestHash = "SHA-256" | "SHA-384" | "SHA-512";
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Returns the unpadded base64url digest of `data` under `hash`, `SHA-256` when omitted.
|
|
5
|
+
* A string is digested as its UTF-8 bytes.
|
|
6
|
+
* Examples: a DPoP `ath` (RFC 9449 §4.2), an `x5t#S256`, a JWK thumbprint.
|
|
7
7
|
*/
|
|
8
8
|
export declare function digestUrl(data: string | Uint8Array<ArrayBuffer>, hash?: DigestHash): Promise<string>;
|
package/dist/_chunks/digest.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { textEncoder } from "./libs/unsecure.mjs";
|
|
2
2
|
import { JWTError } from "../error.mjs";
|
|
3
|
-
import { base64UrlStringify,
|
|
3
|
+
import { base64UrlStringify, hasMisplacedSecret, isCryptoKey, isCryptoKeyPair, isJWK, isJWKSet } from "./jwt.mjs";
|
|
4
4
|
const ECDSA_CURVES = /* @__PURE__ */ Object.freeze({
|
|
5
5
|
ES256: "P-256",
|
|
6
6
|
ES384: "P-384",
|
|
@@ -127,30 +127,29 @@ const ECDH_ES_ALGORITHM_SET = /* @__PURE__ */ new Set(ECDH_ES_ALGORITHMS);
|
|
|
127
127
|
function isECDHESAlgorithm(alg) {
|
|
128
128
|
return ECDH_ES_ALGORITHM_SET.has(alg);
|
|
129
129
|
}
|
|
130
|
-
function inferJWSAllowedAlgorithms(key
|
|
130
|
+
function inferJWSAllowedAlgorithms(key) {
|
|
131
131
|
if (!key || typeof key === "function") return void 0;
|
|
132
132
|
if (typeof key === "string" || key instanceof Uint8Array) return void 0;
|
|
133
133
|
if (typeof key !== "object") return void 0;
|
|
134
134
|
if (isCryptoKeyPair(key)) return inferJWSAllowedAlgorithms(key.publicKey);
|
|
135
|
-
if (isJWKSet(key)) return unionFromJWKSet(key.keys, jwsAlgsFromJWK
|
|
135
|
+
if (isJWKSet(key)) return unionFromJWKSet(key.keys, jwsAlgsFromJWK);
|
|
136
136
|
if (isCryptoKey(key)) return jwsAlgsFromCryptoKey(key);
|
|
137
137
|
if (isJWK(key)) return jwsAlgsFromJWK(key);
|
|
138
138
|
}
|
|
139
|
-
function inferJWEAllowedAlgorithms(key
|
|
139
|
+
function inferJWEAllowedAlgorithms(key) {
|
|
140
140
|
if (!key || typeof key === "function") return void 0;
|
|
141
141
|
if (typeof key === "string") return [...PBES2_ALGORITHMS];
|
|
142
142
|
if (key instanceof Uint8Array) return void 0;
|
|
143
143
|
if (typeof key !== "object") return void 0;
|
|
144
144
|
if (isCryptoKeyPair(key)) return inferJWEAllowedAlgorithms(key.privateKey);
|
|
145
|
-
if (isJWKSet(key)) return unionFromJWKSet(key.keys, jweAlgsFromJWK
|
|
145
|
+
if (isJWKSet(key)) return unionFromJWKSet(key.keys, jweAlgsFromJWK);
|
|
146
146
|
if (isCryptoKey(key)) return jweAlgsFromCryptoKey(key);
|
|
147
147
|
if (isJWK(key)) return jweAlgsFromJWK(key);
|
|
148
148
|
}
|
|
149
|
-
function unionFromJWKSet(keys, mapper
|
|
149
|
+
function unionFromJWKSet(keys, mapper) {
|
|
150
150
|
const algs = /* @__PURE__ */ new Set();
|
|
151
151
|
for (const k of keys) {
|
|
152
|
-
if (!isJWK(k)) continue;
|
|
153
|
-
if (!options?.allowSymmetric && hasSymmetricSecret(k)) continue;
|
|
152
|
+
if (!isJWK(k) || hasMisplacedSecret(k)) continue;
|
|
154
153
|
const mapped = mapper(k);
|
|
155
154
|
if (!mapped) continue;
|
|
156
155
|
for (const a of mapped) algs.add(a);
|
|
@@ -163,6 +162,15 @@ function isEd25519Signature(alg) {
|
|
|
163
162
|
function isSameAlgorithm(a, b) {
|
|
164
163
|
return a === b || isEd25519Signature(a) && isEd25519Signature(b);
|
|
165
164
|
}
|
|
165
|
+
function jwkKtyServes(kty, alg) {
|
|
166
|
+
switch (kty) {
|
|
167
|
+
case "oct": return !/^(RS|PS|ES|Ed|EC)/.test(alg);
|
|
168
|
+
case "RSA": return /^(RS|PS)/.test(alg);
|
|
169
|
+
case "EC": return /^E(S|C)/.test(alg);
|
|
170
|
+
case "OKP": return /^E(d|C)/.test(alg);
|
|
171
|
+
default: return false;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
166
174
|
function jwkAlgServes(jwk, alg) {
|
|
167
175
|
return isSameAlgorithm(jwk.alg, alg) || jwk.kty === "oct" && alg === "dir" && isContentEncryptionAlgorithm(jwk.alg);
|
|
168
176
|
}
|
|
@@ -231,14 +239,14 @@ function jweAlgsFromCryptoKey(key) {
|
|
|
231
239
|
const length = lengthOf(key.algorithm);
|
|
232
240
|
switch (name) {
|
|
233
241
|
case "AES-KW":
|
|
234
|
-
if (length === 128) return ["A128KW"
|
|
235
|
-
if (length === 192) return ["A192KW"
|
|
236
|
-
if (length === 256) return ["A256KW"
|
|
242
|
+
if (length === 128) return ["A128KW"];
|
|
243
|
+
if (length === 192) return ["A192KW"];
|
|
244
|
+
if (length === 256) return ["A256KW"];
|
|
237
245
|
return;
|
|
238
246
|
case "AES-GCM":
|
|
239
|
-
if (length === 128) return ["A128GCMKW"
|
|
240
|
-
if (length === 192) return ["A192GCMKW"
|
|
241
|
-
if (length === 256) return ["A256GCMKW"
|
|
247
|
+
if (length === 128) return ["A128GCMKW"];
|
|
248
|
+
if (length === 192) return ["A192GCMKW"];
|
|
249
|
+
if (length === 256) return ["A256GCMKW"];
|
|
242
250
|
return;
|
|
243
251
|
case "RSA-OAEP":
|
|
244
252
|
if (hashName === "SHA-1") return ["RSA-OAEP"];
|
|
@@ -260,4 +268,4 @@ async function digestUrl(data, hash = "SHA-256") {
|
|
|
260
268
|
const bytes = typeof data === "string" ? textEncoder.encode(data) : data;
|
|
261
269
|
return base64UrlStringify(new Uint8Array(await crypto.subtle.digest(hash, bytes)));
|
|
262
270
|
}
|
|
263
|
-
export { ECDSA_CURVES, PBES2_ALGORITHMS, aesBitsOf, checkKeyUsage, cryptoKeyCurve, digestUrl, hashBitsOf, inferJWEAllowedAlgorithms, inferJWSAllowedAlgorithms, innerKeyWrapAlg, isAlgorithm, isContentEncryptionAlgorithm, isECDHESAlgorithm, isJWSAlgorithm, isKeyManagementAlgorithm, isPBES2Algorithm, isSameAlgorithm, jwkAlgServes, jwsHashOf, lengthOf, modulusLengthOf, namedCurveOf, pbes2HashOf, rsaOaepHashOf, shaBitsOf, unusableKey };
|
|
271
|
+
export { ECDSA_CURVES, PBES2_ALGORITHMS, aesBitsOf, checkKeyUsage, cryptoKeyCurve, digestUrl, hashBitsOf, inferJWEAllowedAlgorithms, inferJWSAllowedAlgorithms, innerKeyWrapAlg, isAlgorithm, isContentEncryptionAlgorithm, isECDHESAlgorithm, isJWSAlgorithm, isKeyManagementAlgorithm, isPBES2Algorithm, isSameAlgorithm, jwkAlgServes, jwkKtyServes, jwsAlgsFromJWK, jwsHashOf, lengthOf, modulusLengthOf, namedCurveOf, pbes2HashOf, rsaOaepHashOf, shaBitsOf, unusableKey };
|
package/dist/_chunks/error.d.mts
CHANGED
|
@@ -1,34 +1,30 @@
|
|
|
1
1
|
import { UnsecureError, UnsecureErrorCode } from "./libs/unsecure.mjs";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Every error code unjwt throws, as a closed union.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
* ERR_JWT_
|
|
7
|
-
* ERR_JWS_
|
|
8
|
-
* ERR_JWE_
|
|
9
|
-
* ERR_JWK_
|
|
10
|
-
* ERR_DPOP_
|
|
11
|
-
* ERR_SESSION_
|
|
5
|
+
* Prefixes follow the specs:
|
|
6
|
+
* ERR_JWT_*: RFC 7519 claim validation
|
|
7
|
+
* ERR_JWS_*: RFC 7515 signatures
|
|
8
|
+
* ERR_JWE_*: RFC 7516 encryption
|
|
9
|
+
* ERR_JWK_*: RFC 7517 keys
|
|
10
|
+
* ERR_DPOP_*: RFC 9449 proofs
|
|
11
|
+
* ERR_SESSION_*: sessions
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* that silently never matches. Use {@link JWTErrorCode} for values that may
|
|
16
|
-
* legitimately carry a code this version does not know about.
|
|
13
|
+
* {@link isJWTError} takes this type, so a misspelled code such as `"ERR_JWS_INVALD"` is a type error.
|
|
14
|
+
* Use {@link JWTErrorCode} for a value that may hold a code outside this union.
|
|
17
15
|
*/
|
|
18
|
-
export type KnownJWTErrorCode = "ERR_JWT_EXPIRED" | "ERR_JWT_NBF" | "ERR_JWT_CLAIM_INVALID" | "ERR_JWT_CLAIM_MISSING" | "ERR_JWS_INVALID" | "ERR_JWS_INVALID_SERIALIZATION" | "ERR_JWS_SIGNATURE_INVALID" | "ERR_JWS_ALG_NOT_ALLOWED" | "ERR_JWS_ALG_MISSING" | "ERR_JWS_SIGNER_ALG_INFERENCE" | "ERR_JWS_NO_MATCHING_SIGNER" | "ERR_JWS_HEADER_PARAMS_NOT_DISJOINT" | "ERR_JWS_B64_INCONSISTENT" | "ERR_JWE_INVALID" | "ERR_JWE_INVALID_SERIALIZATION" | "ERR_JWE_DECRYPTION_FAILED" | "ERR_JWE_ALG_NOT_ALLOWED" | "ERR_JWE_ALG_MISSING" | "ERR_JWE_ALG_FORBIDDEN_IN_MULTI" | "ERR_JWE_ENC_MISSING" | "ERR_JWE_RECIPIENT_ALG_INFERENCE" | "ERR_JWE_NO_MATCHING_RECIPIENT" | "ERR_JWE_HEADER_PARAMS_NOT_DISJOINT" | "ERR_JWK_INVALID" | "ERR_JWK_KEY_NOT_FOUND" | "ERR_JWK_UNSUPPORTED" | "ERR_DPOP_INVALID" | "ERR_DPOP_CLAIM_INVALID" | "ERR_DPOP_NONCE_INVALID" | "ERR_DPOP_REPLAY" | "ERR_SESSION_TOKEN_INVALID" | "ERR_SESSION_READONLY_EVENT" | "ERR_SESSION_KEY_INVALID" | "ERR_SESSION_CONFIG_INVALID" | "ERR_INVALID_OPTION";
|
|
16
|
+
export type KnownJWTErrorCode = "ERR_JWT_EXPIRED" | "ERR_JWT_NBF" | "ERR_JWT_CLAIM_INVALID" | "ERR_JWT_CLAIM_MISSING" | "ERR_JWS_INVALID" | "ERR_JWS_INVALID_SERIALIZATION" | "ERR_JWS_SIGNATURE_INVALID" | "ERR_JWS_ALG_NOT_ALLOWED" | "ERR_JWS_ALG_MISSING" | "ERR_JWS_SIGNER_ALG_INFERENCE" | "ERR_JWS_NO_MATCHING_SIGNER" | "ERR_JWS_HEADER_PARAMS_NOT_DISJOINT" | "ERR_JWS_B64_INCONSISTENT" | "ERR_JWE_INVALID" | "ERR_JWE_INVALID_SERIALIZATION" | "ERR_JWE_DECRYPTION_FAILED" | "ERR_JWE_ALG_NOT_ALLOWED" | "ERR_JWE_ALG_MISSING" | "ERR_JWE_ALG_FORBIDDEN_IN_MULTI" | "ERR_JWE_ENC_MISSING" | "ERR_JWE_RECIPIENT_ALG_INFERENCE" | "ERR_JWE_NO_MATCHING_RECIPIENT" | "ERR_JWE_HEADER_PARAMS_NOT_DISJOINT" | "ERR_JWK_INVALID" | "ERR_JWK_KEY_NOT_FOUND" | "ERR_JWK_UNSUPPORTED" | "ERR_DPOP_INVALID" | "ERR_DPOP_CLAIM_INVALID" | "ERR_DPOP_NONCE_INVALID" | "ERR_DPOP_REPLAY" | "ERR_SESSION_TOKEN_INVALID" | "ERR_SESSION_READ_FAILED" | "ERR_SESSION_READONLY_EVENT" | "ERR_SESSION_KEY_INVALID" | "ERR_SESSION_CONFIG_INVALID" | "ERR_INVALID_OPTION";
|
|
19
17
|
/**
|
|
20
|
-
* A {@link JWTError} `code`:
|
|
21
|
-
*
|
|
22
|
-
* its own `JWTError`) may carry a code this version's union does not list.
|
|
18
|
+
* A {@link JWTError} `code`: any {@link KnownJWTErrorCode} or any other string.
|
|
19
|
+
* A `JWTError` built outside unjwt can hold a code outside the union.
|
|
23
20
|
*/
|
|
24
21
|
export type JWTErrorCode = KnownJWTErrorCode | (string & {});
|
|
25
22
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* Extend this interface when new structured causes are introduced.
|
|
23
|
+
* The structured `cause` of each code that has one.
|
|
24
|
+
* {@link isJWTError} narrows `cause` to this shape for the listed codes.
|
|
29
25
|
*/
|
|
30
26
|
export interface JWTErrorCauseMap {
|
|
31
|
-
/**
|
|
27
|
+
/** The token's `jti` (when a string), `iat` and `exp` claims. */
|
|
32
28
|
ERR_JWT_EXPIRED: {
|
|
33
29
|
jti?: string;
|
|
34
30
|
iat?: number;
|
|
@@ -36,13 +32,13 @@ export interface JWTErrorCauseMap {
|
|
|
36
32
|
};
|
|
37
33
|
}
|
|
38
34
|
/**
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
35
|
+
* The error class of the JWS, JWE, JWK, DPoP and session operations.
|
|
36
|
+
* `code` names the condition. Narrow on it with {@link isJWTError}.
|
|
37
|
+
*
|
|
38
|
+
* The `unjwt/utils` primitives throw {@link UnsecureError} instead.
|
|
43
39
|
*
|
|
44
40
|
* @example
|
|
45
|
-
* import {
|
|
41
|
+
* import { verify, isJWTError } from "unjwt/jws";
|
|
46
42
|
*
|
|
47
43
|
* try {
|
|
48
44
|
* await verify(token, key);
|
|
@@ -58,16 +54,11 @@ export declare class JWTError<TCode extends JWTErrorCode = JWTErrorCode> extends
|
|
|
58
54
|
constructor(message: string, code: TCode, cause?: unknown);
|
|
59
55
|
}
|
|
60
56
|
/**
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
* When called with a code that is a key of `JWTErrorCauseMap`, the return
|
|
64
|
-
* type is additionally narrowed to include the structured `cause` shape for
|
|
65
|
-
* that code. When called with any other `KnownJWTErrorCode`, `code` is narrowed
|
|
66
|
-
* but `cause` remains `unknown`. When called without a code, narrows only to
|
|
67
|
-
* `JWTError`.
|
|
57
|
+
* Returns `true` when `error` is a {@link JWTError}, and with `code`, only when its `code` is that one.
|
|
68
58
|
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
59
|
+
* With a code listed in {@link JWTErrorCauseMap}, `cause` narrows to that shape.
|
|
60
|
+
* With any other code, `code` narrows and `cause` stays `unknown`.
|
|
61
|
+
* `code` is a {@link KnownJWTErrorCode}, so a misspelled code is a type error.
|
|
71
62
|
*
|
|
72
63
|
* @example
|
|
73
64
|
* if (isJWTError(err, "ERR_JWT_EXPIRED")) {
|
|
@@ -80,17 +71,13 @@ export declare function isJWTError<T extends keyof JWTErrorCauseMap>(error: unkn
|
|
|
80
71
|
export declare function isJWTError<T extends KnownJWTErrorCode>(error: unknown, code: T): error is JWTError<T>;
|
|
81
72
|
export declare function isJWTError(error: unknown): error is JWTError;
|
|
82
73
|
/**
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
* `cause` of a {@link JWTError} when a wire segment or key field fails to
|
|
86
|
-
* decode. Pass a `code` to narrow further.
|
|
74
|
+
* Returns `true` when `error` is the {@link UnsecureError} unjwt throws, and with `code`, only when its `code` is that one.
|
|
75
|
+
* Use it on the `cause` of a {@link JWTError} whose token segment or key field failed to decode.
|
|
87
76
|
*
|
|
88
|
-
*
|
|
89
|
-
* consumer-installed `unsecure`: unjwt bundles its own copy, so the two classes
|
|
90
|
-
* are distinct and `instanceof` against the latter is always `false`.
|
|
77
|
+
* A separately installed copy of the class never matches.
|
|
91
78
|
*
|
|
92
79
|
* @example
|
|
93
|
-
* import { isJWTError, isUnsecureError } from "unjwt/jws";
|
|
80
|
+
* import { verify, isJWTError, isUnsecureError } from "unjwt/jws";
|
|
94
81
|
*
|
|
95
82
|
* const error = await verify(token, key).catch((e) => e);
|
|
96
83
|
* if (isJWTError(error, "ERR_JWS_INVALID") && isUnsecureError(error.cause, "MALFORMED")) {
|
package/dist/_chunks/export.mjs
CHANGED
|
@@ -61,9 +61,6 @@ async function exportJWK(key, jwkParams) {
|
|
|
61
61
|
}
|
|
62
62
|
return exportedJwk;
|
|
63
63
|
}
|
|
64
|
-
async function exportKey(key, jwkParams) {
|
|
65
|
-
return exportJWK(key, jwkParams);
|
|
66
|
-
}
|
|
67
64
|
function toPublicJWK(jwk) {
|
|
68
65
|
if (!isAsymmetricJWK(jwk)) throw new JWTError(isJWK(jwk) ? "An oct JWK has no public half." : "toPublicJWK expects an asymmetric JWK.", "ERR_JWK_INVALID");
|
|
69
66
|
const { d: _d, p: _p, q: _q, dp: _dp, dq: _dq, qi: _qi, oth: _oth, k: _k, key_ops, ...publicMembers } = copyJsonObject({ ...jwk }, {
|
|
@@ -94,4 +91,4 @@ function publicKeyOps(keyOps) {
|
|
|
94
91
|
}
|
|
95
92
|
return ops;
|
|
96
93
|
}
|
|
97
|
-
export { assertKidOption, assertNoReservedJWKParams, exportJWK,
|
|
94
|
+
export { assertKidOption, assertNoReservedJWKParams, exportJWK, requestedKeyOps, toPublicJWK, withGeneratedKid, withKeyOps };
|