@classic-homes/auth 0.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 +255 -0
- package/dist/core/index.d.ts +631 -0
- package/dist/core/index.js +887 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1197 -0
- package/dist/svelte/index.d.ts +212 -0
- package/dist/svelte/index.js +341 -0
- package/dist/types-exFUQyBX.d.ts +197 -0
- package/package.json +60 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1197 @@
|
|
|
1
|
+
// src/core/config.ts
|
|
2
|
+
var config = null;
|
|
3
|
+
function initAuth(options) {
|
|
4
|
+
config = {
|
|
5
|
+
...options,
|
|
6
|
+
storageKey: options.storageKey ?? "classic_auth"
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
function getConfig() {
|
|
10
|
+
if (!config) {
|
|
11
|
+
throw new Error(
|
|
12
|
+
'@classic-homes/auth not initialized. Call initAuth({ baseUrl: "..." }) before using auth services.'
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
return config;
|
|
16
|
+
}
|
|
17
|
+
function isInitialized() {
|
|
18
|
+
return config !== null;
|
|
19
|
+
}
|
|
20
|
+
function resetConfig() {
|
|
21
|
+
config = null;
|
|
22
|
+
}
|
|
23
|
+
function getDefaultStorage() {
|
|
24
|
+
if (typeof window !== "undefined" && window.localStorage) {
|
|
25
|
+
return {
|
|
26
|
+
getItem: (key) => localStorage.getItem(key),
|
|
27
|
+
setItem: (key, value) => localStorage.setItem(key, value),
|
|
28
|
+
removeItem: (key) => localStorage.removeItem(key)
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
getItem: () => null,
|
|
33
|
+
setItem: () => {
|
|
34
|
+
},
|
|
35
|
+
removeItem: () => {
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function getStorage() {
|
|
40
|
+
const cfg = getConfig();
|
|
41
|
+
return cfg.storage ?? getDefaultStorage();
|
|
42
|
+
}
|
|
43
|
+
function getFetch() {
|
|
44
|
+
const cfg = getConfig();
|
|
45
|
+
return cfg.fetch ?? fetch;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// src/core/client.ts
|
|
49
|
+
var isRefreshing = false;
|
|
50
|
+
var refreshSubscribers = [];
|
|
51
|
+
function subscribeTokenRefresh(cb) {
|
|
52
|
+
refreshSubscribers.push(cb);
|
|
53
|
+
}
|
|
54
|
+
function onTokenRefreshed(token) {
|
|
55
|
+
refreshSubscribers.forEach((cb) => cb(token));
|
|
56
|
+
refreshSubscribers = [];
|
|
57
|
+
}
|
|
58
|
+
function getAccessToken() {
|
|
59
|
+
const config2 = getConfig();
|
|
60
|
+
const storage = getStorage();
|
|
61
|
+
const authData = storage.getItem(config2.storageKey ?? "classic_auth");
|
|
62
|
+
if (!authData) return null;
|
|
63
|
+
try {
|
|
64
|
+
const parsed = JSON.parse(authData);
|
|
65
|
+
return parsed.accessToken ?? null;
|
|
66
|
+
} catch {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function getRefreshToken() {
|
|
71
|
+
const config2 = getConfig();
|
|
72
|
+
const storage = getStorage();
|
|
73
|
+
const authData = storage.getItem(config2.storageKey ?? "classic_auth");
|
|
74
|
+
if (!authData) return null;
|
|
75
|
+
try {
|
|
76
|
+
const parsed = JSON.parse(authData);
|
|
77
|
+
return parsed.refreshToken ?? null;
|
|
78
|
+
} catch {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function getSessionToken() {
|
|
83
|
+
const storage = getStorage();
|
|
84
|
+
return storage.getItem("sessionToken");
|
|
85
|
+
}
|
|
86
|
+
function updateStoredTokens(accessToken, refreshToken) {
|
|
87
|
+
const config2 = getConfig();
|
|
88
|
+
const storage = getStorage();
|
|
89
|
+
const storageKey = config2.storageKey ?? "classic_auth";
|
|
90
|
+
const authData = storage.getItem(storageKey);
|
|
91
|
+
let parsed = {};
|
|
92
|
+
if (authData) {
|
|
93
|
+
try {
|
|
94
|
+
parsed = JSON.parse(authData);
|
|
95
|
+
} catch {
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
parsed.accessToken = accessToken;
|
|
99
|
+
parsed.refreshToken = refreshToken;
|
|
100
|
+
storage.setItem(storageKey, JSON.stringify(parsed));
|
|
101
|
+
config2.onTokenRefresh?.({ accessToken, refreshToken });
|
|
102
|
+
}
|
|
103
|
+
function clearStoredAuth() {
|
|
104
|
+
const config2 = getConfig();
|
|
105
|
+
const storage = getStorage();
|
|
106
|
+
storage.removeItem(config2.storageKey ?? "classic_auth");
|
|
107
|
+
storage.removeItem("sessionToken");
|
|
108
|
+
}
|
|
109
|
+
async function refreshAccessToken() {
|
|
110
|
+
const refreshToken = getRefreshToken();
|
|
111
|
+
const config2 = getConfig();
|
|
112
|
+
const fetchFn = getFetch();
|
|
113
|
+
if (!refreshToken) {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
try {
|
|
117
|
+
const response = await fetchFn(`${config2.baseUrl}/auth/refresh`, {
|
|
118
|
+
method: "POST",
|
|
119
|
+
headers: {
|
|
120
|
+
"Content-Type": "application/json"
|
|
121
|
+
},
|
|
122
|
+
body: JSON.stringify({ refreshToken })
|
|
123
|
+
});
|
|
124
|
+
if (response.ok) {
|
|
125
|
+
const apiResponse = await response.json();
|
|
126
|
+
const data = apiResponse.data;
|
|
127
|
+
const tokens = data && typeof data === "object" && "data" in data ? data.data : data;
|
|
128
|
+
if (tokens?.accessToken && tokens?.refreshToken) {
|
|
129
|
+
updateStoredTokens(tokens.accessToken, tokens.refreshToken);
|
|
130
|
+
onTokenRefreshed(tokens.accessToken);
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
} catch (error) {
|
|
135
|
+
config2.onAuthError?.(error instanceof Error ? error : new Error("Token refresh failed"));
|
|
136
|
+
}
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
function extractData(response) {
|
|
140
|
+
if (response && typeof response === "object" && "data" in response) {
|
|
141
|
+
const dataField = response.data;
|
|
142
|
+
if (dataField && typeof dataField === "object" && "data" in dataField) {
|
|
143
|
+
return dataField.data;
|
|
144
|
+
}
|
|
145
|
+
if (dataField !== void 0) {
|
|
146
|
+
return dataField;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return response;
|
|
150
|
+
}
|
|
151
|
+
async function apiRequest(endpoint, options = {}) {
|
|
152
|
+
const { authenticated = false, customFetch, body, ...fetchOptions } = options;
|
|
153
|
+
const config2 = getConfig();
|
|
154
|
+
const fetchFn = customFetch ?? getFetch();
|
|
155
|
+
const url = endpoint.startsWith("http") ? endpoint : `${config2.baseUrl}${endpoint}`;
|
|
156
|
+
const headers = {
|
|
157
|
+
"Content-Type": "application/json",
|
|
158
|
+
...fetchOptions.headers
|
|
159
|
+
};
|
|
160
|
+
if (authenticated) {
|
|
161
|
+
const accessToken = getAccessToken();
|
|
162
|
+
const sessionToken = getSessionToken();
|
|
163
|
+
if (accessToken) {
|
|
164
|
+
headers["Authorization"] = `Bearer ${accessToken}`;
|
|
165
|
+
}
|
|
166
|
+
if (sessionToken) {
|
|
167
|
+
headers["X-Session-Token"] = sessionToken;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
try {
|
|
171
|
+
const response = await fetchFn(url, {
|
|
172
|
+
...fetchOptions,
|
|
173
|
+
headers,
|
|
174
|
+
body: body ? JSON.stringify(body) : void 0
|
|
175
|
+
});
|
|
176
|
+
if (response.status === 401 && authenticated && typeof window !== "undefined") {
|
|
177
|
+
if (!isRefreshing) {
|
|
178
|
+
isRefreshing = true;
|
|
179
|
+
const refreshed = await refreshAccessToken();
|
|
180
|
+
isRefreshing = false;
|
|
181
|
+
if (refreshed) {
|
|
182
|
+
return apiRequest(endpoint, options);
|
|
183
|
+
} else {
|
|
184
|
+
clearStoredAuth();
|
|
185
|
+
config2.onLogout?.();
|
|
186
|
+
throw new Error("Authentication failed. Please sign in again.");
|
|
187
|
+
}
|
|
188
|
+
} else {
|
|
189
|
+
return new Promise((resolve, reject) => {
|
|
190
|
+
subscribeTokenRefresh(() => {
|
|
191
|
+
apiRequest(endpoint, options).then(resolve).catch(reject);
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
const data = await response.json();
|
|
197
|
+
if (!response.ok) {
|
|
198
|
+
const dataObj = data;
|
|
199
|
+
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";
|
|
200
|
+
const error = new Error(errorMessage);
|
|
201
|
+
config2.onAuthError?.(error);
|
|
202
|
+
throw error;
|
|
203
|
+
}
|
|
204
|
+
return data;
|
|
205
|
+
} catch (error) {
|
|
206
|
+
if (error instanceof Error) {
|
|
207
|
+
throw error;
|
|
208
|
+
}
|
|
209
|
+
throw new Error("API request failed");
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
var api = {
|
|
213
|
+
get: (endpoint, authenticated = false, customFetch) => apiRequest(endpoint, { method: "GET", authenticated, customFetch }),
|
|
214
|
+
post: (endpoint, body, authenticated = false, customFetch) => apiRequest(endpoint, {
|
|
215
|
+
method: "POST",
|
|
216
|
+
body,
|
|
217
|
+
authenticated,
|
|
218
|
+
customFetch
|
|
219
|
+
}),
|
|
220
|
+
put: (endpoint, body, authenticated = false, customFetch) => apiRequest(endpoint, {
|
|
221
|
+
method: "PUT",
|
|
222
|
+
body,
|
|
223
|
+
authenticated,
|
|
224
|
+
customFetch
|
|
225
|
+
}),
|
|
226
|
+
patch: (endpoint, body, authenticated = false, customFetch) => apiRequest(endpoint, {
|
|
227
|
+
method: "PATCH",
|
|
228
|
+
body,
|
|
229
|
+
authenticated,
|
|
230
|
+
customFetch
|
|
231
|
+
}),
|
|
232
|
+
delete: (endpoint, authenticated = false, customFetch, body) => apiRequest(endpoint, {
|
|
233
|
+
method: "DELETE",
|
|
234
|
+
body,
|
|
235
|
+
authenticated,
|
|
236
|
+
customFetch
|
|
237
|
+
})
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
// src/core/api.ts
|
|
241
|
+
var authApi = {
|
|
242
|
+
// ============================================================================
|
|
243
|
+
// Authentication
|
|
244
|
+
// ============================================================================
|
|
245
|
+
/**
|
|
246
|
+
* Login with username and password.
|
|
247
|
+
*/
|
|
248
|
+
async login(credentials) {
|
|
249
|
+
const response = await api.post("/auth/login", credentials);
|
|
250
|
+
return extractData(response);
|
|
251
|
+
},
|
|
252
|
+
/**
|
|
253
|
+
* Logout the current user.
|
|
254
|
+
*/
|
|
255
|
+
async logout() {
|
|
256
|
+
await api.post("/auth/logout", {}, true);
|
|
257
|
+
},
|
|
258
|
+
/**
|
|
259
|
+
* Register a new user.
|
|
260
|
+
*/
|
|
261
|
+
async register(data) {
|
|
262
|
+
const response = await api.post("/auth/register", data);
|
|
263
|
+
return extractData(response);
|
|
264
|
+
},
|
|
265
|
+
/**
|
|
266
|
+
* Request a password reset email.
|
|
267
|
+
*/
|
|
268
|
+
async forgotPassword(email) {
|
|
269
|
+
await api.post("/auth/forgot-password", { email });
|
|
270
|
+
},
|
|
271
|
+
/**
|
|
272
|
+
* Reset password with a token.
|
|
273
|
+
*/
|
|
274
|
+
async resetPassword(token, newPassword) {
|
|
275
|
+
await api.post("/auth/reset-password", { token, newPassword });
|
|
276
|
+
},
|
|
277
|
+
/**
|
|
278
|
+
* Refresh the access token.
|
|
279
|
+
*/
|
|
280
|
+
async refreshToken(refreshToken) {
|
|
281
|
+
const response = await api.post("/auth/refresh", { refreshToken });
|
|
282
|
+
return extractData(response);
|
|
283
|
+
},
|
|
284
|
+
/**
|
|
285
|
+
* Initiate SSO login by redirecting to the SSO provider.
|
|
286
|
+
*/
|
|
287
|
+
initiateSSOLogin() {
|
|
288
|
+
if (typeof window === "undefined") return;
|
|
289
|
+
const config2 = getConfig();
|
|
290
|
+
const authorizeUrl = config2.sso?.authorizeUrl ?? `${config2.baseUrl}/auth/sso/authorize`;
|
|
291
|
+
window.location.href = authorizeUrl;
|
|
292
|
+
},
|
|
293
|
+
// ============================================================================
|
|
294
|
+
// Profile
|
|
295
|
+
// ============================================================================
|
|
296
|
+
/**
|
|
297
|
+
* Get the current user's profile.
|
|
298
|
+
*/
|
|
299
|
+
async getProfile(customFetch) {
|
|
300
|
+
const response = await api.get("/auth/profile", true, customFetch);
|
|
301
|
+
return response;
|
|
302
|
+
},
|
|
303
|
+
/**
|
|
304
|
+
* Update the current user's profile.
|
|
305
|
+
*/
|
|
306
|
+
async updateProfile(data) {
|
|
307
|
+
const response = await api.put("/auth/profile", data, true);
|
|
308
|
+
return extractData(response);
|
|
309
|
+
},
|
|
310
|
+
/**
|
|
311
|
+
* Change the current user's password.
|
|
312
|
+
*/
|
|
313
|
+
async changePassword(data) {
|
|
314
|
+
await api.put("/auth/change-password", data, true);
|
|
315
|
+
},
|
|
316
|
+
// ============================================================================
|
|
317
|
+
// Email Verification
|
|
318
|
+
// ============================================================================
|
|
319
|
+
/**
|
|
320
|
+
* Resend email verification.
|
|
321
|
+
*/
|
|
322
|
+
async resendVerification() {
|
|
323
|
+
await api.post("/auth/resend-verification", {}, true);
|
|
324
|
+
},
|
|
325
|
+
/**
|
|
326
|
+
* Verify email with a token.
|
|
327
|
+
*/
|
|
328
|
+
async verifyEmail(token) {
|
|
329
|
+
const response = await api.post("/auth/verify-email", {
|
|
330
|
+
token
|
|
331
|
+
});
|
|
332
|
+
return extractData(response);
|
|
333
|
+
},
|
|
334
|
+
// ============================================================================
|
|
335
|
+
// Session Management
|
|
336
|
+
// ============================================================================
|
|
337
|
+
/**
|
|
338
|
+
* Get all active sessions.
|
|
339
|
+
*/
|
|
340
|
+
async getSessions(customFetch) {
|
|
341
|
+
const response = await api.get(
|
|
342
|
+
"/auth/sessions",
|
|
343
|
+
true,
|
|
344
|
+
customFetch
|
|
345
|
+
);
|
|
346
|
+
return response;
|
|
347
|
+
},
|
|
348
|
+
/**
|
|
349
|
+
* Revoke a specific session.
|
|
350
|
+
*/
|
|
351
|
+
async revokeSession(sessionId) {
|
|
352
|
+
await api.delete(`/auth/sessions/${sessionId}`, true);
|
|
353
|
+
},
|
|
354
|
+
/**
|
|
355
|
+
* Revoke all sessions except the current one.
|
|
356
|
+
*/
|
|
357
|
+
async revokeAllSessions() {
|
|
358
|
+
await api.delete("/auth/sessions", true);
|
|
359
|
+
},
|
|
360
|
+
// ============================================================================
|
|
361
|
+
// API Key Management
|
|
362
|
+
// ============================================================================
|
|
363
|
+
/**
|
|
364
|
+
* Get all API keys.
|
|
365
|
+
*/
|
|
366
|
+
async getApiKeys(customFetch) {
|
|
367
|
+
const response = await api.get("/auth/api-keys", true, customFetch);
|
|
368
|
+
return response;
|
|
369
|
+
},
|
|
370
|
+
/**
|
|
371
|
+
* Create a new API key.
|
|
372
|
+
*/
|
|
373
|
+
async createApiKey(data) {
|
|
374
|
+
const response = await api.post("/auth/api-keys", data, true);
|
|
375
|
+
return extractData(response);
|
|
376
|
+
},
|
|
377
|
+
/**
|
|
378
|
+
* Revoke an API key.
|
|
379
|
+
*/
|
|
380
|
+
async revokeApiKey(keyId) {
|
|
381
|
+
await api.delete(`/auth/api-keys/${keyId}`, true);
|
|
382
|
+
},
|
|
383
|
+
/**
|
|
384
|
+
* Update an API key's name.
|
|
385
|
+
*/
|
|
386
|
+
async updateApiKey(keyId, data) {
|
|
387
|
+
await api.put(`/auth/api-keys/${keyId}`, data, true);
|
|
388
|
+
},
|
|
389
|
+
// ============================================================================
|
|
390
|
+
// MFA Management
|
|
391
|
+
// ============================================================================
|
|
392
|
+
/**
|
|
393
|
+
* Get MFA status for the current user.
|
|
394
|
+
*/
|
|
395
|
+
async getMFAStatus() {
|
|
396
|
+
const response = await api.get("/auth/mfa/status", true);
|
|
397
|
+
return response;
|
|
398
|
+
},
|
|
399
|
+
/**
|
|
400
|
+
* Setup MFA (get QR code and backup codes).
|
|
401
|
+
*/
|
|
402
|
+
async setupMFA() {
|
|
403
|
+
const response = await api.post("/auth/mfa-setup", {}, true);
|
|
404
|
+
return extractData(response);
|
|
405
|
+
},
|
|
406
|
+
/**
|
|
407
|
+
* Verify MFA setup with a code.
|
|
408
|
+
*/
|
|
409
|
+
async verifyMFASetup(code) {
|
|
410
|
+
await api.post("/auth/mfa/verify", { code }, true);
|
|
411
|
+
},
|
|
412
|
+
/**
|
|
413
|
+
* Disable MFA.
|
|
414
|
+
*/
|
|
415
|
+
async disableMFA(password) {
|
|
416
|
+
await api.delete("/auth/mfa", true, void 0, { password });
|
|
417
|
+
},
|
|
418
|
+
/**
|
|
419
|
+
* Regenerate MFA backup codes.
|
|
420
|
+
*/
|
|
421
|
+
async regenerateBackupCodes(password) {
|
|
422
|
+
const response = await api.post(
|
|
423
|
+
"/auth/mfa/regenerate-backup-codes",
|
|
424
|
+
{ password },
|
|
425
|
+
true
|
|
426
|
+
);
|
|
427
|
+
return extractData(response);
|
|
428
|
+
},
|
|
429
|
+
/**
|
|
430
|
+
* Verify MFA challenge during login.
|
|
431
|
+
*/
|
|
432
|
+
async verifyMFAChallenge(data) {
|
|
433
|
+
const config2 = getConfig();
|
|
434
|
+
const fetchFn = config2.fetch ?? fetch;
|
|
435
|
+
const requestBody = {
|
|
436
|
+
code: data.code,
|
|
437
|
+
type: data.method === "backup" ? "backup" : "totp",
|
|
438
|
+
trustDevice: data.trustDevice
|
|
439
|
+
};
|
|
440
|
+
const response = await fetchFn(`${config2.baseUrl}/auth/mfa/challenge`, {
|
|
441
|
+
method: "POST",
|
|
442
|
+
headers: {
|
|
443
|
+
"Content-Type": "application/json",
|
|
444
|
+
Authorization: `Bearer ${data.mfaToken}`
|
|
445
|
+
},
|
|
446
|
+
body: JSON.stringify(requestBody)
|
|
447
|
+
});
|
|
448
|
+
if (!response.ok) {
|
|
449
|
+
const error = await response.json().catch(() => ({ message: "Verification failed" }));
|
|
450
|
+
throw new Error(error.error?.message || error.message || "Invalid verification code");
|
|
451
|
+
}
|
|
452
|
+
const result = await response.json();
|
|
453
|
+
return extractData(result);
|
|
454
|
+
},
|
|
455
|
+
// ============================================================================
|
|
456
|
+
// Device Management
|
|
457
|
+
// ============================================================================
|
|
458
|
+
/**
|
|
459
|
+
* Get all devices.
|
|
460
|
+
*/
|
|
461
|
+
async getDevices(customFetch) {
|
|
462
|
+
const response = await api.get(
|
|
463
|
+
"/auth/devices",
|
|
464
|
+
true,
|
|
465
|
+
customFetch
|
|
466
|
+
);
|
|
467
|
+
return response;
|
|
468
|
+
},
|
|
469
|
+
/**
|
|
470
|
+
* Trust a device.
|
|
471
|
+
*/
|
|
472
|
+
async trustDevice(deviceId) {
|
|
473
|
+
await api.put(`/auth/devices/${deviceId}/trust`, {}, true);
|
|
474
|
+
},
|
|
475
|
+
/**
|
|
476
|
+
* Revoke device trust.
|
|
477
|
+
*/
|
|
478
|
+
async revokeDevice(deviceId) {
|
|
479
|
+
await api.delete(`/auth/devices/${deviceId}/revoke`, true);
|
|
480
|
+
},
|
|
481
|
+
/**
|
|
482
|
+
* Remove a device completely.
|
|
483
|
+
*/
|
|
484
|
+
async removeDevice(deviceId) {
|
|
485
|
+
await api.delete(`/auth/devices/${deviceId}`, true);
|
|
486
|
+
},
|
|
487
|
+
/**
|
|
488
|
+
* Approve a device with a token.
|
|
489
|
+
*/
|
|
490
|
+
async approveDevice(token) {
|
|
491
|
+
const response = await api.post("/auth/device/approve", {
|
|
492
|
+
token
|
|
493
|
+
});
|
|
494
|
+
return extractData(response);
|
|
495
|
+
},
|
|
496
|
+
/**
|
|
497
|
+
* Block a device with a token.
|
|
498
|
+
*/
|
|
499
|
+
async blockDevice(token) {
|
|
500
|
+
const response = await api.post("/auth/device/block", {
|
|
501
|
+
token
|
|
502
|
+
});
|
|
503
|
+
return extractData(response);
|
|
504
|
+
},
|
|
505
|
+
// ============================================================================
|
|
506
|
+
// Preferences
|
|
507
|
+
// ============================================================================
|
|
508
|
+
/**
|
|
509
|
+
* Get user preferences.
|
|
510
|
+
*/
|
|
511
|
+
async getPreferences(customFetch) {
|
|
512
|
+
const response = await api.get(
|
|
513
|
+
"/auth/preferences",
|
|
514
|
+
true,
|
|
515
|
+
customFetch
|
|
516
|
+
);
|
|
517
|
+
if (response && typeof response === "object" && "preferences" in response) {
|
|
518
|
+
return response.preferences;
|
|
519
|
+
}
|
|
520
|
+
return response ?? {};
|
|
521
|
+
},
|
|
522
|
+
/**
|
|
523
|
+
* Update user preferences.
|
|
524
|
+
*/
|
|
525
|
+
async updatePreferences(data) {
|
|
526
|
+
await api.put("/auth/preferences", data, true);
|
|
527
|
+
},
|
|
528
|
+
// ============================================================================
|
|
529
|
+
// SSO / Linked Accounts
|
|
530
|
+
// ============================================================================
|
|
531
|
+
/**
|
|
532
|
+
* Get SSO linked accounts.
|
|
533
|
+
*/
|
|
534
|
+
async getSSOAccounts(customFetch) {
|
|
535
|
+
const response = await api.get(
|
|
536
|
+
"/auth/sso/accounts",
|
|
537
|
+
true,
|
|
538
|
+
customFetch
|
|
539
|
+
);
|
|
540
|
+
if (response && typeof response === "object" && "accounts" in response) {
|
|
541
|
+
return response.accounts;
|
|
542
|
+
}
|
|
543
|
+
return Array.isArray(response) ? response : [];
|
|
544
|
+
},
|
|
545
|
+
/**
|
|
546
|
+
* Unlink an SSO account.
|
|
547
|
+
*/
|
|
548
|
+
async unlinkSSOAccount(provider, password) {
|
|
549
|
+
await api.delete("/auth/sso/unlink", true, void 0, { provider, password });
|
|
550
|
+
},
|
|
551
|
+
/**
|
|
552
|
+
* Link an SSO account (redirects to SSO provider).
|
|
553
|
+
*/
|
|
554
|
+
async linkSSOAccount(provider = "authentik") {
|
|
555
|
+
if (typeof window === "undefined") return;
|
|
556
|
+
const accessToken = getAccessToken();
|
|
557
|
+
if (!accessToken) {
|
|
558
|
+
throw new Error("Not authenticated");
|
|
559
|
+
}
|
|
560
|
+
const config2 = getConfig();
|
|
561
|
+
const params = new URLSearchParams({
|
|
562
|
+
token: accessToken,
|
|
563
|
+
provider
|
|
564
|
+
});
|
|
565
|
+
window.location.href = `${config2.baseUrl}/auth/sso/link?${params.toString()}`;
|
|
566
|
+
},
|
|
567
|
+
// ============================================================================
|
|
568
|
+
// Security Events
|
|
569
|
+
// ============================================================================
|
|
570
|
+
/**
|
|
571
|
+
* Get security event history.
|
|
572
|
+
*/
|
|
573
|
+
async getSecurityEvents(params, customFetch) {
|
|
574
|
+
const queryParams = new URLSearchParams();
|
|
575
|
+
if (params?.page) queryParams.append("page", params.page.toString());
|
|
576
|
+
if (params?.limit) queryParams.append("limit", params.limit.toString());
|
|
577
|
+
if (params?.type) queryParams.append("type", params.type);
|
|
578
|
+
const response = await api.get(`/auth/security/events?${queryParams.toString()}`, true, customFetch);
|
|
579
|
+
return extractData(response);
|
|
580
|
+
}
|
|
581
|
+
};
|
|
582
|
+
|
|
583
|
+
// src/core/service.ts
|
|
584
|
+
var AuthService = class {
|
|
585
|
+
// ============================================================================
|
|
586
|
+
// Authentication
|
|
587
|
+
// ============================================================================
|
|
588
|
+
/**
|
|
589
|
+
* Login with username and password.
|
|
590
|
+
*/
|
|
591
|
+
async login(credentials) {
|
|
592
|
+
return await authApi.login(credentials);
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* Logout the current user.
|
|
596
|
+
*/
|
|
597
|
+
async logout() {
|
|
598
|
+
await authApi.logout();
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* Register a new user.
|
|
602
|
+
*/
|
|
603
|
+
async register(data) {
|
|
604
|
+
return await authApi.register(data);
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* Request a password reset email.
|
|
608
|
+
*/
|
|
609
|
+
async forgotPassword(email) {
|
|
610
|
+
await authApi.forgotPassword(email);
|
|
611
|
+
}
|
|
612
|
+
/**
|
|
613
|
+
* Reset password with a token.
|
|
614
|
+
*/
|
|
615
|
+
async resetPassword(token, newPassword) {
|
|
616
|
+
await authApi.resetPassword(token, newPassword);
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
619
|
+
* Change the current user's password.
|
|
620
|
+
*/
|
|
621
|
+
async changePassword(currentPassword, newPassword) {
|
|
622
|
+
await authApi.changePassword({ currentPassword, newPassword });
|
|
623
|
+
}
|
|
624
|
+
/**
|
|
625
|
+
* Refresh the access token.
|
|
626
|
+
*/
|
|
627
|
+
async refreshToken(refreshToken) {
|
|
628
|
+
return await authApi.refreshToken(refreshToken);
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* Initiate SSO login (redirects to SSO provider).
|
|
632
|
+
*/
|
|
633
|
+
initiateSSOLogin() {
|
|
634
|
+
authApi.initiateSSOLogin();
|
|
635
|
+
}
|
|
636
|
+
// ============================================================================
|
|
637
|
+
// Profile
|
|
638
|
+
// ============================================================================
|
|
639
|
+
/**
|
|
640
|
+
* Get the current user's profile.
|
|
641
|
+
*/
|
|
642
|
+
async getProfile(customFetch) {
|
|
643
|
+
return await authApi.getProfile(customFetch);
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* Update the current user's profile.
|
|
647
|
+
*/
|
|
648
|
+
async updateProfile(data) {
|
|
649
|
+
return await authApi.updateProfile(data);
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* Resend email verification.
|
|
653
|
+
*/
|
|
654
|
+
async resendVerification() {
|
|
655
|
+
await authApi.resendVerification();
|
|
656
|
+
}
|
|
657
|
+
/**
|
|
658
|
+
* Verify email with a token.
|
|
659
|
+
*/
|
|
660
|
+
async verifyEmail(token) {
|
|
661
|
+
return await authApi.verifyEmail(token);
|
|
662
|
+
}
|
|
663
|
+
// ============================================================================
|
|
664
|
+
// Session Management
|
|
665
|
+
// ============================================================================
|
|
666
|
+
/**
|
|
667
|
+
* Get all active sessions.
|
|
668
|
+
*/
|
|
669
|
+
async getSessions(customFetch) {
|
|
670
|
+
return await authApi.getSessions(customFetch);
|
|
671
|
+
}
|
|
672
|
+
/**
|
|
673
|
+
* Revoke a specific session.
|
|
674
|
+
*/
|
|
675
|
+
async revokeSession(sessionId) {
|
|
676
|
+
await authApi.revokeSession(sessionId);
|
|
677
|
+
}
|
|
678
|
+
/**
|
|
679
|
+
* Revoke all sessions except the current one.
|
|
680
|
+
*/
|
|
681
|
+
async revokeAllSessions() {
|
|
682
|
+
await authApi.revokeAllSessions();
|
|
683
|
+
}
|
|
684
|
+
// ============================================================================
|
|
685
|
+
// API Key Management
|
|
686
|
+
// ============================================================================
|
|
687
|
+
/**
|
|
688
|
+
* Get all API keys.
|
|
689
|
+
*/
|
|
690
|
+
async getApiKeys(customFetch) {
|
|
691
|
+
return await authApi.getApiKeys(customFetch);
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* Create a new API key.
|
|
695
|
+
*/
|
|
696
|
+
async createApiKey(data) {
|
|
697
|
+
return await authApi.createApiKey(data);
|
|
698
|
+
}
|
|
699
|
+
/**
|
|
700
|
+
* Revoke an API key.
|
|
701
|
+
*/
|
|
702
|
+
async revokeApiKey(keyId) {
|
|
703
|
+
await authApi.revokeApiKey(keyId);
|
|
704
|
+
}
|
|
705
|
+
/**
|
|
706
|
+
* Update an API key's name.
|
|
707
|
+
*/
|
|
708
|
+
async updateApiKey(keyId, name) {
|
|
709
|
+
await authApi.updateApiKey(keyId, { name });
|
|
710
|
+
}
|
|
711
|
+
// ============================================================================
|
|
712
|
+
// MFA Management
|
|
713
|
+
// ============================================================================
|
|
714
|
+
/**
|
|
715
|
+
* Get MFA status for the current user.
|
|
716
|
+
*/
|
|
717
|
+
async getMFAStatus() {
|
|
718
|
+
return await authApi.getMFAStatus();
|
|
719
|
+
}
|
|
720
|
+
/**
|
|
721
|
+
* Setup MFA (get QR code and backup codes).
|
|
722
|
+
*/
|
|
723
|
+
async setupMFA() {
|
|
724
|
+
return await authApi.setupMFA();
|
|
725
|
+
}
|
|
726
|
+
/**
|
|
727
|
+
* Verify MFA setup with a code.
|
|
728
|
+
*/
|
|
729
|
+
async verifyMFASetup(code) {
|
|
730
|
+
await authApi.verifyMFASetup(code);
|
|
731
|
+
}
|
|
732
|
+
/**
|
|
733
|
+
* Disable MFA.
|
|
734
|
+
*/
|
|
735
|
+
async disableMFA(password) {
|
|
736
|
+
await authApi.disableMFA(password);
|
|
737
|
+
}
|
|
738
|
+
/**
|
|
739
|
+
* Regenerate MFA backup codes.
|
|
740
|
+
*/
|
|
741
|
+
async regenerateBackupCodes(password) {
|
|
742
|
+
return await authApi.regenerateBackupCodes(password);
|
|
743
|
+
}
|
|
744
|
+
/**
|
|
745
|
+
* Verify MFA challenge during login.
|
|
746
|
+
*/
|
|
747
|
+
async verifyMFAChallenge(data) {
|
|
748
|
+
return await authApi.verifyMFAChallenge(data);
|
|
749
|
+
}
|
|
750
|
+
// ============================================================================
|
|
751
|
+
// Device Management
|
|
752
|
+
// ============================================================================
|
|
753
|
+
/**
|
|
754
|
+
* Get all devices.
|
|
755
|
+
*/
|
|
756
|
+
async getDevices(customFetch) {
|
|
757
|
+
return await authApi.getDevices(customFetch);
|
|
758
|
+
}
|
|
759
|
+
/**
|
|
760
|
+
* Trust a device.
|
|
761
|
+
*/
|
|
762
|
+
async trustDevice(deviceId) {
|
|
763
|
+
await authApi.trustDevice(deviceId);
|
|
764
|
+
}
|
|
765
|
+
/**
|
|
766
|
+
* Revoke device trust.
|
|
767
|
+
*/
|
|
768
|
+
async revokeDevice(deviceId) {
|
|
769
|
+
await authApi.revokeDevice(deviceId);
|
|
770
|
+
}
|
|
771
|
+
/**
|
|
772
|
+
* Remove a device completely.
|
|
773
|
+
*/
|
|
774
|
+
async removeDevice(deviceId) {
|
|
775
|
+
await authApi.removeDevice(deviceId);
|
|
776
|
+
}
|
|
777
|
+
/**
|
|
778
|
+
* Approve a device with a token.
|
|
779
|
+
*/
|
|
780
|
+
async approveDevice(token) {
|
|
781
|
+
return await authApi.approveDevice(token);
|
|
782
|
+
}
|
|
783
|
+
/**
|
|
784
|
+
* Block a device with a token.
|
|
785
|
+
*/
|
|
786
|
+
async blockDevice(token) {
|
|
787
|
+
return await authApi.blockDevice(token);
|
|
788
|
+
}
|
|
789
|
+
// ============================================================================
|
|
790
|
+
// Preferences
|
|
791
|
+
// ============================================================================
|
|
792
|
+
/**
|
|
793
|
+
* Get user preferences.
|
|
794
|
+
*/
|
|
795
|
+
async getPreferences(customFetch) {
|
|
796
|
+
return await authApi.getPreferences(customFetch);
|
|
797
|
+
}
|
|
798
|
+
/**
|
|
799
|
+
* Update user preferences.
|
|
800
|
+
*/
|
|
801
|
+
async updatePreferences(data) {
|
|
802
|
+
await authApi.updatePreferences(data);
|
|
803
|
+
}
|
|
804
|
+
// ============================================================================
|
|
805
|
+
// SSO / Linked Accounts
|
|
806
|
+
// ============================================================================
|
|
807
|
+
/**
|
|
808
|
+
* Get SSO linked accounts.
|
|
809
|
+
*/
|
|
810
|
+
async getLinkedAccounts(customFetch) {
|
|
811
|
+
return await authApi.getSSOAccounts(customFetch);
|
|
812
|
+
}
|
|
813
|
+
/**
|
|
814
|
+
* Link an SSO account (redirects to SSO provider).
|
|
815
|
+
*/
|
|
816
|
+
async linkAccount(provider = "authentik") {
|
|
817
|
+
await authApi.linkSSOAccount(provider);
|
|
818
|
+
}
|
|
819
|
+
/**
|
|
820
|
+
* Unlink an SSO account.
|
|
821
|
+
*/
|
|
822
|
+
async unlinkAccount(provider, password) {
|
|
823
|
+
await authApi.unlinkSSOAccount(provider, password);
|
|
824
|
+
}
|
|
825
|
+
// ============================================================================
|
|
826
|
+
// Security Events
|
|
827
|
+
// ============================================================================
|
|
828
|
+
/**
|
|
829
|
+
* Get security event history.
|
|
830
|
+
*/
|
|
831
|
+
async getSecurityEvents(params, customFetch) {
|
|
832
|
+
return await authApi.getSecurityEvents(params, customFetch);
|
|
833
|
+
}
|
|
834
|
+
};
|
|
835
|
+
var authService = new AuthService();
|
|
836
|
+
|
|
837
|
+
// src/core/jwt.ts
|
|
838
|
+
function decodeJWT(token) {
|
|
839
|
+
try {
|
|
840
|
+
const parts = token.split(".");
|
|
841
|
+
if (parts.length !== 3) {
|
|
842
|
+
return null;
|
|
843
|
+
}
|
|
844
|
+
const payload = parts[1];
|
|
845
|
+
const decoded = atob(payload.replace(/-/g, "+").replace(/_/g, "/"));
|
|
846
|
+
return JSON.parse(decoded);
|
|
847
|
+
} catch {
|
|
848
|
+
return null;
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
function isTokenExpired(token) {
|
|
852
|
+
const payload = decodeJWT(token);
|
|
853
|
+
if (!payload || !payload.exp) {
|
|
854
|
+
return true;
|
|
855
|
+
}
|
|
856
|
+
return payload.exp * 1e3 < Date.now();
|
|
857
|
+
}
|
|
858
|
+
function getTokenRemainingTime(token) {
|
|
859
|
+
const payload = decodeJWT(token);
|
|
860
|
+
if (!payload || !payload.exp) {
|
|
861
|
+
return 0;
|
|
862
|
+
}
|
|
863
|
+
const remainingMs = payload.exp * 1e3 - Date.now();
|
|
864
|
+
return Math.max(0, remainingMs);
|
|
865
|
+
}
|
|
866
|
+
function getTokenExpiration(token) {
|
|
867
|
+
const payload = decodeJWT(token);
|
|
868
|
+
if (!payload || !payload.exp) {
|
|
869
|
+
return null;
|
|
870
|
+
}
|
|
871
|
+
return new Date(payload.exp * 1e3);
|
|
872
|
+
}
|
|
873
|
+
function extractClaims(token, claims) {
|
|
874
|
+
const payload = decodeJWT(token);
|
|
875
|
+
if (!payload) {
|
|
876
|
+
return null;
|
|
877
|
+
}
|
|
878
|
+
const result = {};
|
|
879
|
+
for (const claim of claims) {
|
|
880
|
+
if (claim in payload) {
|
|
881
|
+
result[claim] = payload[claim];
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
return result;
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
// src/svelte/stores/auth.svelte.ts
|
|
888
|
+
function getStorageKey() {
|
|
889
|
+
return isInitialized() ? getConfig().storageKey ?? "classic_auth" : "classic_auth";
|
|
890
|
+
}
|
|
891
|
+
function getStorageAdapter() {
|
|
892
|
+
return isInitialized() ? getStorage() : getDefaultStorage();
|
|
893
|
+
}
|
|
894
|
+
function loadAuthFromStorage() {
|
|
895
|
+
try {
|
|
896
|
+
const storage = getStorageAdapter();
|
|
897
|
+
const data = storage.getItem(getStorageKey());
|
|
898
|
+
if (!data) {
|
|
899
|
+
return {
|
|
900
|
+
accessToken: null,
|
|
901
|
+
refreshToken: null,
|
|
902
|
+
user: null,
|
|
903
|
+
isAuthenticated: false
|
|
904
|
+
};
|
|
905
|
+
}
|
|
906
|
+
const parsed = JSON.parse(data);
|
|
907
|
+
return {
|
|
908
|
+
accessToken: parsed.accessToken ?? null,
|
|
909
|
+
refreshToken: parsed.refreshToken ?? null,
|
|
910
|
+
user: parsed.user ?? null,
|
|
911
|
+
isAuthenticated: !!parsed.accessToken && !!parsed.user
|
|
912
|
+
};
|
|
913
|
+
} catch {
|
|
914
|
+
return {
|
|
915
|
+
accessToken: null,
|
|
916
|
+
refreshToken: null,
|
|
917
|
+
user: null,
|
|
918
|
+
isAuthenticated: false
|
|
919
|
+
};
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
function saveAuthToStorage(state) {
|
|
923
|
+
try {
|
|
924
|
+
const storage = getStorageAdapter();
|
|
925
|
+
const key = getStorageKey();
|
|
926
|
+
if (!state.accessToken) {
|
|
927
|
+
storage.removeItem(key);
|
|
928
|
+
} else {
|
|
929
|
+
storage.setItem(key, JSON.stringify(state));
|
|
930
|
+
}
|
|
931
|
+
} catch {
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
var AuthStore = class {
|
|
935
|
+
constructor() {
|
|
936
|
+
this.subscribers = /* @__PURE__ */ new Set();
|
|
937
|
+
this.state = loadAuthFromStorage();
|
|
938
|
+
}
|
|
939
|
+
/**
|
|
940
|
+
* Subscribe to state changes (Svelte store contract).
|
|
941
|
+
*/
|
|
942
|
+
subscribe(subscriber) {
|
|
943
|
+
this.subscribers.add(subscriber);
|
|
944
|
+
subscriber(this.state);
|
|
945
|
+
return () => this.subscribers.delete(subscriber);
|
|
946
|
+
}
|
|
947
|
+
notify() {
|
|
948
|
+
this.subscribers.forEach((sub) => sub(this.state));
|
|
949
|
+
}
|
|
950
|
+
// Getters for direct access
|
|
951
|
+
get accessToken() {
|
|
952
|
+
return this.state.accessToken;
|
|
953
|
+
}
|
|
954
|
+
get refreshToken() {
|
|
955
|
+
return this.state.refreshToken;
|
|
956
|
+
}
|
|
957
|
+
get user() {
|
|
958
|
+
return this.state.user;
|
|
959
|
+
}
|
|
960
|
+
get isAuthenticated() {
|
|
961
|
+
return this.state.isAuthenticated;
|
|
962
|
+
}
|
|
963
|
+
/**
|
|
964
|
+
* Get the current auth state snapshot.
|
|
965
|
+
*/
|
|
966
|
+
getState() {
|
|
967
|
+
return { ...this.state };
|
|
968
|
+
}
|
|
969
|
+
/**
|
|
970
|
+
* Set auth data after successful login.
|
|
971
|
+
*/
|
|
972
|
+
setAuth(accessToken, refreshToken, user, sessionToken) {
|
|
973
|
+
const jwtPayload = decodeJWT(accessToken);
|
|
974
|
+
const userWithPermissions = {
|
|
975
|
+
...user,
|
|
976
|
+
permissions: user.permissions || jwtPayload?.permissions || [],
|
|
977
|
+
roles: user.roles || jwtPayload?.roles || (user.role ? [user.role] : [])
|
|
978
|
+
};
|
|
979
|
+
this.state = {
|
|
980
|
+
accessToken,
|
|
981
|
+
refreshToken,
|
|
982
|
+
user: userWithPermissions,
|
|
983
|
+
isAuthenticated: true
|
|
984
|
+
};
|
|
985
|
+
saveAuthToStorage(this.state);
|
|
986
|
+
if (sessionToken && typeof window !== "undefined") {
|
|
987
|
+
getStorageAdapter().setItem("sessionToken", sessionToken);
|
|
988
|
+
}
|
|
989
|
+
if (typeof window !== "undefined") {
|
|
990
|
+
window.dispatchEvent(
|
|
991
|
+
new CustomEvent("auth:login", {
|
|
992
|
+
detail: { user: userWithPermissions }
|
|
993
|
+
})
|
|
994
|
+
);
|
|
995
|
+
}
|
|
996
|
+
this.notify();
|
|
997
|
+
}
|
|
998
|
+
/**
|
|
999
|
+
* Update tokens (e.g., after refresh).
|
|
1000
|
+
*/
|
|
1001
|
+
updateTokens(accessToken, refreshToken) {
|
|
1002
|
+
const jwtPayload = decodeJWT(accessToken);
|
|
1003
|
+
const updatedUser = this.state.user ? {
|
|
1004
|
+
...this.state.user,
|
|
1005
|
+
permissions: jwtPayload?.permissions || this.state.user.permissions || [],
|
|
1006
|
+
roles: jwtPayload?.roles || this.state.user.roles || (this.state.user.role ? [this.state.user.role] : [])
|
|
1007
|
+
} : null;
|
|
1008
|
+
this.state = {
|
|
1009
|
+
...this.state,
|
|
1010
|
+
accessToken,
|
|
1011
|
+
refreshToken,
|
|
1012
|
+
user: updatedUser
|
|
1013
|
+
};
|
|
1014
|
+
saveAuthToStorage(this.state);
|
|
1015
|
+
if (typeof window !== "undefined") {
|
|
1016
|
+
window.dispatchEvent(new CustomEvent("auth:token-refresh"));
|
|
1017
|
+
}
|
|
1018
|
+
this.notify();
|
|
1019
|
+
}
|
|
1020
|
+
/**
|
|
1021
|
+
* Update user data (e.g., after profile update).
|
|
1022
|
+
*/
|
|
1023
|
+
updateUser(user) {
|
|
1024
|
+
const jwtPayload = this.state.accessToken ? decodeJWT(this.state.accessToken) : null;
|
|
1025
|
+
const updatedUser = {
|
|
1026
|
+
...user,
|
|
1027
|
+
permissions: user.permissions || jwtPayload?.permissions || [],
|
|
1028
|
+
roles: user.roles || jwtPayload?.roles || (user.role ? [user.role] : [])
|
|
1029
|
+
};
|
|
1030
|
+
this.state = {
|
|
1031
|
+
...this.state,
|
|
1032
|
+
user: updatedUser
|
|
1033
|
+
};
|
|
1034
|
+
saveAuthToStorage(this.state);
|
|
1035
|
+
if (typeof window !== "undefined") {
|
|
1036
|
+
window.dispatchEvent(
|
|
1037
|
+
new CustomEvent("auth:profile-update", {
|
|
1038
|
+
detail: { user: updatedUser }
|
|
1039
|
+
})
|
|
1040
|
+
);
|
|
1041
|
+
}
|
|
1042
|
+
this.notify();
|
|
1043
|
+
}
|
|
1044
|
+
/**
|
|
1045
|
+
* Clear auth state (logout).
|
|
1046
|
+
*/
|
|
1047
|
+
logout() {
|
|
1048
|
+
this.state = {
|
|
1049
|
+
accessToken: null,
|
|
1050
|
+
refreshToken: null,
|
|
1051
|
+
user: null,
|
|
1052
|
+
isAuthenticated: false
|
|
1053
|
+
};
|
|
1054
|
+
const storage = getStorageAdapter();
|
|
1055
|
+
storage.removeItem(getStorageKey());
|
|
1056
|
+
storage.removeItem("sessionToken");
|
|
1057
|
+
if (typeof window !== "undefined") {
|
|
1058
|
+
window.dispatchEvent(new CustomEvent("auth:logout"));
|
|
1059
|
+
}
|
|
1060
|
+
if (isInitialized()) {
|
|
1061
|
+
getConfig().onLogout?.();
|
|
1062
|
+
}
|
|
1063
|
+
this.notify();
|
|
1064
|
+
}
|
|
1065
|
+
/**
|
|
1066
|
+
* Check if user has a specific permission.
|
|
1067
|
+
*/
|
|
1068
|
+
hasPermission(permission) {
|
|
1069
|
+
return this.state.user?.permissions?.includes(permission) ?? false;
|
|
1070
|
+
}
|
|
1071
|
+
/**
|
|
1072
|
+
* Check if user has a specific role.
|
|
1073
|
+
*/
|
|
1074
|
+
hasRole(role) {
|
|
1075
|
+
return this.state.user?.roles?.includes(role) ?? false;
|
|
1076
|
+
}
|
|
1077
|
+
/**
|
|
1078
|
+
* Check if user has any of the specified roles.
|
|
1079
|
+
*/
|
|
1080
|
+
hasAnyRole(roles) {
|
|
1081
|
+
return roles.some((role) => this.state.user?.roles?.includes(role)) ?? false;
|
|
1082
|
+
}
|
|
1083
|
+
/**
|
|
1084
|
+
* Check if user has all of the specified roles.
|
|
1085
|
+
*/
|
|
1086
|
+
hasAllRoles(roles) {
|
|
1087
|
+
return roles.every((role) => this.state.user?.roles?.includes(role)) ?? false;
|
|
1088
|
+
}
|
|
1089
|
+
/**
|
|
1090
|
+
* Check if user has any of the specified permissions.
|
|
1091
|
+
*/
|
|
1092
|
+
hasAnyPermission(permissions) {
|
|
1093
|
+
return permissions.some((perm) => this.state.user?.permissions?.includes(perm)) ?? false;
|
|
1094
|
+
}
|
|
1095
|
+
/**
|
|
1096
|
+
* Check if user has all of the specified permissions.
|
|
1097
|
+
*/
|
|
1098
|
+
hasAllPermissions(permissions) {
|
|
1099
|
+
return permissions.every((perm) => this.state.user?.permissions?.includes(perm)) ?? false;
|
|
1100
|
+
}
|
|
1101
|
+
/**
|
|
1102
|
+
* Re-hydrate state from storage (useful after config changes).
|
|
1103
|
+
*/
|
|
1104
|
+
rehydrate() {
|
|
1105
|
+
this.state = loadAuthFromStorage();
|
|
1106
|
+
this.notify();
|
|
1107
|
+
}
|
|
1108
|
+
};
|
|
1109
|
+
var authStore = new AuthStore();
|
|
1110
|
+
var authActions = {
|
|
1111
|
+
setAuth: (accessToken, refreshToken, user, sessionToken) => authStore.setAuth(accessToken, refreshToken, user, sessionToken),
|
|
1112
|
+
updateTokens: (accessToken, refreshToken) => authStore.updateTokens(accessToken, refreshToken),
|
|
1113
|
+
updateUser: (user) => authStore.updateUser(user),
|
|
1114
|
+
logout: () => authStore.logout(),
|
|
1115
|
+
hasPermission: (permission) => authStore.hasPermission(permission),
|
|
1116
|
+
hasRole: (role) => authStore.hasRole(role),
|
|
1117
|
+
hasAnyRole: (roles) => authStore.hasAnyRole(roles),
|
|
1118
|
+
hasAllRoles: (roles) => authStore.hasAllRoles(roles),
|
|
1119
|
+
hasAnyPermission: (permissions) => authStore.hasAnyPermission(permissions),
|
|
1120
|
+
hasAllPermissions: (permissions) => authStore.hasAllPermissions(permissions),
|
|
1121
|
+
rehydrate: () => authStore.rehydrate()
|
|
1122
|
+
};
|
|
1123
|
+
var isAuthenticated = {
|
|
1124
|
+
subscribe: (subscriber) => {
|
|
1125
|
+
return authStore.subscribe((state) => subscriber(state.isAuthenticated));
|
|
1126
|
+
}
|
|
1127
|
+
};
|
|
1128
|
+
var currentUser = {
|
|
1129
|
+
subscribe: (subscriber) => {
|
|
1130
|
+
return authStore.subscribe((state) => subscriber(state.user));
|
|
1131
|
+
}
|
|
1132
|
+
};
|
|
1133
|
+
|
|
1134
|
+
// src/svelte/guards/auth-guard.ts
|
|
1135
|
+
function checkAuth(options = {}) {
|
|
1136
|
+
const { roles, permissions, requireAllRoles, requireAllPermissions } = options;
|
|
1137
|
+
if (!authStore.isAuthenticated) {
|
|
1138
|
+
return {
|
|
1139
|
+
allowed: false,
|
|
1140
|
+
reason: "not_authenticated",
|
|
1141
|
+
redirectTo: "/login"
|
|
1142
|
+
};
|
|
1143
|
+
}
|
|
1144
|
+
if (roles && roles.length > 0) {
|
|
1145
|
+
const hasRoles = requireAllRoles ? authStore.hasAllRoles(roles) : authStore.hasAnyRole(roles);
|
|
1146
|
+
if (!hasRoles) {
|
|
1147
|
+
return {
|
|
1148
|
+
allowed: false,
|
|
1149
|
+
reason: "missing_role",
|
|
1150
|
+
redirectTo: "/unauthorized"
|
|
1151
|
+
};
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
if (permissions && permissions.length > 0) {
|
|
1155
|
+
const hasPermissions = requireAllPermissions ? authStore.hasAllPermissions(permissions) : authStore.hasAnyPermission(permissions);
|
|
1156
|
+
if (!hasPermissions) {
|
|
1157
|
+
return {
|
|
1158
|
+
allowed: false,
|
|
1159
|
+
reason: "missing_permission",
|
|
1160
|
+
redirectTo: "/unauthorized"
|
|
1161
|
+
};
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
return { allowed: true };
|
|
1165
|
+
}
|
|
1166
|
+
function createAuthGuard(options = {}) {
|
|
1167
|
+
return (onDenied) => {
|
|
1168
|
+
const result = checkAuth(options);
|
|
1169
|
+
if (!result.allowed) {
|
|
1170
|
+
onDenied(result.redirectTo ?? "/login", result.reason ?? "not_authenticated");
|
|
1171
|
+
return false;
|
|
1172
|
+
}
|
|
1173
|
+
return true;
|
|
1174
|
+
};
|
|
1175
|
+
}
|
|
1176
|
+
function requireAuth() {
|
|
1177
|
+
return checkAuth();
|
|
1178
|
+
}
|
|
1179
|
+
function requireRole(roles, requireAll = false) {
|
|
1180
|
+
const roleArray = Array.isArray(roles) ? roles : [roles];
|
|
1181
|
+
return checkAuth({ roles: roleArray, requireAllRoles: requireAll });
|
|
1182
|
+
}
|
|
1183
|
+
function requirePermission(permissions, requireAll = false) {
|
|
1184
|
+
const permArray = Array.isArray(permissions) ? permissions : [permissions];
|
|
1185
|
+
return checkAuth({ permissions: permArray, requireAllPermissions: requireAll });
|
|
1186
|
+
}
|
|
1187
|
+
function protectedLoad(options, loadFn) {
|
|
1188
|
+
return async (event) => {
|
|
1189
|
+
const result = checkAuth(options);
|
|
1190
|
+
if (!result.allowed) {
|
|
1191
|
+
return { redirect: result.redirectTo ?? "/login" };
|
|
1192
|
+
}
|
|
1193
|
+
return loadFn(event);
|
|
1194
|
+
};
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
export { AuthService, api, apiRequest, authActions, authApi, authService, authStore, checkAuth, clearStoredAuth, createAuthGuard, currentUser, decodeJWT, extractClaims, extractData, getAccessToken, getConfig, getDefaultStorage, getFetch, getRefreshToken, getSessionToken, getStorage, getTokenExpiration, getTokenRemainingTime, initAuth, isAuthenticated, isInitialized, isTokenExpired, protectedLoad, requireAuth, requirePermission, requireRole, resetConfig, updateStoredTokens };
|