@iqauth/sdk 2.6.4 → 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 (117) hide show
  1. package/README.md +173 -1
  2. package/dist/browser-session.d.mts +4 -4
  3. package/dist/browser-session.d.ts +4 -4
  4. package/dist/browser-session.js +212 -46
  5. package/dist/browser-session.mjs +3 -3
  6. package/dist/browser.d.mts +5 -5
  7. package/dist/browser.d.ts +5 -5
  8. package/dist/browser.js +293 -34
  9. package/dist/browser.mjs +5 -5
  10. package/dist/{chunk-BVV54LPI.mjs → chunk-25SSYDIP.mjs} +10 -4
  11. package/dist/{chunk-XAWYUPMO.mjs → chunk-4V7FKOTG.mjs} +242 -22
  12. package/dist/{chunk-6I6RM4MN.mjs → chunk-6PJRLRB4.mjs} +33 -3
  13. package/dist/{chunk-SL3KRS4W.mjs → chunk-CIJORODR.mjs} +23 -1
  14. package/dist/{chunk-LIZYFXH7.mjs → chunk-DFWHSDYQ.mjs} +1 -1
  15. package/dist/chunk-GLXSIGVS.mjs +66 -0
  16. package/dist/{chunk-DJIBN2N7.mjs → chunk-GN37E64I.mjs} +29 -7
  17. package/dist/{chunk-WQWBJSSS.mjs → chunk-HVHNYPDC.mjs} +6 -6
  18. package/dist/chunk-JRDVUWAL.mjs +46 -0
  19. package/dist/{chunk-UNYDG2L4.mjs → chunk-NUO2I65G.mjs} +56 -23
  20. package/dist/{chunk-5T7GHBX6.mjs → chunk-TLET552H.mjs} +36 -0
  21. package/dist/chunk-VYQ3ETCK.mjs +244 -0
  22. package/dist/{chunk-3JULWS6F.mjs → chunk-WCELYTJ3.mjs} +3 -3
  23. package/dist/chunk-WHT6WKTY.mjs +3180 -0
  24. package/dist/{chunk-MKKZULZR.mjs → chunk-WIFG74IK.mjs} +1 -1
  25. package/dist/chunk-WSH4SW7F.mjs +490 -0
  26. package/dist/{chunk-W3F4JYGP.mjs → chunk-ZLJPABB7.mjs} +139 -23
  27. package/dist/cli/index.js +2 -2
  28. package/dist/cli/index.mjs +2 -2
  29. package/dist/{client-BNQe3AgF.d.ts → client-D8L-PaWr.d.mts} +59 -6
  30. package/dist/{client-kYlJFgPv.d.mts → client-DkPL0EPZ.d.ts} +59 -6
  31. package/dist/{doctor-YYNHNMLD.mjs → doctor-JAFXWU3X.mjs} +2 -2
  32. package/dist/errors-Jl1Jtm-6.d.mts +107 -0
  33. package/dist/errors-Jl1Jtm-6.d.ts +107 -0
  34. package/dist/{express-CHpfa7D_.d.ts → express-Budysq4h.d.ts} +2 -2
  35. package/dist/{express-B6_1vBYZ.d.mts → express-DDTA3qV1.d.mts} +2 -2
  36. package/dist/express.d.mts +7 -6
  37. package/dist/express.d.ts +7 -6
  38. package/dist/express.js +563 -85
  39. package/dist/express.mjs +73 -34
  40. package/dist/fastify.d.mts +10 -0
  41. package/dist/fastify.d.ts +10 -0
  42. package/dist/fastify.js +589 -65
  43. package/dist/fastify.mjs +101 -11
  44. package/dist/hono.d.mts +10 -0
  45. package/dist/hono.d.ts +10 -0
  46. package/dist/hono.js +566 -65
  47. package/dist/hono.mjs +78 -11
  48. package/dist/index-Cko-d5po.d.mts +1848 -0
  49. package/dist/index-RNqwEcmY.d.ts +1848 -0
  50. package/dist/index.d.mts +56 -8
  51. package/dist/index.d.ts +56 -8
  52. package/dist/index.js +694 -75
  53. package/dist/index.mjs +30 -10
  54. package/dist/{keys-NLWFAOEM.mjs → keys-6Y776TG2.mjs} +2 -2
  55. package/dist/locales.d.mts +1 -1
  56. package/dist/locales.d.ts +1 -1
  57. package/dist/locales.js +36 -0
  58. package/dist/locales.mjs +1 -1
  59. package/dist/mobile.d.mts +77 -7
  60. package/dist/mobile.d.ts +77 -7
  61. package/dist/mobile.js +307 -46
  62. package/dist/mobile.mjs +98 -3
  63. package/dist/next.d.mts +10 -1
  64. package/dist/next.d.ts +10 -1
  65. package/dist/next.js +596 -205
  66. package/dist/next.mjs +83 -10
  67. package/dist/{provisioningBridge-88xjOS2n.d.mts → provisioningBridge-BXPMZCLe.d.ts} +30 -2
  68. package/dist/{provisioningBridge-DnTfzdZK.d.ts → provisioningBridge-IEycmsgb.d.mts} +30 -2
  69. package/dist/{publishableKey-BaR0HoAH.d.ts → publishableKey-f2kq-rKw.d.mts} +1 -1
  70. package/dist/{publishableKey-BaR0HoAH.d.mts → publishableKey-f2kq-rKw.d.ts} +1 -1
  71. package/dist/react-permissions.d.mts +52 -0
  72. package/dist/react-permissions.d.ts +52 -0
  73. package/dist/react-permissions.js +239 -0
  74. package/dist/react-permissions.mjs +98 -0
  75. package/dist/react.d.mts +9 -1624
  76. package/dist/react.d.ts +9 -1624
  77. package/dist/react.js +882 -73
  78. package/dist/react.mjs +71 -2631
  79. package/dist/{reverify-4UEJXUS6.mjs → reverify-C64QXKJO.mjs} +2 -2
  80. package/dist/server/handlers.d.mts +200 -4
  81. package/dist/server/handlers.d.ts +200 -4
  82. package/dist/server/handlers.js +530 -16
  83. package/dist/server/handlers.mjs +14 -3
  84. package/dist/server.d.mts +171 -8
  85. package/dist/server.d.ts +171 -8
  86. package/dist/server.js +579 -61
  87. package/dist/server.mjs +99 -12
  88. package/dist/service.d.mts +4 -4
  89. package/dist/service.d.ts +4 -4
  90. package/dist/service.js +212 -46
  91. package/dist/service.mjs +3 -3
  92. package/dist/{signIn-CiIBTJIh.d.mts → signIn-CReqfXsh.d.mts} +95 -3
  93. package/dist/{signIn-OCr88Zf8.d.ts → signIn-Cfa1GTpO.d.ts} +95 -3
  94. package/dist/{signIn-4OKLDEIH.mjs → signIn-SHBW6Z4T.mjs} +1 -1
  95. package/dist/test.mjs +3 -3
  96. package/dist/{tokens-DCyzzn8L.d.mts → tokens-9F6ETrzk.d.ts} +9 -2
  97. package/dist/{tokens-aHiGFr_E.d.ts → tokens-B06VtvUi.d.mts} +9 -2
  98. package/dist/{types-DZAflmmq.d.mts → types-Bn8O-OEd.d.mts} +164 -11
  99. package/dist/{types-DZAflmmq.d.ts → types-Bn8O-OEd.d.ts} +164 -11
  100. package/dist/{types-6bNdxesb.d.ts → types-DnU2LhXR.d.mts} +7 -1
  101. package/dist/{types-6bNdxesb.d.mts → types-DnU2LhXR.d.ts} +7 -1
  102. package/dist/webhooks.d.mts +113 -17
  103. package/dist/webhooks.d.ts +113 -17
  104. package/dist/webhooks.js +179 -15
  105. package/dist/webhooks.mjs +7 -1
  106. package/dist/ws.d.mts +2 -2
  107. package/dist/ws.d.ts +2 -2
  108. package/dist/ws.js +80 -30
  109. package/dist/ws.mjs +4 -4
  110. package/docs/error-handling.md +101 -0
  111. package/docs/guides/effective-permissions.md +171 -0
  112. package/docs/guides/invitations.md +65 -0
  113. package/package.json +19 -4
  114. package/dist/chunk-6TDJJER7.mjs +0 -217
  115. package/dist/chunk-UKZLOHZG.mjs +0 -83
  116. package/dist/errors-CDdl24MP.d.mts +0 -52
  117. package/dist/errors-CDdl24MP.d.ts +0 -52
package/dist/next.mjs CHANGED
@@ -1,19 +1,24 @@
1
+ import {
2
+ sanitizeReturnTo
3
+ } from "./chunk-JRDVUWAL.mjs";
1
4
  import {
2
5
  handleCallback,
3
6
  handleRefresh,
4
7
  handleSignout,
8
+ handleUserinfo,
5
9
  serializeCookie
6
- } from "./chunk-6TDJJER7.mjs";
10
+ } from "./chunk-WSH4SW7F.mjs";
7
11
  import {
8
12
  assertPublishableKey
9
- } from "./chunk-WQWBJSSS.mjs";
13
+ } from "./chunk-HVHNYPDC.mjs";
10
14
  import {
11
15
  TokensModule
12
- } from "./chunk-UNYDG2L4.mjs";
13
- import "./chunk-6I6RM4MN.mjs";
16
+ } from "./chunk-NUO2I65G.mjs";
17
+ import "./chunk-6PJRLRB4.mjs";
14
18
  import "./chunk-Y6FXYEAI.mjs";
15
19
 
16
20
  // src/next.ts
21
+ var PKCE_COOKIE = "iqauth_pkce";
17
22
  function readCookieFromHeader(header, name) {
18
23
  if (!header) return void 0;
19
24
  const target = `${name}=`;
@@ -34,32 +39,100 @@ function toResponse(hr) {
34
39
  for (const c of hr.cookies) headers.append("set-cookie", serializeCookie(c));
35
40
  return new Response(JSON.stringify(hr.body), { status: hr.status, headers });
36
41
  }
42
+ function callbackResponse(hr, requestOrigin, returnToCookieValue, returnToCookieName) {
43
+ const returnTo = sanitizeReturnTo(
44
+ returnToCookieValue || hr.body?.returnTo,
45
+ { currentOrigin: requestOrigin, fallback: "/" }
46
+ );
47
+ const headers = new Headers({ "Content-Type": "application/json" });
48
+ for (const c of hr.cookies) headers.append("set-cookie", serializeCookie(c));
49
+ if (hr.status < 400) {
50
+ headers.append("set-cookie", `${returnToCookieName}=; Path=/; Max-Age=0; SameSite=Lax`);
51
+ }
52
+ const body = { ...hr.body, returnTo };
53
+ return new Response(JSON.stringify(body), { status: hr.status, headers });
54
+ }
55
+ function callbackRedirectResponse(hr, requestOrigin, returnToCookieValue, cookieNames) {
56
+ const headers = new Headers();
57
+ for (const c of hr.cookies) headers.append("set-cookie", serializeCookie(c));
58
+ headers.append("set-cookie", `${cookieNames.state}=; Path=/; Max-Age=0; SameSite=Lax`);
59
+ headers.append("set-cookie", `${cookieNames.pkce}=; Path=/; Max-Age=0; SameSite=Lax`);
60
+ if (hr.status >= 400) {
61
+ headers.set("Location", "/");
62
+ return new Response(null, { status: 302, headers });
63
+ }
64
+ const dest = sanitizeReturnTo(returnToCookieValue, {
65
+ currentOrigin: requestOrigin,
66
+ fallback: "/"
67
+ });
68
+ headers.append("set-cookie", `${cookieNames.returnTo}=; Path=/; Max-Age=0; SameSite=Lax`);
69
+ headers.set("Location", dest);
70
+ return new Response(null, { status: 302, headers });
71
+ }
37
72
  function handler(options) {
38
73
  const parsed = assertPublishableKey(options.publishableKey, { context: "@iqauth/sdk/next handler" });
39
74
  const issuer = (options.issuer ?? (parsed.iss.startsWith("http") ? parsed.iss : `https://${parsed.iss}`)).replace(/\/+$/, "");
40
75
  const helperConfig = { ...options, issuer };
41
76
  const accessCookie = options.accessCookieName ?? "iqauth_at";
42
77
  const refreshCookie = options.refreshCookieName ?? "iqauth_rt";
78
+ const returnToCookie = options.returnToCookieName ?? "iqauth_return_to";
43
79
  return async (req) => {
44
80
  const url = new URL(req.url);
45
81
  const action = url.pathname.split("/").pop();
46
- const body = await req.json().catch(() => ({}));
47
82
  const cookieHeader = req.headers.get("cookie");
83
+ if (action === "me" && req.method === "GET") {
84
+ if (!options.mountUserinfo) {
85
+ return new Response(JSON.stringify({ success: false, error: { code: "NOT_FOUND", message: "userinfo route not enabled" } }), {
86
+ status: 404,
87
+ headers: { "Content-Type": "application/json" }
88
+ });
89
+ }
90
+ const auth = req.headers.get("authorization");
91
+ const accessToken = auth && auth.replace(/^Bearer /i, "") || readCookieFromHeader(cookieHeader, accessCookie);
92
+ return toResponse(await handleUserinfo(helperConfig, { accessToken, req }));
93
+ }
94
+ const stateCookie = helperConfig.stateCookieName ?? "iqauth_state";
95
+ if (action === "callback" && req.method === "GET") {
96
+ const code = url.searchParams.get("code") ?? void 0;
97
+ const state = url.searchParams.get("state") ?? void 0;
98
+ const redirectUri = `${url.origin}${url.pathname}`;
99
+ const hr = await handleCallback(helperConfig, {
100
+ code,
101
+ codeVerifier: readCookieFromHeader(cookieHeader, PKCE_COOKIE),
102
+ redirectUri,
103
+ state,
104
+ expectedState: readCookieFromHeader(cookieHeader, stateCookie)
105
+ });
106
+ return callbackRedirectResponse(
107
+ hr,
108
+ url.origin,
109
+ readCookieFromHeader(cookieHeader, returnToCookie),
110
+ { returnTo: returnToCookie, state: stateCookie, pkce: PKCE_COOKIE }
111
+ );
112
+ }
113
+ const body = await req.json().catch(() => ({}));
48
114
  if (action === "callback") {
49
- return toResponse(await handleCallback(helperConfig, {
115
+ const hr = await handleCallback(helperConfig, {
50
116
  code: body.code,
51
117
  codeVerifier: body.codeVerifier,
52
- redirectUri: body.redirectUri
53
- }));
118
+ redirectUri: body.redirectUri,
119
+ // M-2: bind callback to this browser; handleCallback fails closed.
120
+ state: body.state,
121
+ expectedState: readCookieFromHeader(cookieHeader, helperConfig.stateCookieName ?? "iqauth_state")
122
+ });
123
+ return callbackResponse(hr, url.origin, readCookieFromHeader(cookieHeader, returnToCookie), returnToCookie);
54
124
  }
55
125
  if (action === "refresh") {
56
126
  const refreshToken = body.refreshToken || readCookieFromHeader(cookieHeader, refreshCookie);
57
- return toResponse(await handleRefresh(helperConfig, { refreshToken }));
127
+ const idempotencyToken = req.headers.get("x-iqauth-idempotency") || body.idempotencyToken;
128
+ return toResponse(await handleRefresh(helperConfig, { refreshToken, idempotencyToken: idempotencyToken ?? void 0 }));
58
129
  }
59
130
  if (action === "signout") {
60
131
  const auth = req.headers.get("authorization");
61
132
  const accessToken = auth && auth.replace(/^Bearer /i, "") || readCookieFromHeader(cookieHeader, accessCookie);
62
- return toResponse(await handleSignout(helperConfig, { accessToken, ssoCookieHeader: cookieHeader ?? void 0 }));
133
+ const refreshToken = readCookieFromHeader(cookieHeader, refreshCookie);
134
+ const idempotencyToken = req.headers.get("x-iqauth-idempotency") ?? void 0;
135
+ return toResponse(await handleSignout(helperConfig, { accessToken, refreshToken, idempotencyToken, ssoCookieHeader: cookieHeader ?? void 0 }));
63
136
  }
64
137
  return new Response(JSON.stringify({ success: false, error: { code: "NOT_FOUND", message: `Unknown action: ${action}` } }), {
65
138
  status: 404,
@@ -1,4 +1,4 @@
1
- import { J as JwtClaims } from './types-DZAflmmq.mjs';
1
+ import { J as JwtClaims } from './types-Bn8O-OEd.js';
2
2
 
3
3
  /**
4
4
  * createProvisioningBridge — server-side helper that lifts the
@@ -21,6 +21,17 @@ import { J as JwtClaims } from './types-DZAflmmq.mjs';
21
21
  * your local user table. See the JSDoc on each adapter for the contract.
22
22
  */
23
23
 
24
+ /**
25
+ * Thrown when the bridge refuses to adopt a pre-existing local account because
26
+ * the IQAuth claims do not assert a verified email (`email_verified !== true`)
27
+ * and the integrator has not opted in via `allowUnverifiedEmailAdopt`. Carries
28
+ * a stable `.code` so callers can branch (e.g. provision a brand-new row under
29
+ * a different identity instead of adopting the email-matched one).
30
+ */
31
+ declare class ProvisioningError extends Error {
32
+ code: string;
33
+ constructor(code: string, message: string);
34
+ }
24
35
  interface ProvisioningContext<TUser> {
25
36
  claims: JwtClaims;
26
37
  /** The local user record, looked up or freshly inserted. */
@@ -59,6 +70,23 @@ interface ProvisioningBridgeOptions<TUser> {
59
70
  * Defaults to checking for Postgres `23505` and SQLite `SQLITE_CONSTRAINT_UNIQUE`.
60
71
  */
61
72
  isUniqueViolation?: (err: unknown) => boolean;
73
+ /**
74
+ * Security gate (H-4): adopting a pre-existing local row matched by email is
75
+ * only performed when the IQAuth claims assert a verified email
76
+ * (`claims.email_verified === true`). When the claim is absent/false and a
77
+ * local row matches the email, the bridge **refuses to adopt** and throws
78
+ * `ProvisioningError("UNVERIFIED_EMAIL_ADOPT_REFUSED")` (fail closed) so an
79
+ * IdP that does not assert email verification cannot silently take over an
80
+ * existing account.
81
+ *
82
+ * Brand-new users (no email-matched row) are unaffected — there is nothing
83
+ * to take over, so they are inserted regardless of verification status.
84
+ *
85
+ * Set `true` ONLY when your issuer is trusted to never emit an unverified
86
+ * email for adoption (or you have a compensating control). Defaults to
87
+ * `false` (secure).
88
+ */
89
+ allowUnverifiedEmailAdopt?: boolean;
62
90
  }
63
91
  interface ProvisioningBridge<TUser> {
64
92
  /**
@@ -83,4 +111,4 @@ interface ProvisioningBridge<TUser> {
83
111
  */
84
112
  declare function createProvisioningBridge<TUser>(options: ProvisioningBridgeOptions<TUser>): ProvisioningBridge<TUser>;
85
113
 
86
- export { type ProvisioningBridge as P, type ProvisioningBridgeOptions as a, type ProvisioningStorage as b, createProvisioningBridge as c, type ProvisioningContext as d };
114
+ export { type ProvisioningBridge as P, type ProvisioningBridgeOptions as a, type ProvisioningStorage as b, createProvisioningBridge as c, type ProvisioningContext as d, ProvisioningError as e };
@@ -1,4 +1,4 @@
1
- import { J as JwtClaims } from './types-DZAflmmq.js';
1
+ import { J as JwtClaims } from './types-Bn8O-OEd.mjs';
2
2
 
3
3
  /**
4
4
  * createProvisioningBridge — server-side helper that lifts the
@@ -21,6 +21,17 @@ import { J as JwtClaims } from './types-DZAflmmq.js';
21
21
  * your local user table. See the JSDoc on each adapter for the contract.
22
22
  */
23
23
 
24
+ /**
25
+ * Thrown when the bridge refuses to adopt a pre-existing local account because
26
+ * the IQAuth claims do not assert a verified email (`email_verified !== true`)
27
+ * and the integrator has not opted in via `allowUnverifiedEmailAdopt`. Carries
28
+ * a stable `.code` so callers can branch (e.g. provision a brand-new row under
29
+ * a different identity instead of adopting the email-matched one).
30
+ */
31
+ declare class ProvisioningError extends Error {
32
+ code: string;
33
+ constructor(code: string, message: string);
34
+ }
24
35
  interface ProvisioningContext<TUser> {
25
36
  claims: JwtClaims;
26
37
  /** The local user record, looked up or freshly inserted. */
@@ -59,6 +70,23 @@ interface ProvisioningBridgeOptions<TUser> {
59
70
  * Defaults to checking for Postgres `23505` and SQLite `SQLITE_CONSTRAINT_UNIQUE`.
60
71
  */
61
72
  isUniqueViolation?: (err: unknown) => boolean;
73
+ /**
74
+ * Security gate (H-4): adopting a pre-existing local row matched by email is
75
+ * only performed when the IQAuth claims assert a verified email
76
+ * (`claims.email_verified === true`). When the claim is absent/false and a
77
+ * local row matches the email, the bridge **refuses to adopt** and throws
78
+ * `ProvisioningError("UNVERIFIED_EMAIL_ADOPT_REFUSED")` (fail closed) so an
79
+ * IdP that does not assert email verification cannot silently take over an
80
+ * existing account.
81
+ *
82
+ * Brand-new users (no email-matched row) are unaffected — there is nothing
83
+ * to take over, so they are inserted regardless of verification status.
84
+ *
85
+ * Set `true` ONLY when your issuer is trusted to never emit an unverified
86
+ * email for adoption (or you have a compensating control). Defaults to
87
+ * `false` (secure).
88
+ */
89
+ allowUnverifiedEmailAdopt?: boolean;
62
90
  }
63
91
  interface ProvisioningBridge<TUser> {
64
92
  /**
@@ -83,4 +111,4 @@ interface ProvisioningBridge<TUser> {
83
111
  */
84
112
  declare function createProvisioningBridge<TUser>(options: ProvisioningBridgeOptions<TUser>): ProvisioningBridge<TUser>;
85
113
 
86
- export { type ProvisioningBridge as P, type ProvisioningBridgeOptions as a, type ProvisioningStorage as b, createProvisioningBridge as c, type ProvisioningContext as d };
114
+ export { type ProvisioningBridge as P, type ProvisioningBridgeOptions as a, type ProvisioningStorage as b, createProvisioningBridge as c, type ProvisioningContext as d, ProvisioningError as e };
@@ -20,7 +20,7 @@ declare function encodePublishableKey(mode: KeyMode, payload: PublishableKeyPayl
20
20
  declare function parsePublishableKey(raw: string): ParsedPublishableKey | null;
21
21
  /**
22
22
  * Strict counterpart to `parsePublishableKey` — throws a typed `IQAuthError`
23
- * (`code: "CONFIG_INVALID"`) with an actionable message when the key is
23
+ * (`code: "config_invalid"`) with an actionable message when the key is
24
24
  * missing, malformed, or encodes a non-URL `iss`. Use this at SDK init so a
25
25
  * bad key fails loudly at boot instead of cryptically at first verify.
26
26
  */
@@ -20,7 +20,7 @@ declare function encodePublishableKey(mode: KeyMode, payload: PublishableKeyPayl
20
20
  declare function parsePublishableKey(raw: string): ParsedPublishableKey | null;
21
21
  /**
22
22
  * Strict counterpart to `parsePublishableKey` — throws a typed `IQAuthError`
23
- * (`code: "CONFIG_INVALID"`) with an actionable message when the key is
23
+ * (`code: "config_invalid"`) with an actionable message when the key is
24
24
  * missing, malformed, or encodes a non-URL `iss`. Use this at SDK init so a
25
25
  * bad key fails loudly at boot instead of cryptically at first verify.
26
26
  */
@@ -0,0 +1,52 @@
1
+ import { S as SessionError } from './index-Cko-d5po.mjs';
2
+ import 'csstype';
3
+ import 'react/jsx-runtime';
4
+ import 'react';
5
+ import './signIn-CReqfXsh.mjs';
6
+ import './publishableKey-f2kq-rKw.mjs';
7
+ import './types-Bn8O-OEd.mjs';
8
+ import './types-DnU2LhXR.mjs';
9
+
10
+ interface UseEffectivePermissionsOptions {
11
+ /**
12
+ * App key (OIDC client_id / manifest key) the permissions should be
13
+ * resolved against. Required — permissions in IQAuth are app-scoped.
14
+ */
15
+ appKey: string;
16
+ /** Disable the network fetch (claims fallback still applies). */
17
+ enabled?: boolean;
18
+ /** Stale window in ms. Default 5min. */
19
+ staleTime?: number;
20
+ /**
21
+ * Override the issuer URL the hook calls. Defaults to the issuer the
22
+ * provider was booted with.
23
+ */
24
+ issuer?: string;
25
+ }
26
+ interface UseEffectivePermissionsResult {
27
+ /** Normalized, wildcard-collapsed set of granted permission ids. */
28
+ permissions: string[];
29
+ /** Wildcard-aware membership check. Identical semantics on server. */
30
+ hasPermission: (id: string) => boolean;
31
+ /** True only while a fetch is actively in flight (and no cached data yet). */
32
+ isLoading: boolean;
33
+ /** Last fetch error, if any. Cleared on next successful refetch. */
34
+ error: SessionError | null;
35
+ /** Force a refetch, bypassing the staleTime window. */
36
+ refetch: () => Promise<void>;
37
+ }
38
+ /**
39
+ * Canonical hook for resolving the *full* effective permission set of the
40
+ * signed-in user against a single app. Use this whenever the JWT
41
+ * `entitlements` claim is too small (apps with hundreds of nodes can't fit
42
+ * them in the token).
43
+ *
44
+ * Requires a `<QueryClientProvider>` (`@tanstack/react-query`) somewhere
45
+ * above this hook in the tree.
46
+ *
47
+ * Returns `{ permissions, hasPermission, isLoading, error, refetch }`.
48
+ * `hasPermission` honours wildcard semantics (`*`, `metrics.*`).
49
+ */
50
+ declare function useEffectivePermissions(opts: UseEffectivePermissionsOptions): UseEffectivePermissionsResult;
51
+
52
+ export { type UseEffectivePermissionsOptions, type UseEffectivePermissionsResult, useEffectivePermissions };
@@ -0,0 +1,52 @@
1
+ import { S as SessionError } from './index-RNqwEcmY.js';
2
+ import 'csstype';
3
+ import 'react/jsx-runtime';
4
+ import 'react';
5
+ import './signIn-Cfa1GTpO.js';
6
+ import './publishableKey-f2kq-rKw.js';
7
+ import './types-Bn8O-OEd.js';
8
+ import './types-DnU2LhXR.js';
9
+
10
+ interface UseEffectivePermissionsOptions {
11
+ /**
12
+ * App key (OIDC client_id / manifest key) the permissions should be
13
+ * resolved against. Required — permissions in IQAuth are app-scoped.
14
+ */
15
+ appKey: string;
16
+ /** Disable the network fetch (claims fallback still applies). */
17
+ enabled?: boolean;
18
+ /** Stale window in ms. Default 5min. */
19
+ staleTime?: number;
20
+ /**
21
+ * Override the issuer URL the hook calls. Defaults to the issuer the
22
+ * provider was booted with.
23
+ */
24
+ issuer?: string;
25
+ }
26
+ interface UseEffectivePermissionsResult {
27
+ /** Normalized, wildcard-collapsed set of granted permission ids. */
28
+ permissions: string[];
29
+ /** Wildcard-aware membership check. Identical semantics on server. */
30
+ hasPermission: (id: string) => boolean;
31
+ /** True only while a fetch is actively in flight (and no cached data yet). */
32
+ isLoading: boolean;
33
+ /** Last fetch error, if any. Cleared on next successful refetch. */
34
+ error: SessionError | null;
35
+ /** Force a refetch, bypassing the staleTime window. */
36
+ refetch: () => Promise<void>;
37
+ }
38
+ /**
39
+ * Canonical hook for resolving the *full* effective permission set of the
40
+ * signed-in user against a single app. Use this whenever the JWT
41
+ * `entitlements` claim is too small (apps with hundreds of nodes can't fit
42
+ * them in the token).
43
+ *
44
+ * Requires a `<QueryClientProvider>` (`@tanstack/react-query`) somewhere
45
+ * above this hook in the tree.
46
+ *
47
+ * Returns `{ permissions, hasPermission, isLoading, error, refetch }`.
48
+ * `hasPermission` honours wildcard semantics (`*`, `metrics.*`).
49
+ */
50
+ declare function useEffectivePermissions(opts: UseEffectivePermissionsOptions): UseEffectivePermissionsResult;
51
+
52
+ export { type UseEffectivePermissionsOptions, type UseEffectivePermissionsResult, useEffectivePermissions };
@@ -0,0 +1,239 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __esm = (fn, res) => function __init() {
7
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
8
+ };
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
22
+
23
+ // src/errors.ts
24
+ var init_errors = __esm({
25
+ "src/errors.ts"() {
26
+ "use strict";
27
+ }
28
+ });
29
+
30
+ // src/browser/storage.ts
31
+ var init_storage = __esm({
32
+ "src/browser/storage.ts"() {
33
+ "use strict";
34
+ }
35
+ });
36
+
37
+ // src/browser/pkce.ts
38
+ var init_pkce = __esm({
39
+ "src/browser/pkce.ts"() {
40
+ "use strict";
41
+ }
42
+ });
43
+
44
+ // src/browser/signIn.ts
45
+ var init_signIn = __esm({
46
+ "src/browser/signIn.ts"() {
47
+ "use strict";
48
+ init_pkce();
49
+ init_storage();
50
+ }
51
+ });
52
+
53
+ // src/react-permissions.ts
54
+ var react_permissions_exports = {};
55
+ __export(react_permissions_exports, {
56
+ useEffectivePermissions: () => useEffectivePermissions
57
+ });
58
+ module.exports = __toCommonJS(react_permissions_exports);
59
+
60
+ // src/react/permissions.tsx
61
+ var import_react2 = require("react");
62
+ var import_react_query = require("@tanstack/react-query");
63
+
64
+ // src/permissions/wildcard.ts
65
+ var SUFFIX = ".*";
66
+ function wildcardPrefix(pattern) {
67
+ return pattern.slice(0, -SUFFIX.length);
68
+ }
69
+ function hasPermission(set, id) {
70
+ if (!id) return false;
71
+ if (!set) return false;
72
+ if (id === "*") {
73
+ for (const entry of set) if (entry === "*") return true;
74
+ return false;
75
+ }
76
+ const queryIsWildcard = id.endsWith(SUFFIX);
77
+ const queryPrefix = queryIsWildcard ? wildcardPrefix(id) : null;
78
+ for (const entry of set) {
79
+ if (!entry) continue;
80
+ if (entry === "*") return true;
81
+ if (entry === id) return true;
82
+ if (entry.endsWith(SUFFIX)) {
83
+ const prefix = wildcardPrefix(entry);
84
+ if (!queryIsWildcard) {
85
+ if (id === prefix) return true;
86
+ if (id.startsWith(prefix + ".")) return true;
87
+ } else {
88
+ if (queryPrefix === prefix) return true;
89
+ if (queryPrefix !== null && queryPrefix.startsWith(prefix + ".")) return true;
90
+ }
91
+ }
92
+ }
93
+ return false;
94
+ }
95
+ function expandPermissions(set) {
96
+ if (!set) return [];
97
+ const seen = /* @__PURE__ */ new Set();
98
+ for (const raw of set) {
99
+ if (typeof raw !== "string" || raw.length === 0) continue;
100
+ seen.add(raw);
101
+ }
102
+ if (seen.has("*")) return ["*"];
103
+ const wildcards = [];
104
+ for (const entry of seen) if (entry.endsWith(SUFFIX)) wildcards.push(entry);
105
+ const out = [];
106
+ for (const entry of seen) {
107
+ let covered = false;
108
+ for (const w of wildcards) {
109
+ if (w === entry) continue;
110
+ const prefix = wildcardPrefix(w);
111
+ if (entry === prefix) {
112
+ covered = true;
113
+ break;
114
+ }
115
+ if (entry.startsWith(prefix + ".")) {
116
+ covered = true;
117
+ break;
118
+ }
119
+ }
120
+ if (!covered) out.push(entry);
121
+ }
122
+ out.sort();
123
+ return out;
124
+ }
125
+
126
+ // src/react/index.tsx
127
+ var import_react = require("react");
128
+
129
+ // src/browser/sessionManager.ts
130
+ init_errors();
131
+
132
+ // src/publishableKey.ts
133
+ init_errors();
134
+
135
+ // src/browser/sessionManager.ts
136
+ init_storage();
137
+
138
+ // src/react/index.tsx
139
+ init_signIn();
140
+
141
+ // src/browser/accountRegistry.ts
142
+ init_storage();
143
+ var COOKIE_MAX_AGE = 60 * 60 * 24 * 30;
144
+
145
+ // src/react/index.tsx
146
+ var import_jsx_runtime = require("react/jsx-runtime");
147
+ var IQAuthContext = (0, import_react.createContext)(null);
148
+ function __useIQAuthInternal() {
149
+ return useCtx();
150
+ }
151
+ function useCtx() {
152
+ const ctx = (0, import_react.useContext)(IQAuthContext);
153
+ if (!ctx) throw new Error("IQAuth hooks must be used inside <IQAuthProvider>");
154
+ return ctx;
155
+ }
156
+ var MultisessionContext = (0, import_react.createContext)(null);
157
+ var SDK_CSS_MAX_LEN = 50 * 1024;
158
+
159
+ // src/react/permissions.tsx
160
+ var DEFAULT_PERMS_STALE_MS = 5 * 60 * 1e3;
161
+ function projectAllowedScopes(rows) {
162
+ if (!Array.isArray(rows)) return [];
163
+ const allowed = [];
164
+ const denied = /* @__PURE__ */ new Set();
165
+ for (const r of rows) {
166
+ if (!r || typeof r.scope !== "string" || !r.scope) continue;
167
+ if (r.effect === "deny") denied.add(r.scope);
168
+ else allowed.push(r.scope);
169
+ }
170
+ return expandPermissions(allowed.filter((s) => !denied.has(s)));
171
+ }
172
+ function useEffectivePermissions(opts) {
173
+ const { manager, snapshot } = __useIQAuthInternal();
174
+ const { appKey, enabled = true, staleTime = DEFAULT_PERMS_STALE_MS, issuer } = opts;
175
+ const claims = snapshot.claims;
176
+ const isPlatformAdmin = Array.isArray(claims?.roles) && claims.roles.includes("platform_admin");
177
+ const userId = snapshot.user?.sub ?? null;
178
+ const tenantId = snapshot.tenantId ?? claims?.tenantId ?? null;
179
+ const issuerUrl = (issuer ?? manager.issuerUrl).replace(/\/$/, "");
180
+ const queryEnabled = enabled && !!userId && !!tenantId && !!appKey && !isPlatformAdmin;
181
+ const query = (0, import_react_query.useQuery)({
182
+ queryKey: ["iqauth", "effective-permissions", issuerUrl, tenantId, userId, appKey],
183
+ queryFn: async () => {
184
+ const url = `${issuerUrl}/api/v1/tenants/${encodeURIComponent(tenantId)}/users/${encodeURIComponent(userId)}/permissions/effective?appKey=${encodeURIComponent(appKey)}`;
185
+ const res = await manager.fetch(url);
186
+ const json = await res.json().catch(() => ({}));
187
+ if (!res.ok) {
188
+ const code = json?.error?.code || `HTTP_${res.status}`;
189
+ const message = json?.error?.message || `HTTP ${res.status}`;
190
+ const e = { code, message };
191
+ throw e;
192
+ }
193
+ const rows = Array.isArray(json) ? json : json?.data ?? [];
194
+ return projectAllowedScopes(rows);
195
+ },
196
+ enabled: queryEnabled,
197
+ staleTime,
198
+ refetchOnWindowFocus: false,
199
+ retry: false
200
+ });
201
+ const refetch = (0, import_react2.useCallback)(async () => {
202
+ if (!queryEnabled) return;
203
+ await query.refetch();
204
+ }, [query, queryEnabled]);
205
+ return (0, import_react2.useMemo)(() => {
206
+ if (isPlatformAdmin) {
207
+ return {
208
+ permissions: ["*"],
209
+ hasPermission: () => true,
210
+ isLoading: false,
211
+ error: null,
212
+ refetch
213
+ };
214
+ }
215
+ const fetched = query.data;
216
+ const perms = fetched ?? expandPermissions(claims?.entitlements ?? []);
217
+ const isLoading = queryEnabled && query.isLoading;
218
+ const error = query.error ? "code" in query.error && typeof query.error.code === "string" ? query.error : { code: "PERMISSIONS_FETCH_FAILED", message: query.error.message || "Failed to fetch permissions" } : null;
219
+ return {
220
+ permissions: perms,
221
+ hasPermission: (id) => hasPermission(perms, id),
222
+ isLoading,
223
+ error,
224
+ refetch
225
+ };
226
+ }, [
227
+ isPlatformAdmin,
228
+ queryEnabled,
229
+ query.data,
230
+ query.isLoading,
231
+ query.error,
232
+ claims?.entitlements,
233
+ refetch
234
+ ]);
235
+ }
236
+ // Annotate the CommonJS export names for ESM import in node:
237
+ 0 && (module.exports = {
238
+ useEffectivePermissions
239
+ });