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