@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
package/dist/index.js ADDED
@@ -0,0 +1,1133 @@
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/client-configuration.ts
382
+ import { err as err2, isRecord, ok as ok2 } from "@hraness/result";
383
+ var SUITE_ACCOUNTS_CLIENT_CONFIGURATION_VERSION = "suite-accounts-client-configuration-v1";
384
+ var SUITE_ACCOUNTS_WIRE_VERSION = "v1";
385
+ var BINDING_KEYS = [
386
+ "authMode",
387
+ "callbackUrl",
388
+ "clientId",
389
+ "consumer",
390
+ "environment",
391
+ "origin"
392
+ ];
393
+ function snapshotBinding(input) {
394
+ if (!isRecord(input))
395
+ return null;
396
+ try {
397
+ const descriptors = Object.getOwnPropertyDescriptors(input);
398
+ const keys = Reflect.ownKeys(descriptors);
399
+ if (keys.length !== BINDING_KEYS.length || keys.some((key) => typeof key !== "string" || !BINDING_KEYS.includes(key))) {
400
+ return null;
401
+ }
402
+ for (const key of BINDING_KEYS) {
403
+ const descriptor = descriptors[key];
404
+ if (descriptor === undefined || !("value" in descriptor))
405
+ return null;
406
+ }
407
+ return {
408
+ authMode: descriptors.authMode.value,
409
+ callbackUrl: descriptors.callbackUrl.value,
410
+ clientId: descriptors.clientId.value,
411
+ consumer: descriptors.consumer.value,
412
+ environment: descriptors.environment.value,
413
+ origin: descriptors.origin.value
414
+ };
415
+ } catch {
416
+ return null;
417
+ }
418
+ }
419
+ function freezeConfiguration(binding, authBasePath, provider) {
420
+ return deepFreeze({
421
+ authBasePath,
422
+ binding,
423
+ configurationVersion: SUITE_ACCOUNTS_CLIENT_CONFIGURATION_VERSION,
424
+ provider,
425
+ wireVersion: SUITE_ACCOUNTS_WIRE_VERSION
426
+ });
427
+ }
428
+ function createSuiteAccountsClientConfiguration(input) {
429
+ const binding = snapshotBinding(input);
430
+ if (binding === null)
431
+ return err2("invalid-binding");
432
+ if (!isSuiteAccountsCurrentConsumerId(binding.consumer)) {
433
+ return err2("invalid-consumer");
434
+ }
435
+ if (binding.environment !== "production") {
436
+ return err2("invalid-environment");
437
+ }
438
+ const consumer = binding.consumer;
439
+ const environment = binding.environment;
440
+ const registration = getSuiteAccountsCurrentConsumer(consumer);
441
+ const deployed = getSuiteAccountsCurrentConsumerEnvironment(consumer, environment);
442
+ if (deployed === null || binding.origin !== deployed.siteUrl) {
443
+ return err2("invalid-origin");
444
+ }
445
+ if (binding.authMode !== registration.auth.kind) {
446
+ return err2("invalid-auth-mode");
447
+ }
448
+ const oauth = suiteAccountsCurrentOidcClientRegistration(consumer, environment);
449
+ const expectedClientId = oauth?.clientId ?? null;
450
+ const expectedCallbackUrl = oauth?.callbackUrl ?? null;
451
+ if (binding.clientId !== expectedClientId) {
452
+ return err2("invalid-client-id");
453
+ }
454
+ if (binding.callbackUrl !== expectedCallbackUrl) {
455
+ return err2("invalid-callback-url");
456
+ }
457
+ return ok2(freezeConfiguration({
458
+ authMode: registration.auth.kind,
459
+ callbackUrl: expectedCallbackUrl,
460
+ clientId: expectedClientId,
461
+ consumer,
462
+ environment,
463
+ origin: deployed.siteUrl
464
+ }, registration.auth.basePath, suiteAccountsOidcProviderConfiguration(environment)));
465
+ }
466
+
467
+ // src/oidc-session-policy.ts
468
+ var SUITE_OIDC_EARLY_REFRESH_WINDOW_MS = 30000;
469
+
470
+ // src/identity/identifiers.ts
471
+ import { err as err3, ok as ok3 } from "@hraness/result";
472
+ var suiteAccountIdPattern = /^acct_[0-9a-f]{32}$/u;
473
+ var suiteInvoiceRefPattern = /^invref_[0-9a-f]{32}$/u;
474
+ 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;
475
+ function parseSuiteAccountId(value) {
476
+ return typeof value === "string" && suiteAccountIdPattern.test(value) ? ok3(value) : err3("invalid-suite-account-id");
477
+ }
478
+ function parseSuiteInvoiceRef(value) {
479
+ return typeof value === "string" && suiteInvoiceRefPattern.test(value) ? ok3(value) : err3("invalid-suite-invoice-ref");
480
+ }
481
+ function generateSuiteAccountId(randomUuid = () => crypto.randomUUID()) {
482
+ const uuid = randomUuid();
483
+ if (!uuidPattern.test(uuid)) {
484
+ throw new TypeError("The suite account ID source did not return a UUID.");
485
+ }
486
+ const candidate = `acct_${uuid.replaceAll("-", "").toLowerCase()}`;
487
+ const parsed = parseSuiteAccountId(candidate);
488
+ if (!parsed.ok) {
489
+ throw new TypeError("The suite account ID source produced an invalid value.");
490
+ }
491
+ return parsed.value;
492
+ }
493
+ function generateSuiteInvoiceRef(randomUuid = () => crypto.randomUUID()) {
494
+ const uuid = randomUuid();
495
+ if (!uuidPattern.test(uuid)) {
496
+ throw new TypeError("The suite invoice reference source did not return a UUID.");
497
+ }
498
+ const candidate = `invref_${uuid.replaceAll("-", "").toLowerCase()}`;
499
+ const parsed = parseSuiteInvoiceRef(candidate);
500
+ if (!parsed.ok) {
501
+ throw new TypeError("The suite invoice reference source produced an invalid value.");
502
+ }
503
+ return parsed.value;
504
+ }
505
+
506
+ // src/identity/usernames.ts
507
+ import { err as err4, ok as ok4 } from "@hraness/result";
508
+ var SUITE_USERNAME_MIN_LENGTH = 3;
509
+ var SUITE_USERNAME_MAX_LENGTH = 24;
510
+ var suiteUsernamePattern = /^[a-z0-9](?:[a-z0-9]|[-_](?=[a-z0-9]))*[a-z0-9]$/u;
511
+ var reservedSuiteUsernames = new Set([
512
+ "account",
513
+ "accounts",
514
+ "admin",
515
+ "api",
516
+ "auth",
517
+ "billing",
518
+ "design",
519
+ "docs",
520
+ "help",
521
+ "hraness",
522
+ "login",
523
+ "logout",
524
+ "new",
525
+ "newsletter",
526
+ "party",
527
+ "place",
528
+ "preview",
529
+ "pub",
530
+ "root",
531
+ "settings",
532
+ "social-image",
533
+ "source",
534
+ "sources",
535
+ "support",
536
+ "system",
537
+ "user",
538
+ "users",
539
+ "www"
540
+ ]);
541
+ function validateCanonicalSuiteUsername(value) {
542
+ if (value.length < SUITE_USERNAME_MIN_LENGTH) {
543
+ return err4("suite-username-too-short");
544
+ }
545
+ if (value.length > SUITE_USERNAME_MAX_LENGTH) {
546
+ return err4("suite-username-too-long");
547
+ }
548
+ if (!suiteUsernamePattern.test(value)) {
549
+ return err4("invalid-suite-username");
550
+ }
551
+ return ok4(value);
552
+ }
553
+ function normalizeSuiteUsername(value) {
554
+ if (typeof value !== "string")
555
+ return err4("invalid-suite-username");
556
+ for (let index = 0;index < value.length; index += 1) {
557
+ const code = value.charCodeAt(index);
558
+ if (code > 127 || code < 32 || code === 127) {
559
+ return err4("invalid-suite-username");
560
+ }
561
+ }
562
+ const trimmed = value.trim();
563
+ const parsed = validateCanonicalSuiteUsername(trimmed.toLowerCase());
564
+ if (!parsed.ok)
565
+ return parsed;
566
+ return reservedSuiteUsernames.has(parsed.value) ? err4("suite-username-reserved") : parsed;
567
+ }
568
+ function parseSuiteUsername(value) {
569
+ if (typeof value !== "string")
570
+ return err4("invalid-suite-username");
571
+ const parsed = validateCanonicalSuiteUsername(value);
572
+ return parsed.ok && parsed.value === value ? parsed : err4(parsed.ok ? "invalid-suite-username" : parsed.error);
573
+ }
574
+
575
+ // src/convex-browser-auth.ts
576
+ var SUITE_CONVEX_BROWSER_TOKEN_USE = "suite-convex-browser-v1";
577
+ var SUITE_CONVEX_BROWSER_TOKEN_TTL_MS = 5 * 60000;
578
+ var SUITE_CONVEX_BROWSER_PARENT_REFRESH_WINDOW_MS = SUITE_OIDC_EARLY_REFRESH_WINDOW_MS;
579
+ var SUITE_CONVEX_BROWSER_TOKEN_PATH = "/api/convex-auth/token";
580
+ var SUITE_CONVEX_BROWSER_JWKS_PATH = "/api/convex-auth/jwks";
581
+ var SUITE_CONVEX_BROWSER_ISSUER_PATH = "/api/convex-auth";
582
+ var SUITE_CONVEX_BROWSER_AUDIENCE_PATH = "/convex";
583
+ var SUITE_CONVEX_BROWSER_CONSUMER_IDS = deepFreeze([
584
+ "alt"
585
+ ]);
586
+ function isRecord2(value) {
587
+ return typeof value === "object" && value !== null && !Array.isArray(value);
588
+ }
589
+ function enabledConsumer(value) {
590
+ return typeof value === "string" && SUITE_CONVEX_BROWSER_CONSUMER_IDS.includes(value);
591
+ }
592
+ function suiteConvexBrowserConfiguration(consumer, environment) {
593
+ if (!enabledConsumer(consumer)) {
594
+ throw new Error("The suite consumer has no Convex browser-token grant.");
595
+ }
596
+ const consumerEnvironment = getSuiteAccountsCurrentConsumerEnvironment(consumer, environment);
597
+ if (consumerEnvironment === null) {
598
+ throw new Error("The suite consumer is unavailable in this environment.");
599
+ }
600
+ const siteUrl = consumerEnvironment.siteUrl;
601
+ const registration = suiteAccountsCurrentOidcClientRegistration(consumer, environment);
602
+ if (registration === null) {
603
+ throw new Error("The suite consumer has no OIDC client registration.");
604
+ }
605
+ const suiteProvider = suiteAccountsOidcProviderConfiguration(environment);
606
+ return deepFreeze({
607
+ audience: new URL(SUITE_CONVEX_BROWSER_AUDIENCE_PATH, siteUrl).href,
608
+ clientId: registration.clientId,
609
+ consumer,
610
+ environment,
611
+ issuer: new URL(SUITE_CONVEX_BROWSER_ISSUER_PATH, siteUrl).href,
612
+ jwksEndpoint: new URL(SUITE_CONVEX_BROWSER_JWKS_PATH, siteUrl).href,
613
+ siteUrl,
614
+ suiteIssuer: suiteProvider.issuer,
615
+ tokenEndpoint: new URL(SUITE_CONVEX_BROWSER_TOKEN_PATH, siteUrl).href
616
+ });
617
+ }
618
+ function suiteConvexBrowserEnvironmentForOrigin(consumer, value) {
619
+ if (typeof value !== "string" || !enabledConsumer(consumer))
620
+ return null;
621
+ return getSuiteAccountsCurrentConsumerEnvironment(consumer, "production")?.siteUrl === value ? "production" : null;
622
+ }
623
+ function suiteConvexBrowserAuthConfig(consumer, environment) {
624
+ const configuration = suiteConvexBrowserConfiguration(consumer, environment);
625
+ return deepFreeze({
626
+ providers: [
627
+ {
628
+ algorithm: "ES256",
629
+ applicationID: configuration.audience,
630
+ issuer: configuration.issuer,
631
+ jwks: configuration.jwksEndpoint,
632
+ type: "customJwt"
633
+ }
634
+ ]
635
+ });
636
+ }
637
+ function parseSuiteConvexBrowserIdentity(value, configuration) {
638
+ if (!isRecord2(value))
639
+ return { error: "invalid-identity", ok: false };
640
+ if (value["issuer"] !== configuration.issuer) {
641
+ return { error: "invalid-issuer", ok: false };
642
+ }
643
+ if (value["suite_client_id"] !== configuration.clientId || value["suite_issuer"] !== configuration.suiteIssuer) {
644
+ return { error: "invalid-client", ok: false };
645
+ }
646
+ if (value["token_use"] !== SUITE_CONVEX_BROWSER_TOKEN_USE) {
647
+ return { error: "invalid-token-use", ok: false };
648
+ }
649
+ if (value["profile_complete"] !== true || value["profile_revision"] !== "username-v1") {
650
+ return { error: "invalid-profile", ok: false };
651
+ }
652
+ const account = parseSuiteAccountId(value["suite_account_id"]);
653
+ const subject = parseSuiteAccountId(value["subject"]);
654
+ if (!account.ok || !subject.ok || account.value !== subject.value) {
655
+ return { error: "invalid-subject", ok: false };
656
+ }
657
+ const username = parseSuiteUsername(value["username"]);
658
+ if (!username.ok)
659
+ return { error: "invalid-profile", ok: false };
660
+ return deepFreeze({
661
+ ok: true,
662
+ value: {
663
+ issuer: configuration.issuer,
664
+ subject: subject.value,
665
+ suiteAccountId: account.value,
666
+ username: username.value
667
+ }
668
+ });
669
+ }
670
+
671
+ // src/oidc-device-code.ts
672
+ var SUITE_OIDC_DEVICE_CODE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
673
+ var MIN_DEVICE_CODE_INTERVAL_MS = 1000;
674
+ var MAX_DEVICE_CODE_INTERVAL_MS = 600000;
675
+ var MAX_USER_CODE_LENGTH = 256;
676
+ var MAX_DEVICE_CODE_LENGTH = 2048;
677
+ var MAX_VERIFICATION_URI_LENGTH = 4096;
678
+ function isRecord3(value) {
679
+ return typeof value === "object" && value !== null && !Array.isArray(value);
680
+ }
681
+ function boundedString(value, minimum, maximum) {
682
+ return typeof value === "string" && value.length >= minimum && value.length <= maximum && !value.includes("\x00");
683
+ }
684
+ function safeInteger(value) {
685
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
686
+ }
687
+ function containsControlCharacter(value) {
688
+ for (const character of value) {
689
+ const codePoint = character.codePointAt(0) ?? -1;
690
+ if (codePoint <= 31 || codePoint === 127)
691
+ return true;
692
+ }
693
+ return false;
694
+ }
695
+ function normalizeScope(value) {
696
+ if (value === undefined || value === null)
697
+ return null;
698
+ if (typeof value !== "string")
699
+ return null;
700
+ const trimmed = value.trim();
701
+ if (trimmed.length === 0)
702
+ return null;
703
+ if (trimmed.length > 4096 || containsControlCharacter(trimmed))
704
+ return null;
705
+ return trimmed;
706
+ }
707
+ function absoluteHttpsUri(value) {
708
+ if (typeof value !== "string" || value.length === 0 || value.length > MAX_VERIFICATION_URI_LENGTH) {
709
+ return null;
710
+ }
711
+ try {
712
+ const url = new URL(value);
713
+ if (url.protocol !== "https:" && url.protocol !== "http:")
714
+ return null;
715
+ if (url.username !== "" || url.password !== "")
716
+ return null;
717
+ if (url.hash !== "")
718
+ return null;
719
+ return url.href;
720
+ } catch {
721
+ return null;
722
+ }
723
+ }
724
+ function parseDeviceAuthorizationResponse(value, nowMs) {
725
+ if (!isRecord3(value))
726
+ return null;
727
+ const deviceCode = value["device_code"];
728
+ const userCode = value["user_code"];
729
+ const verificationUri = value["verification_uri"];
730
+ const verificationUriComplete = value["verification_uri_complete"];
731
+ const expiresIn = value["expires_in"];
732
+ const interval = value["interval"];
733
+ if (!boundedString(deviceCode, 1, MAX_DEVICE_CODE_LENGTH) || !boundedString(userCode, 1, MAX_USER_CODE_LENGTH) || !boundedString(verificationUri, 1, MAX_VERIFICATION_URI_LENGTH) || !safeInteger(expiresIn) || expiresIn <= 0 || expiresIn > 86400 || !safeInteger(interval) || interval < 1 || interval > MAX_DEVICE_CODE_INTERVAL_MS / 1000) {
734
+ return null;
735
+ }
736
+ const verificationUriCompleteValue = verificationUriComplete === undefined || verificationUriComplete === null ? null : absoluteHttpsUri(verificationUriComplete);
737
+ if (verificationUriComplete !== undefined && verificationUriComplete !== null && verificationUriCompleteValue === null) {
738
+ return null;
739
+ }
740
+ const intervalMs = Math.max(MIN_DEVICE_CODE_INTERVAL_MS, interval * 1000);
741
+ return deepFreeze({
742
+ deviceCode,
743
+ userCode,
744
+ verificationUri,
745
+ verificationUriComplete: verificationUriCompleteValue,
746
+ expiresAtMs: nowMs + expiresIn * 1000,
747
+ intervalMs
748
+ });
749
+ }
750
+ function parseDeviceTokenResponse(value, nowMs) {
751
+ if (!isRecord3(value))
752
+ return null;
753
+ const error = value["error"];
754
+ const errorDescription = value["error_description"];
755
+ if (error !== undefined) {
756
+ if (typeof error !== "string" || error.length === 0 || error.length > 256) {
757
+ return null;
758
+ }
759
+ const normalizedError = normalizeScope(error);
760
+ if (normalizedError === null || normalizedError !== error)
761
+ return null;
762
+ const normalizedDescription = errorDescription === undefined || errorDescription === null ? null : normalizeScope(errorDescription);
763
+ if (errorDescription !== undefined && errorDescription !== null && normalizedDescription === null) {
764
+ return null;
765
+ }
766
+ switch (normalizedError) {
767
+ case "authorization_pending":
768
+ return deepFreeze({ kind: "authorization_pending", intervalMs: 5000 });
769
+ case "slow_down":
770
+ return deepFreeze({ kind: "slow_down", intervalMs: 1e4 });
771
+ case "access_denied":
772
+ return deepFreeze({ kind: "access_denied" });
773
+ case "expired_token":
774
+ return deepFreeze({ kind: "expired_token" });
775
+ default:
776
+ return deepFreeze({
777
+ kind: "error",
778
+ error: normalizedError,
779
+ errorDescription: normalizedDescription
780
+ });
781
+ }
782
+ }
783
+ const accessToken = value["access_token"];
784
+ const tokenType = value["token_type"];
785
+ const expiresIn = value["expires_in"];
786
+ const refreshToken = value["refresh_token"];
787
+ const idToken = value["id_token"];
788
+ const scope = value["scope"];
789
+ if (!boundedString(accessToken, 1, 16384) || !boundedString(tokenType, 1, 64) || !safeInteger(expiresIn) || expiresIn <= 0 || expiresIn > 86400) {
790
+ return null;
791
+ }
792
+ const normalizedRefreshToken = refreshToken === undefined || refreshToken === null ? null : boundedString(refreshToken, 1, 16384) ? refreshToken : null;
793
+ if (refreshToken !== undefined && refreshToken !== null && normalizedRefreshToken === null) {
794
+ return null;
795
+ }
796
+ const normalizedIdToken = idToken === undefined || idToken === null ? null : boundedString(idToken, 1, 16384) ? idToken : null;
797
+ if (idToken !== undefined && idToken !== null && normalizedIdToken === null) {
798
+ return null;
799
+ }
800
+ const normalizedScope = normalizeScope(scope);
801
+ if (scope !== undefined && scope !== null && normalizedScope === null) {
802
+ return null;
803
+ }
804
+ return deepFreeze({
805
+ kind: "token",
806
+ accessToken,
807
+ tokenType: tokenType.toLowerCase(),
808
+ expiresAtMs: nowMs + expiresIn * 1000,
809
+ refreshToken: normalizedRefreshToken,
810
+ idToken: normalizedIdToken,
811
+ scope: normalizedScope
812
+ });
813
+ }
814
+ function deviceAuthorizationBody(request) {
815
+ const params = new URLSearchParams;
816
+ params.set("client_id", request.clientId);
817
+ if (request.scopes !== undefined && request.scopes.length > 0) {
818
+ for (const scope of request.scopes) {
819
+ if (typeof scope !== "string" || scope.length === 0 || scope.length > 256 || /\s/u.test(scope) || containsControlCharacter(scope)) {
820
+ throw new Error("Invalid device authorization scope.");
821
+ }
822
+ }
823
+ params.set("scope", request.scopes.join(" "));
824
+ }
825
+ return params.toString();
826
+ }
827
+ async function readBoundedJson(response) {
828
+ const contentType = response.headers.get("content-type")?.split(";", 1)[0]?.trim().toLowerCase();
829
+ if (contentType !== "application/json") {
830
+ throw new Error("The device-code response was not application/json.");
831
+ }
832
+ const text = await response.text();
833
+ if (text.length > 64 * 1024) {
834
+ throw new Error("The device-code response body was too large.");
835
+ }
836
+ return JSON.parse(text);
837
+ }
838
+ async function initiateSuiteOidcDeviceAuthorization(configuration, request, dependencies = {}) {
839
+ const fetcher = dependencies.fetch ?? globalThis.fetch;
840
+ const now = dependencies.now?.().getTime() ?? Date.now();
841
+ const response = await fetcher(configuration.deviceAuthorizationEndpoint, {
842
+ body: deviceAuthorizationBody(request),
843
+ headers: {
844
+ accept: "application/json",
845
+ "content-type": "application/x-www-form-urlencoded"
846
+ },
847
+ method: "POST",
848
+ redirect: "error"
849
+ });
850
+ const body = await readBoundedJson(response);
851
+ if (!response.ok) {
852
+ throw new Error("The device authorization request failed.");
853
+ }
854
+ const parsed = parseDeviceAuthorizationResponse(body, now);
855
+ if (parsed === null) {
856
+ throw new Error("The device authorization response was invalid.");
857
+ }
858
+ const tokenRequest = {
859
+ clientId: request.clientId,
860
+ deviceCode: parsed.deviceCode
861
+ };
862
+ return {
863
+ poll: () => pollSuiteOidcDeviceToken(configuration.deviceTokenEndpoint, tokenRequest, {
864
+ ...dependencies.now === undefined ? {} : { now: dependencies.now },
865
+ fetch: fetcher
866
+ }),
867
+ response: parsed
868
+ };
869
+ }
870
+ function tokenRequestBody(request) {
871
+ const params = new URLSearchParams;
872
+ params.set("grant_type", SUITE_OIDC_DEVICE_CODE_GRANT_TYPE);
873
+ params.set("client_id", request.clientId);
874
+ params.set("device_code", request.deviceCode);
875
+ return params.toString();
876
+ }
877
+ async function pollSuiteOidcDeviceToken(tokenEndpoint, request, dependencies = {}) {
878
+ const fetcher = dependencies.fetch ?? globalThis.fetch;
879
+ const now = dependencies.now?.().getTime() ?? Date.now();
880
+ const response = await fetcher(tokenEndpoint, {
881
+ body: tokenRequestBody(request),
882
+ headers: {
883
+ accept: "application/json",
884
+ "content-type": "application/x-www-form-urlencoded"
885
+ },
886
+ method: "POST",
887
+ redirect: "error"
888
+ });
889
+ const body = await readBoundedJson(response);
890
+ const parsed = parseDeviceTokenResponse(body, now);
891
+ if (parsed === null) {
892
+ throw new Error("The device token response was invalid.");
893
+ }
894
+ return parsed;
895
+ }
896
+
897
+ // src/convex-url.ts
898
+ var LOCAL_HOSTNAMES = new Set(["127.0.0.1", "[::1]", "localhost"]);
899
+ function invalid(input, reason, message) {
900
+ return deepFreeze({ input, kind: "invalid", message, reason });
901
+ }
902
+ function parseConvexDeployment(value) {
903
+ if (typeof value !== "string" || value.trim() === "") {
904
+ return deepFreeze({ kind: "missing" });
905
+ }
906
+ const input = value.trim();
907
+ let parsed;
908
+ try {
909
+ parsed = new URL(input);
910
+ } catch {
911
+ return invalid(input, "not-a-url", "Use a complete Convex deployment URL.");
912
+ }
913
+ if (parsed.username !== "" || parsed.password !== "") {
914
+ return invalid(input, "credentials", "Deployment URLs cannot contain credentials.");
915
+ }
916
+ if (parsed.pathname !== "/" || parsed.search !== "" || parsed.hash !== "") {
917
+ return invalid(input, "not-an-origin", "Use the deployment origin without a path or query.");
918
+ }
919
+ const local = LOCAL_HOSTNAMES.has(parsed.hostname);
920
+ if (parsed.protocol !== "https:" && !(local && parsed.protocol === "http:")) {
921
+ return invalid(input, "insecure-remote", "Remote Convex deployments must use HTTPS.");
922
+ }
923
+ return deepFreeze({
924
+ kind: "ready",
925
+ origin: parsed.origin,
926
+ transport: local ? "local" : "cloud",
927
+ url: parsed.origin
928
+ });
929
+ }
930
+
931
+ // src/public-config.ts
932
+ var LOOPBACK_HOSTS = new Set(["127.0.0.1", "[::1]", "localhost"]);
933
+ var SUITE_ACCOUNTS_PUBLIC_ENVIRONMENT_KEYS = deepFreeze([
934
+ "NEXT_PUBLIC_ACCOUNTS_CONVEX_URL",
935
+ "NEXT_PUBLIC_ACCOUNTS_CONVEX_SITE_URL",
936
+ "NEXT_PUBLIC_SITE_URL"
937
+ ]);
938
+ function parseOrigin(value, field) {
939
+ let url;
940
+ try {
941
+ url = new URL(value);
942
+ } catch {
943
+ throw new Error(`${field} must be an absolute URL.`);
944
+ }
945
+ const loopback = LOOPBACK_HOSTS.has(url.hostname);
946
+ if (url.protocol !== "https:" && !(loopback && url.protocol === "http:")) {
947
+ throw new Error(`${field} must use HTTPS (HTTP is allowed only on loopback).`);
948
+ }
949
+ if (url.username !== "" || url.password !== "" || url.search !== "" || url.hash !== "" || url.pathname !== "" && url.pathname !== "/") {
950
+ throw new Error(`${field} must be a credential-free origin.`);
951
+ }
952
+ return new URL(url.origin);
953
+ }
954
+ function readyRemoteConfig(consumer, siteUrl, convexUrl, convexSiteUrl) {
955
+ const registration = getPublicConsumer(consumer);
956
+ const environment = "production";
957
+ const consumerEnvironment = getSuiteAccountsCurrentConsumerEnvironment(consumer, environment);
958
+ const deployment = getSuiteAccountsDeployment(environment);
959
+ if (consumerEnvironment?.siteUrl === siteUrl && deployment.convexUrl === convexUrl && deployment.convexSiteUrl === convexSiteUrl) {
960
+ return deepFreeze({
961
+ ...publicAuthConfiguration(registration.auth),
962
+ canonicalProductOrigin: siteUrl,
963
+ consumer,
964
+ convexSiteUrl,
965
+ convexUrl,
966
+ environment,
967
+ kind: "ready",
968
+ siteUrl,
969
+ surfaceOrigin: siteUrl
970
+ });
971
+ }
972
+ return null;
973
+ }
974
+ function publicAuthConfiguration(auth) {
975
+ switch (auth.kind) {
976
+ case "authority":
977
+ return { authBasePath: auth.basePath, authMode: auth.kind };
978
+ case "oidc-rp":
979
+ return { authBasePath: auth.basePath, authMode: auth.kind };
980
+ case "proxy":
981
+ return { authBasePath: auth.basePath, authMode: auth.kind };
982
+ }
983
+ }
984
+ function getPublicConsumer(consumer) {
985
+ return isSuiteAccountsCurrentConsumerId(consumer) ? getSuiteAccountsCurrentConsumer(consumer) : getSuiteAccountsConsumer(consumer);
986
+ }
987
+ function parseSuiteAccountsPublicConfig(consumer, environment) {
988
+ const missing = SUITE_ACCOUNTS_PUBLIC_ENVIRONMENT_KEYS.filter((name) => {
989
+ const value = environment[name];
990
+ return typeof value !== "string" || value.trim() === "";
991
+ });
992
+ if (missing.length > 0)
993
+ return deepFreeze({ kind: "missing", missing });
994
+ const site = parseOrigin(environment.NEXT_PUBLIC_SITE_URL, "NEXT_PUBLIC_SITE_URL");
995
+ const convexSite = parseOrigin(environment.NEXT_PUBLIC_ACCOUNTS_CONVEX_SITE_URL, "NEXT_PUBLIC_ACCOUNTS_CONVEX_SITE_URL");
996
+ const deployment = parseConvexDeployment(environment.NEXT_PUBLIC_ACCOUNTS_CONVEX_URL);
997
+ if (deployment.kind !== "ready") {
998
+ throw new Error(deployment.kind === "invalid" ? `NEXT_PUBLIC_ACCOUNTS_CONVEX_URL is invalid: ${deployment.message}` : "NEXT_PUBLIC_ACCOUNTS_CONVEX_URL is required.");
999
+ }
1000
+ const convex = parseOrigin(deployment.url, "NEXT_PUBLIC_ACCOUNTS_CONVEX_URL");
1001
+ const loopback = [site, convex, convexSite].map((url) => LOOPBACK_HOSTS.has(url.hostname));
1002
+ if (loopback.every(Boolean)) {
1003
+ if (consumer === "aicharts") {
1004
+ throw new Error("AI Charts authentication requires its registered production origin.");
1005
+ }
1006
+ if (deployment.transport !== "local" || site.hostname !== convex.hostname || site.hostname !== convexSite.hostname) {
1007
+ throw new Error("Local consumer and Accounts endpoints must use the same loopback host.");
1008
+ }
1009
+ const registration = getPublicConsumer(consumer);
1010
+ return deepFreeze({
1011
+ ...publicAuthConfiguration(registration.auth),
1012
+ canonicalProductOrigin: site.origin,
1013
+ consumer,
1014
+ convexSiteUrl: convexSite.origin,
1015
+ convexUrl: convex.origin,
1016
+ environment: "local",
1017
+ kind: "ready",
1018
+ siteUrl: site.origin,
1019
+ surfaceOrigin: site.origin
1020
+ });
1021
+ }
1022
+ if (loopback.some(Boolean)) {
1023
+ throw new Error("Consumer and Accounts endpoints cannot mix local and remote environments.");
1024
+ }
1025
+ const previewSurfaceValue = environment.NEXT_PUBLIC_VERCEL_SURFACE_ORIGIN;
1026
+ if (previewSurfaceValue !== undefined) {
1027
+ const previewSurface = parseOrigin(previewSurfaceValue, "NEXT_PUBLIC_VERCEL_SURFACE_ORIGIN");
1028
+ if (!previewSurface.hostname.endsWith(".vercel.app")) {
1029
+ throw new Error("NEXT_PUBLIC_VERCEL_SURFACE_ORIGIN must use a generated .vercel.app origin.");
1030
+ }
1031
+ const production = readyRemoteConfig(consumer, site.origin, convex.origin, convexSite.origin);
1032
+ if (production === null) {
1033
+ throw new Error(`${getPublicConsumer(consumer).displayName} and Accounts endpoints ` + "do not match the production deployment.");
1034
+ }
1035
+ return deepFreeze({
1036
+ canonicalProductOrigin: site.origin,
1037
+ environment: "production",
1038
+ kind: "unavailable",
1039
+ message: "Suite authentication is unavailable on generated Vercel Preview origins.",
1040
+ surfaceOrigin: previewSurface.origin
1041
+ });
1042
+ }
1043
+ const remote = readyRemoteConfig(consumer, site.origin, convex.origin, convexSite.origin);
1044
+ if (remote !== null)
1045
+ return remote;
1046
+ throw new Error(`${getPublicConsumer(consumer).displayName} and Accounts endpoints ` + "do not match an owned deployment environment.");
1047
+ }
1048
+ function suiteAccountsPublicConfigFromEnvironment(consumer, environment = {
1049
+ NEXT_PUBLIC_VERCEL_SURFACE_ORIGIN: process.env.NEXT_PUBLIC_VERCEL_SURFACE_ORIGIN,
1050
+ NEXT_PUBLIC_ACCOUNTS_CONVEX_SITE_URL: process.env.NEXT_PUBLIC_ACCOUNTS_CONVEX_SITE_URL,
1051
+ NEXT_PUBLIC_ACCOUNTS_CONVEX_URL: process.env.NEXT_PUBLIC_ACCOUNTS_CONVEX_URL,
1052
+ NEXT_PUBLIC_SITE_URL: process.env.NEXT_PUBLIC_SITE_URL
1053
+ }) {
1054
+ try {
1055
+ return parseSuiteAccountsPublicConfig(consumer, environment);
1056
+ } catch (error) {
1057
+ return deepFreeze({
1058
+ kind: "invalid",
1059
+ message: error instanceof Error ? error.message : "Suite Accounts configuration is invalid."
1060
+ });
1061
+ }
1062
+ }
1063
+ export {
1064
+ suiteConvexBrowserEnvironmentForOrigin,
1065
+ suiteConvexBrowserConfiguration,
1066
+ suiteConvexBrowserAuthConfig,
1067
+ suiteAccountsPublicConfigFromEnvironment,
1068
+ suiteAccountsOidcProviderConfiguration,
1069
+ suiteAccountsOidcClientRequiresEmailOtp,
1070
+ suiteAccountsOidcClientRegistration,
1071
+ suiteAccountsCurrentOidcClientRequiresEmailOtp,
1072
+ suiteAccountsCurrentOidcClientRegistration,
1073
+ suiteAccountsCurrentDeviceCodeClientAllowed,
1074
+ suiteAccountsCurrentDeviceClientRegistration,
1075
+ suiteAccountsCurrentConsumerRequiresEmailOtp,
1076
+ suiteAccountsConsumerRequiresEmailOtp,
1077
+ suiteAccountsCentralUrl,
1078
+ suiteAccountsBillingReturnUrl,
1079
+ pollSuiteOidcDeviceToken,
1080
+ parseSuiteConvexBrowserIdentity,
1081
+ parseSuiteAccountsPublicConfig,
1082
+ parseDeviceTokenResponse,
1083
+ parseDeviceAuthorizationResponse,
1084
+ isSuiteAccountsRegisteredConsumerId,
1085
+ isSuiteAccountsOidcConsumerId,
1086
+ isSuiteAccountsOAuthConsumerId,
1087
+ isSuiteAccountsLinkedOidcConsumerId,
1088
+ isSuiteAccountsCurrentOidcConsumerId,
1089
+ isSuiteAccountsCurrentOAuthConsumerId,
1090
+ isSuiteAccountsCurrentLinkedOidcConsumerId,
1091
+ isSuiteAccountsCurrentDeviceClientId,
1092
+ isSuiteAccountsCurrentConsumerId,
1093
+ isSuiteAccountsCurrentBrowserDeviceCodeConsumerId,
1094
+ isSuiteAccountsConsumerId,
1095
+ isSuiteAccountsActiveConsumerId,
1096
+ initiateSuiteOidcDeviceAuthorization,
1097
+ getSuiteAccountsDeployment,
1098
+ getSuiteAccountsCurrentConsumerEnvironment,
1099
+ getSuiteAccountsCurrentConsumer,
1100
+ getSuiteAccountsConsumerEnvironment,
1101
+ getSuiteAccountsConsumer,
1102
+ createSuiteAccountsClientConfiguration,
1103
+ SUITE_OIDC_EARLY_REFRESH_WINDOW_MS,
1104
+ SUITE_OIDC_DEVICE_CODE_GRANT_TYPE,
1105
+ SUITE_EMAIL_OTP_REQUIRED_OIDC_CONSUMER_IDS,
1106
+ SUITE_CONVEX_BROWSER_TOKEN_USE,
1107
+ SUITE_CONVEX_BROWSER_TOKEN_TTL_MS,
1108
+ SUITE_CONVEX_BROWSER_TOKEN_PATH,
1109
+ SUITE_CONVEX_BROWSER_PARENT_REFRESH_WINDOW_MS,
1110
+ SUITE_CONVEX_BROWSER_JWKS_PATH,
1111
+ SUITE_CONVEX_BROWSER_ISSUER_PATH,
1112
+ SUITE_CONVEX_BROWSER_CONSUMER_IDS,
1113
+ SUITE_CONVEX_BROWSER_AUDIENCE_PATH,
1114
+ SUITE_CONSUMER_IDS,
1115
+ SUITE_ACCOUNTS_WIRE_VERSION,
1116
+ SUITE_ACCOUNTS_REMOTE_ENVIRONMENTS,
1117
+ SUITE_ACCOUNTS_REGISTERED_CONSUMER_IDS,
1118
+ SUITE_ACCOUNTS_PUBLIC_ENVIRONMENT_KEYS,
1119
+ SUITE_ACCOUNTS_OAUTH_RESOURCE,
1120
+ SUITE_ACCOUNTS_LINKED_OIDC_CONSUMER_IDS,
1121
+ SUITE_ACCOUNTS_DEPLOYMENTS,
1122
+ SUITE_ACCOUNTS_CURRENT_ORIGIN_OVERRIDES,
1123
+ SUITE_ACCOUNTS_CURRENT_LINKED_OIDC_CONSUMER_IDS,
1124
+ SUITE_ACCOUNTS_CURRENT_EMAIL_OTP_REQUIRED_OIDC_CONSUMER_IDS,
1125
+ SUITE_ACCOUNTS_CURRENT_DEVICE_CLIENT_IDS,
1126
+ SUITE_ACCOUNTS_CURRENT_DEVICE_CLIENTS,
1127
+ SUITE_ACCOUNTS_CURRENT_CONSUMER_IDS,
1128
+ SUITE_ACCOUNTS_CURRENT_CONSUMERS,
1129
+ SUITE_ACCOUNTS_CURRENT_BROWSER_DEVICE_CODE_CONSUMER_IDS,
1130
+ SUITE_ACCOUNTS_CONSUMERS,
1131
+ SUITE_ACCOUNTS_CLIENT_CONFIGURATION_VERSION,
1132
+ SUITE_ACCOUNTS_ACTIVE_CONSUMER_IDS
1133
+ };