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