@iqauth/sdk 2.0.0

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 (112) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +287 -0
  3. package/dist/browser-session.d.mts +12 -0
  4. package/dist/browser-session.d.ts +12 -0
  5. package/dist/browser-session.js +1812 -0
  6. package/dist/browser-session.mjs +28 -0
  7. package/dist/browser.d.mts +46 -0
  8. package/dist/browser.d.ts +46 -0
  9. package/dist/browser.js +768 -0
  10. package/dist/browser.mjs +47 -0
  11. package/dist/chunk-5HF3OBNO.mjs +189 -0
  12. package/dist/chunk-5WFR6Y33.mjs +59 -0
  13. package/dist/chunk-6I6RM4MN.mjs +51 -0
  14. package/dist/chunk-73R6BEGO.mjs +176 -0
  15. package/dist/chunk-E46DKOVI.mjs +632 -0
  16. package/dist/chunk-JQWYIIIS.mjs +1740 -0
  17. package/dist/chunk-X3K3WOBR.mjs +64 -0
  18. package/dist/chunk-Y6FXYEAI.mjs +10 -0
  19. package/dist/cli/index.d.mts +1 -0
  20. package/dist/cli/index.d.ts +1 -0
  21. package/dist/cli/index.js +581 -0
  22. package/dist/cli/index.mjs +57 -0
  23. package/dist/client-C1DXfB8Z.d.mts +911 -0
  24. package/dist/client-CggvJmmm.d.ts +911 -0
  25. package/dist/dev-FUTJZSWN.mjs +56 -0
  26. package/dist/doctor-OHJRZBBT.mjs +89 -0
  27. package/dist/errors-CDdl24MP.d.mts +52 -0
  28. package/dist/errors-CDdl24MP.d.ts +52 -0
  29. package/dist/express-BKAXB5Nl.d.ts +61 -0
  30. package/dist/express-CpfyYTmw.d.mts +61 -0
  31. package/dist/express.d.mts +45 -0
  32. package/dist/express.d.ts +45 -0
  33. package/dist/express.js +2252 -0
  34. package/dist/express.mjs +122 -0
  35. package/dist/fastify.d.mts +23 -0
  36. package/dist/fastify.d.ts +23 -0
  37. package/dist/fastify.js +2062 -0
  38. package/dist/fastify.mjs +118 -0
  39. package/dist/hono.d.mts +22 -0
  40. package/dist/hono.d.ts +22 -0
  41. package/dist/hono.js +2051 -0
  42. package/dist/hono.mjs +107 -0
  43. package/dist/index.d.mts +6 -0
  44. package/dist/index.d.ts +6 -0
  45. package/dist/index.js +2070 -0
  46. package/dist/index.mjs +83 -0
  47. package/dist/init-LLCSQGNL.mjs +198 -0
  48. package/dist/keys-NLWFAOEM.mjs +63 -0
  49. package/dist/mobile.d.mts +11 -0
  50. package/dist/mobile.d.ts +11 -0
  51. package/dist/mobile.js +1809 -0
  52. package/dist/mobile.mjs +25 -0
  53. package/dist/next.d.mts +37 -0
  54. package/dist/next.d.ts +37 -0
  55. package/dist/next.js +2078 -0
  56. package/dist/next.mjs +130 -0
  57. package/dist/publishableKey-B5DIK81A.d.mts +24 -0
  58. package/dist/publishableKey-B5DIK81A.d.ts +24 -0
  59. package/dist/react.d.mts +196 -0
  60. package/dist/react.d.ts +196 -0
  61. package/dist/react.js +1457 -0
  62. package/dist/react.mjs +787 -0
  63. package/dist/server/handlers.d.mts +96 -0
  64. package/dist/server/handlers.d.ts +96 -0
  65. package/dist/server/handlers.js +243 -0
  66. package/dist/server/handlers.mjs +14 -0
  67. package/dist/server.d.mts +14 -0
  68. package/dist/server.d.ts +14 -0
  69. package/dist/server.js +2195 -0
  70. package/dist/server.mjs +47 -0
  71. package/dist/service.d.mts +11 -0
  72. package/dist/service.d.ts +11 -0
  73. package/dist/service.js +1809 -0
  74. package/dist/service.mjs +25 -0
  75. package/dist/signIn-C8f6qVjD.d.mts +238 -0
  76. package/dist/signIn-Cy2lbEXb.d.ts +238 -0
  77. package/dist/types-Cxl3bQHt.d.mts +900 -0
  78. package/dist/types-Cxl3bQHt.d.ts +900 -0
  79. package/docs/APP_INTEGRATION_MATRIX.md +59 -0
  80. package/docs/BROWSER_SESSION_MIGRATION.md +69 -0
  81. package/docs/FRESH_IMPLEMENTATION_GUIDE.md +188 -0
  82. package/docs/TARBALL_RELEASE_WORKFLOW.md +98 -0
  83. package/docs/V1_TO_V2_UPGRADE_GUIDE.md +318 -0
  84. package/docs/guides/api-keys.md +130 -0
  85. package/docs/guides/app-registration.md +149 -0
  86. package/docs/guides/auth-flows.md +168 -0
  87. package/docs/guides/branding.md +160 -0
  88. package/docs/guides/entitlements.md +115 -0
  89. package/docs/guides/entity-hierarchy.md +200 -0
  90. package/docs/guides/error-handling.md +251 -0
  91. package/docs/guides/gdpr-compliance.md +123 -0
  92. package/docs/guides/invitations.md +143 -0
  93. package/docs/guides/mfa-enrollment.md +170 -0
  94. package/docs/guides/middleware-reference.md +205 -0
  95. package/docs/guides/mobile-native.md +110 -0
  96. package/docs/guides/roles-and-permissions.md +220 -0
  97. package/docs/guides/scoped-authorization.md +247 -0
  98. package/docs/guides/server-platform-integration.md +52 -0
  99. package/docs/guides/service-automation-integration.md +36 -0
  100. package/docs/guides/session-management.md +97 -0
  101. package/docs/guides/tenant-management.md +216 -0
  102. package/docs/guides/token-verification.md +178 -0
  103. package/docs/guides/user-management.md +184 -0
  104. package/docs/guides/webhooks.md +136 -0
  105. package/docs/integration-prompts/README.md +20 -0
  106. package/docs/integration-prompts/first-party-browser-app.md +29 -0
  107. package/docs/integration-prompts/install-from-tarball.md +41 -0
  108. package/docs/integration-prompts/migrate-from-local-packages-source.md +57 -0
  109. package/docs/integration-prompts/native-mobile-app.md +24 -0
  110. package/docs/integration-prompts/server-platform-app.md +20 -0
  111. package/docs/integration-prompts/service-automation-app.md +20 -0
  112. package/package.json +115 -0
@@ -0,0 +1,47 @@
1
+ import {
2
+ REFRESH_COOKIE,
3
+ SessionManager,
4
+ buildSignInUrl,
5
+ clearCookie,
6
+ createPkcePair,
7
+ getCookie,
8
+ handleAuthCallback,
9
+ randomUrlSafe,
10
+ redirectToSignIn,
11
+ s256Challenge,
12
+ setCookie,
13
+ signIn,
14
+ signOut
15
+ } from "./chunk-E46DKOVI.mjs";
16
+ import {
17
+ encodePublishableKey,
18
+ isPublishableKey,
19
+ isSecretKey,
20
+ parsePublishableKey
21
+ } from "./chunk-5WFR6Y33.mjs";
22
+ import {
23
+ ErrorCodes,
24
+ IQAuthError
25
+ } from "./chunk-6I6RM4MN.mjs";
26
+ import "./chunk-Y6FXYEAI.mjs";
27
+ export {
28
+ ErrorCodes,
29
+ IQAuthError,
30
+ REFRESH_COOKIE,
31
+ SessionManager,
32
+ buildSignInUrl,
33
+ clearCookie,
34
+ createPkcePair,
35
+ encodePublishableKey,
36
+ getCookie,
37
+ handleAuthCallback,
38
+ isPublishableKey,
39
+ isSecretKey,
40
+ parsePublishableKey,
41
+ randomUrlSafe,
42
+ redirectToSignIn,
43
+ s256Challenge,
44
+ setCookie,
45
+ signIn,
46
+ signOut
47
+ };
@@ -0,0 +1,189 @@
1
+ import {
2
+ parsePublishableKey
3
+ } from "./chunk-5WFR6Y33.mjs";
4
+
5
+ // src/server/handlers.ts
6
+ var ACCESS_TOKEN_TTL_SECONDS = 60 * 15;
7
+ var REFRESH_TOKEN_TTL_SECONDS = 60 * 60 * 24 * 30;
8
+ function resolve(config) {
9
+ const parsed = parsePublishableKey(config.publishableKey);
10
+ if (!parsed) {
11
+ throw new Error(
12
+ "@iqauth/sdk: invalid publishable key passed to iqAuth helpers (expected pk_test_\u2026 or pk_live_\u2026)"
13
+ );
14
+ }
15
+ const inferredIssuer = parsed.iss.startsWith("http") ? parsed.iss : `https://${parsed.iss}`;
16
+ return {
17
+ publishableKey: config.publishableKey,
18
+ secretKey: config.secretKey,
19
+ issuer: (config.issuer ?? inferredIssuer).replace(/\/+$/, ""),
20
+ accessCookieName: config.accessCookieName ?? "iqauth_at",
21
+ refreshCookieName: config.refreshCookieName ?? "iqauth_rt",
22
+ cookieDomain: config.cookieDomain,
23
+ sameSite: config.sameSite ?? "lax",
24
+ secure: config.secure ?? true,
25
+ cookiePath: config.cookiePath ?? "/",
26
+ tokenPath: config.tokenPath ?? "/oidc/token",
27
+ refreshPath: config.refreshPath ?? "/api/v1/auth/refresh",
28
+ logoutPath: config.logoutPath ?? "/api/v1/auth/logout",
29
+ fetchImpl: config.fetchImpl ?? (typeof fetch !== "undefined" ? fetch.bind(globalThis) : (() => {
30
+ throw new Error("global fetch is unavailable; pass fetchImpl");
31
+ })),
32
+ appId: parsed.appId,
33
+ tenantId: parsed.tenantId
34
+ };
35
+ }
36
+ function makeCookie(cfg, name, value, maxAge, httpOnly = true) {
37
+ return {
38
+ name,
39
+ value,
40
+ maxAge,
41
+ httpOnly,
42
+ secure: cfg.secure,
43
+ sameSite: cfg.sameSite,
44
+ path: cfg.cookiePath,
45
+ domain: cfg.cookieDomain
46
+ };
47
+ }
48
+ function clearCookies(cfg) {
49
+ return [
50
+ makeCookie(cfg, cfg.accessCookieName, "", 0),
51
+ makeCookie(cfg, cfg.refreshCookieName, "", 0)
52
+ ];
53
+ }
54
+ function serializeCookie(d) {
55
+ const parts = [`${d.name}=${encodeURIComponent(d.value)}`];
56
+ parts.push(`Path=${d.path}`);
57
+ if (d.domain) parts.push(`Domain=${d.domain}`);
58
+ parts.push(`Max-Age=${d.maxAge}`);
59
+ if (d.secure) parts.push("Secure");
60
+ if (d.httpOnly) parts.push("HttpOnly");
61
+ parts.push(`SameSite=${d.sameSite}`);
62
+ return parts.join("; ");
63
+ }
64
+ async function handleCallback(config, input) {
65
+ const cfg = resolve(config);
66
+ if (!input.code || !input.redirectUri) {
67
+ return {
68
+ status: 400,
69
+ body: { success: false, error: { code: "VALIDATION_ERROR", message: "code and redirectUri are required" } },
70
+ cookies: []
71
+ };
72
+ }
73
+ if (!cfg.secretKey) {
74
+ return {
75
+ status: 500,
76
+ body: { success: false, error: { code: "INTERNAL_ERROR", message: "secretKey is required for the callback handler" } },
77
+ cookies: []
78
+ };
79
+ }
80
+ const body = new URLSearchParams({
81
+ grant_type: "authorization_code",
82
+ code: input.code,
83
+ redirect_uri: input.redirectUri,
84
+ client_id: cfg.appId
85
+ });
86
+ if (input.codeVerifier) body.set("code_verifier", input.codeVerifier);
87
+ const res = await cfg.fetchImpl(`${cfg.issuer}${cfg.tokenPath}`, {
88
+ method: "POST",
89
+ headers: {
90
+ "Content-Type": "application/x-www-form-urlencoded",
91
+ Authorization: `Basic ${typeof btoa === "function" ? btoa(`${cfg.appId}:${cfg.secretKey}`) : Buffer.from(`${cfg.appId}:${cfg.secretKey}`).toString("base64")}`
92
+ },
93
+ body: body.toString()
94
+ });
95
+ const json = await res.json().catch(() => ({}));
96
+ if (!res.ok || !json.access_token) {
97
+ return {
98
+ status: res.status || 502,
99
+ body: {
100
+ success: false,
101
+ error: {
102
+ code: json.error || "OIDC_EXCHANGE_FAILED",
103
+ message: json.error_description || "Authorization code exchange failed"
104
+ }
105
+ },
106
+ cookies: []
107
+ };
108
+ }
109
+ const cookies = [];
110
+ cookies.push(
111
+ makeCookie(cfg, cfg.accessCookieName, json.access_token, json.expires_in ?? ACCESS_TOKEN_TTL_SECONDS)
112
+ );
113
+ if (json.refresh_token) {
114
+ cookies.push(makeCookie(cfg, cfg.refreshCookieName, json.refresh_token, REFRESH_TOKEN_TTL_SECONDS));
115
+ }
116
+ return {
117
+ status: 200,
118
+ body: { success: true, data: { authenticated: true } },
119
+ cookies
120
+ };
121
+ }
122
+ async function handleRefresh(config, input) {
123
+ const cfg = resolve(config);
124
+ const refreshToken = input.refreshToken;
125
+ if (!refreshToken) {
126
+ return {
127
+ status: 401,
128
+ body: { success: false, error: { code: "TOKEN_INVALID", message: "Missing refresh token" } },
129
+ cookies: clearCookies(cfg)
130
+ };
131
+ }
132
+ const res = await cfg.fetchImpl(`${cfg.issuer}${cfg.refreshPath}`, {
133
+ method: "POST",
134
+ headers: { "Content-Type": "application/json" },
135
+ body: JSON.stringify({ refreshToken })
136
+ });
137
+ const json = await res.json().catch(() => ({}));
138
+ if (!res.ok || !json.success || !json.data?.accessToken) {
139
+ return {
140
+ status: res.status || 401,
141
+ body: {
142
+ success: false,
143
+ error: {
144
+ code: json.error?.code || "TOKEN_INVALID",
145
+ message: json.error?.message || "Refresh failed"
146
+ }
147
+ },
148
+ cookies: clearCookies(cfg)
149
+ };
150
+ }
151
+ const cookies = [
152
+ makeCookie(cfg, cfg.accessCookieName, json.data.accessToken, ACCESS_TOKEN_TTL_SECONDS)
153
+ ];
154
+ if (json.data.refreshToken) {
155
+ cookies.push(makeCookie(cfg, cfg.refreshCookieName, json.data.refreshToken, REFRESH_TOKEN_TTL_SECONDS));
156
+ }
157
+ return {
158
+ status: 200,
159
+ body: { success: true, data: { accessToken: json.data.accessToken } },
160
+ cookies
161
+ };
162
+ }
163
+ async function handleSignout(config, input) {
164
+ const cfg = resolve(config);
165
+ if (input.accessToken) {
166
+ try {
167
+ await cfg.fetchImpl(`${cfg.issuer}${cfg.logoutPath}`, {
168
+ method: "POST",
169
+ headers: {
170
+ "Content-Type": "application/json",
171
+ Authorization: `Bearer ${input.accessToken}`
172
+ }
173
+ });
174
+ } catch {
175
+ }
176
+ }
177
+ return {
178
+ status: 200,
179
+ body: { success: true, data: { signedOut: true } },
180
+ cookies: clearCookies(cfg)
181
+ };
182
+ }
183
+
184
+ export {
185
+ serializeCookie,
186
+ handleCallback,
187
+ handleRefresh,
188
+ handleSignout
189
+ };
@@ -0,0 +1,59 @@
1
+ import {
2
+ __require
3
+ } from "./chunk-Y6FXYEAI.mjs";
4
+
5
+ // src/publishableKey.ts
6
+ function b64urlEncode(input) {
7
+ if (typeof btoa === "function") {
8
+ const bytes = new TextEncoder().encode(input);
9
+ let bin = "";
10
+ for (let i = 0; i < bytes.byteLength; i++) bin += String.fromCharCode(bytes[i]);
11
+ return btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
12
+ }
13
+ const { Buffer } = __require("buffer");
14
+ return Buffer.from(input, "utf8").toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
15
+ }
16
+ function b64urlDecode(input) {
17
+ const pad = input.length % 4 === 0 ? "" : "=".repeat(4 - input.length % 4);
18
+ const normalized = input.replace(/-/g, "+").replace(/_/g, "/") + pad;
19
+ if (typeof atob === "function") {
20
+ const bin = atob(normalized);
21
+ const bytes = new Uint8Array(bin.length);
22
+ for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
23
+ return new TextDecoder().decode(bytes);
24
+ }
25
+ const { Buffer } = __require("buffer");
26
+ return Buffer.from(normalized, "base64").toString("utf8");
27
+ }
28
+ function encodePublishableKey(mode, payload) {
29
+ if (mode !== "test" && mode !== "live") throw new Error(`Invalid mode: ${mode}`);
30
+ return `pk_${mode}_${b64urlEncode(JSON.stringify(payload))}`;
31
+ }
32
+ function parsePublishableKey(raw) {
33
+ if (typeof raw !== "string") return null;
34
+ const m = raw.match(/^pk_(test|live)_([A-Za-z0-9_-]+)$/);
35
+ if (!m) return null;
36
+ try {
37
+ const json = JSON.parse(b64urlDecode(m[2]));
38
+ if (!json || typeof json !== "object") return null;
39
+ if (typeof json.iss !== "string" || typeof json.appId !== "string" || typeof json.tenantId !== "string" || typeof json.kid !== "string") {
40
+ return null;
41
+ }
42
+ return { mode: m[1], iss: json.iss, appId: json.appId, tenantId: json.tenantId, kid: json.kid, raw };
43
+ } catch {
44
+ return null;
45
+ }
46
+ }
47
+ function isPublishableKey(raw) {
48
+ return typeof raw === "string" && /^pk_(test|live)_/.test(raw);
49
+ }
50
+ function isSecretKey(raw) {
51
+ return typeof raw === "string" && /^sk_(test|live)_/.test(raw);
52
+ }
53
+
54
+ export {
55
+ encodePublishableKey,
56
+ parsePublishableKey,
57
+ isPublishableKey,
58
+ isSecretKey
59
+ };
@@ -0,0 +1,51 @@
1
+ // src/errors.ts
2
+ var IQAuthError = class extends Error {
3
+ constructor(code, message, status, raw) {
4
+ super(message);
5
+ this.name = "IQAuthError";
6
+ this.code = code;
7
+ this.status = status;
8
+ this.raw = raw;
9
+ }
10
+ };
11
+ var ErrorCodes = {
12
+ VALIDATION_ERROR: "VALIDATION_ERROR",
13
+ INVALID_CREDENTIALS: "INVALID_CREDENTIALS",
14
+ ACCOUNT_INACTIVE: "ACCOUNT_INACTIVE",
15
+ ACCOUNT_LOCKED: "ACCOUNT_LOCKED",
16
+ INSUFFICIENT_PERMISSIONS: "INSUFFICIENT_PERMISSIONS",
17
+ TOKEN_INVALID: "TOKEN_INVALID",
18
+ TOKEN_EXPIRED: "TOKEN_EXPIRED",
19
+ TOKEN_REVOKED: "TOKEN_REVOKED",
20
+ USER_INACTIVE: "USER_INACTIVE",
21
+ INTERNAL_ERROR: "INTERNAL_ERROR",
22
+ NOT_FOUND: "NOT_FOUND",
23
+ SESSION_INVALID: "SESSION_INVALID",
24
+ SESSION_EXPIRED: "SESSION_EXPIRED",
25
+ REFRESH_TOKEN_REUSED: "REFRESH_TOKEN_REUSED",
26
+ PASSWORD_EXPIRED: "PASSWORD_EXPIRED",
27
+ PIN_EXPIRED: "PIN_EXPIRED",
28
+ PASSWORD_POLICY_VIOLATION: "PASSWORD_POLICY_VIOLATION",
29
+ MFA_INVALID_CODE: "MFA_INVALID_CODE",
30
+ MFA_METHOD_UNAVAILABLE: "MFA_METHOD_UNAVAILABLE",
31
+ MFA_RATE_LIMITED: "MFA_RATE_LIMITED",
32
+ MFA_ENROLLMENT_REQUIRED: "MFA_ENROLLMENT_REQUIRED",
33
+ API_KEY_REQUIRED: "API_KEY_REQUIRED",
34
+ API_KEY_INVALID: "API_KEY_INVALID",
35
+ AUTH_REQUIRED: "AUTH_REQUIRED",
36
+ ALREADY_EXISTS: "ALREADY_EXISTS",
37
+ FORBIDDEN: "FORBIDDEN",
38
+ OAUTH_NOT_CONFIGURED: "OAUTH_NOT_CONFIGURED",
39
+ UPLOAD_ERROR: "UPLOAD_ERROR",
40
+ EMAIL_SERVICE_UNAVAILABLE: "EMAIL_SERVICE_UNAVAILABLE",
41
+ INVALID_CODE: "INVALID_CODE",
42
+ CODE_ALREADY_USED: "CODE_ALREADY_USED",
43
+ CODE_EXPIRED: "CODE_EXPIRED",
44
+ CODE_IP_MISMATCH: "CODE_IP_MISMATCH",
45
+ UNKNOWN_PAYLOAD: "UNKNOWN_PAYLOAD"
46
+ };
47
+
48
+ export {
49
+ IQAuthError,
50
+ ErrorCodes
51
+ };
@@ -0,0 +1,176 @@
1
+ import {
2
+ parsePublishableKey
3
+ } from "./chunk-5WFR6Y33.mjs";
4
+ import {
5
+ IQAuthClient
6
+ } from "./chunk-JQWYIIIS.mjs";
7
+ import {
8
+ IQAuthError
9
+ } from "./chunk-6I6RM4MN.mjs";
10
+
11
+ // src/middleware/express.ts
12
+ var KNOWN_AUTH_ERROR_CODES = /* @__PURE__ */ new Set([
13
+ "TOKEN_INVALID",
14
+ "TOKEN_EXPIRED",
15
+ "TOKEN_REVOKED",
16
+ "SESSION_EXPIRED",
17
+ "SESSION_INVALID",
18
+ "AUTH_REQUIRED"
19
+ ]);
20
+ var DEFAULT_ACCESS_COOKIE = "iqauth_at";
21
+ var DEFAULT_REFRESH_COOKIE = "iqauth_rt";
22
+ function getAuthorizationHeader(req) {
23
+ const raw = req.headers?.authorization;
24
+ if (Array.isArray(raw)) return raw[0];
25
+ return raw;
26
+ }
27
+ function readCookie(req, name) {
28
+ const cookieJar = req.cookies;
29
+ if (cookieJar && typeof cookieJar[name] === "string") return cookieJar[name];
30
+ const raw = req.headers?.cookie;
31
+ const header = Array.isArray(raw) ? raw.join("; ") : raw;
32
+ if (!header) return void 0;
33
+ const target = `${name}=`;
34
+ for (const seg of header.split(";")) {
35
+ const trimmed = seg.trim();
36
+ if (trimmed.startsWith(target)) {
37
+ try {
38
+ return decodeURIComponent(trimmed.slice(target.length));
39
+ } catch {
40
+ return trimmed.slice(target.length);
41
+ }
42
+ }
43
+ }
44
+ return void 0;
45
+ }
46
+ function clientFromPublishableKey(opts) {
47
+ const parsed = parsePublishableKey(opts.publishableKey);
48
+ if (!parsed) {
49
+ throw new Error("iqAuthMiddleware: invalid publishable key");
50
+ }
51
+ const issuer = (opts.issuer ?? (parsed.iss.startsWith("http") ? parsed.iss : `https://${parsed.iss}`)).replace(/\/+$/, "");
52
+ return new IQAuthClient({ baseUrl: issuer, environment: "server" });
53
+ }
54
+ function iqAuthMiddleware(clientOrOptions, options = {}) {
55
+ let client;
56
+ let resolvedOptions;
57
+ const isPkConfig = !!clientOrOptions && typeof clientOrOptions.publishableKey === "string" && !clientOrOptions.tokens;
58
+ if (isPkConfig) {
59
+ const cfg = clientOrOptions;
60
+ client = clientFromPublishableKey(cfg);
61
+ const { publishableKey: _pk, issuer: _iss, ...rest } = cfg;
62
+ void _pk;
63
+ void _iss;
64
+ resolvedOptions = rest;
65
+ } else {
66
+ client = clientOrOptions;
67
+ resolvedOptions = options;
68
+ }
69
+ const {
70
+ requireAuth = true,
71
+ requiredRoles = [],
72
+ requiredEntitlements = [],
73
+ onUnauthorized,
74
+ onForbidden,
75
+ onError,
76
+ accessCookieName = DEFAULT_ACCESS_COOKIE,
77
+ cookieAware = true
78
+ } = resolvedOptions;
79
+ return async (req, res, next) => {
80
+ let token;
81
+ const authHeader = getAuthorizationHeader(req);
82
+ if (authHeader && authHeader.startsWith("Bearer ")) {
83
+ token = authHeader.substring(7);
84
+ } else if (cookieAware) {
85
+ const cookieToken = readCookie(req, accessCookieName);
86
+ if (cookieToken) token = cookieToken;
87
+ }
88
+ if (!token) {
89
+ if (!requireAuth) {
90
+ next();
91
+ return;
92
+ }
93
+ const reason = "Missing or invalid authorization header";
94
+ if (onUnauthorized) {
95
+ onUnauthorized(res, reason);
96
+ return;
97
+ }
98
+ res.status(401).json({
99
+ success: false,
100
+ error: { code: "TOKEN_INVALID", message: reason }
101
+ });
102
+ return;
103
+ }
104
+ let claims;
105
+ try {
106
+ claims = await client.tokens.verify(token);
107
+ } catch (err) {
108
+ if (err instanceof IQAuthError && KNOWN_AUTH_ERROR_CODES.has(err.code)) {
109
+ const reason = err.message || "Invalid access token";
110
+ if (onUnauthorized) {
111
+ onUnauthorized(res, reason);
112
+ return;
113
+ }
114
+ res.status(401).json({
115
+ success: false,
116
+ error: { code: err.code, message: reason }
117
+ });
118
+ return;
119
+ }
120
+ if (onError) {
121
+ try {
122
+ onError(err);
123
+ } catch {
124
+ }
125
+ }
126
+ res.status(500).json({
127
+ success: false,
128
+ error: {
129
+ code: "INTERNAL_ERROR",
130
+ message: "Authentication failed due to an internal error"
131
+ }
132
+ });
133
+ return;
134
+ }
135
+ req.auth = claims;
136
+ if (requiredRoles.length > 0) {
137
+ const userRoles = claims.roles || [];
138
+ const hasRequired = requiredRoles.some((r) => userRoles.includes(r));
139
+ if (!hasRequired) {
140
+ const reason = `Missing required role. Required one of: ${requiredRoles.join(", ")}`;
141
+ if (onForbidden) {
142
+ onForbidden(res, reason);
143
+ return;
144
+ }
145
+ res.status(403).json({
146
+ success: false,
147
+ error: { code: "INSUFFICIENT_PERMISSIONS", message: reason }
148
+ });
149
+ return;
150
+ }
151
+ }
152
+ if (requiredEntitlements.length > 0) {
153
+ const userEntitlements = claims.entitlements || [];
154
+ const hasRequired = requiredEntitlements.every((e) => userEntitlements.includes(e));
155
+ if (!hasRequired) {
156
+ const reason = `Missing required entitlement(s): ${requiredEntitlements.join(", ")}`;
157
+ if (onForbidden) {
158
+ onForbidden(res, reason);
159
+ return;
160
+ }
161
+ res.status(403).json({
162
+ success: false,
163
+ error: { code: "INSUFFICIENT_PERMISSIONS", message: reason }
164
+ });
165
+ return;
166
+ }
167
+ }
168
+ next();
169
+ };
170
+ }
171
+
172
+ export {
173
+ DEFAULT_ACCESS_COOKIE,
174
+ DEFAULT_REFRESH_COOKIE,
175
+ iqAuthMiddleware
176
+ };