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.
Files changed (141) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/LICENSE +9 -0
  3. package/NOTICE.md +1 -0
  4. package/README.md +313 -0
  5. package/dist/index.js +40 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/src/cookbook/access-refresh.js +86 -0
  8. package/dist/src/cookbook/access-refresh.js.map +1 -0
  9. package/dist/src/http/bearer.js +44 -0
  10. package/dist/src/http/bearer.js.map +1 -0
  11. package/dist/src/http/cookies.js +96 -0
  12. package/dist/src/http/cookies.js.map +1 -0
  13. package/dist/src/jwks/local.js +85 -0
  14. package/dist/src/jwks/local.js.map +1 -0
  15. package/dist/src/jwks/remote.js +121 -0
  16. package/dist/src/jwks/remote.js.map +1 -0
  17. package/dist/src/jwt/decrypt.js +126 -0
  18. package/dist/src/jwt/decrypt.js.map +1 -0
  19. package/dist/src/jwt/decryption_profile.js +78 -0
  20. package/dist/src/jwt/decryption_profile.js.map +1 -0
  21. package/dist/src/jwt/encrypt.js +149 -0
  22. package/dist/src/jwt/encrypt.js.map +1 -0
  23. package/dist/src/jwt/profile.js +102 -0
  24. package/dist/src/jwt/profile.js.map +1 -0
  25. package/dist/src/jwt/sign.js +173 -0
  26. package/dist/src/jwt/sign.js.map +1 -0
  27. package/dist/src/jwt/verify.js +118 -0
  28. package/dist/src/jwt/verify.js.map +1 -0
  29. package/dist/src/key/encryption.js +132 -0
  30. package/dist/src/key/encryption.js.map +1 -0
  31. package/dist/src/key/export.js +46 -0
  32. package/dist/src/key/export.js.map +1 -0
  33. package/dist/src/key/generate.js +47 -0
  34. package/dist/src/key/generate.js.map +1 -0
  35. package/dist/src/key/import.js +136 -0
  36. package/dist/src/key/import.js.map +1 -0
  37. package/dist/src/legacy/rs256.js +11 -0
  38. package/dist/src/legacy/rs256.js.map +1 -0
  39. package/dist/src/legacy/rsa.js +37 -0
  40. package/dist/src/legacy/rsa.js.map +1 -0
  41. package/dist/src/lib/algorithms.js +60 -0
  42. package/dist/src/lib/algorithms.js.map +1 -0
  43. package/dist/src/lib/base64url.js +28 -0
  44. package/dist/src/lib/base64url.js.map +1 -0
  45. package/dist/src/lib/claims.js +111 -0
  46. package/dist/src/lib/claims.js.map +1 -0
  47. package/dist/src/lib/duration.js +24 -0
  48. package/dist/src/lib/duration.js.map +1 -0
  49. package/dist/src/lib/entropy.js +70 -0
  50. package/dist/src/lib/entropy.js.map +1 -0
  51. package/dist/src/lib/headers.js +75 -0
  52. package/dist/src/lib/headers.js.map +1 -0
  53. package/dist/src/lib/invariants.js +12 -0
  54. package/dist/src/lib/invariants.js.map +1 -0
  55. package/dist/src/lib/json.js +140 -0
  56. package/dist/src/lib/json.js.map +1 -0
  57. package/dist/src/lib/jwe_algorithms.js +81 -0
  58. package/dist/src/lib/jwe_algorithms.js.map +1 -0
  59. package/dist/src/lib/payload_hygiene.js +116 -0
  60. package/dist/src/lib/payload_hygiene.js.map +1 -0
  61. package/dist/src/lib/utf8.js +27 -0
  62. package/dist/src/lib/utf8.js.map +1 -0
  63. package/dist/src/revocation/memory.js +67 -0
  64. package/dist/src/revocation/memory.js.map +1 -0
  65. package/dist/src/revocation/store.js +25 -0
  66. package/dist/src/revocation/store.js.map +1 -0
  67. package/dist/src/types.js +51 -0
  68. package/dist/src/types.js.map +1 -0
  69. package/dist/src/util/errors.js +96 -0
  70. package/dist/src/util/errors.js.map +1 -0
  71. package/dist/src/util/unsafe-decode.js +45 -0
  72. package/dist/src/util/unsafe-decode.js.map +1 -0
  73. package/dist/types/index.d.ts +29 -0
  74. package/dist/types/index.d.ts.map +1 -0
  75. package/dist/types/src/cookbook/access-refresh.d.ts +77 -0
  76. package/dist/types/src/cookbook/access-refresh.d.ts.map +1 -0
  77. package/dist/types/src/http/bearer.d.ts +20 -0
  78. package/dist/types/src/http/bearer.d.ts.map +1 -0
  79. package/dist/types/src/http/cookies.d.ts +38 -0
  80. package/dist/types/src/http/cookies.d.ts.map +1 -0
  81. package/dist/types/src/jwks/local.d.ts +16 -0
  82. package/dist/types/src/jwks/local.d.ts.map +1 -0
  83. package/dist/types/src/jwks/remote.d.ts +23 -0
  84. package/dist/types/src/jwks/remote.d.ts.map +1 -0
  85. package/dist/types/src/jwt/decrypt.d.ts +23 -0
  86. package/dist/types/src/jwt/decrypt.d.ts.map +1 -0
  87. package/dist/types/src/jwt/decryption_profile.d.ts +32 -0
  88. package/dist/types/src/jwt/decryption_profile.d.ts.map +1 -0
  89. package/dist/types/src/jwt/encrypt.d.ts +34 -0
  90. package/dist/types/src/jwt/encrypt.d.ts.map +1 -0
  91. package/dist/types/src/jwt/profile.d.ts +37 -0
  92. package/dist/types/src/jwt/profile.d.ts.map +1 -0
  93. package/dist/types/src/jwt/sign.d.ts +59 -0
  94. package/dist/types/src/jwt/sign.d.ts.map +1 -0
  95. package/dist/types/src/jwt/verify.d.ts +41 -0
  96. package/dist/types/src/jwt/verify.d.ts.map +1 -0
  97. package/dist/types/src/key/encryption.d.ts +37 -0
  98. package/dist/types/src/key/encryption.d.ts.map +1 -0
  99. package/dist/types/src/key/export.d.ts +10 -0
  100. package/dist/types/src/key/export.d.ts.map +1 -0
  101. package/dist/types/src/key/generate.d.ts +27 -0
  102. package/dist/types/src/key/generate.d.ts.map +1 -0
  103. package/dist/types/src/key/import.d.ts +20 -0
  104. package/dist/types/src/key/import.d.ts.map +1 -0
  105. package/dist/types/src/legacy/rs256.d.ts +11 -0
  106. package/dist/types/src/legacy/rs256.d.ts.map +1 -0
  107. package/dist/types/src/legacy/rsa.d.ts +25 -0
  108. package/dist/types/src/legacy/rsa.d.ts.map +1 -0
  109. package/dist/types/src/lib/algorithms.d.ts +34 -0
  110. package/dist/types/src/lib/algorithms.d.ts.map +1 -0
  111. package/dist/types/src/lib/base64url.d.ts +8 -0
  112. package/dist/types/src/lib/base64url.d.ts.map +1 -0
  113. package/dist/types/src/lib/claims.d.ts +15 -0
  114. package/dist/types/src/lib/claims.d.ts.map +1 -0
  115. package/dist/types/src/lib/duration.d.ts +7 -0
  116. package/dist/types/src/lib/duration.d.ts.map +1 -0
  117. package/dist/types/src/lib/entropy.d.ts +21 -0
  118. package/dist/types/src/lib/entropy.d.ts.map +1 -0
  119. package/dist/types/src/lib/headers.d.ts +19 -0
  120. package/dist/types/src/lib/headers.d.ts.map +1 -0
  121. package/dist/types/src/lib/invariants.d.ts +6 -0
  122. package/dist/types/src/lib/invariants.d.ts.map +1 -0
  123. package/dist/types/src/lib/json.d.ts +24 -0
  124. package/dist/types/src/lib/json.d.ts.map +1 -0
  125. package/dist/types/src/lib/jwe_algorithms.d.ts +46 -0
  126. package/dist/types/src/lib/jwe_algorithms.d.ts.map +1 -0
  127. package/dist/types/src/lib/payload_hygiene.d.ts +20 -0
  128. package/dist/types/src/lib/payload_hygiene.d.ts.map +1 -0
  129. package/dist/types/src/lib/utf8.d.ts +7 -0
  130. package/dist/types/src/lib/utf8.d.ts.map +1 -0
  131. package/dist/types/src/revocation/memory.d.ts +22 -0
  132. package/dist/types/src/revocation/memory.d.ts.map +1 -0
  133. package/dist/types/src/revocation/store.d.ts +17 -0
  134. package/dist/types/src/revocation/store.d.ts.map +1 -0
  135. package/dist/types/src/types.d.ts +196 -0
  136. package/dist/types/src/types.d.ts.map +1 -0
  137. package/dist/types/src/util/errors.d.ts +89 -0
  138. package/dist/types/src/util/errors.d.ts.map +1 -0
  139. package/dist/types/src/util/unsafe-decode.d.ts +21 -0
  140. package/dist/types/src/util/unsafe-decode.d.ts.map +1 -0
  141. package/package.json +70 -0
@@ -0,0 +1,85 @@
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 { importJWK } from "jose";
10
+ import { EntropyCheckFailed, JWKSNoMatchingKey, KeyImportFailed, } from "../util/errors.js";
11
+ import { getAlgorithmProperties, isValidAlgorithm } from "../lib/algorithms.js";
12
+ import { decodeBase64url } from "../lib/base64url.js";
13
+ import { validateHMACSecret } from "../lib/entropy.js";
14
+ function isCandidate(jwk, header) {
15
+ const info = getAlgorithmProperties(header.alg);
16
+ if (jwk.kty !== info.kty)
17
+ return false;
18
+ if (jwk.use !== undefined && jwk.use !== "sig")
19
+ return false;
20
+ if (jwk.alg !== undefined && jwk.alg !== header.alg)
21
+ return false;
22
+ if (info.crv !== undefined && jwk.crv !== info.crv)
23
+ return false;
24
+ if (header.kid !== undefined && jwk.kid !== header.kid)
25
+ return false;
26
+ return true;
27
+ }
28
+ /** Shared selection + import used by both local and remote sets. */
29
+ export async function resolveFromJwks(keys, header) {
30
+ const candidates = keys.filter((jwk) => isCandidate(jwk, header));
31
+ if (candidates.length === 0) {
32
+ throw new JWKSNoMatchingKey("No key in the JWKS matches this token");
33
+ }
34
+ if (candidates.length > 1) {
35
+ throw new JWKSNoMatchingKey("Multiple keys in the JWKS match this token - issue tokens with a kid");
36
+ }
37
+ const jwk = candidates[0];
38
+ try {
39
+ if (jwk.kty === "oct") {
40
+ if (typeof jwk.k !== "string") {
41
+ throw new KeyImportFailed("oct JWK is missing its key material");
42
+ }
43
+ const secret = decodeBase64url(jwk.k);
44
+ validateHMACSecret(secret, header.alg);
45
+ return { alg: header.alg, key: secret };
46
+ }
47
+ const key = await importJWK(jwk, header.alg);
48
+ return { alg: header.alg, key };
49
+ }
50
+ catch (cause) {
51
+ if (cause instanceof KeyImportFailed || cause instanceof EntropyCheckFailed) {
52
+ throw cause;
53
+ }
54
+ throw new KeyImportFailed("JWKS key could not be imported", { cause });
55
+ }
56
+ }
57
+ /** Validate the developer-supplied JWKS document shape (config error -> TypeError). */
58
+ export function validateJwksShape(jwks) {
59
+ if (typeof jwks !== "object" ||
60
+ jwks === null ||
61
+ !Array.isArray(jwks.keys)) {
62
+ throw new TypeError("A JWKS must be an object with a keys array");
63
+ }
64
+ const keys = jwks.keys;
65
+ if (keys.length > 1000) {
66
+ throw new TypeError("JWKS has an unreasonable number of keys");
67
+ }
68
+ // Entries outside the registry are dropped, not fatal: real-world JWKS
69
+ // documents routinely carry keys for algorithms we refuse to speak.
70
+ return keys.filter((jwk) => typeof jwk === "object" &&
71
+ jwk !== null &&
72
+ typeof jwk.kty === "string" &&
73
+ (jwk.alg === undefined ||
74
+ (typeof jwk.alg === "string" && isValidAlgorithm(jwk.alg))));
75
+ }
76
+ /** Create a {@link KeySource} backed by a static JWKS document. */
77
+ export function createLocalJWKSet(jwks) {
78
+ const keys = validateJwksShape(jwks);
79
+ return {
80
+ getVerificationKey(header) {
81
+ return resolveFromJwks(keys, header);
82
+ },
83
+ };
84
+ }
85
+ //# sourceMappingURL=local.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local.js","sourceRoot":"","sources":["../../../src/jwks/local.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,121 @@
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 { JWKSFetchFailed, JWKSNoMatchingKey } from "../util/errors.js";
11
+ import { resolveFromJwks, validateJwksShape } from "./local.js";
12
+ const DEFAULT_CACHE_TTL_SECONDS = 300;
13
+ const DEFAULT_COOLDOWN_SECONDS = 30;
14
+ const DEFAULT_TIMEOUT_MS = 5000;
15
+ const MAX_CACHE_TTL_SECONDS = 86400;
16
+ const MAX_RESPONSE_BYTES = 1_048_576;
17
+ function cacheTtlFrom(response, fallbackSeconds) {
18
+ const cacheControl = response.headers.get("cache-control");
19
+ const match = cacheControl === null ? null : /max-age\s*=\s*(\d{1,8})/.exec(cacheControl);
20
+ const ttl = match === null ? fallbackSeconds : Number(match[1]);
21
+ return Math.min(Math.max(ttl, 0), MAX_CACHE_TTL_SECONDS);
22
+ }
23
+ /** Create a {@link KeySource} that fetches and caches a remote JWKS. */
24
+ export function createRemoteJWKSet(url, options = {}) {
25
+ const jwksUrl = new URL(url);
26
+ if (jwksUrl.protocol !== "https:") {
27
+ throw new TypeError("JWKS URLs must use https");
28
+ }
29
+ const fetchImpl = options.fetch ?? fetch;
30
+ const fallbackTtl = options.cacheTtlSeconds ?? DEFAULT_CACHE_TTL_SECONDS;
31
+ const cooldownMs = (options.cooldownSeconds ?? DEFAULT_COOLDOWN_SECONDS) * 1000;
32
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
33
+ let cachedKeys;
34
+ let expiresAtMs = 0;
35
+ let lastAttemptMs = 0;
36
+ let inflight;
37
+ async function fetchJwks() {
38
+ lastAttemptMs = Date.now();
39
+ let response;
40
+ try {
41
+ response = await fetchImpl(jwksUrl, {
42
+ redirect: "error",
43
+ headers: { accept: "application/jwk-set+json, application/json" },
44
+ signal: AbortSignal.timeout(timeoutMs),
45
+ });
46
+ }
47
+ catch (cause) {
48
+ throw new JWKSFetchFailed("JWKS endpoint could not be reached", { cause });
49
+ }
50
+ if (!response.ok) {
51
+ throw new JWKSFetchFailed(`JWKS endpoint responded with status ${response.status}`);
52
+ }
53
+ const body = await response.text();
54
+ if (body.length > MAX_RESPONSE_BYTES) {
55
+ throw new JWKSFetchFailed("JWKS response exceeds the size cap");
56
+ }
57
+ let parsed;
58
+ try {
59
+ parsed = JSON.parse(body);
60
+ }
61
+ catch (cause) {
62
+ throw new JWKSFetchFailed("JWKS response is not valid JSON", { cause });
63
+ }
64
+ let keys;
65
+ try {
66
+ keys = validateJwksShape(parsed);
67
+ }
68
+ catch (cause) {
69
+ throw new JWKSFetchFailed("JWKS response is not a valid key set", { cause });
70
+ }
71
+ cachedKeys = keys;
72
+ expiresAtMs = Date.now() + cacheTtlFrom(response, fallbackTtl) * 1000;
73
+ }
74
+ function refresh() {
75
+ // Concurrent verifications share one fetch.
76
+ inflight ??= fetchJwks().finally(() => {
77
+ inflight = undefined;
78
+ });
79
+ return inflight;
80
+ }
81
+ async function ensureFresh() {
82
+ const now = Date.now();
83
+ if (cachedKeys !== undefined && now < expiresAtMs) {
84
+ return;
85
+ }
86
+ if (now - lastAttemptMs < cooldownMs && inflight === undefined) {
87
+ // In cooldown: serve stale keys if we have them, else fail.
88
+ if (cachedKeys !== undefined)
89
+ return;
90
+ throw new JWKSFetchFailed("JWKS fetch is cooling down after a recent failure");
91
+ }
92
+ try {
93
+ await refresh();
94
+ }
95
+ catch (error) {
96
+ // Stale-while-error: an unreachable endpoint must not take down
97
+ // verification while we still hold previously good keys.
98
+ if (cachedKeys === undefined)
99
+ throw error;
100
+ }
101
+ }
102
+ return {
103
+ async getVerificationKey(header) {
104
+ await ensureFresh();
105
+ try {
106
+ return await resolveFromJwks(cachedKeys, header);
107
+ }
108
+ catch (error) {
109
+ // Unknown kid may mean the signer rotated keys: refetch once
110
+ // (respecting the cooldown), then fail closed.
111
+ const canRetry = error instanceof JWKSNoMatchingKey &&
112
+ Date.now() - lastAttemptMs >= cooldownMs;
113
+ if (!canRetry)
114
+ throw error;
115
+ await refresh();
116
+ return resolveFromJwks(cachedKeys, header);
117
+ }
118
+ },
119
+ };
120
+ }
121
+ //# sourceMappingURL=remote.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remote.js","sourceRoot":"","sources":["../../../src/jwks/remote.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,126 @@
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 { compactDecrypt } from "jose";
17
+ import { AlgorithmNotAllowed, JWTClaimValidationFailed, JWTInvalid, JWTRevoked, MissingClaim, RevocationCheckFailed, } from "../util/errors.js";
18
+ import { decodeBase64url } from "../lib/base64url.js";
19
+ import { decodeUTF8 } from "../lib/utf8.js";
20
+ import { parseJsonObject } from "../lib/json.js";
21
+ import { sanitizeKid, normalizeTyp } from "../lib/headers.js";
22
+ import { getContentEncryptionProperties, getKeyManagementProperties, } from "../lib/jwe_algorithms.js";
23
+ import { validateClaims } from "../lib/claims.js";
24
+ import { buildRevocationContext } from "../revocation/store.js";
25
+ import { SetAlg, } from "../types.js";
26
+ const MAX_TOKEN_LENGTH = 16384;
27
+ // Never honored on a JWE header. `zip` is here for the same reason as on the
28
+ // JWS side and then some: an attacker-set `zip: DEF` is a decompression bomb.
29
+ const FORBIDDEN_PARAMS = ["jku", "x5u", "jwk", "x5c", "x5t", "zip", "crit"];
30
+ function validateJweHeader(raw, profile) {
31
+ for (const param of FORBIDDEN_PARAMS) {
32
+ if (param in raw)
33
+ throw new JWTInvalid(`Token header parameter "${param}" is not supported`);
34
+ }
35
+ const alg = raw.alg;
36
+ if (typeof alg !== "string" || alg.toLowerCase() === "none") {
37
+ throw new AlgorithmNotAllowed("Key-management algorithm 'none' (or a missing alg) is not allowed");
38
+ }
39
+ if (!profile.keyManagementAlgorithms.some((allowed) => allowed === alg)) {
40
+ throw new AlgorithmNotAllowed("Token key-management algorithm is not in this profile's allowlist");
41
+ }
42
+ getKeyManagementProperties(alg); // defense in depth
43
+ const enc = raw.enc;
44
+ if (typeof enc !== "string" || enc.toLowerCase() === "none") {
45
+ throw new AlgorithmNotAllowed("Content-encryption algorithm 'none' (or a missing enc) is not allowed");
46
+ }
47
+ if (!profile.contentEncryptionAlgorithms.some((allowed) => allowed === enc)) {
48
+ throw new AlgorithmNotAllowed("Token content-encryption algorithm is not in this profile's allowlist");
49
+ }
50
+ getContentEncryptionProperties(enc);
51
+ const typ = raw.typ;
52
+ if (typeof typ !== "string" || typ.length === 0) {
53
+ throw new JWTInvalid("Token header is missing a typ (explicit typing is mandatory)");
54
+ }
55
+ if (normalizeTyp(typ) !== normalizeTyp(profile.typ)) {
56
+ throw new JWTClaimValidationFailed("typ", "Unexpected token type for this profile");
57
+ }
58
+ const header = { alg: SetAlg(alg), enc: SetAlg(enc), typ };
59
+ if (raw.kid !== undefined)
60
+ header.kid = sanitizeKid(raw.kid);
61
+ if (raw.cty !== undefined) {
62
+ if (typeof raw.cty !== "string")
63
+ throw new JWTInvalid("Token header cty must be a string");
64
+ header.cty = raw.cty;
65
+ }
66
+ return header;
67
+ }
68
+ /**
69
+ * Decrypt and validate a compact JWE against a profile. The returned
70
+ * {@link DecryptedJwt} is the only way to obtain one - there is no backdoor,
71
+ * and it is a distinct type from a `VerifiedJwt` (the signed-token proof).
72
+ */
73
+ export async function jwtDecrypt(token, profile) {
74
+ if (typeof token !== "string" || token.length === 0) {
75
+ throw new JWTInvalid("Token must be a non-empty string");
76
+ }
77
+ if (token.length > MAX_TOKEN_LENGTH) {
78
+ throw new JWTInvalid("Token exceeds the maximum length");
79
+ }
80
+ const segments = token.split(".");
81
+ if (segments.length !== 5) {
82
+ // A JWS has three segments; anything but five is not a compact JWE.
83
+ throw new JWTInvalid("Token is not a compact JWE (expected five segments)");
84
+ }
85
+ const rawHeader = segments[0];
86
+ if (rawHeader === "")
87
+ throw new JWTInvalid("Token has an empty header segment");
88
+ const header = validateJweHeader(parseJsonObject(decodeUTF8(decodeBase64url(rawHeader)), "header"), profile);
89
+ if (profile.key.algorithm !== header.alg) {
90
+ throw new AlgorithmNotAllowed("Token key-management algorithm does not match the profile's key");
91
+ }
92
+ let plaintext;
93
+ try {
94
+ const result = await compactDecrypt(token, profile.key.key, {
95
+ keyManagementAlgorithms: [header.alg],
96
+ contentEncryptionAlgorithms: [header.enc],
97
+ });
98
+ plaintext = result.plaintext;
99
+ }
100
+ catch (cause) {
101
+ throw new JWTInvalid("Token decryption failed", { cause });
102
+ }
103
+ const payload = parseJsonObject(decodeUTF8(plaintext), "payload");
104
+ await validateClaims(payload, profile);
105
+ if (profile.revocation !== undefined) {
106
+ if (typeof payload.jti !== "string") {
107
+ throw new MissingClaim("jti", "This profile has a revocation store but the token carries no jti");
108
+ }
109
+ let revoked;
110
+ try {
111
+ revoked = await profile.revocation.isRevoked(buildRevocationContext(payload));
112
+ }
113
+ catch (cause) {
114
+ if (profile.unsafeFailOpenOnRevocationError === true) {
115
+ revoked = false;
116
+ }
117
+ else {
118
+ throw new RevocationCheckFailed("Revocation store errored - failing closed (LW-rev.4)", { cause });
119
+ }
120
+ }
121
+ if (revoked)
122
+ throw new JWTRevoked("Token has been revoked");
123
+ }
124
+ return { header, payload: payload };
125
+ }
126
+ //# sourceMappingURL=decrypt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decrypt.js","sourceRoot":"","sources":["../../../src/jwt/decrypt.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,78 @@
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 { parseDuration } from "../lib/duration.js";
8
+ import { toValidContentEncryptionAlg, toValidKeyManagementAlg, } from "../lib/jwe_algorithms.js";
9
+ import { isLacewingEncryptionKey, toSeconds, } from "../types.js";
10
+ const DEFAULT_CLOCK_SKEW_SECONDS = 5;
11
+ const MAX_CLOCK_SKEW_SECONDS = 120;
12
+ function requireNonEmptyString(value, field) {
13
+ if (typeof value !== "string" || value.length === 0) {
14
+ throw new TypeError(`Decryption profile "${field}" must be a non-empty string`);
15
+ }
16
+ return value;
17
+ }
18
+ /** Build the security boundary that `jwtDecrypt` enforces. */
19
+ export function defineDecryptionProfile(options) {
20
+ const typ = requireNonEmptyString(options.typ, "typ");
21
+ const iss = requireNonEmptyString(options.issuer, "issuer");
22
+ const aud = requireNonEmptyString(options.audience, "audience");
23
+ if (!Array.isArray(options.keyManagementAlgorithms) || options.keyManagementAlgorithms.length === 0) {
24
+ throw new TypeError("Decryption profile requires a non-empty keyManagementAlgorithms allowlist");
25
+ }
26
+ if (!Array.isArray(options.contentEncryptionAlgorithms) || options.contentEncryptionAlgorithms.length === 0) {
27
+ throw new TypeError("Decryption profile requires a non-empty contentEncryptionAlgorithms allowlist");
28
+ }
29
+ const keyManagementAlgorithms = Object.freeze(options.keyManagementAlgorithms.map(toValidKeyManagementAlg));
30
+ const contentEncryptionAlgorithms = Object.freeze(options.contentEncryptionAlgorithms.map(toValidContentEncryptionAlg));
31
+ if (!isLacewingEncryptionKey(options.key)) {
32
+ throw new TypeError("Decryption profile key must be imported through importEncryptionKey()/generate*");
33
+ }
34
+ if (options.key.keyType === "public") {
35
+ throw new TypeError("Decryption needs the private/secret key, not the public key");
36
+ }
37
+ if (!keyManagementAlgorithms.some((allowed) => allowed === options.key.algorithm)) {
38
+ throw new TypeError("The profile key's algorithm is not in the keyManagementAlgorithms allowlist");
39
+ }
40
+ const maxTokenAge = parseDuration(options.maxTokenAge);
41
+ if (maxTokenAge < 1)
42
+ throw new TypeError("Decryption profile maxTokenAge must be at least one second");
43
+ const maxClockSkew = parseDuration(options.maxClockSkew ?? DEFAULT_CLOCK_SKEW_SECONDS);
44
+ if (maxClockSkew > MAX_CLOCK_SKEW_SECONDS) {
45
+ throw new TypeError(`Decryption profile maxClockSkew is capped at ${MAX_CLOCK_SKEW_SECONDS} seconds`);
46
+ }
47
+ if (options.revocation !== undefined && typeof options.revocation.isRevoked !== "function") {
48
+ throw new TypeError("Decryption profile revocation store must implement isRevoked()");
49
+ }
50
+ if (options.claimValidators !== undefined) {
51
+ for (const [claim, validator] of Object.entries(options.claimValidators)) {
52
+ if (typeof validator !== "function")
53
+ throw new TypeError(`Claim validator for "${claim}" must be a function`);
54
+ }
55
+ }
56
+ if (options.subject !== undefined)
57
+ requireNonEmptyString(options.subject, "subject");
58
+ const profile = {
59
+ typ,
60
+ iss,
61
+ aud,
62
+ keyManagementAlgorithms,
63
+ contentEncryptionAlgorithms,
64
+ key: options.key,
65
+ maxTokenAge: toSeconds(maxTokenAge),
66
+ maxClockSkew: toSeconds(maxClockSkew),
67
+ };
68
+ if (options.revocation !== undefined)
69
+ profile.revocation = options.revocation;
70
+ if (options.claimValidators !== undefined)
71
+ profile.claimValidators = { ...options.claimValidators };
72
+ if (options.subject !== undefined)
73
+ profile.subject = options.subject;
74
+ if (options.unsafeFailOpenOnRevocationError === true)
75
+ profile.unsafeFailOpenOnRevocationError = true;
76
+ return Object.freeze(profile);
77
+ }
78
+ //# sourceMappingURL=decryption_profile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decryption_profile.js","sourceRoot":"","sources":["../../../src/jwt/decryption_profile.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,149 @@
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 { CompactEncrypt } from "jose";
12
+ import { KeyTypeMismatch, MaxLifetimeExceeded, MissingClaim } from "../util/errors.js";
13
+ import { parseDuration } from "../lib/duration.js";
14
+ import { encodeUTF8 } from "../lib/utf8.js";
15
+ import { getContentEncryptionProperties, getKeyManagementProperties } from "../lib/jwe_algorithms.js";
16
+ import { isLacewingEncryptionKey } from "../types.js";
17
+ const DEFAULT_MAX_LIFETIME_SECONDS = 3600;
18
+ const DEFAULT_CONTENT_ENCRYPTION = "A256GCM";
19
+ const RESERVED_CLAIMS = new Set(["iss", "sub", "aud", "exp", "nbf", "iat", "jti"]);
20
+ export class EncryptJWT {
21
+ #typ;
22
+ #maxLifetime;
23
+ #enc;
24
+ #claims = new Map();
25
+ #iss;
26
+ #aud;
27
+ #sub;
28
+ #jti;
29
+ #expiresIn;
30
+ #notBefore;
31
+ #waiveIssuer = false;
32
+ #waiveAudience = false;
33
+ #waiveExpiration = false;
34
+ constructor(typ, options = {}) {
35
+ if (typeof typ !== "string" || typ.length === 0) {
36
+ throw new TypeError("EncryptJWT requires an explicit token typ, e.g. new EncryptJWT(\"at+jwt\")");
37
+ }
38
+ this.#typ = typ;
39
+ this.#maxLifetime = parseDuration(options.maxLifetime ?? DEFAULT_MAX_LIFETIME_SECONDS);
40
+ this.#enc = options.contentEncryption ?? DEFAULT_CONTENT_ENCRYPTION;
41
+ // Fail fast on an unknown enc rather than at .encrypt() time.
42
+ getContentEncryptionProperties(this.#enc);
43
+ }
44
+ issuer(iss) {
45
+ if (typeof iss !== "string" || iss.length === 0)
46
+ throw new TypeError("issuer must be a non-empty string");
47
+ this.#iss = iss;
48
+ return this;
49
+ }
50
+ audience(aud) {
51
+ const values = Array.isArray(aud) ? aud : [aud];
52
+ if (values.length === 0 || values.some((v) => typeof v !== "string" || v.length === 0)) {
53
+ throw new TypeError("audience must be a non-empty string or array of them");
54
+ }
55
+ this.#aud = aud;
56
+ return this;
57
+ }
58
+ subject(sub) {
59
+ if (typeof sub !== "string" || sub.length === 0)
60
+ throw new TypeError("subject must be a non-empty string");
61
+ this.#sub = sub;
62
+ return this;
63
+ }
64
+ /** Override the auto-assigned `jti`. Uniqueness is then your problem. */
65
+ jwtId(jti) {
66
+ if (typeof jti !== "string" || jti.length === 0)
67
+ throw new TypeError("jwtId must be a non-empty string");
68
+ this.#jti = jti;
69
+ return this;
70
+ }
71
+ expiresIn(duration) {
72
+ const seconds = parseDuration(duration);
73
+ if (seconds < 1)
74
+ throw new TypeError("expiresIn must be at least one second");
75
+ this.#expiresIn = seconds;
76
+ return this;
77
+ }
78
+ notBefore(duration) {
79
+ this.#notBefore = parseDuration(duration);
80
+ return this;
81
+ }
82
+ claim(name, value) {
83
+ if (typeof name !== "string" || name.length === 0)
84
+ throw new TypeError("claim name must be a non-empty string");
85
+ if (RESERVED_CLAIMS.has(name)) {
86
+ throw new TypeError(`"${name}" is a registered claim - use the dedicated builder method`);
87
+ }
88
+ this.#claims.set(name, value);
89
+ return this;
90
+ }
91
+ unsafeAllowMissingIssuer() {
92
+ this.#waiveIssuer = true;
93
+ return this;
94
+ }
95
+ unsafeAllowMissingAudience() {
96
+ this.#waiveAudience = true;
97
+ return this;
98
+ }
99
+ unsafeAllowMissingExpiration() {
100
+ this.#waiveExpiration = true;
101
+ return this;
102
+ }
103
+ async encrypt(key) {
104
+ if (!isLacewingEncryptionKey(key)) {
105
+ throw new TypeError("encrypt() only accepts keys imported through importEncryptionKey()/generate*");
106
+ }
107
+ if (key.keyType === "private") {
108
+ throw new KeyTypeMismatch("Encrypt to the recipient's public (or shared secret) key, not a private key");
109
+ }
110
+ const info = getKeyManagementProperties(key.algorithm);
111
+ // A `dir` key IS the content-encryption key, so its size fixes the enc.
112
+ if (info.kind === "dir") {
113
+ const need = getContentEncryptionProperties(this.#enc).cekBytes;
114
+ if (key.key.length !== need) {
115
+ throw new KeyTypeMismatch(`This dir key is not sized for ${this.#enc} (needs ${need} bytes)`);
116
+ }
117
+ }
118
+ if (this.#iss === undefined && !this.#waiveIssuer) {
119
+ throw new MissingClaim("iss", "Tokens must name their issuer - call .issuer() (or .unsafeAllowMissingIssuer())");
120
+ }
121
+ if (this.#aud === undefined && !this.#waiveAudience) {
122
+ throw new MissingClaim("aud", "Tokens must name their audience - call .audience() (or .unsafeAllowMissingAudience())");
123
+ }
124
+ if (this.#expiresIn === undefined && !this.#waiveExpiration) {
125
+ throw new MissingClaim("exp", "Tokens must expire - call .expiresIn() (or .unsafeAllowMissingExpiration())");
126
+ }
127
+ if (this.#expiresIn !== undefined && this.#expiresIn > this.#maxLifetime) {
128
+ throw new MaxLifetimeExceeded(`Requested lifetime exceeds the ${this.#maxLifetime}s cap - long-lived tokens need an explicit maxLifetime option`);
129
+ }
130
+ const now = Math.floor(Date.now() / 1000);
131
+ const payload = Object.fromEntries(this.#claims);
132
+ if (this.#iss !== undefined)
133
+ payload.iss = this.#iss;
134
+ if (this.#sub !== undefined)
135
+ payload.sub = this.#sub;
136
+ if (this.#aud !== undefined)
137
+ payload.aud = this.#aud;
138
+ payload.jti = this.#jti ?? globalThis.crypto.randomUUID();
139
+ if (this.#notBefore !== undefined)
140
+ payload.nbf = now + this.#notBefore;
141
+ if (this.#expiresIn !== undefined)
142
+ payload.exp = now + this.#expiresIn;
143
+ payload.iat = now;
144
+ return new CompactEncrypt(encodeUTF8(JSON.stringify(payload)))
145
+ .setProtectedHeader({ alg: key.algorithm, enc: this.#enc, typ: this.#typ })
146
+ .encrypt(key.key);
147
+ }
148
+ }
149
+ //# sourceMappingURL=encrypt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"encrypt.js","sourceRoot":"","sources":["../../../src/jwt/encrypt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,MAAM,CAAC;AACtC,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACvF,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC5C,OAAO,EAAE,8BAA8B,EAAE,0BAA0B,EAAE,MAAM,0BAA0B,CAAC;AACtG,OAAO,EAAE,uBAAuB,EAAoD,MAAM,aAAa,CAAC;AAExG,MAAM,4BAA4B,GAAG,IAAI,CAAC;AAC1C,MAAM,0BAA0B,GAAG,SAAS,CAAC;AAC7C,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;AASnF,MAAM,OAAO,UAAU;IACb,IAAI,CAAS;IACb,YAAY,CAAkB;IAC9B,IAAI,CAAS;IACb,OAAO,GAAG,IAAI,GAAG,EAAmB,CAAC;IAC9C,IAAI,CAAU;IACd,IAAI,CAAqB;IACzB,IAAI,CAAU;IACd,IAAI,CAAU;IACd,UAAU,CAAmB;IAC7B,UAAU,CAAmB;IAC7B,YAAY,GAAG,KAAK,CAAC;IACrB,cAAc,GAAG,KAAK,CAAC;IACvB,gBAAgB,GAAG,KAAK,CAAC;IAEzB,YAAY,GAAW,EAAE,UAA6B,EAAE;QACvD,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACjD,MAAM,IAAI,SAAS,CAAC,4EAA4E,CAAC,CAAC;QACnG,CAAC;QACD,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC;QAChB,IAAI,CAAC,YAAY,GAAG,aAAa,CAAC,OAAO,CAAC,WAAW,IAAI,4BAA4B,CAAC,CAAC;QACvF,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC,iBAAiB,IAAI,0BAA0B,CAAC;QACpE,8DAA8D;QAC9D,8BAA8B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC3C,CAAC;IAED,MAAM,CAAC,GAAW;QACjB,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC;YAAE,MAAM,IAAI,SAAS,CAAC,mCAAmC,CAAC,CAAC;QAC1G,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC;QAChB,OAAO,IAAI,CAAC;IACb,CAAC;IAED,QAAQ,CAAC,GAAsB;QAC9B,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QAChD,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,EAAE,CAAC;YACxF,MAAM,IAAI,SAAS,CAAC,sDAAsD,CAAC,CAAC;QAC7E,CAAC;QACD,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC;QAChB,OAAO,IAAI,CAAC;IACb,CAAC;IAED,OAAO,CAAC,GAAW;QAClB,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC;YAAE,MAAM,IAAI,SAAS,CAAC,oCAAoC,CAAC,CAAC;QAC3G,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC;QAChB,OAAO,IAAI,CAAC;IACb,CAAC;IAED,yEAAyE;IACzE,KAAK,CAAC,GAAW;QAChB,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC;YAAE,MAAM,IAAI,SAAS,CAAC,kCAAkC,CAAC,CAAC;QACzG,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC;QAChB,OAAO,IAAI,CAAC;IACb,CAAC;IAED,SAAS,CAAC,QAAyB;QAClC,MAAM,OAAO,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;QACxC,IAAI,OAAO,GAAG,CAAC;YAAE,MAAM,IAAI,SAAS,CAAC,uCAAuC,CAAC,CAAC;QAC9E,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC;QAC1B,OAAO,IAAI,CAAC;IACb,CAAC;IAED,SAAS,CAAC,QAAyB;QAClC,IAAI,CAAC,UAAU,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;QAC1C,OAAO,IAAI,CAAC;IACb,CAAC;IAED,KAAK,CAAC,IAAY,EAAE,KAAc;QACjC,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,MAAM,IAAI,SAAS,CAAC,uCAAuC,CAAC,CAAC;QAChH,IAAI,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YAC/B,MAAM,IAAI,SAAS,CAAC,IAAI,IAAI,4DAA4D,CAAC,CAAC;QAC3F,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QAC9B,OAAO,IAAI,CAAC;IACb,CAAC;IAED,wBAAwB;QACvB,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;QACzB,OAAO,IAAI,CAAC;IACb,CAAC;IAED,0BAA0B;QACzB,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC;QAC3B,OAAO,IAAI,CAAC;IACb,CAAC;IAED,4BAA4B;QAC3B,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC;QAC7B,OAAO,IAAI,CAAC;IACb,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,GAA0B;QACvC,IAAI,CAAC,uBAAuB,CAAC,GAAG,CAAC,EAAE,CAAC;YACnC,MAAM,IAAI,SAAS,CAAC,8EAA8E,CAAC,CAAC;QACrG,CAAC;QACD,IAAI,GAAG,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;YAC/B,MAAM,IAAI,eAAe,CAAC,6EAA6E,CAAC,CAAC;QAC1G,CAAC;QACD,MAAM,IAAI,GAAG,0BAA0B,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACvD,wEAAwE;QACxE,IAAI,IAAI,CAAC,IAAI,KAAK,KAAK,EAAE,CAAC;YACzB,MAAM,IAAI,GAAG,8BAA8B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC;YAChE,IAAK,GAAG,CAAC,GAAkB,CAAC,MAAM,KAAK,IAAI,EAAE,CAAC;gBAC7C,MAAM,IAAI,eAAe,CAAC,iCAAiC,IAAI,CAAC,IAAI,WAAW,IAAI,SAAS,CAAC,CAAC;YAC/F,CAAC;QACF,CAAC;QACD,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC;YACnD,MAAM,IAAI,YAAY,CAAC,KAAK,EAAE,iFAAiF,CAAC,CAAC;QAClH,CAAC;QACD,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,CAAC;YACrD,MAAM,IAAI,YAAY,CAAC,KAAK,EAAE,uFAAuF,CAAC,CAAC;QACxH,CAAC;QACD,IAAI,IAAI,CAAC,UAAU,KAAK,SAAS,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,CAAC;YAC7D,MAAM,IAAI,YAAY,CAAC,KAAK,EAAE,6EAA6E,CAAC,CAAC;QAC9G,CAAC;QACD,IAAI,IAAI,CAAC,UAAU,KAAK,SAAS,IAAI,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC;YAC1E,MAAM,IAAI,mBAAmB,CAC5B,kCAAkC,IAAI,CAAC,YAAY,+DAA+D,CAClH,CAAC;QACH,CAAC;QAED,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;QAC1C,MAAM,OAAO,GAA4B,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC1E,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS;YAAE,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC;QACrD,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS;YAAE,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC;QACrD,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS;YAAE,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC;QACrD,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC,IAAI,IAAI,UAAU,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;QAC1D,IAAI,IAAI,CAAC,UAAU,KAAK,SAAS;YAAE,OAAO,CAAC,GAAG,GAAG,GAAG,GAAG,IAAI,CAAC,UAAU,CAAC;QACvE,IAAI,IAAI,CAAC,UAAU,KAAK,SAAS;YAAE,OAAO,CAAC,GAAG,GAAG,GAAG,GAAG,IAAI,CAAC,UAAU,CAAC;QACvE,OAAO,CAAC,GAAG,GAAG,GAAG,CAAC;QAElB,OAAO,IAAI,cAAc,CAAC,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;aAC5D,kBAAkB,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,SAAS,EAAE,GAAG,EAAE,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC;aAC1E,OAAO,CAAC,GAAG,CAAC,GAA+C,CAAC,CAAC;IAChE,CAAC;CACD"}