authmi 1.0.0 → 1.1.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/README.md +7 -7
- package/dist/chunk-EAK7C6YO.mjs +688 -0
- package/dist/client-DmZsG2xs.d.mts +406 -0
- package/dist/client-DmZsG2xs.d.ts +406 -0
- package/dist/index.d.mts +48 -23
- package/dist/index.d.ts +48 -23
- package/dist/index.js +581 -386
- package/dist/index.mjs +115 -3
- package/dist/next.d.mts +7 -37
- package/dist/next.d.ts +7 -37
- package/dist/next.js +476 -416
- package/dist/next.mjs +5 -24
- package/dist/react.d.mts +29 -30
- package/dist/react.d.ts +29 -30
- package/dist/react.js +549 -444
- package/dist/react.mjs +77 -52
- package/package.json +8 -6
- package/dist/chunk-UNKK6FP4.mjs +0 -609
- package/dist/client-D6JiIhlU.d.mts +0 -332
- package/dist/client-D6JiIhlU.d.ts +0 -332
package/dist/react.js
CHANGED
|
@@ -23,9 +23,7 @@ __export(react_exports, {
|
|
|
23
23
|
AuthMiClient: () => AuthMiClient,
|
|
24
24
|
AuthMiError: () => AuthMiError,
|
|
25
25
|
AuthMiProvider: () => AuthMiProvider,
|
|
26
|
-
LocalStorageTokenStorage: () => LocalStorageTokenStorage,
|
|
27
26
|
LoginForm: () => LoginForm,
|
|
28
|
-
MemoryTokenStorage: () => MemoryTokenStorage,
|
|
29
27
|
Protected: () => Protected,
|
|
30
28
|
ScopeError: () => ScopeError,
|
|
31
29
|
useAuthActions: () => useAuthActions,
|
|
@@ -37,6 +35,57 @@ __export(react_exports, {
|
|
|
37
35
|
module.exports = __toCommonJS(react_exports);
|
|
38
36
|
var import_react = require("react");
|
|
39
37
|
|
|
38
|
+
// src/config.ts
|
|
39
|
+
function resolveConfig(provider) {
|
|
40
|
+
const raw = typeof provider === "function" ? provider() : provider;
|
|
41
|
+
return {
|
|
42
|
+
baseUrl: raw.baseUrl,
|
|
43
|
+
apiKey: raw.apiKey ?? "",
|
|
44
|
+
serviceId: raw.serviceId ?? "",
|
|
45
|
+
clientId: raw.clientId ?? "",
|
|
46
|
+
clientSecret: raw.clientSecret ?? "",
|
|
47
|
+
defaultTokenExpiry: raw.defaultTokenExpiry ?? 24
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// src/storage/localStorage.ts
|
|
52
|
+
var STORAGE_KEY = "authmi_token";
|
|
53
|
+
var LocalStorageTokenStorage = class {
|
|
54
|
+
canAccess() {
|
|
55
|
+
if (typeof window === "undefined") return false;
|
|
56
|
+
try {
|
|
57
|
+
const probe = "__authmi_storage_test__";
|
|
58
|
+
localStorage.setItem(probe, "1");
|
|
59
|
+
localStorage.removeItem(probe);
|
|
60
|
+
return true;
|
|
61
|
+
} catch {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
getToken() {
|
|
66
|
+
if (!this.canAccess()) return null;
|
|
67
|
+
try {
|
|
68
|
+
return localStorage.getItem(STORAGE_KEY);
|
|
69
|
+
} catch {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
setToken(token) {
|
|
74
|
+
if (!this.canAccess()) return;
|
|
75
|
+
try {
|
|
76
|
+
localStorage.setItem(STORAGE_KEY, token);
|
|
77
|
+
} catch {
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
removeToken() {
|
|
81
|
+
if (!this.canAccess()) return;
|
|
82
|
+
try {
|
|
83
|
+
localStorage.removeItem(STORAGE_KEY);
|
|
84
|
+
} catch {
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
40
89
|
// src/types.ts
|
|
41
90
|
var AuthMiError = class extends Error {
|
|
42
91
|
constructor(message, code, statusCode) {
|
|
@@ -54,82 +103,38 @@ var ScopeError = class extends AuthMiError {
|
|
|
54
103
|
this.name = "ScopeError";
|
|
55
104
|
}
|
|
56
105
|
};
|
|
57
|
-
var _LocalStorageTokenStorage = class _LocalStorageTokenStorage {
|
|
58
|
-
getToken() {
|
|
59
|
-
if (typeof window === "undefined") return null;
|
|
60
|
-
return localStorage.getItem(_LocalStorageTokenStorage.KEY);
|
|
61
|
-
}
|
|
62
|
-
setToken(token) {
|
|
63
|
-
if (typeof window === "undefined") return;
|
|
64
|
-
localStorage.setItem(_LocalStorageTokenStorage.KEY, token);
|
|
65
|
-
}
|
|
66
|
-
removeToken() {
|
|
67
|
-
if (typeof window === "undefined") return;
|
|
68
|
-
localStorage.removeItem(_LocalStorageTokenStorage.KEY);
|
|
69
|
-
}
|
|
70
|
-
};
|
|
71
|
-
_LocalStorageTokenStorage.KEY = "authmi_token";
|
|
72
|
-
var LocalStorageTokenStorage = _LocalStorageTokenStorage;
|
|
73
|
-
var MemoryTokenStorage = class {
|
|
74
|
-
constructor() {
|
|
75
|
-
this.token = null;
|
|
76
|
-
}
|
|
77
|
-
getToken() {
|
|
78
|
-
return this.token;
|
|
79
|
-
}
|
|
80
|
-
setToken(token) {
|
|
81
|
-
this.token = token;
|
|
82
|
-
}
|
|
83
|
-
removeToken() {
|
|
84
|
-
this.token = null;
|
|
85
|
-
}
|
|
86
|
-
};
|
|
87
106
|
|
|
88
107
|
// src/client.ts
|
|
89
108
|
var AuthMiClient = class {
|
|
90
|
-
constructor(config,
|
|
91
|
-
this.
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
if (this.config.clientId && this.config.clientSecret) {
|
|
104
|
-
const encoded = btoa(`${this.config.clientId}:${this.config.clientSecret}`);
|
|
105
|
-
return `Basic ${encoded}`;
|
|
106
|
-
}
|
|
107
|
-
return null;
|
|
108
|
-
}
|
|
109
|
-
// ============ HTTP Utilities ============
|
|
110
|
-
async request(endpoint, options = {}, requireAuth = false) {
|
|
111
|
-
const url = `${this.config.baseUrl}${endpoint}`;
|
|
109
|
+
constructor(config, options) {
|
|
110
|
+
this.configProvider = config;
|
|
111
|
+
this.storage = options?.storage ?? new LocalStorageTokenStorage();
|
|
112
|
+
this.cache = options?.cache || null;
|
|
113
|
+
}
|
|
114
|
+
/** Resolve current config (supports lazy evaluation) */
|
|
115
|
+
get config() {
|
|
116
|
+
return resolveConfig(this.configProvider);
|
|
117
|
+
}
|
|
118
|
+
// ============ HTTP plumbing ============
|
|
119
|
+
async request(endpoint, options = {}, requireUserToken = false) {
|
|
120
|
+
const cfg = this.config;
|
|
121
|
+
const url = `${cfg.baseUrl}${endpoint}`;
|
|
112
122
|
const headers = {
|
|
113
123
|
"Content-Type": "application/json",
|
|
114
124
|
...options.headers || {}
|
|
115
125
|
};
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
if (token && !headers["Authorization"]) {
|
|
125
|
-
headers["Authorization"] = `Bearer ${token}`;
|
|
126
|
+
if (!headers["Authorization"]) {
|
|
127
|
+
if (requireUserToken) {
|
|
128
|
+
const token = await this.storage.getToken();
|
|
129
|
+
if (token) headers["Authorization"] = `Bearer ${token}`;
|
|
130
|
+
} else if (cfg.clientId && cfg.clientSecret) {
|
|
131
|
+
headers["Authorization"] = `Basic ${btoa(`${cfg.clientId}:${cfg.clientSecret}`)}`;
|
|
132
|
+
} else if (cfg.apiKey) {
|
|
133
|
+
headers["Authorization"] = `Bearer ${cfg.apiKey}`;
|
|
126
134
|
}
|
|
127
135
|
}
|
|
128
136
|
try {
|
|
129
|
-
const response = await fetch(url, {
|
|
130
|
-
...options,
|
|
131
|
-
headers
|
|
132
|
-
});
|
|
137
|
+
const response = await fetch(url, { ...options, headers });
|
|
133
138
|
const data = await response.json();
|
|
134
139
|
if (!response.ok) {
|
|
135
140
|
if (response.status === 403 && data.required && data.provided) {
|
|
@@ -147,9 +152,7 @@ var AuthMiClient = class {
|
|
|
147
152
|
}
|
|
148
153
|
return data;
|
|
149
154
|
} catch (error) {
|
|
150
|
-
if (error instanceof AuthMiError)
|
|
151
|
-
throw error;
|
|
152
|
-
}
|
|
155
|
+
if (error instanceof AuthMiError) throw error;
|
|
153
156
|
throw new AuthMiError(
|
|
154
157
|
error instanceof Error ? error.message : "Network error",
|
|
155
158
|
"NETWORK_ERROR"
|
|
@@ -157,384 +160,497 @@ var AuthMiClient = class {
|
|
|
157
160
|
}
|
|
158
161
|
}
|
|
159
162
|
// ============ Configuration ============
|
|
160
|
-
/**
|
|
161
|
-
* Update the client's service configuration
|
|
162
|
-
*/
|
|
163
163
|
configure(config) {
|
|
164
|
-
|
|
164
|
+
const current = resolveConfig(this.configProvider);
|
|
165
|
+
this.configProvider = { ...current, ...config };
|
|
165
166
|
}
|
|
166
|
-
/**
|
|
167
|
-
* Get current configuration (without sensitive data)
|
|
168
|
-
*/
|
|
169
167
|
getConfig() {
|
|
170
|
-
const { apiKey: _, clientSecret: __, ...rest } = this.
|
|
168
|
+
const { apiKey: _, clientSecret: __, ...rest } = resolveConfig(this.configProvider);
|
|
171
169
|
return rest;
|
|
172
170
|
}
|
|
173
171
|
// ============ Token Management ============
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
getAccessToken() {
|
|
178
|
-
return this.tokenStorage.getToken();
|
|
179
|
-
}
|
|
180
|
-
/**
|
|
181
|
-
* Store an access token
|
|
182
|
-
*/
|
|
172
|
+
async getAccessToken() {
|
|
173
|
+
return this.storage.getToken();
|
|
174
|
+
}
|
|
183
175
|
setAccessToken(token) {
|
|
184
|
-
this.
|
|
176
|
+
this.storage.setToken(token);
|
|
185
177
|
}
|
|
186
|
-
/**
|
|
187
|
-
* Remove the stored access token (logout)
|
|
188
|
-
*/
|
|
189
178
|
clearAccessToken() {
|
|
190
|
-
this.
|
|
191
|
-
}
|
|
192
|
-
/**
|
|
193
|
-
* Check if user is logged in
|
|
194
|
-
*/
|
|
195
|
-
isLoggedIn() {
|
|
196
|
-
return !!this.getAccessToken();
|
|
197
|
-
}
|
|
198
|
-
// ============ Scope Validation ============
|
|
199
|
-
/**
|
|
200
|
-
* Introspect a token or API key and validate required scopes
|
|
201
|
-
*/
|
|
202
|
-
async introspect(request) {
|
|
203
|
-
return this.request("/api/v1/introspect", {
|
|
204
|
-
method: "POST",
|
|
205
|
-
body: JSON.stringify(request)
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
|
-
/**
|
|
209
|
-
* Validate the current token with optional scope checking
|
|
210
|
-
*/
|
|
211
|
-
async validateWithScopes(requiredScopes) {
|
|
212
|
-
const token = this.getAccessToken();
|
|
213
|
-
if (!token) {
|
|
214
|
-
return { valid: false, error: "No token provided" };
|
|
215
|
-
}
|
|
216
|
-
if (!this.config.apiKey && !this.config.clientSecret) {
|
|
217
|
-
return { valid: false, error: "Service credentials not configured" };
|
|
218
|
-
}
|
|
219
|
-
if (!this.config.serviceId) {
|
|
220
|
-
return { valid: false, error: "Service ID not configured in AuthMiConfig" };
|
|
221
|
-
}
|
|
222
|
-
try {
|
|
223
|
-
const result = await this.introspect({
|
|
224
|
-
serviceId: this.config.serviceId,
|
|
225
|
-
token,
|
|
226
|
-
requiredScopes
|
|
227
|
-
});
|
|
228
|
-
return {
|
|
229
|
-
valid: result.valid,
|
|
230
|
-
type: result.type === "bearer" ? "token" : "api_key",
|
|
231
|
-
scopes: result.scopes,
|
|
232
|
-
userId: result.userId,
|
|
233
|
-
email: result.email,
|
|
234
|
-
error: result.error
|
|
235
|
-
};
|
|
236
|
-
} catch (error) {
|
|
237
|
-
if (error instanceof ScopeError) {
|
|
238
|
-
return {
|
|
239
|
-
valid: false,
|
|
240
|
-
error: error.message,
|
|
241
|
-
scopes: error.providedScopes
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
if (error instanceof AuthMiError && error.statusCode === 401) {
|
|
245
|
-
return { valid: false, error: "Invalid or expired token" };
|
|
246
|
-
}
|
|
247
|
-
throw error;
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
/**
|
|
251
|
-
* Check if the current user has all the required scopes
|
|
252
|
-
* Throws ScopeError if scopes are insufficient
|
|
253
|
-
*/
|
|
254
|
-
async requireScopes(...requiredScopes) {
|
|
255
|
-
const result = await this.validateWithScopes(requiredScopes);
|
|
256
|
-
if (!result.valid) {
|
|
257
|
-
throw new AuthMiError(
|
|
258
|
-
result.error || "Authentication required",
|
|
259
|
-
"UNAUTHORIZED",
|
|
260
|
-
401
|
|
261
|
-
);
|
|
262
|
-
}
|
|
263
|
-
const hasScopes = requiredScopes.every(
|
|
264
|
-
(scope) => result.scopes?.includes(scope)
|
|
265
|
-
);
|
|
266
|
-
if (!hasScopes) {
|
|
267
|
-
throw new ScopeError(
|
|
268
|
-
`Required scopes: ${requiredScopes.join(", ")}`,
|
|
269
|
-
requiredScopes,
|
|
270
|
-
result.scopes || []
|
|
271
|
-
);
|
|
272
|
-
}
|
|
273
|
-
return result;
|
|
274
|
-
}
|
|
275
|
-
/**
|
|
276
|
-
* Execute a function only if the user has the required scopes
|
|
277
|
-
*/
|
|
278
|
-
async withScope(scopes, fn) {
|
|
279
|
-
await this.requireScopes(...scopes);
|
|
280
|
-
return fn();
|
|
281
|
-
}
|
|
282
|
-
// ============ OAuth ============
|
|
283
|
-
/**
|
|
284
|
-
* Initiate OAuth login flow and return the authorization URL
|
|
285
|
-
*/
|
|
286
|
-
async initiateOAuth(provider, redirectUri) {
|
|
287
|
-
if (!this.config.apiKey && !this.config.clientSecret) {
|
|
288
|
-
throw new AuthMiError(
|
|
289
|
-
"API key or client credentials required for OAuth. Configure the client first.",
|
|
290
|
-
"MISSING_CREDENTIALS"
|
|
291
|
-
);
|
|
292
|
-
}
|
|
293
|
-
if (!this.config.serviceId) {
|
|
294
|
-
throw new AuthMiError(
|
|
295
|
-
"serviceId required for OAuth. Configure the client first.",
|
|
296
|
-
"MISSING_SERVICE_ID"
|
|
297
|
-
);
|
|
298
|
-
}
|
|
299
|
-
const response = await this.request(
|
|
300
|
-
"/api/v1/auth/oauth/initiate",
|
|
301
|
-
{
|
|
302
|
-
method: "POST",
|
|
303
|
-
body: JSON.stringify({ provider, redirectUri, client_id: this.config.serviceId })
|
|
304
|
-
}
|
|
305
|
-
);
|
|
306
|
-
return response.url;
|
|
179
|
+
this.storage.removeToken();
|
|
307
180
|
}
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
*/
|
|
312
|
-
handleOAuthCallback(url) {
|
|
313
|
-
const parsedUrl = typeof url === "string" ? new URL(url) : url;
|
|
314
|
-
const token = parsedUrl.searchParams.get("token");
|
|
315
|
-
const userId = parsedUrl.searchParams.get("user_id");
|
|
316
|
-
const email = parsedUrl.searchParams.get("email");
|
|
317
|
-
if (!token || !userId || !email) {
|
|
318
|
-
return null;
|
|
319
|
-
}
|
|
320
|
-
this.setAccessToken(token);
|
|
321
|
-
return { token, userId, email };
|
|
181
|
+
async isLoggedIn() {
|
|
182
|
+
const token = await this.storage.getToken();
|
|
183
|
+
return !!token;
|
|
322
184
|
}
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
185
|
+
// ============ Auth Endpoints ============
|
|
186
|
+
async signup(credentials) {
|
|
187
|
+
const response = await this.request("/api/v1/auth/signup", {
|
|
188
|
+
method: "POST",
|
|
189
|
+
body: JSON.stringify(credentials)
|
|
190
|
+
});
|
|
191
|
+
return this.mapAuthToken(response);
|
|
329
192
|
}
|
|
330
|
-
// ============ Authentication ============
|
|
331
|
-
/**
|
|
332
|
-
* Login a user and store the access token
|
|
333
|
-
*/
|
|
334
193
|
async login(credentials) {
|
|
335
|
-
|
|
336
|
-
throw new AuthMiError(
|
|
337
|
-
"API key or client credentials required for login. Configure the client first.",
|
|
338
|
-
"MISSING_CREDENTIALS"
|
|
339
|
-
);
|
|
340
|
-
}
|
|
194
|
+
const cfg = this.config;
|
|
341
195
|
const response = await this.request("/api/v1/auth/login", {
|
|
342
196
|
method: "POST",
|
|
343
197
|
body: JSON.stringify({
|
|
344
198
|
email: credentials.email,
|
|
345
199
|
password: credentials.password,
|
|
346
|
-
client_id:
|
|
347
|
-
expires_in_hours: credentials.expiresInHours ||
|
|
200
|
+
client_id: cfg.serviceId,
|
|
201
|
+
expires_in_hours: credentials.expiresInHours || cfg.defaultTokenExpiry,
|
|
348
202
|
scopes: credentials.scopes
|
|
349
203
|
})
|
|
350
204
|
});
|
|
351
|
-
const token =
|
|
352
|
-
accessToken: response.access_token,
|
|
353
|
-
tokenType: response.token_type,
|
|
354
|
-
expiresIn: response.expires_in,
|
|
355
|
-
expiresAt: response.expires_at,
|
|
356
|
-
userId: response.user_id,
|
|
357
|
-
email: response.email,
|
|
358
|
-
scopes: response.scopes
|
|
359
|
-
};
|
|
205
|
+
const token = this.mapAuthToken(response);
|
|
360
206
|
this.setAccessToken(token.accessToken);
|
|
361
207
|
return token;
|
|
362
208
|
}
|
|
363
|
-
/**
|
|
364
|
-
* Logout the current user
|
|
365
|
-
*/
|
|
366
209
|
async logout() {
|
|
367
|
-
const token = this.
|
|
210
|
+
const token = await this.storage.getToken();
|
|
368
211
|
if (token) {
|
|
369
212
|
try {
|
|
370
|
-
await this.request("/api/v1/auth/logout", {
|
|
371
|
-
|
|
372
|
-
}, true);
|
|
373
|
-
} catch (error) {
|
|
213
|
+
await this.request("/api/v1/auth/logout", { method: "POST" }, true);
|
|
214
|
+
} catch {
|
|
374
215
|
}
|
|
375
216
|
}
|
|
376
217
|
this.clearAccessToken();
|
|
377
218
|
}
|
|
378
|
-
/**
|
|
379
|
-
* Validate the current token or an API key
|
|
380
|
-
* @deprecated Use validateWithScopes instead
|
|
381
|
-
*/
|
|
382
|
-
async validate(_credential) {
|
|
383
|
-
return this.validateWithScopes();
|
|
384
|
-
}
|
|
385
|
-
/**
|
|
386
|
-
* Refresh the current token
|
|
387
|
-
*/
|
|
388
219
|
async refreshToken() {
|
|
389
|
-
const response = await this.request("/api/v1/auth/refresh", {
|
|
390
|
-
method: "POST"
|
|
391
|
-
}, true);
|
|
220
|
+
const response = await this.request("/api/v1/auth/refresh", { method: "POST" }, true);
|
|
392
221
|
const token = {
|
|
393
222
|
accessToken: response.access_token,
|
|
394
|
-
tokenType:
|
|
223
|
+
tokenType: "Bearer",
|
|
395
224
|
expiresIn: response.expires_in,
|
|
396
225
|
expiresAt: response.expires_at,
|
|
397
226
|
userId: "",
|
|
398
|
-
// Refresh doesn't return user info
|
|
399
227
|
email: ""
|
|
400
228
|
};
|
|
401
229
|
this.setAccessToken(token.accessToken);
|
|
402
230
|
return token;
|
|
403
231
|
}
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
async
|
|
409
|
-
|
|
232
|
+
async getMe() {
|
|
233
|
+
const response = await this.request("/api/v1/auth/me");
|
|
234
|
+
return { userId: response.user_id, email: response.email, name: response.name, createdAt: response.created_at };
|
|
235
|
+
}
|
|
236
|
+
async forgotPassword(request) {
|
|
237
|
+
return this.request("/api/v1/auth/forgot-password", {
|
|
410
238
|
method: "POST",
|
|
411
|
-
body: JSON.stringify(request)
|
|
239
|
+
body: JSON.stringify({ email: request.email, client_id: this.config.serviceId })
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
async resetPassword(request) {
|
|
243
|
+
return this.request("/api/v1/auth/reset-password", {
|
|
244
|
+
method: "POST",
|
|
245
|
+
body: JSON.stringify({ token: request.token, new_password: request.newPassword })
|
|
412
246
|
});
|
|
247
|
+
}
|
|
248
|
+
async verifyEmail(request) {
|
|
249
|
+
return this.request("/api/v1/auth/verify-email", {
|
|
250
|
+
method: "POST",
|
|
251
|
+
body: JSON.stringify({ token: request.token })
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
async resendVerification(request) {
|
|
255
|
+
return this.request("/api/v1/auth/resend-verification", {
|
|
256
|
+
method: "POST",
|
|
257
|
+
body: JSON.stringify({ email: request.email, client_id: this.config.serviceId })
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
async deactivateAccount(password) {
|
|
261
|
+
await this.request("/api/v1/auth/deactivate", {
|
|
262
|
+
method: "POST",
|
|
263
|
+
body: JSON.stringify({ password })
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
mapAuthToken(raw) {
|
|
413
267
|
return {
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
268
|
+
accessToken: raw.access_token,
|
|
269
|
+
tokenType: raw.token_type,
|
|
270
|
+
expiresIn: raw.expires_in,
|
|
271
|
+
expiresAt: raw.expires_at,
|
|
272
|
+
userId: raw.user_id,
|
|
273
|
+
email: raw.email,
|
|
274
|
+
emailVerified: raw.email_verified,
|
|
275
|
+
scopes: raw.scopes
|
|
417
276
|
};
|
|
418
277
|
}
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
278
|
+
// ============ OAuth ============
|
|
279
|
+
async initiateOAuth(provider, redirectUri) {
|
|
280
|
+
const cfg = this.config;
|
|
281
|
+
if (!cfg.clientId && !cfg.clientSecret && !cfg.apiKey) {
|
|
282
|
+
throw new AuthMiError("Credentials required for OAuth", "MISSING_CREDENTIALS");
|
|
283
|
+
}
|
|
284
|
+
const response = await this.request("/api/v1/auth/oauth/initiate", {
|
|
285
|
+
method: "POST",
|
|
286
|
+
body: JSON.stringify({ provider, redirectUri, client_id: cfg.serviceId || cfg.clientId })
|
|
287
|
+
});
|
|
288
|
+
return response.url;
|
|
289
|
+
}
|
|
290
|
+
handleOAuthCallback(url) {
|
|
291
|
+
const parsed = typeof url === "string" ? new URL(url) : url;
|
|
292
|
+
const token = parsed.searchParams.get("token");
|
|
293
|
+
const userId = parsed.searchParams.get("user_id");
|
|
294
|
+
const email = parsed.searchParams.get("email");
|
|
295
|
+
if (!token || !userId || !email) return null;
|
|
296
|
+
this.setAccessToken(token);
|
|
297
|
+
return { token, userId, email };
|
|
298
|
+
}
|
|
299
|
+
isOAuthCallback(url) {
|
|
300
|
+
const parsed = typeof url === "string" ? new URL(url) : url;
|
|
301
|
+
return !!(parsed.searchParams.has("token") && parsed.searchParams.has("user_id") && parsed.searchParams.has("email"));
|
|
302
|
+
}
|
|
303
|
+
// ============ SAML ============
|
|
304
|
+
async initiateSaml(request) {
|
|
305
|
+
return this.request("/api/v1/auth/saml/initiate", {
|
|
306
|
+
method: "POST",
|
|
307
|
+
body: JSON.stringify({ email: request.email, redirect_uri: request.redirectUri })
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
async parseSamlMetadata(xml) {
|
|
311
|
+
return this.request("/api/v1/auth/saml/parse-metadata", {
|
|
312
|
+
method: "POST",
|
|
313
|
+
body: JSON.stringify({ metadata_xml: xml })
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
async listSamlConnections() {
|
|
317
|
+
const response = await this.request("/api/v1/auth/saml/connections");
|
|
318
|
+
return response.connections;
|
|
319
|
+
}
|
|
320
|
+
async createSamlConnection(request) {
|
|
321
|
+
return this.request("/api/v1/auth/saml/connections", {
|
|
322
|
+
method: "POST",
|
|
323
|
+
body: JSON.stringify(request)
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
async deleteSamlConnection(id) {
|
|
327
|
+
await this.request(`/api/v1/auth/saml/connections/${encodeURIComponent(id)}`, {
|
|
328
|
+
method: "DELETE"
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
// ============ 2FA ============
|
|
332
|
+
async setup2fa() {
|
|
333
|
+
return this.request("/api/v1/auth/2fa/setup", { method: "POST" });
|
|
334
|
+
}
|
|
335
|
+
async enable2fa(code) {
|
|
336
|
+
return this.request("/api/v1/auth/2fa/enable", {
|
|
337
|
+
method: "POST",
|
|
338
|
+
body: JSON.stringify({ code })
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
async disable2fa(password) {
|
|
342
|
+
await this.request("/api/v1/auth/2fa/disable", {
|
|
343
|
+
method: "POST",
|
|
344
|
+
body: JSON.stringify({ password })
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
async regenerateBackupCodes() {
|
|
348
|
+
return this.request("/api/v1/auth/2fa/backup-codes", { method: "POST" });
|
|
349
|
+
}
|
|
350
|
+
async challenge2fa(request) {
|
|
351
|
+
return this.request("/api/v1/auth/2fa/challenge", {
|
|
352
|
+
method: "POST",
|
|
353
|
+
body: JSON.stringify(request)
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
// ============ Users ============
|
|
357
|
+
async createUser(request) {
|
|
358
|
+
const response = await this.request(
|
|
359
|
+
"/api/v1/users/create",
|
|
360
|
+
{
|
|
361
|
+
method: "POST",
|
|
362
|
+
body: JSON.stringify(request)
|
|
363
|
+
}
|
|
364
|
+
);
|
|
365
|
+
return { userId: response.user_id, email: response.email, name: response.name, createdAt: response.created_at };
|
|
366
|
+
}
|
|
422
367
|
async listUsers() {
|
|
423
368
|
const response = await this.request("/api/v1/users/list");
|
|
424
369
|
return response.users.map((u) => ({
|
|
425
370
|
userId: u.user_id,
|
|
426
371
|
email: u.email,
|
|
372
|
+
name: u.name,
|
|
427
373
|
createdAt: u.created_at
|
|
428
374
|
}));
|
|
429
375
|
}
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
376
|
+
async getUser(userId) {
|
|
377
|
+
const response = await this.request(
|
|
378
|
+
`/api/v1/users/${encodeURIComponent(userId)}`
|
|
379
|
+
);
|
|
380
|
+
return { userId: response.user_id, email: response.email, name: response.name, createdAt: response.created_at };
|
|
381
|
+
}
|
|
433
382
|
async deleteUser(userId) {
|
|
434
383
|
await this.request("/api/v1/users/delete", {
|
|
435
384
|
method: "POST",
|
|
436
385
|
body: JSON.stringify({ user_id: userId })
|
|
437
386
|
});
|
|
438
387
|
}
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
388
|
+
async deleteUserById(userId) {
|
|
389
|
+
await this.request(`/api/v1/users/${encodeURIComponent(userId)}`, { method: "DELETE" });
|
|
390
|
+
}
|
|
442
391
|
async updatePassword(userId, oldPassword, newPassword) {
|
|
443
392
|
await this.request("/api/v1/users/password", {
|
|
444
393
|
method: "POST",
|
|
445
|
-
body: JSON.stringify({
|
|
446
|
-
user_id: userId,
|
|
447
|
-
old_password: oldPassword,
|
|
448
|
-
new_password: newPassword
|
|
449
|
-
})
|
|
394
|
+
body: JSON.stringify({ user_id: userId, old_password: oldPassword, new_password: newPassword })
|
|
450
395
|
});
|
|
451
396
|
}
|
|
452
|
-
// ============
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
return response.
|
|
397
|
+
// ============ Roles ============
|
|
398
|
+
async createRole(request) {
|
|
399
|
+
const response = await this.request(
|
|
400
|
+
"/api/v1/users/roles/create",
|
|
401
|
+
{ method: "POST", body: JSON.stringify(request) }
|
|
402
|
+
);
|
|
403
|
+
return { roleId: response.role_id, name: response.name, description: response.description, createdAt: response.created_at };
|
|
404
|
+
}
|
|
405
|
+
async listRoles() {
|
|
406
|
+
const response = await this.request("/api/v1/users/roles/list");
|
|
407
|
+
return response.roles.map((r) => ({
|
|
408
|
+
roleId: r.role_id,
|
|
409
|
+
name: r.name,
|
|
410
|
+
description: r.description,
|
|
411
|
+
createdAt: r.created_at
|
|
412
|
+
}));
|
|
459
413
|
}
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
async
|
|
464
|
-
|
|
414
|
+
async deleteRole(roleId) {
|
|
415
|
+
await this.request(`/api/v1/users/roles/${encodeURIComponent(roleId)}`, { method: "DELETE" });
|
|
416
|
+
}
|
|
417
|
+
async assignRole(userId, roleId) {
|
|
418
|
+
await this.request("/api/v1/users/roles/assign", {
|
|
465
419
|
method: "POST",
|
|
466
|
-
body: JSON.stringify(
|
|
420
|
+
body: JSON.stringify({ user_id: userId, role_id: roleId })
|
|
467
421
|
});
|
|
468
|
-
return {
|
|
469
|
-
scopeId: response.scope_id,
|
|
470
|
-
name: response.name,
|
|
471
|
-
key: response.key,
|
|
472
|
-
description: response.description,
|
|
473
|
-
createdAt: response.created_at
|
|
474
|
-
};
|
|
475
422
|
}
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
*/
|
|
479
|
-
async deleteScope(scopeId) {
|
|
480
|
-
await this.request("/api/v1/scopes/delete", {
|
|
423
|
+
async unassignRole(userId, roleId) {
|
|
424
|
+
await this.request("/api/v1/users/roles/unassign", {
|
|
481
425
|
method: "POST",
|
|
482
|
-
body: JSON.stringify({
|
|
426
|
+
body: JSON.stringify({ user_id: userId, role_id: roleId })
|
|
483
427
|
});
|
|
484
428
|
}
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
429
|
+
async getUserRoles(userId) {
|
|
430
|
+
const response = await this.request(
|
|
431
|
+
`/api/v1/users/${encodeURIComponent(userId)}/roles`
|
|
432
|
+
);
|
|
433
|
+
return response.roles.map((r) => ({
|
|
434
|
+
roleId: r.role_id,
|
|
435
|
+
name: r.name,
|
|
436
|
+
description: r.description,
|
|
437
|
+
createdAt: r.created_at
|
|
438
|
+
}));
|
|
439
|
+
}
|
|
440
|
+
// ============ API Keys ============
|
|
489
441
|
async listApiKeys() {
|
|
490
442
|
const response = await this.request("/api/v1/keys/list");
|
|
491
443
|
return response.keys.map((k) => ({
|
|
492
444
|
keyId: k.keyId,
|
|
493
445
|
name: k.name,
|
|
494
446
|
scope: k.scope,
|
|
495
|
-
scopes:
|
|
447
|
+
scopes: k.scopes,
|
|
496
448
|
keyType: k.keyType,
|
|
449
|
+
tpsLimit: k.tpsLimit,
|
|
497
450
|
revoked: k.revoked,
|
|
498
|
-
createdAt: k.createdAt
|
|
499
|
-
tpsLimit: k.tpsLimit
|
|
451
|
+
createdAt: k.createdAt
|
|
500
452
|
}));
|
|
501
453
|
}
|
|
502
|
-
/**
|
|
503
|
-
* Create a new API key with optional custom scopes
|
|
504
|
-
*/
|
|
505
454
|
async createApiKey(request) {
|
|
506
|
-
const response = await this.request(
|
|
455
|
+
const response = await this.request(
|
|
456
|
+
"/api/v1/keys/create",
|
|
457
|
+
{
|
|
458
|
+
method: "POST",
|
|
459
|
+
body: JSON.stringify({
|
|
460
|
+
user_id: request.userId,
|
|
461
|
+
name: request.name,
|
|
462
|
+
scope: request.scope || "service",
|
|
463
|
+
scopes: request.scopes,
|
|
464
|
+
key_type: request.keyType || "service",
|
|
465
|
+
tps_limit: request.tpsLimit || 100
|
|
466
|
+
})
|
|
467
|
+
}
|
|
468
|
+
);
|
|
469
|
+
return { keyId: response.key_id, apiKey: response.api_key, name: response.name, scope: response.scope, scopes: response.scopes };
|
|
470
|
+
}
|
|
471
|
+
async revokeApiKey(keyId) {
|
|
472
|
+
await this.request("/api/v1/keys/revoke", {
|
|
473
|
+
method: "POST",
|
|
474
|
+
body: JSON.stringify({ key_id: keyId })
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
// ============ Scopes ============
|
|
478
|
+
async listScopes() {
|
|
479
|
+
const response = await this.request("/api/v1/scopes/list");
|
|
480
|
+
return response.scopes;
|
|
481
|
+
}
|
|
482
|
+
async createScope(request) {
|
|
483
|
+
const response = await this.request(
|
|
484
|
+
"/api/v1/scopes/create",
|
|
485
|
+
{ method: "POST", body: JSON.stringify(request) }
|
|
486
|
+
);
|
|
487
|
+
return { scopeId: response.scope_id, name: response.name, key: response.key, description: response.description, createdAt: response.created_at };
|
|
488
|
+
}
|
|
489
|
+
async deleteScope(scopeId) {
|
|
490
|
+
await this.request("/api/v1/scopes/delete", {
|
|
491
|
+
method: "POST",
|
|
492
|
+
body: JSON.stringify({ scope_id: scopeId })
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
// ============ Introspection & Validation ============
|
|
496
|
+
async introspect(request) {
|
|
497
|
+
if (this.cache && request.token) {
|
|
498
|
+
const cached = this.cache.get(request.token);
|
|
499
|
+
if (cached) return cached;
|
|
500
|
+
}
|
|
501
|
+
const result = await this.request("/api/v1/introspect", {
|
|
507
502
|
method: "POST",
|
|
508
503
|
body: JSON.stringify({
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
key_type: request.keyType || "service",
|
|
514
|
-
tps_limit: request.tpsLimit || 100
|
|
504
|
+
serviceId: request.serviceId,
|
|
505
|
+
token: request.token,
|
|
506
|
+
apiKey: request.apiKey,
|
|
507
|
+
requiredScopes: request.requiredScopes
|
|
515
508
|
})
|
|
516
509
|
});
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
scope: response.scope,
|
|
522
|
-
scopes: response.scopes
|
|
523
|
-
};
|
|
510
|
+
if (this.cache && request.token) {
|
|
511
|
+
this.cache.set(request.token, result);
|
|
512
|
+
}
|
|
513
|
+
return result;
|
|
524
514
|
}
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
515
|
+
async validate() {
|
|
516
|
+
const cfg = this.config;
|
|
517
|
+
const token = await this.storage.getToken();
|
|
518
|
+
if (!token) return { valid: false, error: "No token stored" };
|
|
519
|
+
if (!cfg.serviceId) return { valid: false, error: "Service ID not configured" };
|
|
520
|
+
if (!cfg.apiKey && !cfg.clientSecret) return { valid: false, error: "Service credentials not configured" };
|
|
521
|
+
try {
|
|
522
|
+
const result = await this.introspect({ serviceId: cfg.serviceId, token });
|
|
523
|
+
return {
|
|
524
|
+
valid: result.valid,
|
|
525
|
+
type: result.type === "bearer" ? "token" : "api_key",
|
|
526
|
+
scopes: result.scopes,
|
|
527
|
+
userId: result.userId,
|
|
528
|
+
email: result.email,
|
|
529
|
+
error: result.error
|
|
530
|
+
};
|
|
531
|
+
} catch (error) {
|
|
532
|
+
if (error instanceof ScopeError) {
|
|
533
|
+
return { valid: false, error: error.message, scopes: error.providedScopes };
|
|
534
|
+
}
|
|
535
|
+
if (error instanceof AuthMiError && error.statusCode === 401) {
|
|
536
|
+
return { valid: false, error: "Invalid or expired token" };
|
|
537
|
+
}
|
|
538
|
+
throw error;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
async validateWithScopes(requiredScopes) {
|
|
542
|
+
const cfg = this.config;
|
|
543
|
+
const token = await this.storage.getToken();
|
|
544
|
+
if (!token) return { valid: false, error: "No token provided" };
|
|
545
|
+
if (!cfg.apiKey && !cfg.clientSecret) return { valid: false, error: "Service credentials not configured" };
|
|
546
|
+
if (!cfg.serviceId) return { valid: false, error: "Service ID not configured" };
|
|
547
|
+
try {
|
|
548
|
+
const result = await this.introspect({ serviceId: cfg.serviceId, token, requiredScopes });
|
|
549
|
+
return {
|
|
550
|
+
valid: result.valid,
|
|
551
|
+
type: result.type === "bearer" ? "token" : "api_key",
|
|
552
|
+
scopes: result.scopes,
|
|
553
|
+
userId: result.userId,
|
|
554
|
+
email: result.email,
|
|
555
|
+
error: result.error
|
|
556
|
+
};
|
|
557
|
+
} catch (error) {
|
|
558
|
+
if (error instanceof ScopeError) {
|
|
559
|
+
return { valid: false, error: error.message, scopes: error.providedScopes };
|
|
560
|
+
}
|
|
561
|
+
if (error instanceof AuthMiError && error.statusCode === 401) {
|
|
562
|
+
return { valid: false, error: "Invalid or expired token" };
|
|
563
|
+
}
|
|
564
|
+
throw error;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
async requireScopes(...requiredScopes) {
|
|
568
|
+
const result = await this.validateWithScopes(requiredScopes);
|
|
569
|
+
if (!result.valid) {
|
|
570
|
+
throw new AuthMiError(result.error || "Authentication required", "UNAUTHORIZED", 401);
|
|
571
|
+
}
|
|
572
|
+
const hasScopes = requiredScopes.every((s) => result.scopes?.includes(s));
|
|
573
|
+
if (!hasScopes) {
|
|
574
|
+
throw new ScopeError(`Required: ${requiredScopes.join(", ")}`, requiredScopes, result.scopes || []);
|
|
575
|
+
}
|
|
576
|
+
return result;
|
|
577
|
+
}
|
|
578
|
+
async withScope(scopes, fn) {
|
|
579
|
+
await this.requireScopes(...scopes);
|
|
580
|
+
return fn();
|
|
581
|
+
}
|
|
582
|
+
// ============ Webhooks ============
|
|
583
|
+
async listWebhooks() {
|
|
584
|
+
const response = await this.request("/api/v1/webhooks/list");
|
|
585
|
+
return response.webhooks;
|
|
586
|
+
}
|
|
587
|
+
async registerWebhook(request) {
|
|
588
|
+
const response = await this.request(
|
|
589
|
+
"/api/v1/webhooks/register",
|
|
590
|
+
{ method: "POST", body: JSON.stringify(request) }
|
|
591
|
+
);
|
|
592
|
+
return { webhookId: response.webhook_id, url: response.url, events: response.events, active: response.active, createdAt: response.created_at };
|
|
593
|
+
}
|
|
594
|
+
async deleteWebhook(webhookId) {
|
|
595
|
+
await this.request("/api/v1/webhooks/delete", {
|
|
530
596
|
method: "POST",
|
|
531
|
-
body: JSON.stringify({
|
|
597
|
+
body: JSON.stringify({ webhook_id: webhookId })
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
// ============ Billing ============
|
|
601
|
+
async listPlans() {
|
|
602
|
+
const response = await this.request("/api/v1/billing/plans");
|
|
603
|
+
return response.plans;
|
|
604
|
+
}
|
|
605
|
+
async getSubscription() {
|
|
606
|
+
return this.request("/api/v1/billing/subscription");
|
|
607
|
+
}
|
|
608
|
+
async createCheckout(priceId) {
|
|
609
|
+
return this.request("/api/v1/billing/checkout", {
|
|
610
|
+
method: "POST",
|
|
611
|
+
body: JSON.stringify({ price_id: priceId })
|
|
532
612
|
});
|
|
533
613
|
}
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
614
|
+
async createPortalSession() {
|
|
615
|
+
return this.request("/api/v1/billing/portal", { method: "POST" });
|
|
616
|
+
}
|
|
617
|
+
// ============ Audit ============
|
|
618
|
+
async getAuditLogs(query) {
|
|
619
|
+
const params = new URLSearchParams();
|
|
620
|
+
if (query?.limit) params.set("limit", String(query.limit));
|
|
621
|
+
if (query?.offset) params.set("offset", String(query.offset));
|
|
622
|
+
if (query?.actorId) params.set("actor_id", query.actorId);
|
|
623
|
+
if (query?.action) params.set("action", query.action);
|
|
624
|
+
if (query?.resourceType) params.set("resource_type", query.resourceType);
|
|
625
|
+
if (query?.startDate) params.set("start_date", query.startDate);
|
|
626
|
+
if (query?.endDate) params.set("end_date", query.endDate);
|
|
627
|
+
const qs = params.toString();
|
|
628
|
+
const response = await this.request(
|
|
629
|
+
`/api/v1/audit-logs${qs ? `?${qs}` : ""}`
|
|
630
|
+
);
|
|
631
|
+
return response.logs;
|
|
632
|
+
}
|
|
633
|
+
// ============ Services ============
|
|
634
|
+
async getService(serviceId) {
|
|
635
|
+
return this.request(`/api/v1/services/${encodeURIComponent(serviceId)}`);
|
|
636
|
+
}
|
|
637
|
+
async updateService(serviceId, data) {
|
|
638
|
+
return this.request(`/api/v1/services/${encodeURIComponent(serviceId)}`, {
|
|
639
|
+
method: "POST",
|
|
640
|
+
body: JSON.stringify(data)
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
async rotateClientSecret(serviceId) {
|
|
644
|
+
return this.request(
|
|
645
|
+
`/api/v1/services/${encodeURIComponent(serviceId)}/rotate-secret`,
|
|
646
|
+
{ method: "POST" }
|
|
647
|
+
);
|
|
648
|
+
}
|
|
649
|
+
// ============ JWKS ============
|
|
650
|
+
async getJwks(serviceId) {
|
|
651
|
+
return this.request(`/.well-known/jwks.json?service_id=${encodeURIComponent(serviceId)}`);
|
|
652
|
+
}
|
|
653
|
+
// ============ Onboarding (admin only) ============
|
|
538
654
|
async onboardService(request, masterAdminKey) {
|
|
539
655
|
const response = await this.request("/api/v1/services/onboard", {
|
|
540
656
|
method: "POST",
|
|
@@ -555,9 +671,6 @@ var AuthMiClient = class {
|
|
|
555
671
|
createdAt: response.created_at
|
|
556
672
|
};
|
|
557
673
|
}
|
|
558
|
-
/**
|
|
559
|
-
* Delete a service (requires master admin key)
|
|
560
|
-
*/
|
|
561
674
|
async deleteService(serviceId, masterAdminKey) {
|
|
562
675
|
await this.request("/api/v1/services/deboard", {
|
|
563
676
|
method: "POST",
|
|
@@ -567,41 +680,6 @@ var AuthMiClient = class {
|
|
|
567
680
|
body: JSON.stringify({ service_id: serviceId })
|
|
568
681
|
});
|
|
569
682
|
}
|
|
570
|
-
// ============ Webhook Management ============
|
|
571
|
-
/**
|
|
572
|
-
* List webhooks
|
|
573
|
-
*/
|
|
574
|
-
async listWebhooks() {
|
|
575
|
-
const response = await this.request(
|
|
576
|
-
"/api/v1/webhooks/list"
|
|
577
|
-
);
|
|
578
|
-
return response.webhooks;
|
|
579
|
-
}
|
|
580
|
-
/**
|
|
581
|
-
* Register a webhook
|
|
582
|
-
*/
|
|
583
|
-
async registerWebhook(request) {
|
|
584
|
-
const response = await this.request("/api/v1/webhooks/register", {
|
|
585
|
-
method: "POST",
|
|
586
|
-
body: JSON.stringify(request)
|
|
587
|
-
});
|
|
588
|
-
return {
|
|
589
|
-
webhookId: response.webhook_id,
|
|
590
|
-
url: response.url,
|
|
591
|
-
events: response.events,
|
|
592
|
-
active: response.active,
|
|
593
|
-
createdAt: response.created_at
|
|
594
|
-
};
|
|
595
|
-
}
|
|
596
|
-
/**
|
|
597
|
-
* Delete a webhook
|
|
598
|
-
*/
|
|
599
|
-
async deleteWebhook(webhookId) {
|
|
600
|
-
await this.request("/api/v1/webhooks/delete", {
|
|
601
|
-
method: "POST",
|
|
602
|
-
body: JSON.stringify({ webhook_id: webhookId })
|
|
603
|
-
});
|
|
604
|
-
}
|
|
605
683
|
};
|
|
606
684
|
|
|
607
685
|
// src/react.tsx
|
|
@@ -609,27 +687,30 @@ var import_jsx_runtime = require("react/jsx-runtime");
|
|
|
609
687
|
var AuthMiContext = (0, import_react.createContext)(null);
|
|
610
688
|
function AuthMiProvider({
|
|
611
689
|
config,
|
|
612
|
-
|
|
690
|
+
options,
|
|
613
691
|
children,
|
|
614
692
|
validateOnMount = true,
|
|
615
693
|
validationInterval = 5 * 60 * 1e3
|
|
616
694
|
}) {
|
|
617
|
-
const [client] = (0, import_react.useState)(() => new AuthMiClient(config,
|
|
695
|
+
const [client] = (0, import_react.useState)(() => new AuthMiClient(config, options));
|
|
618
696
|
const [isAuthenticated, setIsAuthenticated] = (0, import_react.useState)(false);
|
|
619
697
|
const [user, setUser] = (0, import_react.useState)(null);
|
|
620
698
|
const [isLoading, setIsLoading] = (0, import_react.useState)(true);
|
|
621
699
|
(0, import_react.useEffect)(() => {
|
|
622
700
|
const checkAuth = async () => {
|
|
623
|
-
const token = client.getAccessToken();
|
|
701
|
+
const token = await client.getAccessToken();
|
|
624
702
|
if (token && validateOnMount) {
|
|
625
703
|
try {
|
|
626
704
|
const result = await client.validate();
|
|
627
705
|
if (result.valid) {
|
|
628
706
|
setIsAuthenticated(true);
|
|
707
|
+
if (result.userId && result.email) {
|
|
708
|
+
setUser({ userId: result.userId, email: result.email, createdAt: "" });
|
|
709
|
+
}
|
|
629
710
|
} else {
|
|
630
711
|
client.clearAccessToken();
|
|
631
712
|
}
|
|
632
|
-
} catch
|
|
713
|
+
} catch {
|
|
633
714
|
client.clearAccessToken();
|
|
634
715
|
}
|
|
635
716
|
}
|
|
@@ -646,30 +727,34 @@ function AuthMiProvider({
|
|
|
646
727
|
setIsAuthenticated(false);
|
|
647
728
|
setUser(null);
|
|
648
729
|
}
|
|
649
|
-
} catch
|
|
730
|
+
} catch {
|
|
650
731
|
setIsAuthenticated(false);
|
|
651
732
|
setUser(null);
|
|
652
733
|
}
|
|
653
734
|
}, validationInterval);
|
|
654
735
|
return () => clearInterval(interval);
|
|
655
736
|
}, [client, isAuthenticated, validationInterval]);
|
|
656
|
-
const login = (0, import_react.useCallback)(
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
737
|
+
const login = (0, import_react.useCallback)(async (email, password) => {
|
|
738
|
+
setIsLoading(true);
|
|
739
|
+
try {
|
|
740
|
+
const token = await client.login({ email, password });
|
|
741
|
+
setIsAuthenticated(true);
|
|
742
|
+
setUser({ userId: token.userId, email: token.email, createdAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
743
|
+
} finally {
|
|
744
|
+
setIsLoading(false);
|
|
745
|
+
}
|
|
746
|
+
}, [client]);
|
|
747
|
+
const signup = (0, import_react.useCallback)(async (email, password, name) => {
|
|
748
|
+
setIsLoading(true);
|
|
749
|
+
try {
|
|
750
|
+
const cfg = client["config"];
|
|
751
|
+
const token = await client.signup({ email, password, name, client_id: cfg.serviceId || cfg.clientId });
|
|
752
|
+
setIsAuthenticated(true);
|
|
753
|
+
setUser({ userId: token.userId, email: token.email, createdAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
754
|
+
} finally {
|
|
755
|
+
setIsLoading(false);
|
|
756
|
+
}
|
|
757
|
+
}, [client]);
|
|
673
758
|
const logout = (0, import_react.useCallback)(async () => {
|
|
674
759
|
setIsLoading(true);
|
|
675
760
|
try {
|
|
@@ -694,17 +779,26 @@ function AuthMiProvider({
|
|
|
694
779
|
user,
|
|
695
780
|
isLoading,
|
|
696
781
|
login,
|
|
782
|
+
signup,
|
|
697
783
|
logout,
|
|
698
|
-
validate
|
|
784
|
+
validate,
|
|
785
|
+
setup2fa: () => client.setup2fa(),
|
|
786
|
+
enable2fa: (code) => client.enable2fa(code),
|
|
787
|
+
disable2fa: (password) => client.disable2fa(password),
|
|
788
|
+
regenerateBackupCodes: () => client.regenerateBackupCodes(),
|
|
789
|
+
challenge2fa: (request) => client.challenge2fa(request),
|
|
790
|
+
forgotPassword: (email) => client.forgotPassword({ email }),
|
|
791
|
+
initiateOAuth: (provider, redirectUri) => client.initiateOAuth(provider, redirectUri),
|
|
792
|
+
handleOAuthCallback: (url) => client.handleOAuthCallback(url),
|
|
793
|
+
isOAuthCallback: (url) => client.isOAuthCallback(url),
|
|
794
|
+
getMe: () => client.getMe()
|
|
699
795
|
};
|
|
700
796
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AuthMiContext.Provider, { value, children });
|
|
701
797
|
}
|
|
702
798
|
function useAuthMi() {
|
|
703
|
-
const
|
|
704
|
-
if (!
|
|
705
|
-
|
|
706
|
-
}
|
|
707
|
-
return context;
|
|
799
|
+
const ctx = (0, import_react.useContext)(AuthMiContext);
|
|
800
|
+
if (!ctx) throw new Error("useAuthMi must be used within AuthMiProvider");
|
|
801
|
+
return ctx;
|
|
708
802
|
}
|
|
709
803
|
function useAuthMiClient() {
|
|
710
804
|
return useAuthMi().client;
|
|
@@ -716,17 +810,37 @@ function useUser() {
|
|
|
716
810
|
return useAuthMi().user;
|
|
717
811
|
}
|
|
718
812
|
function useAuthActions() {
|
|
719
|
-
const {
|
|
720
|
-
|
|
813
|
+
const {
|
|
814
|
+
login,
|
|
815
|
+
signup,
|
|
816
|
+
logout,
|
|
817
|
+
isLoading,
|
|
818
|
+
forgotPassword,
|
|
819
|
+
setup2fa,
|
|
820
|
+
enable2fa,
|
|
821
|
+
disable2fa,
|
|
822
|
+
regenerateBackupCodes,
|
|
823
|
+
challenge2fa,
|
|
824
|
+
initiateOAuth
|
|
825
|
+
} = useAuthMi();
|
|
826
|
+
return {
|
|
827
|
+
login,
|
|
828
|
+
signup,
|
|
829
|
+
logout,
|
|
830
|
+
isLoading,
|
|
831
|
+
forgotPassword,
|
|
832
|
+
setup2fa,
|
|
833
|
+
enable2fa,
|
|
834
|
+
disable2fa,
|
|
835
|
+
regenerateBackupCodes,
|
|
836
|
+
challenge2fa,
|
|
837
|
+
initiateOAuth
|
|
838
|
+
};
|
|
721
839
|
}
|
|
722
840
|
function Protected({ children, fallback = null }) {
|
|
723
841
|
const { isAuthenticated, isLoading } = useAuthMi();
|
|
724
|
-
if (isLoading) {
|
|
725
|
-
|
|
726
|
-
}
|
|
727
|
-
if (!isAuthenticated) {
|
|
728
|
-
return null;
|
|
729
|
-
}
|
|
842
|
+
if (isLoading) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: fallback });
|
|
843
|
+
if (!isAuthenticated) return null;
|
|
730
844
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children });
|
|
731
845
|
}
|
|
732
846
|
function LoginForm({ onSuccess, onError, className }) {
|
|
@@ -741,9 +855,9 @@ function LoginForm({ onSuccess, onError, className }) {
|
|
|
741
855
|
await login(email, password);
|
|
742
856
|
onSuccess?.();
|
|
743
857
|
} catch (err) {
|
|
744
|
-
const
|
|
745
|
-
setError(
|
|
746
|
-
onError?.(err instanceof Error ? err : new Error(
|
|
858
|
+
const msg = err instanceof Error ? err.message : "Login failed";
|
|
859
|
+
setError(msg);
|
|
860
|
+
onError?.(err instanceof Error ? err : new Error(msg));
|
|
747
861
|
}
|
|
748
862
|
};
|
|
749
863
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", { onSubmit: handleSubmit, className, children: [
|
|
@@ -781,14 +895,7 @@ function LoginForm({ onSuccess, onError, className }) {
|
|
|
781
895
|
{
|
|
782
896
|
type: "submit",
|
|
783
897
|
disabled: isLoading,
|
|
784
|
-
style: {
|
|
785
|
-
padding: "0.5rem 1rem",
|
|
786
|
-
background: isLoading ? "#ccc" : "#007bff",
|
|
787
|
-
color: "white",
|
|
788
|
-
border: "none",
|
|
789
|
-
borderRadius: "4px",
|
|
790
|
-
cursor: isLoading ? "not-allowed" : "pointer"
|
|
791
|
-
},
|
|
898
|
+
style: { padding: "0.5rem 1rem", background: isLoading ? "#ccc" : "#007bff", color: "white", border: "none", borderRadius: "4px", cursor: isLoading ? "not-allowed" : "pointer" },
|
|
792
899
|
children: isLoading ? "Logging in..." : "Login"
|
|
793
900
|
}
|
|
794
901
|
)
|
|
@@ -799,9 +906,7 @@ function LoginForm({ onSuccess, onError, className }) {
|
|
|
799
906
|
AuthMiClient,
|
|
800
907
|
AuthMiError,
|
|
801
908
|
AuthMiProvider,
|
|
802
|
-
LocalStorageTokenStorage,
|
|
803
909
|
LoginForm,
|
|
804
|
-
MemoryTokenStorage,
|
|
805
910
|
Protected,
|
|
806
911
|
ScopeError,
|
|
807
912
|
useAuthActions,
|