@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.
- package/LICENSE +21 -0
- package/README.md +287 -0
- package/dist/browser-session.d.mts +12 -0
- package/dist/browser-session.d.ts +12 -0
- package/dist/browser-session.js +1812 -0
- package/dist/browser-session.mjs +28 -0
- package/dist/browser.d.mts +46 -0
- package/dist/browser.d.ts +46 -0
- package/dist/browser.js +768 -0
- package/dist/browser.mjs +47 -0
- package/dist/chunk-5HF3OBNO.mjs +189 -0
- package/dist/chunk-5WFR6Y33.mjs +59 -0
- package/dist/chunk-6I6RM4MN.mjs +51 -0
- package/dist/chunk-73R6BEGO.mjs +176 -0
- package/dist/chunk-E46DKOVI.mjs +632 -0
- package/dist/chunk-JQWYIIIS.mjs +1740 -0
- package/dist/chunk-X3K3WOBR.mjs +64 -0
- package/dist/chunk-Y6FXYEAI.mjs +10 -0
- package/dist/cli/index.d.mts +1 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +581 -0
- package/dist/cli/index.mjs +57 -0
- package/dist/client-C1DXfB8Z.d.mts +911 -0
- package/dist/client-CggvJmmm.d.ts +911 -0
- package/dist/dev-FUTJZSWN.mjs +56 -0
- package/dist/doctor-OHJRZBBT.mjs +89 -0
- package/dist/errors-CDdl24MP.d.mts +52 -0
- package/dist/errors-CDdl24MP.d.ts +52 -0
- package/dist/express-BKAXB5Nl.d.ts +61 -0
- package/dist/express-CpfyYTmw.d.mts +61 -0
- package/dist/express.d.mts +45 -0
- package/dist/express.d.ts +45 -0
- package/dist/express.js +2252 -0
- package/dist/express.mjs +122 -0
- package/dist/fastify.d.mts +23 -0
- package/dist/fastify.d.ts +23 -0
- package/dist/fastify.js +2062 -0
- package/dist/fastify.mjs +118 -0
- package/dist/hono.d.mts +22 -0
- package/dist/hono.d.ts +22 -0
- package/dist/hono.js +2051 -0
- package/dist/hono.mjs +107 -0
- package/dist/index.d.mts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +2070 -0
- package/dist/index.mjs +83 -0
- package/dist/init-LLCSQGNL.mjs +198 -0
- package/dist/keys-NLWFAOEM.mjs +63 -0
- package/dist/mobile.d.mts +11 -0
- package/dist/mobile.d.ts +11 -0
- package/dist/mobile.js +1809 -0
- package/dist/mobile.mjs +25 -0
- package/dist/next.d.mts +37 -0
- package/dist/next.d.ts +37 -0
- package/dist/next.js +2078 -0
- package/dist/next.mjs +130 -0
- package/dist/publishableKey-B5DIK81A.d.mts +24 -0
- package/dist/publishableKey-B5DIK81A.d.ts +24 -0
- package/dist/react.d.mts +196 -0
- package/dist/react.d.ts +196 -0
- package/dist/react.js +1457 -0
- package/dist/react.mjs +787 -0
- package/dist/server/handlers.d.mts +96 -0
- package/dist/server/handlers.d.ts +96 -0
- package/dist/server/handlers.js +243 -0
- package/dist/server/handlers.mjs +14 -0
- package/dist/server.d.mts +14 -0
- package/dist/server.d.ts +14 -0
- package/dist/server.js +2195 -0
- package/dist/server.mjs +47 -0
- package/dist/service.d.mts +11 -0
- package/dist/service.d.ts +11 -0
- package/dist/service.js +1809 -0
- package/dist/service.mjs +25 -0
- package/dist/signIn-C8f6qVjD.d.mts +238 -0
- package/dist/signIn-Cy2lbEXb.d.ts +238 -0
- package/dist/types-Cxl3bQHt.d.mts +900 -0
- package/dist/types-Cxl3bQHt.d.ts +900 -0
- package/docs/APP_INTEGRATION_MATRIX.md +59 -0
- package/docs/BROWSER_SESSION_MIGRATION.md +69 -0
- package/docs/FRESH_IMPLEMENTATION_GUIDE.md +188 -0
- package/docs/TARBALL_RELEASE_WORKFLOW.md +98 -0
- package/docs/V1_TO_V2_UPGRADE_GUIDE.md +318 -0
- package/docs/guides/api-keys.md +130 -0
- package/docs/guides/app-registration.md +149 -0
- package/docs/guides/auth-flows.md +168 -0
- package/docs/guides/branding.md +160 -0
- package/docs/guides/entitlements.md +115 -0
- package/docs/guides/entity-hierarchy.md +200 -0
- package/docs/guides/error-handling.md +251 -0
- package/docs/guides/gdpr-compliance.md +123 -0
- package/docs/guides/invitations.md +143 -0
- package/docs/guides/mfa-enrollment.md +170 -0
- package/docs/guides/middleware-reference.md +205 -0
- package/docs/guides/mobile-native.md +110 -0
- package/docs/guides/roles-and-permissions.md +220 -0
- package/docs/guides/scoped-authorization.md +247 -0
- package/docs/guides/server-platform-integration.md +52 -0
- package/docs/guides/service-automation-integration.md +36 -0
- package/docs/guides/session-management.md +97 -0
- package/docs/guides/tenant-management.md +216 -0
- package/docs/guides/token-verification.md +178 -0
- package/docs/guides/user-management.md +184 -0
- package/docs/guides/webhooks.md +136 -0
- package/docs/integration-prompts/README.md +20 -0
- package/docs/integration-prompts/first-party-browser-app.md +29 -0
- package/docs/integration-prompts/install-from-tarball.md +41 -0
- package/docs/integration-prompts/migrate-from-local-packages-source.md +57 -0
- package/docs/integration-prompts/native-mobile-app.md +24 -0
- package/docs/integration-prompts/server-platform-app.md +20 -0
- package/docs/integration-prompts/service-automation-app.md +20 -0
- package/package.json +115 -0
package/dist/hono.js
ADDED
|
@@ -0,0 +1,2051 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/hono.ts
|
|
31
|
+
var hono_exports = {};
|
|
32
|
+
__export(hono_exports, {
|
|
33
|
+
iqAuth: () => iqAuth
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(hono_exports);
|
|
36
|
+
|
|
37
|
+
// src/errors.ts
|
|
38
|
+
var IQAuthError = class extends Error {
|
|
39
|
+
constructor(code, message, status, raw) {
|
|
40
|
+
super(message);
|
|
41
|
+
this.name = "IQAuthError";
|
|
42
|
+
this.code = code;
|
|
43
|
+
this.status = status;
|
|
44
|
+
this.raw = raw;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// src/http.ts
|
|
49
|
+
var DEFAULT_RETRY = {
|
|
50
|
+
maxAttempts: 3,
|
|
51
|
+
baseDelayMs: 100,
|
|
52
|
+
maxDelayMs: 2e3
|
|
53
|
+
};
|
|
54
|
+
function resolveRetry(cfg) {
|
|
55
|
+
return {
|
|
56
|
+
maxAttempts: Math.max(1, cfg?.maxAttempts ?? DEFAULT_RETRY.maxAttempts),
|
|
57
|
+
baseDelayMs: Math.max(0, cfg?.baseDelayMs ?? DEFAULT_RETRY.baseDelayMs),
|
|
58
|
+
maxDelayMs: Math.max(0, cfg?.maxDelayMs ?? DEFAULT_RETRY.maxDelayMs)
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function sleep(ms) {
|
|
62
|
+
if (ms <= 0) return Promise.resolve();
|
|
63
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
64
|
+
}
|
|
65
|
+
var HttpClient = class {
|
|
66
|
+
constructor(config) {
|
|
67
|
+
this.refreshPromise = null;
|
|
68
|
+
this.config = config;
|
|
69
|
+
this.retryConfig = resolveRetry(config.retry);
|
|
70
|
+
}
|
|
71
|
+
computeBackoffDelay(attempt) {
|
|
72
|
+
const exp = Math.min(this.retryConfig.maxDelayMs, this.retryConfig.baseDelayMs * 2 ** (attempt - 1));
|
|
73
|
+
return Math.floor(Math.random() * exp);
|
|
74
|
+
}
|
|
75
|
+
isRetryableStatus(status) {
|
|
76
|
+
return status === 429 || status >= 500 && status <= 599;
|
|
77
|
+
}
|
|
78
|
+
async fetchWithRetry(url, init) {
|
|
79
|
+
const { maxAttempts } = this.retryConfig;
|
|
80
|
+
let lastError;
|
|
81
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
82
|
+
try {
|
|
83
|
+
const res = await fetch(url, init);
|
|
84
|
+
if (this.isRetryableStatus(res.status) && attempt < maxAttempts) {
|
|
85
|
+
await sleep(this.computeBackoffDelay(attempt));
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
return res;
|
|
89
|
+
} catch (err) {
|
|
90
|
+
lastError = err;
|
|
91
|
+
if (attempt >= maxAttempts) break;
|
|
92
|
+
await sleep(this.computeBackoffDelay(attempt));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
throw lastError instanceof Error ? lastError : new IQAuthError("INTERNAL_ERROR", "Network request failed");
|
|
96
|
+
}
|
|
97
|
+
get baseUrl() {
|
|
98
|
+
return this.config.baseUrl;
|
|
99
|
+
}
|
|
100
|
+
get environment() {
|
|
101
|
+
return this.config.environment;
|
|
102
|
+
}
|
|
103
|
+
isBrowserSession() {
|
|
104
|
+
return this.config.environment === "browser_session";
|
|
105
|
+
}
|
|
106
|
+
hasCredentials() {
|
|
107
|
+
return this.isBrowserSession() || !!(this.config.getApiKey() || this.config.getAccessToken());
|
|
108
|
+
}
|
|
109
|
+
buildHeaders(overrideAuth) {
|
|
110
|
+
const headers = {
|
|
111
|
+
"Content-Type": "application/json"
|
|
112
|
+
};
|
|
113
|
+
if (this.isBrowserSession()) {
|
|
114
|
+
const headerName = this.config.sessionHeaderName || "x-iqauth-session";
|
|
115
|
+
headers[headerName] = this.config.sessionHeaderValue || "cookie";
|
|
116
|
+
return headers;
|
|
117
|
+
}
|
|
118
|
+
const authMode = overrideAuth || (this.config.getApiKey() ? "apikey" : "bearer");
|
|
119
|
+
if (authMode === "apikey") {
|
|
120
|
+
const apiKey = this.config.getApiKey();
|
|
121
|
+
if (apiKey) {
|
|
122
|
+
headers["X-API-Key"] = apiKey;
|
|
123
|
+
}
|
|
124
|
+
} else {
|
|
125
|
+
const token = this.config.getAccessToken();
|
|
126
|
+
if (token) {
|
|
127
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return headers;
|
|
131
|
+
}
|
|
132
|
+
isTokenExpiringSoon() {
|
|
133
|
+
const token = this.config.getAccessToken();
|
|
134
|
+
if (!token) return false;
|
|
135
|
+
try {
|
|
136
|
+
const parts = token.split(".");
|
|
137
|
+
if (parts.length !== 3) return false;
|
|
138
|
+
const payload = JSON.parse(
|
|
139
|
+
typeof atob === "function" ? atob(parts[1].replace(/-/g, "+").replace(/_/g, "/")) : Buffer.from(parts[1], "base64url").toString("utf8")
|
|
140
|
+
);
|
|
141
|
+
if (!payload.exp) return false;
|
|
142
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
143
|
+
return payload.exp - now < 60;
|
|
144
|
+
} catch {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
async attemptRefresh() {
|
|
149
|
+
if (this.refreshPromise) {
|
|
150
|
+
return this.refreshPromise;
|
|
151
|
+
}
|
|
152
|
+
this.refreshPromise = (async () => {
|
|
153
|
+
try {
|
|
154
|
+
const res = await this.fetchWithRetry(`${this.config.baseUrl}/api/v1/auth/refresh`, {
|
|
155
|
+
method: "POST",
|
|
156
|
+
headers: this.buildHeaders(),
|
|
157
|
+
...this.isBrowserSession() ? { credentials: "include" } : (() => {
|
|
158
|
+
const refreshToken = this.config.getRefreshToken();
|
|
159
|
+
if (!refreshToken) throw new IQAuthError("TOKEN_INVALID", "No refresh token available");
|
|
160
|
+
return { body: JSON.stringify({ refreshToken }) };
|
|
161
|
+
})()
|
|
162
|
+
});
|
|
163
|
+
const body = await res.json();
|
|
164
|
+
if (!body.success) {
|
|
165
|
+
throw new IQAuthError(
|
|
166
|
+
body.error.code,
|
|
167
|
+
body.error.message,
|
|
168
|
+
res.status,
|
|
169
|
+
body
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
if (this.isBrowserSession()) {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
if (!body.data.accessToken || !body.data.refreshToken) {
|
|
176
|
+
throw new IQAuthError("TOKEN_INVALID", "Refresh response did not include a token pair");
|
|
177
|
+
}
|
|
178
|
+
const tokens = {
|
|
179
|
+
accessToken: body.data.accessToken,
|
|
180
|
+
refreshToken: body.data.refreshToken
|
|
181
|
+
};
|
|
182
|
+
this.config.setTokens(tokens);
|
|
183
|
+
this.config.onTokenRefresh?.(tokens);
|
|
184
|
+
} finally {
|
|
185
|
+
this.refreshPromise = null;
|
|
186
|
+
}
|
|
187
|
+
})();
|
|
188
|
+
return this.refreshPromise;
|
|
189
|
+
}
|
|
190
|
+
async request(method, path, body, options) {
|
|
191
|
+
return this.requestWithRetry(method, path, body, options, false);
|
|
192
|
+
}
|
|
193
|
+
async requestWithRetry(method, path, body, options, hasRetried) {
|
|
194
|
+
if (this.config.autoRefresh && !options?.skipAutoRefresh && !this.isBrowserSession() && this.config.getRefreshToken() && this.isTokenExpiringSoon()) {
|
|
195
|
+
await this.attemptRefresh();
|
|
196
|
+
}
|
|
197
|
+
const url = `${this.config.baseUrl}${path}`;
|
|
198
|
+
const headers = this.buildHeaders(options?.authMode);
|
|
199
|
+
const fetchOptions = {
|
|
200
|
+
method,
|
|
201
|
+
headers,
|
|
202
|
+
...this.isBrowserSession() ? { credentials: "include" } : {}
|
|
203
|
+
};
|
|
204
|
+
if (body !== void 0 && method !== "GET") {
|
|
205
|
+
fetchOptions.body = JSON.stringify(body);
|
|
206
|
+
}
|
|
207
|
+
const res = await this.fetchWithRetry(url, fetchOptions);
|
|
208
|
+
if (res.status === 204) {
|
|
209
|
+
return void 0;
|
|
210
|
+
}
|
|
211
|
+
const responseBody = await res.json();
|
|
212
|
+
if (!responseBody.success) {
|
|
213
|
+
const shouldRetryRefresh = !hasRetried && this.config.autoRefresh && !options?.skipAutoRefresh && responseBody.error.code === "TOKEN_EXPIRED" && (this.isBrowserSession() || !!this.config.getRefreshToken());
|
|
214
|
+
if (shouldRetryRefresh) {
|
|
215
|
+
await this.attemptRefresh();
|
|
216
|
+
return this.requestWithRetry(method, path, body, options, true);
|
|
217
|
+
}
|
|
218
|
+
throw new IQAuthError(
|
|
219
|
+
responseBody.error.code,
|
|
220
|
+
responseBody.error.message,
|
|
221
|
+
res.status,
|
|
222
|
+
responseBody
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
return responseBody.data;
|
|
226
|
+
}
|
|
227
|
+
async requestRaw(method, path, body) {
|
|
228
|
+
const url = `${this.config.baseUrl}${path}`;
|
|
229
|
+
const headers = { "Content-Type": "application/json" };
|
|
230
|
+
if (this.isBrowserSession()) {
|
|
231
|
+
const headerName = this.config.sessionHeaderName || "x-iqauth-session";
|
|
232
|
+
headers[headerName] = this.config.sessionHeaderValue || "cookie";
|
|
233
|
+
}
|
|
234
|
+
const token = this.config.getAccessToken();
|
|
235
|
+
if (token) {
|
|
236
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
237
|
+
}
|
|
238
|
+
const fetchOptions = { method, headers };
|
|
239
|
+
if (this.isBrowserSession()) {
|
|
240
|
+
fetchOptions.credentials = "include";
|
|
241
|
+
}
|
|
242
|
+
if (body !== void 0 && method !== "GET") {
|
|
243
|
+
fetchOptions.body = JSON.stringify(body);
|
|
244
|
+
}
|
|
245
|
+
const res = await fetch(url, fetchOptions);
|
|
246
|
+
return await res.json();
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
// src/modules/auth.ts
|
|
251
|
+
function parseLoginResponse(data, browserSessionMode) {
|
|
252
|
+
if (data.accessToken && data.refreshToken && data.user) {
|
|
253
|
+
return {
|
|
254
|
+
status: "authenticated",
|
|
255
|
+
authMode: "token",
|
|
256
|
+
tokens: { accessToken: data.accessToken, refreshToken: data.refreshToken },
|
|
257
|
+
user: data.user
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
if (browserSessionMode && data.user) {
|
|
261
|
+
return {
|
|
262
|
+
status: "authenticated",
|
|
263
|
+
authMode: "session",
|
|
264
|
+
user: data.user
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
if (data.mfaChallengeToken && !data.tenantSelectionToken) {
|
|
268
|
+
return {
|
|
269
|
+
status: "mfa_required",
|
|
270
|
+
mfaChallengeToken: data.mfaChallengeToken,
|
|
271
|
+
availableMethods: data.availableMethods ?? []
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
if (data.tenantSelectionToken && data.tenants) {
|
|
275
|
+
return {
|
|
276
|
+
status: "tenant_selection",
|
|
277
|
+
tenantSelectionToken: data.tenantSelectionToken,
|
|
278
|
+
tenants: data.tenants
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
throw new Error("Unexpected login response shape");
|
|
282
|
+
}
|
|
283
|
+
var AuthModule = class {
|
|
284
|
+
constructor(http) {
|
|
285
|
+
this.http = http;
|
|
286
|
+
}
|
|
287
|
+
async login(email, password) {
|
|
288
|
+
const data = await this.http.request(
|
|
289
|
+
"POST",
|
|
290
|
+
"/api/v1/auth/login",
|
|
291
|
+
{ email, password },
|
|
292
|
+
{ skipAutoRefresh: true }
|
|
293
|
+
);
|
|
294
|
+
return parseLoginResponse(data, this.http.isBrowserSession());
|
|
295
|
+
}
|
|
296
|
+
async signup(input) {
|
|
297
|
+
const data = await this.http.request(
|
|
298
|
+
"POST",
|
|
299
|
+
"/api/v1/auth/signup",
|
|
300
|
+
input,
|
|
301
|
+
{ skipAutoRefresh: true }
|
|
302
|
+
);
|
|
303
|
+
return parseLoginResponse(data, this.http.isBrowserSession());
|
|
304
|
+
}
|
|
305
|
+
async completeMfa(mfaChallengeToken, code, method) {
|
|
306
|
+
const data = await this.http.request(
|
|
307
|
+
"POST",
|
|
308
|
+
"/api/v1/mfa/verify",
|
|
309
|
+
{ mfaChallengeToken, code, method },
|
|
310
|
+
{ skipAutoRefresh: true }
|
|
311
|
+
);
|
|
312
|
+
return parseMfaResponse(data, this.http.isBrowserSession());
|
|
313
|
+
}
|
|
314
|
+
async completeMfaWithBackup(mfaChallengeToken, backupCode) {
|
|
315
|
+
const data = await this.http.request(
|
|
316
|
+
"POST",
|
|
317
|
+
"/api/v1/mfa/verify-backup",
|
|
318
|
+
{ mfaChallengeToken, backupCode },
|
|
319
|
+
{ skipAutoRefresh: true }
|
|
320
|
+
);
|
|
321
|
+
return parseMfaResponse(data, this.http.isBrowserSession());
|
|
322
|
+
}
|
|
323
|
+
async sendMfaChallenge(mfaChallengeToken, method) {
|
|
324
|
+
return this.http.request("POST", "/api/v1/mfa/challenge", {
|
|
325
|
+
mfaChallengeToken,
|
|
326
|
+
method
|
|
327
|
+
}, { skipAutoRefresh: true });
|
|
328
|
+
}
|
|
329
|
+
async selectTenant(tenantSelectionToken, tenantId) {
|
|
330
|
+
const data = await this.http.request(
|
|
331
|
+
"POST",
|
|
332
|
+
"/api/v1/auth/select-tenant",
|
|
333
|
+
{
|
|
334
|
+
tenantSelectionToken,
|
|
335
|
+
tenantId
|
|
336
|
+
},
|
|
337
|
+
{ skipAutoRefresh: true }
|
|
338
|
+
);
|
|
339
|
+
return parseLoginResponse(data, this.http.isBrowserSession());
|
|
340
|
+
}
|
|
341
|
+
async logout() {
|
|
342
|
+
return this.http.request("POST", "/api/v1/auth/logout");
|
|
343
|
+
}
|
|
344
|
+
async logoutAll() {
|
|
345
|
+
return this.http.request("POST", "/api/v1/auth/logout-all");
|
|
346
|
+
}
|
|
347
|
+
async refreshTokens(refreshToken) {
|
|
348
|
+
if (this.http.isBrowserSession()) {
|
|
349
|
+
throw new Error("refreshTokens(refreshToken) is not used in browser_session mode; the backend session should own refresh.");
|
|
350
|
+
}
|
|
351
|
+
const data = await this.http.request(
|
|
352
|
+
"POST",
|
|
353
|
+
"/api/v1/auth/refresh",
|
|
354
|
+
{ refreshToken },
|
|
355
|
+
{ skipAutoRefresh: true }
|
|
356
|
+
);
|
|
357
|
+
return { accessToken: data.accessToken, refreshToken: data.refreshToken };
|
|
358
|
+
}
|
|
359
|
+
async forgotPassword(email) {
|
|
360
|
+
return this.http.request("POST", "/api/v1/auth/password/reset/request", { email }, { skipAutoRefresh: true });
|
|
361
|
+
}
|
|
362
|
+
async resetPassword(token, newPassword) {
|
|
363
|
+
return this.http.request("POST", "/api/v1/auth/password/reset/confirm", { token, newPassword }, { skipAutoRefresh: true });
|
|
364
|
+
}
|
|
365
|
+
async changePassword(currentPassword, newPassword) {
|
|
366
|
+
return this.http.request("POST", "/api/v1/auth/password/change", {
|
|
367
|
+
currentPassword,
|
|
368
|
+
newPassword
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
async verifyToken() {
|
|
372
|
+
return this.http.request("GET", "/api/v1/auth/verify");
|
|
373
|
+
}
|
|
374
|
+
async exchangeOAuthCode(code) {
|
|
375
|
+
const data = await this.http.request(
|
|
376
|
+
"POST",
|
|
377
|
+
"/api/v1/auth/oauth/exchange",
|
|
378
|
+
{ code },
|
|
379
|
+
{ skipAutoRefresh: true }
|
|
380
|
+
);
|
|
381
|
+
return parseLoginResponse(data, this.http.isBrowserSession());
|
|
382
|
+
}
|
|
383
|
+
async getSessionUser() {
|
|
384
|
+
return this.http.request("GET", "/api/v1/auth/me");
|
|
385
|
+
}
|
|
386
|
+
};
|
|
387
|
+
function parseMfaResponse(data, browserSessionMode) {
|
|
388
|
+
if (data.accessToken && data.refreshToken && data.user) {
|
|
389
|
+
return {
|
|
390
|
+
authMode: "token",
|
|
391
|
+
tokens: { accessToken: data.accessToken, refreshToken: data.refreshToken },
|
|
392
|
+
user: data.user,
|
|
393
|
+
...data.remainingBackupCodes !== void 0 ? { remainingBackupCodes: data.remainingBackupCodes } : {},
|
|
394
|
+
...data.warning ? { warning: data.warning } : {}
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
if (browserSessionMode && data.user) {
|
|
398
|
+
return {
|
|
399
|
+
authMode: "session",
|
|
400
|
+
user: data.user,
|
|
401
|
+
...data.remainingBackupCodes !== void 0 ? { remainingBackupCodes: data.remainingBackupCodes } : {},
|
|
402
|
+
...data.warning ? { warning: data.warning } : {}
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
throw new Error("Unexpected MFA response shape");
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// src/modules/tokens.ts
|
|
409
|
+
var import_crypto = __toESM(require("crypto"));
|
|
410
|
+
var import_jsonwebtoken = __toESM(require("jsonwebtoken"));
|
|
411
|
+
var JWKS_CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
412
|
+
var DEFAULT_TOKEN_ISSUER = "auth.dispositioniq.com";
|
|
413
|
+
var DEFAULT_TOKEN_AUDIENCE = [
|
|
414
|
+
"dispositioniq",
|
|
415
|
+
"iqcapture",
|
|
416
|
+
"iqreuse",
|
|
417
|
+
"iqvalidate"
|
|
418
|
+
];
|
|
419
|
+
var DEFAULT_CLOCK_TOLERANCE_SECONDS = 30;
|
|
420
|
+
var TokensModule = class {
|
|
421
|
+
constructor(baseUrl, options = {}) {
|
|
422
|
+
this.jwksCache = null;
|
|
423
|
+
this.inFlightRefresh = null;
|
|
424
|
+
this.baseUrl = baseUrl;
|
|
425
|
+
this.defaultIssuer = options.issuer ?? DEFAULT_TOKEN_ISSUER;
|
|
426
|
+
this.defaultAudience = options.audience ?? DEFAULT_TOKEN_AUDIENCE;
|
|
427
|
+
this.defaultClockTolerance = options.clockTolerance ?? DEFAULT_CLOCK_TOLERANCE_SECONDS;
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Verify a JWT access token using RS256 via JWKS from /.well-known/jwks.json.
|
|
431
|
+
* Caches JWKS keys for 1 hour. Retries once on unknown `kid`.
|
|
432
|
+
*
|
|
433
|
+
* @remarks Validates against /.well-known/jwks.json. Issuer, audience, and
|
|
434
|
+
* clock tolerance default to client config but can be overridden per call.
|
|
435
|
+
*/
|
|
436
|
+
async verify(token, options = {}) {
|
|
437
|
+
const decoded = import_jsonwebtoken.default.decode(token, { complete: true });
|
|
438
|
+
if (!decoded || typeof decoded === "string") {
|
|
439
|
+
throw new IQAuthError("TOKEN_INVALID", "Unable to decode token");
|
|
440
|
+
}
|
|
441
|
+
const kid = decoded.header.kid;
|
|
442
|
+
if (!kid) {
|
|
443
|
+
throw new IQAuthError("TOKEN_INVALID", "Token missing kid header");
|
|
444
|
+
}
|
|
445
|
+
let publicKey = await this.getPublicKey(kid);
|
|
446
|
+
if (!publicKey) {
|
|
447
|
+
await this.refreshJwks();
|
|
448
|
+
publicKey = await this.getPublicKey(kid);
|
|
449
|
+
}
|
|
450
|
+
if (!publicKey) {
|
|
451
|
+
throw new IQAuthError("TOKEN_INVALID", `Unknown key ID: ${kid}`);
|
|
452
|
+
}
|
|
453
|
+
const issuer = options.issuer ?? this.defaultIssuer;
|
|
454
|
+
const audience = options.audience ?? this.defaultAudience;
|
|
455
|
+
const clockTolerance = options.clockTolerance ?? this.defaultClockTolerance;
|
|
456
|
+
const algorithms = options.algorithms ?? ["RS256"];
|
|
457
|
+
try {
|
|
458
|
+
const verifyOptions = {
|
|
459
|
+
algorithms,
|
|
460
|
+
clockTolerance,
|
|
461
|
+
// The jsonwebtoken types insist on tuple types for arrays; runtime
|
|
462
|
+
// accepts plain string[] so we cast to satisfy the compiler.
|
|
463
|
+
issuer,
|
|
464
|
+
audience
|
|
465
|
+
};
|
|
466
|
+
const verified = import_jsonwebtoken.default.verify(token, publicKey, verifyOptions);
|
|
467
|
+
return verified;
|
|
468
|
+
} catch (err) {
|
|
469
|
+
if (err instanceof Error) {
|
|
470
|
+
if (err.name === "TokenExpiredError") {
|
|
471
|
+
throw new IQAuthError("TOKEN_EXPIRED", "Token has expired");
|
|
472
|
+
}
|
|
473
|
+
throw new IQAuthError("TOKEN_INVALID", err.message);
|
|
474
|
+
}
|
|
475
|
+
throw new IQAuthError("TOKEN_INVALID", "Token verification failed");
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Decode a JWT without verification. Returns null if malformed.
|
|
480
|
+
*
|
|
481
|
+
* @remarks Local decode only — no network call
|
|
482
|
+
*/
|
|
483
|
+
decode(token) {
|
|
484
|
+
const decoded = import_jsonwebtoken.default.decode(token);
|
|
485
|
+
return decoded;
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Check if a token is expired based on the `exp` claim.
|
|
489
|
+
*
|
|
490
|
+
* @remarks Local check only — no network call
|
|
491
|
+
*/
|
|
492
|
+
isExpired(token) {
|
|
493
|
+
const claims = this.decode(token);
|
|
494
|
+
if (!claims?.exp) return true;
|
|
495
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
496
|
+
return claims.exp <= now;
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* Get the claims from a token without verification.
|
|
500
|
+
*
|
|
501
|
+
* @remarks Local decode only — no network call
|
|
502
|
+
*/
|
|
503
|
+
getClaims(token) {
|
|
504
|
+
const claims = this.decode(token);
|
|
505
|
+
if (!claims) {
|
|
506
|
+
throw new IQAuthError("TOKEN_INVALID", "Unable to decode token claims");
|
|
507
|
+
}
|
|
508
|
+
return claims;
|
|
509
|
+
}
|
|
510
|
+
async getPublicKey(kid) {
|
|
511
|
+
if (!this.jwksCache || Date.now() - this.jwksCache.fetchedAt > JWKS_CACHE_TTL_MS) {
|
|
512
|
+
await this.refreshJwks();
|
|
513
|
+
}
|
|
514
|
+
return this.jwksCache?.keys.get(kid) ?? null;
|
|
515
|
+
}
|
|
516
|
+
async refreshJwks() {
|
|
517
|
+
if (this.inFlightRefresh) {
|
|
518
|
+
return this.inFlightRefresh;
|
|
519
|
+
}
|
|
520
|
+
this.inFlightRefresh = (async () => {
|
|
521
|
+
try {
|
|
522
|
+
const res = await fetch(`${this.baseUrl}/.well-known/jwks.json`);
|
|
523
|
+
if (!res.ok) {
|
|
524
|
+
throw new IQAuthError(
|
|
525
|
+
"INTERNAL_ERROR",
|
|
526
|
+
`Failed to fetch JWKS: ${res.status}`
|
|
527
|
+
);
|
|
528
|
+
}
|
|
529
|
+
let jwks;
|
|
530
|
+
try {
|
|
531
|
+
jwks = await res.json();
|
|
532
|
+
} catch {
|
|
533
|
+
throw new IQAuthError("INTERNAL_ERROR", "Malformed JWKS response: invalid JSON");
|
|
534
|
+
}
|
|
535
|
+
if (!jwks || !Array.isArray(jwks.keys)) {
|
|
536
|
+
throw new IQAuthError(
|
|
537
|
+
"INTERNAL_ERROR",
|
|
538
|
+
"Malformed JWKS response: expected { keys: [...] }"
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
const keys = /* @__PURE__ */ new Map();
|
|
542
|
+
for (const key of jwks.keys) {
|
|
543
|
+
if (!key || typeof key.kid !== "string" || typeof key.n !== "string" || typeof key.e !== "string") {
|
|
544
|
+
throw new IQAuthError(
|
|
545
|
+
"INTERNAL_ERROR",
|
|
546
|
+
"Malformed JWKS response: key missing required fields"
|
|
547
|
+
);
|
|
548
|
+
}
|
|
549
|
+
const pem = this.jwkToPem(key);
|
|
550
|
+
keys.set(key.kid, pem);
|
|
551
|
+
}
|
|
552
|
+
this.jwksCache = { keys, fetchedAt: Date.now() };
|
|
553
|
+
} finally {
|
|
554
|
+
this.inFlightRefresh = null;
|
|
555
|
+
}
|
|
556
|
+
})();
|
|
557
|
+
return this.inFlightRefresh;
|
|
558
|
+
}
|
|
559
|
+
jwkToPem(jwk) {
|
|
560
|
+
const keyObject = import_crypto.default.createPublicKey({
|
|
561
|
+
key: {
|
|
562
|
+
kty: jwk.kty,
|
|
563
|
+
n: jwk.n,
|
|
564
|
+
e: jwk.e
|
|
565
|
+
},
|
|
566
|
+
format: "jwk"
|
|
567
|
+
});
|
|
568
|
+
return keyObject.export({ type: "spki", format: "pem" });
|
|
569
|
+
}
|
|
570
|
+
/** @internal Exposed for testing — clears JWKS cache */
|
|
571
|
+
clearCache() {
|
|
572
|
+
this.jwksCache = null;
|
|
573
|
+
}
|
|
574
|
+
};
|
|
575
|
+
|
|
576
|
+
// src/modules/sessions.ts
|
|
577
|
+
var SessionsModule = class {
|
|
578
|
+
constructor(http) {
|
|
579
|
+
this.http = http;
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* List all active sessions for the current user.
|
|
583
|
+
*
|
|
584
|
+
* @remarks Wraps GET /api/v1/sessions
|
|
585
|
+
*/
|
|
586
|
+
async list() {
|
|
587
|
+
return this.http.request("GET", "/api/v1/sessions");
|
|
588
|
+
}
|
|
589
|
+
/**
|
|
590
|
+
* Revoke (terminate) a specific session by ID.
|
|
591
|
+
*
|
|
592
|
+
* @remarks Wraps DELETE /api/v1/sessions/:sessionId
|
|
593
|
+
*/
|
|
594
|
+
async revoke(sessionId) {
|
|
595
|
+
return this.http.request("DELETE", `/api/v1/sessions/${sessionId}`);
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* Revoke all sessions for the current user.
|
|
599
|
+
*
|
|
600
|
+
* @remarks Wraps POST /api/v1/auth/logout-all
|
|
601
|
+
*/
|
|
602
|
+
async revokeAll() {
|
|
603
|
+
return this.http.request("POST", "/api/v1/auth/logout-all");
|
|
604
|
+
}
|
|
605
|
+
};
|
|
606
|
+
|
|
607
|
+
// src/modules/users.ts
|
|
608
|
+
var UsersModule = class {
|
|
609
|
+
constructor(http) {
|
|
610
|
+
this.http = http;
|
|
611
|
+
}
|
|
612
|
+
/**
|
|
613
|
+
* Get the currently authenticated user's profile.
|
|
614
|
+
*
|
|
615
|
+
* @remarks Wraps GET /api/v1/users/me
|
|
616
|
+
*/
|
|
617
|
+
async getCurrent() {
|
|
618
|
+
return this.http.request("GET", "/api/v1/users/me");
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* Get a user by ID.
|
|
622
|
+
*
|
|
623
|
+
* @remarks Wraps GET /api/v1/users/:id
|
|
624
|
+
*/
|
|
625
|
+
async getById(userId) {
|
|
626
|
+
return this.http.request("GET", `/api/v1/users/${userId}`);
|
|
627
|
+
}
|
|
628
|
+
/**
|
|
629
|
+
* List users in the current tenant. Requires tenant_admin role.
|
|
630
|
+
*
|
|
631
|
+
* @remarks Wraps GET /api/v1/users
|
|
632
|
+
*/
|
|
633
|
+
async list(params) {
|
|
634
|
+
const query = new URLSearchParams();
|
|
635
|
+
if (params?.email) query.set("email", params.email);
|
|
636
|
+
if (params?.tenantId) query.set("tenantId", params.tenantId);
|
|
637
|
+
const qs = query.toString();
|
|
638
|
+
return this.http.request("GET", `/api/v1/users${qs ? `?${qs}` : ""}`);
|
|
639
|
+
}
|
|
640
|
+
/**
|
|
641
|
+
* Provision (create) a new user in a tenant. Requires platform_admin or tenant-scoped API key with admin role.
|
|
642
|
+
*
|
|
643
|
+
* @remarks Wraps POST /api/v1/tenants/:tenantId/users/provision
|
|
644
|
+
*/
|
|
645
|
+
async create(tenantId, data) {
|
|
646
|
+
return this.http.request("POST", `/api/v1/tenants/${tenantId}/users/provision`, data);
|
|
647
|
+
}
|
|
648
|
+
/**
|
|
649
|
+
* Update the current user's profile (name, picture).
|
|
650
|
+
*
|
|
651
|
+
* @remarks Wraps PATCH /api/v1/users/me
|
|
652
|
+
*/
|
|
653
|
+
async update(data) {
|
|
654
|
+
return this.http.request("PATCH", "/api/v1/users/me", data);
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Deactivate a user in the current tenant. Requires tenant_admin role.
|
|
658
|
+
*
|
|
659
|
+
* @remarks Wraps PATCH /api/v1/users/:id/deactivate
|
|
660
|
+
*/
|
|
661
|
+
async deactivate(userId) {
|
|
662
|
+
return this.http.request("PATCH", `/api/v1/users/${userId}/deactivate`);
|
|
663
|
+
}
|
|
664
|
+
/**
|
|
665
|
+
* Reactivate a user in the current tenant. Requires tenant_admin role.
|
|
666
|
+
*
|
|
667
|
+
* @remarks Wraps PATCH /api/v1/users/:id/reactivate
|
|
668
|
+
*/
|
|
669
|
+
async reactivate(userId) {
|
|
670
|
+
return this.http.request("PATCH", `/api/v1/users/${userId}/reactivate`);
|
|
671
|
+
}
|
|
672
|
+
/**
|
|
673
|
+
* Unlock a locked user account in the current tenant. Requires tenant_admin role.
|
|
674
|
+
*
|
|
675
|
+
* @remarks Wraps PATCH /api/v1/users/:id/unlock
|
|
676
|
+
*/
|
|
677
|
+
async unlock(userId) {
|
|
678
|
+
return this.http.request("PATCH", `/api/v1/users/${userId}/unlock`);
|
|
679
|
+
}
|
|
680
|
+
/**
|
|
681
|
+
* Get effective permissions for a user for a specific product.
|
|
682
|
+
*
|
|
683
|
+
* @remarks Wraps GET /api/v1/users/:id/permissions?product=...
|
|
684
|
+
*/
|
|
685
|
+
async getPermissions(userId, product) {
|
|
686
|
+
return this.http.request("GET", `/api/v1/users/${userId}/permissions?product=${encodeURIComponent(product)}`);
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
689
|
+
* Change the current user's password.
|
|
690
|
+
*
|
|
691
|
+
* @remarks Wraps POST /api/v1/auth/password/change
|
|
692
|
+
*/
|
|
693
|
+
async updatePassword(currentPassword, newPassword) {
|
|
694
|
+
return this.http.request("POST", "/api/v1/auth/password/change", {
|
|
695
|
+
currentPassword,
|
|
696
|
+
newPassword
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
};
|
|
700
|
+
|
|
701
|
+
// src/modules/permissions.ts
|
|
702
|
+
var PermissionsModule = class {
|
|
703
|
+
constructor(claimsProvider) {
|
|
704
|
+
this.getClaims = claimsProvider;
|
|
705
|
+
}
|
|
706
|
+
/**
|
|
707
|
+
* Get the roles from the current JWT claims.
|
|
708
|
+
*
|
|
709
|
+
* @remarks Extracted from JWT claim `roles` (string[])
|
|
710
|
+
*/
|
|
711
|
+
getRoles() {
|
|
712
|
+
return this.getClaims()?.roles ?? [];
|
|
713
|
+
}
|
|
714
|
+
/**
|
|
715
|
+
* Get the entitlements from the current JWT claims.
|
|
716
|
+
*
|
|
717
|
+
* @remarks Extracted from JWT claim `entitlements` (string[])
|
|
718
|
+
*/
|
|
719
|
+
getEntitlements() {
|
|
720
|
+
return this.getClaims()?.entitlements ?? [];
|
|
721
|
+
}
|
|
722
|
+
/**
|
|
723
|
+
* Check if the current user has a specific role.
|
|
724
|
+
*
|
|
725
|
+
* @remarks Checks against JWT claim `roles`
|
|
726
|
+
*/
|
|
727
|
+
hasRole(role) {
|
|
728
|
+
return this.getRoles().includes(role);
|
|
729
|
+
}
|
|
730
|
+
/**
|
|
731
|
+
* Check if the current user has a specific entitlement.
|
|
732
|
+
*
|
|
733
|
+
* @remarks Checks against JWT claim `entitlements`
|
|
734
|
+
*/
|
|
735
|
+
hasEntitlement(entitlement) {
|
|
736
|
+
return this.getEntitlements().includes(entitlement);
|
|
737
|
+
}
|
|
738
|
+
/**
|
|
739
|
+
* Check if the current user has all of the specified roles.
|
|
740
|
+
*
|
|
741
|
+
* @remarks Checks against JWT claim `roles`
|
|
742
|
+
*/
|
|
743
|
+
hasAllRoles(roles) {
|
|
744
|
+
const userRoles = this.getRoles();
|
|
745
|
+
return roles.every((r) => userRoles.includes(r));
|
|
746
|
+
}
|
|
747
|
+
/**
|
|
748
|
+
* Check if the current user has any of the specified roles.
|
|
749
|
+
*
|
|
750
|
+
* @remarks Checks against JWT claim `roles`
|
|
751
|
+
*/
|
|
752
|
+
hasAnyRole(roles) {
|
|
753
|
+
const userRoles = this.getRoles();
|
|
754
|
+
return roles.some((r) => userRoles.includes(r));
|
|
755
|
+
}
|
|
756
|
+
/**
|
|
757
|
+
* Check if the current user has all of the specified entitlements.
|
|
758
|
+
*
|
|
759
|
+
* @remarks Checks against JWT claim `entitlements`
|
|
760
|
+
*/
|
|
761
|
+
hasAllEntitlements(entitlements) {
|
|
762
|
+
const userEntitlements = this.getEntitlements();
|
|
763
|
+
return entitlements.every((e) => userEntitlements.includes(e));
|
|
764
|
+
}
|
|
765
|
+
/**
|
|
766
|
+
* Check if the current user has any of the specified entitlements.
|
|
767
|
+
*
|
|
768
|
+
* @remarks Checks against JWT claim `entitlements`
|
|
769
|
+
*/
|
|
770
|
+
hasAnyEntitlement(entitlements) {
|
|
771
|
+
const userEntitlements = this.getEntitlements();
|
|
772
|
+
return entitlements.some((e) => userEntitlements.includes(e));
|
|
773
|
+
}
|
|
774
|
+
};
|
|
775
|
+
|
|
776
|
+
// src/modules/oidc.ts
|
|
777
|
+
var import_crypto2 = __toESM(require("crypto"));
|
|
778
|
+
var InMemoryOidcStateStore = class {
|
|
779
|
+
constructor() {
|
|
780
|
+
this.map = /* @__PURE__ */ new Map();
|
|
781
|
+
}
|
|
782
|
+
set(state, value) {
|
|
783
|
+
this.map.set(state, value);
|
|
784
|
+
}
|
|
785
|
+
get(state) {
|
|
786
|
+
const entry = this.map.get(state);
|
|
787
|
+
if (!entry) return null;
|
|
788
|
+
if (entry.expiresAt < Date.now()) {
|
|
789
|
+
this.map.delete(state);
|
|
790
|
+
return null;
|
|
791
|
+
}
|
|
792
|
+
return entry;
|
|
793
|
+
}
|
|
794
|
+
delete(state) {
|
|
795
|
+
this.map.delete(state);
|
|
796
|
+
}
|
|
797
|
+
};
|
|
798
|
+
var DEFAULT_REQUEST_TTL_MS = 10 * 60 * 1e3;
|
|
799
|
+
function base64UrlEncode(buf) {
|
|
800
|
+
return buf.toString("base64").replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
|
|
801
|
+
}
|
|
802
|
+
var OidcModule = class {
|
|
803
|
+
constructor(http, baseUrl, options = {}) {
|
|
804
|
+
this.http = http;
|
|
805
|
+
this.baseUrl = baseUrl;
|
|
806
|
+
this.stateStore = options.stateStore ?? new InMemoryOidcStateStore();
|
|
807
|
+
this.requestTtlMs = options.requestTtlMs ?? DEFAULT_REQUEST_TTL_MS;
|
|
808
|
+
this.tokensModule = options.tokens;
|
|
809
|
+
}
|
|
810
|
+
/** @internal Allow the client to inject its TokensModule after construction. */
|
|
811
|
+
_setTokensModule(tokens) {
|
|
812
|
+
if (!this.tokensModule) this.tokensModule = tokens;
|
|
813
|
+
}
|
|
814
|
+
/**
|
|
815
|
+
* Fetch the OpenID Connect discovery document.
|
|
816
|
+
*
|
|
817
|
+
* @remarks Wraps GET /.well-known/openid-configuration
|
|
818
|
+
*/
|
|
819
|
+
async getDiscovery() {
|
|
820
|
+
const res = await fetch(`${this.baseUrl}/.well-known/openid-configuration`);
|
|
821
|
+
if (!res.ok) throw new Error(`Failed to fetch OIDC discovery: ${res.status}`);
|
|
822
|
+
return res.json();
|
|
823
|
+
}
|
|
824
|
+
/**
|
|
825
|
+
* Fetch the JSON Web Key Set.
|
|
826
|
+
*
|
|
827
|
+
* @remarks Wraps GET /.well-known/jwks.json
|
|
828
|
+
*/
|
|
829
|
+
async getJwks() {
|
|
830
|
+
const res = await fetch(`${this.baseUrl}/.well-known/jwks.json`);
|
|
831
|
+
if (!res.ok) throw new Error(`Failed to fetch JWKS: ${res.status}`);
|
|
832
|
+
return res.json();
|
|
833
|
+
}
|
|
834
|
+
/**
|
|
835
|
+
* Build an OIDC authorization URL for redirect-based login.
|
|
836
|
+
*
|
|
837
|
+
* @remarks Constructs URL pointing to /oidc/authorize. Prefer
|
|
838
|
+
* {@link createAuthRequest} which also generates and stores PKCE/state/nonce.
|
|
839
|
+
*/
|
|
840
|
+
buildAuthorizationUrl(params) {
|
|
841
|
+
const url = new URL("/oidc/authorize", this.baseUrl);
|
|
842
|
+
url.searchParams.set("response_type", "code");
|
|
843
|
+
url.searchParams.set("client_id", params.clientId);
|
|
844
|
+
url.searchParams.set("redirect_uri", params.redirectUri);
|
|
845
|
+
url.searchParams.set("scope", params.scope || "openid");
|
|
846
|
+
url.searchParams.set("state", params.state);
|
|
847
|
+
if (params.nonce) url.searchParams.set("nonce", params.nonce);
|
|
848
|
+
if (params.codeChallenge) url.searchParams.set("code_challenge", params.codeChallenge);
|
|
849
|
+
if (params.codeChallengeMethod) url.searchParams.set("code_challenge_method", params.codeChallengeMethod);
|
|
850
|
+
return url.toString();
|
|
851
|
+
}
|
|
852
|
+
/**
|
|
853
|
+
* Generate `code_verifier`, `code_challenge`, `state`, and `nonce`, persist
|
|
854
|
+
* them via the configured storage adapter, and return an authorization URL
|
|
855
|
+
* ready to redirect the user to.
|
|
856
|
+
*/
|
|
857
|
+
async createAuthRequest(params) {
|
|
858
|
+
const codeVerifier = base64UrlEncode(import_crypto2.default.randomBytes(32));
|
|
859
|
+
const codeChallenge = base64UrlEncode(
|
|
860
|
+
import_crypto2.default.createHash("sha256").update(codeVerifier).digest()
|
|
861
|
+
);
|
|
862
|
+
const state = base64UrlEncode(import_crypto2.default.randomBytes(16));
|
|
863
|
+
const nonce = base64UrlEncode(import_crypto2.default.randomBytes(16));
|
|
864
|
+
await this.stateStore.set(state, {
|
|
865
|
+
codeVerifier,
|
|
866
|
+
state,
|
|
867
|
+
nonce,
|
|
868
|
+
redirectUri: params.redirectUri,
|
|
869
|
+
clientId: params.clientId,
|
|
870
|
+
expiresAt: Date.now() + this.requestTtlMs
|
|
871
|
+
});
|
|
872
|
+
const authorizationUrl = this.buildAuthorizationUrl({
|
|
873
|
+
clientId: params.clientId,
|
|
874
|
+
redirectUri: params.redirectUri,
|
|
875
|
+
scope: params.scope,
|
|
876
|
+
state,
|
|
877
|
+
nonce,
|
|
878
|
+
codeChallenge,
|
|
879
|
+
codeChallengeMethod: "S256"
|
|
880
|
+
});
|
|
881
|
+
return {
|
|
882
|
+
authorizationUrl,
|
|
883
|
+
state,
|
|
884
|
+
nonce,
|
|
885
|
+
codeVerifier,
|
|
886
|
+
codeChallenge,
|
|
887
|
+
codeChallengeMethod: "S256"
|
|
888
|
+
};
|
|
889
|
+
}
|
|
890
|
+
/**
|
|
891
|
+
* Validate the callback `state`, exchange the code with the bound PKCE
|
|
892
|
+
* verifier, and verify that the returned `id_token` (if any) carries the
|
|
893
|
+
* stored `nonce` and `aud === clientId`.
|
|
894
|
+
*/
|
|
895
|
+
async handleCallback(params) {
|
|
896
|
+
if (!params.state) {
|
|
897
|
+
throw new IQAuthError("VALIDATION_ERROR", "OIDC callback missing state parameter");
|
|
898
|
+
}
|
|
899
|
+
if (!params.code) {
|
|
900
|
+
throw new IQAuthError("VALIDATION_ERROR", "OIDC callback missing code parameter");
|
|
901
|
+
}
|
|
902
|
+
const stored = await this.stateStore.get(params.state);
|
|
903
|
+
if (!stored) {
|
|
904
|
+
throw new IQAuthError("VALIDATION_ERROR", "Unknown or expired OIDC state");
|
|
905
|
+
}
|
|
906
|
+
let tokens;
|
|
907
|
+
try {
|
|
908
|
+
tokens = await this.exchangeCode({
|
|
909
|
+
code: params.code,
|
|
910
|
+
redirectUri: stored.redirectUri,
|
|
911
|
+
clientId: stored.clientId,
|
|
912
|
+
clientSecret: params.clientSecret,
|
|
913
|
+
codeVerifier: stored.codeVerifier
|
|
914
|
+
});
|
|
915
|
+
} finally {
|
|
916
|
+
await this.stateStore.delete(params.state);
|
|
917
|
+
}
|
|
918
|
+
let idTokenClaims = null;
|
|
919
|
+
if (tokens.id_token) {
|
|
920
|
+
if (!this.tokensModule) {
|
|
921
|
+
throw new IQAuthError(
|
|
922
|
+
"INTERNAL_ERROR",
|
|
923
|
+
"OIDC handleCallback received an id_token but no TokensModule is configured for verification"
|
|
924
|
+
);
|
|
925
|
+
}
|
|
926
|
+
idTokenClaims = await this.tokensModule.verify(tokens.id_token, {
|
|
927
|
+
audience: stored.clientId
|
|
928
|
+
});
|
|
929
|
+
const claimsBag = idTokenClaims;
|
|
930
|
+
const tokenNonce = typeof claimsBag.nonce === "string" ? claimsBag.nonce : void 0;
|
|
931
|
+
if (!tokenNonce || tokenNonce !== stored.nonce) {
|
|
932
|
+
throw new IQAuthError(
|
|
933
|
+
"TOKEN_INVALID",
|
|
934
|
+
"OIDC id_token nonce did not match the stored value"
|
|
935
|
+
);
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
return { tokens, idTokenClaims };
|
|
939
|
+
}
|
|
940
|
+
/**
|
|
941
|
+
* Exchange an authorization code for tokens at the OIDC token endpoint.
|
|
942
|
+
*
|
|
943
|
+
* @remarks Wraps POST /oidc/token (or /api/v1/oidc/token)
|
|
944
|
+
*/
|
|
945
|
+
async exchangeCode(params) {
|
|
946
|
+
const body = {
|
|
947
|
+
grant_type: "authorization_code",
|
|
948
|
+
code: params.code,
|
|
949
|
+
redirect_uri: params.redirectUri,
|
|
950
|
+
client_id: params.clientId
|
|
951
|
+
};
|
|
952
|
+
if (params.clientSecret) body.client_secret = params.clientSecret;
|
|
953
|
+
if (params.codeVerifier) body.code_verifier = params.codeVerifier;
|
|
954
|
+
const res = await fetch(`${this.baseUrl}/oidc/token`, {
|
|
955
|
+
method: "POST",
|
|
956
|
+
headers: { "Content-Type": "application/json" },
|
|
957
|
+
body: JSON.stringify(body)
|
|
958
|
+
});
|
|
959
|
+
if (!res.ok) {
|
|
960
|
+
const err = await res.json().catch(() => ({}));
|
|
961
|
+
throw new Error(
|
|
962
|
+
`OIDC token exchange failed: ${err.error_description || err.error || res.status}`
|
|
963
|
+
);
|
|
964
|
+
}
|
|
965
|
+
return res.json();
|
|
966
|
+
}
|
|
967
|
+
/**
|
|
968
|
+
* Get user info from the OIDC userinfo endpoint.
|
|
969
|
+
*
|
|
970
|
+
* @remarks Wraps GET /oidc/userinfo (requires Bearer token)
|
|
971
|
+
*/
|
|
972
|
+
async getUserInfo() {
|
|
973
|
+
if (this.http.isBrowserSession()) {
|
|
974
|
+
throw new Error("oidc.getUserInfo() requires a bearer token and is not supported in browser_session mode.");
|
|
975
|
+
}
|
|
976
|
+
return this.http.requestRaw("GET", "/oidc/userinfo");
|
|
977
|
+
}
|
|
978
|
+
async getClientContext(clientId) {
|
|
979
|
+
const res = await fetch(`${this.baseUrl}/oidc/client-context?client_id=${encodeURIComponent(clientId)}`);
|
|
980
|
+
if (!res.ok) throw new Error(`Failed to fetch OIDC client context: ${res.status}`);
|
|
981
|
+
const payload = await res.json();
|
|
982
|
+
if (!payload.success || !payload.data) {
|
|
983
|
+
throw new Error("OIDC client context response was invalid");
|
|
984
|
+
}
|
|
985
|
+
return payload.data;
|
|
986
|
+
}
|
|
987
|
+
};
|
|
988
|
+
|
|
989
|
+
// src/modules/tenants.ts
|
|
990
|
+
function normalizeBrandingConfig(config) {
|
|
991
|
+
return {
|
|
992
|
+
...config,
|
|
993
|
+
brandName: config.brandName ?? config.companyName,
|
|
994
|
+
loginHeadline: config.loginHeadline ?? config.headline ?? null,
|
|
995
|
+
loginSubheadline: config.loginSubheadline ?? config.subheadline ?? null,
|
|
996
|
+
companyName: config.companyName ?? config.brandName,
|
|
997
|
+
headline: config.headline ?? config.loginHeadline ?? null,
|
|
998
|
+
subheadline: config.subheadline ?? config.loginSubheadline ?? null
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
var TenantsModule = class {
|
|
1002
|
+
constructor(http) {
|
|
1003
|
+
this.http = http;
|
|
1004
|
+
}
|
|
1005
|
+
async getCurrent(tenantId) {
|
|
1006
|
+
return this.http.request("GET", `/api/v1/tenants/${tenantId}`);
|
|
1007
|
+
}
|
|
1008
|
+
async get(tenantId) {
|
|
1009
|
+
return this.http.request("GET", `/api/v1/tenants/${tenantId}`);
|
|
1010
|
+
}
|
|
1011
|
+
async list(params) {
|
|
1012
|
+
const query = new URLSearchParams();
|
|
1013
|
+
if (params?.vendorId) query.set("vendorId", params.vendorId);
|
|
1014
|
+
const qs = query.toString();
|
|
1015
|
+
return this.http.request("GET", `/api/v1/tenants${qs ? `?${qs}` : ""}`);
|
|
1016
|
+
}
|
|
1017
|
+
async create(data) {
|
|
1018
|
+
return this.http.request("POST", "/api/v1/tenants", data);
|
|
1019
|
+
}
|
|
1020
|
+
async update(tenantId, data) {
|
|
1021
|
+
return this.http.request("PATCH", `/api/v1/tenants/${tenantId}`, data);
|
|
1022
|
+
}
|
|
1023
|
+
async delete(tenantId) {
|
|
1024
|
+
return this.http.request("DELETE", `/api/v1/tenants/${tenantId}`);
|
|
1025
|
+
}
|
|
1026
|
+
async promoteToVendor(tenantId, data) {
|
|
1027
|
+
return this.http.request("POST", `/api/v1/tenants/${tenantId}/promote-to-vendor`, data);
|
|
1028
|
+
}
|
|
1029
|
+
async getUsers(tenantId) {
|
|
1030
|
+
return this.http.request("GET", `/api/v1/tenants/${tenantId}/users`);
|
|
1031
|
+
}
|
|
1032
|
+
async inviteUser(tenantId, data) {
|
|
1033
|
+
return this.http.request("POST", `/api/v1/tenants/${tenantId}/users/invite`, data);
|
|
1034
|
+
}
|
|
1035
|
+
async changeUserRole(tenantId, userId, role) {
|
|
1036
|
+
return this.http.request("PATCH", `/api/v1/tenants/${tenantId}/users/${userId}/role`, { role });
|
|
1037
|
+
}
|
|
1038
|
+
async migrateUser(tenantId, userId, data) {
|
|
1039
|
+
return this.http.request("POST", `/api/v1/tenants/${tenantId}/users/${userId}/migrate`, data);
|
|
1040
|
+
}
|
|
1041
|
+
async removeUser(tenantId, userId) {
|
|
1042
|
+
return this.http.request("DELETE", `/api/v1/tenants/${tenantId}/users/${userId}`);
|
|
1043
|
+
}
|
|
1044
|
+
async getPasswordPolicy(tenantId) {
|
|
1045
|
+
return this.http.request("GET", `/api/v1/tenants/${tenantId}/password-policy`);
|
|
1046
|
+
}
|
|
1047
|
+
async updatePasswordPolicy(tenantId, data) {
|
|
1048
|
+
return this.http.request("PATCH", `/api/v1/tenants/${tenantId}/password-policy`, data);
|
|
1049
|
+
}
|
|
1050
|
+
async getMfaPolicies(tenantId) {
|
|
1051
|
+
return this.http.request("GET", `/api/v1/tenants/${tenantId}/mfa-policies`);
|
|
1052
|
+
}
|
|
1053
|
+
async updateMfaPolicy(tenantId, role, data) {
|
|
1054
|
+
return this.http.request("PATCH", `/api/v1/tenants/${tenantId}/mfa-policies/${role}`, data);
|
|
1055
|
+
}
|
|
1056
|
+
async getPublicBranding(params) {
|
|
1057
|
+
const query = new URLSearchParams();
|
|
1058
|
+
if (params?.vendor) query.set("vendor", params.vendor);
|
|
1059
|
+
const qs = query.toString();
|
|
1060
|
+
const url = `${this.http.baseUrl}/api/public/branding${qs ? `?${qs}` : ""}`;
|
|
1061
|
+
const res = await fetch(url);
|
|
1062
|
+
if (!res.ok) return null;
|
|
1063
|
+
const body = await res.json();
|
|
1064
|
+
if (body.success && body.data) return normalizeBrandingConfig(body.data);
|
|
1065
|
+
return null;
|
|
1066
|
+
}
|
|
1067
|
+
async getPublicBrandingBySlug(vendorSlug) {
|
|
1068
|
+
const url = `${this.http.baseUrl}/api/public/branding/by-slug/${encodeURIComponent(vendorSlug)}`;
|
|
1069
|
+
const res = await fetch(url);
|
|
1070
|
+
if (!res.ok) return null;
|
|
1071
|
+
const body = await res.json();
|
|
1072
|
+
if (body.success && body.data) return normalizeBrandingConfig(body.data);
|
|
1073
|
+
return null;
|
|
1074
|
+
}
|
|
1075
|
+
};
|
|
1076
|
+
|
|
1077
|
+
// src/modules/apps.ts
|
|
1078
|
+
var AppsModule = class {
|
|
1079
|
+
constructor(http) {
|
|
1080
|
+
this.http = http;
|
|
1081
|
+
}
|
|
1082
|
+
/**
|
|
1083
|
+
* Self-service Phase A: provision an app + OIDC client + key pair in one shot.
|
|
1084
|
+
* Caller must be authenticated as a tenant_admin (or platform_admin).
|
|
1085
|
+
*
|
|
1086
|
+
* @remarks Wraps POST /api/v1/apps
|
|
1087
|
+
*/
|
|
1088
|
+
async create(data) {
|
|
1089
|
+
return this.http.request("POST", "/api/v1/apps", data);
|
|
1090
|
+
}
|
|
1091
|
+
async update(id, data) {
|
|
1092
|
+
return this.http.request("PATCH", `/api/v1/apps/${encodeURIComponent(id)}`, data);
|
|
1093
|
+
}
|
|
1094
|
+
async remove(id) {
|
|
1095
|
+
return this.http.request("DELETE", `/api/v1/apps/${encodeURIComponent(id)}`);
|
|
1096
|
+
}
|
|
1097
|
+
async listOrigins(id) {
|
|
1098
|
+
return this.http.request("GET", `/api/v1/apps/${encodeURIComponent(id)}/origins`);
|
|
1099
|
+
}
|
|
1100
|
+
async addOrigin(id, origin) {
|
|
1101
|
+
return this.http.request("POST", `/api/v1/apps/${encodeURIComponent(id)}/origins`, { origin });
|
|
1102
|
+
}
|
|
1103
|
+
async removeOrigin(id, origin) {
|
|
1104
|
+
return this.http.request("DELETE", `/api/v1/apps/${encodeURIComponent(id)}/origins/${encodeURIComponent(origin)}`);
|
|
1105
|
+
}
|
|
1106
|
+
async listKeys(id) {
|
|
1107
|
+
return this.http.request("GET", `/api/v1/apps/${encodeURIComponent(id)}/keys`);
|
|
1108
|
+
}
|
|
1109
|
+
async createKey(id, data) {
|
|
1110
|
+
return this.http.request("POST", `/api/v1/apps/${encodeURIComponent(id)}/keys`, data);
|
|
1111
|
+
}
|
|
1112
|
+
async rotateKey(id, keyId) {
|
|
1113
|
+
return this.http.request("POST", `/api/v1/apps/${encodeURIComponent(id)}/keys/${encodeURIComponent(keyId)}/rotate`, {});
|
|
1114
|
+
}
|
|
1115
|
+
async revokeKey(id, keyId) {
|
|
1116
|
+
return this.http.request("DELETE", `/api/v1/apps/${encodeURIComponent(id)}/keys/${encodeURIComponent(keyId)}`);
|
|
1117
|
+
}
|
|
1118
|
+
/**
|
|
1119
|
+
* List all registered applications.
|
|
1120
|
+
* Requires `apps.view` permission on the `iqauth-admin` app.
|
|
1121
|
+
*
|
|
1122
|
+
* @remarks Wraps GET /api/v1/apps
|
|
1123
|
+
*/
|
|
1124
|
+
async list() {
|
|
1125
|
+
return this.http.request("GET", "/api/v1/apps");
|
|
1126
|
+
}
|
|
1127
|
+
/**
|
|
1128
|
+
* Get a registered application by its unique key, including its permission nodes.
|
|
1129
|
+
* Requires `apps.view` permission on the `iqauth-admin` app.
|
|
1130
|
+
*
|
|
1131
|
+
* @remarks Wraps GET /api/v1/apps/:appKey
|
|
1132
|
+
*/
|
|
1133
|
+
async get(appKey) {
|
|
1134
|
+
return this.http.request("GET", `/api/v1/apps/${encodeURIComponent(appKey)}`);
|
|
1135
|
+
}
|
|
1136
|
+
/**
|
|
1137
|
+
* Register or sync an application manifest. This is an idempotent upsert —
|
|
1138
|
+
* it creates the app if it doesn't exist, or updates it if it does.
|
|
1139
|
+
* Permission nodes are also upserted (created or updated) from the manifest tree.
|
|
1140
|
+
*
|
|
1141
|
+
* Requires `platform_admin` role and `apps.manage` permission.
|
|
1142
|
+
*
|
|
1143
|
+
* @remarks Wraps POST /api/v1/apps/sync
|
|
1144
|
+
*/
|
|
1145
|
+
async register(manifest) {
|
|
1146
|
+
if (!this.http.hasCredentials()) {
|
|
1147
|
+
throw new IQAuthError(
|
|
1148
|
+
"AUTH_REQUIRED",
|
|
1149
|
+
"Cannot sync app manifest: no API key or access token configured. Initialize the client with an apiKey or accessToken.",
|
|
1150
|
+
401
|
|
1151
|
+
);
|
|
1152
|
+
}
|
|
1153
|
+
return this.http.request("POST", "/api/v1/apps/sync", manifest);
|
|
1154
|
+
}
|
|
1155
|
+
/**
|
|
1156
|
+
* Check if an application is registered by its key.
|
|
1157
|
+
* Returns `true` if the app exists, `false` otherwise.
|
|
1158
|
+
*
|
|
1159
|
+
* @remarks Uses GET /api/v1/apps/:appKey — catches 404 errors
|
|
1160
|
+
*/
|
|
1161
|
+
async isRegistered(appKey) {
|
|
1162
|
+
try {
|
|
1163
|
+
await this.get(appKey);
|
|
1164
|
+
return true;
|
|
1165
|
+
} catch (err) {
|
|
1166
|
+
if (err.code === "NOT_FOUND" || err.status === 404) {
|
|
1167
|
+
return false;
|
|
1168
|
+
}
|
|
1169
|
+
throw err;
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
};
|
|
1173
|
+
|
|
1174
|
+
// src/modules/roles.ts
|
|
1175
|
+
var RolesModule = class {
|
|
1176
|
+
constructor(http) {
|
|
1177
|
+
this.http = http;
|
|
1178
|
+
}
|
|
1179
|
+
async list(tenantId) {
|
|
1180
|
+
return this.http.request("GET", `/api/v1/tenants/${tenantId}/roles`);
|
|
1181
|
+
}
|
|
1182
|
+
async create(tenantId, data) {
|
|
1183
|
+
return this.http.request("POST", `/api/v1/tenants/${tenantId}/roles`, data);
|
|
1184
|
+
}
|
|
1185
|
+
async update(tenantId, roleId, data) {
|
|
1186
|
+
return this.http.request("PATCH", `/api/v1/tenants/${tenantId}/roles/${roleId}`, data);
|
|
1187
|
+
}
|
|
1188
|
+
async delete(tenantId, roleId) {
|
|
1189
|
+
return this.http.request("DELETE", `/api/v1/tenants/${tenantId}/roles/${roleId}`);
|
|
1190
|
+
}
|
|
1191
|
+
async getUserRoles(tenantId, userId) {
|
|
1192
|
+
return this.http.request("GET", `/api/v1/tenants/${tenantId}/users/${userId}/roles`);
|
|
1193
|
+
}
|
|
1194
|
+
async assignRole(tenantId, userId, data) {
|
|
1195
|
+
return this.http.request("POST", `/api/v1/tenants/${tenantId}/users/${userId}/roles`, data);
|
|
1196
|
+
}
|
|
1197
|
+
async removeRole(tenantId, userId, roleId) {
|
|
1198
|
+
return this.http.request("DELETE", `/api/v1/tenants/${tenantId}/users/${userId}/roles/${roleId}`);
|
|
1199
|
+
}
|
|
1200
|
+
};
|
|
1201
|
+
|
|
1202
|
+
// src/modules/permissionGroups.ts
|
|
1203
|
+
var PermissionGroupsModule = class {
|
|
1204
|
+
constructor(http) {
|
|
1205
|
+
this.http = http;
|
|
1206
|
+
}
|
|
1207
|
+
async list(tenantId) {
|
|
1208
|
+
return this.http.request("GET", `/api/v1/tenants/${tenantId}/permission-groups`);
|
|
1209
|
+
}
|
|
1210
|
+
async create(tenantId, name, description) {
|
|
1211
|
+
return this.http.request("POST", `/api/v1/tenants/${tenantId}/permission-groups`, { name, description });
|
|
1212
|
+
}
|
|
1213
|
+
async update(tenantId, groupId, data) {
|
|
1214
|
+
return this.http.request("PATCH", `/api/v1/tenants/${tenantId}/permission-groups/${groupId}`, data);
|
|
1215
|
+
}
|
|
1216
|
+
async delete(tenantId, groupId) {
|
|
1217
|
+
return this.http.request("DELETE", `/api/v1/tenants/${tenantId}/permission-groups/${groupId}`);
|
|
1218
|
+
}
|
|
1219
|
+
async getPermissions(tenantId, groupId) {
|
|
1220
|
+
return this.http.request("GET", `/api/v1/tenants/${tenantId}/permission-groups/${groupId}/permissions`);
|
|
1221
|
+
}
|
|
1222
|
+
async addPermission(tenantId, groupId, data) {
|
|
1223
|
+
return this.http.request("POST", `/api/v1/tenants/${tenantId}/permission-groups/${groupId}/permissions`, data);
|
|
1224
|
+
}
|
|
1225
|
+
async removePermission(tenantId, groupId, permissionId) {
|
|
1226
|
+
return this.http.request("DELETE", `/api/v1/tenants/${tenantId}/permission-groups/${groupId}/permissions/${permissionId}`);
|
|
1227
|
+
}
|
|
1228
|
+
async addInheritance(tenantId, groupId, inheritsFromGroupId) {
|
|
1229
|
+
return this.http.request("POST", `/api/v1/tenants/${tenantId}/permission-groups/${groupId}/inherit`, { inheritsFromGroupId });
|
|
1230
|
+
}
|
|
1231
|
+
async removeInheritance(tenantId, groupId, inheritedGroupId) {
|
|
1232
|
+
return this.http.request("DELETE", `/api/v1/tenants/${tenantId}/permission-groups/${groupId}/inherit/${inheritedGroupId}`);
|
|
1233
|
+
}
|
|
1234
|
+
async getUserGroups(tenantId, userId) {
|
|
1235
|
+
return this.http.request("GET", `/api/v1/tenants/${tenantId}/users/${userId}/groups`);
|
|
1236
|
+
}
|
|
1237
|
+
async assignUserToGroup(tenantId, userId, groupId) {
|
|
1238
|
+
return this.http.request("POST", `/api/v1/tenants/${tenantId}/users/${userId}/groups`, { groupId });
|
|
1239
|
+
}
|
|
1240
|
+
async removeUserFromGroup(tenantId, userId, groupId) {
|
|
1241
|
+
return this.http.request("DELETE", `/api/v1/tenants/${tenantId}/users/${userId}/groups/${groupId}`);
|
|
1242
|
+
}
|
|
1243
|
+
async getUserOverrides(tenantId, userId) {
|
|
1244
|
+
return this.http.request("GET", `/api/v1/tenants/${tenantId}/users/${userId}/permissions/overrides`);
|
|
1245
|
+
}
|
|
1246
|
+
async addUserOverride(tenantId, userId, data) {
|
|
1247
|
+
return this.http.request("POST", `/api/v1/tenants/${tenantId}/users/${userId}/permissions/overrides`, data);
|
|
1248
|
+
}
|
|
1249
|
+
async removeUserOverride(tenantId, userId, overrideId) {
|
|
1250
|
+
return this.http.request("DELETE", `/api/v1/tenants/${tenantId}/users/${userId}/permissions/overrides/${overrideId}`);
|
|
1251
|
+
}
|
|
1252
|
+
async getEffectivePermissions(tenantId, userId, params) {
|
|
1253
|
+
const query = new URLSearchParams();
|
|
1254
|
+
if (params.product) query.set("product", params.product);
|
|
1255
|
+
if (params.appKey) query.set("appKey", params.appKey);
|
|
1256
|
+
const qs = query.toString();
|
|
1257
|
+
return this.http.request("GET", `/api/v1/tenants/${tenantId}/users/${userId}/permissions/effective${qs ? `?${qs}` : ""}`);
|
|
1258
|
+
}
|
|
1259
|
+
async checkPermission(tenantId, userId, appKey, nodeKey) {
|
|
1260
|
+
return this.http.request("POST", `/api/v1/tenants/${tenantId}/users/${userId}/permissions/check`, { appKey, nodeKey });
|
|
1261
|
+
}
|
|
1262
|
+
};
|
|
1263
|
+
|
|
1264
|
+
// src/modules/apiKeys.ts
|
|
1265
|
+
var ApiKeysModule = class {
|
|
1266
|
+
constructor(http) {
|
|
1267
|
+
this.http = http;
|
|
1268
|
+
}
|
|
1269
|
+
async create(data) {
|
|
1270
|
+
return this.http.request("POST", "/api/v1/api-keys", data);
|
|
1271
|
+
}
|
|
1272
|
+
async list(params) {
|
|
1273
|
+
const query = new URLSearchParams();
|
|
1274
|
+
if (params?.tenantId) query.set("tenantId", params.tenantId);
|
|
1275
|
+
const qs = query.toString();
|
|
1276
|
+
return this.http.request("GET", `/api/v1/api-keys${qs ? `?${qs}` : ""}`);
|
|
1277
|
+
}
|
|
1278
|
+
async revoke(id) {
|
|
1279
|
+
return this.http.request("DELETE", `/api/v1/api-keys/${id}`);
|
|
1280
|
+
}
|
|
1281
|
+
async introspect(apiKey) {
|
|
1282
|
+
return this.http.request("POST", "/api/v1/api-keys/introspect", { apiKey }, { skipAutoRefresh: true });
|
|
1283
|
+
}
|
|
1284
|
+
};
|
|
1285
|
+
|
|
1286
|
+
// src/modules/invites.ts
|
|
1287
|
+
var InvitesModule = class {
|
|
1288
|
+
constructor(http) {
|
|
1289
|
+
this.http = http;
|
|
1290
|
+
}
|
|
1291
|
+
async create(data) {
|
|
1292
|
+
return this.http.request("POST", "/api/v1/invites", data);
|
|
1293
|
+
}
|
|
1294
|
+
async validate(token) {
|
|
1295
|
+
return this.http.request("GET", `/api/v1/invites/${token}/validate`);
|
|
1296
|
+
}
|
|
1297
|
+
async accept(token, data) {
|
|
1298
|
+
return this.http.request("POST", `/api/v1/invites/${token}/accept`, data, { skipAutoRefresh: true });
|
|
1299
|
+
}
|
|
1300
|
+
};
|
|
1301
|
+
|
|
1302
|
+
// src/modules/webhooks.ts
|
|
1303
|
+
function normalizeWebhookDelivery(delivery) {
|
|
1304
|
+
return {
|
|
1305
|
+
...delivery,
|
|
1306
|
+
endpointId: delivery.endpointId ?? delivery.webhookEndpointId,
|
|
1307
|
+
webhookEndpointId: delivery.webhookEndpointId ?? delivery.endpointId,
|
|
1308
|
+
event: delivery.event ?? delivery.eventType,
|
|
1309
|
+
eventType: delivery.eventType ?? delivery.event,
|
|
1310
|
+
statusCode: delivery.statusCode ?? delivery.responseStatus ?? null,
|
|
1311
|
+
responseStatus: delivery.responseStatus ?? delivery.statusCode ?? null,
|
|
1312
|
+
response: delivery.response ?? delivery.responseBody ?? null,
|
|
1313
|
+
responseBody: delivery.responseBody ?? delivery.response ?? null,
|
|
1314
|
+
deliveredAt: delivery.deliveredAt ?? delivery.createdAt
|
|
1315
|
+
};
|
|
1316
|
+
}
|
|
1317
|
+
var WebhooksModule = class {
|
|
1318
|
+
constructor(http) {
|
|
1319
|
+
this.http = http;
|
|
1320
|
+
}
|
|
1321
|
+
async createEndpoint(data) {
|
|
1322
|
+
return this.http.request("POST", "/api/v1/webhooks/endpoints", data);
|
|
1323
|
+
}
|
|
1324
|
+
async listEndpoints() {
|
|
1325
|
+
return this.http.request("GET", "/api/v1/webhooks/endpoints");
|
|
1326
|
+
}
|
|
1327
|
+
async deleteEndpoint(id) {
|
|
1328
|
+
return this.http.request("DELETE", `/api/v1/webhooks/endpoints/${id}`);
|
|
1329
|
+
}
|
|
1330
|
+
async getDeliveries(endpointId) {
|
|
1331
|
+
const deliveries = await this.http.request("GET", `/api/v1/webhooks/deliveries?endpointId=${encodeURIComponent(endpointId)}`);
|
|
1332
|
+
return deliveries.map(normalizeWebhookDelivery);
|
|
1333
|
+
}
|
|
1334
|
+
async testEndpoint(id) {
|
|
1335
|
+
return this.http.request("POST", `/api/v1/webhooks/endpoints/${id}/test`);
|
|
1336
|
+
}
|
|
1337
|
+
async rotateSecret(id) {
|
|
1338
|
+
return this.http.request("POST", `/api/v1/webhooks/endpoints/${id}/rotate-secret`);
|
|
1339
|
+
}
|
|
1340
|
+
};
|
|
1341
|
+
|
|
1342
|
+
// src/modules/entitlements.ts
|
|
1343
|
+
var EntitlementsModule = class {
|
|
1344
|
+
constructor(http) {
|
|
1345
|
+
this.http = http;
|
|
1346
|
+
}
|
|
1347
|
+
async list(tenantId) {
|
|
1348
|
+
return this.http.request("GET", `/api/v1/tenants/${tenantId}/entitlements`);
|
|
1349
|
+
}
|
|
1350
|
+
async grant(tenantId, data) {
|
|
1351
|
+
return this.http.request("POST", `/api/v1/tenants/${tenantId}/entitlements`, data);
|
|
1352
|
+
}
|
|
1353
|
+
async revoke(tenantId, product) {
|
|
1354
|
+
return this.http.request("DELETE", `/api/v1/tenants/${tenantId}/entitlements/${encodeURIComponent(product)}`);
|
|
1355
|
+
}
|
|
1356
|
+
};
|
|
1357
|
+
|
|
1358
|
+
// src/modules/vendors.ts
|
|
1359
|
+
var VendorsModule = class {
|
|
1360
|
+
constructor(http) {
|
|
1361
|
+
this.http = http;
|
|
1362
|
+
}
|
|
1363
|
+
async list() {
|
|
1364
|
+
return this.http.request("GET", "/api/v1/vendors");
|
|
1365
|
+
}
|
|
1366
|
+
async get(vendorId) {
|
|
1367
|
+
return this.http.request("GET", `/api/v1/vendors/${vendorId}`);
|
|
1368
|
+
}
|
|
1369
|
+
async create(data) {
|
|
1370
|
+
return this.http.request("POST", "/api/v1/vendors", data);
|
|
1371
|
+
}
|
|
1372
|
+
async update(vendorId, data) {
|
|
1373
|
+
return this.http.request("PATCH", `/api/v1/vendors/${vendorId}`, data);
|
|
1374
|
+
}
|
|
1375
|
+
async delete(vendorId) {
|
|
1376
|
+
return this.http.request("DELETE", `/api/v1/vendors/${vendorId}`);
|
|
1377
|
+
}
|
|
1378
|
+
};
|
|
1379
|
+
|
|
1380
|
+
// src/modules/sources.ts
|
|
1381
|
+
var SourcesModule = class {
|
|
1382
|
+
constructor(http) {
|
|
1383
|
+
this.http = http;
|
|
1384
|
+
}
|
|
1385
|
+
async create(vendorId, data) {
|
|
1386
|
+
return this.http.request("POST", `/api/v1/vendors/${vendorId}/sources`, data);
|
|
1387
|
+
}
|
|
1388
|
+
async listForVendor(vendorId) {
|
|
1389
|
+
return this.http.request("GET", `/api/v1/vendors/${vendorId}/sources`);
|
|
1390
|
+
}
|
|
1391
|
+
async get(sourceId) {
|
|
1392
|
+
return this.http.request("GET", `/api/v1/sources/${sourceId}`);
|
|
1393
|
+
}
|
|
1394
|
+
async update(sourceId, data) {
|
|
1395
|
+
return this.http.request("PATCH", `/api/v1/sources/${sourceId}`, data);
|
|
1396
|
+
}
|
|
1397
|
+
async delete(sourceId) {
|
|
1398
|
+
return this.http.request("DELETE", `/api/v1/sources/${sourceId}`);
|
|
1399
|
+
}
|
|
1400
|
+
async createClient(sourceId, data) {
|
|
1401
|
+
return this.http.request("POST", `/api/v1/sources/${sourceId}/clients`, data);
|
|
1402
|
+
}
|
|
1403
|
+
async listClients(sourceId) {
|
|
1404
|
+
return this.http.request("GET", `/api/v1/sources/${sourceId}/clients`);
|
|
1405
|
+
}
|
|
1406
|
+
};
|
|
1407
|
+
|
|
1408
|
+
// src/modules/clients.ts
|
|
1409
|
+
var ClientsModule = class {
|
|
1410
|
+
constructor(http) {
|
|
1411
|
+
this.http = http;
|
|
1412
|
+
}
|
|
1413
|
+
async get(clientId) {
|
|
1414
|
+
return this.http.request("GET", `/api/v1/clients/${clientId}`);
|
|
1415
|
+
}
|
|
1416
|
+
async update(clientId, data) {
|
|
1417
|
+
return this.http.request("PATCH", `/api/v1/clients/${clientId}`, data);
|
|
1418
|
+
}
|
|
1419
|
+
async delete(clientId) {
|
|
1420
|
+
return this.http.request("DELETE", `/api/v1/clients/${clientId}`);
|
|
1421
|
+
}
|
|
1422
|
+
};
|
|
1423
|
+
|
|
1424
|
+
// src/modules/hierarchy.ts
|
|
1425
|
+
var HierarchyModule = class {
|
|
1426
|
+
constructor(http) {
|
|
1427
|
+
this.http = http;
|
|
1428
|
+
}
|
|
1429
|
+
async getGraph() {
|
|
1430
|
+
return this.http.request("GET", "/api/v1/hierarchy");
|
|
1431
|
+
}
|
|
1432
|
+
async linkVendorSource(vendorId, sourceId) {
|
|
1433
|
+
return this.http.request("POST", "/api/v1/hierarchy/link/vendor-source", { vendorId, sourceId });
|
|
1434
|
+
}
|
|
1435
|
+
async unlinkVendorSource(vendorId, sourceId) {
|
|
1436
|
+
return this.http.request("DELETE", "/api/v1/hierarchy/link/vendor-source", { vendorId, sourceId });
|
|
1437
|
+
}
|
|
1438
|
+
async linkSourceClient(sourceId, clientId) {
|
|
1439
|
+
return this.http.request("POST", "/api/v1/hierarchy/link/source-client", { sourceId, clientId });
|
|
1440
|
+
}
|
|
1441
|
+
async unlinkSourceClient(sourceId, clientId) {
|
|
1442
|
+
return this.http.request("DELETE", "/api/v1/hierarchy/link/source-client", { sourceId, clientId });
|
|
1443
|
+
}
|
|
1444
|
+
};
|
|
1445
|
+
|
|
1446
|
+
// src/modules/memberships.ts
|
|
1447
|
+
var MembershipsModule = class {
|
|
1448
|
+
constructor(http) {
|
|
1449
|
+
this.http = http;
|
|
1450
|
+
}
|
|
1451
|
+
async listForUser(userId, tenantId) {
|
|
1452
|
+
return this.http.request("GET", `/api/v1/users/${userId}/memberships?tenantId=${encodeURIComponent(tenantId)}`);
|
|
1453
|
+
}
|
|
1454
|
+
async listForScope(scopeType, scopeId) {
|
|
1455
|
+
return this.http.request("GET", `/api/v1/memberships/scope/${scopeType}/${scopeId}`);
|
|
1456
|
+
}
|
|
1457
|
+
async grant(data) {
|
|
1458
|
+
return this.http.request("POST", "/api/v1/memberships", data);
|
|
1459
|
+
}
|
|
1460
|
+
async revoke(id) {
|
|
1461
|
+
return this.http.request("DELETE", `/api/v1/memberships/${id}`);
|
|
1462
|
+
}
|
|
1463
|
+
async update(id, data) {
|
|
1464
|
+
return this.http.request("PATCH", `/api/v1/memberships/${id}`, data);
|
|
1465
|
+
}
|
|
1466
|
+
async listForTenant(params) {
|
|
1467
|
+
const query = new URLSearchParams();
|
|
1468
|
+
if (params?.scopeType) query.set("scopeType", params.scopeType);
|
|
1469
|
+
if (params?.roleName) query.set("roleName", params.roleName);
|
|
1470
|
+
const qs = query.toString();
|
|
1471
|
+
return this.http.request("GET", `/api/v1/memberships/tenant${qs ? `?${qs}` : ""}`);
|
|
1472
|
+
}
|
|
1473
|
+
};
|
|
1474
|
+
|
|
1475
|
+
// src/modules/scope.ts
|
|
1476
|
+
var ScopeModule = class {
|
|
1477
|
+
constructor(http) {
|
|
1478
|
+
this.http = http;
|
|
1479
|
+
}
|
|
1480
|
+
async getAvailable() {
|
|
1481
|
+
return this.http.request("GET", "/api/v1/auth/available-scopes");
|
|
1482
|
+
}
|
|
1483
|
+
async switchScope(scopeType, scopeId) {
|
|
1484
|
+
return this.http.request("POST", "/api/v1/auth/switch-scope", { scopeType, scopeId });
|
|
1485
|
+
}
|
|
1486
|
+
};
|
|
1487
|
+
|
|
1488
|
+
// src/modules/gdpr.ts
|
|
1489
|
+
var GdprModule = class {
|
|
1490
|
+
constructor(http) {
|
|
1491
|
+
this.http = http;
|
|
1492
|
+
}
|
|
1493
|
+
async exportData() {
|
|
1494
|
+
return this.http.request("GET", "/api/v1/gdpr/export");
|
|
1495
|
+
}
|
|
1496
|
+
async deleteAccount(confirmEmail) {
|
|
1497
|
+
return this.http.request("POST", "/api/v1/gdpr/delete", { confirmEmail });
|
|
1498
|
+
}
|
|
1499
|
+
};
|
|
1500
|
+
|
|
1501
|
+
// src/modules/pin.ts
|
|
1502
|
+
var PinModule = class {
|
|
1503
|
+
constructor(http) {
|
|
1504
|
+
this.http = http;
|
|
1505
|
+
}
|
|
1506
|
+
async set(pin) {
|
|
1507
|
+
return this.http.request("POST", "/api/v1/pin/set", { pin });
|
|
1508
|
+
}
|
|
1509
|
+
async change(currentPin, newPin) {
|
|
1510
|
+
return this.http.request("POST", "/api/v1/pin/change", { currentPin, newPin });
|
|
1511
|
+
}
|
|
1512
|
+
async remove() {
|
|
1513
|
+
return this.http.request("DELETE", "/api/v1/pin");
|
|
1514
|
+
}
|
|
1515
|
+
async getStatus() {
|
|
1516
|
+
return this.http.request("GET", "/api/v1/pin/status");
|
|
1517
|
+
}
|
|
1518
|
+
async login(email, pin, deviceFingerprint) {
|
|
1519
|
+
const data = await this.http.request(
|
|
1520
|
+
"POST",
|
|
1521
|
+
"/api/v1/pin/login",
|
|
1522
|
+
{ email, pin, ...deviceFingerprint && { deviceFingerprint } },
|
|
1523
|
+
{ skipAutoRefresh: true }
|
|
1524
|
+
);
|
|
1525
|
+
if (data.type === "success" && data.accessToken && data.refreshToken && data.user) {
|
|
1526
|
+
return {
|
|
1527
|
+
status: "authenticated",
|
|
1528
|
+
authMode: "token",
|
|
1529
|
+
tokens: { accessToken: data.accessToken, refreshToken: data.refreshToken },
|
|
1530
|
+
user: data.user
|
|
1531
|
+
};
|
|
1532
|
+
}
|
|
1533
|
+
if (data.type === "mfa_required" && data.mfaChallengeToken) {
|
|
1534
|
+
return {
|
|
1535
|
+
status: "mfa_required",
|
|
1536
|
+
mfaChallengeToken: data.mfaChallengeToken,
|
|
1537
|
+
availableMethods: data.availableMethods || []
|
|
1538
|
+
};
|
|
1539
|
+
}
|
|
1540
|
+
if (data.type === "tenant_selection" && data.tenantSelectionToken && data.tenants) {
|
|
1541
|
+
return {
|
|
1542
|
+
status: "tenant_selection",
|
|
1543
|
+
tenantSelectionToken: data.tenantSelectionToken,
|
|
1544
|
+
tenants: data.tenants
|
|
1545
|
+
};
|
|
1546
|
+
}
|
|
1547
|
+
throw new Error("Unexpected PIN login response shape");
|
|
1548
|
+
}
|
|
1549
|
+
};
|
|
1550
|
+
|
|
1551
|
+
// src/modules/mfa.ts
|
|
1552
|
+
var MfaModule = class {
|
|
1553
|
+
constructor(http) {
|
|
1554
|
+
this.http = http;
|
|
1555
|
+
}
|
|
1556
|
+
async getAvailableMethods() {
|
|
1557
|
+
return this.http.request("GET", "/api/v1/mfa/methods");
|
|
1558
|
+
}
|
|
1559
|
+
async enrollTotp() {
|
|
1560
|
+
return this.http.request("POST", "/api/v1/mfa/enroll/totp");
|
|
1561
|
+
}
|
|
1562
|
+
async verifyTotpEnrollment(code, secret) {
|
|
1563
|
+
return this.http.request("POST", "/api/v1/mfa/enroll/totp/verify", { code, secret });
|
|
1564
|
+
}
|
|
1565
|
+
async enrollSms(phoneNumber) {
|
|
1566
|
+
return this.http.request("POST", "/api/v1/mfa/enroll/sms", { phoneNumber });
|
|
1567
|
+
}
|
|
1568
|
+
async verifySmsEnrollment(code, phoneNumber) {
|
|
1569
|
+
return this.http.request("POST", "/api/v1/mfa/enroll/sms/verify", { code, phoneNumber });
|
|
1570
|
+
}
|
|
1571
|
+
async startEmailEnrollment() {
|
|
1572
|
+
return this.http.request("POST", "/api/v1/mfa/enroll/email/start");
|
|
1573
|
+
}
|
|
1574
|
+
async verifyEmailEnrollment(code) {
|
|
1575
|
+
return this.http.request("POST", "/api/v1/mfa/enroll/email/verify", { code });
|
|
1576
|
+
}
|
|
1577
|
+
async listEnrollments() {
|
|
1578
|
+
return this.http.request("GET", "/api/v1/mfa/enrollments");
|
|
1579
|
+
}
|
|
1580
|
+
async setPrimaryEnrollment(enrollmentId) {
|
|
1581
|
+
return this.http.request("PATCH", `/api/v1/mfa/enrollments/${enrollmentId}/primary`);
|
|
1582
|
+
}
|
|
1583
|
+
async deactivateEnrollment(enrollmentId) {
|
|
1584
|
+
return this.http.request("DELETE", `/api/v1/mfa/enrollments/${enrollmentId}`);
|
|
1585
|
+
}
|
|
1586
|
+
async regenerateBackupCodes() {
|
|
1587
|
+
return this.http.request("POST", "/api/v1/mfa/backup-codes/regenerate");
|
|
1588
|
+
}
|
|
1589
|
+
async getBackupCodeCount() {
|
|
1590
|
+
return this.http.request("GET", "/api/v1/mfa/backup-codes/count");
|
|
1591
|
+
}
|
|
1592
|
+
};
|
|
1593
|
+
|
|
1594
|
+
// src/modules/branding.ts
|
|
1595
|
+
function normalizeBrandingConfig2(config) {
|
|
1596
|
+
return {
|
|
1597
|
+
...config,
|
|
1598
|
+
brandName: config.brandName ?? config.companyName,
|
|
1599
|
+
loginHeadline: config.loginHeadline ?? config.headline ?? null,
|
|
1600
|
+
loginSubheadline: config.loginSubheadline ?? config.subheadline ?? null,
|
|
1601
|
+
companyName: config.companyName ?? config.brandName,
|
|
1602
|
+
headline: config.headline ?? config.loginHeadline ?? null,
|
|
1603
|
+
subheadline: config.subheadline ?? config.loginSubheadline ?? null
|
|
1604
|
+
};
|
|
1605
|
+
}
|
|
1606
|
+
function normalizeBrandingAsset(asset) {
|
|
1607
|
+
return {
|
|
1608
|
+
...asset,
|
|
1609
|
+
url: asset.url ?? asset.publicUrl,
|
|
1610
|
+
publicUrl: asset.publicUrl ?? asset.url
|
|
1611
|
+
};
|
|
1612
|
+
}
|
|
1613
|
+
function normalizeBrandingUpdate(data) {
|
|
1614
|
+
return {
|
|
1615
|
+
...data,
|
|
1616
|
+
brandName: data.brandName ?? data.companyName,
|
|
1617
|
+
loginHeadline: data.loginHeadline ?? data.headline,
|
|
1618
|
+
loginSubheadline: data.loginSubheadline ?? data.subheadline
|
|
1619
|
+
};
|
|
1620
|
+
}
|
|
1621
|
+
var BrandingModule = class {
|
|
1622
|
+
constructor(http) {
|
|
1623
|
+
this.http = http;
|
|
1624
|
+
}
|
|
1625
|
+
async get(vendorId) {
|
|
1626
|
+
const config = await this.http.request("GET", `/api/v1/branding/${vendorId}`);
|
|
1627
|
+
return normalizeBrandingConfig2(config);
|
|
1628
|
+
}
|
|
1629
|
+
async updateBranding(vendorId, data) {
|
|
1630
|
+
const config = await this.http.request("PUT", `/api/v1/branding/${vendorId}`, normalizeBrandingUpdate(data));
|
|
1631
|
+
return normalizeBrandingConfig2(config);
|
|
1632
|
+
}
|
|
1633
|
+
async publishBranding(vendorId) {
|
|
1634
|
+
const config = await this.http.request("POST", `/api/v1/branding/${vendorId}/publish`);
|
|
1635
|
+
return normalizeBrandingConfig2(config);
|
|
1636
|
+
}
|
|
1637
|
+
async unpublishBranding(vendorId) {
|
|
1638
|
+
const config = await this.http.request("POST", `/api/v1/branding/${vendorId}/unpublish`);
|
|
1639
|
+
return normalizeBrandingConfig2(config);
|
|
1640
|
+
}
|
|
1641
|
+
async resetBranding(vendorId) {
|
|
1642
|
+
const config = await this.http.request("POST", `/api/v1/branding/${vendorId}/reset`);
|
|
1643
|
+
return normalizeBrandingConfig2(config);
|
|
1644
|
+
}
|
|
1645
|
+
async uploadAsset(vendorId, data) {
|
|
1646
|
+
const asset = await this.http.request("POST", `/api/v1/branding/${vendorId}/upload`, data);
|
|
1647
|
+
return normalizeBrandingAsset(asset);
|
|
1648
|
+
}
|
|
1649
|
+
async listAssets(vendorId) {
|
|
1650
|
+
const assets = await this.http.request("GET", `/api/v1/branding/${vendorId}/assets`);
|
|
1651
|
+
return assets.map(normalizeBrandingAsset);
|
|
1652
|
+
}
|
|
1653
|
+
async deleteAsset(vendorId, assetId) {
|
|
1654
|
+
return this.http.request("DELETE", `/api/v1/branding/${vendorId}/assets/${assetId}`);
|
|
1655
|
+
}
|
|
1656
|
+
async listDomains(vendorId) {
|
|
1657
|
+
return this.http.request("GET", `/api/v1/branding/${vendorId}/domains`);
|
|
1658
|
+
}
|
|
1659
|
+
async addDomain(vendorId, domain) {
|
|
1660
|
+
return this.http.request("POST", `/api/v1/branding/${vendorId}/domains`, { domain });
|
|
1661
|
+
}
|
|
1662
|
+
async removeDomain(vendorId, domainId) {
|
|
1663
|
+
return this.http.request("DELETE", `/api/v1/branding/${vendorId}/domains/${domainId}`);
|
|
1664
|
+
}
|
|
1665
|
+
};
|
|
1666
|
+
|
|
1667
|
+
// src/client.ts
|
|
1668
|
+
var IQAuthClient = class _IQAuthClient {
|
|
1669
|
+
constructor(config) {
|
|
1670
|
+
this.config = config;
|
|
1671
|
+
this.environment = _IQAuthClient.resolveEnvironment(config);
|
|
1672
|
+
this._accessToken = "accessToken" in config ? config.accessToken : void 0;
|
|
1673
|
+
this._refreshToken = "refreshToken" in config ? config.refreshToken : void 0;
|
|
1674
|
+
this._apiKey = "apiKey" in config ? config.apiKey : void 0;
|
|
1675
|
+
this.httpClient = new HttpClient({
|
|
1676
|
+
baseUrl: config.baseUrl,
|
|
1677
|
+
environment: this.environment,
|
|
1678
|
+
getAccessToken: () => this._accessToken,
|
|
1679
|
+
getRefreshToken: () => this._refreshToken,
|
|
1680
|
+
getApiKey: () => this._apiKey,
|
|
1681
|
+
setTokens: (tokens) => {
|
|
1682
|
+
this._accessToken = tokens.accessToken;
|
|
1683
|
+
this._refreshToken = tokens.refreshToken;
|
|
1684
|
+
},
|
|
1685
|
+
autoRefresh: "autoRefresh" in config ? config.autoRefresh !== false : true,
|
|
1686
|
+
onTokenRefresh: "onTokenRefresh" in config ? config.onTokenRefresh : void 0,
|
|
1687
|
+
sessionHeaderName: config.sessionHeaderName,
|
|
1688
|
+
sessionHeaderValue: config.sessionHeaderValue,
|
|
1689
|
+
retry: config.retry
|
|
1690
|
+
});
|
|
1691
|
+
this.auth = new AuthModule(this.httpClient);
|
|
1692
|
+
this.tokens = new TokensModule(config.baseUrl, {
|
|
1693
|
+
issuer: config.verify?.issuer,
|
|
1694
|
+
audience: config.verify?.audience,
|
|
1695
|
+
clockTolerance: config.verify?.clockTolerance
|
|
1696
|
+
});
|
|
1697
|
+
this.sessions = new SessionsModule(this.httpClient);
|
|
1698
|
+
this.users = new UsersModule(this.httpClient);
|
|
1699
|
+
this.permissions = new PermissionsModule(() => this.getCurrentClaims());
|
|
1700
|
+
this.oidc = new OidcModule(this.httpClient, config.baseUrl, { tokens: this.tokens });
|
|
1701
|
+
this.tenants = new TenantsModule(this.httpClient);
|
|
1702
|
+
this.apps = new AppsModule(this.httpClient);
|
|
1703
|
+
this.roles = new RolesModule(this.httpClient);
|
|
1704
|
+
this.permissionGroups = new PermissionGroupsModule(this.httpClient);
|
|
1705
|
+
this.apiKeys = new ApiKeysModule(this.httpClient);
|
|
1706
|
+
this.invites = new InvitesModule(this.httpClient);
|
|
1707
|
+
this.webhooks = new WebhooksModule(this.httpClient);
|
|
1708
|
+
this.entitlements = new EntitlementsModule(this.httpClient);
|
|
1709
|
+
this.vendors = new VendorsModule(this.httpClient);
|
|
1710
|
+
this.sources = new SourcesModule(this.httpClient);
|
|
1711
|
+
this.clients = new ClientsModule(this.httpClient);
|
|
1712
|
+
this.hierarchy = new HierarchyModule(this.httpClient);
|
|
1713
|
+
this.memberships = new MembershipsModule(this.httpClient);
|
|
1714
|
+
this.scope = new ScopeModule(this.httpClient);
|
|
1715
|
+
this.gdpr = new GdprModule(this.httpClient);
|
|
1716
|
+
this.pin = new PinModule(this.httpClient);
|
|
1717
|
+
this.mfa = new MfaModule(this.httpClient);
|
|
1718
|
+
this.branding = new BrandingModule(this.httpClient);
|
|
1719
|
+
}
|
|
1720
|
+
static forBrowserSession(config) {
|
|
1721
|
+
return new _IQAuthClient({ ...config, environment: "browser_session" });
|
|
1722
|
+
}
|
|
1723
|
+
static forServer(config) {
|
|
1724
|
+
return new _IQAuthClient({ ...config, environment: "server" });
|
|
1725
|
+
}
|
|
1726
|
+
static forMobile(config) {
|
|
1727
|
+
return new _IQAuthClient({ ...config, environment: "mobile" });
|
|
1728
|
+
}
|
|
1729
|
+
static forService(config) {
|
|
1730
|
+
return new _IQAuthClient({ ...config, environment: "service" });
|
|
1731
|
+
}
|
|
1732
|
+
setTokens(tokens) {
|
|
1733
|
+
this._accessToken = tokens.accessToken;
|
|
1734
|
+
this._refreshToken = tokens.refreshToken;
|
|
1735
|
+
}
|
|
1736
|
+
getAccessToken() {
|
|
1737
|
+
return this._accessToken;
|
|
1738
|
+
}
|
|
1739
|
+
getRefreshToken() {
|
|
1740
|
+
return this._refreshToken;
|
|
1741
|
+
}
|
|
1742
|
+
getCurrentClaims() {
|
|
1743
|
+
if (!this._accessToken) return null;
|
|
1744
|
+
return this.tokens.decode(this._accessToken);
|
|
1745
|
+
}
|
|
1746
|
+
static resolveEnvironment(config) {
|
|
1747
|
+
if (config.environment) return config.environment;
|
|
1748
|
+
if (config.apiKey) return "service";
|
|
1749
|
+
return "server";
|
|
1750
|
+
}
|
|
1751
|
+
};
|
|
1752
|
+
|
|
1753
|
+
// src/publishableKey.ts
|
|
1754
|
+
function b64urlDecode(input) {
|
|
1755
|
+
const pad = input.length % 4 === 0 ? "" : "=".repeat(4 - input.length % 4);
|
|
1756
|
+
const normalized = input.replace(/-/g, "+").replace(/_/g, "/") + pad;
|
|
1757
|
+
if (typeof atob === "function") {
|
|
1758
|
+
const bin = atob(normalized);
|
|
1759
|
+
const bytes = new Uint8Array(bin.length);
|
|
1760
|
+
for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
|
|
1761
|
+
return new TextDecoder().decode(bytes);
|
|
1762
|
+
}
|
|
1763
|
+
const { Buffer: Buffer2 } = require("buffer");
|
|
1764
|
+
return Buffer2.from(normalized, "base64").toString("utf8");
|
|
1765
|
+
}
|
|
1766
|
+
function parsePublishableKey(raw) {
|
|
1767
|
+
if (typeof raw !== "string") return null;
|
|
1768
|
+
const m = raw.match(/^pk_(test|live)_([A-Za-z0-9_-]+)$/);
|
|
1769
|
+
if (!m) return null;
|
|
1770
|
+
try {
|
|
1771
|
+
const json = JSON.parse(b64urlDecode(m[2]));
|
|
1772
|
+
if (!json || typeof json !== "object") return null;
|
|
1773
|
+
if (typeof json.iss !== "string" || typeof json.appId !== "string" || typeof json.tenantId !== "string" || typeof json.kid !== "string") {
|
|
1774
|
+
return null;
|
|
1775
|
+
}
|
|
1776
|
+
return { mode: m[1], iss: json.iss, appId: json.appId, tenantId: json.tenantId, kid: json.kid, raw };
|
|
1777
|
+
} catch {
|
|
1778
|
+
return null;
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1782
|
+
// src/server/handlers.ts
|
|
1783
|
+
var ACCESS_TOKEN_TTL_SECONDS = 60 * 15;
|
|
1784
|
+
var REFRESH_TOKEN_TTL_SECONDS = 60 * 60 * 24 * 30;
|
|
1785
|
+
function resolve(config) {
|
|
1786
|
+
const parsed = parsePublishableKey(config.publishableKey);
|
|
1787
|
+
if (!parsed) {
|
|
1788
|
+
throw new Error(
|
|
1789
|
+
"@iqauth/sdk: invalid publishable key passed to iqAuth helpers (expected pk_test_\u2026 or pk_live_\u2026)"
|
|
1790
|
+
);
|
|
1791
|
+
}
|
|
1792
|
+
const inferredIssuer = parsed.iss.startsWith("http") ? parsed.iss : `https://${parsed.iss}`;
|
|
1793
|
+
return {
|
|
1794
|
+
publishableKey: config.publishableKey,
|
|
1795
|
+
secretKey: config.secretKey,
|
|
1796
|
+
issuer: (config.issuer ?? inferredIssuer).replace(/\/+$/, ""),
|
|
1797
|
+
accessCookieName: config.accessCookieName ?? "iqauth_at",
|
|
1798
|
+
refreshCookieName: config.refreshCookieName ?? "iqauth_rt",
|
|
1799
|
+
cookieDomain: config.cookieDomain,
|
|
1800
|
+
sameSite: config.sameSite ?? "lax",
|
|
1801
|
+
secure: config.secure ?? true,
|
|
1802
|
+
cookiePath: config.cookiePath ?? "/",
|
|
1803
|
+
tokenPath: config.tokenPath ?? "/oidc/token",
|
|
1804
|
+
refreshPath: config.refreshPath ?? "/api/v1/auth/refresh",
|
|
1805
|
+
logoutPath: config.logoutPath ?? "/api/v1/auth/logout",
|
|
1806
|
+
fetchImpl: config.fetchImpl ?? (typeof fetch !== "undefined" ? fetch.bind(globalThis) : (() => {
|
|
1807
|
+
throw new Error("global fetch is unavailable; pass fetchImpl");
|
|
1808
|
+
})),
|
|
1809
|
+
appId: parsed.appId,
|
|
1810
|
+
tenantId: parsed.tenantId
|
|
1811
|
+
};
|
|
1812
|
+
}
|
|
1813
|
+
function makeCookie(cfg, name, value, maxAge, httpOnly = true) {
|
|
1814
|
+
return {
|
|
1815
|
+
name,
|
|
1816
|
+
value,
|
|
1817
|
+
maxAge,
|
|
1818
|
+
httpOnly,
|
|
1819
|
+
secure: cfg.secure,
|
|
1820
|
+
sameSite: cfg.sameSite,
|
|
1821
|
+
path: cfg.cookiePath,
|
|
1822
|
+
domain: cfg.cookieDomain
|
|
1823
|
+
};
|
|
1824
|
+
}
|
|
1825
|
+
function clearCookies(cfg) {
|
|
1826
|
+
return [
|
|
1827
|
+
makeCookie(cfg, cfg.accessCookieName, "", 0),
|
|
1828
|
+
makeCookie(cfg, cfg.refreshCookieName, "", 0)
|
|
1829
|
+
];
|
|
1830
|
+
}
|
|
1831
|
+
function serializeCookie(d) {
|
|
1832
|
+
const parts = [`${d.name}=${encodeURIComponent(d.value)}`];
|
|
1833
|
+
parts.push(`Path=${d.path}`);
|
|
1834
|
+
if (d.domain) parts.push(`Domain=${d.domain}`);
|
|
1835
|
+
parts.push(`Max-Age=${d.maxAge}`);
|
|
1836
|
+
if (d.secure) parts.push("Secure");
|
|
1837
|
+
if (d.httpOnly) parts.push("HttpOnly");
|
|
1838
|
+
parts.push(`SameSite=${d.sameSite}`);
|
|
1839
|
+
return parts.join("; ");
|
|
1840
|
+
}
|
|
1841
|
+
async function handleCallback(config, input) {
|
|
1842
|
+
const cfg = resolve(config);
|
|
1843
|
+
if (!input.code || !input.redirectUri) {
|
|
1844
|
+
return {
|
|
1845
|
+
status: 400,
|
|
1846
|
+
body: { success: false, error: { code: "VALIDATION_ERROR", message: "code and redirectUri are required" } },
|
|
1847
|
+
cookies: []
|
|
1848
|
+
};
|
|
1849
|
+
}
|
|
1850
|
+
if (!cfg.secretKey) {
|
|
1851
|
+
return {
|
|
1852
|
+
status: 500,
|
|
1853
|
+
body: { success: false, error: { code: "INTERNAL_ERROR", message: "secretKey is required for the callback handler" } },
|
|
1854
|
+
cookies: []
|
|
1855
|
+
};
|
|
1856
|
+
}
|
|
1857
|
+
const body = new URLSearchParams({
|
|
1858
|
+
grant_type: "authorization_code",
|
|
1859
|
+
code: input.code,
|
|
1860
|
+
redirect_uri: input.redirectUri,
|
|
1861
|
+
client_id: cfg.appId
|
|
1862
|
+
});
|
|
1863
|
+
if (input.codeVerifier) body.set("code_verifier", input.codeVerifier);
|
|
1864
|
+
const res = await cfg.fetchImpl(`${cfg.issuer}${cfg.tokenPath}`, {
|
|
1865
|
+
method: "POST",
|
|
1866
|
+
headers: {
|
|
1867
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
1868
|
+
Authorization: `Basic ${typeof btoa === "function" ? btoa(`${cfg.appId}:${cfg.secretKey}`) : Buffer.from(`${cfg.appId}:${cfg.secretKey}`).toString("base64")}`
|
|
1869
|
+
},
|
|
1870
|
+
body: body.toString()
|
|
1871
|
+
});
|
|
1872
|
+
const json = await res.json().catch(() => ({}));
|
|
1873
|
+
if (!res.ok || !json.access_token) {
|
|
1874
|
+
return {
|
|
1875
|
+
status: res.status || 502,
|
|
1876
|
+
body: {
|
|
1877
|
+
success: false,
|
|
1878
|
+
error: {
|
|
1879
|
+
code: json.error || "OIDC_EXCHANGE_FAILED",
|
|
1880
|
+
message: json.error_description || "Authorization code exchange failed"
|
|
1881
|
+
}
|
|
1882
|
+
},
|
|
1883
|
+
cookies: []
|
|
1884
|
+
};
|
|
1885
|
+
}
|
|
1886
|
+
const cookies = [];
|
|
1887
|
+
cookies.push(
|
|
1888
|
+
makeCookie(cfg, cfg.accessCookieName, json.access_token, json.expires_in ?? ACCESS_TOKEN_TTL_SECONDS)
|
|
1889
|
+
);
|
|
1890
|
+
if (json.refresh_token) {
|
|
1891
|
+
cookies.push(makeCookie(cfg, cfg.refreshCookieName, json.refresh_token, REFRESH_TOKEN_TTL_SECONDS));
|
|
1892
|
+
}
|
|
1893
|
+
return {
|
|
1894
|
+
status: 200,
|
|
1895
|
+
body: { success: true, data: { authenticated: true } },
|
|
1896
|
+
cookies
|
|
1897
|
+
};
|
|
1898
|
+
}
|
|
1899
|
+
async function handleRefresh(config, input) {
|
|
1900
|
+
const cfg = resolve(config);
|
|
1901
|
+
const refreshToken = input.refreshToken;
|
|
1902
|
+
if (!refreshToken) {
|
|
1903
|
+
return {
|
|
1904
|
+
status: 401,
|
|
1905
|
+
body: { success: false, error: { code: "TOKEN_INVALID", message: "Missing refresh token" } },
|
|
1906
|
+
cookies: clearCookies(cfg)
|
|
1907
|
+
};
|
|
1908
|
+
}
|
|
1909
|
+
const res = await cfg.fetchImpl(`${cfg.issuer}${cfg.refreshPath}`, {
|
|
1910
|
+
method: "POST",
|
|
1911
|
+
headers: { "Content-Type": "application/json" },
|
|
1912
|
+
body: JSON.stringify({ refreshToken })
|
|
1913
|
+
});
|
|
1914
|
+
const json = await res.json().catch(() => ({}));
|
|
1915
|
+
if (!res.ok || !json.success || !json.data?.accessToken) {
|
|
1916
|
+
return {
|
|
1917
|
+
status: res.status || 401,
|
|
1918
|
+
body: {
|
|
1919
|
+
success: false,
|
|
1920
|
+
error: {
|
|
1921
|
+
code: json.error?.code || "TOKEN_INVALID",
|
|
1922
|
+
message: json.error?.message || "Refresh failed"
|
|
1923
|
+
}
|
|
1924
|
+
},
|
|
1925
|
+
cookies: clearCookies(cfg)
|
|
1926
|
+
};
|
|
1927
|
+
}
|
|
1928
|
+
const cookies = [
|
|
1929
|
+
makeCookie(cfg, cfg.accessCookieName, json.data.accessToken, ACCESS_TOKEN_TTL_SECONDS)
|
|
1930
|
+
];
|
|
1931
|
+
if (json.data.refreshToken) {
|
|
1932
|
+
cookies.push(makeCookie(cfg, cfg.refreshCookieName, json.data.refreshToken, REFRESH_TOKEN_TTL_SECONDS));
|
|
1933
|
+
}
|
|
1934
|
+
return {
|
|
1935
|
+
status: 200,
|
|
1936
|
+
body: { success: true, data: { accessToken: json.data.accessToken } },
|
|
1937
|
+
cookies
|
|
1938
|
+
};
|
|
1939
|
+
}
|
|
1940
|
+
async function handleSignout(config, input) {
|
|
1941
|
+
const cfg = resolve(config);
|
|
1942
|
+
if (input.accessToken) {
|
|
1943
|
+
try {
|
|
1944
|
+
await cfg.fetchImpl(`${cfg.issuer}${cfg.logoutPath}`, {
|
|
1945
|
+
method: "POST",
|
|
1946
|
+
headers: {
|
|
1947
|
+
"Content-Type": "application/json",
|
|
1948
|
+
Authorization: `Bearer ${input.accessToken}`
|
|
1949
|
+
}
|
|
1950
|
+
});
|
|
1951
|
+
} catch {
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
return {
|
|
1955
|
+
status: 200,
|
|
1956
|
+
body: { success: true, data: { signedOut: true } },
|
|
1957
|
+
cookies: clearCookies(cfg)
|
|
1958
|
+
};
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1961
|
+
// src/hono.ts
|
|
1962
|
+
var KNOWN_AUTH_ERRORS = /* @__PURE__ */ new Set([
|
|
1963
|
+
"TOKEN_INVALID",
|
|
1964
|
+
"TOKEN_EXPIRED",
|
|
1965
|
+
"TOKEN_REVOKED",
|
|
1966
|
+
"SESSION_EXPIRED",
|
|
1967
|
+
"SESSION_INVALID",
|
|
1968
|
+
"AUTH_REQUIRED"
|
|
1969
|
+
]);
|
|
1970
|
+
function readCookieFromHeader(header, name) {
|
|
1971
|
+
if (!header) return void 0;
|
|
1972
|
+
const target = `${name}=`;
|
|
1973
|
+
for (const seg of header.split(";")) {
|
|
1974
|
+
const t = seg.trim();
|
|
1975
|
+
if (t.startsWith(target)) {
|
|
1976
|
+
try {
|
|
1977
|
+
return decodeURIComponent(t.slice(target.length));
|
|
1978
|
+
} catch {
|
|
1979
|
+
return t.slice(target.length);
|
|
1980
|
+
}
|
|
1981
|
+
}
|
|
1982
|
+
}
|
|
1983
|
+
return void 0;
|
|
1984
|
+
}
|
|
1985
|
+
function honoResponse(hr) {
|
|
1986
|
+
const headers = new Headers({ "Content-Type": "application/json" });
|
|
1987
|
+
for (const c of hr.cookies) headers.append("set-cookie", serializeCookie(c));
|
|
1988
|
+
return new Response(JSON.stringify(hr.body), { status: hr.status, headers });
|
|
1989
|
+
}
|
|
1990
|
+
function iqAuth(options) {
|
|
1991
|
+
const parsed = parsePublishableKey(options.publishableKey);
|
|
1992
|
+
if (!parsed) throw new Error("@iqauth/sdk/hono: invalid publishable key");
|
|
1993
|
+
const issuer = (options.issuer ?? (parsed.iss.startsWith("http") ? parsed.iss : `https://${parsed.iss}`)).replace(/\/+$/, "");
|
|
1994
|
+
const helperConfig = { ...options, issuer };
|
|
1995
|
+
const client = new IQAuthClient({ baseUrl: issuer, environment: "server" });
|
|
1996
|
+
const accessCookie = options.accessCookieName ?? "iqauth_at";
|
|
1997
|
+
const refreshCookie = options.refreshCookieName ?? "iqauth_rt";
|
|
1998
|
+
const mount = (options.mountPath ?? "/api/iqauth").replace(/\/+$/, "");
|
|
1999
|
+
const mountHelpers = options.mountHelperRoutes !== false;
|
|
2000
|
+
const isPublic = (p) => {
|
|
2001
|
+
if (Array.isArray(options.publicPaths)) return options.publicPaths.includes(p);
|
|
2002
|
+
if (typeof options.publicPaths === "function") return options.publicPaths(p);
|
|
2003
|
+
return false;
|
|
2004
|
+
};
|
|
2005
|
+
return async (c, next) => {
|
|
2006
|
+
const url = new URL(c.req.url);
|
|
2007
|
+
const path = url.pathname;
|
|
2008
|
+
if (mountHelpers && path.startsWith(mount + "/") && c.req.method === "POST") {
|
|
2009
|
+
const body = await c.req.json().catch(() => ({}));
|
|
2010
|
+
const cookieHeader = c.req.header("cookie");
|
|
2011
|
+
if (path === `${mount}/callback`) {
|
|
2012
|
+
return honoResponse(await handleCallback(helperConfig, {
|
|
2013
|
+
code: body.code,
|
|
2014
|
+
codeVerifier: body.codeVerifier,
|
|
2015
|
+
redirectUri: body.redirectUri
|
|
2016
|
+
}));
|
|
2017
|
+
}
|
|
2018
|
+
if (path === `${mount}/refresh`) {
|
|
2019
|
+
const refreshToken = body.refreshToken || readCookieFromHeader(cookieHeader, refreshCookie);
|
|
2020
|
+
return honoResponse(await handleRefresh(helperConfig, { refreshToken }));
|
|
2021
|
+
}
|
|
2022
|
+
if (path === `${mount}/signout`) {
|
|
2023
|
+
const auth2 = c.req.header("authorization");
|
|
2024
|
+
const accessToken = auth2 && auth2.replace(/^Bearer /i, "") || readCookieFromHeader(cookieHeader, accessCookie);
|
|
2025
|
+
return honoResponse(await handleSignout(helperConfig, { accessToken }));
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
if (isPublic(path)) return next();
|
|
2029
|
+
const auth = c.req.header("authorization");
|
|
2030
|
+
let token;
|
|
2031
|
+
if (auth && auth.startsWith("Bearer ")) token = auth.slice(7);
|
|
2032
|
+
if (!token) token = readCookieFromHeader(c.req.header("cookie"), accessCookie);
|
|
2033
|
+
if (!token) {
|
|
2034
|
+
return c.json({ success: false, error: { code: "TOKEN_INVALID", message: "Missing access token" } }, 401);
|
|
2035
|
+
}
|
|
2036
|
+
try {
|
|
2037
|
+
const claims = await client.tokens.verify(token);
|
|
2038
|
+
c.set("auth", claims);
|
|
2039
|
+
} catch (err) {
|
|
2040
|
+
if (err instanceof IQAuthError && KNOWN_AUTH_ERRORS.has(err.code)) {
|
|
2041
|
+
return c.json({ success: false, error: { code: err.code, message: err.message } }, 401);
|
|
2042
|
+
}
|
|
2043
|
+
return c.json({ success: false, error: { code: "INTERNAL_ERROR", message: "Authentication failed" } }, 500);
|
|
2044
|
+
}
|
|
2045
|
+
return next();
|
|
2046
|
+
};
|
|
2047
|
+
}
|
|
2048
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
2049
|
+
0 && (module.exports = {
|
|
2050
|
+
iqAuth
|
|
2051
|
+
});
|