@hraness/suite-accounts 0.9.20

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 (73) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +496 -0
  3. package/compiler-foundation.css +1 -0
  4. package/dist/auth-client.js +16 -0
  5. package/dist/auth-proxy.js +757 -0
  6. package/dist/bearer-verifier.js +1119 -0
  7. package/dist/browser-session.js +208 -0
  8. package/dist/client-configuration.js +470 -0
  9. package/dist/convex-browser-auth-browser.js +923 -0
  10. package/dist/convex-browser-auth-server.js +846 -0
  11. package/dist/convex-browser-auth.js +597 -0
  12. package/dist/entitlements.js +502 -0
  13. package/dist/identity/functions.js +44 -0
  14. package/dist/identity/index.js +1716 -0
  15. package/dist/identity/return-targets.js +33 -0
  16. package/dist/index.js +1133 -0
  17. package/dist/oidc-device-code.js +253 -0
  18. package/dist/oidc-rp.js +2259 -0
  19. package/dist/oidc-session-policy.js +5 -0
  20. package/dist/oidc-surface-server.js +2332 -0
  21. package/dist/profile-form.js +1934 -0
  22. package/dist/profile.js +17 -0
  23. package/dist/public-config.js +462 -0
  24. package/dist/react.js +137 -0
  25. package/dist/receipt-verifier.js +619 -0
  26. package/dist/registry.js +327 -0
  27. package/dist/stylex-manifest.json +1 -0
  28. package/dist/stylex.css +435 -0
  29. package/dist/urls.js +391 -0
  30. package/package.json +263 -0
  31. package/portfolio-inventory.json +37 -0
  32. package/src/auth-client.ts +37 -0
  33. package/src/auth-proxy.ts +473 -0
  34. package/src/bearer-verifier.ts +478 -0
  35. package/src/browser-session.ts +260 -0
  36. package/src/client-configuration.ts +172 -0
  37. package/src/convex-browser-auth-browser.ts +238 -0
  38. package/src/convex-browser-auth-server.ts +492 -0
  39. package/src/convex-browser-auth.ts +208 -0
  40. package/src/convex-url.ts +74 -0
  41. package/src/entitlements.ts +317 -0
  42. package/src/identity/catalog.ts +227 -0
  43. package/src/identity/consumers.ts +53 -0
  44. package/src/identity/functions.ts +70 -0
  45. package/src/identity/identifiers.ts +82 -0
  46. package/src/identity/index.ts +10 -0
  47. package/src/identity/links.ts +280 -0
  48. package/src/identity/membership.ts +156 -0
  49. package/src/identity/principals.ts +301 -0
  50. package/src/identity/profiles-v2.ts +330 -0
  51. package/src/identity/profiles.ts +524 -0
  52. package/src/identity/return-targets.ts +20 -0
  53. package/src/identity/usernames.ts +101 -0
  54. package/src/identity/views.ts +289 -0
  55. package/src/immutable.ts +25 -0
  56. package/src/index.ts +7 -0
  57. package/src/oidc-continuation.ts +59 -0
  58. package/src/oidc-device-code.ts +401 -0
  59. package/src/oidc-rp.ts +2217 -0
  60. package/src/oidc-session-policy.ts +6 -0
  61. package/src/oidc-surface-server.ts +186 -0
  62. package/src/profile-form.css +1 -0
  63. package/src/profile-form.stylex.ts +226 -0
  64. package/src/profile-form.tsx +314 -0
  65. package/src/profile.ts +33 -0
  66. package/src/public-config.ts +294 -0
  67. package/src/public-profile-form-result.ts +82 -0
  68. package/src/public-profile-form.tsx +319 -0
  69. package/src/public-profile-links.ts +100 -0
  70. package/src/react.tsx +196 -0
  71. package/src/receipt-verifier.ts +273 -0
  72. package/src/registry.ts +600 -0
  73. package/src/urls.ts +234 -0
@@ -0,0 +1,2332 @@
1
+ // src/identity/consumers.ts
2
+ import { err, ok } from "@hraness/result";
3
+
4
+ // src/immutable.ts
5
+ function deepFreeze(value) {
6
+ const visited = new WeakSet;
7
+ function freezeOwned(current) {
8
+ if (current === null || typeof current !== "object")
9
+ return;
10
+ if (visited.has(current))
11
+ return;
12
+ visited.add(current);
13
+ for (const key of Reflect.ownKeys(current)) {
14
+ const descriptor = Object.getOwnPropertyDescriptor(current, key);
15
+ if (descriptor !== undefined && "value" in descriptor) {
16
+ freezeOwned(descriptor.value);
17
+ }
18
+ }
19
+ Object.freeze(current);
20
+ }
21
+ freezeOwned(value);
22
+ return value;
23
+ }
24
+
25
+ // src/identity/consumers.ts
26
+ var SUITE_CONSUMER_IDS = deepFreeze([
27
+ "accounts",
28
+ "act60",
29
+ "elders",
30
+ "soundfish",
31
+ "oh-computer",
32
+ "draw-money",
33
+ "oprte",
34
+ "sponge"
35
+ ]);
36
+ var LEGACY_SUITE_CONSUMER_IDS = deepFreeze([
37
+ "kitchen"
38
+ ]);
39
+ function parseSuiteConsumerId(value) {
40
+ switch (value) {
41
+ case "accounts":
42
+ case "act60":
43
+ case "elders":
44
+ case "soundfish":
45
+ case "oh-computer":
46
+ case "draw-money":
47
+ case "oprte":
48
+ case "sponge":
49
+ return ok(value);
50
+ case "kitchen":
51
+ return ok("oprte");
52
+ default:
53
+ return err("invalid-consumer");
54
+ }
55
+ }
56
+
57
+ // src/registry.ts
58
+ var SUITE_ACCOUNTS_REMOTE_ENVIRONMENTS = deepFreeze([
59
+ "production"
60
+ ]);
61
+ var accountsCookies = deepFreeze({
62
+ chunked: ["account_data", "session_data"],
63
+ names: [
64
+ "account_data",
65
+ "convex_jwt",
66
+ "dont_remember",
67
+ "session_data",
68
+ "session_token"
69
+ ]
70
+ });
71
+ var consumerCookies = deepFreeze({
72
+ chunked: ["session_data"],
73
+ names: ["dont_remember", "session_data", "session_token"]
74
+ });
75
+ var SUITE_ACCOUNTS_DEPLOYMENTS = deepFreeze({
76
+ production: {
77
+ accountsOrigin: "https://account.hraness.com",
78
+ convexSiteUrl: "https://qualified-marmot-22.convex.site",
79
+ convexUrl: "https://qualified-marmot-22.convex.cloud"
80
+ }
81
+ });
82
+ function unsupported(siteUrl) {
83
+ return { billingReturn: { kind: "unsupported" }, siteUrl };
84
+ }
85
+ function oidcSite(id, displayName, productionSiteUrl) {
86
+ return {
87
+ auth: { basePath: "/api/suite-auth", kind: "oidc-rp" },
88
+ displayName,
89
+ environments: {
90
+ production: unsupported(productionSiteUrl)
91
+ },
92
+ id
93
+ };
94
+ }
95
+ var SUITE_ACCOUNTS_REGISTERED_CONSUMER_IDS = deepFreeze([
96
+ "accounts",
97
+ "act60",
98
+ "soundfish",
99
+ "oh-computer",
100
+ "draw-money",
101
+ "sponge"
102
+ ]);
103
+ var SUITE_ACCOUNTS_CONSUMERS = deepFreeze({
104
+ accounts: {
105
+ auth: {
106
+ basePath: "/api/auth",
107
+ cookies: accountsCookies,
108
+ kind: "authority"
109
+ },
110
+ displayName: "Accounts",
111
+ environments: {
112
+ production: {
113
+ billingReturn: { kind: "supported", path: "/account" },
114
+ siteUrl: "https://account.hraness.com"
115
+ }
116
+ },
117
+ id: "accounts"
118
+ },
119
+ act60: oidcSite("act60", "ACT60", "https://act60.me"),
120
+ soundfish: {
121
+ auth: { basePath: "/api/suite-auth", kind: "oidc-rp" },
122
+ displayName: "Soundfish",
123
+ environments: {
124
+ production: unsupported("https://sound.fish")
125
+ },
126
+ id: "soundfish"
127
+ },
128
+ "oh-computer": oidcSite("oh-computer", "Oh", "https://oh.computer"),
129
+ "draw-money": {
130
+ auth: {
131
+ basePath: "/api/auth",
132
+ cookies: consumerCookies,
133
+ kind: "proxy"
134
+ },
135
+ displayName: "Draw Money",
136
+ environments: {
137
+ production: unsupported("https://draw.money")
138
+ },
139
+ id: "draw-money"
140
+ },
141
+ sponge: oidcSite("sponge", "Sponge", "https://spongesearch.com")
142
+ });
143
+ var SUITE_ACCOUNTS_CURRENT_ORIGIN_OVERRIDES = deepFreeze({
144
+ sponge: {
145
+ production: unsupported("https://sponge.computer")
146
+ }
147
+ });
148
+ var SUITE_ACCOUNTS_CURRENT_CONSUMER_IDS = deepFreeze([
149
+ "accounts",
150
+ "act60",
151
+ "soundfish",
152
+ "oh-computer",
153
+ "sponge",
154
+ "peopleblade",
155
+ "aicharts",
156
+ "hraness",
157
+ "soulscrape",
158
+ "platonik",
159
+ "ghostget",
160
+ "alt",
161
+ "algal"
162
+ ]);
163
+ function currentOidcSite(id, displayName, productionSiteUrl) {
164
+ return {
165
+ auth: { basePath: "/api/suite-auth", kind: "oidc-rp" },
166
+ displayName,
167
+ environments: {
168
+ production: unsupported(productionSiteUrl)
169
+ },
170
+ id
171
+ };
172
+ }
173
+ var SUITE_ACCOUNTS_CURRENT_CONSUMERS = deepFreeze({
174
+ accounts: SUITE_ACCOUNTS_CONSUMERS.accounts,
175
+ act60: SUITE_ACCOUNTS_CONSUMERS.act60,
176
+ soundfish: SUITE_ACCOUNTS_CONSUMERS.soundfish,
177
+ "oh-computer": SUITE_ACCOUNTS_CONSUMERS["oh-computer"],
178
+ sponge: currentOidcSite("sponge", "Sponge", SUITE_ACCOUNTS_CURRENT_ORIGIN_OVERRIDES.sponge.production.siteUrl),
179
+ peopleblade: currentOidcSite("peopleblade", "PeopleBlade", "https://peopleblade.com"),
180
+ aicharts: currentOidcSite("aicharts", "AI Charts", "https://aicharts.io"),
181
+ hraness: currentOidcSite("hraness", "Hraness", "https://hraness.com"),
182
+ soulscrape: currentOidcSite("soulscrape", "Soulscrape", "https://soulscrape.com"),
183
+ platonik: currentOidcSite("platonik", "Platonik", "https://platonik.space"),
184
+ ghostget: currentOidcSite("ghostget", "Ghostget", "https://ghostget.com"),
185
+ alt: currentOidcSite("alt", "Alt", "https://alt.dog"),
186
+ algal: currentOidcSite("algal", "Algal", "https://algal.cloud")
187
+ });
188
+ var SUITE_ACCOUNTS_ACTIVE_CONSUMER_IDS = deepFreeze([
189
+ "accounts",
190
+ "act60",
191
+ "soundfish",
192
+ "oh-computer",
193
+ "sponge"
194
+ ]);
195
+ var SUITE_EMAIL_OTP_REQUIRED_OIDC_CONSUMER_IDS = deepFreeze(SUITE_ACCOUNTS_REGISTERED_CONSUMER_IDS.filter((consumer) => SUITE_ACCOUNTS_CONSUMERS[consumer].auth.kind === "oidc-rp"));
196
+ function suiteAccountsConsumerRequiresEmailOtp(consumer) {
197
+ return SUITE_EMAIL_OTP_REQUIRED_OIDC_CONSUMER_IDS.includes(consumer);
198
+ }
199
+ var SUITE_ACCOUNTS_LINKED_OIDC_CONSUMER_IDS = deepFreeze([
200
+ "soundfish"
201
+ ]);
202
+ var SUITE_ACCOUNTS_CURRENT_EMAIL_OTP_REQUIRED_OIDC_CONSUMER_IDS = deepFreeze(SUITE_ACCOUNTS_CURRENT_CONSUMER_IDS.filter((consumer) => SUITE_ACCOUNTS_CURRENT_CONSUMERS[consumer].auth.kind === "oidc-rp"));
203
+ var SUITE_ACCOUNTS_CURRENT_LINKED_OIDC_CONSUMER_IDS = deepFreeze([
204
+ "soundfish",
205
+ "peopleblade",
206
+ "ghostget"
207
+ ]);
208
+ var SUITE_ACCOUNTS_CURRENT_BROWSER_DEVICE_CODE_CONSUMER_IDS = deepFreeze([
209
+ "act60",
210
+ "soundfish",
211
+ "oh-computer",
212
+ "sponge",
213
+ "peopleblade",
214
+ "aicharts",
215
+ "hraness",
216
+ "soulscrape",
217
+ "platonik",
218
+ "ghostget",
219
+ "alt"
220
+ ]);
221
+ var SUITE_ACCOUNTS_CURRENT_DEVICE_CLIENT_IDS = deepFreeze([
222
+ "algal-cli"
223
+ ]);
224
+ var SUITE_ACCOUNTS_CURRENT_DEVICE_CLIENTS = deepFreeze({
225
+ "algal-cli": {
226
+ consumer: "algal",
227
+ displayName: "Algal CLI",
228
+ environments: ["production"],
229
+ id: "algal-cli"
230
+ }
231
+ });
232
+ function isSuiteAccountsConsumerId(value) {
233
+ return typeof value === "string" && SUITE_CONSUMER_IDS.includes(value);
234
+ }
235
+ function isSuiteAccountsRegisteredConsumerId(value) {
236
+ return typeof value === "string" && SUITE_ACCOUNTS_REGISTERED_CONSUMER_IDS.includes(value);
237
+ }
238
+ function isSuiteAccountsOidcConsumerId(value) {
239
+ return isSuiteAccountsRegisteredConsumerId(value) && SUITE_ACCOUNTS_CONSUMERS[value].auth.kind === "oidc-rp";
240
+ }
241
+ function isSuiteAccountsLinkedOidcConsumerId(value) {
242
+ return SUITE_ACCOUNTS_LINKED_OIDC_CONSUMER_IDS.includes(value);
243
+ }
244
+ function isSuiteAccountsOAuthConsumerId(value) {
245
+ return isSuiteAccountsRegisteredConsumerId(value) && SUITE_ACCOUNTS_CONSUMERS[value].auth.kind === "oidc-rp";
246
+ }
247
+ function isSuiteAccountsCurrentConsumerId(value) {
248
+ return typeof value === "string" && SUITE_ACCOUNTS_CURRENT_CONSUMER_IDS.includes(value);
249
+ }
250
+ function isSuiteAccountsCurrentOidcConsumerId(value) {
251
+ return getSuiteAccountsCurrentConsumer(value).auth.kind === "oidc-rp";
252
+ }
253
+ function isSuiteAccountsCurrentLinkedOidcConsumerId(value) {
254
+ return SUITE_ACCOUNTS_CURRENT_LINKED_OIDC_CONSUMER_IDS.includes(value);
255
+ }
256
+ function isSuiteAccountsCurrentOAuthConsumerId(value) {
257
+ return getSuiteAccountsCurrentConsumer(value).auth.kind === "oidc-rp";
258
+ }
259
+ function isSuiteAccountsCurrentBrowserDeviceCodeConsumerId(value) {
260
+ return typeof value === "string" && SUITE_ACCOUNTS_CURRENT_BROWSER_DEVICE_CODE_CONSUMER_IDS.includes(value);
261
+ }
262
+ function isSuiteAccountsCurrentDeviceClientId(value) {
263
+ return typeof value === "string" && SUITE_ACCOUNTS_CURRENT_DEVICE_CLIENT_IDS.includes(value);
264
+ }
265
+ function suiteAccountsCurrentConsumerRequiresEmailOtp(consumer) {
266
+ return SUITE_ACCOUNTS_CURRENT_EMAIL_OTP_REQUIRED_OIDC_CONSUMER_IDS.includes(consumer);
267
+ }
268
+ function getSuiteAccountsConsumer(consumer) {
269
+ return SUITE_ACCOUNTS_CONSUMERS[consumer];
270
+ }
271
+ function getSuiteAccountsConsumerEnvironment(consumer, environment) {
272
+ if (!isSuiteAccountsRegisteredConsumerId(consumer))
273
+ return null;
274
+ const registration = getSuiteAccountsConsumer(consumer);
275
+ return registration.environments[environment] ?? null;
276
+ }
277
+ function getSuiteAccountsCurrentConsumer(consumer) {
278
+ return SUITE_ACCOUNTS_CURRENT_CONSUMERS[consumer];
279
+ }
280
+ function getSuiteAccountsCurrentConsumerEnvironment(consumer, environment) {
281
+ if (!isSuiteAccountsCurrentConsumerId(consumer))
282
+ return null;
283
+ return getSuiteAccountsCurrentConsumer(consumer).environments[environment] ?? null;
284
+ }
285
+ function isSuiteAccountsActiveConsumerId(value) {
286
+ return SUITE_ACCOUNTS_ACTIVE_CONSUMER_IDS.includes(value);
287
+ }
288
+ function getSuiteAccountsDeployment(environment) {
289
+ return SUITE_ACCOUNTS_DEPLOYMENTS[environment];
290
+ }
291
+
292
+ // src/urls.ts
293
+ var SUITE_ACCOUNTS_OAUTH_RESOURCE = "https://hraness.com/suite";
294
+ var CENTRAL_PATHS = deepFreeze({
295
+ account: "/account",
296
+ home: "/",
297
+ login: "/login"
298
+ });
299
+ function suiteAccountsCentralUrl(environment, destination) {
300
+ return new URL(CENTRAL_PATHS[destination], getSuiteAccountsDeployment(environment).accountsOrigin).href;
301
+ }
302
+ function suiteAccountsBillingReturnUrl(consumer, environment) {
303
+ const consumerEnvironment = getSuiteAccountsConsumerEnvironment(consumer, environment);
304
+ if (consumerEnvironment === null)
305
+ return null;
306
+ return consumerEnvironment.billingReturn.kind === "supported" ? new URL(consumerEnvironment.billingReturn.path, consumerEnvironment.siteUrl).href : null;
307
+ }
308
+ function suiteAccountsOidcClientRegistration(consumer, environment) {
309
+ if (!isSuiteAccountsConsumerId(consumer) || !isSuiteAccountsOAuthConsumerId(consumer))
310
+ return null;
311
+ const consumerEnvironment = getSuiteAccountsConsumerEnvironment(consumer, environment);
312
+ if (consumerEnvironment === null)
313
+ return null;
314
+ return deepFreeze({
315
+ callbackUrl: new URL("/api/suite-auth/callback", consumerEnvironment.siteUrl).href,
316
+ clientId: `hraness:${consumer}:${environment}:v1`
317
+ });
318
+ }
319
+ function suiteAccountsCurrentOidcClientRegistration(consumer, environment) {
320
+ if (!isSuiteAccountsCurrentConsumerId(consumer) || !isSuiteAccountsCurrentOAuthConsumerId(consumer))
321
+ return null;
322
+ const consumerEnvironment = getSuiteAccountsCurrentConsumerEnvironment(consumer, environment);
323
+ if (consumerEnvironment === null)
324
+ return null;
325
+ return deepFreeze({
326
+ callbackUrl: new URL("/api/suite-auth/callback", consumerEnvironment.siteUrl).href,
327
+ clientId: `hraness:${consumer}:${environment}:v1`
328
+ });
329
+ }
330
+ function suiteAccountsOidcClientRequiresEmailOtp(clientId) {
331
+ if (typeof clientId !== "string")
332
+ return false;
333
+ return SUITE_EMAIL_OTP_REQUIRED_OIDC_CONSUMER_IDS.some((consumer) => suiteAccountsOidcClientRegistration(consumer, "production")?.clientId === clientId);
334
+ }
335
+ function suiteAccountsCurrentOidcClientRequiresEmailOtp(clientId) {
336
+ if (typeof clientId !== "string")
337
+ return false;
338
+ return SUITE_ACCOUNTS_CURRENT_EMAIL_OTP_REQUIRED_OIDC_CONSUMER_IDS.some((consumer) => suiteAccountsCurrentOidcClientRegistration(consumer, "production")?.clientId === clientId);
339
+ }
340
+ var DEVICE_CODE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
341
+ function suiteAccountsCurrentDeviceClientRegistration(deviceClient, environment) {
342
+ if (!isSuiteAccountsCurrentDeviceClientId(deviceClient))
343
+ return null;
344
+ const registration = SUITE_ACCOUNTS_CURRENT_DEVICE_CLIENTS[deviceClient];
345
+ if (!registration.environments.includes(environment) || suiteAccountsCurrentOidcClientRegistration(registration.consumer, environment) === null) {
346
+ return null;
347
+ }
348
+ return deepFreeze({
349
+ clientId: `hraness:${deviceClient}:${environment}:v1`,
350
+ consumer: registration.consumer,
351
+ grantTypes: [DEVICE_CODE_GRANT_TYPE],
352
+ redirectUris: [],
353
+ scopes: ["openid", "email", "profile"],
354
+ tokenEndpointAuthMethod: "none"
355
+ });
356
+ }
357
+ function suiteAccountsCurrentDeviceCodeClientAllowed(clientId) {
358
+ if (typeof clientId !== "string")
359
+ return false;
360
+ return SUITE_ACCOUNTS_CURRENT_DEVICE_CLIENT_IDS.some((deviceClient) => suiteAccountsCurrentDeviceClientRegistration(deviceClient, "production")?.clientId === clientId) || SUITE_ACCOUNTS_CURRENT_BROWSER_DEVICE_CODE_CONSUMER_IDS.some((consumer) => isSuiteAccountsCurrentBrowserDeviceCodeConsumerId(consumer) && suiteAccountsCurrentOidcClientRegistration(consumer, "production")?.clientId === clientId);
361
+ }
362
+ function suiteAccountsOidcProviderConfiguration(environment) {
363
+ const issuer = getSuiteAccountsDeployment(environment).accountsOrigin;
364
+ const authBase = new URL("/api/auth/", issuer);
365
+ return deepFreeze({
366
+ authorizationEndpoint: new URL("oauth2/authorize", authBase).href,
367
+ deviceAuthorizationEndpoint: new URL("oauth2/device_authorization", authBase).href,
368
+ deviceTokenEndpoint: new URL("oauth2/device/token", authBase).href,
369
+ discoveryEndpoint: new URL("/.well-known/openid-configuration", issuer).href,
370
+ entitlementReceiptEndpoint: new URL("/suite/entitlements/receipt", issuer).href,
371
+ identityLinkReceiptEndpoint: new URL("/suite/identity-links/receipt", issuer).href,
372
+ issuer,
373
+ jwksEndpoint: new URL("jwks", authBase).href,
374
+ resource: SUITE_ACCOUNTS_OAUTH_RESOURCE,
375
+ revocationEndpoint: new URL("oauth2/revoke", authBase).href,
376
+ tokenEndpoint: new URL("oauth2/token", authBase).href,
377
+ userInfoAudience: new URL("oauth2/userinfo", authBase).href
378
+ });
379
+ }
380
+
381
+ // src/oidc-session-policy.ts
382
+ var SUITE_OIDC_EARLY_REFRESH_WINDOW_MS = 30000;
383
+
384
+ // src/identity/catalog.ts
385
+ import { err as err2, ok as ok2 } from "@hraness/result";
386
+ var SUITE_CATALOG_REVISION = "hraness-suite-v4";
387
+ var PREVIOUS_SUITE_CATALOG_REVISION = "cclrte-suite-v3";
388
+ var LEGACY_SUITE_CATALOG_REVISION = "cclrte-suite-v2";
389
+ var ARCHIVED_SUITE_CATALOG_REVISION = "cclrte-suite-v1";
390
+ var SUITE_CATALOG_REVISIONS = deepFreeze([
391
+ ARCHIVED_SUITE_CATALOG_REVISION,
392
+ LEGACY_SUITE_CATALOG_REVISION,
393
+ PREVIOUS_SUITE_CATALOG_REVISION,
394
+ SUITE_CATALOG_REVISION
395
+ ]);
396
+ var SUITE_PLAN_IDS = deepFreeze(["community", "pro"]);
397
+ var SUITE_HISTORICAL_PLAN_IDS = deepFreeze([
398
+ "individual",
399
+ "business"
400
+ ]);
401
+ var SUITE_CURRENT_FEATURE_IDS = deepFreeze([
402
+ "suite.paid",
403
+ "suite.community",
404
+ "suite.pro"
405
+ ]);
406
+ var SUITE_PREVIOUS_FEATURE_IDS = deepFreeze([
407
+ "suite.paid",
408
+ "suite.believer"
409
+ ]);
410
+ var SUITE_LEGACY_FEATURE_IDS = deepFreeze([
411
+ "suite.paid",
412
+ "suite.business"
413
+ ]);
414
+ var SUITE_FEATURE_IDS = deepFreeze([
415
+ "suite.paid",
416
+ "suite.community",
417
+ "suite.pro",
418
+ "suite.believer",
419
+ "suite.business"
420
+ ]);
421
+ var CURRENT_PLAN_FEATURES = deepFreeze({
422
+ community: ["suite.paid", "suite.community"],
423
+ pro: ["suite.paid", "suite.community", "suite.pro"]
424
+ });
425
+ var PREVIOUS_PLAN_FEATURES = deepFreeze({
426
+ business: ["suite.paid", "suite.believer"],
427
+ individual: ["suite.paid"]
428
+ });
429
+ var LEGACY_PLAN_FEATURES = deepFreeze({
430
+ business: ["suite.paid", "suite.business"],
431
+ individual: ["suite.paid"]
432
+ });
433
+ function parseSuitePlanId(value, revision = SUITE_CATALOG_REVISION) {
434
+ const ids = revision === SUITE_CATALOG_REVISION ? SUITE_PLAN_IDS : SUITE_HISTORICAL_PLAN_IDS;
435
+ return typeof value === "string" && ids.includes(value) ? ok2(value) : err2("invalid-plan");
436
+ }
437
+ function parseAnySuitePlanId(value) {
438
+ return typeof value === "string" && [...SUITE_PLAN_IDS, ...SUITE_HISTORICAL_PLAN_IDS].includes(value) ? ok2(value) : err2("invalid-plan");
439
+ }
440
+ function parseCurrentSuiteFeatureId(value) {
441
+ return value === "suite.paid" || value === "suite.community" || value === "suite.pro" ? ok2(value) : err2("invalid-feature");
442
+ }
443
+ function parseSuiteFeatureId(value) {
444
+ return typeof value === "string" && SUITE_FEATURE_IDS.includes(value) ? ok2(value) : err2("invalid-feature");
445
+ }
446
+ function parseSuiteCatalogRevision(value) {
447
+ return typeof value === "string" && SUITE_CATALOG_REVISIONS.includes(value) ? ok2(value) : err2("invalid-catalog-revision");
448
+ }
449
+ function featuresForSuitePlan(plan, revision = SUITE_CATALOG_REVISION) {
450
+ const table = revision === SUITE_CATALOG_REVISION ? CURRENT_PLAN_FEATURES : revision === ARCHIVED_SUITE_CATALOG_REVISION ? LEGACY_PLAN_FEATURES : PREVIOUS_PLAN_FEATURES;
451
+ const features = table[plan];
452
+ return features === undefined ? [] : [...features];
453
+ }
454
+ function grantedSuiteFeatureSets(revision) {
455
+ if (revision === SUITE_CATALOG_REVISION) {
456
+ return deepFreeze([
457
+ [],
458
+ CURRENT_PLAN_FEATURES.community,
459
+ CURRENT_PLAN_FEATURES.pro
460
+ ]);
461
+ }
462
+ const table = revision === ARCHIVED_SUITE_CATALOG_REVISION ? LEGACY_PLAN_FEATURES : PREVIOUS_PLAN_FEATURES;
463
+ return deepFreeze([
464
+ [],
465
+ table.individual,
466
+ table.business
467
+ ]);
468
+ }
469
+ function suitePlanIncludesFeature(plan, feature) {
470
+ return plan !== null && featuresForSuitePlan(plan).includes(feature);
471
+ }
472
+
473
+ // src/identity/principals.ts
474
+ import { err as err5, isRecord, ok as ok5 } from "@hraness/result";
475
+
476
+ // src/identity/identifiers.ts
477
+ import { err as err3, ok as ok3 } from "@hraness/result";
478
+ var suiteAccountIdPattern = /^acct_[0-9a-f]{32}$/u;
479
+ var suiteInvoiceRefPattern = /^invref_[0-9a-f]{32}$/u;
480
+ var uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu;
481
+ function parseSuiteAccountId(value) {
482
+ return typeof value === "string" && suiteAccountIdPattern.test(value) ? ok3(value) : err3("invalid-suite-account-id");
483
+ }
484
+ function parseSuiteInvoiceRef(value) {
485
+ return typeof value === "string" && suiteInvoiceRefPattern.test(value) ? ok3(value) : err3("invalid-suite-invoice-ref");
486
+ }
487
+ function generateSuiteAccountId(randomUuid = () => crypto.randomUUID()) {
488
+ const uuid = randomUuid();
489
+ if (!uuidPattern.test(uuid)) {
490
+ throw new TypeError("The suite account ID source did not return a UUID.");
491
+ }
492
+ const candidate = `acct_${uuid.replaceAll("-", "").toLowerCase()}`;
493
+ const parsed = parseSuiteAccountId(candidate);
494
+ if (!parsed.ok) {
495
+ throw new TypeError("The suite account ID source produced an invalid value.");
496
+ }
497
+ return parsed.value;
498
+ }
499
+ function generateSuiteInvoiceRef(randomUuid = () => crypto.randomUUID()) {
500
+ const uuid = randomUuid();
501
+ if (!uuidPattern.test(uuid)) {
502
+ throw new TypeError("The suite invoice reference source did not return a UUID.");
503
+ }
504
+ const candidate = `invref_${uuid.replaceAll("-", "").toLowerCase()}`;
505
+ const parsed = parseSuiteInvoiceRef(candidate);
506
+ if (!parsed.ok) {
507
+ throw new TypeError("The suite invoice reference source produced an invalid value.");
508
+ }
509
+ return parsed.value;
510
+ }
511
+
512
+ // src/identity/usernames.ts
513
+ import { err as err4, ok as ok4 } from "@hraness/result";
514
+ var SUITE_USERNAME_MIN_LENGTH = 3;
515
+ var SUITE_USERNAME_MAX_LENGTH = 24;
516
+ var suiteUsernamePattern = /^[a-z0-9](?:[a-z0-9]|[-_](?=[a-z0-9]))*[a-z0-9]$/u;
517
+ var reservedSuiteUsernames = new Set([
518
+ "account",
519
+ "accounts",
520
+ "admin",
521
+ "api",
522
+ "auth",
523
+ "billing",
524
+ "design",
525
+ "docs",
526
+ "help",
527
+ "hraness",
528
+ "login",
529
+ "logout",
530
+ "new",
531
+ "newsletter",
532
+ "party",
533
+ "place",
534
+ "preview",
535
+ "pub",
536
+ "root",
537
+ "settings",
538
+ "social-image",
539
+ "source",
540
+ "sources",
541
+ "support",
542
+ "system",
543
+ "user",
544
+ "users",
545
+ "www"
546
+ ]);
547
+ function validateCanonicalSuiteUsername(value) {
548
+ if (value.length < SUITE_USERNAME_MIN_LENGTH) {
549
+ return err4("suite-username-too-short");
550
+ }
551
+ if (value.length > SUITE_USERNAME_MAX_LENGTH) {
552
+ return err4("suite-username-too-long");
553
+ }
554
+ if (!suiteUsernamePattern.test(value)) {
555
+ return err4("invalid-suite-username");
556
+ }
557
+ return ok4(value);
558
+ }
559
+ function normalizeSuiteUsername(value) {
560
+ if (typeof value !== "string")
561
+ return err4("invalid-suite-username");
562
+ for (let index = 0;index < value.length; index += 1) {
563
+ const code = value.charCodeAt(index);
564
+ if (code > 127 || code < 32 || code === 127) {
565
+ return err4("invalid-suite-username");
566
+ }
567
+ }
568
+ const trimmed = value.trim();
569
+ const parsed = validateCanonicalSuiteUsername(trimmed.toLowerCase());
570
+ if (!parsed.ok)
571
+ return parsed;
572
+ return reservedSuiteUsernames.has(parsed.value) ? err4("suite-username-reserved") : parsed;
573
+ }
574
+ function parseSuiteUsername(value) {
575
+ if (typeof value !== "string")
576
+ return err4("invalid-suite-username");
577
+ const parsed = validateCanonicalSuiteUsername(value);
578
+ return parsed.ok && parsed.value === value ? parsed : err4(parsed.ok ? "invalid-suite-username" : parsed.error);
579
+ }
580
+
581
+ // src/identity/principals.ts
582
+ var SUITE_PRODUCTS = deepFreeze([
583
+ "soundfish",
584
+ "hra",
585
+ "peopleblade",
586
+ "ghostget",
587
+ "crclte",
588
+ "pub"
589
+ ]);
590
+ var LEGACY_SUITE_PRODUCT_IDS = deepFreeze([
591
+ "oprte",
592
+ "kitchen"
593
+ ]);
594
+ var SUITE_ENVIRONMENTS = deepFreeze([
595
+ "development",
596
+ "staging",
597
+ "production"
598
+ ]);
599
+ var SUITE_ISSUABLE_ENVIRONMENTS = deepFreeze([
600
+ "development",
601
+ "production"
602
+ ]);
603
+ var localIssuerHosts = new Set(["127.0.0.1", "[::1]", "localhost"]);
604
+ function containsAsciiControl(value) {
605
+ for (let index = 0;index < value.length; index += 1) {
606
+ const code = value.charCodeAt(index);
607
+ if (code <= 31 || code === 127)
608
+ return true;
609
+ }
610
+ return false;
611
+ }
612
+ function parseIdentityIssuer(value) {
613
+ if (typeof value !== "string" || value.length > 2048 || value.trim() !== value) {
614
+ return err5("invalid-issuer");
615
+ }
616
+ let parsed;
617
+ try {
618
+ parsed = new URL(value);
619
+ } catch {
620
+ return err5("invalid-issuer");
621
+ }
622
+ const local = localIssuerHosts.has(parsed.hostname);
623
+ if (parsed.protocol !== "https:" && !(local && parsed.protocol === "http:") || parsed.username !== "" || parsed.password !== "" || parsed.pathname !== "/" || parsed.search !== "" || parsed.hash !== "") {
624
+ return err5("invalid-issuer");
625
+ }
626
+ return ok5(parsed.origin);
627
+ }
628
+ function parseIdentitySubject(value) {
629
+ if (typeof value !== "string" || value.length < 1 || value.length > 255 || value.trim() !== value || containsAsciiControl(value)) {
630
+ return err5("invalid-subject");
631
+ }
632
+ return ok5(value);
633
+ }
634
+ function parseIssuerSubject(value) {
635
+ if (!isRecord(value))
636
+ return err5("invalid-subject");
637
+ const issuer = parseIdentityIssuer(value["issuer"]);
638
+ if (!issuer.ok)
639
+ return issuer;
640
+ const subject = parseIdentitySubject(value["subject"]);
641
+ return subject.ok ? ok5({ issuer: issuer.value, subject: subject.value }) : subject;
642
+ }
643
+ function parseSuiteProduct(value) {
644
+ switch (value) {
645
+ case "soundfish":
646
+ case "hra":
647
+ case "peopleblade":
648
+ case "ghostget":
649
+ case "crclte":
650
+ case "pub":
651
+ return ok5(value);
652
+ case "oprte":
653
+ case "kitchen":
654
+ return ok5("hra");
655
+ default:
656
+ return err5("invalid-product");
657
+ }
658
+ }
659
+ function parseSuiteEnvironment(value) {
660
+ return typeof value === "string" && SUITE_ENVIRONMENTS.includes(value) ? ok5(value) : err5("invalid-environment");
661
+ }
662
+ function isSuiteIssuableEnvironment(value) {
663
+ return value === "development" || value === "production";
664
+ }
665
+ function parseLegacyPrincipalLink(value) {
666
+ if (!isRecord(value))
667
+ return err5("invalid-legacy-link");
668
+ const product = parseSuiteProduct(value["product"]);
669
+ if (!product.ok)
670
+ return product;
671
+ const environment = parseSuiteEnvironment(value["environment"]);
672
+ if (!environment.ok)
673
+ return environment;
674
+ const legacySubject = parseIdentitySubject(value["legacySubject"]);
675
+ if (!legacySubject.ok)
676
+ return legacySubject;
677
+ return ok5({
678
+ environment: environment.value,
679
+ legacySubject: legacySubject.value,
680
+ product: product.value
681
+ });
682
+ }
683
+ function parseAudience(value) {
684
+ const values = typeof value === "string" ? [value] : value;
685
+ if (!Array.isArray(values) || values.length < 1 || values.length > 8 || values.some((entry) => typeof entry !== "string" || entry.length < 1 || entry.length > 255 || entry.trim() !== entry || containsAsciiControl(entry)) || new Set(values).size !== values.length) {
686
+ return err5("invalid-audience");
687
+ }
688
+ return ok5(values);
689
+ }
690
+ function parseTimestamp(value) {
691
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : null;
692
+ }
693
+ function parseSuiteJwtClaims(value) {
694
+ if (!isRecord(value))
695
+ return err5("invalid-jwt-claims");
696
+ const principal = parseIssuerSubject({
697
+ issuer: value["iss"],
698
+ subject: value["sub"]
699
+ });
700
+ if (!principal.ok)
701
+ return principal;
702
+ const suiteAccountId = parseSuiteAccountId(value["suite_account_id"]);
703
+ if (!suiteAccountId.ok)
704
+ return suiteAccountId;
705
+ const audience = parseAudience(value["aud"]);
706
+ if (!audience.ok)
707
+ return audience;
708
+ const issuedAtSeconds = parseTimestamp(value["iat"]);
709
+ const expiresAtSeconds = parseTimestamp(value["exp"]);
710
+ const notBeforeSeconds = value["nbf"] === undefined ? undefined : parseTimestamp(value["nbf"]);
711
+ const legacyProfile = value["profile_revision"] === undefined && value["profile_complete"] === undefined && value["username"] === undefined;
712
+ const profileRevision = legacyProfile ? null : value["profile_revision"] === "username-v1" ? "username-v1" : undefined;
713
+ const profileComplete = legacyProfile ? false : typeof value["profile_complete"] === "boolean" ? value["profile_complete"] : undefined;
714
+ const username = legacyProfile || value["username"] === null ? null : parseSuiteUsername(value["username"]);
715
+ if (issuedAtSeconds === null || expiresAtSeconds === null || expiresAtSeconds <= issuedAtSeconds || notBeforeSeconds === null || notBeforeSeconds !== undefined && notBeforeSeconds > expiresAtSeconds || profileRevision === undefined || profileComplete === undefined || username !== null && !username.ok || profileComplete !== (username !== null)) {
716
+ return err5("invalid-jwt-claims");
717
+ }
718
+ return ok5({
719
+ audience: audience.value,
720
+ expiresAtSeconds,
721
+ issuedAtSeconds,
722
+ ...notBeforeSeconds === undefined ? {} : { notBeforeSeconds },
723
+ principal: principal.value,
724
+ profileComplete,
725
+ profileRevision,
726
+ suiteAccountId: suiteAccountId.value,
727
+ username: username?.value ?? null
728
+ });
729
+ }
730
+
731
+ // src/entitlements.ts
732
+ var SUITE_ENTITLEMENTS_CLAIM_VERSION = "suite-entitlements-v1";
733
+ var SUITE_ENTITLEMENT_MAX_PROVIDER_AGE_MS = 26 * 60 * 60000;
734
+ function isRecord2(value) {
735
+ return typeof value === "object" && value !== null && !Array.isArray(value);
736
+ }
737
+ function nonNegativeInteger(value) {
738
+ return Number.isSafeInteger(value) && typeof value === "number" && value >= 0;
739
+ }
740
+ function canonicalFeatures(revision, features) {
741
+ return grantedSuiteFeatureSets(revision).some((set) => set.length === features.length && set.every((feature, index) => feature === features[index]));
742
+ }
743
+ function validReceiptProjection(projection) {
744
+ const revision = parseSuiteCatalogRevision(projection.catalogRevision);
745
+ return revision.ok && projection.suiteAccountId.length >= 1 && projection.suiteAccountId.length <= 128 && canonicalFeatures(revision.value, projection.features) && nonNegativeInteger(projection.observedAtMs) && nonNegativeInteger(projection.expiresAtMs) && projection.expiresAtMs > projection.observedAtMs && nonNegativeInteger(projection.projectionRevision) && nonNegativeInteger(projection.receiptIssuedAtMs) && projection.receiptIssuedAtMs < projection.expiresAtMs && (projection.receiptDigest === undefined || /^[a-f0-9]{64}$/u.test(projection.receiptDigest));
746
+ }
747
+ function sameFeatures(left, right) {
748
+ return left.length === right.length && left.every((feature, index) => feature === right[index]);
749
+ }
750
+ function decideSuiteEntitlementReceiptProjection(current, incoming) {
751
+ if (!validReceiptProjection(incoming))
752
+ return "conflict";
753
+ if (current === null)
754
+ return "insert";
755
+ if (!validReceiptProjection(current) || current.suiteAccountId !== incoming.suiteAccountId || incoming.projectionRevision < current.projectionRevision || incoming.observedAtMs < current.observedAtMs || incoming.receiptIssuedAtMs < current.receiptIssuedAtMs) {
756
+ return "conflict";
757
+ }
758
+ if (incoming.projectionRevision > current.projectionRevision || incoming.receiptIssuedAtMs > current.receiptIssuedAtMs) {
759
+ return "replace";
760
+ }
761
+ return incoming.catalogRevision === current.catalogRevision && incoming.expiresAtMs === current.expiresAtMs && incoming.observedAtMs === current.observedAtMs && sameFeatures(incoming.features, current.features) && incoming.receiptDigest === current.receiptDigest ? "replay" : "conflict";
762
+ }
763
+ function parseEntitlements(value) {
764
+ if (!isRecord2(value))
765
+ return null;
766
+ const catalogRevision = parseSuiteCatalogRevision(value["catalogRevision"]);
767
+ if (value["version"] !== SUITE_ENTITLEMENTS_CLAIM_VERSION || !catalogRevision.ok || !nonNegativeInteger(value["observedAtMs"]) || !nonNegativeInteger(value["expiresAtMs"]) || !nonNegativeInteger(value["projectionRevision"]) || !Array.isArray(value["features"]) || value["features"].length > SUITE_FEATURE_IDS.length) {
768
+ return null;
769
+ }
770
+ const features = [];
771
+ for (const valueFeature of value["features"]) {
772
+ const feature = parseSuiteFeatureId(valueFeature);
773
+ if (!feature.ok || features.includes(feature.value))
774
+ return null;
775
+ features.push(feature.value);
776
+ }
777
+ if (value["expiresAtMs"] <= value["observedAtMs"] || !canonicalFeatures(catalogRevision.value, features))
778
+ return null;
779
+ return {
780
+ catalogRevision: catalogRevision.value,
781
+ expiresAtMs: value["expiresAtMs"],
782
+ features,
783
+ observedAtMs: value["observedAtMs"],
784
+ projectionRevision: value["projectionRevision"],
785
+ version: SUITE_ENTITLEMENTS_CLAIM_VERSION
786
+ };
787
+ }
788
+ function validExpectedString(value) {
789
+ let hasControl = false;
790
+ for (let index = 0;index < value.length; index += 1) {
791
+ const code = value.charCodeAt(index);
792
+ if (code <= 31 || code === 127) {
793
+ hasControl = true;
794
+ break;
795
+ }
796
+ }
797
+ return value.length >= 1 && value.length <= 2048 && value.trim() === value && !hasControl;
798
+ }
799
+ async function verifySuiteEntitlementToken(compactToken, options) {
800
+ if (compactToken.length < 1 || compactToken.length > 16384 || !validExpectedString(options.expectedAudience) || !validExpectedString(options.expectedIssuer)) {
801
+ return { kind: "invalid", reason: "claims" };
802
+ }
803
+ let payload;
804
+ try {
805
+ payload = await options.verify(compactToken);
806
+ } catch {
807
+ return { kind: "invalid", reason: "signature" };
808
+ }
809
+ const parsed = parseSuiteJwtClaims(payload);
810
+ if (!parsed.ok)
811
+ return { kind: "invalid", reason: "claims" };
812
+ const claims = parsed.value;
813
+ if (claims.principal.issuer !== options.expectedIssuer) {
814
+ return { kind: "invalid", reason: "issuer" };
815
+ }
816
+ if (!claims.audience.includes(options.expectedAudience)) {
817
+ return { kind: "invalid", reason: "audience" };
818
+ }
819
+ const nowMs = options.nowMs ?? Date.now();
820
+ const clockSkewMs = options.clockSkewMs ?? 30000;
821
+ const maxTokenLifetimeMs = options.maxTokenLifetimeMs ?? 20 * 60000;
822
+ const maxProjectionAgeMs = options.maxProjectionAgeMs ?? SUITE_ENTITLEMENT_MAX_PROVIDER_AGE_MS;
823
+ if (!nonNegativeInteger(nowMs) || !nonNegativeInteger(clockSkewMs) || !nonNegativeInteger(maxTokenLifetimeMs) || !nonNegativeInteger(maxProjectionAgeMs)) {
824
+ return { kind: "invalid", reason: "time" };
825
+ }
826
+ const issuedAtMs = claims.issuedAtSeconds * 1000;
827
+ const expiresAtMs = claims.expiresAtSeconds * 1000;
828
+ const notBeforeMs = claims.notBeforeSeconds === undefined ? issuedAtMs : claims.notBeforeSeconds * 1000;
829
+ if (expiresAtMs - issuedAtMs > maxTokenLifetimeMs || issuedAtMs > nowMs + clockSkewMs || notBeforeMs > nowMs + clockSkewMs || expiresAtMs <= nowMs - clockSkewMs) {
830
+ return { kind: "invalid", reason: "time" };
831
+ }
832
+ if (!isRecord2(payload))
833
+ return { kind: "invalid", reason: "claims" };
834
+ const rawEntitlements = payload["suite_entitlements"];
835
+ if (rawEntitlements === undefined) {
836
+ return {
837
+ claims,
838
+ entitlements: { claim: null, features: [], kind: "legacy" },
839
+ kind: "verified"
840
+ };
841
+ }
842
+ const claim = parseEntitlements(rawEntitlements);
843
+ if (claim === null || claim.expiresAtMs > expiresAtMs) {
844
+ return { kind: "invalid", reason: "entitlements" };
845
+ }
846
+ const fresh = claim.observedAtMs <= nowMs + clockSkewMs && claim.expiresAtMs > nowMs - clockSkewMs && nowMs - claim.observedAtMs <= maxProjectionAgeMs;
847
+ return {
848
+ claims,
849
+ entitlements: fresh ? { claim, features: claim.features, kind: "fresh" } : { claim, features: [], kind: "stale" },
850
+ kind: "verified"
851
+ };
852
+ }
853
+ function suiteTokenGrantsFeature(result, feature) {
854
+ return result.kind === "verified" && result.entitlements.kind === "fresh" && result.entitlements.features.includes(feature);
855
+ }
856
+
857
+ // src/identity/links.ts
858
+ import { ok as ok6 } from "@hraness/result";
859
+ var IDENTITY_LINK_PROOF_VERSION = "suite-product-link-proof-v1";
860
+ var IDENTITY_LINK_RECEIPT_VERSION = "suite-link-receipt-v1";
861
+ var SUITE_ENTITLEMENTS_CLAIM_VERSION2 = "suite-entitlements-v1";
862
+ var SUITE_ENTITLEMENT_RECEIPT_VERSION = "suite-entitlement-receipt-v1";
863
+ var IDENTITY_LINK_MAX_TTL_MS = 5 * 60000;
864
+ var SUITE_ENTITLEMENT_RECEIPT_MAX_TTL_MS = 5 * 60000;
865
+ var IDENTITY_LINK_CLOCK_SKEW_MS = 30000;
866
+ var SUITE_LINK_PRODUCTS = deepFreeze([
867
+ "soundfish",
868
+ "hra",
869
+ "peopleblade",
870
+ "ghostget",
871
+ "crclte",
872
+ "pub"
873
+ ]);
874
+ var LEGACY_SUITE_LINK_PRODUCTS = deepFreeze([
875
+ "oprte",
876
+ "kitchen"
877
+ ]);
878
+ function parseSuiteLinkProduct(value) {
879
+ const parsed = parseSuiteProduct(value);
880
+ if (!parsed.ok)
881
+ return parsed;
882
+ switch (parsed.value) {
883
+ case "soundfish":
884
+ case "hra":
885
+ case "peopleblade":
886
+ case "ghostget":
887
+ case "crclte":
888
+ case "pub":
889
+ return ok6(parsed.value);
890
+ }
891
+ }
892
+ function safeInteger(value) {
893
+ return Number.isSafeInteger(value) && value >= 0;
894
+ }
895
+ function validateProductLinkProof(input, now) {
896
+ const product = parseSuiteLinkProduct(input.product);
897
+ const environment = parseSuiteEnvironment(input.environment);
898
+ const localSubject = parseIdentitySubject(input.localSubject);
899
+ if (!safeInteger(now) || !product.ok || !environment.ok || !localSubject.ok || !/^[A-Za-z0-9_-]{22,128}$/u.test(input.challengeId) || !/^[a-z0-9][a-z0-9._-]{0,31}$/u.test(input.keyVersion) || !safeInteger(input.issuedAtMs) || !safeInteger(input.expiresAtMs) || input.expiresAtMs <= input.issuedAtMs || input.expiresAtMs - input.issuedAtMs > IDENTITY_LINK_MAX_TTL_MS) {
900
+ return "invalid";
901
+ }
902
+ if (input.issuedAtMs > now + IDENTITY_LINK_CLOCK_SKEW_MS) {
903
+ return "not-yet-valid";
904
+ }
905
+ return input.expiresAtMs <= now ? "expired" : null;
906
+ }
907
+ function productLinkProofMessage(input) {
908
+ return JSON.stringify([
909
+ IDENTITY_LINK_PROOF_VERSION,
910
+ input.product,
911
+ input.environment,
912
+ input.localSubject,
913
+ input.challengeId,
914
+ input.issuedAtMs,
915
+ input.expiresAtMs,
916
+ input.keyVersion
917
+ ]);
918
+ }
919
+ function suiteLinkReceiptMessage(input) {
920
+ return JSON.stringify([
921
+ IDENTITY_LINK_RECEIPT_VERSION,
922
+ input.product,
923
+ input.environment,
924
+ input.localSubject,
925
+ input.suiteAccountId,
926
+ input.challengeId,
927
+ input.issuedAtMs,
928
+ input.expiresAtMs,
929
+ input.keyVersion
930
+ ]);
931
+ }
932
+ function validateSuiteLinkReceipt(input, now) {
933
+ const proofIssue = validateProductLinkProof(input, now);
934
+ if (proofIssue !== null || input.version !== IDENTITY_LINK_RECEIPT_VERSION || !parseSuiteAccountId(input.suiteAccountId).ok || !/^[A-Za-z0-9_-]{43}$/u.test(input.signature)) {
935
+ return proofIssue ?? "invalid";
936
+ }
937
+ return null;
938
+ }
939
+ function exactGrantedFeatures(revision, values) {
940
+ if (values.length > SUITE_FEATURE_IDS.length)
941
+ return false;
942
+ const parsed = [];
943
+ for (const value of values) {
944
+ const feature = parseSuiteFeatureId(value);
945
+ if (!feature.ok || parsed.includes(feature.value))
946
+ return false;
947
+ parsed.push(feature.value);
948
+ }
949
+ return grantedSuiteFeatureSets(revision).some((set) => set.length === parsed.length && set.every((feature, index) => feature === parsed[index]));
950
+ }
951
+ function validateSuiteEntitlementsClaim(input) {
952
+ const catalogRevision = parseSuiteCatalogRevision(input.catalogRevision);
953
+ return input.version === SUITE_ENTITLEMENTS_CLAIM_VERSION2 && catalogRevision.ok && safeInteger(input.observedAtMs) && safeInteger(input.expiresAtMs) && input.expiresAtMs > input.observedAtMs && safeInteger(input.projectionRevision) && Array.isArray(input.features) && exactGrantedFeatures(catalogRevision.value, input.features);
954
+ }
955
+ function suiteEntitlementReceiptMessage(input) {
956
+ return JSON.stringify([
957
+ SUITE_ENTITLEMENT_RECEIPT_VERSION,
958
+ input.product,
959
+ input.environment,
960
+ input.suiteAccountId,
961
+ input.keyVersion,
962
+ input.issuedAtMs,
963
+ input.expiresAtMs,
964
+ input.entitlements.version,
965
+ input.entitlements.catalogRevision,
966
+ input.entitlements.observedAtMs,
967
+ input.entitlements.expiresAtMs,
968
+ input.entitlements.projectionRevision,
969
+ input.entitlements.features
970
+ ]);
971
+ }
972
+ function validateSuiteEntitlementReceipt(input, now) {
973
+ const product = parseSuiteLinkProduct(input.product);
974
+ const environment = parseSuiteEnvironment(input.environment);
975
+ if (!safeInteger(now) || input.version !== SUITE_ENTITLEMENT_RECEIPT_VERSION || !product.ok || !environment.ok || !parseSuiteAccountId(input.suiteAccountId).ok || !/^[a-z0-9][a-z0-9._-]{0,31}$/u.test(input.keyVersion) || !safeInteger(input.issuedAtMs) || !safeInteger(input.expiresAtMs) || input.expiresAtMs <= input.issuedAtMs || input.expiresAtMs - input.issuedAtMs > SUITE_ENTITLEMENT_RECEIPT_MAX_TTL_MS || !validateSuiteEntitlementsClaim(input.entitlements) || input.expiresAtMs > input.entitlements.expiresAtMs || !/^[A-Za-z0-9_-]{43}$/u.test(input.signature)) {
976
+ return "invalid";
977
+ }
978
+ if (input.issuedAtMs > now + IDENTITY_LINK_CLOCK_SKEW_MS || input.entitlements.observedAtMs > now + IDENTITY_LINK_CLOCK_SKEW_MS) {
979
+ return "not-yet-valid";
980
+ }
981
+ return input.expiresAtMs <= now ? "expired" : null;
982
+ }
983
+
984
+ // src/oidc-rp.ts
985
+ import {
986
+ createLocalJWKSet,
987
+ decodeProtectedHeader,
988
+ jwtVerify
989
+ } from "jose";
990
+
991
+ // src/oidc-continuation.ts
992
+ function htmlAttribute(value) {
993
+ return value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&#39;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
994
+ }
995
+ function inlineJson(value) {
996
+ return JSON.stringify(value).replaceAll("&", "\\u0026").replaceAll("<", "\\u003c").replaceAll(">", "\\u003e").replaceAll("\u2028", "\\u2028").replaceAll("\u2029", "\\u2029");
997
+ }
998
+ var SUITE_OIDC_JUST_SIGNED_IN_STORAGE_KEY = "hraness:suite:signed-in:v1";
999
+ function createOidcContinuationResponse(returnTo, nonce, cookies) {
1000
+ const headers = new Headers({
1001
+ "cache-control": "no-store",
1002
+ "content-security-policy": [
1003
+ "default-src 'none'",
1004
+ "base-uri 'none'",
1005
+ "form-action 'none'",
1006
+ "frame-ancestors 'none'",
1007
+ `script-src 'nonce-${nonce}'`
1008
+ ].join("; "),
1009
+ "content-type": "text/html; charset=utf-8",
1010
+ pragma: "no-cache",
1011
+ "referrer-policy": "no-referrer",
1012
+ "x-content-type-options": "nosniff",
1013
+ "x-frame-options": "DENY"
1014
+ });
1015
+ for (const cookie of cookies)
1016
+ headers.append("set-cookie", cookie);
1017
+ const body = [
1018
+ "<!doctype html>",
1019
+ '<html lang="en">',
1020
+ "<head>",
1021
+ '<meta charset="utf-8">',
1022
+ '<meta name="viewport" content="width=device-width, initial-scale=1">',
1023
+ "<title>Signed in</title>",
1024
+ "</head>",
1025
+ "<body>",
1026
+ "<p>Signed in.</p>",
1027
+ `<p><a href="${htmlAttribute(returnTo)}">Continue</a></p>`,
1028
+ `<script nonce="${nonce}">try{sessionStorage.setItem(${inlineJson(SUITE_OIDC_JUST_SIGNED_IN_STORAGE_KEY)},"1")}catch{}location.replace(${inlineJson(returnTo)});</script>`,
1029
+ "</body>",
1030
+ "</html>"
1031
+ ].join("");
1032
+ return new Response(body, { headers, status: 200 });
1033
+ }
1034
+
1035
+ // src/oidc-rp.ts
1036
+ var TRANSACTION_TTL_MS = 10 * 60000;
1037
+ var SESSION_TTL_MS = 7 * 24 * 60 * 60000;
1038
+ var FETCH_TIMEOUT_MS = 8000;
1039
+ var MAX_PROVIDER_BODY_BYTES = 64 * 1024;
1040
+ var MAX_COOKIE_BYTES = 4096;
1041
+ var MAX_RETURN_PATH_BYTES = 1024;
1042
+ var MAX_CODE_BYTES = 2048;
1043
+ var ALLOWED_TOKEN_ALGORITHMS = ["ES256"];
1044
+ var REMOTE_TRANSACTION_COOKIE = "__Host-hraness-suite-oidc-transaction";
1045
+ var REMOTE_SESSION_COOKIE = "__Host-hraness-suite-oidc-session";
1046
+ var LOCAL_TRANSACTION_COOKIE = "hraness-suite-oidc-local-transaction";
1047
+ var LOCAL_SESSION_COOKIE = "hraness-suite-oidc-local-session";
1048
+ function isReceiptConsumer(consumer) {
1049
+ return isSuiteAccountsCurrentLinkedOidcConsumerId(consumer);
1050
+ }
1051
+ function isRecord3(value) {
1052
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1053
+ }
1054
+ function safeInteger2(value) {
1055
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
1056
+ }
1057
+ function boundedString(value, minimum, maximum) {
1058
+ return typeof value === "string" && value.length >= minimum && value.length <= maximum && !value.includes("\x00");
1059
+ }
1060
+ function encodeBase64Url(bytes) {
1061
+ let binary = "";
1062
+ for (const byte of bytes)
1063
+ binary += String.fromCharCode(byte);
1064
+ return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/u, "");
1065
+ }
1066
+ function decodeBase64Url(value) {
1067
+ if (!/^[A-Za-z0-9_-]+$/u.test(value))
1068
+ return null;
1069
+ const padded = value.replaceAll("-", "+").replaceAll("_", "/") + "=".repeat((4 - value.length % 4) % 4);
1070
+ try {
1071
+ const binary = atob(padded);
1072
+ return Uint8Array.from(binary, (character) => character.charCodeAt(0));
1073
+ } catch {
1074
+ return null;
1075
+ }
1076
+ }
1077
+ function ownedBytes(value) {
1078
+ const result = new Uint8Array(new ArrayBuffer(value.byteLength));
1079
+ result.set(value);
1080
+ return result;
1081
+ }
1082
+ function randomValue(length, source) {
1083
+ const bytes = source(length);
1084
+ if (bytes.byteLength !== length) {
1085
+ throw new Error("The OIDC random source returned the wrong byte length.");
1086
+ }
1087
+ return encodeBase64Url(bytes);
1088
+ }
1089
+ async function sha256Base64Url(value) {
1090
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(value));
1091
+ return encodeBase64Url(new Uint8Array(digest));
1092
+ }
1093
+ function cookieNames(siteUrl) {
1094
+ const secure = new URL(siteUrl).protocol === "https:";
1095
+ return {
1096
+ secure,
1097
+ session: secure ? REMOTE_SESSION_COOKIE : LOCAL_SESSION_COOKIE,
1098
+ transaction: secure ? REMOTE_TRANSACTION_COOKIE : LOCAL_TRANSACTION_COOKIE
1099
+ };
1100
+ }
1101
+ function cookieAttributes(secure, maxAgeSeconds) {
1102
+ return [
1103
+ `Max-Age=${maxAgeSeconds}`,
1104
+ "Path=/",
1105
+ "HttpOnly",
1106
+ ...secure ? ["Secure"] : [],
1107
+ "SameSite=Lax"
1108
+ ].join("; ");
1109
+ }
1110
+ function setCookie(name, value, secure, maxAgeSeconds) {
1111
+ return `${name}=${value}; ${cookieAttributes(secure, maxAgeSeconds)}`;
1112
+ }
1113
+ function clearCookie(name, secure) {
1114
+ return [
1115
+ `${name}=`,
1116
+ "Max-Age=0",
1117
+ "Expires=Thu, 01 Jan 1970 00:00:00 GMT",
1118
+ "Path=/",
1119
+ "HttpOnly",
1120
+ ...secure ? ["Secure"] : [],
1121
+ "SameSite=Lax"
1122
+ ].join("; ");
1123
+ }
1124
+ function requestCookie(request, name) {
1125
+ const header = request.headers.get("cookie");
1126
+ if (header === null || header.length > 16384)
1127
+ return null;
1128
+ const values = [];
1129
+ for (const segment of header.split(";")) {
1130
+ const trimmed = segment.trim();
1131
+ const separator = trimmed.indexOf("=");
1132
+ if (separator <= 0 || trimmed.slice(0, separator) !== name)
1133
+ continue;
1134
+ values.push(trimmed.slice(separator + 1));
1135
+ }
1136
+ return values.length === 1 ? values[0] : null;
1137
+ }
1138
+ async function deriveCookieKey(secret, consumer, environment) {
1139
+ const material = new TextEncoder().encode(`hraness-suite-oidc-cookie-v1\x00${consumer}\x00${environment}\x00${secret}`);
1140
+ const digest = await crypto.subtle.digest("SHA-256", material);
1141
+ return await crypto.subtle.importKey("raw", digest, { name: "AES-GCM" }, false, ["decrypt", "encrypt"]);
1142
+ }
1143
+ async function sealCookie(value, key, purpose, randomBytes) {
1144
+ const iv = ownedBytes(randomBytes(12));
1145
+ if (iv.byteLength !== 12)
1146
+ throw new Error("Invalid OIDC cookie IV.");
1147
+ const plaintext = new TextEncoder().encode(JSON.stringify(value));
1148
+ if (plaintext.byteLength > MAX_COOKIE_BYTES - 256) {
1149
+ throw new Error("The OIDC cookie payload was too large.");
1150
+ }
1151
+ const aad = new TextEncoder().encode(`hraness-suite-oidc-${purpose}-v1`);
1152
+ const encrypted = await crypto.subtle.encrypt({ additionalData: aad, iv, name: "AES-GCM", tagLength: 128 }, key, plaintext);
1153
+ const sealed = `v1.${encodeBase64Url(iv)}.${encodeBase64Url(new Uint8Array(encrypted))}`;
1154
+ if (sealed.length > MAX_COOKIE_BYTES) {
1155
+ throw new Error("The sealed OIDC cookie was too large.");
1156
+ }
1157
+ return sealed;
1158
+ }
1159
+ async function unsealCookie(value, key, purpose) {
1160
+ if (value.length < 1 || value.length > MAX_COOKIE_BYTES)
1161
+ return null;
1162
+ const parts = value.split(".");
1163
+ if (parts.length !== 3 || parts[0] !== "v1")
1164
+ return null;
1165
+ const iv = decodeBase64Url(parts[1]);
1166
+ const ciphertext = decodeBase64Url(parts[2]);
1167
+ if (iv?.byteLength !== 12 || ciphertext === null)
1168
+ return null;
1169
+ try {
1170
+ const plaintext = await crypto.subtle.decrypt({
1171
+ additionalData: new TextEncoder().encode(`hraness-suite-oidc-${purpose}-v1`),
1172
+ iv: ownedBytes(iv),
1173
+ name: "AES-GCM",
1174
+ tagLength: 128
1175
+ }, key, ownedBytes(ciphertext));
1176
+ if (plaintext.byteLength > MAX_COOKIE_BYTES)
1177
+ return null;
1178
+ return JSON.parse(new TextDecoder().decode(plaintext));
1179
+ } catch {
1180
+ return null;
1181
+ }
1182
+ }
1183
+ function parseTransaction(value, consumer, environment, nowMs) {
1184
+ if (!isRecord3(value) || value["version"] !== 1 && value["version"] !== 2 || value["consumer"] !== consumer || value["environment"] !== environment || !safeInteger2(value["issuedAtMs"]) || !safeInteger2(value["expiresAtMs"]) || value["expiresAtMs"] <= nowMs || value["issuedAtMs"] > nowMs + 30000 || value["expiresAtMs"] - value["issuedAtMs"] > TRANSACTION_TTL_MS || !boundedString(value["state"], 43, 128) || !boundedString(value["nonce"], 43, 128) || !boundedString(value["verifier"], 43, 128) || parseReturnPath(value["returnTo"], "") === null) {
1185
+ return null;
1186
+ }
1187
+ if (value["version"] === 2 && (!safeInteger2(nowMs) || value["issuedAtMs"] > nowMs || value["expiresAtMs"] <= value["issuedAtMs"] || !validFreshContext(value["context"]) || Object.keys(value).length !== 10))
1188
+ return null;
1189
+ return value;
1190
+ }
1191
+ function validFreshContext(value) {
1192
+ return typeof value === "string" && /^[A-Za-z0-9_-]{32,256}$/u.test(value);
1193
+ }
1194
+ function parseFreshAuthenticationInput(value, nowMs) {
1195
+ if (!isRecord3(value) || !safeInteger2(nowMs) || !safeInteger2(nowMs + TRANSACTION_TTL_MS))
1196
+ return null;
1197
+ const descriptors = Object.getOwnPropertyDescriptors(value);
1198
+ const ownKeys = Reflect.ownKeys(descriptors);
1199
+ if (ownKeys.length !== 2 && ownKeys.length !== 3 || !("value" in (descriptors["context"] ?? {})) || !("value" in (descriptors["expiresAtMs"] ?? {})))
1200
+ return null;
1201
+ if (ownKeys.length === 3 && !("value" in (descriptors["authenticationNotBeforeMs"] ?? {})))
1202
+ return null;
1203
+ const context = descriptors["context"].value;
1204
+ const expiresAtMs = descriptors["expiresAtMs"].value;
1205
+ const notBeforeMs = descriptors["authenticationNotBeforeMs"]?.value;
1206
+ if (!validFreshContext(context) || !safeInteger2(expiresAtMs) || expiresAtMs <= nowMs || expiresAtMs > nowMs + TRANSACTION_TTL_MS || notBeforeMs !== undefined && (!safeInteger2(notBeforeMs) || notBeforeMs < 0))
1207
+ return null;
1208
+ return notBeforeMs === undefined ? { context, expiresAtMs } : { context, expiresAtMs, authenticationNotBeforeMs: notBeforeMs };
1209
+ }
1210
+ function parseStoredEntitlements(value) {
1211
+ if (!isRecord3(value) || !Array.isArray(value["features"]))
1212
+ return null;
1213
+ const features = [];
1214
+ for (const rawFeature of value["features"]) {
1215
+ const feature = parseSuiteFeatureId(rawFeature);
1216
+ if (!feature.ok || features.includes(feature.value))
1217
+ return null;
1218
+ features.push(feature.value);
1219
+ }
1220
+ if (value["kind"] === "legacy") {
1221
+ return value["claim"] === null && features.length === 0 ? { claim: null, features: [], kind: "legacy" } : null;
1222
+ }
1223
+ if (value["kind"] !== "fresh" && value["kind"] !== "stale")
1224
+ return null;
1225
+ const claim = value["claim"];
1226
+ const catalogRevision = isRecord3(claim) ? parseSuiteCatalogRevision(claim["catalogRevision"]) : null;
1227
+ if (!isRecord3(claim) || claim["version"] !== "suite-entitlements-v1" || catalogRevision === null || !catalogRevision.ok || !safeInteger2(claim["observedAtMs"]) || !safeInteger2(claim["expiresAtMs"]) || !safeInteger2(claim["projectionRevision"]) || !Array.isArray(claim["features"])) {
1228
+ return null;
1229
+ }
1230
+ const claimFeatures = [];
1231
+ for (const rawFeature of claim["features"]) {
1232
+ const feature = parseSuiteFeatureId(rawFeature);
1233
+ if (!feature.ok || claimFeatures.includes(feature.value))
1234
+ return null;
1235
+ claimFeatures.push(feature.value);
1236
+ }
1237
+ if (claim["expiresAtMs"] <= claim["observedAtMs"] || value["kind"] === "fresh" && (features.length !== claimFeatures.length || features.some((feature) => !claimFeatures.includes(feature)))) {
1238
+ return null;
1239
+ }
1240
+ const parsedClaim = {
1241
+ catalogRevision: catalogRevision.value,
1242
+ expiresAtMs: claim["expiresAtMs"],
1243
+ features: claimFeatures,
1244
+ observedAtMs: claim["observedAtMs"],
1245
+ projectionRevision: claim["projectionRevision"],
1246
+ version: "suite-entitlements-v1"
1247
+ };
1248
+ return value["kind"] === "fresh" ? { claim: parsedClaim, features, kind: "fresh" } : { claim: parsedClaim, features: [], kind: "stale" };
1249
+ }
1250
+ function parseEntitlementReceipt(value, consumer, environment, keyVersion, suiteAccountId, nowMs) {
1251
+ if (!isReceiptConsumer(consumer))
1252
+ return null;
1253
+ if (!isRecord3(value) || !isRecord3(value["entitlements"])) {
1254
+ return null;
1255
+ }
1256
+ try {
1257
+ const receipt = value;
1258
+ return validateSuiteEntitlementReceipt(receipt, nowMs) === null && receipt.product === consumer && receipt.environment === environment && receipt.keyVersion === keyVersion && receipt.suiteAccountId === suiteAccountId ? receipt : null;
1259
+ } catch {
1260
+ return null;
1261
+ }
1262
+ }
1263
+ function parseIdentityLinkReceiptRequest(value, consumer, environment, nowMs) {
1264
+ if (!isReceiptConsumer(consumer))
1265
+ return null;
1266
+ if (!isRecord3(value) || Object.keys(value).length !== 8 || value["product"] !== consumer || value["environment"] !== environment || !boundedString(value["challengeId"], 22, 128) || !boundedString(value["localSubject"], 1, 255) || !boundedString(value["keyVersion"], 1, 32) || !boundedString(value["proofSignature"], 43, 43) || !safeInteger2(value["issuedAtMs"]) || !safeInteger2(value["expiresAtMs"])) {
1267
+ return null;
1268
+ }
1269
+ const proof = {
1270
+ challengeId: value["challengeId"],
1271
+ environment,
1272
+ expiresAtMs: value["expiresAtMs"],
1273
+ issuedAtMs: value["issuedAtMs"],
1274
+ keyVersion: value["keyVersion"],
1275
+ localSubject: value["localSubject"],
1276
+ product: consumer
1277
+ };
1278
+ return validateProductLinkProof(proof, nowMs) === null && /^[A-Za-z0-9_-]{43}$/u.test(value["proofSignature"]) ? { ...proof, proofSignature: value["proofSignature"] } : null;
1279
+ }
1280
+ function parseIdentityLinkReceipt(value, proof, suiteAccountId, nowMs) {
1281
+ if (!isRecord3(value))
1282
+ return null;
1283
+ try {
1284
+ const receipt = value;
1285
+ return validateSuiteLinkReceipt(receipt, nowMs) === null && receipt.challengeId === proof.challengeId && receipt.environment === proof.environment && receipt.expiresAtMs === proof.expiresAtMs && receipt.issuedAtMs === proof.issuedAtMs && receipt.keyVersion === proof.keyVersion && receipt.localSubject === proof.localSubject && receipt.product === proof.product && receipt.suiteAccountId === suiteAccountId ? receipt : null;
1286
+ } catch {
1287
+ return null;
1288
+ }
1289
+ }
1290
+ function parseStoredProfile(value) {
1291
+ const profileRevision = value["profileRevision"];
1292
+ const profileComplete = value["profileComplete"];
1293
+ const rawUsername = value["username"];
1294
+ if (profileComplete === false && (profileRevision === null || profileRevision === "username-v1") && rawUsername === null) {
1295
+ return {
1296
+ profileComplete: false,
1297
+ profileRevision,
1298
+ username: null
1299
+ };
1300
+ }
1301
+ if (profileComplete !== true || profileRevision !== "username-v1") {
1302
+ return null;
1303
+ }
1304
+ const username = parseSuiteUsername(rawUsername);
1305
+ return username.ok ? {
1306
+ profileComplete: true,
1307
+ profileRevision: "username-v1",
1308
+ username: username.value
1309
+ } : null;
1310
+ }
1311
+ function profileFromClaims(claims) {
1312
+ if (claims.profileComplete && claims.profileRevision === "username-v1" && claims.username !== null) {
1313
+ return {
1314
+ profileComplete: true,
1315
+ profileRevision: "username-v1",
1316
+ username: claims.username
1317
+ };
1318
+ }
1319
+ return !claims.profileComplete && claims.username === null ? {
1320
+ profileComplete: false,
1321
+ profileRevision: claims.profileRevision,
1322
+ username: null
1323
+ } : null;
1324
+ }
1325
+ function parseSession(value, configuration, consumer, environment, receiptKeyVersion, nowMs) {
1326
+ if (!isRecord3(value) || value["version"] !== 2 || value["environment"] !== environment || value["issuer"] !== configuration.provider.issuer || !boundedString(value["accessToken"], 16, 16384) || value["audience"] !== configuration.provider.resource || !boundedString(value["subject"], 1, 255) || !/^acct_[0-9a-f]{32}$/u.test(String(value["suiteAccountId"])) || !boundedString(value["nonce"], 43, 128) || !boundedString(value["refreshToken"], 16, 2048) || !safeInteger2(value["accessTokenExpiresAtMs"]) || !safeInteger2(value["expiresAtMs"]) || value["expiresAtMs"] <= nowMs || value["expiresAtMs"] > nowMs + SESSION_TTL_MS + 30000) {
1327
+ return null;
1328
+ }
1329
+ const suiteAccountId = parseSuiteAccountId(value["suiteAccountId"]);
1330
+ const entitlements = parseStoredEntitlements(value["entitlements"]);
1331
+ const profile = parseStoredProfile(value);
1332
+ const pendingEntitlementReceipt = value["pendingEntitlementReceipt"] === null ? null : parseEntitlementReceipt(value["pendingEntitlementReceipt"], consumer, environment, receiptKeyVersion, String(value["suiteAccountId"]), nowMs);
1333
+ return entitlements === null || profile === null || !suiteAccountId.ok ? null : {
1334
+ ...value,
1335
+ entitlements,
1336
+ pendingEntitlementReceipt,
1337
+ ...profile,
1338
+ suiteAccountId: suiteAccountId.value
1339
+ };
1340
+ }
1341
+ function parseReturnPath(value, fallback = "/") {
1342
+ if (value === undefined || value === null || value === "")
1343
+ return fallback;
1344
+ if (typeof value !== "string" || value.length > MAX_RETURN_PATH_BYTES || !value.startsWith("/") || value.startsWith("//") || value.includes("\\") || value.includes("#")) {
1345
+ return null;
1346
+ }
1347
+ let parsed;
1348
+ try {
1349
+ parsed = new URL(value, "https://return.invalid");
1350
+ } catch {
1351
+ return null;
1352
+ }
1353
+ if (parsed.origin !== "https://return.invalid" || parsed.username !== "" || parsed.password !== "" || parsed.pathname.startsWith("/api/suite-auth/")) {
1354
+ return null;
1355
+ }
1356
+ return `${parsed.pathname}${parsed.search}`;
1357
+ }
1358
+ function exactRequest(request, siteUrl, path, method) {
1359
+ let url;
1360
+ try {
1361
+ url = new URL(request.url);
1362
+ } catch {
1363
+ return false;
1364
+ }
1365
+ if (request.method !== method || url.origin !== siteUrl || url.pathname !== path) {
1366
+ return false;
1367
+ }
1368
+ const fetchSite = request.headers.get("sec-fetch-site");
1369
+ if (fetchSite !== null && fetchSite.toLowerCase() !== "same-origin") {
1370
+ return false;
1371
+ }
1372
+ if (method === "POST" && request.headers.get("origin") !== siteUrl) {
1373
+ return false;
1374
+ }
1375
+ return true;
1376
+ }
1377
+ function exactCallbackRequest(request, siteUrl) {
1378
+ let url;
1379
+ try {
1380
+ url = new URL(request.url);
1381
+ } catch {
1382
+ return false;
1383
+ }
1384
+ if (request.method !== "GET" || url.origin !== siteUrl || url.pathname !== "/api/suite-auth/callback") {
1385
+ return false;
1386
+ }
1387
+ const mode = request.headers.get("sec-fetch-mode");
1388
+ const destination = request.headers.get("sec-fetch-dest");
1389
+ const site = request.headers.get("sec-fetch-site");
1390
+ return (mode === null || mode.toLowerCase() === "navigate") && (destination === null || destination.toLowerCase() === "document") && (site === null || ["cross-site", "same-origin", "same-site", "none"].includes(site.toLowerCase()));
1391
+ }
1392
+ function jsonResponse(body, status, cookies = []) {
1393
+ const headers = new Headers({
1394
+ "cache-control": "no-store",
1395
+ "content-type": "application/json; charset=utf-8",
1396
+ pragma: "no-cache"
1397
+ });
1398
+ for (const cookie of cookies)
1399
+ headers.append("set-cookie", cookie);
1400
+ return new Response(JSON.stringify(body), { headers, status });
1401
+ }
1402
+ function failure(code, status, cookies = []) {
1403
+ return jsonResponse({
1404
+ error: { code, retryable: status >= 500 },
1405
+ schemaVersion: 1
1406
+ }, status, cookies);
1407
+ }
1408
+ async function readBoundedJson(response, maximumBytes = MAX_PROVIDER_BODY_BYTES) {
1409
+ const declared = response.headers.get("content-length");
1410
+ if (declared !== null && (!/^(?:0|[1-9]\d*)$/u.test(declared) || Number(declared) > maximumBytes)) {
1411
+ throw new Error("Provider response was too large.");
1412
+ }
1413
+ if (response.body === null)
1414
+ throw new Error("Provider response was empty.");
1415
+ const reader = response.body.getReader();
1416
+ const chunks = [];
1417
+ let length = 0;
1418
+ while (true) {
1419
+ const result = await reader.read();
1420
+ if (result.done)
1421
+ break;
1422
+ length += result.value.byteLength;
1423
+ if (length > maximumBytes) {
1424
+ await reader.cancel();
1425
+ throw new Error("Provider response was too large.");
1426
+ }
1427
+ chunks.push(result.value);
1428
+ }
1429
+ const bytes = new Uint8Array(length);
1430
+ let offset = 0;
1431
+ for (const chunk of chunks) {
1432
+ bytes.set(chunk, offset);
1433
+ offset += chunk.byteLength;
1434
+ }
1435
+ return JSON.parse(new TextDecoder().decode(bytes));
1436
+ }
1437
+ async function consumeBoundedBody(response, maximumBytes) {
1438
+ const declared = response.headers.get("content-length");
1439
+ if (declared !== null && (!/^(?:0|[1-9]\d*)$/u.test(declared) || Number(declared) > maximumBytes)) {
1440
+ throw new Error("Provider response was too large.");
1441
+ }
1442
+ if (response.body === null)
1443
+ return;
1444
+ const reader = response.body.getReader();
1445
+ let length = 0;
1446
+ while (true) {
1447
+ const result = await reader.read();
1448
+ if (result.done)
1449
+ return;
1450
+ length += result.value.byteLength;
1451
+ if (length > maximumBytes) {
1452
+ await reader.cancel();
1453
+ throw new Error("Provider response was too large.");
1454
+ }
1455
+ }
1456
+ }
1457
+ async function readBoundedRequestJson(request, maximumBytes) {
1458
+ const declared = request.headers.get("content-length");
1459
+ if (declared !== null && (!/^(?:0|[1-9]\d*)$/u.test(declared) || Number(declared) > maximumBytes)) {
1460
+ throw new Error("Request body was too large.");
1461
+ }
1462
+ if (request.body === null)
1463
+ throw new Error("Request body was empty.");
1464
+ const reader = request.body.getReader();
1465
+ const chunks = [];
1466
+ let length = 0;
1467
+ while (true) {
1468
+ const result = await reader.read();
1469
+ if (result.done)
1470
+ break;
1471
+ if (result.value.byteLength > maximumBytes - length) {
1472
+ await reader.cancel();
1473
+ throw new Error("Request body was too large.");
1474
+ }
1475
+ length += result.value.byteLength;
1476
+ chunks.push(result.value);
1477
+ }
1478
+ const bytes = new Uint8Array(length);
1479
+ let offset = 0;
1480
+ for (const chunk of chunks) {
1481
+ bytes.set(chunk, offset);
1482
+ offset += chunk.byteLength;
1483
+ }
1484
+ return JSON.parse(new TextDecoder().decode(bytes));
1485
+ }
1486
+ async function providerJson(url, init, fetchImplementation, timeoutMs) {
1487
+ const controller = new AbortController;
1488
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
1489
+ try {
1490
+ const response = await fetchImplementation(url, {
1491
+ ...init,
1492
+ redirect: "manual",
1493
+ signal: controller.signal
1494
+ });
1495
+ if (response.status !== 200 || response.headers.has("location")) {
1496
+ throw new Error("Provider request failed.");
1497
+ }
1498
+ return await readBoundedJson(response);
1499
+ } finally {
1500
+ clearTimeout(timeout);
1501
+ }
1502
+ }
1503
+ function validateSuiteOidcDiscovery(value, expected) {
1504
+ if (!isRecord3(value))
1505
+ return false;
1506
+ const exact = value["issuer"] === expected.issuer && value["authorization_endpoint"] === expected.authorizationEndpoint && value["token_endpoint"] === expected.tokenEndpoint && value["jwks_uri"] === expected.jwksEndpoint && value["revocation_endpoint"] === expected.revocationEndpoint;
1507
+ const responseTypes = value["response_types_supported"];
1508
+ const grants = value["grant_types_supported"];
1509
+ const challenges = value["code_challenge_methods_supported"];
1510
+ const methods = value["token_endpoint_auth_methods_supported"];
1511
+ const algorithms = value["id_token_signing_alg_values_supported"];
1512
+ return exact && Array.isArray(responseTypes) && responseTypes.includes("code") && Array.isArray(grants) && grants.includes("authorization_code") && grants.includes("refresh_token") && Array.isArray(challenges) && challenges.includes("S256") && Array.isArray(methods) && methods.includes("none") && Array.isArray(algorithms) && algorithms.length === 1 && algorithms[0] === "ES256" && !algorithms.includes("HS256");
1513
+ }
1514
+ function parseTokenResponse(value, requireIdToken) {
1515
+ if (!isRecord3(value) || value["token_type"] !== "Bearer" || !boundedString(value["access_token"], 16, 16384) || !boundedString(value["refresh_token"], 16, 4096) || value["id_token"] !== undefined && !boundedString(value["id_token"], 64, 16384) || requireIdToken && !boundedString(value["id_token"], 64, 16384)) {
1516
+ return null;
1517
+ }
1518
+ return {
1519
+ accessToken: value["access_token"],
1520
+ idToken: typeof value["id_token"] === "string" ? value["id_token"] : null,
1521
+ refreshToken: value["refresh_token"],
1522
+ tokenType: "Bearer"
1523
+ };
1524
+ }
1525
+ function parseJwks(value) {
1526
+ if (!isRecord3(value) || !Array.isArray(value["keys"]) || value["keys"].length < 1 || value["keys"].length > 8) {
1527
+ return null;
1528
+ }
1529
+ for (const key of value["keys"]) {
1530
+ if (!isRecord3(key) || !boundedString(key["kid"], 1, 128) || key["alg"] !== undefined && key["alg"] !== "ES256" || key["kty"] !== "EC" || key["crv"] !== "P-256" || !boundedString(key["x"], 1, 256) || !boundedString(key["y"], 1, 256) || key["d"] !== undefined) {
1531
+ return null;
1532
+ }
1533
+ }
1534
+ return value;
1535
+ }
1536
+ async function verifiedIdToken(input) {
1537
+ const header = decodeProtectedHeader(input.idToken);
1538
+ if (!boundedString(header.kid, 1, 128) || header.alg !== "ES256") {
1539
+ return null;
1540
+ }
1541
+ const verified = await jwtVerify(input.idToken, createLocalJWKSet(input.jwks), {
1542
+ algorithms: [...ALLOWED_TOKEN_ALGORITHMS],
1543
+ audience: input.configuration.clientId,
1544
+ clockTolerance: input.freshStartedAtMs === undefined ? 30 : 0,
1545
+ currentDate: new Date(input.nowMs),
1546
+ issuer: input.configuration.provider.issuer,
1547
+ maxTokenAge: "20m"
1548
+ });
1549
+ if (verified.payload["nonce"] !== input.expectedNonce || !boundedString(verified.payload.sub, 1, 255)) {
1550
+ return null;
1551
+ }
1552
+ const claims = parseSuiteJwtClaims(verified.payload);
1553
+ if (!claims.ok)
1554
+ return null;
1555
+ const profile = profileFromClaims(claims.value);
1556
+ if (profile === null)
1557
+ return null;
1558
+ let freshTimes = null;
1559
+ if (input.freshStartedAtMs !== undefined) {
1560
+ const authTime = verified.payload["auth_time"];
1561
+ const issuedAt = verified.payload.iat;
1562
+ const expiresAt = verified.payload.exp;
1563
+ if (!safeInteger2(input.nowMs) || input.nowMs < input.freshStartedAtMs || !safeInteger2(authTime) || !safeInteger2(authTime * 1000) || !safeInteger2(issuedAt) || !safeInteger2(expiresAt) || !safeInteger2(expiresAt * 1000) || authTime < Math.floor(input.freshStartedAtMs / 1000) || authTime > Math.floor(input.nowMs / 1000) || authTime > issuedAt || issuedAt > Math.floor(input.nowMs / 1000) || expiresAt * 1000 <= input.nowMs || verified.payload.aud !== input.configuration.clientId || verified.payload["azp"] !== undefined && verified.payload["azp"] !== input.configuration.clientId)
1564
+ return null;
1565
+ freshTimes = { authenticatedAtMs: authTime * 1000, expiresAtMs: expiresAt * 1000 };
1566
+ }
1567
+ return {
1568
+ freshTimes,
1569
+ profile,
1570
+ subject: String(claims.value.principal.subject),
1571
+ suiteAccountId: claims.value.suiteAccountId
1572
+ };
1573
+ }
1574
+ function exactAccessTokenAudience(value, provider) {
1575
+ if (!Array.isArray(value) || value.length !== 2)
1576
+ return false;
1577
+ const audiences = new Set(value);
1578
+ return audiences.size === 2 && audiences.has(provider.resource) && audiences.has(provider.userInfoAudience);
1579
+ }
1580
+ async function verifiedSessionFromAccessToken(input) {
1581
+ const header = decodeProtectedHeader(input.accessToken);
1582
+ if (!boundedString(header.kid, 1, 128) || header.alg !== "ES256") {
1583
+ return null;
1584
+ }
1585
+ let verifiedPayload;
1586
+ try {
1587
+ const verified = await jwtVerify(input.accessToken, createLocalJWKSet(input.jwks), {
1588
+ algorithms: [...ALLOWED_TOKEN_ALGORITHMS],
1589
+ audience: input.configuration.provider.resource,
1590
+ clockTolerance: input.requireFresh === true ? 0 : 30,
1591
+ currentDate: new Date(input.nowMs),
1592
+ issuer: input.configuration.provider.issuer,
1593
+ maxTokenAge: "20m"
1594
+ });
1595
+ verifiedPayload = verified.payload;
1596
+ } catch {
1597
+ return null;
1598
+ }
1599
+ const result = await verifySuiteEntitlementToken(input.accessToken, {
1600
+ ...input.requireFresh === true ? { clockSkewMs: 0 } : {},
1601
+ expectedAudience: input.configuration.provider.resource,
1602
+ expectedIssuer: input.configuration.provider.issuer,
1603
+ nowMs: input.nowMs,
1604
+ verify: () => Promise.resolve(verifiedPayload)
1605
+ });
1606
+ const profile = result.kind === "verified" ? profileFromClaims(result.claims) : null;
1607
+ const expectedProfileMatches = input.expectedProfile === undefined || (input.expectedProfile.profileComplete ? profile?.profileComplete === true && profile.username === input.expectedProfile.username : input.profileTransition === "forward" ? profile?.profileComplete === true || profile?.profileComplete === false && (input.expectedProfile.profileRevision === null || profile.profileRevision === input.expectedProfile.profileRevision) : profile?.profileComplete === false && profile.profileRevision === input.expectedProfile.profileRevision);
1608
+ if (result.kind !== "verified" || profile === null || !expectedProfileMatches || !exactAccessTokenAudience(verifiedPayload.aud, input.configuration.provider) || verifiedPayload["azp"] !== input.configuration.clientId || verifiedPayload["suite_client_id"] !== input.configuration.clientId || input.expectedSubject !== undefined && result.claims.principal.subject !== input.expectedSubject || input.expectedAccountId !== undefined && result.claims.suiteAccountId !== input.expectedAccountId) {
1609
+ return null;
1610
+ }
1611
+ return {
1612
+ accessToken: input.accessToken,
1613
+ audience: input.configuration.provider.resource,
1614
+ entitlements: result.entitlements,
1615
+ environment: input.environment,
1616
+ expiresAtMs: input.nowMs + SESSION_TTL_MS,
1617
+ accessTokenExpiresAtMs: result.claims.expiresAtSeconds * 1000,
1618
+ issuer: input.configuration.provider.issuer,
1619
+ nonce: input.nonce,
1620
+ pendingEntitlementReceipt: null,
1621
+ ...profile,
1622
+ refreshToken: input.refreshToken,
1623
+ subject: String(result.claims.principal.subject),
1624
+ suiteAccountId: result.claims.suiteAccountId,
1625
+ version: 2
1626
+ };
1627
+ }
1628
+ function currentView(session, nowMs) {
1629
+ if (session.accessTokenExpiresAtMs <= nowMs)
1630
+ return null;
1631
+ const entitlements = session.entitlements;
1632
+ const fresh = entitlements.kind === "fresh" && entitlements.claim.expiresAtMs > nowMs && nowMs - entitlements.claim.observedAtMs <= SUITE_ENTITLEMENT_MAX_PROVIDER_AGE_MS;
1633
+ const base = {
1634
+ entitlementReceipt: session.pendingEntitlementReceipt,
1635
+ entitlements: {
1636
+ features: fresh ? entitlements.features : [],
1637
+ kind: entitlements.kind === "legacy" ? "legacy" : fresh ? "fresh" : "stale"
1638
+ },
1639
+ suiteAccountId: session.suiteAccountId
1640
+ };
1641
+ return session.profileComplete ? {
1642
+ ...base,
1643
+ profileComplete: true,
1644
+ profileRevision: "username-v1",
1645
+ username: session.username
1646
+ } : {
1647
+ ...base,
1648
+ profileComplete: false,
1649
+ profileRevision: session.profileRevision,
1650
+ username: null
1651
+ };
1652
+ }
1653
+ function liveUserInfoMatchesSession(value, configuration, session, view) {
1654
+ if (!isRecord3(value))
1655
+ return false;
1656
+ const account = parseSuiteAccountId(value["suite_account_id"]);
1657
+ if (!account.ok || account.value !== session.suiteAccountId || value["suite_client_id"] !== configuration.clientId || value["sub"] !== session.subject || value["profile_complete"] !== view.profileComplete || value["profile_revision"] !== view.profileRevision) {
1658
+ return false;
1659
+ }
1660
+ if (!view.profileComplete)
1661
+ return value["username"] === null;
1662
+ const username = parseSuiteUsername(value["username"]);
1663
+ return username.ok && username.value === view.username;
1664
+ }
1665
+ function verifiedEmailFromUserInfo(value) {
1666
+ if (!isRecord3(value) || value["email_verified"] !== true)
1667
+ return null;
1668
+ const rawEmail = value["email"];
1669
+ if (typeof rawEmail !== "string" || rawEmail.length === 0 || rawEmail.length > 320 || rawEmail.trim() !== rawEmail || [...rawEmail].some((character) => {
1670
+ const codePoint = character.codePointAt(0);
1671
+ return codePoint !== undefined && (codePoint <= 32 || codePoint === 127);
1672
+ }) || !/^[^@]+@[^@]+\.[^@]+$/u.test(rawEmail))
1673
+ return null;
1674
+ return rawEmail.toLocaleLowerCase("en-US");
1675
+ }
1676
+ function createSuiteOidcRelyingParty(options) {
1677
+ const consumer = options.consumer;
1678
+ const cookieSecret = options.cookieSecret;
1679
+ const environment = options.environment;
1680
+ const receiptKeyVersion = options.receiptKeyVersion;
1681
+ const cookieSecretBytes = new TextEncoder().encode(cookieSecret);
1682
+ if (cookieSecretBytes.byteLength < 32 || cookieSecretBytes.byteLength > 1024) {
1683
+ throw new Error("The suite OIDC cookie secret must be 32–1024 bytes.");
1684
+ }
1685
+ if (!/^[a-z0-9][a-z0-9._-]{0,31}$/u.test(receiptKeyVersion)) {
1686
+ throw new Error("The suite receipt key version was invalid.");
1687
+ }
1688
+ const registration = suiteAccountsCurrentOidcClientRegistration(consumer, environment);
1689
+ if (registration === null) {
1690
+ throw new Error("The suite consumer has no OIDC client in this environment.");
1691
+ }
1692
+ const consumerEnvironment = getSuiteAccountsCurrentConsumerEnvironment(consumer, environment);
1693
+ if (consumerEnvironment === null) {
1694
+ throw new Error("The suite consumer has no registered origin in this environment.");
1695
+ }
1696
+ const siteUrl = consumerEnvironment.siteUrl;
1697
+ const provider = suiteAccountsOidcProviderConfiguration(environment);
1698
+ const configuration = deepFreeze({
1699
+ callbackUrl: registration.callbackUrl,
1700
+ clientId: registration.clientId,
1701
+ provider,
1702
+ siteUrl
1703
+ });
1704
+ const names = cookieNames(siteUrl);
1705
+ const fetchImplementation = options.fetch ?? fetch;
1706
+ const timeoutMs = options.fetchTimeoutMs ?? FETCH_TIMEOUT_MS;
1707
+ if (!safeInteger2(timeoutMs) || timeoutMs < 100 || timeoutMs > 30000) {
1708
+ throw new Error("The suite OIDC fetch timeout must be 100–30000ms.");
1709
+ }
1710
+ const now = options.now ?? Date.now;
1711
+ const randomBytes = options.randomBytes ?? ((length) => {
1712
+ const value = new Uint8Array(length);
1713
+ crypto.getRandomValues(value);
1714
+ return value;
1715
+ });
1716
+ const key = deriveCookieKey(cookieSecret, consumer, environment);
1717
+ async function discovery() {
1718
+ const value = await providerJson(provider.discoveryEndpoint, { headers: { accept: "application/json" }, method: "GET" }, fetchImplementation, timeoutMs);
1719
+ if (!validateSuiteOidcDiscovery(value, provider)) {
1720
+ throw new Error("The OIDC provider metadata did not match the registry.");
1721
+ }
1722
+ }
1723
+ async function jwks() {
1724
+ const value = await providerJson(provider.jwksEndpoint, { headers: { accept: "application/json" }, method: "GET" }, fetchImplementation, timeoutMs);
1725
+ const parsed = parseJwks(value);
1726
+ if (parsed === null)
1727
+ throw new Error("The OIDC JWKS was invalid.");
1728
+ return parsed;
1729
+ }
1730
+ async function tokenRequest(body, requireIdToken) {
1731
+ const value = await providerJson(provider.tokenEndpoint, {
1732
+ body,
1733
+ headers: {
1734
+ accept: "application/json",
1735
+ "content-type": "application/x-www-form-urlencoded",
1736
+ origin: provider.issuer
1737
+ },
1738
+ method: "POST"
1739
+ }, fetchImplementation, timeoutMs);
1740
+ const parsed = parseTokenResponse(value, requireIdToken);
1741
+ if (parsed === null)
1742
+ throw new Error("The OIDC token response was invalid.");
1743
+ return parsed;
1744
+ }
1745
+ async function revokeRefreshToken(refreshToken) {
1746
+ const controller = new AbortController;
1747
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
1748
+ try {
1749
+ const response = await fetchImplementation(provider.revocationEndpoint, {
1750
+ body: new URLSearchParams({
1751
+ client_id: configuration.clientId,
1752
+ token: refreshToken,
1753
+ token_type_hint: "refresh_token"
1754
+ }),
1755
+ headers: {
1756
+ accept: "application/json",
1757
+ "content-type": "application/x-www-form-urlencoded",
1758
+ origin: provider.issuer
1759
+ },
1760
+ method: "POST",
1761
+ redirect: "manual",
1762
+ signal: controller.signal
1763
+ });
1764
+ if (response.status !== 200 || response.headers.has("location")) {
1765
+ throw new Error("OIDC refresh-token revocation failed.");
1766
+ }
1767
+ await consumeBoundedBody(response, 1024);
1768
+ } finally {
1769
+ clearTimeout(timeout);
1770
+ }
1771
+ }
1772
+ async function entitlementReceipt(accessToken, suiteAccountId) {
1773
+ if (!isReceiptConsumer(consumer))
1774
+ return null;
1775
+ const value = await providerJson(provider.entitlementReceiptEndpoint, {
1776
+ body: JSON.stringify({
1777
+ environment,
1778
+ keyVersion: receiptKeyVersion,
1779
+ product: consumer
1780
+ }),
1781
+ headers: {
1782
+ accept: "application/json",
1783
+ authorization: `Bearer ${accessToken}`,
1784
+ "content-type": "application/json"
1785
+ },
1786
+ method: "POST"
1787
+ }, fetchImplementation, timeoutMs);
1788
+ const parsed = parseEntitlementReceipt(value, consumer, environment, receiptKeyVersion, suiteAccountId, now());
1789
+ if (parsed === null) {
1790
+ throw new Error("The suite entitlement receipt was invalid.");
1791
+ }
1792
+ return parsed;
1793
+ }
1794
+ async function requestIdentityLinkReceipt(accessToken, proof, suiteAccountId) {
1795
+ const controller = new AbortController;
1796
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
1797
+ try {
1798
+ const response = await fetchImplementation(provider.identityLinkReceiptEndpoint, {
1799
+ body: JSON.stringify(proof),
1800
+ headers: {
1801
+ accept: "application/json",
1802
+ authorization: `Bearer ${accessToken}`,
1803
+ "content-type": "application/json"
1804
+ },
1805
+ method: "POST",
1806
+ redirect: "manual",
1807
+ signal: controller.signal
1808
+ });
1809
+ if (response.headers.has("location")) {
1810
+ throw new Error("Identity-link receipt endpoint redirected.");
1811
+ }
1812
+ if ([400, 401, 403, 409, 410, 413].includes(response.status)) {
1813
+ await consumeBoundedBody(response, 16384);
1814
+ return {
1815
+ kind: "rejected",
1816
+ status: response.status
1817
+ };
1818
+ }
1819
+ if (response.status !== 200) {
1820
+ throw new Error("Identity-link receipt endpoint failed.");
1821
+ }
1822
+ const value = await readBoundedJson(response, 16384);
1823
+ const receipt = parseIdentityLinkReceipt(value, proof, suiteAccountId, now());
1824
+ if (receipt === null) {
1825
+ throw new Error("Identity-link receipt was invalid.");
1826
+ }
1827
+ return { kind: "receipt", receipt };
1828
+ } finally {
1829
+ clearTimeout(timeout);
1830
+ }
1831
+ }
1832
+ async function loadSession(request) {
1833
+ const sealed = requestCookie(request, names.session);
1834
+ if (sealed === null)
1835
+ return null;
1836
+ return parseSession(await unsealCookie(sealed, await key, "session"), configuration, consumer, environment, receiptKeyVersion, now());
1837
+ }
1838
+ async function startAuthorization(request, freshInput, requireFresh = false) {
1839
+ if (!exactRequest(request, siteUrl, "/api/suite-auth/start", "GET")) {
1840
+ return failure("OIDC_START_REJECTED", 403);
1841
+ }
1842
+ const requestUrl = new URL(request.url);
1843
+ const returnTo = parseReturnPath(requestUrl.searchParams.get("return_to"));
1844
+ if (returnTo === null)
1845
+ return failure("OIDC_RETURN_REJECTED", 400);
1846
+ const issuedAtMs = now();
1847
+ const fresh = requireFresh ? parseFreshAuthenticationInput(freshInput, issuedAtMs) : null;
1848
+ if (requireFresh && fresh === null)
1849
+ return failure("OIDC_FRESH_INPUT_INVALID", 400);
1850
+ const verifier = randomValue(48, randomBytes);
1851
+ const transaction = {
1852
+ consumer,
1853
+ environment,
1854
+ expiresAtMs: fresh?.expiresAtMs ?? issuedAtMs + TRANSACTION_TTL_MS,
1855
+ issuedAtMs,
1856
+ nonce: randomValue(32, randomBytes),
1857
+ returnTo,
1858
+ state: randomValue(32, randomBytes),
1859
+ verifier,
1860
+ ...fresh === null ? { version: 1 } : { version: 2, context: fresh.context }
1861
+ };
1862
+ const authorize = new URL(provider.authorizationEndpoint);
1863
+ authorize.searchParams.set("client_id", configuration.clientId);
1864
+ authorize.searchParams.set("code_challenge", await sha256Base64Url(verifier));
1865
+ authorize.searchParams.set("code_challenge_method", "S256");
1866
+ authorize.searchParams.set("nonce", transaction.nonce);
1867
+ if (requireFresh) {
1868
+ authorize.searchParams.set("prompt", "login");
1869
+ const notBefore = fresh?.authenticationNotBeforeMs;
1870
+ authorize.searchParams.set("max_age", String(notBefore === undefined ? 0 : Math.max(0, Math.ceil((issuedAtMs - notBefore) / 1000))));
1871
+ }
1872
+ authorize.searchParams.set("redirect_uri", configuration.callbackUrl);
1873
+ authorize.searchParams.set("resource", provider.resource);
1874
+ authorize.searchParams.set("response_type", "code");
1875
+ authorize.searchParams.set("scope", suiteAccountsCurrentConsumerRequiresEmailOtp(consumer) ? "openid profile email offline_access" : "openid offline_access");
1876
+ authorize.searchParams.set("state", transaction.state);
1877
+ const sealed = await sealCookie(transaction, await key, "transaction", randomBytes);
1878
+ return new Response(null, {
1879
+ headers: {
1880
+ "cache-control": "no-store",
1881
+ location: authorize.href,
1882
+ "set-cookie": setCookie(names.transaction, sealed, names.secure, Math.ceil((transaction.expiresAtMs - issuedAtMs) / 1000))
1883
+ },
1884
+ status: 302
1885
+ });
1886
+ }
1887
+ async function start(request) {
1888
+ return await startAuthorization(request);
1889
+ }
1890
+ async function startFreshAuthentication(request, input) {
1891
+ try {
1892
+ return await startAuthorization(request, input, true);
1893
+ } catch {
1894
+ return failure("OIDC_FRESH_START_FAILED", 400);
1895
+ }
1896
+ }
1897
+ async function completeAuthorization(request, requireFresh) {
1898
+ const clear = clearCookie(names.transaction, names.secure);
1899
+ const reject = (code, status) => ({
1900
+ kind: "rejected",
1901
+ response: failure(code, status, [clear])
1902
+ });
1903
+ if (!exactCallbackRequest(request, siteUrl)) {
1904
+ return reject("OIDC_CALLBACK_REJECTED", 403);
1905
+ }
1906
+ const sealed = requestCookie(request, names.transaction);
1907
+ const transaction = sealed === null ? null : parseTransaction(await unsealCookie(sealed, await key, "transaction"), consumer, environment, now());
1908
+ if (transaction === null || transaction.version === 2 !== requireFresh) {
1909
+ return reject("OIDC_TRANSACTION_INVALID", 400);
1910
+ }
1911
+ const url = new URL(request.url);
1912
+ const states = url.searchParams.getAll("state");
1913
+ const codes = url.searchParams.getAll("code");
1914
+ const errors = url.searchParams.getAll("error");
1915
+ if (states.length !== 1 || states[0] !== transaction.state || errors.length > 0 || codes.length !== 1 || !boundedString(codes[0], 1, MAX_CODE_BYTES)) {
1916
+ return reject("OIDC_CALLBACK_INVALID", 400);
1917
+ }
1918
+ try {
1919
+ await discovery();
1920
+ const tokens = await tokenRequest(new URLSearchParams({
1921
+ client_id: configuration.clientId,
1922
+ code: codes[0],
1923
+ code_verifier: transaction.verifier,
1924
+ grant_type: "authorization_code",
1925
+ redirect_uri: configuration.callbackUrl,
1926
+ resource: provider.resource
1927
+ }), true);
1928
+ if (tokens.idToken === null) {
1929
+ return reject("OIDC_TOKEN_INVALID", 502);
1930
+ }
1931
+ const providerKeys = await jwks();
1932
+ const identity = await verifiedIdToken({
1933
+ configuration,
1934
+ expectedNonce: transaction.nonce,
1935
+ idToken: tokens.idToken,
1936
+ jwks: providerKeys,
1937
+ nowMs: now(),
1938
+ ...transaction.version === 2 ? { freshStartedAtMs: transaction.issuedAtMs } : {}
1939
+ });
1940
+ if (identity === null) {
1941
+ return reject("OIDC_TOKEN_INVALID", 502);
1942
+ }
1943
+ const verifiedSession = await verifiedSessionFromAccessToken({
1944
+ accessToken: tokens.accessToken,
1945
+ configuration,
1946
+ environment,
1947
+ expectedAccountId: identity.suiteAccountId,
1948
+ expectedProfile: identity.profile,
1949
+ expectedSubject: identity.subject,
1950
+ jwks: providerKeys,
1951
+ nonce: transaction.nonce,
1952
+ nowMs: now(),
1953
+ profileTransition: "exact",
1954
+ refreshToken: tokens.refreshToken,
1955
+ requireFresh
1956
+ });
1957
+ if (verifiedSession === null) {
1958
+ return reject("OIDC_TOKEN_INVALID", 502);
1959
+ }
1960
+ const session = {
1961
+ ...verifiedSession,
1962
+ pendingEntitlementReceipt: await entitlementReceipt(tokens.accessToken, verifiedSession.suiteAccountId)
1963
+ };
1964
+ const sessionCookie = await sealCookie(session, await key, "session", randomBytes);
1965
+ let authentication = null;
1966
+ if (transaction.version === 2) {
1967
+ const completedAtMs = now();
1968
+ const times = identity.freshTimes;
1969
+ const expiresAtMs = Math.min(transaction.expiresAtMs, times?.expiresAtMs ?? 0, session.accessTokenExpiresAtMs);
1970
+ if (times === null || !safeInteger2(completedAtMs) || completedAtMs < transaction.issuedAtMs || times.authenticatedAtMs > completedAtMs || !safeInteger2(expiresAtMs) || expiresAtMs <= completedAtMs) {
1971
+ return reject("OIDC_FRESH_AUTHENTICATION_INVALID", 502);
1972
+ }
1973
+ authentication = Object.freeze({
1974
+ authenticatedAtMs: times.authenticatedAtMs,
1975
+ context: transaction.context,
1976
+ expiresAtMs,
1977
+ startedAtMs: transaction.issuedAtMs,
1978
+ suiteAccountId: identity.suiteAccountId
1979
+ });
1980
+ }
1981
+ const response = createOidcContinuationResponse(transaction.returnTo, randomValue(24, randomBytes), [
1982
+ clear,
1983
+ setCookie(names.session, sessionCookie, names.secure, SESSION_TTL_MS / 1000)
1984
+ ]);
1985
+ return authentication === null ? { kind: "ordinary", response } : Object.freeze({ kind: "authenticated", authentication, response });
1986
+ } catch {
1987
+ return reject("OIDC_UPSTREAM_FAILED", 502);
1988
+ }
1989
+ }
1990
+ async function callback(request) {
1991
+ return (await completeAuthorization(request, false)).response;
1992
+ }
1993
+ async function completeFreshAuthentication(request) {
1994
+ const result = await completeAuthorization(request, true);
1995
+ return result.kind === "ordinary" ? { kind: "rejected", response: failure("OIDC_TRANSACTION_INVALID", 400) } : result;
1996
+ }
1997
+ async function currentSession(request) {
1998
+ if (!exactRequest(request, siteUrl, "/api/suite-auth/session", "GET")) {
1999
+ return failure("OIDC_SESSION_REJECTED", 403);
2000
+ }
2001
+ const session = await loadSession(request);
2002
+ if (session === null) {
2003
+ return jsonResponse({ kind: "signed_out" }, 200, [
2004
+ clearCookie(names.session, names.secure)
2005
+ ]);
2006
+ }
2007
+ const nowMs = now();
2008
+ const view = currentView(session, nowMs);
2009
+ return view === null || session.accessTokenExpiresAtMs - nowMs < SUITE_OIDC_EARLY_REFRESH_WINDOW_MS ? jsonResponse({ kind: "refresh_required" }, 200) : jsonResponse({ kind: "signed_in", session: view }, 200);
2010
+ }
2011
+ async function serverSessionState(request) {
2012
+ let requestOrigin;
2013
+ try {
2014
+ requestOrigin = new URL(request.url).origin;
2015
+ } catch {
2016
+ return null;
2017
+ }
2018
+ const fetchSite = request.headers.get("sec-fetch-site");
2019
+ if (requestOrigin !== siteUrl || fetchSite !== null && fetchSite.toLowerCase() !== "same-origin") {
2020
+ return null;
2021
+ }
2022
+ const session = await loadSession(request);
2023
+ const view = session === null ? null : currentView(session, now());
2024
+ if (session === null || view === null)
2025
+ return null;
2026
+ let verifiedEmail = null;
2027
+ if (suiteAccountsCurrentConsumerRequiresEmailOtp(consumer)) {
2028
+ let userInfo;
2029
+ try {
2030
+ userInfo = await providerJson(provider.userInfoAudience, {
2031
+ headers: {
2032
+ accept: "application/json",
2033
+ authorization: `Bearer ${session.accessToken}`
2034
+ },
2035
+ method: "GET"
2036
+ }, fetchImplementation, timeoutMs);
2037
+ } catch {
2038
+ return null;
2039
+ }
2040
+ if (!liveUserInfoMatchesSession(userInfo, configuration, session, view))
2041
+ return null;
2042
+ verifiedEmail = verifiedEmailFromUserInfo(userInfo);
2043
+ }
2044
+ return { session, verifiedEmail, view };
2045
+ }
2046
+ async function serverAccountSession(request) {
2047
+ const state = await serverSessionState(request);
2048
+ return state === null ? null : {
2049
+ accessToken: state.session.accessToken,
2050
+ accessTokenExpiresAtMs: state.session.accessTokenExpiresAtMs,
2051
+ suiteAccountId: state.view.suiteAccountId
2052
+ };
2053
+ }
2054
+ async function serverVerifiedAccountEmail(request) {
2055
+ const state = await serverSessionState(request);
2056
+ return state === null || state.verifiedEmail === null ? null : {
2057
+ accessTokenExpiresAtMs: state.session.accessTokenExpiresAtMs,
2058
+ email: state.verifiedEmail,
2059
+ suiteAccountId: state.view.suiteAccountId
2060
+ };
2061
+ }
2062
+ async function serverSession(request) {
2063
+ const state = await serverSessionState(request);
2064
+ return state?.view.profileComplete === true ? {
2065
+ accessToken: state.session.accessToken,
2066
+ accessTokenExpiresAtMs: state.session.accessTokenExpiresAtMs,
2067
+ suiteAccountId: state.view.suiteAccountId,
2068
+ username: state.view.username
2069
+ } : null;
2070
+ }
2071
+ async function serverVerifiedEmail(request) {
2072
+ const state = await serverSessionState(request);
2073
+ return state === null || state.verifiedEmail === null || state.view.profileComplete !== true ? null : {
2074
+ accessTokenExpiresAtMs: state.session.accessTokenExpiresAtMs,
2075
+ email: state.verifiedEmail,
2076
+ suiteAccountId: state.view.suiteAccountId,
2077
+ username: state.view.username
2078
+ };
2079
+ }
2080
+ async function acknowledgeEntitlementReceipt(request) {
2081
+ const clear = clearCookie(names.session, names.secure);
2082
+ if (!exactRequest(request, siteUrl, "/api/suite-auth/entitlements/ack", "POST")) {
2083
+ return failure("OIDC_ENTITLEMENT_ACK_REJECTED", 403);
2084
+ }
2085
+ const session = await loadSession(request);
2086
+ if (session === null) {
2087
+ return failure("OIDC_SESSION_INVALID", 401, [clear]);
2088
+ }
2089
+ let body;
2090
+ try {
2091
+ body = await readBoundedRequestJson(request, 512);
2092
+ } catch {
2093
+ return failure("OIDC_ENTITLEMENT_ACK_INVALID", 400);
2094
+ }
2095
+ if (!isRecord3(body) || Object.keys(body).length !== 1 || !boundedString(body["signature"], 43, 43) || session.pendingEntitlementReceipt?.signature !== body["signature"]) {
2096
+ return failure("OIDC_ENTITLEMENT_ACK_INVALID", 400);
2097
+ }
2098
+ const updated = {
2099
+ ...session,
2100
+ pendingEntitlementReceipt: null
2101
+ };
2102
+ const sealed = await sealCookie(updated, await key, "session", randomBytes);
2103
+ return jsonResponse({ acknowledged: true }, 200, [
2104
+ setCookie(names.session, sealed, names.secure, SESSION_TTL_MS / 1000)
2105
+ ]);
2106
+ }
2107
+ async function linkReceipt(request) {
2108
+ if (!exactRequest(request, siteUrl, "/api/suite-auth/link-receipt", "POST")) {
2109
+ return failure("OIDC_LINK_RECEIPT_REJECTED", 403);
2110
+ }
2111
+ const session = await loadSession(request);
2112
+ if (session === null || session.accessTokenExpiresAtMs <= now()) {
2113
+ return failure("OIDC_REFRESH_REQUIRED", 401);
2114
+ }
2115
+ let value;
2116
+ try {
2117
+ value = await readBoundedRequestJson(request, 16384);
2118
+ } catch {
2119
+ return failure("OIDC_LINK_PROOF_INVALID", 400);
2120
+ }
2121
+ const proof = parseIdentityLinkReceiptRequest(value, consumer, environment, now());
2122
+ if (proof === null) {
2123
+ return failure("OIDC_LINK_PROOF_INVALID", 400);
2124
+ }
2125
+ try {
2126
+ const result = await requestIdentityLinkReceipt(session.accessToken, proof, session.suiteAccountId);
2127
+ return result.kind === "receipt" ? jsonResponse({ receipt: result.receipt }, 200) : failure("OIDC_LINK_RECEIPT_REJECTED", result.status);
2128
+ } catch {
2129
+ return failure("OIDC_LINK_RECEIPT_FAILED", 502);
2130
+ }
2131
+ }
2132
+ async function refreshSession(request) {
2133
+ const clear = clearCookie(names.session, names.secure);
2134
+ if (!exactRequest(request, siteUrl, "/api/suite-auth/refresh", "POST")) {
2135
+ return failure("OIDC_REFRESH_REJECTED", 403);
2136
+ }
2137
+ const session = await loadSession(request);
2138
+ if (session === null) {
2139
+ return failure("OIDC_SESSION_INVALID", 401, [clear]);
2140
+ }
2141
+ try {
2142
+ await discovery();
2143
+ const tokens = await tokenRequest(new URLSearchParams({
2144
+ client_id: configuration.clientId,
2145
+ grant_type: "refresh_token",
2146
+ refresh_token: session.refreshToken,
2147
+ resource: provider.resource
2148
+ }), false);
2149
+ if (tokens.refreshToken === session.refreshToken) {
2150
+ return failure("OIDC_REFRESH_ROTATION_INVALID", 502, [clear]);
2151
+ }
2152
+ const verifiedRefresh = await verifiedSessionFromAccessToken({
2153
+ accessToken: tokens.accessToken,
2154
+ configuration,
2155
+ environment,
2156
+ expectedAccountId: session.suiteAccountId,
2157
+ expectedProfile: session.profileComplete ? {
2158
+ profileComplete: true,
2159
+ profileRevision: "username-v1",
2160
+ username: session.username
2161
+ } : {
2162
+ profileComplete: false,
2163
+ profileRevision: session.profileRevision,
2164
+ username: null
2165
+ },
2166
+ expectedSubject: session.subject,
2167
+ jwks: await jwks(),
2168
+ nonce: session.nonce,
2169
+ nowMs: now(),
2170
+ profileTransition: "forward",
2171
+ refreshToken: tokens.refreshToken
2172
+ });
2173
+ if (verifiedRefresh === null) {
2174
+ return failure("OIDC_REFRESH_TOKEN_INVALID", 502, [clear]);
2175
+ }
2176
+ const refreshed = {
2177
+ ...verifiedRefresh,
2178
+ pendingEntitlementReceipt: await entitlementReceipt(tokens.accessToken, verifiedRefresh.suiteAccountId)
2179
+ };
2180
+ const view = currentView(refreshed, now());
2181
+ if (view === null) {
2182
+ return failure("OIDC_REFRESH_TOKEN_EXPIRED", 502, [clear]);
2183
+ }
2184
+ const sealed = await sealCookie(refreshed, await key, "session", randomBytes);
2185
+ return jsonResponse({ kind: "signed_in", session: view }, 200, [
2186
+ setCookie(names.session, sealed, names.secure, SESSION_TTL_MS / 1000)
2187
+ ]);
2188
+ } catch {
2189
+ return failure("OIDC_REFRESH_FAILED", 502, [clear]);
2190
+ }
2191
+ }
2192
+ async function signOut(request) {
2193
+ if (!exactRequest(request, siteUrl, "/api/suite-auth/sign-out", "POST")) {
2194
+ return failure("OIDC_SIGN_OUT_REJECTED", 403);
2195
+ }
2196
+ const session = await loadSession(request);
2197
+ if (session !== null) {
2198
+ try {
2199
+ await revokeRefreshToken(session.refreshToken);
2200
+ } catch {}
2201
+ }
2202
+ return jsonResponse({ kind: "signed_out" }, 200, [
2203
+ clearCookie(names.session, names.secure),
2204
+ clearCookie(names.transaction, names.secure)
2205
+ ]);
2206
+ }
2207
+ async function handle(request) {
2208
+ let path;
2209
+ try {
2210
+ path = new URL(request.url).pathname;
2211
+ } catch {
2212
+ return failure("OIDC_ROUTE_NOT_FOUND", 404);
2213
+ }
2214
+ if (request.method === "GET") {
2215
+ if (path === "/api/suite-auth/start")
2216
+ return await start(request);
2217
+ if (path === "/api/suite-auth/callback")
2218
+ return await callback(request);
2219
+ if (path === "/api/suite-auth/session") {
2220
+ return await currentSession(request);
2221
+ }
2222
+ }
2223
+ if (request.method === "POST") {
2224
+ if (path === "/api/suite-auth/refresh") {
2225
+ return await refreshSession(request);
2226
+ }
2227
+ if (path === "/api/suite-auth/sign-out")
2228
+ return await signOut(request);
2229
+ if (path === "/api/suite-auth/link-receipt") {
2230
+ return await linkReceipt(request);
2231
+ }
2232
+ if (path === "/api/suite-auth/entitlements/ack") {
2233
+ return await acknowledgeEntitlementReceipt(request);
2234
+ }
2235
+ }
2236
+ return failure("OIDC_ROUTE_NOT_FOUND", 404);
2237
+ }
2238
+ return Object.freeze({
2239
+ acknowledgeEntitlementReceipt,
2240
+ callback,
2241
+ completeFreshAuthentication,
2242
+ configuration,
2243
+ currentSession,
2244
+ handle,
2245
+ linkReceipt,
2246
+ refreshSession,
2247
+ serverAccountSession,
2248
+ serverVerifiedAccountEmail,
2249
+ serverSession,
2250
+ serverVerifiedEmail,
2251
+ signOut,
2252
+ start,
2253
+ startFreshAuthentication
2254
+ });
2255
+ }
2256
+
2257
+ // src/oidc-surface-server.ts
2258
+ function suiteEnvironmentForConsumerOrigin(consumer, siteUrl) {
2259
+ if (siteUrl === undefined)
2260
+ return null;
2261
+ return getSuiteAccountsCurrentConsumerEnvironment(consumer, "production")?.siteUrl === siteUrl ? "production" : null;
2262
+ }
2263
+ function processEnvironment() {
2264
+ return {
2265
+ NEXT_PUBLIC_VERCEL_SURFACE_ORIGIN: process.env.NEXT_PUBLIC_VERCEL_SURFACE_ORIGIN,
2266
+ NEXT_PUBLIC_SITE_URL: process.env.NEXT_PUBLIC_SITE_URL,
2267
+ SUITE_IDENTITY_RECEIPT_KEY_VERSION: process.env.SUITE_IDENTITY_RECEIPT_KEY_VERSION,
2268
+ SUITE_OIDC_COOKIE_SECRET: process.env.SUITE_OIDC_COOKIE_SECRET
2269
+ };
2270
+ }
2271
+ function createSurfaceSuiteRelyingParty(consumer, injectedEnvironment = processEnvironment()) {
2272
+ const environment = suiteEnvironmentForConsumerOrigin(consumer, injectedEnvironment.NEXT_PUBLIC_SITE_URL);
2273
+ const cookieSecret = injectedEnvironment.SUITE_OIDC_COOKIE_SECRET;
2274
+ const receiptKeyVersion = isSuiteAccountsCurrentLinkedOidcConsumerId(consumer) ? injectedEnvironment.SUITE_IDENTITY_RECEIPT_KEY_VERSION : "identity-v1";
2275
+ if (injectedEnvironment.NEXT_PUBLIC_VERCEL_SURFACE_ORIGIN !== undefined || environment === null || cookieSecret === undefined || receiptKeyVersion === undefined) {
2276
+ return null;
2277
+ }
2278
+ try {
2279
+ return createSuiteOidcRelyingParty({
2280
+ consumer,
2281
+ cookieSecret,
2282
+ environment,
2283
+ receiptKeyVersion
2284
+ });
2285
+ } catch {
2286
+ return null;
2287
+ }
2288
+ }
2289
+ function unavailable() {
2290
+ return Response.json({
2291
+ error: {
2292
+ code: "SUITE_OIDC_UNAVAILABLE",
2293
+ message: "Suite sign-in is not configured for this surface.",
2294
+ retryable: false
2295
+ },
2296
+ schemaVersion: 1
2297
+ }, { headers: { "cache-control": "no-store" }, status: 503 });
2298
+ }
2299
+ function suiteOidcSurfaceHandler(consumer, options = {}) {
2300
+ return async (request) => {
2301
+ if (process.env.NEXT_PUBLIC_VERCEL_SURFACE_ORIGIN !== undefined) {
2302
+ return unavailable();
2303
+ }
2304
+ const relyingParty = options.createRelyingParty?.(request) ?? createSurfaceSuiteRelyingParty(consumer);
2305
+ return relyingParty === null ? unavailable() : await relyingParty.handle(request);
2306
+ };
2307
+ }
2308
+ async function suiteOidcSurfaceServerSession(consumer, request, injectedEnvironment) {
2309
+ const relyingParty = createSurfaceSuiteRelyingParty(consumer, injectedEnvironment);
2310
+ return relyingParty === null ? null : await relyingParty.serverSession(request);
2311
+ }
2312
+ async function suiteOidcSurfaceServerAccountSession(consumer, request, injectedEnvironment) {
2313
+ const relyingParty = createSurfaceSuiteRelyingParty(consumer, injectedEnvironment);
2314
+ return relyingParty === null ? null : await relyingParty.serverAccountSession(request);
2315
+ }
2316
+ async function suiteOidcSurfaceServerVerifiedAccountEmail(consumer, request, injectedEnvironment, options = {}) {
2317
+ const relyingParty = options.createRelyingParty?.(request) ?? createSurfaceSuiteRelyingParty(consumer, injectedEnvironment);
2318
+ return relyingParty === null ? null : await relyingParty.serverVerifiedAccountEmail(request);
2319
+ }
2320
+ async function suiteOidcSurfaceServerVerifiedEmail(consumer, request, injectedEnvironment, options = {}) {
2321
+ const relyingParty = options.createRelyingParty?.(request) ?? createSurfaceSuiteRelyingParty(consumer, injectedEnvironment);
2322
+ return relyingParty === null ? null : await relyingParty.serverVerifiedEmail(request);
2323
+ }
2324
+ export {
2325
+ suiteOidcSurfaceServerVerifiedEmail,
2326
+ suiteOidcSurfaceServerVerifiedAccountEmail,
2327
+ suiteOidcSurfaceServerSession,
2328
+ suiteOidcSurfaceServerAccountSession,
2329
+ suiteOidcSurfaceHandler,
2330
+ suiteEnvironmentForConsumerOrigin,
2331
+ createSurfaceSuiteRelyingParty
2332
+ };