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/index.js
CHANGED
|
@@ -22,6 +22,8 @@ var index_exports = {};
|
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
AuthMiClient: () => AuthMiClient,
|
|
24
24
|
AuthMiError: () => AuthMiError,
|
|
25
|
+
CookieTokenStorage: () => CookieTokenStorage,
|
|
26
|
+
IntrospectCache: () => IntrospectCache,
|
|
25
27
|
LocalStorageTokenStorage: () => LocalStorageTokenStorage,
|
|
26
28
|
MemoryTokenStorage: () => MemoryTokenStorage,
|
|
27
29
|
ScopeError: () => ScopeError,
|
|
@@ -29,39 +31,58 @@ __export(index_exports, {
|
|
|
29
31
|
});
|
|
30
32
|
module.exports = __toCommonJS(index_exports);
|
|
31
33
|
|
|
32
|
-
// src/
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
34
|
+
// src/config.ts
|
|
35
|
+
function resolveConfig(provider) {
|
|
36
|
+
const raw = typeof provider === "function" ? provider() : provider;
|
|
37
|
+
return {
|
|
38
|
+
baseUrl: raw.baseUrl,
|
|
39
|
+
apiKey: raw.apiKey ?? "",
|
|
40
|
+
serviceId: raw.serviceId ?? "",
|
|
41
|
+
clientId: raw.clientId ?? "",
|
|
42
|
+
clientSecret: raw.clientSecret ?? "",
|
|
43
|
+
defaultTokenExpiry: raw.defaultTokenExpiry ?? 24
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// src/storage/localStorage.ts
|
|
48
|
+
var STORAGE_KEY = "authmi_token";
|
|
49
|
+
var LocalStorageTokenStorage = class {
|
|
50
|
+
canAccess() {
|
|
51
|
+
if (typeof window === "undefined") return false;
|
|
52
|
+
try {
|
|
53
|
+
const probe = "__authmi_storage_test__";
|
|
54
|
+
localStorage.setItem(probe, "1");
|
|
55
|
+
localStorage.removeItem(probe);
|
|
56
|
+
return true;
|
|
57
|
+
} catch {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
47
60
|
}
|
|
48
|
-
};
|
|
49
|
-
var _LocalStorageTokenStorage = class _LocalStorageTokenStorage {
|
|
50
61
|
getToken() {
|
|
51
|
-
if (
|
|
52
|
-
|
|
62
|
+
if (!this.canAccess()) return null;
|
|
63
|
+
try {
|
|
64
|
+
return localStorage.getItem(STORAGE_KEY);
|
|
65
|
+
} catch {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
53
68
|
}
|
|
54
69
|
setToken(token) {
|
|
55
|
-
if (
|
|
56
|
-
|
|
70
|
+
if (!this.canAccess()) return;
|
|
71
|
+
try {
|
|
72
|
+
localStorage.setItem(STORAGE_KEY, token);
|
|
73
|
+
} catch {
|
|
74
|
+
}
|
|
57
75
|
}
|
|
58
76
|
removeToken() {
|
|
59
|
-
if (
|
|
60
|
-
|
|
77
|
+
if (!this.canAccess()) return;
|
|
78
|
+
try {
|
|
79
|
+
localStorage.removeItem(STORAGE_KEY);
|
|
80
|
+
} catch {
|
|
81
|
+
}
|
|
61
82
|
}
|
|
62
83
|
};
|
|
63
|
-
|
|
64
|
-
|
|
84
|
+
|
|
85
|
+
// src/storage/memory.ts
|
|
65
86
|
var MemoryTokenStorage = class {
|
|
66
87
|
constructor() {
|
|
67
88
|
this.token = null;
|
|
@@ -77,51 +98,56 @@ var MemoryTokenStorage = class {
|
|
|
77
98
|
}
|
|
78
99
|
};
|
|
79
100
|
|
|
80
|
-
// src/
|
|
81
|
-
var
|
|
82
|
-
constructor(
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
clientSecret: "",
|
|
88
|
-
defaultTokenExpiry: 24,
|
|
89
|
-
...config
|
|
90
|
-
};
|
|
91
|
-
this.tokenStorage = tokenStorage || new LocalStorageTokenStorage();
|
|
101
|
+
// src/types.ts
|
|
102
|
+
var AuthMiError = class extends Error {
|
|
103
|
+
constructor(message, code, statusCode) {
|
|
104
|
+
super(message);
|
|
105
|
+
this.code = code;
|
|
106
|
+
this.statusCode = statusCode;
|
|
107
|
+
this.name = "AuthMiError";
|
|
92
108
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
109
|
+
};
|
|
110
|
+
var ScopeError = class extends AuthMiError {
|
|
111
|
+
constructor(message, requiredScopes, providedScopes) {
|
|
112
|
+
super(message, "INSUFFICIENT_SCOPES", 403);
|
|
113
|
+
this.requiredScopes = requiredScopes;
|
|
114
|
+
this.providedScopes = providedScopes;
|
|
115
|
+
this.name = "ScopeError";
|
|
100
116
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
// src/client.ts
|
|
120
|
+
var AuthMiClient = class {
|
|
121
|
+
constructor(config, options) {
|
|
122
|
+
this.configProvider = config;
|
|
123
|
+
this.storage = options?.storage ?? (typeof window !== "undefined" ? new LocalStorageTokenStorage() : new MemoryTokenStorage());
|
|
124
|
+
this.cache = options?.cache || null;
|
|
125
|
+
}
|
|
126
|
+
/** Resolve current config (supports lazy evaluation) */
|
|
127
|
+
get config() {
|
|
128
|
+
return resolveConfig(this.configProvider);
|
|
129
|
+
}
|
|
130
|
+
// ============ HTTP plumbing ============
|
|
131
|
+
async request(endpoint, options = {}, requireUserToken = false, requireServiceAuth = true) {
|
|
132
|
+
const cfg = this.config;
|
|
133
|
+
const url = `${cfg.baseUrl}${endpoint}`;
|
|
104
134
|
const headers = {
|
|
105
135
|
"Content-Type": "application/json",
|
|
106
136
|
...options.headers || {}
|
|
107
137
|
};
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
headers["Authorization"] = `Bearer ${token}`;
|
|
138
|
+
if (!headers["Authorization"]) {
|
|
139
|
+
if (requireUserToken) {
|
|
140
|
+
const token = await this.storage.getToken();
|
|
141
|
+
if (token) headers["Authorization"] = `Bearer ${token}`;
|
|
142
|
+
} else if (requireServiceAuth && cfg.clientId && cfg.clientSecret) {
|
|
143
|
+
headers["Authorization"] = `Basic ${btoa(`${cfg.clientId}:${cfg.clientSecret}`)}`;
|
|
144
|
+
headers["X-Service-ID"] = cfg.serviceId || cfg.clientId;
|
|
145
|
+
} else if (requireServiceAuth && cfg.apiKey) {
|
|
146
|
+
headers["Authorization"] = `Bearer ${cfg.apiKey}`;
|
|
118
147
|
}
|
|
119
148
|
}
|
|
120
149
|
try {
|
|
121
|
-
const response = await fetch(url, {
|
|
122
|
-
...options,
|
|
123
|
-
headers
|
|
124
|
-
});
|
|
150
|
+
const response = await fetch(url, { ...options, headers });
|
|
125
151
|
const data = await response.json();
|
|
126
152
|
if (!response.ok) {
|
|
127
153
|
if (response.status === 403 && data.required && data.provided) {
|
|
@@ -139,9 +165,7 @@ var AuthMiClient = class {
|
|
|
139
165
|
}
|
|
140
166
|
return data;
|
|
141
167
|
} catch (error) {
|
|
142
|
-
if (error instanceof AuthMiError)
|
|
143
|
-
throw error;
|
|
144
|
-
}
|
|
168
|
+
if (error instanceof AuthMiError) throw error;
|
|
145
169
|
throw new AuthMiError(
|
|
146
170
|
error instanceof Error ? error.message : "Network error",
|
|
147
171
|
"NETWORK_ERROR"
|
|
@@ -149,384 +173,497 @@ var AuthMiClient = class {
|
|
|
149
173
|
}
|
|
150
174
|
}
|
|
151
175
|
// ============ Configuration ============
|
|
152
|
-
/**
|
|
153
|
-
* Update the client's service configuration
|
|
154
|
-
*/
|
|
155
176
|
configure(config) {
|
|
156
|
-
|
|
177
|
+
const current = resolveConfig(this.configProvider);
|
|
178
|
+
this.configProvider = { ...current, ...config };
|
|
157
179
|
}
|
|
158
|
-
/**
|
|
159
|
-
* Get current configuration (without sensitive data)
|
|
160
|
-
*/
|
|
161
180
|
getConfig() {
|
|
162
|
-
const { apiKey: _, clientSecret: __, ...rest } = this.
|
|
181
|
+
const { apiKey: _, clientSecret: __, ...rest } = resolveConfig(this.configProvider);
|
|
163
182
|
return rest;
|
|
164
183
|
}
|
|
165
184
|
// ============ Token Management ============
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
*/
|
|
169
|
-
getAccessToken() {
|
|
170
|
-
return this.tokenStorage.getToken();
|
|
185
|
+
async getAccessToken() {
|
|
186
|
+
return this.storage.getToken();
|
|
171
187
|
}
|
|
172
|
-
/**
|
|
173
|
-
* Store an access token
|
|
174
|
-
*/
|
|
175
188
|
setAccessToken(token) {
|
|
176
|
-
this.
|
|
189
|
+
this.storage.setToken(token);
|
|
177
190
|
}
|
|
178
|
-
/**
|
|
179
|
-
* Remove the stored access token (logout)
|
|
180
|
-
*/
|
|
181
191
|
clearAccessToken() {
|
|
182
|
-
this.
|
|
192
|
+
this.storage.removeToken();
|
|
183
193
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
isLoggedIn() {
|
|
188
|
-
return !!this.getAccessToken();
|
|
194
|
+
async isLoggedIn() {
|
|
195
|
+
const token = await this.storage.getToken();
|
|
196
|
+
return !!token;
|
|
189
197
|
}
|
|
190
|
-
// ============
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
*/
|
|
194
|
-
async introspect(request) {
|
|
195
|
-
return this.request("/api/v1/introspect", {
|
|
198
|
+
// ============ Auth Endpoints ============
|
|
199
|
+
async signup(credentials) {
|
|
200
|
+
const response = await this.request("/api/v1/auth/signup", {
|
|
196
201
|
method: "POST",
|
|
197
|
-
body: JSON.stringify(
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* Validate the current token with optional scope checking
|
|
202
|
-
*/
|
|
203
|
-
async validateWithScopes(requiredScopes) {
|
|
204
|
-
const token = this.getAccessToken();
|
|
205
|
-
if (!token) {
|
|
206
|
-
return { valid: false, error: "No token provided" };
|
|
207
|
-
}
|
|
208
|
-
if (!this.config.apiKey && !this.config.clientSecret) {
|
|
209
|
-
return { valid: false, error: "Service credentials not configured" };
|
|
210
|
-
}
|
|
211
|
-
if (!this.config.serviceId) {
|
|
212
|
-
return { valid: false, error: "Service ID not configured in AuthMiConfig" };
|
|
213
|
-
}
|
|
214
|
-
try {
|
|
215
|
-
const result = await this.introspect({
|
|
216
|
-
serviceId: this.config.serviceId,
|
|
217
|
-
token,
|
|
218
|
-
requiredScopes
|
|
219
|
-
});
|
|
220
|
-
return {
|
|
221
|
-
valid: result.valid,
|
|
222
|
-
type: result.type === "bearer" ? "token" : "api_key",
|
|
223
|
-
scopes: result.scopes,
|
|
224
|
-
userId: result.userId,
|
|
225
|
-
email: result.email,
|
|
226
|
-
error: result.error
|
|
227
|
-
};
|
|
228
|
-
} catch (error) {
|
|
229
|
-
if (error instanceof ScopeError) {
|
|
230
|
-
return {
|
|
231
|
-
valid: false,
|
|
232
|
-
error: error.message,
|
|
233
|
-
scopes: error.providedScopes
|
|
234
|
-
};
|
|
235
|
-
}
|
|
236
|
-
if (error instanceof AuthMiError && error.statusCode === 401) {
|
|
237
|
-
return { valid: false, error: "Invalid or expired token" };
|
|
238
|
-
}
|
|
239
|
-
throw error;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
/**
|
|
243
|
-
* Check if the current user has all the required scopes
|
|
244
|
-
* Throws ScopeError if scopes are insufficient
|
|
245
|
-
*/
|
|
246
|
-
async requireScopes(...requiredScopes) {
|
|
247
|
-
const result = await this.validateWithScopes(requiredScopes);
|
|
248
|
-
if (!result.valid) {
|
|
249
|
-
throw new AuthMiError(
|
|
250
|
-
result.error || "Authentication required",
|
|
251
|
-
"UNAUTHORIZED",
|
|
252
|
-
401
|
|
253
|
-
);
|
|
254
|
-
}
|
|
255
|
-
const hasScopes = requiredScopes.every(
|
|
256
|
-
(scope) => result.scopes?.includes(scope)
|
|
257
|
-
);
|
|
258
|
-
if (!hasScopes) {
|
|
259
|
-
throw new ScopeError(
|
|
260
|
-
`Required scopes: ${requiredScopes.join(", ")}`,
|
|
261
|
-
requiredScopes,
|
|
262
|
-
result.scopes || []
|
|
263
|
-
);
|
|
264
|
-
}
|
|
265
|
-
return result;
|
|
202
|
+
body: JSON.stringify(credentials)
|
|
203
|
+
}, false, false);
|
|
204
|
+
return this.mapAuthToken(response);
|
|
266
205
|
}
|
|
267
|
-
/**
|
|
268
|
-
* Execute a function only if the user has the required scopes
|
|
269
|
-
*/
|
|
270
|
-
async withScope(scopes, fn) {
|
|
271
|
-
await this.requireScopes(...scopes);
|
|
272
|
-
return fn();
|
|
273
|
-
}
|
|
274
|
-
// ============ OAuth ============
|
|
275
|
-
/**
|
|
276
|
-
* Initiate OAuth login flow and return the authorization URL
|
|
277
|
-
*/
|
|
278
|
-
async initiateOAuth(provider, redirectUri) {
|
|
279
|
-
if (!this.config.apiKey && !this.config.clientSecret) {
|
|
280
|
-
throw new AuthMiError(
|
|
281
|
-
"API key or client credentials required for OAuth. Configure the client first.",
|
|
282
|
-
"MISSING_CREDENTIALS"
|
|
283
|
-
);
|
|
284
|
-
}
|
|
285
|
-
if (!this.config.serviceId) {
|
|
286
|
-
throw new AuthMiError(
|
|
287
|
-
"serviceId required for OAuth. Configure the client first.",
|
|
288
|
-
"MISSING_SERVICE_ID"
|
|
289
|
-
);
|
|
290
|
-
}
|
|
291
|
-
const response = await this.request(
|
|
292
|
-
"/api/v1/auth/oauth/initiate",
|
|
293
|
-
{
|
|
294
|
-
method: "POST",
|
|
295
|
-
body: JSON.stringify({ provider, redirectUri, client_id: this.config.serviceId })
|
|
296
|
-
}
|
|
297
|
-
);
|
|
298
|
-
return response.url;
|
|
299
|
-
}
|
|
300
|
-
/**
|
|
301
|
-
* Handle OAuth callback URL and store the access token
|
|
302
|
-
* Returns null if no OAuth params are present in the URL
|
|
303
|
-
*/
|
|
304
|
-
handleOAuthCallback(url) {
|
|
305
|
-
const parsedUrl = typeof url === "string" ? new URL(url) : url;
|
|
306
|
-
const token = parsedUrl.searchParams.get("token");
|
|
307
|
-
const userId = parsedUrl.searchParams.get("user_id");
|
|
308
|
-
const email = parsedUrl.searchParams.get("email");
|
|
309
|
-
if (!token || !userId || !email) {
|
|
310
|
-
return null;
|
|
311
|
-
}
|
|
312
|
-
this.setAccessToken(token);
|
|
313
|
-
return { token, userId, email };
|
|
314
|
-
}
|
|
315
|
-
/**
|
|
316
|
-
* Check if the current URL contains OAuth callback params
|
|
317
|
-
*/
|
|
318
|
-
isOAuthCallback(url) {
|
|
319
|
-
const parsedUrl = typeof url === "string" ? new URL(url) : url;
|
|
320
|
-
return parsedUrl.searchParams.has("token") && parsedUrl.searchParams.has("user_id") && parsedUrl.searchParams.has("email");
|
|
321
|
-
}
|
|
322
|
-
// ============ Authentication ============
|
|
323
|
-
/**
|
|
324
|
-
* Login a user and store the access token
|
|
325
|
-
*/
|
|
326
206
|
async login(credentials) {
|
|
327
|
-
|
|
328
|
-
throw new AuthMiError(
|
|
329
|
-
"API key or client credentials required for login. Configure the client first.",
|
|
330
|
-
"MISSING_CREDENTIALS"
|
|
331
|
-
);
|
|
332
|
-
}
|
|
207
|
+
const cfg = this.config;
|
|
333
208
|
const response = await this.request("/api/v1/auth/login", {
|
|
334
209
|
method: "POST",
|
|
335
210
|
body: JSON.stringify({
|
|
336
211
|
email: credentials.email,
|
|
337
212
|
password: credentials.password,
|
|
338
|
-
client_id:
|
|
339
|
-
expires_in_hours: credentials.expiresInHours ||
|
|
213
|
+
client_id: cfg.serviceId,
|
|
214
|
+
expires_in_hours: credentials.expiresInHours || cfg.defaultTokenExpiry,
|
|
340
215
|
scopes: credentials.scopes
|
|
341
216
|
})
|
|
342
|
-
});
|
|
343
|
-
const token =
|
|
344
|
-
accessToken: response.access_token,
|
|
345
|
-
tokenType: response.token_type,
|
|
346
|
-
expiresIn: response.expires_in,
|
|
347
|
-
expiresAt: response.expires_at,
|
|
348
|
-
userId: response.user_id,
|
|
349
|
-
email: response.email,
|
|
350
|
-
scopes: response.scopes
|
|
351
|
-
};
|
|
217
|
+
}, false, false);
|
|
218
|
+
const token = this.mapAuthToken(response);
|
|
352
219
|
this.setAccessToken(token.accessToken);
|
|
353
220
|
return token;
|
|
354
221
|
}
|
|
355
|
-
/**
|
|
356
|
-
* Logout the current user
|
|
357
|
-
*/
|
|
358
222
|
async logout() {
|
|
359
|
-
const token = this.
|
|
223
|
+
const token = await this.storage.getToken();
|
|
360
224
|
if (token) {
|
|
361
225
|
try {
|
|
362
|
-
await this.request("/api/v1/auth/logout", {
|
|
363
|
-
|
|
364
|
-
}, true);
|
|
365
|
-
} catch (error) {
|
|
226
|
+
await this.request("/api/v1/auth/logout", { method: "POST" }, true);
|
|
227
|
+
} catch {
|
|
366
228
|
}
|
|
367
229
|
}
|
|
368
230
|
this.clearAccessToken();
|
|
369
231
|
}
|
|
370
|
-
/**
|
|
371
|
-
* Validate the current token or an API key
|
|
372
|
-
* @deprecated Use validateWithScopes instead
|
|
373
|
-
*/
|
|
374
|
-
async validate(_credential) {
|
|
375
|
-
return this.validateWithScopes();
|
|
376
|
-
}
|
|
377
|
-
/**
|
|
378
|
-
* Refresh the current token
|
|
379
|
-
*/
|
|
380
232
|
async refreshToken() {
|
|
381
|
-
const response = await this.request("/api/v1/auth/refresh", {
|
|
382
|
-
method: "POST"
|
|
383
|
-
}, true);
|
|
233
|
+
const response = await this.request("/api/v1/auth/refresh", { method: "POST" }, true);
|
|
384
234
|
const token = {
|
|
385
235
|
accessToken: response.access_token,
|
|
386
|
-
tokenType:
|
|
236
|
+
tokenType: "Bearer",
|
|
387
237
|
expiresIn: response.expires_in,
|
|
388
238
|
expiresAt: response.expires_at,
|
|
389
239
|
userId: "",
|
|
390
|
-
// Refresh doesn't return user info
|
|
391
240
|
email: ""
|
|
392
241
|
};
|
|
393
242
|
this.setAccessToken(token.accessToken);
|
|
394
243
|
return token;
|
|
395
244
|
}
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
async
|
|
401
|
-
|
|
245
|
+
async getMe() {
|
|
246
|
+
const response = await this.request("/api/v1/auth/me", {}, true);
|
|
247
|
+
return { userId: response.user_id, email: response.email, name: response.name, createdAt: response.created_at };
|
|
248
|
+
}
|
|
249
|
+
async forgotPassword(request) {
|
|
250
|
+
return this.request("/api/v1/auth/forgot-password", {
|
|
402
251
|
method: "POST",
|
|
403
|
-
body: JSON.stringify(request)
|
|
404
|
-
});
|
|
252
|
+
body: JSON.stringify({ email: request.email, client_id: this.config.serviceId })
|
|
253
|
+
}, false, false);
|
|
254
|
+
}
|
|
255
|
+
async resetPassword(request) {
|
|
256
|
+
return this.request("/api/v1/auth/reset-password", {
|
|
257
|
+
method: "POST",
|
|
258
|
+
body: JSON.stringify({ token: request.token, new_password: request.newPassword })
|
|
259
|
+
}, false, false);
|
|
260
|
+
}
|
|
261
|
+
async verifyEmail(request) {
|
|
262
|
+
return this.request("/api/v1/auth/verify-email", {
|
|
263
|
+
method: "POST",
|
|
264
|
+
body: JSON.stringify({ token: request.token })
|
|
265
|
+
}, false, false);
|
|
266
|
+
}
|
|
267
|
+
async resendVerification(request) {
|
|
268
|
+
return this.request("/api/v1/auth/resend-verification", {
|
|
269
|
+
method: "POST",
|
|
270
|
+
body: JSON.stringify({ email: request.email, client_id: this.config.serviceId })
|
|
271
|
+
}, false, false);
|
|
272
|
+
}
|
|
273
|
+
async deactivateAccount(password) {
|
|
274
|
+
await this.request("/api/v1/auth/deactivate", {
|
|
275
|
+
method: "POST",
|
|
276
|
+
body: JSON.stringify({ password })
|
|
277
|
+
}, true, false);
|
|
278
|
+
}
|
|
279
|
+
mapAuthToken(raw) {
|
|
405
280
|
return {
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
281
|
+
accessToken: raw.access_token,
|
|
282
|
+
tokenType: raw.token_type,
|
|
283
|
+
expiresIn: raw.expires_in,
|
|
284
|
+
expiresAt: raw.expires_at,
|
|
285
|
+
userId: raw.user_id,
|
|
286
|
+
email: raw.email,
|
|
287
|
+
emailVerified: raw.email_verified,
|
|
288
|
+
scopes: raw.scopes
|
|
409
289
|
};
|
|
410
290
|
}
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
291
|
+
// ============ OAuth ============
|
|
292
|
+
async initiateOAuth(provider, redirectUri) {
|
|
293
|
+
const cfg = this.config;
|
|
294
|
+
if (!cfg.clientId && !cfg.clientSecret && !cfg.apiKey) {
|
|
295
|
+
throw new AuthMiError("Credentials required for OAuth", "MISSING_CREDENTIALS");
|
|
296
|
+
}
|
|
297
|
+
const response = await this.request("/api/v1/auth/oauth/initiate", {
|
|
298
|
+
method: "POST",
|
|
299
|
+
body: JSON.stringify({ provider, redirectUri, client_id: cfg.serviceId || cfg.clientId })
|
|
300
|
+
}, false, false);
|
|
301
|
+
return response.url;
|
|
302
|
+
}
|
|
303
|
+
handleOAuthCallback(url) {
|
|
304
|
+
const parsed = typeof url === "string" ? new URL(url) : url;
|
|
305
|
+
const token = parsed.searchParams.get("token");
|
|
306
|
+
const userId = parsed.searchParams.get("user_id");
|
|
307
|
+
const email = parsed.searchParams.get("email");
|
|
308
|
+
if (!token || !userId || !email) return null;
|
|
309
|
+
this.setAccessToken(token);
|
|
310
|
+
return { token, userId, email };
|
|
311
|
+
}
|
|
312
|
+
isOAuthCallback(url) {
|
|
313
|
+
const parsed = typeof url === "string" ? new URL(url) : url;
|
|
314
|
+
return !!(parsed.searchParams.has("token") && parsed.searchParams.has("user_id") && parsed.searchParams.has("email"));
|
|
315
|
+
}
|
|
316
|
+
// ============ SAML ============
|
|
317
|
+
async initiateSaml(request) {
|
|
318
|
+
return this.request("/api/v1/auth/saml/initiate", {
|
|
319
|
+
method: "POST",
|
|
320
|
+
body: JSON.stringify({ email: request.email, redirect_uri: request.redirectUri })
|
|
321
|
+
}, false, false);
|
|
322
|
+
}
|
|
323
|
+
async parseSamlMetadata(xml) {
|
|
324
|
+
return this.request("/api/v1/auth/saml/parse-metadata", {
|
|
325
|
+
method: "POST",
|
|
326
|
+
body: JSON.stringify({ metadata_xml: xml })
|
|
327
|
+
}, false, false);
|
|
328
|
+
}
|
|
329
|
+
async listSamlConnections() {
|
|
330
|
+
const response = await this.request("/api/v1/auth/saml/connections");
|
|
331
|
+
return response.connections;
|
|
332
|
+
}
|
|
333
|
+
async createSamlConnection(request) {
|
|
334
|
+
return this.request("/api/v1/auth/saml/connections", {
|
|
335
|
+
method: "POST",
|
|
336
|
+
body: JSON.stringify(request)
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
async deleteSamlConnection(id) {
|
|
340
|
+
await this.request(`/api/v1/auth/saml/connections/${encodeURIComponent(id)}`, {
|
|
341
|
+
method: "DELETE"
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
// ============ 2FA ============
|
|
345
|
+
async setup2fa() {
|
|
346
|
+
return this.request("/api/v1/auth/2fa/setup", { method: "POST" }, true, false);
|
|
347
|
+
}
|
|
348
|
+
async enable2fa(code) {
|
|
349
|
+
return this.request("/api/v1/auth/2fa/enable", {
|
|
350
|
+
method: "POST",
|
|
351
|
+
body: JSON.stringify({ code })
|
|
352
|
+
}, true, false);
|
|
353
|
+
}
|
|
354
|
+
async disable2fa(password) {
|
|
355
|
+
await this.request("/api/v1/auth/2fa/disable", {
|
|
356
|
+
method: "POST",
|
|
357
|
+
body: JSON.stringify({ password })
|
|
358
|
+
}, true, false);
|
|
359
|
+
}
|
|
360
|
+
async regenerateBackupCodes() {
|
|
361
|
+
return this.request("/api/v1/auth/2fa/backup-codes", { method: "POST" }, true, false);
|
|
362
|
+
}
|
|
363
|
+
async challenge2fa(request) {
|
|
364
|
+
return this.request("/api/v1/auth/2fa/challenge", {
|
|
365
|
+
method: "POST",
|
|
366
|
+
body: JSON.stringify(request)
|
|
367
|
+
}, false, false);
|
|
368
|
+
}
|
|
369
|
+
// ============ Users ============
|
|
370
|
+
async createUser(request) {
|
|
371
|
+
const response = await this.request(
|
|
372
|
+
"/api/v1/users/create",
|
|
373
|
+
{
|
|
374
|
+
method: "POST",
|
|
375
|
+
body: JSON.stringify(request)
|
|
376
|
+
}
|
|
377
|
+
);
|
|
378
|
+
return { userId: response.user_id, email: response.email, name: response.name, createdAt: response.created_at };
|
|
379
|
+
}
|
|
414
380
|
async listUsers() {
|
|
415
381
|
const response = await this.request("/api/v1/users/list");
|
|
416
382
|
return response.users.map((u) => ({
|
|
417
383
|
userId: u.user_id,
|
|
418
384
|
email: u.email,
|
|
385
|
+
name: u.name,
|
|
419
386
|
createdAt: u.created_at
|
|
420
387
|
}));
|
|
421
388
|
}
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
389
|
+
async getUser(userId) {
|
|
390
|
+
const response = await this.request(
|
|
391
|
+
`/api/v1/users/${encodeURIComponent(userId)}`
|
|
392
|
+
);
|
|
393
|
+
return { userId: response.user_id, email: response.email, name: response.name, createdAt: response.created_at };
|
|
394
|
+
}
|
|
425
395
|
async deleteUser(userId) {
|
|
426
396
|
await this.request("/api/v1/users/delete", {
|
|
427
397
|
method: "POST",
|
|
428
398
|
body: JSON.stringify({ user_id: userId })
|
|
429
399
|
});
|
|
430
400
|
}
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
401
|
+
async deleteUserById(userId) {
|
|
402
|
+
await this.request(`/api/v1/users/${encodeURIComponent(userId)}`, { method: "DELETE" });
|
|
403
|
+
}
|
|
434
404
|
async updatePassword(userId, oldPassword, newPassword) {
|
|
435
405
|
await this.request("/api/v1/users/password", {
|
|
436
406
|
method: "POST",
|
|
437
|
-
body: JSON.stringify({
|
|
438
|
-
user_id: userId,
|
|
439
|
-
old_password: oldPassword,
|
|
440
|
-
new_password: newPassword
|
|
441
|
-
})
|
|
407
|
+
body: JSON.stringify({ user_id: userId, old_password: oldPassword, new_password: newPassword })
|
|
442
408
|
});
|
|
443
409
|
}
|
|
444
|
-
// ============
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
return response.
|
|
410
|
+
// ============ Roles ============
|
|
411
|
+
async createRole(request) {
|
|
412
|
+
const response = await this.request(
|
|
413
|
+
"/api/v1/users/roles/create",
|
|
414
|
+
{ method: "POST", body: JSON.stringify(request) }
|
|
415
|
+
);
|
|
416
|
+
return { roleId: response.role_id, name: response.name, description: response.description, createdAt: response.created_at };
|
|
417
|
+
}
|
|
418
|
+
async listRoles() {
|
|
419
|
+
const response = await this.request("/api/v1/users/roles/list");
|
|
420
|
+
return response.roles.map((r) => ({
|
|
421
|
+
roleId: r.role_id,
|
|
422
|
+
name: r.name,
|
|
423
|
+
description: r.description,
|
|
424
|
+
createdAt: r.created_at
|
|
425
|
+
}));
|
|
451
426
|
}
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
async
|
|
456
|
-
|
|
427
|
+
async deleteRole(roleId) {
|
|
428
|
+
await this.request(`/api/v1/users/roles/${encodeURIComponent(roleId)}`, { method: "DELETE" });
|
|
429
|
+
}
|
|
430
|
+
async assignRole(userId, roleId) {
|
|
431
|
+
await this.request("/api/v1/users/roles/assign", {
|
|
457
432
|
method: "POST",
|
|
458
|
-
body: JSON.stringify(
|
|
433
|
+
body: JSON.stringify({ user_id: userId, role_id: roleId })
|
|
459
434
|
});
|
|
460
|
-
return {
|
|
461
|
-
scopeId: response.scope_id,
|
|
462
|
-
name: response.name,
|
|
463
|
-
key: response.key,
|
|
464
|
-
description: response.description,
|
|
465
|
-
createdAt: response.created_at
|
|
466
|
-
};
|
|
467
435
|
}
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
*/
|
|
471
|
-
async deleteScope(scopeId) {
|
|
472
|
-
await this.request("/api/v1/scopes/delete", {
|
|
436
|
+
async unassignRole(userId, roleId) {
|
|
437
|
+
await this.request("/api/v1/users/roles/unassign", {
|
|
473
438
|
method: "POST",
|
|
474
|
-
body: JSON.stringify({
|
|
439
|
+
body: JSON.stringify({ user_id: userId, role_id: roleId })
|
|
475
440
|
});
|
|
476
441
|
}
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
442
|
+
async getUserRoles(userId) {
|
|
443
|
+
const response = await this.request(
|
|
444
|
+
`/api/v1/users/${encodeURIComponent(userId)}/roles`
|
|
445
|
+
);
|
|
446
|
+
return response.roles.map((r) => ({
|
|
447
|
+
roleId: r.role_id,
|
|
448
|
+
name: r.name,
|
|
449
|
+
description: r.description,
|
|
450
|
+
createdAt: r.created_at
|
|
451
|
+
}));
|
|
452
|
+
}
|
|
453
|
+
// ============ API Keys ============
|
|
481
454
|
async listApiKeys() {
|
|
482
455
|
const response = await this.request("/api/v1/keys/list");
|
|
483
456
|
return response.keys.map((k) => ({
|
|
484
457
|
keyId: k.keyId,
|
|
485
458
|
name: k.name,
|
|
486
459
|
scope: k.scope,
|
|
487
|
-
scopes:
|
|
460
|
+
scopes: k.scopes,
|
|
488
461
|
keyType: k.keyType,
|
|
462
|
+
tpsLimit: k.tpsLimit,
|
|
489
463
|
revoked: k.revoked,
|
|
490
|
-
createdAt: k.createdAt
|
|
491
|
-
tpsLimit: k.tpsLimit
|
|
464
|
+
createdAt: k.createdAt
|
|
492
465
|
}));
|
|
493
466
|
}
|
|
494
|
-
/**
|
|
495
|
-
* Create a new API key with optional custom scopes
|
|
496
|
-
*/
|
|
497
467
|
async createApiKey(request) {
|
|
498
|
-
const response = await this.request(
|
|
468
|
+
const response = await this.request(
|
|
469
|
+
"/api/v1/keys/create",
|
|
470
|
+
{
|
|
471
|
+
method: "POST",
|
|
472
|
+
body: JSON.stringify({
|
|
473
|
+
user_id: request.userId,
|
|
474
|
+
name: request.name,
|
|
475
|
+
scope: request.scope || "service",
|
|
476
|
+
scopes: request.scopes,
|
|
477
|
+
key_type: request.keyType || "service",
|
|
478
|
+
tps_limit: request.tpsLimit || 100
|
|
479
|
+
})
|
|
480
|
+
}
|
|
481
|
+
);
|
|
482
|
+
return { keyId: response.key_id, apiKey: response.api_key, name: response.name, scope: response.scope, scopes: response.scopes };
|
|
483
|
+
}
|
|
484
|
+
async revokeApiKey(keyId) {
|
|
485
|
+
await this.request("/api/v1/keys/revoke", {
|
|
486
|
+
method: "POST",
|
|
487
|
+
body: JSON.stringify({ key_id: keyId })
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
// ============ Scopes ============
|
|
491
|
+
async listScopes() {
|
|
492
|
+
const response = await this.request("/api/v1/scopes/list");
|
|
493
|
+
return response.scopes;
|
|
494
|
+
}
|
|
495
|
+
async createScope(request) {
|
|
496
|
+
const response = await this.request(
|
|
497
|
+
"/api/v1/scopes/create",
|
|
498
|
+
{ method: "POST", body: JSON.stringify(request) }
|
|
499
|
+
);
|
|
500
|
+
return { scopeId: response.scope_id, name: response.name, key: response.key, description: response.description, createdAt: response.created_at };
|
|
501
|
+
}
|
|
502
|
+
async deleteScope(scopeId) {
|
|
503
|
+
await this.request("/api/v1/scopes/delete", {
|
|
504
|
+
method: "POST",
|
|
505
|
+
body: JSON.stringify({ scope_id: scopeId })
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
// ============ Introspection & Validation ============
|
|
509
|
+
async introspect(request) {
|
|
510
|
+
if (this.cache && request.token) {
|
|
511
|
+
const cached = this.cache.get(request.token);
|
|
512
|
+
if (cached) return cached;
|
|
513
|
+
}
|
|
514
|
+
const result = await this.request("/api/v1/introspect", {
|
|
499
515
|
method: "POST",
|
|
500
516
|
body: JSON.stringify({
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
key_type: request.keyType || "service",
|
|
506
|
-
tps_limit: request.tpsLimit || 100
|
|
517
|
+
serviceId: request.serviceId,
|
|
518
|
+
token: request.token,
|
|
519
|
+
apiKey: request.apiKey,
|
|
520
|
+
requiredScopes: request.requiredScopes
|
|
507
521
|
})
|
|
508
522
|
});
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
scope: response.scope,
|
|
514
|
-
scopes: response.scopes
|
|
515
|
-
};
|
|
523
|
+
if (this.cache && request.token) {
|
|
524
|
+
this.cache.set(request.token, result);
|
|
525
|
+
}
|
|
526
|
+
return result;
|
|
516
527
|
}
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
528
|
+
async validate() {
|
|
529
|
+
const cfg = this.config;
|
|
530
|
+
const token = await this.storage.getToken();
|
|
531
|
+
if (!token) return { valid: false, error: "No token stored" };
|
|
532
|
+
if (!cfg.serviceId) return { valid: false, error: "Service ID not configured" };
|
|
533
|
+
if (!cfg.apiKey && !cfg.clientSecret) return { valid: false, error: "Service credentials not configured" };
|
|
534
|
+
try {
|
|
535
|
+
const result = await this.introspect({ serviceId: cfg.serviceId, token });
|
|
536
|
+
return {
|
|
537
|
+
valid: result.valid,
|
|
538
|
+
type: result.type === "bearer" ? "token" : "api_key",
|
|
539
|
+
scopes: result.scopes,
|
|
540
|
+
userId: result.userId,
|
|
541
|
+
email: result.email,
|
|
542
|
+
error: result.error
|
|
543
|
+
};
|
|
544
|
+
} catch (error) {
|
|
545
|
+
if (error instanceof ScopeError) {
|
|
546
|
+
return { valid: false, error: error.message, scopes: error.providedScopes };
|
|
547
|
+
}
|
|
548
|
+
if (error instanceof AuthMiError && error.statusCode === 401) {
|
|
549
|
+
return { valid: false, error: "Invalid or expired token" };
|
|
550
|
+
}
|
|
551
|
+
throw error;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
async validateWithScopes(requiredScopes) {
|
|
555
|
+
const cfg = this.config;
|
|
556
|
+
const token = await this.storage.getToken();
|
|
557
|
+
if (!token) return { valid: false, error: "No token provided" };
|
|
558
|
+
if (!cfg.apiKey && !cfg.clientSecret) return { valid: false, error: "Service credentials not configured" };
|
|
559
|
+
if (!cfg.serviceId) return { valid: false, error: "Service ID not configured" };
|
|
560
|
+
try {
|
|
561
|
+
const result = await this.introspect({ serviceId: cfg.serviceId, token, requiredScopes });
|
|
562
|
+
return {
|
|
563
|
+
valid: result.valid,
|
|
564
|
+
type: result.type === "bearer" ? "token" : "api_key",
|
|
565
|
+
scopes: result.scopes,
|
|
566
|
+
userId: result.userId,
|
|
567
|
+
email: result.email,
|
|
568
|
+
error: result.error
|
|
569
|
+
};
|
|
570
|
+
} catch (error) {
|
|
571
|
+
if (error instanceof ScopeError) {
|
|
572
|
+
return { valid: false, error: error.message, scopes: error.providedScopes };
|
|
573
|
+
}
|
|
574
|
+
if (error instanceof AuthMiError && error.statusCode === 401) {
|
|
575
|
+
return { valid: false, error: "Invalid or expired token" };
|
|
576
|
+
}
|
|
577
|
+
throw error;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
async requireScopes(...requiredScopes) {
|
|
581
|
+
const result = await this.validateWithScopes(requiredScopes);
|
|
582
|
+
if (!result.valid) {
|
|
583
|
+
throw new AuthMiError(result.error || "Authentication required", "UNAUTHORIZED", 401);
|
|
584
|
+
}
|
|
585
|
+
const hasScopes = requiredScopes.every((s) => result.scopes?.includes(s));
|
|
586
|
+
if (!hasScopes) {
|
|
587
|
+
throw new ScopeError(`Required: ${requiredScopes.join(", ")}`, requiredScopes, result.scopes || []);
|
|
588
|
+
}
|
|
589
|
+
return result;
|
|
590
|
+
}
|
|
591
|
+
async withScope(scopes, fn) {
|
|
592
|
+
await this.requireScopes(...scopes);
|
|
593
|
+
return fn();
|
|
594
|
+
}
|
|
595
|
+
// ============ Webhooks ============
|
|
596
|
+
async listWebhooks() {
|
|
597
|
+
const response = await this.request("/api/v1/webhooks/list");
|
|
598
|
+
return response.webhooks;
|
|
599
|
+
}
|
|
600
|
+
async registerWebhook(request) {
|
|
601
|
+
const response = await this.request(
|
|
602
|
+
"/api/v1/webhooks/register",
|
|
603
|
+
{ method: "POST", body: JSON.stringify(request) }
|
|
604
|
+
);
|
|
605
|
+
return { webhookId: response.webhook_id, url: response.url, events: response.events, active: response.active, createdAt: response.created_at };
|
|
606
|
+
}
|
|
607
|
+
async deleteWebhook(webhookId) {
|
|
608
|
+
await this.request("/api/v1/webhooks/delete", {
|
|
522
609
|
method: "POST",
|
|
523
|
-
body: JSON.stringify({
|
|
610
|
+
body: JSON.stringify({ webhook_id: webhookId })
|
|
524
611
|
});
|
|
525
612
|
}
|
|
526
|
-
// ============
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
613
|
+
// ============ Billing ============
|
|
614
|
+
async listPlans() {
|
|
615
|
+
const response = await this.request("/api/v1/billing/plans");
|
|
616
|
+
return response.plans;
|
|
617
|
+
}
|
|
618
|
+
async getSubscription() {
|
|
619
|
+
return this.request("/api/v1/billing/subscription");
|
|
620
|
+
}
|
|
621
|
+
async createCheckout(priceId) {
|
|
622
|
+
return this.request("/api/v1/billing/checkout", {
|
|
623
|
+
method: "POST",
|
|
624
|
+
body: JSON.stringify({ price_id: priceId })
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
async createPortalSession() {
|
|
628
|
+
return this.request("/api/v1/billing/portal", { method: "POST" });
|
|
629
|
+
}
|
|
630
|
+
// ============ Audit ============
|
|
631
|
+
async getAuditLogs(query) {
|
|
632
|
+
const params = new URLSearchParams();
|
|
633
|
+
if (query?.limit) params.set("limit", String(query.limit));
|
|
634
|
+
if (query?.offset) params.set("offset", String(query.offset));
|
|
635
|
+
if (query?.actorId) params.set("actor_id", query.actorId);
|
|
636
|
+
if (query?.action) params.set("action", query.action);
|
|
637
|
+
if (query?.resourceType) params.set("resource_type", query.resourceType);
|
|
638
|
+
if (query?.startDate) params.set("start_date", query.startDate);
|
|
639
|
+
if (query?.endDate) params.set("end_date", query.endDate);
|
|
640
|
+
const qs = params.toString();
|
|
641
|
+
const response = await this.request(
|
|
642
|
+
`/api/v1/audit-logs${qs ? `?${qs}` : ""}`
|
|
643
|
+
);
|
|
644
|
+
return response.logs;
|
|
645
|
+
}
|
|
646
|
+
// ============ Services ============
|
|
647
|
+
async getService(serviceId) {
|
|
648
|
+
return this.request(`/api/v1/services/${encodeURIComponent(serviceId)}`);
|
|
649
|
+
}
|
|
650
|
+
async updateService(serviceId, data) {
|
|
651
|
+
return this.request(`/api/v1/services/${encodeURIComponent(serviceId)}`, {
|
|
652
|
+
method: "POST",
|
|
653
|
+
body: JSON.stringify(data)
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
async rotateClientSecret(serviceId) {
|
|
657
|
+
return this.request(
|
|
658
|
+
`/api/v1/services/${encodeURIComponent(serviceId)}/rotate-secret`,
|
|
659
|
+
{ method: "POST" }
|
|
660
|
+
);
|
|
661
|
+
}
|
|
662
|
+
// ============ JWKS ============
|
|
663
|
+
async getJwks(serviceId) {
|
|
664
|
+
return this.request(`/.well-known/jwks.json?service_id=${encodeURIComponent(serviceId)}`);
|
|
665
|
+
}
|
|
666
|
+
// ============ Onboarding (admin only) ============
|
|
530
667
|
async onboardService(request, masterAdminKey) {
|
|
531
668
|
const response = await this.request("/api/v1/services/onboard", {
|
|
532
669
|
method: "POST",
|
|
@@ -547,9 +684,6 @@ var AuthMiClient = class {
|
|
|
547
684
|
createdAt: response.created_at
|
|
548
685
|
};
|
|
549
686
|
}
|
|
550
|
-
/**
|
|
551
|
-
* Delete a service (requires master admin key)
|
|
552
|
-
*/
|
|
553
687
|
async deleteService(serviceId, masterAdminKey) {
|
|
554
688
|
await this.request("/api/v1/services/deboard", {
|
|
555
689
|
method: "POST",
|
|
@@ -559,49 +693,111 @@ var AuthMiClient = class {
|
|
|
559
693
|
body: JSON.stringify({ service_id: serviceId })
|
|
560
694
|
});
|
|
561
695
|
}
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
696
|
+
};
|
|
697
|
+
|
|
698
|
+
// src/storage/cookie.ts
|
|
699
|
+
var CookieTokenStorage = class _CookieTokenStorage {
|
|
700
|
+
/**
|
|
701
|
+
* @param cookieStr The raw `Cookie` header value (server-side) or `document.cookie` (browser).
|
|
702
|
+
* Pass an empty string in environments where cookies are managed via Set-Cookie.
|
|
703
|
+
*/
|
|
704
|
+
constructor(cookieStr = "", options = {}) {
|
|
705
|
+
this.cookieStr = cookieStr;
|
|
706
|
+
this.options = {
|
|
707
|
+
name: "authmi_token",
|
|
708
|
+
httpOnly: true,
|
|
709
|
+
secure: typeof process !== "undefined" && process.env?.NODE_ENV === "production",
|
|
710
|
+
sameSite: "lax",
|
|
711
|
+
domain: "",
|
|
712
|
+
path: "/",
|
|
713
|
+
maxAge: 86400,
|
|
714
|
+
...options
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
/** Update the raw cookie string (e.g. per-request in server middleware) */
|
|
718
|
+
setCookieString(cookieStr) {
|
|
719
|
+
this.cookieStr = cookieStr;
|
|
720
|
+
}
|
|
721
|
+
/** Escape special regex characters in a string */
|
|
722
|
+
static escapeRegex(s) {
|
|
723
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
724
|
+
}
|
|
725
|
+
getToken() {
|
|
726
|
+
const name = _CookieTokenStorage.escapeRegex(this.options.name);
|
|
727
|
+
const match = this.cookieStr.match(
|
|
728
|
+
new RegExp(`(?:^|;\\s*)${name}=([^;]*)`)
|
|
569
729
|
);
|
|
570
|
-
return
|
|
730
|
+
return match ? decodeURIComponent(match[1]) : null;
|
|
571
731
|
}
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
createdAt: response.created_at
|
|
586
|
-
};
|
|
732
|
+
setToken(token) {
|
|
733
|
+
const { name, httpOnly, secure, sameSite, domain, path, maxAge } = this.options;
|
|
734
|
+
let cookie = `${name}=${encodeURIComponent(token)}; path=${path}; max-age=${maxAge}`;
|
|
735
|
+
if (httpOnly) cookie += "; HttpOnly";
|
|
736
|
+
if (secure) cookie += "; Secure";
|
|
737
|
+
if (sameSite) cookie += `; SameSite=${sameSite}`;
|
|
738
|
+
if (domain) cookie += `; domain=${domain}`;
|
|
739
|
+
this.lastSetCookie = cookie;
|
|
740
|
+
this.cookieStr = `${name}=${encodeURIComponent(token)}`;
|
|
741
|
+
}
|
|
742
|
+
/** The last Set-Cookie header generated by setToken() or removeToken() */
|
|
743
|
+
getSetCookieHeader() {
|
|
744
|
+
return this.lastSetCookie;
|
|
587
745
|
}
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
746
|
+
removeToken() {
|
|
747
|
+
const { name, domain, path } = this.options;
|
|
748
|
+
let cookie = `${name}=; path=${path}; max-age=0`;
|
|
749
|
+
if (domain) cookie += `; domain=${domain}`;
|
|
750
|
+
this.lastSetCookie = cookie;
|
|
751
|
+
this.cookieStr = "";
|
|
752
|
+
}
|
|
753
|
+
};
|
|
754
|
+
|
|
755
|
+
// src/cache.ts
|
|
756
|
+
var IntrospectCache = class {
|
|
757
|
+
constructor(ttlMs = 6e4) {
|
|
758
|
+
this.store = /* @__PURE__ */ new Map();
|
|
759
|
+
this.defaultTtlMs = ttlMs;
|
|
760
|
+
}
|
|
761
|
+
/** Build a cache key from the token (simple hash) */
|
|
762
|
+
key(token) {
|
|
763
|
+
let hash = 0;
|
|
764
|
+
for (let i = 0; i < token.length; i++) {
|
|
765
|
+
hash = hash * 31 + token.charCodeAt(i) | 0;
|
|
766
|
+
}
|
|
767
|
+
return `tok:${hash.toString(36)}`;
|
|
768
|
+
}
|
|
769
|
+
get(token) {
|
|
770
|
+
const entry = this.store.get(this.key(token));
|
|
771
|
+
if (!entry) return void 0;
|
|
772
|
+
if (Date.now() > entry.expiresAt) {
|
|
773
|
+
this.store.delete(this.key(token));
|
|
774
|
+
return void 0;
|
|
775
|
+
}
|
|
776
|
+
return entry.result;
|
|
777
|
+
}
|
|
778
|
+
set(token, result, ttlMs) {
|
|
779
|
+
if (!result.valid) return;
|
|
780
|
+
this.store.set(this.key(token), {
|
|
781
|
+
result,
|
|
782
|
+
expiresAt: Date.now() + (ttlMs ?? this.defaultTtlMs)
|
|
595
783
|
});
|
|
596
784
|
}
|
|
785
|
+
invalidate(token) {
|
|
786
|
+
this.store.delete(this.key(token));
|
|
787
|
+
}
|
|
788
|
+
clear() {
|
|
789
|
+
this.store.clear();
|
|
790
|
+
}
|
|
597
791
|
};
|
|
598
792
|
|
|
599
793
|
// src/index.ts
|
|
600
|
-
var VERSION = "1.
|
|
794
|
+
var VERSION = "1.1.0";
|
|
601
795
|
// Annotate the CommonJS export names for ESM import in node:
|
|
602
796
|
0 && (module.exports = {
|
|
603
797
|
AuthMiClient,
|
|
604
798
|
AuthMiError,
|
|
799
|
+
CookieTokenStorage,
|
|
800
|
+
IntrospectCache,
|
|
605
801
|
LocalStorageTokenStorage,
|
|
606
802
|
MemoryTokenStorage,
|
|
607
803
|
ScopeError,
|