@frontmcp/auth 1.10.15 → 1.11.0-rc.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.
Files changed (175) hide show
  1. package/browser/authorities/authorities.context.mjs +115 -0
  2. package/browser/authorities/authorities.engine.mjs +272 -0
  3. package/browser/authorities/authorities.errors.mjs +59 -0
  4. package/browser/authorities/authorities.evaluator.mjs +225 -0
  5. package/browser/authorities/authorities.metadata-augment.mjs +18 -0
  6. package/browser/authorities/authorities.registry.mjs +112 -0
  7. package/browser/authorities/authorities.schema.mjs +92 -0
  8. package/browser/authorities/authorities.scope-mapping.mjs +30 -0
  9. package/browser/authorities/authorities.validation.mjs +208 -0
  10. package/browser/authorities/index.mjs +43 -0
  11. package/browser/authorization/authorization.class.mjs +252 -0
  12. package/browser/authorization/authorization.types.mjs +64 -0
  13. package/browser/authorization/dcr-client.registry.mjs +222 -0
  14. package/browser/authorization/index.mjs +41 -0
  15. package/browser/authorization/orchestrated.accessor.mjs +86 -0
  16. package/browser/authorization/orchestrated.authorization.mjs +289 -0
  17. package/browser/authorization/public.authorization.mjs +124 -0
  18. package/browser/authorization/transparent.authorization.mjs +130 -0
  19. package/browser/cimd/cimd-redis.cache.mjs +231 -0
  20. package/browser/cimd/cimd.cache.mjs +215 -0
  21. package/browser/cimd/cimd.errors.mjs +142 -0
  22. package/browser/cimd/cimd.logger.mjs +15 -0
  23. package/browser/cimd/cimd.service.mjs +376 -0
  24. package/browser/cimd/cimd.types.mjs +228 -0
  25. package/browser/cimd/cimd.validator.mjs +217 -0
  26. package/browser/cimd/index.mjs +63 -0
  27. package/browser/common/auth-logger.interface.mjs +14 -0
  28. package/browser/common/index.mjs +6 -0
  29. package/browser/common/jwt.types.mjs +38 -0
  30. package/browser/common/session-user.types.mjs +0 -0
  31. package/browser/common/session.types.mjs +58 -0
  32. package/browser/common/zod-utils.mjs +0 -0
  33. package/browser/consent/consent.types.mjs +94 -0
  34. package/browser/consent/index.mjs +17 -0
  35. package/browser/context/frontmcp-auth-context.factory.mjs +29 -0
  36. package/browser/context/frontmcp-auth-context.impl.mjs +112 -0
  37. package/browser/context/index.mjs +7 -0
  38. package/browser/detection/auth-provider-detection.mjs +176 -0
  39. package/browser/detection/index.mjs +19 -0
  40. package/browser/errors/auth-internal.error.mjs +42 -0
  41. package/browser/errors/auth-internal.errors.mjs +150 -0
  42. package/browser/errors/index.mjs +53 -0
  43. package/browser/fetch/fetch-credential-middleware.mjs +91 -0
  44. package/browser/fetch/index.mjs +19 -0
  45. package/browser/index.mjs +573 -0
  46. package/browser/jwks/index.mjs +9 -0
  47. package/browser/jwks/jose.loader.mjs +28 -0
  48. package/browser/jwks/jwks.service.mjs +521 -0
  49. package/browser/jwks/jwks.utils.mjs +33 -0
  50. package/browser/options/app-auth.schema.mjs +18 -0
  51. package/browser/options/index.mjs +89 -0
  52. package/browser/options/orchestrated.schema.mjs +128 -0
  53. package/browser/options/public.schema.mjs +39 -0
  54. package/browser/options/schema.mjs +16 -0
  55. package/browser/options/shared.schemas.mjs +506 -0
  56. package/browser/options/static.schema.mjs +41 -0
  57. package/browser/options/transparent.schema.mjs +43 -0
  58. package/browser/options/utils.mjs +47 -0
  59. package/browser/people/index.mjs +15 -0
  60. package/browser/people/loopback-token.auth.mjs +115 -0
  61. package/browser/people/people-auth.options.mjs +37 -0
  62. package/browser/session/authorization-vault.mjs +279 -0
  63. package/browser/session/authorization.store.mjs +336 -0
  64. package/browser/session/consent.store.mjs +34 -0
  65. package/browser/session/credential-resume-link.mjs +43 -0
  66. package/browser/session/credentials-accessor.impl.mjs +54 -0
  67. package/browser/session/credentials-accessor.mjs +5 -0
  68. package/browser/session/encrypted-authorization-vault.mjs +405 -0
  69. package/browser/session/federated-auth.session.mjs +176 -0
  70. package/browser/session/incremental-auth-ticket.mjs +42 -0
  71. package/browser/session/index.mjs +266 -0
  72. package/browser/session/orchestrated-token.crypto.mjs +47 -0
  73. package/browser/session/orchestrated-token.store.mjs +286 -0
  74. package/browser/session/pending-login.mjs +37 -0
  75. package/browser/session/redis-session.store.mjs +255 -0
  76. package/browser/session/secure-store-accessor.impl.mjs +78 -0
  77. package/browser/session/secure-store-accessor.mjs +5 -0
  78. package/browser/session/secure-store-backends.mjs +111 -0
  79. package/browser/session/secure-store.factory.mjs +77 -0
  80. package/browser/session/session-credential-vault.factory.mjs +18 -0
  81. package/browser/session/session-credential-vault.mjs +243 -0
  82. package/browser/session/session-crypto.mjs +39 -0
  83. package/browser/session/session-rate-limiter.mjs +126 -0
  84. package/browser/session/session.transport.mjs +16 -0
  85. package/browser/session/signin-binding.mjs +30 -0
  86. package/browser/session/storage/in-memory-authorization-vault.mjs +28 -0
  87. package/browser/session/storage/index.mjs +14 -0
  88. package/browser/session/storage/storage-authorization-vault.mjs +301 -0
  89. package/browser/session/storage/storage-token-store.mjs +83 -0
  90. package/browser/session/storage-authorization.store.mjs +134 -0
  91. package/browser/session/storage-consent.store.mjs +33 -0
  92. package/browser/session/storage-federated-auth.session.mjs +55 -0
  93. package/browser/session/storage-orchestrated-token.store.mjs +197 -0
  94. package/browser/session/token-storage.factory.mjs +58 -0
  95. package/browser/session/token.refresh.mjs +36 -0
  96. package/browser/session/token.vault.mjs +61 -0
  97. package/browser/session/transport-session.types.mjs +108 -0
  98. package/browser/session/utils/auth-token.utils.mjs +58 -0
  99. package/browser/session/utils/index.mjs +23 -0
  100. package/browser/session/utils/session-crypto.utils.mjs +49 -0
  101. package/browser/session/utils/tiny-ttl-cache.mjs +46 -0
  102. package/browser/session/vault-encryption.mjs +187 -0
  103. package/browser/session/vercel-kv-session.store.mjs +230 -0
  104. package/browser/shared-server/index.mjs +17 -0
  105. package/browser/shared-server/shared-server-identity.mjs +55 -0
  106. package/browser/ui/base-layout.mjs +184 -0
  107. package/browser/ui/index.mjs +51 -0
  108. package/browser/ui/local-login.helper.mjs +46 -0
  109. package/browser/ui/templates.mjs +482 -0
  110. package/browser/utils/audience.validator.mjs +110 -0
  111. package/browser/utils/authorization-id.utils.mjs +10 -0
  112. package/browser/utils/index.mjs +45 -0
  113. package/browser/utils/redirect-uri.utils.mjs +22 -0
  114. package/browser/utils/scope-grant.utils.mjs +36 -0
  115. package/browser/utils/token-scopes.utils.mjs +12 -0
  116. package/browser/utils/www-authenticate.utils.mjs +164 -0
  117. package/browser/vault/auth-providers.accessor.impl.mjs +236 -0
  118. package/browser/vault/auth-providers.accessor.mjs +5 -0
  119. package/browser/vault/auth-providers.registry.mjs +140 -0
  120. package/browser/vault/auth-providers.types.mjs +47 -0
  121. package/browser/vault/auth-providers.vault.mjs +206 -0
  122. package/browser/vault/credential-cache.mjs +187 -0
  123. package/browser/vault/credential-helpers.mjs +15 -0
  124. package/browser/vault/credential-loaders/eager-loader.mjs +91 -0
  125. package/browser/vault/credential-loaders/lazy-loader.mjs +120 -0
  126. package/browser/vault/index.mjs +35 -0
  127. package/cimd/cimd.types.d.ts +1 -1
  128. package/cimd/cimd.types.d.ts.map +1 -1
  129. package/common/jwt.types.d.ts +1 -1
  130. package/common/jwt.types.d.ts.map +1 -1
  131. package/common/session.types.d.ts +9 -1
  132. package/common/session.types.d.ts.map +1 -1
  133. package/consent/consent.types.d.ts +1 -1
  134. package/consent/consent.types.d.ts.map +1 -1
  135. package/detection/auth-provider-detection.d.ts +1 -1
  136. package/detection/auth-provider-detection.d.ts.map +1 -1
  137. package/errors/auth-internal.errors.d.ts +6 -0
  138. package/errors/auth-internal.errors.d.ts.map +1 -1
  139. package/errors/index.d.ts +1 -1
  140. package/errors/index.d.ts.map +1 -1
  141. package/esm/index.mjs +2 -2
  142. package/esm/package.json +21 -5
  143. package/index.d.ts +5 -1
  144. package/index.d.ts.map +1 -1
  145. package/index.js +2 -2
  146. package/options/app-auth.schema.d.ts +19 -1
  147. package/options/app-auth.schema.d.ts.map +1 -1
  148. package/options/orchestrated.schema.d.ts +37 -1
  149. package/options/orchestrated.schema.d.ts.map +1 -1
  150. package/options/public.schema.d.ts +1 -1
  151. package/options/public.schema.d.ts.map +1 -1
  152. package/options/schema.d.ts +19 -1
  153. package/options/schema.d.ts.map +1 -1
  154. package/options/shared.schemas.d.ts +10 -14
  155. package/options/shared.schemas.d.ts.map +1 -1
  156. package/options/static.schema.d.ts +1 -1
  157. package/options/static.schema.d.ts.map +1 -1
  158. package/options/transparent.schema.d.ts +1 -1
  159. package/options/transparent.schema.d.ts.map +1 -1
  160. package/package.json +21 -5
  161. package/people/index.d.ts +3 -0
  162. package/people/index.d.ts.map +1 -0
  163. package/people/loopback-token.auth.d.ts +75 -0
  164. package/people/loopback-token.auth.d.ts.map +1 -0
  165. package/people/people-auth.options.d.ts +36 -0
  166. package/people/people-auth.options.d.ts.map +1 -0
  167. package/session/authorization-vault.d.ts +1 -1
  168. package/session/authorization-vault.d.ts.map +1 -1
  169. package/session/encrypted-authorization-vault.d.ts +1 -1
  170. package/session/encrypted-authorization-vault.d.ts.map +1 -1
  171. package/session/secure-store.factory.d.ts.map +1 -1
  172. package/shared-server/index.d.ts +3 -0
  173. package/shared-server/index.d.ts.map +1 -0
  174. package/shared-server/shared-server-identity.d.ts +26 -0
  175. package/shared-server/shared-server-identity.d.ts.map +1 -0
@@ -0,0 +1,231 @@
1
+ // libs/auth/src/cimd/cimd-redis.cache.ts
2
+ import { RedisStorageAdapter } from "@frontmcp/utils";
3
+ import { sha256Hex } from "@frontmcp/utils";
4
+ import { extractCacheHeaders, parseCacheHeaders } from "./cimd.cache.mjs";
5
+ var RedisCimdCache = class {
6
+ redis;
7
+ keyPrefix;
8
+ config;
9
+ constructor(config) {
10
+ if (!config.redis) {
11
+ throw new Error("Redis configuration is required for RedisCimdCache");
12
+ }
13
+ this.config = {
14
+ defaultTtlMs: config.defaultTtlMs ?? 36e5,
15
+ maxTtlMs: config.maxTtlMs ?? 864e5,
16
+ minTtlMs: config.minTtlMs ?? 6e4
17
+ };
18
+ this.keyPrefix = config.redis.keyPrefix ?? "cimd:";
19
+ const redisOptions = {};
20
+ if (config.redis.url) {
21
+ redisOptions.url = config.redis.url;
22
+ } else if (config.redis.host) {
23
+ redisOptions.config = {
24
+ host: config.redis.host,
25
+ port: config.redis.port,
26
+ password: config.redis.password,
27
+ db: config.redis.db,
28
+ tls: config.redis.tls
29
+ };
30
+ }
31
+ this.redis = new RedisStorageAdapter(redisOptions);
32
+ }
33
+ /**
34
+ * Connect to Redis.
35
+ * Must be called before using any cache operations.
36
+ */
37
+ async connect() {
38
+ await this.redis.connect();
39
+ }
40
+ /**
41
+ * Generate a Redis key for a client ID.
42
+ * Uses SHA-256 hash to handle URLs with special characters.
43
+ */
44
+ cacheKey(clientId) {
45
+ const hash = sha256Hex(clientId);
46
+ return `${this.keyPrefix}${hash}`;
47
+ }
48
+ /**
49
+ * Get a cached entry by client_id.
50
+ *
51
+ * @param clientId - The client_id URL
52
+ * @returns The cached entry if valid, or undefined
53
+ */
54
+ async get(clientId) {
55
+ const key = this.cacheKey(clientId);
56
+ const data = await this.redis.get(key);
57
+ if (!data) {
58
+ return void 0;
59
+ }
60
+ try {
61
+ const entry = JSON.parse(data);
62
+ if (entry.expiresAt < Date.now()) {
63
+ return void 0;
64
+ }
65
+ return entry;
66
+ } catch {
67
+ await this.redis.delete(key);
68
+ return void 0;
69
+ }
70
+ }
71
+ /**
72
+ * Get a stale entry for conditional revalidation.
73
+ *
74
+ * @param clientId - The client_id URL
75
+ * @returns The stale entry (even if expired), or undefined if not cached
76
+ */
77
+ async getStale(clientId) {
78
+ const key = this.cacheKey(clientId);
79
+ const data = await this.redis.get(key);
80
+ if (!data) {
81
+ return void 0;
82
+ }
83
+ try {
84
+ return JSON.parse(data);
85
+ } catch {
86
+ return void 0;
87
+ }
88
+ }
89
+ /**
90
+ * Store a document in the cache.
91
+ *
92
+ * @param clientId - The client_id URL
93
+ * @param document - The metadata document
94
+ * @param headers - HTTP response headers
95
+ */
96
+ async set(clientId, document, headers) {
97
+ const cacheHeaders = extractCacheHeaders(headers);
98
+ const { ttlMs, etag, lastModified } = parseCacheHeaders(cacheHeaders, this.config);
99
+ const now = Date.now();
100
+ const entry = {
101
+ document,
102
+ expiresAt: now + ttlMs,
103
+ etag,
104
+ lastModified,
105
+ cachedAt: now
106
+ };
107
+ const key = this.cacheKey(clientId);
108
+ const value = JSON.stringify(entry);
109
+ const redisTtlSeconds = Math.ceil((ttlMs + this.config.maxTtlMs * 2) / 1e3);
110
+ await this.redis.set(key, value, { ttlSeconds: redisTtlSeconds });
111
+ }
112
+ /**
113
+ * Update an existing cache entry (after 304 Not Modified).
114
+ *
115
+ * @param clientId - The client_id URL
116
+ * @param headers - New HTTP headers with updated cache directives
117
+ */
118
+ async revalidate(clientId, headers) {
119
+ const key = this.cacheKey(clientId);
120
+ const data = await this.redis.get(key);
121
+ if (!data) {
122
+ return false;
123
+ }
124
+ try {
125
+ const existing = JSON.parse(data);
126
+ const cacheHeaders = extractCacheHeaders(headers);
127
+ const { ttlMs, etag, lastModified } = parseCacheHeaders(cacheHeaders, this.config);
128
+ existing.expiresAt = Date.now() + ttlMs;
129
+ if (etag) existing.etag = etag;
130
+ if (lastModified) existing.lastModified = lastModified;
131
+ const value = JSON.stringify(existing);
132
+ const redisTtlSeconds = Math.ceil((ttlMs + this.config.maxTtlMs * 2) / 1e3);
133
+ await this.redis.set(key, value, { ttlSeconds: redisTtlSeconds });
134
+ return true;
135
+ } catch {
136
+ return false;
137
+ }
138
+ }
139
+ /**
140
+ * Delete a cache entry.
141
+ *
142
+ * @param clientId - The client_id URL
143
+ * @returns true if an entry was deleted
144
+ */
145
+ async delete(clientId) {
146
+ const key = this.cacheKey(clientId);
147
+ return this.redis.delete(key);
148
+ }
149
+ /**
150
+ * Get conditional request headers for a cached entry.
151
+ *
152
+ * @param clientId - The client_id URL
153
+ * @returns Headers for conditional request, or undefined if not cached
154
+ */
155
+ async getConditionalHeaders(clientId) {
156
+ const key = this.cacheKey(clientId);
157
+ const data = await this.redis.get(key);
158
+ if (!data) {
159
+ return void 0;
160
+ }
161
+ try {
162
+ const entry = JSON.parse(data);
163
+ const headers = {};
164
+ if (entry.etag) {
165
+ headers["If-None-Match"] = entry.etag;
166
+ }
167
+ if (entry.lastModified) {
168
+ headers["If-Modified-Since"] = entry.lastModified;
169
+ }
170
+ return Object.keys(headers).length > 0 ? headers : void 0;
171
+ } catch {
172
+ return void 0;
173
+ }
174
+ }
175
+ /**
176
+ * Clear all cached entries.
177
+ * Uses Redis SCAN to find and delete all keys with our prefix.
178
+ */
179
+ async clear() {
180
+ const keys = await this.redis.keys(`${this.keyPrefix}*`);
181
+ if (keys.length > 0) {
182
+ await this.redis.mdelete(keys);
183
+ }
184
+ }
185
+ /**
186
+ * Get the number of cached entries.
187
+ * Uses Redis SCAN to count keys with our prefix.
188
+ */
189
+ async size() {
190
+ const keys = await this.redis.keys(`${this.keyPrefix}*`);
191
+ return keys.length;
192
+ }
193
+ /**
194
+ * Remove expired entries.
195
+ *
196
+ * Note: Redis handles expiration automatically via TTL.
197
+ * This method is primarily for explicit cleanup of entries that are
198
+ * well past their HTTP cache expiration but still within Redis TTL.
199
+ *
200
+ * @returns Number of entries removed
201
+ */
202
+ async cleanup() {
203
+ const now = Date.now();
204
+ let removed = 0;
205
+ const keys = await this.redis.keys(`${this.keyPrefix}*`);
206
+ for (const key of keys) {
207
+ const data = await this.redis.get(key);
208
+ if (!data) continue;
209
+ try {
210
+ const entry = JSON.parse(data);
211
+ if (entry.expiresAt + this.config.maxTtlMs * 2 < now) {
212
+ await this.redis.delete(key);
213
+ removed++;
214
+ }
215
+ } catch {
216
+ await this.redis.delete(key);
217
+ removed++;
218
+ }
219
+ }
220
+ return removed;
221
+ }
222
+ /**
223
+ * Close the Redis connection.
224
+ */
225
+ async close() {
226
+ await this.redis.disconnect();
227
+ }
228
+ };
229
+ export {
230
+ RedisCimdCache
231
+ };
@@ -0,0 +1,215 @@
1
+ // libs/auth/src/cimd/cimd.cache.ts
2
+ function extractCacheHeaders(headers) {
3
+ return {
4
+ "cache-control": headers.get("cache-control") ?? void 0,
5
+ expires: headers.get("expires") ?? void 0,
6
+ etag: headers.get("etag") ?? void 0,
7
+ "last-modified": headers.get("last-modified") ?? void 0,
8
+ age: headers.get("age") ?? void 0
9
+ };
10
+ }
11
+ function parseCacheHeaders(headers, config) {
12
+ let ttlMs = config.defaultTtlMs;
13
+ if (headers["cache-control"]) {
14
+ const cacheControl = parseCacheControlHeader(headers["cache-control"]);
15
+ if (cacheControl["no-store"] || cacheControl["no-cache"]) {
16
+ ttlMs = config.minTtlMs;
17
+ } else if (typeof cacheControl["max-age"] === "number") {
18
+ let maxAgeSecs = cacheControl["max-age"];
19
+ if (headers.age) {
20
+ const ageSeconds = parseInt(headers.age, 10);
21
+ if (!isNaN(ageSeconds)) {
22
+ maxAgeSecs = Math.max(0, maxAgeSecs - ageSeconds);
23
+ }
24
+ }
25
+ ttlMs = maxAgeSecs * 1e3;
26
+ }
27
+ if (typeof cacheControl["s-maxage"] === "number") {
28
+ let sMaxAgeSecs = cacheControl["s-maxage"];
29
+ if (headers.age) {
30
+ const ageSeconds = parseInt(headers.age, 10);
31
+ if (!isNaN(ageSeconds)) {
32
+ sMaxAgeSecs = Math.max(0, sMaxAgeSecs - ageSeconds);
33
+ }
34
+ }
35
+ ttlMs = sMaxAgeSecs * 1e3;
36
+ }
37
+ } else if (headers.expires) {
38
+ const expiresDate = new Date(headers.expires);
39
+ if (!isNaN(expiresDate.getTime())) {
40
+ ttlMs = Math.max(0, expiresDate.getTime() - Date.now());
41
+ }
42
+ }
43
+ ttlMs = Math.max(config.minTtlMs, Math.min(config.maxTtlMs, ttlMs));
44
+ return {
45
+ ttlMs,
46
+ etag: headers.etag,
47
+ lastModified: headers["last-modified"]
48
+ };
49
+ }
50
+ function parseCacheControlHeader(value) {
51
+ const result = {};
52
+ const directives = value.toLowerCase().split(",").map((d) => d.trim());
53
+ for (const directive of directives) {
54
+ const [key, val] = directive.split("=").map((s) => s.trim());
55
+ if (val !== void 0) {
56
+ const numVal = parseInt(val, 10);
57
+ if (!isNaN(numVal)) {
58
+ result[key] = numVal;
59
+ }
60
+ } else {
61
+ result[key] = true;
62
+ }
63
+ }
64
+ return result;
65
+ }
66
+ var InMemoryCimdCache = class {
67
+ cache = /* @__PURE__ */ new Map();
68
+ config;
69
+ constructor(config) {
70
+ this.config = {
71
+ defaultTtlMs: config?.defaultTtlMs ?? 36e5,
72
+ maxTtlMs: config?.maxTtlMs ?? 864e5,
73
+ minTtlMs: config?.minTtlMs ?? 6e4
74
+ };
75
+ }
76
+ /**
77
+ * Get a cached entry by client_id.
78
+ *
79
+ * @param clientId - The client_id URL
80
+ * @returns The cached entry if valid, or undefined
81
+ */
82
+ async get(clientId) {
83
+ const entry = this.cache.get(clientId);
84
+ if (!entry) {
85
+ return void 0;
86
+ }
87
+ if (entry.expiresAt < Date.now()) {
88
+ return void 0;
89
+ }
90
+ return entry;
91
+ }
92
+ /**
93
+ * Get a stale entry for conditional revalidation.
94
+ *
95
+ * @param clientId - The client_id URL
96
+ * @returns The stale entry (even if expired), or undefined if not cached
97
+ */
98
+ async getStale(clientId) {
99
+ return this.cache.get(clientId);
100
+ }
101
+ /**
102
+ * Store a document in the cache.
103
+ *
104
+ * @param clientId - The client_id URL
105
+ * @param document - The metadata document
106
+ * @param headers - HTTP response headers
107
+ */
108
+ async set(clientId, document, headers) {
109
+ const cacheHeaders = extractCacheHeaders(headers);
110
+ const { ttlMs, etag, lastModified } = parseCacheHeaders(cacheHeaders, this.config);
111
+ const now = Date.now();
112
+ const entry = {
113
+ document,
114
+ expiresAt: now + ttlMs,
115
+ etag,
116
+ lastModified,
117
+ cachedAt: now
118
+ };
119
+ this.cache.set(clientId, entry);
120
+ }
121
+ /**
122
+ * Update an existing cache entry (after 304 Not Modified).
123
+ *
124
+ * @param clientId - The client_id URL
125
+ * @param headers - New HTTP headers with updated cache directives
126
+ */
127
+ async revalidate(clientId, headers) {
128
+ const existing = this.cache.get(clientId);
129
+ if (!existing) {
130
+ return false;
131
+ }
132
+ const cacheHeaders = extractCacheHeaders(headers);
133
+ const { ttlMs, etag, lastModified } = parseCacheHeaders(cacheHeaders, this.config);
134
+ existing.expiresAt = Date.now() + ttlMs;
135
+ if (etag) existing.etag = etag;
136
+ if (lastModified) existing.lastModified = lastModified;
137
+ return true;
138
+ }
139
+ /**
140
+ * Delete a cache entry.
141
+ *
142
+ * @param clientId - The client_id URL
143
+ * @returns true if an entry was deleted
144
+ */
145
+ async delete(clientId) {
146
+ return this.cache.delete(clientId);
147
+ }
148
+ /**
149
+ * Get conditional request headers for a cached entry.
150
+ *
151
+ * @param clientId - The client_id URL
152
+ * @returns Headers for conditional request, or undefined if not cached
153
+ */
154
+ async getConditionalHeaders(clientId) {
155
+ const entry = this.cache.get(clientId);
156
+ if (!entry) {
157
+ return void 0;
158
+ }
159
+ const headers = {};
160
+ if (entry.etag) {
161
+ headers["If-None-Match"] = entry.etag;
162
+ }
163
+ if (entry.lastModified) {
164
+ headers["If-Modified-Since"] = entry.lastModified;
165
+ }
166
+ return Object.keys(headers).length > 0 ? headers : void 0;
167
+ }
168
+ /**
169
+ * Clear all cached entries.
170
+ */
171
+ async clear() {
172
+ this.cache.clear();
173
+ }
174
+ /**
175
+ * Get the number of cached entries.
176
+ */
177
+ async size() {
178
+ return this.cache.size;
179
+ }
180
+ /**
181
+ * Remove expired entries.
182
+ *
183
+ * @returns Number of entries removed
184
+ */
185
+ async cleanup() {
186
+ const now = Date.now();
187
+ let removed = 0;
188
+ for (const [clientId, entry] of this.cache) {
189
+ if (entry.expiresAt + this.config.maxTtlMs * 2 < now) {
190
+ this.cache.delete(clientId);
191
+ removed++;
192
+ }
193
+ }
194
+ return removed;
195
+ }
196
+ };
197
+ async function createCimdCache(config) {
198
+ const cacheType = config?.type ?? "memory";
199
+ if (cacheType === "redis") {
200
+ if (!config?.redis) {
201
+ throw new Error('Redis configuration is required when cache type is "redis"');
202
+ }
203
+ const { RedisCimdCache } = await import("./cimd-redis.cache.mjs");
204
+ const cache = new RedisCimdCache(config);
205
+ await cache.connect();
206
+ return cache;
207
+ }
208
+ return new InMemoryCimdCache(config);
209
+ }
210
+ export {
211
+ InMemoryCimdCache,
212
+ createCimdCache,
213
+ extractCacheHeaders,
214
+ parseCacheHeaders
215
+ };
@@ -0,0 +1,142 @@
1
+ // libs/auth/src/cimd/cimd.errors.ts
2
+ var CimdError = class extends Error {
3
+ /**
4
+ * Error code for machine-readable identification.
5
+ */
6
+ code;
7
+ /**
8
+ * HTTP status code to return when this error occurs.
9
+ */
10
+ statusCode;
11
+ /**
12
+ * The client_id URL that caused the error.
13
+ */
14
+ clientIdUrl;
15
+ constructor(message, code, statusCode, clientIdUrl) {
16
+ super(message);
17
+ this.name = this.constructor.name;
18
+ this.code = code;
19
+ this.statusCode = statusCode;
20
+ this.clientIdUrl = clientIdUrl;
21
+ if (Error.captureStackTrace) {
22
+ Error.captureStackTrace(this, this.constructor);
23
+ }
24
+ }
25
+ /**
26
+ * Get a message safe to return to clients.
27
+ * Override in subclasses to provide user-friendly messages.
28
+ */
29
+ getPublicMessage() {
30
+ return this.message;
31
+ }
32
+ };
33
+ var InvalidClientIdUrlError = class extends CimdError {
34
+ reason;
35
+ constructor(clientIdUrl, reason) {
36
+ super(`Invalid CIMD client_id URL: ${reason}`, "INVALID_CLIENT_ID_URL", 400, clientIdUrl);
37
+ this.reason = reason;
38
+ }
39
+ getPublicMessage() {
40
+ return `Invalid client_id URL: ${this.reason}`;
41
+ }
42
+ };
43
+ var CimdFetchError = class extends CimdError {
44
+ httpStatus;
45
+ originalError;
46
+ constructor(clientIdUrl, message, options) {
47
+ super(`Failed to fetch CIMD document from ${clientIdUrl}: ${message}`, "CIMD_FETCH_ERROR", 502, clientIdUrl);
48
+ this.httpStatus = options?.httpStatus;
49
+ this.originalError = options?.originalError;
50
+ }
51
+ getPublicMessage() {
52
+ if (this.httpStatus) {
53
+ return `Failed to fetch client metadata: HTTP ${this.httpStatus}`;
54
+ }
55
+ return "Failed to fetch client metadata document";
56
+ }
57
+ };
58
+ var CimdValidationError = class extends CimdError {
59
+ validationErrors;
60
+ constructor(clientIdUrl, errors) {
61
+ super(`CIMD document validation failed: ${errors.join("; ")}`, "CIMD_VALIDATION_ERROR", 400, clientIdUrl);
62
+ this.validationErrors = errors;
63
+ }
64
+ getPublicMessage() {
65
+ return `Client metadata document validation failed: ${this.validationErrors.join("; ")}`;
66
+ }
67
+ };
68
+ var CimdClientIdMismatchError = class extends CimdError {
69
+ documentClientId;
70
+ constructor(urlClientId, documentClientId) {
71
+ super(
72
+ `CIMD client_id mismatch: URL is "${urlClientId}" but document contains "${documentClientId}"`,
73
+ "CIMD_CLIENT_ID_MISMATCH",
74
+ 400,
75
+ urlClientId
76
+ );
77
+ this.documentClientId = documentClientId;
78
+ }
79
+ getPublicMessage() {
80
+ return "Client ID in metadata document does not match the request";
81
+ }
82
+ };
83
+ var CimdSecurityError = class extends CimdError {
84
+ securityReason;
85
+ constructor(clientIdUrl, reason) {
86
+ super(`CIMD security check failed for ${clientIdUrl}: ${reason}`, "CIMD_SECURITY_ERROR", 403, clientIdUrl);
87
+ this.securityReason = reason;
88
+ }
89
+ getPublicMessage() {
90
+ return "Client ID URL is not allowed by security policy";
91
+ }
92
+ };
93
+ var RedirectUriMismatchError = class extends CimdError {
94
+ requestedRedirectUri;
95
+ allowedRedirectUris;
96
+ constructor(clientIdUrl, requestedRedirectUri, allowedRedirectUris) {
97
+ super(
98
+ `Redirect URI "${requestedRedirectUri}" is not registered for client "${clientIdUrl}"`,
99
+ "REDIRECT_URI_MISMATCH",
100
+ 400,
101
+ clientIdUrl
102
+ );
103
+ this.requestedRedirectUri = requestedRedirectUri;
104
+ this.allowedRedirectUris = allowedRedirectUris;
105
+ }
106
+ getPublicMessage() {
107
+ return "The redirect_uri is not registered for this client";
108
+ }
109
+ };
110
+ var CimdResponseTooLargeError = class extends CimdError {
111
+ maxBytes;
112
+ actualBytes;
113
+ constructor(clientIdUrl, maxBytes, actualBytes) {
114
+ super(
115
+ `CIMD response from ${clientIdUrl} exceeds maximum size of ${maxBytes} bytes${actualBytes ? ` (received ${actualBytes} bytes)` : ""}`,
116
+ "CIMD_RESPONSE_TOO_LARGE",
117
+ 502,
118
+ clientIdUrl
119
+ );
120
+ this.maxBytes = maxBytes;
121
+ this.actualBytes = actualBytes;
122
+ }
123
+ getPublicMessage() {
124
+ return "Client metadata document is too large";
125
+ }
126
+ };
127
+ var CimdDisabledError = class extends CimdError {
128
+ constructor() {
129
+ super("CIMD (Client ID Metadata Documents) is disabled on this server", "CIMD_DISABLED", 400);
130
+ }
131
+ };
132
+ export {
133
+ CimdClientIdMismatchError,
134
+ CimdDisabledError,
135
+ CimdError,
136
+ CimdFetchError,
137
+ CimdResponseTooLargeError,
138
+ CimdSecurityError,
139
+ CimdValidationError,
140
+ InvalidClientIdUrlError,
141
+ RedirectUriMismatchError
142
+ };
@@ -0,0 +1,15 @@
1
+ // libs/auth/src/cimd/cimd.logger.ts
2
+ var noopLogger = {
3
+ child: () => noopLogger,
4
+ debug: () => {
5
+ },
6
+ info: () => {
7
+ },
8
+ warn: () => {
9
+ },
10
+ error: () => {
11
+ }
12
+ };
13
+ export {
14
+ noopLogger
15
+ };