@qyh213/easyauth-client 1.0.0-beta.1

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.
@@ -0,0 +1,495 @@
1
+ // src/types.ts
2
+ var EasyAuthError = class extends Error {
3
+ constructor(message, code, statusCode) {
4
+ super(message);
5
+ this.code = code;
6
+ this.statusCode = statusCode;
7
+ this.name = "EasyAuthError";
8
+ }
9
+ };
10
+ var ScopeError = class extends EasyAuthError {
11
+ constructor(message, requiredScopes, providedScopes) {
12
+ super(message, "INSUFFICIENT_SCOPES", 403);
13
+ this.requiredScopes = requiredScopes;
14
+ this.providedScopes = providedScopes;
15
+ this.name = "ScopeError";
16
+ }
17
+ };
18
+ var _LocalStorageTokenStorage = class _LocalStorageTokenStorage {
19
+ getToken() {
20
+ if (typeof window === "undefined") return null;
21
+ return localStorage.getItem(_LocalStorageTokenStorage.KEY);
22
+ }
23
+ setToken(token) {
24
+ if (typeof window === "undefined") return;
25
+ localStorage.setItem(_LocalStorageTokenStorage.KEY, token);
26
+ }
27
+ removeToken() {
28
+ if (typeof window === "undefined") return;
29
+ localStorage.removeItem(_LocalStorageTokenStorage.KEY);
30
+ }
31
+ };
32
+ _LocalStorageTokenStorage.KEY = "easy_auth_token";
33
+ var LocalStorageTokenStorage = _LocalStorageTokenStorage;
34
+ var MemoryTokenStorage = class {
35
+ constructor() {
36
+ this.token = null;
37
+ }
38
+ getToken() {
39
+ return this.token;
40
+ }
41
+ setToken(token) {
42
+ this.token = token;
43
+ }
44
+ removeToken() {
45
+ this.token = null;
46
+ }
47
+ };
48
+
49
+ // src/client.ts
50
+ var EasyAuthClient = class {
51
+ constructor(config, tokenStorage) {
52
+ this.config = {
53
+ apiKey: "",
54
+ defaultTokenExpiry: 24,
55
+ ...config
56
+ };
57
+ this.tokenStorage = tokenStorage || new LocalStorageTokenStorage();
58
+ }
59
+ // ============ HTTP Utilities ============
60
+ async request(endpoint, options = {}, requireAuth = false) {
61
+ const url = `${this.config.baseUrl}${endpoint}`;
62
+ const headers = {
63
+ "Content-Type": "application/json",
64
+ ...options.headers || {}
65
+ };
66
+ if (this.config.apiKey && !requireAuth) {
67
+ headers["Authorization"] = `Bearer ${this.config.apiKey}`;
68
+ }
69
+ if (requireAuth) {
70
+ const token = this.getAccessToken();
71
+ if (token) {
72
+ headers["Authorization"] = `Bearer ${token}`;
73
+ }
74
+ }
75
+ try {
76
+ const response = await fetch(url, {
77
+ ...options,
78
+ headers
79
+ });
80
+ const data = await response.json();
81
+ if (!response.ok) {
82
+ if (response.status === 403 && data.required && data.provided) {
83
+ throw new ScopeError(
84
+ data.error || "Insufficient scopes",
85
+ data.required,
86
+ data.provided
87
+ );
88
+ }
89
+ throw new EasyAuthError(
90
+ data.error || `HTTP ${response.status}`,
91
+ data.code || "UNKNOWN_ERROR",
92
+ response.status
93
+ );
94
+ }
95
+ return data;
96
+ } catch (error) {
97
+ if (error instanceof EasyAuthError) {
98
+ throw error;
99
+ }
100
+ throw new EasyAuthError(
101
+ error instanceof Error ? error.message : "Network error",
102
+ "NETWORK_ERROR"
103
+ );
104
+ }
105
+ }
106
+ // ============ Configuration ============
107
+ /**
108
+ * Update the client's service configuration
109
+ */
110
+ configure(config) {
111
+ this.config = { ...this.config, ...config };
112
+ }
113
+ /**
114
+ * Get current configuration (without sensitive data)
115
+ */
116
+ getConfig() {
117
+ const { apiKey: _, ...rest } = this.config;
118
+ return rest;
119
+ }
120
+ // ============ Token Management ============
121
+ /**
122
+ * Get the stored access token
123
+ */
124
+ getAccessToken() {
125
+ return this.tokenStorage.getToken();
126
+ }
127
+ /**
128
+ * Store an access token
129
+ */
130
+ setAccessToken(token) {
131
+ this.tokenStorage.setToken(token);
132
+ }
133
+ /**
134
+ * Remove the stored access token (logout)
135
+ */
136
+ clearAccessToken() {
137
+ this.tokenStorage.removeToken();
138
+ }
139
+ /**
140
+ * Check if user is logged in
141
+ */
142
+ isLoggedIn() {
143
+ return !!this.getAccessToken();
144
+ }
145
+ // ============ Scope Validation ============
146
+ /**
147
+ * Introspect a token or API key and validate required scopes
148
+ */
149
+ async introspect(request) {
150
+ return this.request("/api/v1/introspect", {
151
+ method: "POST",
152
+ body: JSON.stringify(request)
153
+ });
154
+ }
155
+ /**
156
+ * Validate the current token with optional scope checking
157
+ */
158
+ async validateWithScopes(requiredScopes) {
159
+ const token = this.getAccessToken();
160
+ if (!token) {
161
+ return { valid: false, error: "No token provided" };
162
+ }
163
+ if (!this.config.apiKey) {
164
+ return { valid: false, error: "Service API key not configured" };
165
+ }
166
+ try {
167
+ const serviceId = this.config.apiKey.split(".")[0];
168
+ const result = await this.introspect({
169
+ serviceId,
170
+ token,
171
+ requiredScopes
172
+ });
173
+ return {
174
+ valid: result.valid,
175
+ type: result.type === "bearer" ? "token" : "api_key",
176
+ scopes: result.scopes,
177
+ userId: result.userId,
178
+ email: result.email,
179
+ error: result.error
180
+ };
181
+ } catch (error) {
182
+ if (error instanceof ScopeError) {
183
+ return {
184
+ valid: false,
185
+ error: error.message,
186
+ scopes: error.providedScopes
187
+ };
188
+ }
189
+ if (error instanceof EasyAuthError && error.statusCode === 401) {
190
+ return { valid: false, error: "Invalid or expired token" };
191
+ }
192
+ throw error;
193
+ }
194
+ }
195
+ /**
196
+ * Check if the current user has all the required scopes
197
+ * Throws ScopeError if scopes are insufficient
198
+ */
199
+ async requireScopes(...requiredScopes) {
200
+ const result = await this.validateWithScopes(requiredScopes);
201
+ if (!result.valid) {
202
+ throw new EasyAuthError(
203
+ result.error || "Authentication required",
204
+ "UNAUTHORIZED",
205
+ 401
206
+ );
207
+ }
208
+ const hasScopes = requiredScopes.every(
209
+ (scope) => result.scopes?.includes(scope)
210
+ );
211
+ if (!hasScopes) {
212
+ throw new ScopeError(
213
+ `Required scopes: ${requiredScopes.join(", ")}`,
214
+ requiredScopes,
215
+ result.scopes || []
216
+ );
217
+ }
218
+ return result;
219
+ }
220
+ /**
221
+ * Execute a function only if the user has the required scopes
222
+ */
223
+ async withScope(scopes, fn) {
224
+ await this.requireScopes(...scopes);
225
+ return fn();
226
+ }
227
+ // ============ Authentication ============
228
+ /**
229
+ * Login a user and store the access token
230
+ */
231
+ async login(credentials) {
232
+ if (!this.config.apiKey) {
233
+ throw new EasyAuthError(
234
+ "API key required for login. Configure the client first.",
235
+ "MISSING_API_KEY"
236
+ );
237
+ }
238
+ const response = await this.request("/api/v1/auth/login", {
239
+ method: "POST",
240
+ body: JSON.stringify({
241
+ email: credentials.email,
242
+ password: credentials.password,
243
+ expires_in_hours: credentials.expiresInHours || this.config.defaultTokenExpiry,
244
+ scopes: credentials.scopes
245
+ })
246
+ });
247
+ const token = {
248
+ accessToken: response.access_token,
249
+ tokenType: response.token_type,
250
+ expiresIn: response.expires_in,
251
+ expiresAt: response.expires_at,
252
+ userId: response.user_id,
253
+ email: response.email,
254
+ scopes: response.scopes
255
+ };
256
+ this.setAccessToken(token.accessToken);
257
+ return token;
258
+ }
259
+ /**
260
+ * Logout the current user
261
+ */
262
+ async logout() {
263
+ const token = this.getAccessToken();
264
+ if (token) {
265
+ try {
266
+ await this.request("/api/v1/auth/logout", {
267
+ method: "POST"
268
+ });
269
+ } catch (error) {
270
+ }
271
+ }
272
+ this.clearAccessToken();
273
+ }
274
+ /**
275
+ * Validate the current token or an API key
276
+ * @deprecated Use validateWithScopes instead
277
+ */
278
+ async validate(_credential) {
279
+ return this.validateWithScopes();
280
+ }
281
+ /**
282
+ * Refresh the current token
283
+ */
284
+ async refreshToken() {
285
+ const response = await this.request("/api/v1/auth/refresh", {
286
+ method: "POST"
287
+ }, true);
288
+ const token = {
289
+ accessToken: response.access_token,
290
+ tokenType: response.token_type,
291
+ expiresIn: response.expires_in,
292
+ expiresAt: response.expires_at,
293
+ userId: "",
294
+ // Refresh doesn't return user info
295
+ email: ""
296
+ };
297
+ this.setAccessToken(token.accessToken);
298
+ return token;
299
+ }
300
+ // ============ User Management ============
301
+ /**
302
+ * Create a new user (requires admin API key)
303
+ */
304
+ async createUser(request) {
305
+ const response = await this.request("/api/v1/users/create", {
306
+ method: "POST",
307
+ body: JSON.stringify(request)
308
+ });
309
+ return {
310
+ userId: response.user_id,
311
+ email: response.email,
312
+ createdAt: response.created_at
313
+ };
314
+ }
315
+ /**
316
+ * List all users for the service
317
+ */
318
+ async listUsers() {
319
+ const response = await this.request("/api/v1/users/list");
320
+ return response.users;
321
+ }
322
+ /**
323
+ * Delete a user
324
+ */
325
+ async deleteUser(userId) {
326
+ await this.request("/api/v1/users/delete", {
327
+ method: "POST",
328
+ body: JSON.stringify({ user_id: userId })
329
+ });
330
+ }
331
+ /**
332
+ * Update user password
333
+ */
334
+ async updatePassword(userId, oldPassword, newPassword) {
335
+ await this.request("/api/v1/users/password", {
336
+ method: "POST",
337
+ body: JSON.stringify({
338
+ user_id: userId,
339
+ old_password: oldPassword,
340
+ new_password: newPassword
341
+ })
342
+ });
343
+ }
344
+ // ============ Scope Management ============
345
+ /**
346
+ * List custom scopes for the service
347
+ */
348
+ async listScopes() {
349
+ const response = await this.request("/api/v1/scopes/list");
350
+ return response.scopes;
351
+ }
352
+ /**
353
+ * Create a custom scope
354
+ */
355
+ async createScope(request) {
356
+ const response = await this.request("/api/v1/scopes/create", {
357
+ method: "POST",
358
+ body: JSON.stringify(request)
359
+ });
360
+ return {
361
+ scopeId: response.scope_id,
362
+ name: response.name,
363
+ key: response.key,
364
+ description: response.description,
365
+ createdAt: response.created_at
366
+ };
367
+ }
368
+ /**
369
+ * Delete a custom scope
370
+ */
371
+ async deleteScope(scopeId) {
372
+ await this.request("/api/v1/scopes/delete", {
373
+ method: "POST",
374
+ body: JSON.stringify({ scope_id: scopeId })
375
+ });
376
+ }
377
+ // ============ API Key Management ============
378
+ /**
379
+ * List all API keys for the service
380
+ */
381
+ async listApiKeys() {
382
+ const response = await this.request("/api/v1/keys/list");
383
+ return response.apiKeys;
384
+ }
385
+ /**
386
+ * Create a new API key with optional custom scopes
387
+ */
388
+ async createApiKey(request) {
389
+ const response = await this.request("/api/v1/keys/create", {
390
+ method: "POST",
391
+ body: JSON.stringify({
392
+ name: request.name,
393
+ scope: request.scope || "service",
394
+ scopes: request.scopes,
395
+ key_type: request.keyType || "service",
396
+ tps_limit: request.tpsLimit || 100
397
+ })
398
+ });
399
+ return {
400
+ keyId: response.key_id,
401
+ apiKey: response.api_key,
402
+ name: response.name,
403
+ scope: response.scope,
404
+ scopes: response.scopes
405
+ };
406
+ }
407
+ /**
408
+ * Revoke an API key
409
+ */
410
+ async revokeApiKey(keyId) {
411
+ await this.request("/api/v1/keys/revoke", {
412
+ method: "POST",
413
+ body: JSON.stringify({ key_id: keyId })
414
+ });
415
+ }
416
+ // ============ Service Management ============
417
+ /**
418
+ * Onboard a new service (requires master admin key)
419
+ */
420
+ async onboardService(request, masterAdminKey) {
421
+ const response = await this.request("/api/v1/services/onboard", {
422
+ method: "POST",
423
+ headers: {
424
+ "x-onboarding-admin-key": masterAdminKey
425
+ },
426
+ body: JSON.stringify({
427
+ service_name: request.serviceName,
428
+ owner_email: request.ownerEmail
429
+ })
430
+ });
431
+ return {
432
+ serviceId: response.service_id,
433
+ serviceName: response.service_name,
434
+ ownerEmail: response.owner_email,
435
+ adminKeyId: response.admin_key_id,
436
+ serviceAdminApiKey: response.service_admin_api_key,
437
+ createdAt: response.created_at
438
+ };
439
+ }
440
+ /**
441
+ * Delete a service (requires master admin key)
442
+ */
443
+ async deleteService(serviceId, masterAdminKey) {
444
+ await this.request("/api/v1/services/deboard", {
445
+ method: "POST",
446
+ headers: {
447
+ "x-onboarding-admin-key": masterAdminKey
448
+ },
449
+ body: JSON.stringify({ service_id: serviceId })
450
+ });
451
+ }
452
+ // ============ Webhook Management ============
453
+ /**
454
+ * List webhooks
455
+ */
456
+ async listWebhooks() {
457
+ const response = await this.request(
458
+ "/api/v1/webhooks/list"
459
+ );
460
+ return response.webhooks;
461
+ }
462
+ /**
463
+ * Register a webhook
464
+ */
465
+ async registerWebhook(request) {
466
+ const response = await this.request("/api/v1/webhooks/register", {
467
+ method: "POST",
468
+ body: JSON.stringify(request)
469
+ });
470
+ return {
471
+ webhookId: response.webhook_id,
472
+ url: response.url,
473
+ events: response.events,
474
+ active: response.active,
475
+ createdAt: response.created_at
476
+ };
477
+ }
478
+ /**
479
+ * Delete a webhook
480
+ */
481
+ async deleteWebhook(webhookId) {
482
+ await this.request("/api/v1/webhooks/delete", {
483
+ method: "POST",
484
+ body: JSON.stringify({ webhook_id: webhookId })
485
+ });
486
+ }
487
+ };
488
+
489
+ export {
490
+ EasyAuthError,
491
+ ScopeError,
492
+ LocalStorageTokenStorage,
493
+ MemoryTokenStorage,
494
+ EasyAuthClient
495
+ };