@classic-homes/auth 0.1.24 → 0.1.26

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,558 @@
1
+ import { getConfig, getStorage, getFetch } from './chunk-DCGC6CNV.js';
2
+
3
+ // src/core/client.ts
4
+ var isRefreshing = false;
5
+ var refreshSubscribers = [];
6
+ function subscribeTokenRefresh(cb) {
7
+ refreshSubscribers.push(cb);
8
+ }
9
+ function onTokenRefreshed(token) {
10
+ refreshSubscribers.forEach((cb) => cb(token));
11
+ refreshSubscribers = [];
12
+ }
13
+ function getAccessToken() {
14
+ const config = getConfig();
15
+ const storage = getStorage();
16
+ const authData = storage.getItem(config.storageKey ?? "classic_auth");
17
+ if (!authData) return null;
18
+ try {
19
+ const parsed = JSON.parse(authData);
20
+ return parsed.accessToken ?? null;
21
+ } catch {
22
+ return null;
23
+ }
24
+ }
25
+ function getRefreshToken() {
26
+ const config = getConfig();
27
+ const storage = getStorage();
28
+ const authData = storage.getItem(config.storageKey ?? "classic_auth");
29
+ if (!authData) return null;
30
+ try {
31
+ const parsed = JSON.parse(authData);
32
+ return parsed.refreshToken ?? null;
33
+ } catch {
34
+ return null;
35
+ }
36
+ }
37
+ function getSessionToken() {
38
+ const storage = getStorage();
39
+ return storage.getItem("sessionToken");
40
+ }
41
+ function updateStoredTokens(accessToken, refreshToken) {
42
+ const config = getConfig();
43
+ const storage = getStorage();
44
+ const storageKey = config.storageKey ?? "classic_auth";
45
+ const authData = storage.getItem(storageKey);
46
+ let parsed = {};
47
+ if (authData) {
48
+ try {
49
+ parsed = JSON.parse(authData);
50
+ } catch {
51
+ }
52
+ }
53
+ parsed.accessToken = accessToken;
54
+ parsed.refreshToken = refreshToken;
55
+ storage.setItem(storageKey, JSON.stringify(parsed));
56
+ import('./auth.svelte-PKMR4LYT.js').then(({ authStore }) => {
57
+ authStore.updateTokens(accessToken, refreshToken);
58
+ }).catch(() => {
59
+ });
60
+ config.onTokenRefresh?.({ accessToken, refreshToken });
61
+ }
62
+ function clearStoredAuth() {
63
+ const config = getConfig();
64
+ const storage = getStorage();
65
+ storage.removeItem(config.storageKey ?? "classic_auth");
66
+ storage.removeItem("sessionToken");
67
+ }
68
+ async function refreshAccessToken() {
69
+ const refreshToken = getRefreshToken();
70
+ const config = getConfig();
71
+ const fetchFn = getFetch();
72
+ if (!refreshToken) {
73
+ return false;
74
+ }
75
+ try {
76
+ const response = await fetchFn(`${config.baseUrl}/auth/refresh`, {
77
+ method: "POST",
78
+ headers: {
79
+ "Content-Type": "application/json"
80
+ },
81
+ body: JSON.stringify({ refreshToken })
82
+ });
83
+ if (response.ok) {
84
+ const apiResponse = await response.json();
85
+ const data = apiResponse.data;
86
+ const tokens = data && typeof data === "object" && "data" in data ? data.data : data;
87
+ if (tokens?.accessToken && tokens?.refreshToken) {
88
+ updateStoredTokens(tokens.accessToken, tokens.refreshToken);
89
+ onTokenRefreshed(tokens.accessToken);
90
+ return true;
91
+ }
92
+ }
93
+ } catch (error) {
94
+ config.onAuthError?.(error instanceof Error ? error : new Error("Token refresh failed"));
95
+ }
96
+ return false;
97
+ }
98
+ function extractData(response) {
99
+ if (response && typeof response === "object" && "data" in response) {
100
+ const dataField = response.data;
101
+ if (dataField && typeof dataField === "object" && "data" in dataField) {
102
+ return dataField.data;
103
+ }
104
+ if (dataField !== void 0) {
105
+ return dataField;
106
+ }
107
+ }
108
+ return response;
109
+ }
110
+ async function apiRequest(endpoint, options = {}) {
111
+ const { authenticated = false, customFetch, body, ...fetchOptions } = options;
112
+ const config = getConfig();
113
+ const fetchFn = customFetch ?? getFetch();
114
+ const url = endpoint.startsWith("http") ? endpoint : `${config.baseUrl}${endpoint}`;
115
+ const headers = {
116
+ "Content-Type": "application/json",
117
+ ...fetchOptions.headers
118
+ };
119
+ if (authenticated) {
120
+ const accessToken = getAccessToken();
121
+ const sessionToken = getSessionToken();
122
+ if (accessToken) {
123
+ headers["Authorization"] = `Bearer ${accessToken}`;
124
+ }
125
+ if (sessionToken) {
126
+ headers["X-Session-Token"] = sessionToken;
127
+ }
128
+ }
129
+ try {
130
+ const response = await fetchFn(url, {
131
+ ...fetchOptions,
132
+ headers,
133
+ body: body ? JSON.stringify(body) : void 0
134
+ });
135
+ if (response.status === 401 && authenticated && typeof window !== "undefined") {
136
+ if (!isRefreshing) {
137
+ isRefreshing = true;
138
+ const refreshed = await refreshAccessToken();
139
+ isRefreshing = false;
140
+ if (refreshed) {
141
+ return apiRequest(endpoint, options);
142
+ } else {
143
+ clearStoredAuth();
144
+ config.onLogout?.();
145
+ throw new Error("Authentication failed. Please sign in again.");
146
+ }
147
+ } else {
148
+ return new Promise((resolve, reject) => {
149
+ subscribeTokenRefresh(() => {
150
+ apiRequest(endpoint, options).then(resolve).catch(reject);
151
+ });
152
+ });
153
+ }
154
+ }
155
+ const data = await response.json();
156
+ if (!response.ok) {
157
+ const dataObj = data;
158
+ const errorMessage = data.error?.message || (typeof dataObj.message === "string" ? dataObj.message : void 0) || data.meta?.message || (typeof dataObj.detail === "string" ? dataObj.detail : void 0) || "Request failed";
159
+ const error = new Error(errorMessage);
160
+ config.onAuthError?.(error);
161
+ throw error;
162
+ }
163
+ return data;
164
+ } catch (error) {
165
+ if (error instanceof Error) {
166
+ throw error;
167
+ }
168
+ throw new Error("API request failed");
169
+ }
170
+ }
171
+ var api = {
172
+ get: (endpoint, authenticated = false, customFetch) => apiRequest(endpoint, { method: "GET", authenticated, customFetch }),
173
+ post: (endpoint, body, authenticated = false, customFetch) => apiRequest(endpoint, {
174
+ method: "POST",
175
+ body,
176
+ authenticated,
177
+ customFetch
178
+ }),
179
+ put: (endpoint, body, authenticated = false, customFetch) => apiRequest(endpoint, {
180
+ method: "PUT",
181
+ body,
182
+ authenticated,
183
+ customFetch
184
+ }),
185
+ patch: (endpoint, body, authenticated = false, customFetch) => apiRequest(endpoint, {
186
+ method: "PATCH",
187
+ body,
188
+ authenticated,
189
+ customFetch
190
+ }),
191
+ delete: (endpoint, authenticated = false, customFetch, body) => apiRequest(endpoint, {
192
+ method: "DELETE",
193
+ body,
194
+ authenticated,
195
+ customFetch
196
+ })
197
+ };
198
+
199
+ // src/core/api.ts
200
+ var authApi = {
201
+ // ============================================================================
202
+ // Authentication
203
+ // ============================================================================
204
+ /**
205
+ * Login with username and password.
206
+ */
207
+ async login(credentials) {
208
+ const response = await api.post("/auth/login", credentials);
209
+ return extractData(response);
210
+ },
211
+ /**
212
+ * Logout the current user.
213
+ * Returns SSO logout URL if applicable for SSO users.
214
+ */
215
+ async logout() {
216
+ try {
217
+ const response = await api.post("/auth/logout", {}, true);
218
+ return extractData(response);
219
+ } catch {
220
+ return { success: true };
221
+ }
222
+ },
223
+ /**
224
+ * Register a new user.
225
+ */
226
+ async register(data) {
227
+ const response = await api.post("/auth/register", data);
228
+ return extractData(response);
229
+ },
230
+ /**
231
+ * Request a password reset email.
232
+ */
233
+ async forgotPassword(email) {
234
+ await api.post("/auth/forgot-password", { email });
235
+ },
236
+ /**
237
+ * Reset password with a token.
238
+ */
239
+ async resetPassword(token, newPassword) {
240
+ await api.post("/auth/reset-password", { token, newPassword });
241
+ },
242
+ /**
243
+ * Refresh the access token.
244
+ */
245
+ async refreshToken(refreshToken) {
246
+ const response = await api.post("/auth/refresh", { refreshToken });
247
+ return extractData(response);
248
+ },
249
+ /**
250
+ * Initiate SSO login by redirecting to the SSO provider.
251
+ * @param options.callbackUrl - The URL where the SSO provider should redirect after auth
252
+ * @param options.redirectUrl - The final URL to redirect to after processing the callback
253
+ */
254
+ initiateSSOLogin(options) {
255
+ if (typeof window === "undefined") return;
256
+ const config = getConfig();
257
+ const authorizeUrl = config.sso?.authorizeUrl ?? `${config.baseUrl}/auth/sso/authorize`;
258
+ const params = new URLSearchParams();
259
+ if (options?.callbackUrl) {
260
+ params.set("callback", options.callbackUrl);
261
+ }
262
+ if (options?.redirectUrl) {
263
+ params.set("redirect", options.redirectUrl);
264
+ }
265
+ const url = params.toString() ? `${authorizeUrl}?${params}` : authorizeUrl;
266
+ window.location.href = url;
267
+ },
268
+ // ============================================================================
269
+ // Profile
270
+ // ============================================================================
271
+ /**
272
+ * Get the current user's profile.
273
+ */
274
+ async getProfile(customFetch) {
275
+ const response = await api.get("/auth/profile", true, customFetch);
276
+ return response;
277
+ },
278
+ /**
279
+ * Update the current user's profile.
280
+ */
281
+ async updateProfile(data) {
282
+ const response = await api.put("/auth/profile", data, true);
283
+ return extractData(response);
284
+ },
285
+ /**
286
+ * Change the current user's password.
287
+ */
288
+ async changePassword(data) {
289
+ await api.put("/auth/change-password", data, true);
290
+ },
291
+ // ============================================================================
292
+ // Email Verification
293
+ // ============================================================================
294
+ /**
295
+ * Resend email verification.
296
+ */
297
+ async resendVerification() {
298
+ await api.post("/auth/resend-verification", {}, true);
299
+ },
300
+ /**
301
+ * Verify email with a token.
302
+ */
303
+ async verifyEmail(token) {
304
+ const response = await api.post("/auth/verify-email", {
305
+ token
306
+ });
307
+ return extractData(response);
308
+ },
309
+ // ============================================================================
310
+ // Session Management
311
+ // ============================================================================
312
+ /**
313
+ * Get all active sessions.
314
+ */
315
+ async getSessions(customFetch) {
316
+ const response = await api.get(
317
+ "/auth/sessions",
318
+ true,
319
+ customFetch
320
+ );
321
+ return response;
322
+ },
323
+ /**
324
+ * Revoke a specific session.
325
+ */
326
+ async revokeSession(sessionId) {
327
+ await api.delete(`/auth/sessions/${sessionId}`, true);
328
+ },
329
+ /**
330
+ * Revoke all sessions except the current one.
331
+ */
332
+ async revokeAllSessions() {
333
+ await api.delete("/auth/sessions", true);
334
+ },
335
+ // ============================================================================
336
+ // API Key Management
337
+ // ============================================================================
338
+ /**
339
+ * Get all API keys.
340
+ */
341
+ async getApiKeys(customFetch) {
342
+ const response = await api.get("/auth/api-keys", true, customFetch);
343
+ return response;
344
+ },
345
+ /**
346
+ * Create a new API key.
347
+ */
348
+ async createApiKey(data) {
349
+ const response = await api.post("/auth/api-keys", data, true);
350
+ return extractData(response);
351
+ },
352
+ /**
353
+ * Revoke an API key.
354
+ */
355
+ async revokeApiKey(keyId) {
356
+ await api.delete(`/auth/api-keys/${keyId}`, true);
357
+ },
358
+ /**
359
+ * Update an API key's name.
360
+ */
361
+ async updateApiKey(keyId, data) {
362
+ await api.put(`/auth/api-keys/${keyId}`, data, true);
363
+ },
364
+ // ============================================================================
365
+ // MFA Management
366
+ // ============================================================================
367
+ /**
368
+ * Get MFA status for the current user.
369
+ */
370
+ async getMFAStatus() {
371
+ const response = await api.get("/auth/mfa/status", true);
372
+ return response;
373
+ },
374
+ /**
375
+ * Setup MFA (get QR code and backup codes).
376
+ */
377
+ async setupMFA() {
378
+ const response = await api.post("/auth/mfa-setup", {}, true);
379
+ return extractData(response);
380
+ },
381
+ /**
382
+ * Verify MFA setup with a code.
383
+ */
384
+ async verifyMFASetup(code) {
385
+ await api.post("/auth/mfa/verify", { code }, true);
386
+ },
387
+ /**
388
+ * Disable MFA.
389
+ */
390
+ async disableMFA(password) {
391
+ await api.delete("/auth/mfa", true, void 0, { password });
392
+ },
393
+ /**
394
+ * Regenerate MFA backup codes.
395
+ */
396
+ async regenerateBackupCodes(password) {
397
+ const response = await api.post(
398
+ "/auth/mfa/regenerate-backup-codes",
399
+ { password },
400
+ true
401
+ );
402
+ return extractData(response);
403
+ },
404
+ /**
405
+ * Verify MFA challenge during login.
406
+ */
407
+ async verifyMFAChallenge(data) {
408
+ const config = getConfig();
409
+ const fetchFn = config.fetch ?? fetch;
410
+ const requestBody = {
411
+ code: data.code,
412
+ type: data.method === "backup" ? "backup" : "totp",
413
+ trustDevice: data.trustDevice
414
+ };
415
+ const response = await fetchFn(`${config.baseUrl}/auth/mfa/challenge`, {
416
+ method: "POST",
417
+ headers: {
418
+ "Content-Type": "application/json",
419
+ Authorization: `Bearer ${data.mfaToken}`
420
+ },
421
+ body: JSON.stringify(requestBody)
422
+ });
423
+ if (!response.ok) {
424
+ const error = await response.json().catch(() => ({ message: "Verification failed" }));
425
+ throw new Error(error.error?.message || error.message || "Invalid verification code");
426
+ }
427
+ const result = await response.json();
428
+ return extractData(result);
429
+ },
430
+ // ============================================================================
431
+ // Device Management
432
+ // ============================================================================
433
+ /**
434
+ * Get all devices.
435
+ */
436
+ async getDevices(customFetch) {
437
+ const response = await api.get(
438
+ "/auth/devices",
439
+ true,
440
+ customFetch
441
+ );
442
+ return response;
443
+ },
444
+ /**
445
+ * Trust a device.
446
+ */
447
+ async trustDevice(deviceId) {
448
+ await api.put(`/auth/devices/${deviceId}/trust`, {}, true);
449
+ },
450
+ /**
451
+ * Revoke device trust.
452
+ */
453
+ async revokeDevice(deviceId) {
454
+ await api.delete(`/auth/devices/${deviceId}/revoke`, true);
455
+ },
456
+ /**
457
+ * Remove a device completely.
458
+ */
459
+ async removeDevice(deviceId) {
460
+ await api.delete(`/auth/devices/${deviceId}`, true);
461
+ },
462
+ /**
463
+ * Approve a device with a token.
464
+ */
465
+ async approveDevice(token) {
466
+ const response = await api.post("/auth/device/approve", {
467
+ token
468
+ });
469
+ return extractData(response);
470
+ },
471
+ /**
472
+ * Block a device with a token.
473
+ */
474
+ async blockDevice(token) {
475
+ const response = await api.post("/auth/device/block", {
476
+ token
477
+ });
478
+ return extractData(response);
479
+ },
480
+ // ============================================================================
481
+ // Preferences
482
+ // ============================================================================
483
+ /**
484
+ * Get user preferences.
485
+ */
486
+ async getPreferences(customFetch) {
487
+ const response = await api.get(
488
+ "/auth/preferences",
489
+ true,
490
+ customFetch
491
+ );
492
+ if (response && typeof response === "object" && "preferences" in response) {
493
+ return response.preferences;
494
+ }
495
+ return response ?? {};
496
+ },
497
+ /**
498
+ * Update user preferences.
499
+ */
500
+ async updatePreferences(data) {
501
+ await api.put("/auth/preferences", data, true);
502
+ },
503
+ // ============================================================================
504
+ // SSO / Linked Accounts
505
+ // ============================================================================
506
+ /**
507
+ * Get SSO linked accounts.
508
+ */
509
+ async getSSOAccounts(customFetch) {
510
+ const response = await api.get(
511
+ "/auth/sso/accounts",
512
+ true,
513
+ customFetch
514
+ );
515
+ if (response && typeof response === "object" && "accounts" in response) {
516
+ return response.accounts;
517
+ }
518
+ return Array.isArray(response) ? response : [];
519
+ },
520
+ /**
521
+ * Unlink an SSO account.
522
+ */
523
+ async unlinkSSOAccount(provider, password) {
524
+ await api.delete("/auth/sso/unlink", true, void 0, { provider, password });
525
+ },
526
+ /**
527
+ * Link an SSO account (redirects to SSO provider).
528
+ */
529
+ async linkSSOAccount(provider = "authentik") {
530
+ if (typeof window === "undefined") return;
531
+ const accessToken = getAccessToken();
532
+ if (!accessToken) {
533
+ throw new Error("Not authenticated");
534
+ }
535
+ const config = getConfig();
536
+ const params = new URLSearchParams({
537
+ token: accessToken,
538
+ provider
539
+ });
540
+ window.location.href = `${config.baseUrl}/auth/sso/link?${params.toString()}`;
541
+ },
542
+ // ============================================================================
543
+ // Security Events
544
+ // ============================================================================
545
+ /**
546
+ * Get security event history.
547
+ */
548
+ async getSecurityEvents(params, customFetch) {
549
+ const queryParams = new URLSearchParams();
550
+ if (params?.page) queryParams.append("page", params.page.toString());
551
+ if (params?.limit) queryParams.append("limit", params.limit.toString());
552
+ if (params?.type) queryParams.append("type", params.type);
553
+ const response = await api.get(`/auth/security/events?${queryParams.toString()}`, true, customFetch);
554
+ return extractData(response);
555
+ }
556
+ };
557
+
558
+ export { api, apiRequest, authApi, clearStoredAuth, extractData, getAccessToken, getRefreshToken, getSessionToken, updateStoredTokens };