@iqauth/sdk 2.7.0 → 2.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/dist/browser-session.d.mts +3 -3
  2. package/dist/browser-session.d.ts +3 -3
  3. package/dist/browser-session.js +31 -5
  4. package/dist/browser-session.mjs +1 -1
  5. package/dist/browser.d.mts +3 -3
  6. package/dist/browser.d.ts +3 -3
  7. package/dist/browser.js +23 -3
  8. package/dist/browser.mjs +1 -1
  9. package/dist/{chunk-YVALAG3B.mjs → chunk-25SSYDIP.mjs} +1 -1
  10. package/dist/{chunk-RTJAIBXY.mjs → chunk-4V7FKOTG.mjs} +23 -3
  11. package/dist/{chunk-SL3KRS4W.mjs → chunk-CIJORODR.mjs} +23 -1
  12. package/dist/chunk-JRDVUWAL.mjs +46 -0
  13. package/dist/{chunk-5T7GHBX6.mjs → chunk-TLET552H.mjs} +36 -0
  14. package/dist/{chunk-PMAFENVI.mjs → chunk-VYQ3ETCK.mjs} +27 -12
  15. package/dist/{chunk-RR2MGPTK.mjs → chunk-WHT6WKTY.mjs} +539 -83
  16. package/dist/{chunk-RUJXRTEW.mjs → chunk-WSH4SW7F.mjs} +122 -8
  17. package/dist/{chunk-JXQI62A7.mjs → chunk-ZLJPABB7.mjs} +31 -5
  18. package/dist/{client-BGFnBpfc.d.mts → client-D8L-PaWr.d.mts} +14 -4
  19. package/dist/{client-CDQ21LvW.d.ts → client-DkPL0EPZ.d.ts} +14 -4
  20. package/dist/{express-Piv2WhWM.d.ts → express-Budysq4h.d.ts} +2 -2
  21. package/dist/{express-CVNQEkOr.d.mts → express-DDTA3qV1.d.mts} +2 -2
  22. package/dist/express.d.mts +5 -5
  23. package/dist/express.d.ts +5 -5
  24. package/dist/express.js +217 -36
  25. package/dist/express.mjs +38 -26
  26. package/dist/fastify.d.mts +10 -2
  27. package/dist/fastify.d.ts +10 -2
  28. package/dist/fastify.js +260 -16
  29. package/dist/fastify.mjs +80 -5
  30. package/dist/hono.d.mts +10 -2
  31. package/dist/hono.d.ts +10 -2
  32. package/dist/hono.js +240 -16
  33. package/dist/hono.mjs +60 -5
  34. package/dist/{index-5KSZEnDe.d.ts → index-Cko-d5po.d.mts} +227 -5
  35. package/dist/{index-CKoZHAoc.d.mts → index-RNqwEcmY.d.ts} +227 -5
  36. package/dist/index.d.mts +5 -5
  37. package/dist/index.d.ts +5 -5
  38. package/dist/index.js +149 -26
  39. package/dist/index.mjs +5 -5
  40. package/dist/locales.d.mts +1 -1
  41. package/dist/locales.d.ts +1 -1
  42. package/dist/locales.js +36 -0
  43. package/dist/locales.mjs +1 -1
  44. package/dist/mobile.d.mts +3 -3
  45. package/dist/mobile.d.ts +3 -3
  46. package/dist/mobile.js +31 -5
  47. package/dist/mobile.mjs +1 -1
  48. package/dist/next.d.mts +10 -2
  49. package/dist/next.d.ts +10 -2
  50. package/dist/next.js +212 -11
  51. package/dist/next.mjs +62 -4
  52. package/dist/{provisioningBridge-M5G47LWO.d.mts → provisioningBridge-BXPMZCLe.d.ts} +30 -2
  53. package/dist/{provisioningBridge-CGpMRie4.d.ts → provisioningBridge-IEycmsgb.d.mts} +30 -2
  54. package/dist/react-permissions.d.mts +4 -4
  55. package/dist/react-permissions.d.ts +4 -4
  56. package/dist/react-permissions.mjs +4 -3
  57. package/dist/react.d.mts +4 -4
  58. package/dist/react.d.ts +4 -4
  59. package/dist/react.js +570 -41
  60. package/dist/react.mjs +19 -5
  61. package/dist/server/handlers.d.mts +56 -5
  62. package/dist/server/handlers.d.ts +56 -5
  63. package/dist/server/handlers.js +123 -8
  64. package/dist/server/handlers.mjs +3 -1
  65. package/dist/server.d.mts +28 -8
  66. package/dist/server.d.ts +28 -8
  67. package/dist/server.js +176 -14
  68. package/dist/server.mjs +9 -4
  69. package/dist/service.d.mts +3 -3
  70. package/dist/service.d.ts +3 -3
  71. package/dist/service.js +31 -5
  72. package/dist/service.mjs +1 -1
  73. package/dist/{signIn-T-CZ6t6r.d.mts → signIn-CReqfXsh.d.mts} +18 -1
  74. package/dist/{signIn-BLFnz8SV.d.ts → signIn-Cfa1GTpO.d.ts} +18 -1
  75. package/dist/{tokens-Bqhmqq_R.d.ts → tokens-9F6ETrzk.d.ts} +1 -1
  76. package/dist/{tokens-CITeoG6P.d.mts → tokens-B06VtvUi.d.mts} +1 -1
  77. package/dist/{types-XOV9XPVi.d.mts → types-Bn8O-OEd.d.mts} +66 -2
  78. package/dist/{types-XOV9XPVi.d.ts → types-Bn8O-OEd.d.ts} +66 -2
  79. package/dist/{types-BdQ2lqfT.d.mts → types-DnU2LhXR.d.mts} +6 -0
  80. package/dist/{types-BdQ2lqfT.d.ts → types-DnU2LhXR.d.ts} +6 -0
  81. package/dist/webhooks.d.mts +22 -9
  82. package/dist/webhooks.d.ts +22 -9
  83. package/dist/webhooks.js +27 -12
  84. package/dist/webhooks.mjs +1 -1
  85. package/dist/ws.d.mts +2 -2
  86. package/dist/ws.d.ts +2 -2
  87. package/docs/guides/invitations.md +65 -0
  88. package/package.json +7 -2
@@ -1,7 +1,7 @@
1
- import { I as IQAuthBrowserSessionClientConfig, S as SessionUser } from './types-XOV9XPVi.mjs';
2
- import { I as IQAuthClient } from './client-BGFnBpfc.mjs';
1
+ import { I as IQAuthBrowserSessionClientConfig, S as SessionUser } from './types-Bn8O-OEd.mjs';
2
+ import { I as IQAuthClient } from './client-D8L-PaWr.mjs';
3
3
  export { E as ErrorCodes, I as IQAuthError } from './errors-Jl1Jtm-6.mjs';
4
- import './tokens-CITeoG6P.mjs';
4
+ import './tokens-B06VtvUi.mjs';
5
5
 
6
6
  declare class BrowserSessionIQAuthClient extends IQAuthClient {
7
7
  constructor(config: Omit<IQAuthBrowserSessionClientConfig, "environment">);
@@ -1,7 +1,7 @@
1
- import { I as IQAuthBrowserSessionClientConfig, S as SessionUser } from './types-XOV9XPVi.js';
2
- import { I as IQAuthClient } from './client-CDQ21LvW.js';
1
+ import { I as IQAuthBrowserSessionClientConfig, S as SessionUser } from './types-Bn8O-OEd.js';
2
+ import { I as IQAuthClient } from './client-DkPL0EPZ.js';
3
3
  export { E as ErrorCodes, I as IQAuthError } from './errors-Jl1Jtm-6.js';
4
- import './tokens-Bqhmqq_R.js';
4
+ import './tokens-9F6ETrzk.js';
5
5
 
6
6
  declare class BrowserSessionIQAuthClient extends IQAuthClient {
7
7
  constructor(config: Omit<IQAuthBrowserSessionClientConfig, "environment">);
@@ -335,17 +335,27 @@ function parseLoginResponse(data, browserSessionMode) {
335
335
  tenants: data.tenants
336
336
  };
337
337
  }
338
+ if (data.type === "scope_selection" && data.scopeSelectionToken && data.scopes && data.tenantId) {
339
+ return {
340
+ status: "scope_selection",
341
+ scopeSelectionToken: data.scopeSelectionToken,
342
+ tenantId: data.tenantId,
343
+ scopes: data.scopes
344
+ };
345
+ }
338
346
  throw new Error("Unexpected login response shape");
339
347
  }
340
348
  var AuthModule = class {
341
349
  constructor(http) {
342
350
  this.http = http;
343
351
  }
344
- async login(email, password) {
352
+ async login(email, password, opts) {
353
+ const body = { email, password };
354
+ if (opts?.scopeHint) body.scopeHint = opts.scopeHint;
345
355
  const data = await this.http.request(
346
356
  "POST",
347
357
  "/api/v1/auth/login",
348
- { email, password },
358
+ body,
349
359
  { skipAutoRefresh: true }
350
360
  );
351
361
  return parseLoginResponse(data, this.http.isBrowserSession());
@@ -383,13 +393,29 @@ var AuthModule = class {
383
393
  method
384
394
  }, { skipAutoRefresh: true });
385
395
  }
386
- async selectTenant(tenantSelectionToken, tenantId) {
396
+ async selectTenant(tenantSelectionToken, tenantId, opts) {
397
+ const body = { tenantSelectionToken, tenantId };
398
+ if (opts?.scopeHint) body.scopeHint = opts.scopeHint;
387
399
  const data = await this.http.request(
388
400
  "POST",
389
401
  "/api/v1/auth/select-tenant",
402
+ body,
403
+ { skipAutoRefresh: true }
404
+ );
405
+ return parseLoginResponse(data, this.http.isBrowserSession());
406
+ }
407
+ /**
408
+ * Task #171 — redeem a scope-selection token + chosen membership for a
409
+ * real authenticated session. `membershipId` must be one of the scopes
410
+ * returned in the prior `scope_selection` envelope.
411
+ */
412
+ async selectScope(scopeSelectionToken, membershipId) {
413
+ const data = await this.http.request(
414
+ "POST",
415
+ "/api/v1/auth/select-scope",
390
416
  {
391
- tenantSelectionToken,
392
- tenantId
417
+ scopeSelectionToken,
418
+ membershipId
393
419
  },
394
420
  { skipAutoRefresh: true }
395
421
  );
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  IQAuthClient
3
- } from "./chunk-JXQI62A7.mjs";
3
+ } from "./chunk-ZLJPABB7.mjs";
4
4
  import "./chunk-NUO2I65G.mjs";
5
5
  import {
6
6
  ErrorCodes,
@@ -1,8 +1,8 @@
1
- import { S as SessionManager } from './signIn-T-CZ6t6r.mjs';
2
- export { p as AccountRecord, A as AccountRegistry, C as CallbackResult, d as LinkProviderInput, L as LinkedIdentity, M as MagicLinkRequestInput, o as MultiAccountTokenStore, n as PasskeyAuthInput, P as PasswordlessOptions, z as REFRESH_COOKIE, R as RefreshTokenStore, e as SessionManagerOptions, a as SessionSnapshot, f as SessionStatus, b as SignInOptions, c as SignOutOptions, U as UnlinkProviderInput, g as beginPasskeyAuthentication, i as beginPasskeyRegistration, q as buildSignInUrl, B as clearCookie, k as enrollPasskey, h as finishPasskeyAuthentication, j as finishPasskeyRegistration, D as getCookie, t as handleAuthCallback, m as linkProvider, l as listLinkedIdentities, w as redirectToSignIn, r as requestMagicLink, E as setCookie, x as signIn, s as signInWithPasskey, y as signOut, u as unlinkProvider, v as verifyMagicLink } from './signIn-T-CZ6t6r.mjs';
1
+ import { S as SessionManager } from './signIn-CReqfXsh.mjs';
2
+ export { p as AccountRecord, A as AccountRegistry, C as CallbackResult, d as LinkProviderInput, L as LinkedIdentity, M as MagicLinkRequestInput, o as MultiAccountTokenStore, n as PasskeyAuthInput, P as PasswordlessOptions, z as REFRESH_COOKIE, R as RefreshTokenStore, e as SessionManagerOptions, a as SessionSnapshot, f as SessionStatus, b as SignInOptions, c as SignOutOptions, U as UnlinkProviderInput, g as beginPasskeyAuthentication, i as beginPasskeyRegistration, q as buildSignInUrl, B as clearCookie, k as enrollPasskey, h as finishPasskeyAuthentication, j as finishPasskeyRegistration, D as getCookie, t as handleAuthCallback, m as linkProvider, l as listLinkedIdentities, w as redirectToSignIn, r as requestMagicLink, E as setCookie, x as signIn, s as signInWithPasskey, y as signOut, u as unlinkProvider, v as verifyMagicLink } from './signIn-CReqfXsh.mjs';
3
3
  export { K as KeyMode, c as ParsedPublishableKey, P as PublishableKeyPayload, e as encodePublishableKey, i as isPublishableKey, b as isSecretKey, p as parsePublishableKey } from './publishableKey-f2kq-rKw.mjs';
4
4
  export { b as ErrorCode, E as ErrorCodes, I as IQAuthError } from './errors-Jl1Jtm-6.mjs';
5
- import './types-XOV9XPVi.mjs';
5
+ import './types-Bn8O-OEd.mjs';
6
6
 
7
7
  /**
8
8
  * Browser-safe PKCE + state/nonce generation using WebCrypto.
package/dist/browser.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- import { S as SessionManager } from './signIn-BLFnz8SV.js';
2
- export { p as AccountRecord, A as AccountRegistry, C as CallbackResult, d as LinkProviderInput, L as LinkedIdentity, M as MagicLinkRequestInput, o as MultiAccountTokenStore, n as PasskeyAuthInput, P as PasswordlessOptions, z as REFRESH_COOKIE, R as RefreshTokenStore, e as SessionManagerOptions, a as SessionSnapshot, f as SessionStatus, b as SignInOptions, c as SignOutOptions, U as UnlinkProviderInput, g as beginPasskeyAuthentication, i as beginPasskeyRegistration, q as buildSignInUrl, B as clearCookie, k as enrollPasskey, h as finishPasskeyAuthentication, j as finishPasskeyRegistration, D as getCookie, t as handleAuthCallback, m as linkProvider, l as listLinkedIdentities, w as redirectToSignIn, r as requestMagicLink, E as setCookie, x as signIn, s as signInWithPasskey, y as signOut, u as unlinkProvider, v as verifyMagicLink } from './signIn-BLFnz8SV.js';
1
+ import { S as SessionManager } from './signIn-Cfa1GTpO.js';
2
+ export { p as AccountRecord, A as AccountRegistry, C as CallbackResult, d as LinkProviderInput, L as LinkedIdentity, M as MagicLinkRequestInput, o as MultiAccountTokenStore, n as PasskeyAuthInput, P as PasswordlessOptions, z as REFRESH_COOKIE, R as RefreshTokenStore, e as SessionManagerOptions, a as SessionSnapshot, f as SessionStatus, b as SignInOptions, c as SignOutOptions, U as UnlinkProviderInput, g as beginPasskeyAuthentication, i as beginPasskeyRegistration, q as buildSignInUrl, B as clearCookie, k as enrollPasskey, h as finishPasskeyAuthentication, j as finishPasskeyRegistration, D as getCookie, t as handleAuthCallback, m as linkProvider, l as listLinkedIdentities, w as redirectToSignIn, r as requestMagicLink, E as setCookie, x as signIn, s as signInWithPasskey, y as signOut, u as unlinkProvider, v as verifyMagicLink } from './signIn-Cfa1GTpO.js';
3
3
  export { K as KeyMode, c as ParsedPublishableKey, P as PublishableKeyPayload, e as encodePublishableKey, i as isPublishableKey, b as isSecretKey, p as parsePublishableKey } from './publishableKey-f2kq-rKw.js';
4
4
  export { b as ErrorCode, E as ErrorCodes, I as IQAuthError } from './errors-Jl1Jtm-6.js';
5
- import './types-XOV9XPVi.js';
5
+ import './types-Bn8O-OEd.js';
6
6
 
7
7
  /**
8
8
  * Browser-safe PKCE + state/nonce generation using WebCrypto.
package/dist/browser.js CHANGED
@@ -717,7 +717,10 @@ function claimsToSessionUser(claims) {
717
717
  ...claims.email_verified !== void 0 ? { emailVerified: claims.email_verified } : {},
718
718
  ...claims.given_name !== void 0 ? { givenName: claims.given_name } : {},
719
719
  ...claims.family_name !== void 0 ? { familyName: claims.family_name } : {},
720
- ...claims.locale !== void 0 ? { locale: claims.locale } : {}
720
+ ...claims.locale !== void 0 ? { locale: claims.locale } : {},
721
+ // Task #171 — surface the active source/client scope when the token was
722
+ // minted scoped, so consumers reading useUser().user can branch on it.
723
+ ...claims.scopeContext !== void 0 ? { scopeContext: claims.scopeContext } : {}
721
724
  };
722
725
  }
723
726
  var EMPTY = {
@@ -1092,10 +1095,27 @@ var SessionManager = class {
1092
1095
  * session and notify subscribers and other tabs.
1093
1096
  */
1094
1097
  applyAccessToken(accessToken, refreshToken) {
1098
+ this.adoptAccessToken(accessToken, { refreshToken });
1099
+ }
1100
+ /**
1101
+ * Task #197 — Adopt an access token that the server has already minted
1102
+ * for us (e.g. from `POST /api/v1/auth/switch-scope`) without contacting
1103
+ * the issuer. Swaps the in-memory token, re-decodes claims, bumps
1104
+ * `version`, schedules proactive refresh, and broadcasts a
1105
+ * `session:update` to peer tabs.
1106
+ *
1107
+ * This is the safe path for any server endpoint that returns a fresh
1108
+ * access token in its JSON body: we want the new claims (scope, roles,
1109
+ * etc.) to take effect immediately, even if the refresh-cookie round-trip
1110
+ * would have failed (network blip, rate limit, signout race). When the
1111
+ * server also rotated the refresh token, pass it via
1112
+ * `opts.refreshToken` so the cookie stays aligned.
1113
+ */
1114
+ adoptAccessToken(accessToken, opts) {
1095
1115
  const claims = decodeClaims(accessToken);
1096
1116
  const user = claimsToSessionUser(claims);
1097
- if (refreshToken) {
1098
- void Promise.resolve(this.tokenStore.write(refreshToken, { claims }));
1117
+ if (opts?.refreshToken) {
1118
+ void Promise.resolve(this.tokenStore.write(opts.refreshToken, { claims }));
1099
1119
  }
1100
1120
  this.update({
1101
1121
  status: user ? "authenticated" : "unauthenticated",
package/dist/browser.mjs CHANGED
@@ -20,7 +20,7 @@ import {
20
20
  signInWithPasskey,
21
21
  unlinkProvider,
22
22
  verifyMagicLink
23
- } from "./chunk-RTJAIBXY.mjs";
23
+ } from "./chunk-4V7FKOTG.mjs";
24
24
  import {
25
25
  REFRESH_COOKIE,
26
26
  buildSignInUrl,
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-HVHNYPDC.mjs";
4
4
  import {
5
5
  IQAuthClient
6
- } from "./chunk-JXQI62A7.mjs";
6
+ } from "./chunk-ZLJPABB7.mjs";
7
7
  import {
8
8
  IQAuthError
9
9
  } from "./chunk-6PJRLRB4.mjs";
@@ -56,7 +56,10 @@ function claimsToSessionUser(claims) {
56
56
  ...claims.email_verified !== void 0 ? { emailVerified: claims.email_verified } : {},
57
57
  ...claims.given_name !== void 0 ? { givenName: claims.given_name } : {},
58
58
  ...claims.family_name !== void 0 ? { familyName: claims.family_name } : {},
59
- ...claims.locale !== void 0 ? { locale: claims.locale } : {}
59
+ ...claims.locale !== void 0 ? { locale: claims.locale } : {},
60
+ // Task #171 — surface the active source/client scope when the token was
61
+ // minted scoped, so consumers reading useUser().user can branch on it.
62
+ ...claims.scopeContext !== void 0 ? { scopeContext: claims.scopeContext } : {}
60
63
  };
61
64
  }
62
65
  var EMPTY = {
@@ -431,10 +434,27 @@ var SessionManager = class {
431
434
  * session and notify subscribers and other tabs.
432
435
  */
433
436
  applyAccessToken(accessToken, refreshToken) {
437
+ this.adoptAccessToken(accessToken, { refreshToken });
438
+ }
439
+ /**
440
+ * Task #197 — Adopt an access token that the server has already minted
441
+ * for us (e.g. from `POST /api/v1/auth/switch-scope`) without contacting
442
+ * the issuer. Swaps the in-memory token, re-decodes claims, bumps
443
+ * `version`, schedules proactive refresh, and broadcasts a
444
+ * `session:update` to peer tabs.
445
+ *
446
+ * This is the safe path for any server endpoint that returns a fresh
447
+ * access token in its JSON body: we want the new claims (scope, roles,
448
+ * etc.) to take effect immediately, even if the refresh-cookie round-trip
449
+ * would have failed (network blip, rate limit, signout race). When the
450
+ * server also rotated the refresh token, pass it via
451
+ * `opts.refreshToken` so the cookie stays aligned.
452
+ */
453
+ adoptAccessToken(accessToken, opts) {
434
454
  const claims = decodeClaims(accessToken);
435
455
  const user = claimsToSessionUser(claims);
436
- if (refreshToken) {
437
- void Promise.resolve(this.tokenStore.write(refreshToken, { claims }));
456
+ if (opts?.refreshToken) {
457
+ void Promise.resolve(this.tokenStore.write(opts.refreshToken, { claims }));
438
458
  }
439
459
  this.update({
440
460
  status: user ? "authenticated" : "unauthenticated",
@@ -1,4 +1,11 @@
1
1
  // src/server/provisioningBridge.ts
2
+ var ProvisioningError = class extends Error {
3
+ constructor(code, message) {
4
+ super(message);
5
+ this.name = "ProvisioningError";
6
+ this.code = code;
7
+ }
8
+ };
2
9
  function defaultIsUniqueViolation(err) {
3
10
  if (!err || typeof err !== "object") return false;
4
11
  const e = err;
@@ -10,6 +17,16 @@ function defaultIsUniqueViolation(err) {
10
17
  function createProvisioningBridge(options) {
11
18
  const { storage } = options;
12
19
  const isUniqueViolation = options.isUniqueViolation ?? defaultIsUniqueViolation;
20
+ const allowUnverifiedEmailAdopt = options.allowUnverifiedEmailAdopt === true;
21
+ const emailVerified = (claims) => claims.email_verified === true;
22
+ const assertAdoptAllowed = (claims) => {
23
+ if (!allowUnverifiedEmailAdopt && !emailVerified(claims)) {
24
+ throw new ProvisioningError(
25
+ "UNVERIFIED_EMAIL_ADOPT_REFUSED",
26
+ "Refusing to adopt a pre-existing local account from an unverified email (claims.email_verified !== true). Set allowUnverifiedEmailAdopt:true only if your issuer is trusted to never emit unverified emails for adoption."
27
+ );
28
+ }
29
+ };
13
30
  const roleOf = (claims) => {
14
31
  try {
15
32
  return options.roleMapper?.(claims) ?? null;
@@ -26,6 +43,7 @@ function createProvisioningBridge(options) {
26
43
  if (claims.email) {
27
44
  const byEmail = await storage.findByEmail(claims.email);
28
45
  if (byEmail) {
46
+ assertAdoptAllowed(claims);
29
47
  if (storage.adoptByEmail) {
30
48
  const adopted = await storage.adoptByEmail(byEmail, claims, roleOf(claims));
31
49
  return { user: adopted, claims, created: false, adopted: true };
@@ -41,7 +59,10 @@ function createProvisioningBridge(options) {
41
59
  if (after) return { user: after, claims, created: false, adopted: false };
42
60
  if (claims.email) {
43
61
  const byEmail = await storage.findByEmail(claims.email);
44
- if (byEmail) return { user: byEmail, claims, created: false, adopted: true };
62
+ if (byEmail) {
63
+ assertAdoptAllowed(claims);
64
+ return { user: byEmail, claims, created: false, adopted: true };
65
+ }
45
66
  }
46
67
  throw err;
47
68
  }
@@ -50,5 +71,6 @@ function createProvisioningBridge(options) {
50
71
  }
51
72
 
52
73
  export {
74
+ ProvisioningError,
53
75
  createProvisioningBridge
54
76
  };
@@ -0,0 +1,46 @@
1
+ // src/browser/returnTo.ts
2
+ function normalizeOrigin(o) {
3
+ try {
4
+ return new URL(o).origin;
5
+ } catch {
6
+ return o.replace(/\/+$/, "");
7
+ }
8
+ }
9
+ function sanitizeReturnTo(input, options = {}) {
10
+ const fallback = options.fallback ?? "/";
11
+ if (!input || typeof input !== "string") return fallback;
12
+ const trimmed = input.trim();
13
+ if (!trimmed) return fallback;
14
+ if (trimmed.includes("\\")) return fallback;
15
+ if (trimmed.startsWith("//")) return fallback;
16
+ if (trimmed.startsWith("/") || trimmed.startsWith("#") || trimmed.startsWith("?")) {
17
+ return trimmed;
18
+ }
19
+ if (!/^[a-z][a-z0-9+\-.]*:/i.test(trimmed)) {
20
+ return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
21
+ }
22
+ let parsed;
23
+ try {
24
+ parsed = new URL(trimmed);
25
+ } catch {
26
+ return fallback;
27
+ }
28
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return fallback;
29
+ const currentOrigin = options.currentOrigin ?? (typeof window !== "undefined" ? window.location.origin : "");
30
+ const allowed = /* @__PURE__ */ new Set();
31
+ if (currentOrigin) allowed.add(normalizeOrigin(currentOrigin));
32
+ for (const o of options.allowedOrigins ?? []) allowed.add(normalizeOrigin(o));
33
+ if (allowed.has(parsed.origin)) return parsed.toString();
34
+ return fallback;
35
+ }
36
+ function isReturnToAllowed(input, options = {}) {
37
+ const fallback = options.fallback ?? "/";
38
+ const out = sanitizeReturnTo(input, options);
39
+ if (!input) return false;
40
+ return out !== fallback || input === fallback;
41
+ }
42
+
43
+ export {
44
+ sanitizeReturnTo,
45
+ isReturnToAllowed
46
+ };
@@ -37,6 +37,7 @@ var enUS = {
37
37
  "signIn.useDifferentAccount": "Use a different account",
38
38
  "signIn.selectTenant": "Choose an organization",
39
39
  "signIn.selectTenantSubtitle": "You belong to multiple organizations. Pick one to continue.",
40
+ "signIn.selectScope": "Choose scope",
40
41
  "signIn.dividerOr": "or",
41
42
  "signIn.preparingExperience": "Preparing your sign-in experience.",
42
43
  "signIn.applicationUnavailable": "Application unavailable",
@@ -125,6 +126,11 @@ var enUS = {
125
126
  "orgSwitcher.createNew": "Create organization",
126
127
  "orgSwitcher.manage": "Manage organization",
127
128
  "orgSwitcher.noOrgs": "You don't belong to any organizations yet.",
129
+ "orgSwitcher.mfaRequiredTitle": "Two-factor verification required",
130
+ "orgSwitcher.mfaRequiredBody": "This organization requires an extra verification step. Continue in the hosted sign-in to finish switching.",
131
+ "orgSwitcher.scopeSelectionRequiredTitle": "Choose what to access",
132
+ "orgSwitcher.scopeSelectionRequiredBody": "This organization needs you to pick which workspace to open. Continue in the hosted sign-in to choose.",
133
+ "orgSwitcher.continueInHostedSignIn": "Continue in hosted sign-in \u2192",
128
134
  "orgProfile.title": "Organization settings",
129
135
  "orgProfile.generalTab": "General",
130
136
  "orgProfile.membersTab": "Members",
@@ -217,6 +223,7 @@ var frFR = {
217
223
  "signIn.useDifferentAccount": "Utiliser un autre compte",
218
224
  "signIn.selectTenant": "Choisir une organisation",
219
225
  "signIn.selectTenantSubtitle": "Vous appartenez \xE0 plusieurs organisations. Choisissez-en une pour continuer.",
226
+ "signIn.selectScope": "Choisir une port\xE9e",
220
227
  "signIn.dividerOr": "ou",
221
228
  "signIn.preparingExperience": "Pr\xE9paration de votre exp\xE9rience de connexion.",
222
229
  "signIn.applicationUnavailable": "Application indisponible",
@@ -305,6 +312,11 @@ var frFR = {
305
312
  "orgSwitcher.createNew": "Cr\xE9er une organisation",
306
313
  "orgSwitcher.manage": "G\xE9rer l'organisation",
307
314
  "orgSwitcher.noOrgs": "Vous n'appartenez encore \xE0 aucune organisation.",
315
+ "orgSwitcher.mfaRequiredTitle": "V\xE9rification en deux \xE9tapes requise",
316
+ "orgSwitcher.mfaRequiredBody": "Cette organisation n\xE9cessite une \xE9tape de v\xE9rification suppl\xE9mentaire. Poursuivez sur la page de connexion h\xE9berg\xE9e pour terminer le changement.",
317
+ "orgSwitcher.scopeSelectionRequiredTitle": "Choisir l'espace \xE0 ouvrir",
318
+ "orgSwitcher.scopeSelectionRequiredBody": "Cette organisation n\xE9cessite que vous choisissiez un espace de travail. Poursuivez sur la page de connexion h\xE9berg\xE9e pour choisir.",
319
+ "orgSwitcher.continueInHostedSignIn": "Continuer sur la connexion h\xE9berg\xE9e \u2192",
308
320
  "orgProfile.title": "Param\xE8tres de l'organisation",
309
321
  "orgProfile.generalTab": "G\xE9n\xE9ral",
310
322
  "orgProfile.membersTab": "Membres",
@@ -397,6 +409,7 @@ var esES = {
397
409
  "signIn.useDifferentAccount": "Usar otra cuenta",
398
410
  "signIn.selectTenant": "Elige una organizaci\xF3n",
399
411
  "signIn.selectTenantSubtitle": "Perteneces a varias organizaciones. Selecciona una para continuar.",
412
+ "signIn.selectScope": "Elegir \xE1mbito",
400
413
  "signIn.dividerOr": "o",
401
414
  "signIn.preparingExperience": "Preparando tu experiencia de inicio de sesi\xF3n.",
402
415
  "signIn.applicationUnavailable": "Aplicaci\xF3n no disponible",
@@ -485,6 +498,11 @@ var esES = {
485
498
  "orgSwitcher.createNew": "Crear organizaci\xF3n",
486
499
  "orgSwitcher.manage": "Gestionar organizaci\xF3n",
487
500
  "orgSwitcher.noOrgs": "A\xFAn no perteneces a ninguna organizaci\xF3n.",
501
+ "orgSwitcher.mfaRequiredTitle": "Se requiere verificaci\xF3n en dos pasos",
502
+ "orgSwitcher.mfaRequiredBody": "Esta organizaci\xF3n requiere un paso de verificaci\xF3n adicional. Contin\xFAa en el inicio de sesi\xF3n alojado para completar el cambio.",
503
+ "orgSwitcher.scopeSelectionRequiredTitle": "Elige a qu\xE9 acceder",
504
+ "orgSwitcher.scopeSelectionRequiredBody": "Esta organizaci\xF3n requiere que elijas un espacio de trabajo. Contin\xFAa en el inicio de sesi\xF3n alojado para elegir.",
505
+ "orgSwitcher.continueInHostedSignIn": "Continuar en el inicio de sesi\xF3n alojado \u2192",
488
506
  "orgProfile.title": "Configuraci\xF3n de la organizaci\xF3n",
489
507
  "orgProfile.generalTab": "Generales",
490
508
  "orgProfile.membersTab": "Miembros",
@@ -577,6 +595,7 @@ var deDE = {
577
595
  "signIn.useDifferentAccount": "Anderes Konto verwenden",
578
596
  "signIn.selectTenant": "Organisation ausw\xE4hlen",
579
597
  "signIn.selectTenantSubtitle": "Sie geh\xF6ren zu mehreren Organisationen. Bitte w\xE4hlen Sie eine aus.",
598
+ "signIn.selectScope": "Bereich ausw\xE4hlen",
580
599
  "signIn.preparingExperience": "Anmeldung wird vorbereitet.",
581
600
  "signIn.applicationUnavailable": "Anwendung nicht verf\xFCgbar",
582
601
  "signIn.applicationNotFound": "Anwendung nicht gefunden",
@@ -665,6 +684,11 @@ var deDE = {
665
684
  "orgSwitcher.createNew": "Organisation erstellen",
666
685
  "orgSwitcher.manage": "Organisation verwalten",
667
686
  "orgSwitcher.noOrgs": "Sie geh\xF6ren noch keiner Organisation an.",
687
+ "orgSwitcher.mfaRequiredTitle": "Zwei-Faktor-Verifizierung erforderlich",
688
+ "orgSwitcher.mfaRequiredBody": "Diese Organisation erfordert einen zus\xE4tzlichen Verifizierungsschritt. Fahren Sie in der gehosteten Anmeldung fort, um den Wechsel abzuschlie\xDFen.",
689
+ "orgSwitcher.scopeSelectionRequiredTitle": "Zugriff ausw\xE4hlen",
690
+ "orgSwitcher.scopeSelectionRequiredBody": "F\xFCr diese Organisation m\xFCssen Sie einen Arbeitsbereich ausw\xE4hlen. Fahren Sie in der gehosteten Anmeldung fort, um auszuw\xE4hlen.",
691
+ "orgSwitcher.continueInHostedSignIn": "In der gehosteten Anmeldung fortfahren \u2192",
668
692
  "orgProfile.title": "Organisationseinstellungen",
669
693
  "orgProfile.generalTab": "Allgemein",
670
694
  "orgProfile.membersTab": "Mitglieder",
@@ -757,6 +781,7 @@ var jaJP = {
757
781
  "signIn.useDifferentAccount": "\u5225\u306E\u30A2\u30AB\u30A6\u30F3\u30C8\u3092\u4F7F\u7528",
758
782
  "signIn.selectTenant": "\u7D44\u7E54\u3092\u9078\u629E",
759
783
  "signIn.selectTenantSubtitle": "\u8907\u6570\u306E\u7D44\u7E54\u306B\u6240\u5C5E\u3057\u3066\u3044\u307E\u3059\u3002\u7D9A\u3051\u308B\u306B\u306F 1 \u3064\u3092\u9078\u629E\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
784
+ "signIn.selectScope": "\u30B9\u30B3\u30FC\u30D7\u3092\u9078\u629E",
760
785
  "signIn.preparingExperience": "\u30B5\u30A4\u30F3\u30A4\u30F3\u306E\u6E96\u5099\u3092\u3057\u3066\u3044\u307E\u3059\u3002",
761
786
  "signIn.applicationUnavailable": "\u30A2\u30D7\u30EA\u30B1\u30FC\u30B7\u30E7\u30F3\u3092\u5229\u7528\u3067\u304D\u307E\u305B\u3093",
762
787
  "signIn.applicationNotFound": "\u30A2\u30D7\u30EA\u30B1\u30FC\u30B7\u30E7\u30F3\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093",
@@ -845,6 +870,11 @@ var jaJP = {
845
870
  "orgSwitcher.createNew": "\u7D44\u7E54\u3092\u4F5C\u6210",
846
871
  "orgSwitcher.manage": "\u7D44\u7E54\u3092\u7BA1\u7406",
847
872
  "orgSwitcher.noOrgs": "\u307E\u3060\u3069\u306E\u7D44\u7E54\u306B\u3082\u6240\u5C5E\u3057\u3066\u3044\u307E\u305B\u3093\u3002",
873
+ "orgSwitcher.mfaRequiredTitle": "\u4E8C\u6BB5\u968E\u8A8D\u8A3C\u304C\u5FC5\u8981\u3067\u3059",
874
+ "orgSwitcher.mfaRequiredBody": "\u3053\u306E\u7D44\u7E54\u3067\u306F\u8FFD\u52A0\u306E\u672C\u4EBA\u78BA\u8A8D\u304C\u5FC5\u8981\u3067\u3059\u3002\u30DB\u30B9\u30C8\u3055\u308C\u305F\u30B5\u30A4\u30F3\u30A4\u30F3\u3067\u7D9A\u3051\u3066\u5207\u308A\u66FF\u3048\u3092\u5B8C\u4E86\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
875
+ "orgSwitcher.scopeSelectionRequiredTitle": "\u30A2\u30AF\u30BB\u30B9\u5148\u3092\u9078\u629E",
876
+ "orgSwitcher.scopeSelectionRequiredBody": "\u3053\u306E\u7D44\u7E54\u3067\u306F\u30EF\u30FC\u30AF\u30B9\u30DA\u30FC\u30B9\u306E\u9078\u629E\u304C\u5FC5\u8981\u3067\u3059\u3002\u30DB\u30B9\u30C8\u3055\u308C\u305F\u30B5\u30A4\u30F3\u30A4\u30F3\u3067\u9078\u629E\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
877
+ "orgSwitcher.continueInHostedSignIn": "\u30DB\u30B9\u30C8\u3055\u308C\u305F\u30B5\u30A4\u30F3\u30A4\u30F3\u306B\u9032\u3080 \u2192",
848
878
  "orgProfile.title": "\u7D44\u7E54\u306E\u8A2D\u5B9A",
849
879
  "orgProfile.generalTab": "\u4E00\u822C",
850
880
  "orgProfile.membersTab": "\u30E1\u30F3\u30D0\u30FC",
@@ -937,6 +967,7 @@ var ptBR = {
937
967
  "signIn.useDifferentAccount": "Usar outra conta",
938
968
  "signIn.selectTenant": "Escolher uma organiza\xE7\xE3o",
939
969
  "signIn.selectTenantSubtitle": "Voc\xEA pertence a v\xE1rias organiza\xE7\xF5es. Escolha uma para continuar.",
970
+ "signIn.selectScope": "Escolher escopo",
940
971
  "signIn.preparingExperience": "Preparando sua experi\xEAncia de login.",
941
972
  "signIn.applicationUnavailable": "Aplicativo indispon\xEDvel",
942
973
  "signIn.applicationNotFound": "Aplicativo n\xE3o encontrado",
@@ -1024,6 +1055,11 @@ var ptBR = {
1024
1055
  "orgSwitcher.personal": "Conta pessoal",
1025
1056
  "orgSwitcher.createNew": "Criar organiza\xE7\xE3o",
1026
1057
  "orgSwitcher.manage": "Gerenciar organiza\xE7\xE3o",
1058
+ "orgSwitcher.mfaRequiredTitle": "Verifica\xE7\xE3o em duas etapas necess\xE1ria",
1059
+ "orgSwitcher.mfaRequiredBody": "Esta organiza\xE7\xE3o requer uma etapa de verifica\xE7\xE3o adicional. Continue no login hospedado para concluir a troca.",
1060
+ "orgSwitcher.scopeSelectionRequiredTitle": "Escolha o que acessar",
1061
+ "orgSwitcher.scopeSelectionRequiredBody": "Esta organiza\xE7\xE3o exige que voc\xEA escolha um espa\xE7o de trabalho. Continue no login hospedado para escolher.",
1062
+ "orgSwitcher.continueInHostedSignIn": "Continuar no login hospedado \u2192",
1027
1063
  "orgSwitcher.noOrgs": "Voc\xEA ainda n\xE3o pertence a nenhuma organiza\xE7\xE3o.",
1028
1064
  "orgProfile.title": "Configura\xE7\xF5es da organiza\xE7\xE3o",
1029
1065
  "orgProfile.generalTab": "Geral",
@@ -74,12 +74,8 @@ function verifyWebhookSignature(opts) {
74
74
  }
75
75
  const body = toBuffer(opts.payload);
76
76
  const { modern, legacy } = computeSignatures(opts.secret, body, t);
77
- const matched = v1.some((sig) => {
78
- const lower = sig.toLowerCase();
79
- if (timingSafeEqualHex(lower, modern)) return true;
80
- if (legacy && timingSafeEqualHex(lower, legacy)) return true;
81
- return false;
82
- });
77
+ const expected = Number.isFinite(t) ? legacy : modern;
78
+ const matched = expected !== null && v1.some((sig) => timingSafeEqualHex(sig.toLowerCase(), expected));
83
79
  if (!matched) {
84
80
  throw new WebhookSignatureError("SIGNATURE_MISMATCH", "Webhook signature does not match expected value");
85
81
  }
@@ -89,6 +85,29 @@ function verifyWebhookSignature(opts) {
89
85
  } catch {
90
86
  throw new WebhookSignatureError("MALFORMED_BODY", "Webhook body is not valid JSON");
91
87
  }
88
+ if (!Number.isFinite(t) && !opts.allowMissingTimestamp) {
89
+ const rawTime = parsed.time;
90
+ if (typeof rawTime !== "string" || !rawTime) {
91
+ throw new WebhookSignatureError(
92
+ "MISSING_TIMESTAMP",
93
+ "Modern webhook delivery has no header `t=` and no envelope `time`; cannot enforce replay protection. Use parseWebhookEvent, or set allowMissingTimestamp:true (insecure)."
94
+ );
95
+ }
96
+ const eventMs = Date.parse(rawTime);
97
+ if (!Number.isFinite(eventMs)) {
98
+ throw new WebhookSignatureError(
99
+ "MALFORMED_TIMESTAMP",
100
+ `Envelope \`time\` is not a valid ISO timestamp: ${rawTime}`
101
+ );
102
+ }
103
+ const nowMs = (opts.nowSeconds ?? Math.floor(Date.now() / 1e3)) * 1e3;
104
+ if (Math.abs(nowMs - eventMs) > tolerance * 1e3) {
105
+ throw new WebhookSignatureError(
106
+ "TIMESTAMP_OUT_OF_TOLERANCE",
107
+ `Envelope time ${rawTime} is outside the ${tolerance}s tolerance window`
108
+ );
109
+ }
110
+ }
92
111
  return parsed;
93
112
  }
94
113
  function isValidWebhookSignature(opts) {
@@ -158,12 +177,8 @@ function parseWebhookEvent(rawBody, headers, secrets, opts = {}) {
158
177
  const secret = secrets[i];
159
178
  if (!secret) continue;
160
179
  const { modern, legacy } = computeSignatures(secret, body, t);
161
- const ok = v1.some((sig) => {
162
- const lower = sig.toLowerCase();
163
- if (timingSafeEqualHex(lower, modern)) return true;
164
- if (legacy && timingSafeEqualHex(lower, legacy)) return true;
165
- return false;
166
- });
180
+ const expected = Number.isFinite(t) ? legacy : modern;
181
+ const ok = expected !== null && v1.some((sig) => timingSafeEqualHex(sig.toLowerCase(), expected));
167
182
  if (ok) {
168
183
  verifiedIdx = i;
169
184
  break;