lacewing 1.0.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.
- package/CHANGELOG.md +11 -0
- package/LICENSE +9 -0
- package/NOTICE.md +1 -0
- package/README.md +313 -0
- package/dist/index.js +40 -0
- package/dist/index.js.map +1 -0
- package/dist/src/cookbook/access-refresh.js +86 -0
- package/dist/src/cookbook/access-refresh.js.map +1 -0
- package/dist/src/http/bearer.js +44 -0
- package/dist/src/http/bearer.js.map +1 -0
- package/dist/src/http/cookies.js +96 -0
- package/dist/src/http/cookies.js.map +1 -0
- package/dist/src/jwks/local.js +85 -0
- package/dist/src/jwks/local.js.map +1 -0
- package/dist/src/jwks/remote.js +121 -0
- package/dist/src/jwks/remote.js.map +1 -0
- package/dist/src/jwt/decrypt.js +126 -0
- package/dist/src/jwt/decrypt.js.map +1 -0
- package/dist/src/jwt/decryption_profile.js +78 -0
- package/dist/src/jwt/decryption_profile.js.map +1 -0
- package/dist/src/jwt/encrypt.js +149 -0
- package/dist/src/jwt/encrypt.js.map +1 -0
- package/dist/src/jwt/profile.js +102 -0
- package/dist/src/jwt/profile.js.map +1 -0
- package/dist/src/jwt/sign.js +173 -0
- package/dist/src/jwt/sign.js.map +1 -0
- package/dist/src/jwt/verify.js +118 -0
- package/dist/src/jwt/verify.js.map +1 -0
- package/dist/src/key/encryption.js +132 -0
- package/dist/src/key/encryption.js.map +1 -0
- package/dist/src/key/export.js +46 -0
- package/dist/src/key/export.js.map +1 -0
- package/dist/src/key/generate.js +47 -0
- package/dist/src/key/generate.js.map +1 -0
- package/dist/src/key/import.js +136 -0
- package/dist/src/key/import.js.map +1 -0
- package/dist/src/legacy/rs256.js +11 -0
- package/dist/src/legacy/rs256.js.map +1 -0
- package/dist/src/legacy/rsa.js +37 -0
- package/dist/src/legacy/rsa.js.map +1 -0
- package/dist/src/lib/algorithms.js +60 -0
- package/dist/src/lib/algorithms.js.map +1 -0
- package/dist/src/lib/base64url.js +28 -0
- package/dist/src/lib/base64url.js.map +1 -0
- package/dist/src/lib/claims.js +111 -0
- package/dist/src/lib/claims.js.map +1 -0
- package/dist/src/lib/duration.js +24 -0
- package/dist/src/lib/duration.js.map +1 -0
- package/dist/src/lib/entropy.js +70 -0
- package/dist/src/lib/entropy.js.map +1 -0
- package/dist/src/lib/headers.js +75 -0
- package/dist/src/lib/headers.js.map +1 -0
- package/dist/src/lib/invariants.js +12 -0
- package/dist/src/lib/invariants.js.map +1 -0
- package/dist/src/lib/json.js +140 -0
- package/dist/src/lib/json.js.map +1 -0
- package/dist/src/lib/jwe_algorithms.js +81 -0
- package/dist/src/lib/jwe_algorithms.js.map +1 -0
- package/dist/src/lib/payload_hygiene.js +116 -0
- package/dist/src/lib/payload_hygiene.js.map +1 -0
- package/dist/src/lib/utf8.js +27 -0
- package/dist/src/lib/utf8.js.map +1 -0
- package/dist/src/revocation/memory.js +67 -0
- package/dist/src/revocation/memory.js.map +1 -0
- package/dist/src/revocation/store.js +25 -0
- package/dist/src/revocation/store.js.map +1 -0
- package/dist/src/types.js +51 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/util/errors.js +96 -0
- package/dist/src/util/errors.js.map +1 -0
- package/dist/src/util/unsafe-decode.js +45 -0
- package/dist/src/util/unsafe-decode.js.map +1 -0
- package/dist/types/index.d.ts +29 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/src/cookbook/access-refresh.d.ts +77 -0
- package/dist/types/src/cookbook/access-refresh.d.ts.map +1 -0
- package/dist/types/src/http/bearer.d.ts +20 -0
- package/dist/types/src/http/bearer.d.ts.map +1 -0
- package/dist/types/src/http/cookies.d.ts +38 -0
- package/dist/types/src/http/cookies.d.ts.map +1 -0
- package/dist/types/src/jwks/local.d.ts +16 -0
- package/dist/types/src/jwks/local.d.ts.map +1 -0
- package/dist/types/src/jwks/remote.d.ts +23 -0
- package/dist/types/src/jwks/remote.d.ts.map +1 -0
- package/dist/types/src/jwt/decrypt.d.ts +23 -0
- package/dist/types/src/jwt/decrypt.d.ts.map +1 -0
- package/dist/types/src/jwt/decryption_profile.d.ts +32 -0
- package/dist/types/src/jwt/decryption_profile.d.ts.map +1 -0
- package/dist/types/src/jwt/encrypt.d.ts +34 -0
- package/dist/types/src/jwt/encrypt.d.ts.map +1 -0
- package/dist/types/src/jwt/profile.d.ts +37 -0
- package/dist/types/src/jwt/profile.d.ts.map +1 -0
- package/dist/types/src/jwt/sign.d.ts +59 -0
- package/dist/types/src/jwt/sign.d.ts.map +1 -0
- package/dist/types/src/jwt/verify.d.ts +41 -0
- package/dist/types/src/jwt/verify.d.ts.map +1 -0
- package/dist/types/src/key/encryption.d.ts +37 -0
- package/dist/types/src/key/encryption.d.ts.map +1 -0
- package/dist/types/src/key/export.d.ts +10 -0
- package/dist/types/src/key/export.d.ts.map +1 -0
- package/dist/types/src/key/generate.d.ts +27 -0
- package/dist/types/src/key/generate.d.ts.map +1 -0
- package/dist/types/src/key/import.d.ts +20 -0
- package/dist/types/src/key/import.d.ts.map +1 -0
- package/dist/types/src/legacy/rs256.d.ts +11 -0
- package/dist/types/src/legacy/rs256.d.ts.map +1 -0
- package/dist/types/src/legacy/rsa.d.ts +25 -0
- package/dist/types/src/legacy/rsa.d.ts.map +1 -0
- package/dist/types/src/lib/algorithms.d.ts +34 -0
- package/dist/types/src/lib/algorithms.d.ts.map +1 -0
- package/dist/types/src/lib/base64url.d.ts +8 -0
- package/dist/types/src/lib/base64url.d.ts.map +1 -0
- package/dist/types/src/lib/claims.d.ts +15 -0
- package/dist/types/src/lib/claims.d.ts.map +1 -0
- package/dist/types/src/lib/duration.d.ts +7 -0
- package/dist/types/src/lib/duration.d.ts.map +1 -0
- package/dist/types/src/lib/entropy.d.ts +21 -0
- package/dist/types/src/lib/entropy.d.ts.map +1 -0
- package/dist/types/src/lib/headers.d.ts +19 -0
- package/dist/types/src/lib/headers.d.ts.map +1 -0
- package/dist/types/src/lib/invariants.d.ts +6 -0
- package/dist/types/src/lib/invariants.d.ts.map +1 -0
- package/dist/types/src/lib/json.d.ts +24 -0
- package/dist/types/src/lib/json.d.ts.map +1 -0
- package/dist/types/src/lib/jwe_algorithms.d.ts +46 -0
- package/dist/types/src/lib/jwe_algorithms.d.ts.map +1 -0
- package/dist/types/src/lib/payload_hygiene.d.ts +20 -0
- package/dist/types/src/lib/payload_hygiene.d.ts.map +1 -0
- package/dist/types/src/lib/utf8.d.ts +7 -0
- package/dist/types/src/lib/utf8.d.ts.map +1 -0
- package/dist/types/src/revocation/memory.d.ts +22 -0
- package/dist/types/src/revocation/memory.d.ts.map +1 -0
- package/dist/types/src/revocation/store.d.ts +17 -0
- package/dist/types/src/revocation/store.d.ts.map +1 -0
- package/dist/types/src/types.d.ts +196 -0
- package/dist/types/src/types.d.ts.map +1 -0
- package/dist/types/src/util/errors.d.ts +89 -0
- package/dist/types/src/util/errors.d.ts.map +1 -0
- package/dist/types/src/util/unsafe-decode.d.ts +21 -0
- package/dist/types/src/util/unsafe-decode.d.ts.map +1 -0
- package/package.json +70 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed error hierarchy.
|
|
3
|
+
*
|
|
4
|
+
* Every error carries a machine-readable `code` so operators can branch on
|
|
5
|
+
* failure modes, while messages stay generic: security-relevant rejections
|
|
6
|
+
* never echo untrusted token content (no oracle for attackers, no log
|
|
7
|
+
* injection - RFC 8725 §3.10 hygiene).
|
|
8
|
+
*/
|
|
9
|
+
export class JWTError extends Error {
|
|
10
|
+
constructor(message, options) {
|
|
11
|
+
super(message, options);
|
|
12
|
+
this.name = new.target.name;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/** Token structure or signature is invalid. */
|
|
16
|
+
export class JWTInvalid extends JWTError {
|
|
17
|
+
code = "JWT_INVALID";
|
|
18
|
+
}
|
|
19
|
+
/** Token is past `exp`, or older than the profile's `maxTokenAge`. */
|
|
20
|
+
export class JWTExpired extends JWTError {
|
|
21
|
+
code = "JWT_EXPIRED";
|
|
22
|
+
}
|
|
23
|
+
/** A claim is present but does not satisfy the profile's rules. */
|
|
24
|
+
export class JWTClaimValidationFailed extends JWTError {
|
|
25
|
+
code = "JWT_CLAIM_VALIDATION_FAILED";
|
|
26
|
+
/** Name of the offending claim (never its value). */
|
|
27
|
+
claim;
|
|
28
|
+
constructor(claim, message, options) {
|
|
29
|
+
super(message, options);
|
|
30
|
+
this.claim = claim;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/** A claim required by the profile or by Lacewing policy is absent. */
|
|
34
|
+
export class MissingClaim extends JWTError {
|
|
35
|
+
code = "MISSING_CLAIM";
|
|
36
|
+
claim;
|
|
37
|
+
constructor(claim, message) {
|
|
38
|
+
super(message ?? `Required claim "${claim}" is missing`);
|
|
39
|
+
this.claim = claim;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/** Algorithm is not in the registry / profile allowlist. */
|
|
43
|
+
export class AlgorithmNotAllowed extends JWTError {
|
|
44
|
+
code = "ALGORITHM_NOT_ALLOWED";
|
|
45
|
+
}
|
|
46
|
+
/** Key material could not be imported. */
|
|
47
|
+
export class KeyImportFailed extends JWTError {
|
|
48
|
+
code = "KEY_IMPORT_FAILED";
|
|
49
|
+
}
|
|
50
|
+
/** Key does not match the algorithm it is being used with. */
|
|
51
|
+
export class KeyTypeMismatch extends JWTError {
|
|
52
|
+
code = "KEY_TYPE_MISMATCH";
|
|
53
|
+
}
|
|
54
|
+
/** Key material could not be exported. */
|
|
55
|
+
export class KeyExportFailed extends JWTError {
|
|
56
|
+
code = "KEY_EXPORT_FAILED";
|
|
57
|
+
}
|
|
58
|
+
/** HMAC secret failed the strength checks (RFC 8725 §3.5). */
|
|
59
|
+
export class EntropyCheckFailed extends JWTError {
|
|
60
|
+
code = "ENTROPY_CHECK_FAILED";
|
|
61
|
+
}
|
|
62
|
+
/** Remote JWKS could not be fetched or parsed. */
|
|
63
|
+
export class JWKSFetchFailed extends JWTError {
|
|
64
|
+
code = "JWKS_FETCH_FAILED";
|
|
65
|
+
}
|
|
66
|
+
/** No key in the JWKS matches the token's header. */
|
|
67
|
+
export class JWKSNoMatchingKey extends JWTError {
|
|
68
|
+
code = "JWKS_NO_MATCHING_KEY";
|
|
69
|
+
}
|
|
70
|
+
/** Token is valid but its `jti` has been revoked (LW-rev.2). */
|
|
71
|
+
export class JWTRevoked extends JWTError {
|
|
72
|
+
code = "JWT_REVOKED";
|
|
73
|
+
}
|
|
74
|
+
/** The revocation store errored; Lacewing fails closed (LW-rev.4). */
|
|
75
|
+
export class RevocationCheckFailed extends JWTError {
|
|
76
|
+
code = "REVOCATION_CHECK_FAILED";
|
|
77
|
+
}
|
|
78
|
+
/** Sign-time payload hygiene scanner found sensitive data (LW-payload). */
|
|
79
|
+
export class PayloadHygieneViolation extends JWTError {
|
|
80
|
+
code = "PAYLOAD_HYGIENE_VIOLATION";
|
|
81
|
+
/** Name of the offending claim (never its value). */
|
|
82
|
+
claim;
|
|
83
|
+
constructor(claim, message) {
|
|
84
|
+
super(message);
|
|
85
|
+
this.claim = claim;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/** Requested token lifetime exceeds the configured cap (LW-life.1). */
|
|
89
|
+
export class MaxLifetimeExceeded extends JWTError {
|
|
90
|
+
code = "MAX_LIFETIME_EXCEEDED";
|
|
91
|
+
}
|
|
92
|
+
/** `Authorization` header failed strict RFC 6750 parsing (LW-http.2). */
|
|
93
|
+
export class BearerParseFailed extends JWTError {
|
|
94
|
+
code = "BEARER_PARSE_FAILED";
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../../src/util/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,OAAgB,QAAS,SAAQ,KAAK;IAG3C,YAAY,OAAe,EAAE,OAA6B;QACzD,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACxB,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC;IAC7B,CAAC;CACD;AAED,+CAA+C;AAC/C,MAAM,OAAO,UAAW,SAAQ,QAAQ;IAC9B,IAAI,GAAG,aAAa,CAAC;CAC9B;AAED,sEAAsE;AACtE,MAAM,OAAO,UAAW,SAAQ,QAAQ;IAC9B,IAAI,GAAG,aAAa,CAAC;CAC9B;AAED,mEAAmE;AACnE,MAAM,OAAO,wBAAyB,SAAQ,QAAQ;IAC5C,IAAI,GAAG,6BAA6B,CAAC;IAC9C,qDAAqD;IAC5C,KAAK,CAAS;IAEvB,YAAY,KAAa,EAAE,OAAe,EAAE,OAA6B;QACxE,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACxB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACpB,CAAC;CACD;AAED,uEAAuE;AACvE,MAAM,OAAO,YAAa,SAAQ,QAAQ;IAChC,IAAI,GAAG,eAAe,CAAC;IACvB,KAAK,CAAS;IAEvB,YAAY,KAAa,EAAE,OAAgB;QAC1C,KAAK,CAAC,OAAO,IAAI,mBAAmB,KAAK,cAAc,CAAC,CAAC;QACzD,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACpB,CAAC;CACD;AAED,4DAA4D;AAC5D,MAAM,OAAO,mBAAoB,SAAQ,QAAQ;IACvC,IAAI,GAAG,uBAAuB,CAAC;CACxC;AAED,0CAA0C;AAC1C,MAAM,OAAO,eAAgB,SAAQ,QAAQ;IACnC,IAAI,GAAG,mBAAmB,CAAC;CACpC;AAED,8DAA8D;AAC9D,MAAM,OAAO,eAAgB,SAAQ,QAAQ;IACnC,IAAI,GAAG,mBAAmB,CAAC;CACpC;AAED,0CAA0C;AAC1C,MAAM,OAAO,eAAgB,SAAQ,QAAQ;IACnC,IAAI,GAAG,mBAAmB,CAAC;CACpC;AAED,8DAA8D;AAC9D,MAAM,OAAO,kBAAmB,SAAQ,QAAQ;IACtC,IAAI,GAAG,sBAAsB,CAAC;CACvC;AAED,kDAAkD;AAClD,MAAM,OAAO,eAAgB,SAAQ,QAAQ;IACnC,IAAI,GAAG,mBAAmB,CAAC;CACpC;AAED,qDAAqD;AACrD,MAAM,OAAO,iBAAkB,SAAQ,QAAQ;IACrC,IAAI,GAAG,sBAAsB,CAAC;CACvC;AAED,gEAAgE;AAChE,MAAM,OAAO,UAAW,SAAQ,QAAQ;IAC9B,IAAI,GAAG,aAAa,CAAC;CAC9B;AAED,sEAAsE;AACtE,MAAM,OAAO,qBAAsB,SAAQ,QAAQ;IACzC,IAAI,GAAG,yBAAyB,CAAC;CAC1C;AAED,2EAA2E;AAC3E,MAAM,OAAO,uBAAwB,SAAQ,QAAQ;IAC3C,IAAI,GAAG,2BAA2B,CAAC;IAC5C,qDAAqD;IAC5C,KAAK,CAAS;IAEvB,YAAY,KAAa,EAAE,OAAe;QACzC,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACpB,CAAC;CACD;AAED,uEAAuE;AACvE,MAAM,OAAO,mBAAoB,SAAQ,QAAQ;IACvC,IAAI,GAAG,uBAAuB,CAAC;CACxC;AAED,yEAAyE;AACzE,MAAM,OAAO,iBAAkB,SAAQ,QAAQ;IACrC,IAAI,GAAG,qBAAqB,CAAC;CACtC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DANGER: decode-without-verify, for debugging and inspection only.
|
|
3
|
+
*/
|
|
4
|
+
import { JWTInvalid } from "./errors.js";
|
|
5
|
+
import { decodeBase64url } from "../lib/base64url.js";
|
|
6
|
+
import { decodeUTF8 } from "../lib/utf8.js";
|
|
7
|
+
import { parseJsonObject } from "../lib/json.js";
|
|
8
|
+
const MAX_TOKEN_LENGTH = 16384;
|
|
9
|
+
function parseSegment(segment, what) {
|
|
10
|
+
return parseJsonObject(decodeUTF8(decodeBase64url(segment)), what);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* DANGER: Decodes a JWT **without verifying the signature** and without
|
|
14
|
+
* validating a single claim - expired, forged and `alg: none` tokens all
|
|
15
|
+
* decode successfully. The returned {@link UntrustedJwt} is type-branded
|
|
16
|
+
* and incompatible with `VerifiedJwt`, so it cannot flow into
|
|
17
|
+
* authentication logic. Never base an auth decision on it.
|
|
18
|
+
*
|
|
19
|
+
* Legitimate uses: inspecting expired tokens while debugging, examining
|
|
20
|
+
* token structure without the key, observability of token contents.
|
|
21
|
+
* Since the token is unverified, fields the types mark mandatory may be
|
|
22
|
+
* absent at runtime.
|
|
23
|
+
*
|
|
24
|
+
* Throws only on malformed structure (base64url / UTF-8 / JSON) - never
|
|
25
|
+
* because a signature is missing or wrong.
|
|
26
|
+
*/
|
|
27
|
+
export function unsafeDecode(token) {
|
|
28
|
+
if (typeof token !== "string" || token.length === 0) {
|
|
29
|
+
throw new JWTInvalid("Token must be a non-empty string");
|
|
30
|
+
}
|
|
31
|
+
if (token.length > MAX_TOKEN_LENGTH) {
|
|
32
|
+
throw new JWTInvalid("Token exceeds the maximum length");
|
|
33
|
+
}
|
|
34
|
+
const segments = token.split(".");
|
|
35
|
+
if (segments.length !== 3) {
|
|
36
|
+
throw new JWTInvalid("Token must have exactly three segments");
|
|
37
|
+
}
|
|
38
|
+
const [rawHeader, rawPayload] = segments;
|
|
39
|
+
// Deliberately no allowlist, typ, or kid checks here - this is a raw
|
|
40
|
+
// view of untrusted data, and the brand says exactly that.
|
|
41
|
+
const header = parseSegment(rawHeader, "header");
|
|
42
|
+
const payload = parseSegment(rawPayload, "payload");
|
|
43
|
+
return { header, payload };
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=unsafe-decode.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unsafe-decode.js","sourceRoot":"","sources":["../../../src/util/unsafe-decode.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC5C,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAGjD,MAAM,gBAAgB,GAAG,KAAK,CAAC;AAE/B,SAAS,YAAY,CAAC,OAAe,EAAE,IAAY;IAClD,OAAO,eAAe,CAAC,UAAU,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;AACpE,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,YAAY,CAAC,KAAa;IACzC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrD,MAAM,IAAI,UAAU,CAAC,kCAAkC,CAAC,CAAC;IAC1D,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,GAAG,gBAAgB,EAAE,CAAC;QACrC,MAAM,IAAI,UAAU,CAAC,kCAAkC,CAAC,CAAC;IAC1D,CAAC;IACD,MAAM,QAAQ,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAClC,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,UAAU,CAAC,wCAAwC,CAAC,CAAC;IAChE,CAAC;IACD,MAAM,CAAC,SAAS,EAAE,UAAU,CAAC,GAAG,QAAoC,CAAC;IACrE,qEAAqE;IACrE,2DAA2D;IAC3D,MAAM,MAAM,GAAG,YAAY,CAAC,SAAS,EAAE,QAAQ,CAAyB,CAAC;IACzE,MAAM,OAAO,GAAG,YAAY,CAAC,UAAU,EAAE,SAAS,CAA0B,CAAC;IAC7E,OAAO,EAAE,MAAM,EAAE,OAAO,EAAkB,CAAC;AAC5C,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lacewing - an opinionated JWT library where RFC 8725 (JWT Best Current
|
|
3
|
+
* Practices) is the default behavior, not an optional configuration.
|
|
4
|
+
*
|
|
5
|
+
* This barrel is the public API. Internals under `src/lib/` are policy
|
|
6
|
+
* machinery and stay private; legacy interop algorithms live behind the
|
|
7
|
+
* explicit `lacewing/legacy/*` imports.
|
|
8
|
+
*/
|
|
9
|
+
export { defineProfile, type ProfileOptions } from "./src/jwt/profile.js";
|
|
10
|
+
export { jwtVerify } from "./src/jwt/verify.js";
|
|
11
|
+
export { SignJWT, type SignJwtOptions } from "./src/jwt/sign.js";
|
|
12
|
+
export { EncryptJWT, type EncryptJwtOptions } from "./src/jwt/encrypt.js";
|
|
13
|
+
export { jwtDecrypt } from "./src/jwt/decrypt.js";
|
|
14
|
+
export { defineDecryptionProfile, type DecryptionProfileOptions, } from "./src/jwt/decryption_profile.js";
|
|
15
|
+
export { generateEncryptionKeyPair, generateEncryptionSecret, generateDirectKey, importEncryptionKey, type EncryptionKeyMaterial, type GenerateEncryptionKeyPairOptions, } from "./src/key/encryption.js";
|
|
16
|
+
export { accessTokenProfile, refreshTokenProfile, newAccessToken, newRefreshToken, ACCESS_TOKEN_TYP, REFRESH_TOKEN_TYP, type TokenProfileOptions, } from "./src/cookbook/access-refresh.js";
|
|
17
|
+
export { importKey, type KeyMaterial } from "./src/key/import.js";
|
|
18
|
+
export { generateKeyPair, generateSecret, type GenerateKeyPairOptions, } from "./src/key/generate.js";
|
|
19
|
+
export { exportKeyJWK, exportKeyPEM } from "./src/key/export.js";
|
|
20
|
+
export { createLocalJWKSet } from "./src/jwks/local.js";
|
|
21
|
+
export { createRemoteJWKSet, type RemoteJWKSetOptions } from "./src/jwks/remote.js";
|
|
22
|
+
export { buildRevocationContext } from "./src/revocation/store.js";
|
|
23
|
+
export { MemoryRevocationStore } from "./src/revocation/memory.js";
|
|
24
|
+
export { buildTokenCookie, setTokenCookie, clearTokenCookie, readTokenCookie, type TokenCookieOptions, } from "./src/http/cookies.js";
|
|
25
|
+
export { parseBearer } from "./src/http/bearer.js";
|
|
26
|
+
export { unsafeDecode } from "./src/util/unsafe-decode.js";
|
|
27
|
+
export { JWTError, JWTInvalid, JWTExpired, JWTClaimValidationFailed, MissingClaim, AlgorithmNotAllowed, KeyImportFailed, KeyExportFailed, KeyTypeMismatch, EntropyCheckFailed, JWKSFetchFailed, JWKSNoMatchingKey, JWTRevoked, RevocationCheckFailed, PayloadHygieneViolation, MaxLifetimeExceeded, BearerParseFailed, } from "./src/util/errors.js";
|
|
28
|
+
export { toSeconds, type ClaimsPolicy, type DecryptedJwt, type DurationSeconds, type ExpectedDecryptionProfile, type ExpectedJwtProfile, type JweHeader, type JwtHeader, type LacewingEncryptionKey, type JwtPayLoad, type JWKSConfig, type KeySource, type LacewingKey, type RemoteJWKS, type ResolvedVerificationKey, type RevocationStore, type StaticJWK, type StaticJWKS, type TokenRevocationContext, type UntrustedJwt, type ValidAlg, type ValidateClaim, type VerifiedJwt, } from "./src/types.js";
|
|
29
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,aAAa,EAAE,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC1E,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAGhD,OAAO,EAAE,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAGjE,OAAO,EAAE,UAAU,EAAE,KAAK,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAC1E,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,EACN,uBAAuB,EACvB,KAAK,wBAAwB,GAC7B,MAAM,iCAAiC,CAAC;AACzC,OAAO,EACN,yBAAyB,EACzB,wBAAwB,EACxB,iBAAiB,EACjB,mBAAmB,EACnB,KAAK,qBAAqB,EAC1B,KAAK,gCAAgC,GACrC,MAAM,yBAAyB,CAAC;AAGjC,OAAO,EACN,kBAAkB,EAClB,mBAAmB,EACnB,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,iBAAiB,EACjB,KAAK,mBAAmB,GACxB,MAAM,kCAAkC,CAAC;AAG1C,OAAO,EAAE,SAAS,EAAE,KAAK,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClE,OAAO,EACN,eAAe,EACf,cAAc,EACd,KAAK,sBAAsB,GAC3B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAGjE,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAE,KAAK,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAGpF,OAAO,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACnE,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAGnE,OAAO,EACN,gBAAgB,EAChB,cAAc,EACd,gBAAgB,EAChB,eAAe,EACf,KAAK,kBAAkB,GACvB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAGnD,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAG3D,OAAO,EACN,QAAQ,EACR,UAAU,EACV,UAAU,EACV,wBAAwB,EACxB,YAAY,EACZ,mBAAmB,EACnB,eAAe,EACf,eAAe,EACf,eAAe,EACf,kBAAkB,EAClB,eAAe,EACf,iBAAiB,EACjB,UAAU,EACV,qBAAqB,EACrB,uBAAuB,EACvB,mBAAmB,EACnB,iBAAiB,GACjB,MAAM,sBAAsB,CAAC;AAG9B,OAAO,EACN,SAAS,EACT,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,eAAe,EACpB,KAAK,yBAAyB,EAC9B,KAAK,kBAAkB,EACvB,KAAK,SAAS,EACd,KAAK,SAAS,EACd,KAAK,qBAAqB,EAC1B,KAAK,UAAU,EACf,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,uBAAuB,EAC5B,KAAK,eAAe,EACpB,KAAK,SAAS,EACd,KAAK,UAAU,EACf,KAAK,sBAAsB,EAC3B,KAAK,YAAY,EACjB,KAAK,QAAQ,EACb,KAAK,aAAa,EAClB,KAAK,WAAW,GAChB,MAAM,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cookbook: access tokens vs refresh tokens (LW-kind.1).
|
|
3
|
+
*
|
|
4
|
+
* The single most common token-confusion bug is letting a refresh token buy
|
|
5
|
+
* access to an API (or letting a stolen access token mint new sessions). RFC
|
|
6
|
+
* 8725 §3.12 says the validation rules for different kinds of JWT must be
|
|
7
|
+
* mutually exclusive; Lacewing makes that concrete by shipping the two
|
|
8
|
+
* profiles rather than leaving them as an exercise.
|
|
9
|
+
*
|
|
10
|
+
* The separation is enforced on three axes at once:
|
|
11
|
+
*
|
|
12
|
+
* | | access | refresh |
|
|
13
|
+
* |---|---|---|
|
|
14
|
+
* | `typ` | `at+jwt` | `rt+jwt` |
|
|
15
|
+
* | audience | the API | the auth server's token endpoint |
|
|
16
|
+
* | lifetime | minutes (default 10m) | days (default 30d) |
|
|
17
|
+
* | key source | usually a public JWKS | usually a private, server-only key |
|
|
18
|
+
* | revocation | optional | **strongly recommended** |
|
|
19
|
+
*
|
|
20
|
+
* `typ` alone is load-bearing: even with identical keys, claims and audience,
|
|
21
|
+
* each profile refuses the other's tokens. The rest is defense in depth.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```ts
|
|
25
|
+
* import { accessTokenProfile, refreshTokenProfile, newAccessToken } from "lacewing";
|
|
26
|
+
*
|
|
27
|
+
* const api = accessTokenProfile({
|
|
28
|
+
* issuer: "https://auth.example.com",
|
|
29
|
+
* audience: "https://api.example.com",
|
|
30
|
+
* algorithms: ["EdDSA"],
|
|
31
|
+
* keys: { jwksUri: "https://auth.example.com/jwks" },
|
|
32
|
+
* });
|
|
33
|
+
*
|
|
34
|
+
* const token = await newAccessToken()
|
|
35
|
+
* .issuer("https://auth.example.com")
|
|
36
|
+
* .audience("https://api.example.com")
|
|
37
|
+
* .subject("user-42")
|
|
38
|
+
* .expiresIn("10m")
|
|
39
|
+
* .sign(privateKey);
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
import { type ProfileOptions } from "../jwt/profile.js";
|
|
43
|
+
import { SignJWT } from "../jwt/sign.js";
|
|
44
|
+
import type { ExpectedJwtProfile } from "../types.js";
|
|
45
|
+
/** The `typ` of an OAuth 2.0 JWT access token (RFC 9068). */
|
|
46
|
+
export declare const ACCESS_TOKEN_TYP = "at+jwt";
|
|
47
|
+
/** The `typ` Lacewing uses for refresh tokens - anything but the access `typ`. */
|
|
48
|
+
export declare const REFRESH_TOKEN_TYP = "rt+jwt";
|
|
49
|
+
/**
|
|
50
|
+
* Everything a cookbook profile needs except the `typ`, which is fixed by the
|
|
51
|
+
* factory. `maxTokenAge` becomes optional here - each kind has a sane default.
|
|
52
|
+
*/
|
|
53
|
+
export type TokenProfileOptions = Omit<ProfileOptions, "typ" | "maxTokenAge"> & {
|
|
54
|
+
/** Defaults to 10m for access tokens, 30d for refresh tokens. */
|
|
55
|
+
maxTokenAge?: number | string;
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* An access-token profile: short-lived, audience-scoped to your API, and
|
|
59
|
+
* pinned to `typ: "at+jwt"`. Verifies tokens presented by clients.
|
|
60
|
+
*/
|
|
61
|
+
export declare function accessTokenProfile(options: TokenProfileOptions): ExpectedJwtProfile;
|
|
62
|
+
/**
|
|
63
|
+
* A refresh-token profile: long-lived, audience-scoped to the *auth server*
|
|
64
|
+
* (never the API), and pinned to `typ: "rt+jwt"`. Pass a `revocation` store -
|
|
65
|
+
* a long-lived token you cannot revoke is a long-lived incident.
|
|
66
|
+
*/
|
|
67
|
+
export declare function refreshTokenProfile(options: TokenProfileOptions): ExpectedJwtProfile;
|
|
68
|
+
/** A {@link SignJWT} builder pre-set to the access-token `typ` and a 1h cap. */
|
|
69
|
+
export declare function newAccessToken(): SignJWT;
|
|
70
|
+
/**
|
|
71
|
+
* A {@link SignJWT} builder pre-set to the refresh-token `typ`. The cap is
|
|
72
|
+
* raised to 90d because that is the point of a refresh token - but the
|
|
73
|
+
* lifetime you actually pass to `.expiresIn()` should be as short as your UX
|
|
74
|
+
* tolerates, and the token should be revocable.
|
|
75
|
+
*/
|
|
76
|
+
export declare function newRefreshToken(): SignJWT;
|
|
77
|
+
//# sourceMappingURL=access-refresh.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"access-refresh.d.ts","sourceRoot":"","sources":["../../../../src/cookbook/access-refresh.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAEH,OAAO,EAAiB,KAAK,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACvE,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AACzC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEtD,6DAA6D;AAC7D,eAAO,MAAM,gBAAgB,WAAW,CAAC;AACzC,kFAAkF;AAClF,eAAO,MAAM,iBAAiB,WAAW,CAAC;AAK1C;;;GAGG;AACH,MAAM,MAAM,mBAAmB,GAAG,IAAI,CAAC,cAAc,EAAE,KAAK,GAAG,aAAa,CAAC,GAAG;IAC/E,iEAAiE;IACjE,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC9B,CAAC;AAEF;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,mBAAmB,GAAG,kBAAkB,CAMnF;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,mBAAmB,GAAG,kBAAkB,CAMpF;AAED,gFAAgF;AAChF,wBAAgB,cAAc,IAAI,OAAO,CAExC;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,IAAI,OAAO,CAEzC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strict `Authorization: Bearer` parsing (RFC 6750 §2.1, LW-http.2).
|
|
3
|
+
*
|
|
4
|
+
* Deliberately unsupported: tokens in query strings (they leak through
|
|
5
|
+
* logs and the Referer header), multiple tokens, alternate casings and
|
|
6
|
+
* whitespace tricks. One header, one scheme, one token.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Extract the bearer token from an `Authorization` header. Accepts a
|
|
10
|
+
* `Headers` object, anything with a `.headers` (e.g. `Request`), or the
|
|
11
|
+
* raw header value. Throws {@link BearerParseFailed} on anything that
|
|
12
|
+
* isn't exactly one well-formed `Bearer <token>`.
|
|
13
|
+
*
|
|
14
|
+
* Note: when multiple `Authorization` headers were sent, `Headers`
|
|
15
|
+
* joins them with `", "` - which this parser rejects, by design.
|
|
16
|
+
*/
|
|
17
|
+
export declare function parseBearer(source: Headers | {
|
|
18
|
+
headers: Headers;
|
|
19
|
+
} | string | null | undefined): string;
|
|
20
|
+
//# sourceMappingURL=bearer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bearer.d.ts","sourceRoot":"","sources":["../../../../src/http/bearer.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAQH;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAC1B,MAAM,EAAE,OAAO,GAAG;IAAE,OAAO,EAAE,OAAO,CAAA;CAAE,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,GAChE,MAAM,CAsBR"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cookie transport helpers (LW-http.1).
|
|
3
|
+
*
|
|
4
|
+
* The paved road for browser token storage: `HttpOnly; Secure; SameSite`
|
|
5
|
+
* are always emitted and cannot be configured away - weaker cookies are
|
|
6
|
+
* unrepresentable. `localStorage`/`sessionStorage` are script-readable
|
|
7
|
+
* and therefore one XSS away from token theft; don't put tokens there.
|
|
8
|
+
*
|
|
9
|
+
* Framework-agnostic: works on WHATWG `Headers` (and anything exposing
|
|
10
|
+
* them, e.g. `Request`/`Response`).
|
|
11
|
+
*/
|
|
12
|
+
export interface TokenCookieOptions {
|
|
13
|
+
/** Cookie name (default `__Host-token`, the most locked-down prefix). */
|
|
14
|
+
name?: string;
|
|
15
|
+
/** `None` is not an option - cross-site token cookies are how CSRF happens. */
|
|
16
|
+
sameSite?: "Strict" | "Lax";
|
|
17
|
+
/** Default `/` (mandatory for `__Host-` names). */
|
|
18
|
+
path?: string;
|
|
19
|
+
/** Cookie lifetime; omit for a session cookie. */
|
|
20
|
+
maxAgeSeconds?: number;
|
|
21
|
+
/** Not allowed with `__Host-` names. */
|
|
22
|
+
domain?: string;
|
|
23
|
+
}
|
|
24
|
+
/** Build a hardened `Set-Cookie` value. Prefer {@link setTokenCookie}. */
|
|
25
|
+
export declare function buildTokenCookie(token: string, options?: TokenCookieOptions): string;
|
|
26
|
+
/** Append a hardened token cookie to a response's headers. */
|
|
27
|
+
export declare function setTokenCookie(headers: Headers, token: string, options?: TokenCookieOptions): void;
|
|
28
|
+
/** Expire a previously set token cookie. */
|
|
29
|
+
export declare function clearTokenCookie(headers: Headers, options?: Pick<TokenCookieOptions, "name" | "path" | "domain">): void;
|
|
30
|
+
/**
|
|
31
|
+
* Read a token cookie from a request. Accepts a `Headers` object,
|
|
32
|
+
* anything with a `.headers` (e.g. `Request`), or the raw `Cookie`
|
|
33
|
+
* header string. Returns `undefined` when absent or malformed.
|
|
34
|
+
*/
|
|
35
|
+
export declare function readTokenCookie(source: Headers | {
|
|
36
|
+
headers: Headers;
|
|
37
|
+
} | string | null | undefined, name?: string): string | undefined;
|
|
38
|
+
//# sourceMappingURL=cookies.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cookies.d.ts","sourceRoot":"","sources":["../../../../src/http/cookies.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAQH,MAAM,WAAW,kBAAkB;IAClC,yEAAyE;IACzE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,QAAQ,GAAG,KAAK,CAAC;IAC5B,mDAAmD;IACnD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kDAAkD;IAClD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,wCAAwC;IACxC,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,0EAA0E;AAC1E,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,kBAAuB,GAAG,MAAM,CAwCxF;AAED,8DAA8D;AAC9D,wBAAgB,cAAc,CAC7B,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,kBAAuB,GAC9B,IAAI,CAEN;AAED,4CAA4C;AAC5C,wBAAgB,gBAAgB,CAC/B,OAAO,EAAE,OAAO,EAChB,OAAO,GAAE,IAAI,CAAC,kBAAkB,EAAE,MAAM,GAAG,MAAM,GAAG,QAAQ,CAAM,GAChE,IAAI,CAKN;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC9B,MAAM,EAAE,OAAO,GAAG;IAAE,OAAO,EAAE,OAAO,CAAA;CAAE,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,EAClE,IAAI,GAAE,MAA4B,GAChC,MAAM,GAAG,SAAS,CAoBpB"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Static (local) JWK Sets.
|
|
3
|
+
*
|
|
4
|
+
* Selection is registry-driven: entries whose `kty`/`alg`/`crv` fall
|
|
5
|
+
* outside what the token's (already allowlisted) algorithm requires are
|
|
6
|
+
* never candidates. `kid` values arriving here have already been
|
|
7
|
+
* sanitized by header validation (§3.10).
|
|
8
|
+
*/
|
|
9
|
+
import type { JwtHeader, KeySource, ResolvedVerificationKey, StaticJWK, StaticJWKS } from "../types.js";
|
|
10
|
+
/** Shared selection + import used by both local and remote sets. */
|
|
11
|
+
export declare function resolveFromJwks(keys: readonly StaticJWK[], header: JwtHeader): Promise<ResolvedVerificationKey>;
|
|
12
|
+
/** Validate the developer-supplied JWKS document shape (config error -> TypeError). */
|
|
13
|
+
export declare function validateJwksShape(jwks: unknown): StaticJWK[];
|
|
14
|
+
/** Create a {@link KeySource} backed by a static JWKS document. */
|
|
15
|
+
export declare function createLocalJWKSet(jwks: StaticJWKS): KeySource;
|
|
16
|
+
//# sourceMappingURL=local.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local.d.ts","sourceRoot":"","sources":["../../../../src/jwks/local.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAWH,OAAO,KAAK,EACX,SAAS,EACT,SAAS,EACT,uBAAuB,EACvB,SAAS,EACT,UAAU,EACV,MAAM,aAAa,CAAC;AAYrB,oEAAoE;AACpE,wBAAsB,eAAe,CACpC,IAAI,EAAE,SAAS,SAAS,EAAE,EAC1B,MAAM,EAAE,SAAS,GACf,OAAO,CAAC,uBAAuB,CAAC,CA+BlC;AAED,uFAAuF;AACvF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,OAAO,GAAG,SAAS,EAAE,CAsB5D;AAED,mEAAmE;AACnE,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,UAAU,GAAG,SAAS,CAO7D"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remote JWK Sets with caching and rotation.
|
|
3
|
+
*
|
|
4
|
+
* - HTTPS-only URLs; redirects are never followed
|
|
5
|
+
* - caches with a configurable TTL and honors `Cache-Control: max-age`
|
|
6
|
+
* - cooldown between refetches (no attacker-driven fetch storms)
|
|
7
|
+
* - refetches once on unknown `kid` (key rotation), then fails closed
|
|
8
|
+
* - response size is capped; entries outside the registry are dropped
|
|
9
|
+
*/
|
|
10
|
+
import type { KeySource } from "../types.js";
|
|
11
|
+
export interface RemoteJWKSetOptions {
|
|
12
|
+
/** Cache lifetime when the response has no usable Cache-Control (default 300s). */
|
|
13
|
+
cacheTtlSeconds?: number;
|
|
14
|
+
/** Minimum interval between fetch attempts (default 30s). */
|
|
15
|
+
cooldownSeconds?: number;
|
|
16
|
+
/** Fetch timeout (default 5000ms). */
|
|
17
|
+
timeoutMs?: number;
|
|
18
|
+
/** Fetch implementation override - intended for tests. */
|
|
19
|
+
fetch?: typeof fetch;
|
|
20
|
+
}
|
|
21
|
+
/** Create a {@link KeySource} that fetches and caches a remote JWKS. */
|
|
22
|
+
export declare function createRemoteJWKSet(url: URL | string, options?: RemoteJWKSetOptions): KeySource;
|
|
23
|
+
//# sourceMappingURL=remote.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remote.d.ts","sourceRoot":"","sources":["../../../../src/jwks/remote.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EAEX,SAAS,EAGT,MAAM,aAAa,CAAC;AAQrB,MAAM,WAAW,mBAAmB;IACnC,mFAAmF;IACnF,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,6DAA6D;IAC7D,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,sCAAsC;IACtC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0DAA0D;IAC1D,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;CACrB;AASD,wEAAwE;AACxE,wBAAgB,kBAAkB,CACjC,GAAG,EAAE,GAAG,GAAG,MAAM,EACjB,OAAO,GAAE,mBAAwB,GAC/B,SAAS,CA8FX"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one and only decryption path (the JWE analogue of `jwtVerify`).
|
|
3
|
+
*
|
|
4
|
+
* Order of operations, fail-fast, all-or-nothing:
|
|
5
|
+
* 1. structural parse (5 segments, length cap, strict base64url/UTF-8/JSON)
|
|
6
|
+
* 2. header validation (alg + enc allowlists, typ, kid hygiene, forbidden
|
|
7
|
+
* params - including `zip`, the JWE decompression-bomb vector)
|
|
8
|
+
* 3. decryption with the profile's key, restricted to the allowlisted alg+enc
|
|
9
|
+
* 4. claims validation
|
|
10
|
+
* 5. revocation check - last, so unauthenticated input can never drive the
|
|
11
|
+
* store (LW-rev.3)
|
|
12
|
+
*
|
|
13
|
+
* A JWS (three segments) handed here is rejected at step 1, and a JWE handed to
|
|
14
|
+
* `jwtVerify` is rejected there in turn - the two formats never cross over.
|
|
15
|
+
*/
|
|
16
|
+
import { type DecryptedJwt, type ExpectedDecryptionProfile } from "../types.js";
|
|
17
|
+
/**
|
|
18
|
+
* Decrypt and validate a compact JWE against a profile. The returned
|
|
19
|
+
* {@link DecryptedJwt} is the only way to obtain one - there is no backdoor,
|
|
20
|
+
* and it is a distinct type from a `VerifiedJwt` (the signed-token proof).
|
|
21
|
+
*/
|
|
22
|
+
export declare function jwtDecrypt(token: string, profile: ExpectedDecryptionProfile): Promise<DecryptedJwt>;
|
|
23
|
+
//# sourceMappingURL=decrypt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decrypt.d.ts","sourceRoot":"","sources":["../../../../src/jwt/decrypt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAqBH,OAAO,EAEN,KAAK,YAAY,EACjB,KAAK,yBAAyB,EAG9B,MAAM,aAAa,CAAC;AAgDrB;;;;GAIG;AACH,wBAAsB,UAAU,CAC/B,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,yBAAyB,GAChC,OAAO,CAAC,YAAY,CAAC,CA4DvB"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decryption profiles - the JWE analogue of `defineProfile`. A profile is the
|
|
3
|
+
* only argument shape `jwtDecrypt` accepts, which makes `typ`, issuer,
|
|
4
|
+
* audience, the two algorithm allowlists, and the decrypting key structurally
|
|
5
|
+
* mandatory. The key belongs to the profile, which belongs to an issuer.
|
|
6
|
+
*/
|
|
7
|
+
import { type ExpectedDecryptionProfile, type LacewingEncryptionKey, type RevocationStore, type ValidateClaim } from "../types.js";
|
|
8
|
+
export interface DecryptionProfileOptions {
|
|
9
|
+
/** Expected token type, e.g. `"at+jwt"`. */
|
|
10
|
+
typ: string;
|
|
11
|
+
/** The one issuer this profile trusts. */
|
|
12
|
+
issuer: string;
|
|
13
|
+
/** The audience value that must appear in the token. */
|
|
14
|
+
audience: string;
|
|
15
|
+
/** Explicit key-management (`alg`) allowlist - the header never decides. */
|
|
16
|
+
keyManagementAlgorithms: readonly string[];
|
|
17
|
+
/** Explicit content-encryption (`enc`) allowlist. */
|
|
18
|
+
contentEncryptionAlgorithms: readonly string[];
|
|
19
|
+
/** The private/secret key that decrypts (bound to one key-management alg). */
|
|
20
|
+
key: LacewingEncryptionKey;
|
|
21
|
+
/** Maximum accepted token age, independent of `exp`. */
|
|
22
|
+
maxTokenAge: number | string;
|
|
23
|
+
/** Allowed clock skew, default 5s, capped at 120s. */
|
|
24
|
+
maxClockSkew?: number | string;
|
|
25
|
+
revocation?: RevocationStore;
|
|
26
|
+
claimValidators?: Record<string, ValidateClaim>;
|
|
27
|
+
subject?: string;
|
|
28
|
+
unsafeFailOpenOnRevocationError?: boolean;
|
|
29
|
+
}
|
|
30
|
+
/** Build the security boundary that `jwtDecrypt` enforces. */
|
|
31
|
+
export declare function defineDecryptionProfile(options: DecryptionProfileOptions): ExpectedDecryptionProfile;
|
|
32
|
+
//# sourceMappingURL=decryption_profile.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decryption_profile.d.ts","sourceRoot":"","sources":["../../../../src/jwt/decryption_profile.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAOH,OAAO,EAGN,KAAK,yBAAyB,EAC9B,KAAK,qBAAqB,EAC1B,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,MAAM,aAAa,CAAC;AAKrB,MAAM,WAAW,wBAAwB;IACxC,4CAA4C;IAC5C,GAAG,EAAE,MAAM,CAAC;IACZ,0CAA0C;IAC1C,MAAM,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,QAAQ,EAAE,MAAM,CAAC;IACjB,4EAA4E;IAC5E,uBAAuB,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3C,qDAAqD;IACrD,2BAA2B,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/C,8EAA8E;IAC9E,GAAG,EAAE,qBAAqB,CAAC;IAC3B,wDAAwD;IACxD,WAAW,EAAE,MAAM,GAAG,MAAM,CAAC;IAC7B,sDAAsD;IACtD,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,UAAU,CAAC,EAAE,eAAe,CAAC;IAC7B,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IAChD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,+BAA+B,CAAC,EAAE,OAAO,CAAC;CAC1C;AASD,8DAA8D;AAC9D,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,wBAAwB,GAAG,yBAAyB,CAuDpG"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The encrypted-JWT builder - the JWE counterpart of `SignJWT`.
|
|
3
|
+
*
|
|
4
|
+
* Same claim discipline: `typ` is a constructor argument, `.encrypt()` refuses
|
|
5
|
+
* to run without `iss`, `aud` and `exp` (each waivable only via a grep-loud
|
|
6
|
+
* `unsafeAllowMissing*`), every token gets a unique `jti`, and lifetimes are
|
|
7
|
+
* capped. The one deliberate difference from signing: there is **no** payload
|
|
8
|
+
* hygiene scanner - the whole point of encryption is that the payload is not
|
|
9
|
+
* readable plaintext, so embedding a secret is exactly the supported use.
|
|
10
|
+
*/
|
|
11
|
+
import { type LacewingEncryptionKey } from "../types.js";
|
|
12
|
+
export interface EncryptJwtOptions {
|
|
13
|
+
/** Cap on `.expiresIn()`. Default 1h - raising it is a visible choice. */
|
|
14
|
+
maxLifetime?: number | string;
|
|
15
|
+
/** Content-encryption algorithm (`enc`). Default `A256GCM`. */
|
|
16
|
+
contentEncryption?: string;
|
|
17
|
+
}
|
|
18
|
+
export declare class EncryptJWT {
|
|
19
|
+
#private;
|
|
20
|
+
constructor(typ: string, options?: EncryptJwtOptions);
|
|
21
|
+
issuer(iss: string): this;
|
|
22
|
+
audience(aud: string | string[]): this;
|
|
23
|
+
subject(sub: string): this;
|
|
24
|
+
/** Override the auto-assigned `jti`. Uniqueness is then your problem. */
|
|
25
|
+
jwtId(jti: string): this;
|
|
26
|
+
expiresIn(duration: number | string): this;
|
|
27
|
+
notBefore(duration: number | string): this;
|
|
28
|
+
claim(name: string, value: unknown): this;
|
|
29
|
+
unsafeAllowMissingIssuer(): this;
|
|
30
|
+
unsafeAllowMissingAudience(): this;
|
|
31
|
+
unsafeAllowMissingExpiration(): this;
|
|
32
|
+
encrypt(key: LacewingEncryptionKey): Promise<string>;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=encrypt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"encrypt.d.ts","sourceRoot":"","sources":["../../../../src/jwt/encrypt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAOH,OAAO,EAAiD,KAAK,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAMxG,MAAM,WAAW,iBAAiB;IACjC,0EAA0E;IAC1E,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC9B,+DAA+D;IAC/D,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,qBAAa,UAAU;;gBAeV,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,iBAAsB;IAWxD,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAMzB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI;IAStC,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAM1B,yEAAyE;IACzE,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAMxB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAO1C,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAK1C,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI;IASzC,wBAAwB,IAAI,IAAI;IAKhC,0BAA0B,IAAI,IAAI;IAKlC,4BAA4B,IAAI,IAAI;IAK9B,OAAO,CAAC,GAAG,EAAE,qBAAqB,GAAG,OAAO,CAAC,MAAM,CAAC;CA4C1D"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verification profiles - the RFC 8725 §3.12 mechanism and the library's
|
|
3
|
+
* signature API.
|
|
4
|
+
*
|
|
5
|
+
* A profile is the *only* argument shape `jwtVerify` accepts, which makes
|
|
6
|
+
* `typ`, issuer, audience, and the algorithm allowlist structurally
|
|
7
|
+
* mandatory. The key source belongs to the profile, which belongs to an
|
|
8
|
+
* issuer - that is the §3.8 issuer-to-key binding.
|
|
9
|
+
*/
|
|
10
|
+
import { type ExpectedJwtProfile, type KeySource, type LacewingKey, type RemoteJWKS, type RevocationStore, type StaticJWKS, type ValidateClaim } from "../types.js";
|
|
11
|
+
export interface ProfileOptions {
|
|
12
|
+
/** Expected token type, e.g. `"at+jwt"` (§3.11). */
|
|
13
|
+
typ: string;
|
|
14
|
+
/** The one issuer this profile trusts (§3.8). */
|
|
15
|
+
issuer: string;
|
|
16
|
+
/** The audience value that must appear in the token (§3.9). */
|
|
17
|
+
audience: string;
|
|
18
|
+
/** Explicit algorithm allowlist - the token header never decides (§3.1). */
|
|
19
|
+
algorithms: readonly string[];
|
|
20
|
+
/** Key source: a JWKS (static or remote config), a KeySource, or a single key. */
|
|
21
|
+
keys: KeySource | LacewingKey | StaticJWKS | RemoteJWKS;
|
|
22
|
+
/** Maximum accepted token age, independent of `exp`. */
|
|
23
|
+
maxTokenAge: number | string;
|
|
24
|
+
/** Allowed clock skew, default 5s, capped at 120s. */
|
|
25
|
+
maxClockSkew?: number | string;
|
|
26
|
+
/** Optional revocation store - its absence is a visible choice. */
|
|
27
|
+
revocation?: RevocationStore;
|
|
28
|
+
/** Extra per-claim predicates, run after all standard checks. */
|
|
29
|
+
claimValidators?: Record<string, ValidateClaim>;
|
|
30
|
+
/** Pin the expected `sub` (§3.8.2). */
|
|
31
|
+
subject?: string;
|
|
32
|
+
/** Ugly on purpose: accept tokens when the revocation store errors (LW-rev.4). */
|
|
33
|
+
unsafeFailOpenOnRevocationError?: boolean;
|
|
34
|
+
}
|
|
35
|
+
/** Build the security boundary that `jwtVerify` enforces. */
|
|
36
|
+
export declare function defineProfile(options: ProfileOptions): ExpectedJwtProfile;
|
|
37
|
+
//# sourceMappingURL=profile.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"profile.d.ts","sourceRoot":"","sources":["../../../../src/jwt/profile.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH,OAAO,EAGN,KAAK,kBAAkB,EACvB,KAAK,SAAS,EACd,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,eAAe,EACpB,KAAK,UAAU,EACf,KAAK,aAAa,EAClB,MAAM,aAAa,CAAC;AAKrB,MAAM,WAAW,cAAc;IAC9B,oDAAoD;IACpD,GAAG,EAAE,MAAM,CAAC;IACZ,iDAAiD;IACjD,MAAM,EAAE,MAAM,CAAC;IACf,+DAA+D;IAC/D,QAAQ,EAAE,MAAM,CAAC;IACjB,4EAA4E;IAC5E,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9B,kFAAkF;IAClF,IAAI,EAAE,SAAS,GAAG,WAAW,GAAG,UAAU,GAAG,UAAU,CAAC;IACxD,wDAAwD;IACxD,WAAW,EAAE,MAAM,GAAG,MAAM,CAAC;IAC7B,sDAAsD;IACtD,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,mEAAmE;IACnE,UAAU,CAAC,EAAE,eAAe,CAAC;IAC7B,iEAAiE;IACjE,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IAChD,uCAAuC;IACvC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kFAAkF;IAClF,+BAA+B,CAAC,EAAE,OAAO,CAAC;CAC1C;AA+CD,6DAA6D;AAC7D,wBAAgB,aAAa,CAAC,OAAO,EAAE,cAAc,GAAG,kBAAkB,CAmDzE"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The sign builder.
|
|
3
|
+
*
|
|
4
|
+
* `.sign()` refuses to produce a token unless `typ` (constructor), `iss`,
|
|
5
|
+
* `aud` and `exp` are set - each requirement individually waivable only
|
|
6
|
+
* via a grep-loud `unsafeAllowMissing*` call. Every token gets a unique
|
|
7
|
+
* `jti`, lifetimes are capped, and the payload hygiene scanner runs as
|
|
8
|
+
* the final step before signing.
|
|
9
|
+
*/
|
|
10
|
+
import { type LacewingKey } from "../types.js";
|
|
11
|
+
export interface SignJwtOptions {
|
|
12
|
+
/**
|
|
13
|
+
* Cap on `.expiresIn()`. Default 1h - raising it is a
|
|
14
|
+
* deliberate, visible configuration choice.
|
|
15
|
+
*/
|
|
16
|
+
maxLifetime?: number | string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Fluent builder for a signed JWT. `typ` is a constructor argument, and
|
|
20
|
+
* `.sign()` refuses to run without `issuer`, `audience` and `expiresIn`
|
|
21
|
+
* (each waivable only via a grep-loud `unsafeAllowMissing*` call).
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```ts
|
|
25
|
+
* const token = await new SignJWT("at+jwt")
|
|
26
|
+
* .issuer("https://auth.example.com")
|
|
27
|
+
* .audience("https://api.example.com")
|
|
28
|
+
* .subject("user-42")
|
|
29
|
+
* .claim("scope", "read")
|
|
30
|
+
* .expiresIn("10m")
|
|
31
|
+
* .sign(privateKey);
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export declare class SignJWT {
|
|
35
|
+
#private;
|
|
36
|
+
/** Explicit typing is mandatory (§3.11): the `typ` is a constructor argument. */
|
|
37
|
+
constructor(typ: string, options?: SignJwtOptions);
|
|
38
|
+
issuer(iss: string): this;
|
|
39
|
+
audience(aud: string | string[]): this;
|
|
40
|
+
subject(sub: string): this;
|
|
41
|
+
/** Override the auto-assigned `jti`. Uniqueness is then your problem. */
|
|
42
|
+
jwtId(jti: string): this;
|
|
43
|
+
/** Token lifetime from now, e.g. `"10m"` or `600`. Capped by maxLifetime. */
|
|
44
|
+
expiresIn(duration: number | string): this;
|
|
45
|
+
/** Delay validity by `duration` from now (`nbf`). */
|
|
46
|
+
notBefore(duration: number | string): this;
|
|
47
|
+
/** Set a custom claim. Registered claims must use their dedicated methods. */
|
|
48
|
+
claim(name: string, value: unknown): this;
|
|
49
|
+
/** Waive the payload hygiene scanner for one claim (LW-payload). Grep for this in review. */
|
|
50
|
+
unsafeAllowClaim(name: string): this;
|
|
51
|
+
/** Waive the mandatory `iss`. Grep for this in review. */
|
|
52
|
+
unsafeAllowMissingIssuer(): this;
|
|
53
|
+
/** Waive the mandatory `aud`. Grep for this in review. */
|
|
54
|
+
unsafeAllowMissingAudience(): this;
|
|
55
|
+
/** Waive the mandatory `exp`. Grep for this in review. */
|
|
56
|
+
unsafeAllowMissingExpiration(): this;
|
|
57
|
+
sign(key: LacewingKey): Promise<string>;
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=sign.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sign.d.ts","sourceRoot":"","sources":["../../../../src/jwt/sign.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAWH,OAAO,EAAuC,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AAQpF,MAAM,WAAW,cAAc;IAC9B;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC9B;AAED;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,OAAO;;IAenB,iFAAiF;gBACrE,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,cAAmB;IAUrD,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAQzB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI;IAStC,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAQ1B,yEAAyE;IACzE,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAQxB,6EAA6E;IAC7E,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAS1C,qDAAqD;IACrD,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAK1C,8EAA8E;IAC9E,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI;IAazC,6FAA6F;IAC7F,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAKpC,0DAA0D;IAC1D,wBAAwB,IAAI,IAAI;IAKhC,0DAA0D;IAC1D,0BAA0B,IAAI,IAAI;IAKlC,0DAA0D;IAC1D,4BAA4B,IAAI,IAAI;IAK9B,IAAI,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC;CAgD7C"}
|