@strands.gg/accui 2.15.16 → 2.17.9
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/dist/StrandsUIPlugin-Bwc7jBcb.cjs.js +143 -0
- package/dist/StrandsUIPlugin-RTFzvRED.es.js +20536 -0
- package/dist/accui.css +1 -1
- package/dist/index.cjs.js +5 -1
- package/dist/index.es.js +8805 -12557
- package/dist/nuxt/module.cjs.js +23 -1
- package/dist/nuxt/module.es.js +58 -82
- package/dist/nuxt/runtime/composables/useAuthenticatedFetch.cjs.js +1 -1
- package/dist/nuxt/runtime/composables/useAuthenticatedFetch.es.js +82 -111
- package/dist/nuxt/runtime/composables/useStrandsAuth.cjs.js +1 -1
- package/dist/nuxt/runtime/composables/useStrandsAuth.es.js +30 -46
- package/dist/nuxt/runtime/middleware/auth.global.cjs.js +1 -1
- package/dist/nuxt/runtime/middleware/auth.global.es.js +28 -44
- package/dist/nuxt/runtime/plugin.client.cjs.js +1 -1
- package/dist/nuxt/runtime/plugin.client.es.js +10 -19
- package/dist/nuxt/runtime/plugin.server.cjs.js +1 -1
- package/dist/nuxt/runtime/plugin.server.es.js +8 -10
- package/dist/nuxt/runtime/plugins/auth-interceptor.client.cjs.js +1 -1
- package/dist/nuxt/runtime/plugins/auth-interceptor.client.es.js +27 -48
- package/dist/nuxt.cjs.js +1 -1
- package/dist/nuxt.es.js +9 -9
- package/dist/useStrandsAuth-BVqpuhrO.cjs.js +1 -0
- package/dist/useStrandsAuth-Bv61_QkS.es.js +653 -0
- package/dist/useStrandsConfig-Cd22bj_E.es.js +209 -0
- package/dist/useStrandsConfig-D7a0QXz3.cjs.js +1 -0
- package/dist/vite.cjs.js +29 -1
- package/dist/vite.es.js +58 -75
- package/dist/vue/utils/modalStack.d.ts +3 -0
- package/package.json +2 -2
- package/dist/StrandsUIPlugin-BRGzLy44.cjs.js +0 -1
- package/dist/StrandsUIPlugin-DtGqMkbS.es.js +0 -26371
- package/dist/useStrandsAuth-BCnUxo-R.es.js +0 -915
- package/dist/useStrandsAuth-DoJxpNLb.cjs.js +0 -1
- package/dist/useStrandsConfig-Sr6NG90B.cjs.js +0 -1
- package/dist/useStrandsConfig-fRu-OG08.es.js +0 -248
|
@@ -1,915 +0,0 @@
|
|
|
1
|
-
import { computed, ref, getCurrentInstance, onUnmounted } from "vue";
|
|
2
|
-
import { u as useStrandsConfig } from "./useStrandsConfig-fRu-OG08.es.js";
|
|
3
|
-
class RequestCache {
|
|
4
|
-
cache = /* @__PURE__ */ new Map();
|
|
5
|
-
DEFAULT_TTL = 5 * 60 * 1e3;
|
|
6
|
-
// 5 minutes
|
|
7
|
-
/**
|
|
8
|
-
* Memoized fetch - prevents duplicate requests and caches results
|
|
9
|
-
*/
|
|
10
|
-
async fetch(key, fetcher, ttl = this.DEFAULT_TTL) {
|
|
11
|
-
const now = Date.now();
|
|
12
|
-
const entry = this.cache.get(key);
|
|
13
|
-
if (entry && now - entry.timestamp < entry.ttl) {
|
|
14
|
-
return entry.promise;
|
|
15
|
-
}
|
|
16
|
-
this.cleanExpired();
|
|
17
|
-
const promise = fetcher().finally(() => {
|
|
18
|
-
setTimeout(() => {
|
|
19
|
-
this.cache.delete(key);
|
|
20
|
-
}, ttl);
|
|
21
|
-
});
|
|
22
|
-
this.cache.set(key, {
|
|
23
|
-
promise,
|
|
24
|
-
timestamp: now,
|
|
25
|
-
ttl
|
|
26
|
-
});
|
|
27
|
-
return promise;
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* Clear all cached entries
|
|
31
|
-
*/
|
|
32
|
-
clear() {
|
|
33
|
-
this.cache.clear();
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
|
-
* Remove a specific cache entry
|
|
37
|
-
*/
|
|
38
|
-
invalidate(key) {
|
|
39
|
-
this.cache.delete(key);
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* Clean expired cache entries
|
|
43
|
-
*/
|
|
44
|
-
cleanExpired() {
|
|
45
|
-
const now = Date.now();
|
|
46
|
-
for (const [key, entry] of this.cache.entries()) {
|
|
47
|
-
if (now - entry.timestamp > entry.ttl) {
|
|
48
|
-
this.cache.delete(key);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* Get cache statistics (for debugging)
|
|
54
|
-
*/
|
|
55
|
-
getStats() {
|
|
56
|
-
return {
|
|
57
|
-
size: this.cache.size,
|
|
58
|
-
entries: Array.from(this.cache.keys())
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
const requestCache = new RequestCache();
|
|
63
|
-
function useRequestCache() {
|
|
64
|
-
return {
|
|
65
|
-
fetch: requestCache.fetch.bind(requestCache),
|
|
66
|
-
clear: requestCache.clear.bind(requestCache),
|
|
67
|
-
invalidate: requestCache.invalidate.bind(requestCache),
|
|
68
|
-
getStats: requestCache.getStats.bind(requestCache)
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
function debounce(func, wait) {
|
|
72
|
-
let timeout = null;
|
|
73
|
-
return (...args) => {
|
|
74
|
-
if (timeout) {
|
|
75
|
-
clearTimeout(timeout);
|
|
76
|
-
}
|
|
77
|
-
timeout = setTimeout(() => {
|
|
78
|
-
func(...args);
|
|
79
|
-
}, wait);
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
const debouncedSetItem = debounce((key, value) => {
|
|
83
|
-
if (typeof window !== "undefined") {
|
|
84
|
-
localStorage.setItem(key, value);
|
|
85
|
-
}
|
|
86
|
-
}, 300);
|
|
87
|
-
const mapApiUserToFrontend = (apiUser) => {
|
|
88
|
-
return {
|
|
89
|
-
id: apiUser.id,
|
|
90
|
-
email: apiUser.email,
|
|
91
|
-
firstName: apiUser.first_name || apiUser.firstName || "",
|
|
92
|
-
lastName: apiUser.last_name || apiUser.lastName || "",
|
|
93
|
-
avatar: apiUser.avatar_url || apiUser.avatar,
|
|
94
|
-
mfaEnabled: apiUser.mfa_enabled ?? apiUser.mfaEnabled ?? false,
|
|
95
|
-
emailVerified: apiUser.email_verified ?? apiUser.emailVerified ?? false,
|
|
96
|
-
passwordUpdatedAt: apiUser.password_updated_at || apiUser.passwordUpdatedAt,
|
|
97
|
-
settings: apiUser.settings || {},
|
|
98
|
-
xp: apiUser.xp || 0,
|
|
99
|
-
level: apiUser.level || 1,
|
|
100
|
-
next_level_xp: apiUser.next_level_xp || apiUser.next_level_xp || 4,
|
|
101
|
-
username: apiUser.username,
|
|
102
|
-
usernameLastChangedAt: apiUser.username_last_changed_at || apiUser.usernameLastChangedAt,
|
|
103
|
-
createdAt: apiUser.created_at || apiUser.createdAt,
|
|
104
|
-
updatedAt: apiUser.updated_at || apiUser.updatedAt || (/* @__PURE__ */ new Date()).toISOString()
|
|
105
|
-
};
|
|
106
|
-
};
|
|
107
|
-
const globalState = {
|
|
108
|
-
currentUser: ref(null),
|
|
109
|
-
currentSession: ref(null),
|
|
110
|
-
loadingStates: ref({
|
|
111
|
-
initializing: true,
|
|
112
|
-
signingIn: false,
|
|
113
|
-
signingUp: false,
|
|
114
|
-
signingOut: false,
|
|
115
|
-
refreshingToken: false,
|
|
116
|
-
sendingMfaEmail: false,
|
|
117
|
-
verifyingMfa: false,
|
|
118
|
-
loadingProfile: false
|
|
119
|
-
}),
|
|
120
|
-
isInitialized: ref(false),
|
|
121
|
-
mfaRequired: ref(false),
|
|
122
|
-
mfaSessionId: ref(null),
|
|
123
|
-
availableMfaMethods: ref([])
|
|
124
|
-
};
|
|
125
|
-
let refreshTimer = null;
|
|
126
|
-
let refreshPromise = null;
|
|
127
|
-
function useStrandsAuth() {
|
|
128
|
-
const { getUrl, config } = useStrandsConfig();
|
|
129
|
-
const { fetch: cachedFetch, clear: clearCache, invalidate } = useRequestCache();
|
|
130
|
-
const { currentUser, currentSession, loadingStates, isInitialized, mfaRequired, mfaSessionId, availableMfaMethods } = globalState;
|
|
131
|
-
const handleAuthLoss = () => {
|
|
132
|
-
currentUser.value = null;
|
|
133
|
-
currentSession.value = null;
|
|
134
|
-
mfaRequired.value = false;
|
|
135
|
-
mfaSessionId.value = null;
|
|
136
|
-
availableMfaMethods.value = [];
|
|
137
|
-
if (typeof window !== "undefined") {
|
|
138
|
-
localStorage.removeItem("strands_auth_session");
|
|
139
|
-
localStorage.removeItem("strands_auth_user");
|
|
140
|
-
}
|
|
141
|
-
stopTokenRefreshTimer();
|
|
142
|
-
refreshPromise = null;
|
|
143
|
-
clearCache();
|
|
144
|
-
if (typeof window !== "undefined" && config.value?.onSignOutUrl) {
|
|
145
|
-
const currentPath = window.location.pathname + window.location.search;
|
|
146
|
-
const signOutUrl = config.value.onSignOutUrl;
|
|
147
|
-
if (currentPath !== signOutUrl) {
|
|
148
|
-
window.location.href = signOutUrl;
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
};
|
|
152
|
-
const isInitializing = computed(() => loadingStates.value.initializing);
|
|
153
|
-
const isSigningIn = computed(() => loadingStates.value.signingIn);
|
|
154
|
-
const isSigningUp = computed(() => loadingStates.value.signingUp);
|
|
155
|
-
const isSigningOut = computed(() => loadingStates.value.signingOut);
|
|
156
|
-
const isRefreshingToken = computed(() => loadingStates.value.refreshingToken);
|
|
157
|
-
const isSendingMfaEmail = computed(() => loadingStates.value.sendingMfaEmail);
|
|
158
|
-
const isVerifyingMfa = computed(() => loadingStates.value.verifyingMfa);
|
|
159
|
-
computed(() => loadingStates.value.loadingProfile);
|
|
160
|
-
const loading = computed(
|
|
161
|
-
() => loadingStates.value.signingIn || loadingStates.value.signingUp || loadingStates.value.signingOut || loadingStates.value.refreshingToken || loadingStates.value.sendingMfaEmail || loadingStates.value.verifyingMfa || loadingStates.value.loadingProfile
|
|
162
|
-
);
|
|
163
|
-
const isLoading = computed(() => loadingStates.value.initializing || loading.value);
|
|
164
|
-
const loadingMessage = computed(() => {
|
|
165
|
-
const states = loadingStates.value;
|
|
166
|
-
if (states.initializing) return "Checking authentication...";
|
|
167
|
-
if (states.signingIn) return "Signing you in...";
|
|
168
|
-
if (states.signingUp) return "Creating your account...";
|
|
169
|
-
if (states.signingOut) return "Signing you out...";
|
|
170
|
-
if (states.refreshingToken) return "Refreshing session...";
|
|
171
|
-
if (states.sendingMfaEmail) return "Sending verification code...";
|
|
172
|
-
if (states.verifyingMfa) return "Verifying code...";
|
|
173
|
-
if (states.loadingProfile) return "Loading profile...";
|
|
174
|
-
return "Loading...";
|
|
175
|
-
});
|
|
176
|
-
const getAuthHeaders = () => {
|
|
177
|
-
const headers = {};
|
|
178
|
-
if (currentSession.value?.accessToken) {
|
|
179
|
-
headers["Authorization"] = `Bearer ${currentSession.value.accessToken}`;
|
|
180
|
-
}
|
|
181
|
-
if (currentSession.value?.refreshToken) {
|
|
182
|
-
headers["x-refresh-token"] = currentSession.value.refreshToken;
|
|
183
|
-
}
|
|
184
|
-
return headers;
|
|
185
|
-
};
|
|
186
|
-
const completeHardwareKeyRegistration = async (deviceId, credential, accessToken) => {
|
|
187
|
-
const response = await fetch(getUrl("mfaHardwareCompleteRegistration"), {
|
|
188
|
-
method: "POST",
|
|
189
|
-
headers: {
|
|
190
|
-
"Content-Type": "application/json",
|
|
191
|
-
"Authorization": `Bearer ${currentSession.value.accessToken}`
|
|
192
|
-
},
|
|
193
|
-
body: JSON.stringify({
|
|
194
|
-
device_id: deviceId,
|
|
195
|
-
credential
|
|
196
|
-
})
|
|
197
|
-
});
|
|
198
|
-
if (!response.ok) {
|
|
199
|
-
const errorText = await response.text();
|
|
200
|
-
let errorMessage = "Failed to complete hardware key registration";
|
|
201
|
-
try {
|
|
202
|
-
const errorData = JSON.parse(errorText);
|
|
203
|
-
errorMessage = errorData.message || errorData.error || errorText;
|
|
204
|
-
} catch {
|
|
205
|
-
errorMessage = errorText || "Failed to complete hardware key registration";
|
|
206
|
-
}
|
|
207
|
-
throw new Error(errorMessage);
|
|
208
|
-
}
|
|
209
|
-
return response.json();
|
|
210
|
-
};
|
|
211
|
-
const registerHardwareKey = async (deviceName, accessToken, deviceType = "hardware") => {
|
|
212
|
-
const response = await fetch(getUrl("mfaHardwareStartRegistration"), {
|
|
213
|
-
method: "POST",
|
|
214
|
-
headers: {
|
|
215
|
-
"Content-Type": "application/json",
|
|
216
|
-
"Authorization": `Bearer ${currentSession.value.accessToken}`
|
|
217
|
-
},
|
|
218
|
-
body: JSON.stringify({
|
|
219
|
-
device_name: deviceName,
|
|
220
|
-
device_type: deviceType
|
|
221
|
-
})
|
|
222
|
-
});
|
|
223
|
-
if (!response.ok) {
|
|
224
|
-
const errorText = await response.text();
|
|
225
|
-
let errorMessage = "Failed to start hardware key registration";
|
|
226
|
-
try {
|
|
227
|
-
const errorData = JSON.parse(errorText);
|
|
228
|
-
errorMessage = errorData.message || errorData.error || errorText;
|
|
229
|
-
} catch {
|
|
230
|
-
errorMessage = errorText || "Failed to start hardware key registration";
|
|
231
|
-
}
|
|
232
|
-
throw new Error(errorMessage);
|
|
233
|
-
}
|
|
234
|
-
return response.json();
|
|
235
|
-
};
|
|
236
|
-
const isAuthenticated = computed(() => currentUser.value !== null);
|
|
237
|
-
const signIn = async (credentials) => {
|
|
238
|
-
loadingStates.value.signingIn = true;
|
|
239
|
-
try {
|
|
240
|
-
mfaRequired.value = false;
|
|
241
|
-
mfaSessionId.value = null;
|
|
242
|
-
availableMfaMethods.value = [];
|
|
243
|
-
const headers = {
|
|
244
|
-
"Content-Type": "application/json"
|
|
245
|
-
};
|
|
246
|
-
if (typeof window !== "undefined" && window.location) {
|
|
247
|
-
headers["Origin"] = window.location.origin;
|
|
248
|
-
}
|
|
249
|
-
const response = await fetch(getUrl("signIn"), {
|
|
250
|
-
method: "POST",
|
|
251
|
-
headers,
|
|
252
|
-
body: JSON.stringify(credentials)
|
|
253
|
-
});
|
|
254
|
-
if (!response.ok) {
|
|
255
|
-
if (response.status === 401) {
|
|
256
|
-
throw new Error("Invalid email or password");
|
|
257
|
-
} else if (response.status === 403) {
|
|
258
|
-
throw new Error("Please verify your email address before signing in");
|
|
259
|
-
} else {
|
|
260
|
-
throw new Error(`Sign in failed: ${response.status} ${response.statusText}`);
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
const authData = await response.json();
|
|
264
|
-
if (authData.mfa_required) {
|
|
265
|
-
mfaRequired.value = true;
|
|
266
|
-
mfaSessionId.value = authData.mfa_session_id || null;
|
|
267
|
-
const methods = (authData.available_mfa_methods || []).map((method) => {
|
|
268
|
-
let fallbackName = `${method.device_type.charAt(0).toUpperCase() + method.device_type.slice(1)} Authentication`;
|
|
269
|
-
if (method.device_type === "hardware") {
|
|
270
|
-
fallbackName = method.device_name || "Security Key";
|
|
271
|
-
} else if (method.device_type === "totp") {
|
|
272
|
-
fallbackName = method.device_name || "Authenticator App";
|
|
273
|
-
} else if (method.device_type === "email") {
|
|
274
|
-
fallbackName = method.device_name || "Email Verification";
|
|
275
|
-
}
|
|
276
|
-
return {
|
|
277
|
-
id: method.device_id,
|
|
278
|
-
device_type: method.device_type,
|
|
279
|
-
device_name: method.device_name || fallbackName,
|
|
280
|
-
is_active: true,
|
|
281
|
-
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
282
|
-
last_used_at: method.last_used_at,
|
|
283
|
-
// Pass through additional metadata if available
|
|
284
|
-
credential_id: method.credential_id,
|
|
285
|
-
device_info: method.device_info
|
|
286
|
-
};
|
|
287
|
-
});
|
|
288
|
-
availableMfaMethods.value = methods;
|
|
289
|
-
loadingStates.value.signingIn = false;
|
|
290
|
-
return authData;
|
|
291
|
-
}
|
|
292
|
-
await setAuthData(authData);
|
|
293
|
-
return authData;
|
|
294
|
-
} catch (error) {
|
|
295
|
-
throw error;
|
|
296
|
-
} finally {
|
|
297
|
-
loadingStates.value.signingIn = false;
|
|
298
|
-
}
|
|
299
|
-
};
|
|
300
|
-
const signUp = async (userData) => {
|
|
301
|
-
loadingStates.value.signingUp = true;
|
|
302
|
-
try {
|
|
303
|
-
throw new Error("Sign up not implemented - please integrate with auth SDK");
|
|
304
|
-
} finally {
|
|
305
|
-
loadingStates.value.signingUp = false;
|
|
306
|
-
}
|
|
307
|
-
};
|
|
308
|
-
const signOut = async () => {
|
|
309
|
-
loadingStates.value.signingOut = true;
|
|
310
|
-
try {
|
|
311
|
-
stopTokenRefreshTimer();
|
|
312
|
-
refreshPromise = null;
|
|
313
|
-
clearCache();
|
|
314
|
-
currentUser.value = null;
|
|
315
|
-
currentSession.value = null;
|
|
316
|
-
mfaRequired.value = false;
|
|
317
|
-
mfaSessionId.value = null;
|
|
318
|
-
availableMfaMethods.value = [];
|
|
319
|
-
if (typeof window !== "undefined") {
|
|
320
|
-
localStorage.removeItem("strands_auth_session");
|
|
321
|
-
localStorage.removeItem("strands_auth_user");
|
|
322
|
-
}
|
|
323
|
-
} finally {
|
|
324
|
-
loadingStates.value.signingOut = false;
|
|
325
|
-
}
|
|
326
|
-
};
|
|
327
|
-
const refreshToken = async () => {
|
|
328
|
-
if (!currentSession.value?.refreshToken) {
|
|
329
|
-
return false;
|
|
330
|
-
}
|
|
331
|
-
if (refreshPromise) {
|
|
332
|
-
return await refreshPromise;
|
|
333
|
-
}
|
|
334
|
-
refreshPromise = (async () => {
|
|
335
|
-
loadingStates.value.refreshingToken = true;
|
|
336
|
-
try {
|
|
337
|
-
const response = await fetch(getUrl("refresh"), {
|
|
338
|
-
method: "POST",
|
|
339
|
-
headers: {
|
|
340
|
-
"Content-Type": "application/json"
|
|
341
|
-
},
|
|
342
|
-
body: JSON.stringify({
|
|
343
|
-
refresh_token: currentSession.value.refreshToken
|
|
344
|
-
})
|
|
345
|
-
});
|
|
346
|
-
if (!response.ok) {
|
|
347
|
-
if (response.status === 401) {
|
|
348
|
-
handleAuthLoss();
|
|
349
|
-
return false;
|
|
350
|
-
}
|
|
351
|
-
throw new Error(`Token refresh failed: ${response.status} ${response.statusText}`);
|
|
352
|
-
}
|
|
353
|
-
const tokenData = await response.json();
|
|
354
|
-
if (tokenData.user) {
|
|
355
|
-
currentUser.value = mapApiUserToFrontend(tokenData.user);
|
|
356
|
-
if (currentUser.value && typeof window !== "undefined") {
|
|
357
|
-
localStorage.setItem("strands_auth_user", JSON.stringify(currentUser.value));
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
const newSession = {
|
|
361
|
-
accessToken: tokenData.access_token,
|
|
362
|
-
refreshToken: tokenData.refresh_token,
|
|
363
|
-
expiresAt: new Date(Date.now() + 5 * 60 * 1e3),
|
|
364
|
-
// 5 minutes from now
|
|
365
|
-
userId: tokenData.user?.id || currentUser.value?.id
|
|
366
|
-
};
|
|
367
|
-
currentSession.value = newSession;
|
|
368
|
-
if (typeof window !== "undefined") {
|
|
369
|
-
localStorage.setItem("strands_auth_session", JSON.stringify(newSession));
|
|
370
|
-
}
|
|
371
|
-
startTokenRefreshTimer();
|
|
372
|
-
invalidate(`sessions:${currentSession.value.accessToken.slice(0, 20)}`);
|
|
373
|
-
return true;
|
|
374
|
-
} catch (error) {
|
|
375
|
-
handleAuthLoss();
|
|
376
|
-
return false;
|
|
377
|
-
} finally {
|
|
378
|
-
loadingStates.value.refreshingToken = false;
|
|
379
|
-
}
|
|
380
|
-
})();
|
|
381
|
-
const result = await refreshPromise;
|
|
382
|
-
refreshPromise = null;
|
|
383
|
-
return result;
|
|
384
|
-
};
|
|
385
|
-
const fetchProfile = async () => {
|
|
386
|
-
const cacheKey = `profile:${currentSession.value.accessToken.slice(0, 20)}`;
|
|
387
|
-
loadingStates.value.loadingProfile = true;
|
|
388
|
-
try {
|
|
389
|
-
return await cachedFetch(cacheKey, async () => {
|
|
390
|
-
const response = await fetch(getUrl("profile"), {
|
|
391
|
-
method: "GET",
|
|
392
|
-
headers: {
|
|
393
|
-
"Content-Type": "application/json",
|
|
394
|
-
"Authorization": `Bearer ${currentSession.value?.accessToken}`
|
|
395
|
-
}
|
|
396
|
-
});
|
|
397
|
-
if (!response.ok) {
|
|
398
|
-
if (response.status === 401) {
|
|
399
|
-
throw new Error("Authentication expired. Please sign in again.");
|
|
400
|
-
} else {
|
|
401
|
-
throw new Error(`Failed to fetch profile: ${response.status} ${response.statusText}`);
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
const userData = await response.json();
|
|
405
|
-
currentUser.value = mapApiUserToFrontend(userData);
|
|
406
|
-
if (currentUser.value && typeof window !== "undefined") {
|
|
407
|
-
localStorage.setItem("strands_auth_user", JSON.stringify(currentUser.value));
|
|
408
|
-
}
|
|
409
|
-
return currentUser.value;
|
|
410
|
-
});
|
|
411
|
-
} finally {
|
|
412
|
-
loadingStates.value.loadingProfile = false;
|
|
413
|
-
}
|
|
414
|
-
};
|
|
415
|
-
const updateProfile = async (profileData) => {
|
|
416
|
-
loadingStates.value.loadingProfile = true;
|
|
417
|
-
try {
|
|
418
|
-
const response = await fetch(getUrl("profile"), {
|
|
419
|
-
method: "POST",
|
|
420
|
-
headers: {
|
|
421
|
-
"Content-Type": "application/json",
|
|
422
|
-
"Authorization": `Bearer ${currentSession.value.accessToken}`
|
|
423
|
-
},
|
|
424
|
-
body: JSON.stringify({
|
|
425
|
-
first_name: profileData.firstName,
|
|
426
|
-
last_name: profileData.lastName
|
|
427
|
-
})
|
|
428
|
-
});
|
|
429
|
-
if (!response.ok) {
|
|
430
|
-
if (response.status === 401) {
|
|
431
|
-
throw new Error("Authentication expired. Please sign in again.");
|
|
432
|
-
} else {
|
|
433
|
-
throw new Error(`Profile update failed: ${response.status} ${response.statusText}`);
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
const updatedUserData = await response.json();
|
|
437
|
-
currentUser.value = mapApiUserToFrontend(updatedUserData);
|
|
438
|
-
if (currentUser.value) {
|
|
439
|
-
debouncedSetItem("strands_auth_user", JSON.stringify(currentUser.value));
|
|
440
|
-
}
|
|
441
|
-
return currentUser.value;
|
|
442
|
-
} finally {
|
|
443
|
-
loadingStates.value.loadingProfile = false;
|
|
444
|
-
}
|
|
445
|
-
};
|
|
446
|
-
const updateUserSettings = async (settings) => {
|
|
447
|
-
loadingStates.value.loadingProfile = true;
|
|
448
|
-
try {
|
|
449
|
-
const response = await fetch(getUrl("settings"), {
|
|
450
|
-
method: "POST",
|
|
451
|
-
headers: {
|
|
452
|
-
"Content-Type": "application/json",
|
|
453
|
-
"Authorization": `Bearer ${currentSession.value.accessToken}`
|
|
454
|
-
},
|
|
455
|
-
body: JSON.stringify({
|
|
456
|
-
settings
|
|
457
|
-
})
|
|
458
|
-
});
|
|
459
|
-
if (!response.ok) {
|
|
460
|
-
if (response.status === 401) {
|
|
461
|
-
throw new Error("Authentication expired. Please sign in again.");
|
|
462
|
-
} else {
|
|
463
|
-
throw new Error(`Settings update failed: ${response.status} ${response.statusText}`);
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
const updatedUserData = await response.json();
|
|
467
|
-
currentUser.value = mapApiUserToFrontend(updatedUserData);
|
|
468
|
-
if (currentUser.value) {
|
|
469
|
-
debouncedSetItem("strands_auth_user", JSON.stringify(currentUser.value));
|
|
470
|
-
}
|
|
471
|
-
return currentUser.value;
|
|
472
|
-
} finally {
|
|
473
|
-
loadingStates.value.loadingProfile = false;
|
|
474
|
-
}
|
|
475
|
-
};
|
|
476
|
-
const changeEmail = async (newEmail, password) => {
|
|
477
|
-
loadingStates.value.loadingProfile = true;
|
|
478
|
-
try {
|
|
479
|
-
const response = await fetch(getUrl("changeEmail"), {
|
|
480
|
-
method: "POST",
|
|
481
|
-
headers: {
|
|
482
|
-
"Content-Type": "application/json",
|
|
483
|
-
"Authorization": `Bearer ${currentSession.value.accessToken}`
|
|
484
|
-
},
|
|
485
|
-
body: JSON.stringify({
|
|
486
|
-
new_email: newEmail,
|
|
487
|
-
password
|
|
488
|
-
})
|
|
489
|
-
});
|
|
490
|
-
if (!response.ok) {
|
|
491
|
-
if (response.status === 401) {
|
|
492
|
-
throw new Error("Authentication expired. Please sign in again.");
|
|
493
|
-
} else {
|
|
494
|
-
const errorData = await response.json().catch(() => ({}));
|
|
495
|
-
throw new Error(errorData.message || `Email change failed: ${response.status} ${response.statusText}`);
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
const result = await response.json();
|
|
499
|
-
if (currentUser.value) {
|
|
500
|
-
currentUser.value = {
|
|
501
|
-
...currentUser.value,
|
|
502
|
-
email: newEmail,
|
|
503
|
-
emailVerified: false,
|
|
504
|
-
// Email needs to be verified again
|
|
505
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
506
|
-
};
|
|
507
|
-
if (typeof window !== "undefined") {
|
|
508
|
-
localStorage.setItem("strands_auth_user", JSON.stringify(currentUser.value));
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
return result;
|
|
512
|
-
} finally {
|
|
513
|
-
loadingStates.value.loadingProfile = false;
|
|
514
|
-
}
|
|
515
|
-
};
|
|
516
|
-
const verifyMfa = async (deviceId, code, isBackupCode = false) => {
|
|
517
|
-
if (!mfaSessionId.value) {
|
|
518
|
-
throw new Error("No MFA session available");
|
|
519
|
-
}
|
|
520
|
-
loadingStates.value.verifyingMfa = true;
|
|
521
|
-
try {
|
|
522
|
-
const endpoint = isBackupCode ? getUrl("mfaBackupCodeVerify") : getUrl("mfaSigninVerify");
|
|
523
|
-
const body = isBackupCode ? { mfa_session_id: mfaSessionId.value, backup_code: code } : { mfa_session_id: mfaSessionId.value, device_id: deviceId, code };
|
|
524
|
-
const response = await fetch(endpoint, {
|
|
525
|
-
method: "POST",
|
|
526
|
-
headers: { "Content-Type": "application/json" },
|
|
527
|
-
body: JSON.stringify(body)
|
|
528
|
-
});
|
|
529
|
-
if (!response.ok) {
|
|
530
|
-
const errorText = await response.text();
|
|
531
|
-
let errorMessage = "MFA verification failed";
|
|
532
|
-
try {
|
|
533
|
-
const errorData = JSON.parse(errorText);
|
|
534
|
-
errorMessage = errorData.message || errorData.error || errorText;
|
|
535
|
-
} catch {
|
|
536
|
-
errorMessage = errorText || "MFA verification failed";
|
|
537
|
-
}
|
|
538
|
-
throw new Error(errorMessage);
|
|
539
|
-
}
|
|
540
|
-
const authData = await response.json();
|
|
541
|
-
mfaRequired.value = false;
|
|
542
|
-
mfaSessionId.value = null;
|
|
543
|
-
availableMfaMethods.value = [];
|
|
544
|
-
await setAuthData(authData);
|
|
545
|
-
return authData;
|
|
546
|
-
} finally {
|
|
547
|
-
loadingStates.value.verifyingMfa = false;
|
|
548
|
-
}
|
|
549
|
-
};
|
|
550
|
-
const sendMfaEmailCode = async (deviceId) => {
|
|
551
|
-
if (!mfaSessionId.value) {
|
|
552
|
-
throw new Error("No MFA session available");
|
|
553
|
-
}
|
|
554
|
-
loadingStates.value.sendingMfaEmail = true;
|
|
555
|
-
try {
|
|
556
|
-
const response = await fetch(getUrl("mfaSigninSendEmail"), {
|
|
557
|
-
method: "POST",
|
|
558
|
-
headers: { "Content-Type": "application/json" },
|
|
559
|
-
body: JSON.stringify({
|
|
560
|
-
mfa_session_id: mfaSessionId.value,
|
|
561
|
-
device_id: deviceId
|
|
562
|
-
})
|
|
563
|
-
});
|
|
564
|
-
if (!response.ok) {
|
|
565
|
-
const errorText = await response.text();
|
|
566
|
-
let errorMessage = "Failed to send MFA email code";
|
|
567
|
-
try {
|
|
568
|
-
const errorData = JSON.parse(errorText);
|
|
569
|
-
errorMessage = errorData.message || errorData.error || errorText;
|
|
570
|
-
} catch {
|
|
571
|
-
errorMessage = errorText || "Failed to send MFA email code";
|
|
572
|
-
}
|
|
573
|
-
throw new Error(errorMessage);
|
|
574
|
-
}
|
|
575
|
-
const result = await response.json();
|
|
576
|
-
return result;
|
|
577
|
-
} finally {
|
|
578
|
-
loadingStates.value.sendingMfaEmail = false;
|
|
579
|
-
}
|
|
580
|
-
};
|
|
581
|
-
const getMfaWebAuthnChallenge = async (deviceId) => {
|
|
582
|
-
if (!mfaSessionId.value) {
|
|
583
|
-
throw new Error("No MFA session available");
|
|
584
|
-
}
|
|
585
|
-
const response = await fetch(getUrl("mfaWebAuthnChallenge"), {
|
|
586
|
-
method: "POST",
|
|
587
|
-
headers: { "Content-Type": "application/json" },
|
|
588
|
-
body: JSON.stringify({
|
|
589
|
-
mfa_session_id: mfaSessionId.value,
|
|
590
|
-
device_id: deviceId
|
|
591
|
-
})
|
|
592
|
-
});
|
|
593
|
-
if (!response.ok) {
|
|
594
|
-
const errorText = await response.text();
|
|
595
|
-
let errorMessage = "Failed to get WebAuthn challenge";
|
|
596
|
-
try {
|
|
597
|
-
const errorData = JSON.parse(errorText);
|
|
598
|
-
errorMessage = errorData.message || errorData.error || errorText;
|
|
599
|
-
} catch {
|
|
600
|
-
errorMessage = errorText || errorMessage;
|
|
601
|
-
}
|
|
602
|
-
throw new Error(errorMessage);
|
|
603
|
-
}
|
|
604
|
-
return response.json();
|
|
605
|
-
};
|
|
606
|
-
const setAuthData = async (authResponse) => {
|
|
607
|
-
try {
|
|
608
|
-
if (authResponse.user) {
|
|
609
|
-
currentUser.value = mapApiUserToFrontend(authResponse.user);
|
|
610
|
-
}
|
|
611
|
-
const session = {
|
|
612
|
-
accessToken: authResponse.access_token,
|
|
613
|
-
refreshToken: authResponse.refresh_token,
|
|
614
|
-
expiresAt: new Date(Date.now() + 5 * 60 * 1e3),
|
|
615
|
-
// 5 minutes from now (matching API token expiry)
|
|
616
|
-
userId: currentUser.value?.id || authResponse.user?.id
|
|
617
|
-
};
|
|
618
|
-
currentSession.value = session;
|
|
619
|
-
if (typeof window !== "undefined") {
|
|
620
|
-
localStorage.setItem("strands_auth_session", JSON.stringify(session));
|
|
621
|
-
if (currentUser.value) {
|
|
622
|
-
localStorage.setItem("strands_auth_user", JSON.stringify(currentUser.value));
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
startTokenRefreshTimer();
|
|
626
|
-
} catch (error) {
|
|
627
|
-
}
|
|
628
|
-
};
|
|
629
|
-
const startTokenRefreshTimer = () => {
|
|
630
|
-
if (refreshTimer) {
|
|
631
|
-
clearTimeout(refreshTimer);
|
|
632
|
-
}
|
|
633
|
-
if (!currentSession.value) return;
|
|
634
|
-
if (typeof document !== "undefined" && document.visibilityState === "hidden") {
|
|
635
|
-
return;
|
|
636
|
-
}
|
|
637
|
-
const now = /* @__PURE__ */ new Date();
|
|
638
|
-
const expiresAt = currentSession.value.expiresAt;
|
|
639
|
-
const timeUntilRefresh = expiresAt.getTime() - now.getTime() - 1 * 60 * 1e3;
|
|
640
|
-
if (timeUntilRefresh <= 0) {
|
|
641
|
-
refreshToken();
|
|
642
|
-
return;
|
|
643
|
-
}
|
|
644
|
-
refreshTimer = setTimeout(async () => {
|
|
645
|
-
if (typeof document === "undefined" || document.visibilityState === "visible") {
|
|
646
|
-
const success = await refreshToken();
|
|
647
|
-
if (success) {
|
|
648
|
-
startTokenRefreshTimer();
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
}, timeUntilRefresh);
|
|
652
|
-
};
|
|
653
|
-
const stopTokenRefreshTimer = () => {
|
|
654
|
-
if (refreshTimer) {
|
|
655
|
-
clearTimeout(refreshTimer);
|
|
656
|
-
refreshTimer = null;
|
|
657
|
-
}
|
|
658
|
-
};
|
|
659
|
-
const initialize = async () => {
|
|
660
|
-
if (isInitialized.value) return;
|
|
661
|
-
loadingStates.value.initializing = true;
|
|
662
|
-
try {
|
|
663
|
-
if (typeof window !== "undefined") {
|
|
664
|
-
const storedSession = localStorage.getItem("strands_auth_session");
|
|
665
|
-
const storedUser = localStorage.getItem("strands_auth_user");
|
|
666
|
-
if (storedSession && storedUser) {
|
|
667
|
-
try {
|
|
668
|
-
const session = JSON.parse(storedSession);
|
|
669
|
-
const user = JSON.parse(storedUser);
|
|
670
|
-
session.expiresAt = new Date(session.expiresAt);
|
|
671
|
-
if (session.expiresAt <= /* @__PURE__ */ new Date() && session.refreshToken) {
|
|
672
|
-
currentSession.value = session;
|
|
673
|
-
currentUser.value = user;
|
|
674
|
-
const refreshSuccess = await refreshToken();
|
|
675
|
-
if (!refreshSuccess) {
|
|
676
|
-
handleAuthLoss();
|
|
677
|
-
}
|
|
678
|
-
} else if (session.expiresAt > /* @__PURE__ */ new Date()) {
|
|
679
|
-
currentSession.value = session;
|
|
680
|
-
currentUser.value = user;
|
|
681
|
-
startTokenRefreshTimer();
|
|
682
|
-
} else {
|
|
683
|
-
localStorage.removeItem("strands_auth_session");
|
|
684
|
-
localStorage.removeItem("strands_auth_user");
|
|
685
|
-
}
|
|
686
|
-
} catch (error) {
|
|
687
|
-
localStorage.removeItem("strands_auth_session");
|
|
688
|
-
localStorage.removeItem("strands_auth_user");
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
isInitialized.value = true;
|
|
693
|
-
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
694
|
-
} catch (error) {
|
|
695
|
-
} finally {
|
|
696
|
-
loadingStates.value.initializing = false;
|
|
697
|
-
}
|
|
698
|
-
};
|
|
699
|
-
const changeUsername = async (newUsername) => {
|
|
700
|
-
loadingStates.value.loadingProfile = true;
|
|
701
|
-
try {
|
|
702
|
-
const response = await fetch(getUrl("changeUsername"), {
|
|
703
|
-
method: "POST",
|
|
704
|
-
headers: {
|
|
705
|
-
"Content-Type": "application/json",
|
|
706
|
-
"Authorization": `Bearer ${currentSession.value.accessToken}`
|
|
707
|
-
},
|
|
708
|
-
body: JSON.stringify({
|
|
709
|
-
username: newUsername
|
|
710
|
-
})
|
|
711
|
-
});
|
|
712
|
-
if (!response.ok) {
|
|
713
|
-
const errorData = await response.json().catch(() => ({}));
|
|
714
|
-
if (response.status === 409) {
|
|
715
|
-
throw new Error("Username is already taken");
|
|
716
|
-
} else if (errorData.cooldown_end) {
|
|
717
|
-
throw new Error(`You can only change your username once every 30 days. You can change it again on ${new Date(errorData.cooldown_end).toLocaleDateString()}`);
|
|
718
|
-
}
|
|
719
|
-
throw new Error(errorData.message || `Username change failed: ${response.status} ${response.statusText}`);
|
|
720
|
-
}
|
|
721
|
-
const result = await response.json();
|
|
722
|
-
if (currentUser.value) {
|
|
723
|
-
currentUser.value = {
|
|
724
|
-
...currentUser.value,
|
|
725
|
-
username: newUsername,
|
|
726
|
-
usernameLastChangedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
727
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
728
|
-
};
|
|
729
|
-
if (typeof window !== "undefined") {
|
|
730
|
-
localStorage.setItem("strands_auth_user", JSON.stringify(currentUser.value));
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
|
-
return result;
|
|
734
|
-
} finally {
|
|
735
|
-
loadingStates.value.loadingProfile = false;
|
|
736
|
-
}
|
|
737
|
-
};
|
|
738
|
-
const getUsernameCooldown = async () => {
|
|
739
|
-
const response = await fetch(getUrl("usernameCooldown"), {
|
|
740
|
-
method: "GET",
|
|
741
|
-
headers: {
|
|
742
|
-
"Authorization": `Bearer ${currentSession.value.accessToken}`
|
|
743
|
-
}
|
|
744
|
-
});
|
|
745
|
-
if (!response.ok) {
|
|
746
|
-
throw new Error(`Failed to get username cooldown: ${response.status} ${response.statusText}`);
|
|
747
|
-
}
|
|
748
|
-
return response.json();
|
|
749
|
-
};
|
|
750
|
-
const checkUsernameAvailability = async (username) => {
|
|
751
|
-
const url = getUrl("checkUsernameAvailability").replace("{username}", encodeURIComponent(username));
|
|
752
|
-
const response = await fetch(url, {
|
|
753
|
-
method: "GET",
|
|
754
|
-
headers: {
|
|
755
|
-
"Content-Type": "application/json"
|
|
756
|
-
}
|
|
757
|
-
});
|
|
758
|
-
if (!response.ok) {
|
|
759
|
-
throw new Error(`Failed to check username availability: ${response.status} ${response.statusText}`);
|
|
760
|
-
}
|
|
761
|
-
return response.json();
|
|
762
|
-
};
|
|
763
|
-
const getUserSessions = async () => {
|
|
764
|
-
const cacheKey = `sessions:${currentSession.value?.accessToken?.slice(0, 20) || "no-token"}`;
|
|
765
|
-
try {
|
|
766
|
-
return await cachedFetch(cacheKey, async () => {
|
|
767
|
-
const headers = getAuthHeaders();
|
|
768
|
-
const response = await fetch(getUrl("sessions"), {
|
|
769
|
-
method: "GET",
|
|
770
|
-
headers
|
|
771
|
-
});
|
|
772
|
-
if (!response.ok) {
|
|
773
|
-
await response.text();
|
|
774
|
-
throw new Error(`Failed to get user sessions: ${response.status} ${response.statusText}`);
|
|
775
|
-
}
|
|
776
|
-
return response.json();
|
|
777
|
-
}, 2 * 60 * 1e3);
|
|
778
|
-
} catch (error) {
|
|
779
|
-
throw error;
|
|
780
|
-
}
|
|
781
|
-
};
|
|
782
|
-
const getSessionStats = async () => {
|
|
783
|
-
const response = await fetch(getUrl("sessionsStats"), {
|
|
784
|
-
method: "GET",
|
|
785
|
-
headers: getAuthHeaders()
|
|
786
|
-
});
|
|
787
|
-
if (!response.ok) {
|
|
788
|
-
throw new Error(`Failed to get session stats: ${response.status} ${response.statusText}`);
|
|
789
|
-
}
|
|
790
|
-
return response.json();
|
|
791
|
-
};
|
|
792
|
-
const revokeSession = async (sessionId) => {
|
|
793
|
-
const url = getUrl("sessionRevoke").replace("{session_id}", encodeURIComponent(sessionId));
|
|
794
|
-
const response = await fetch(url, {
|
|
795
|
-
method: "POST",
|
|
796
|
-
headers: getAuthHeaders()
|
|
797
|
-
});
|
|
798
|
-
if (!response.ok) {
|
|
799
|
-
throw new Error(`Failed to revoke session: ${response.status} ${response.statusText}`);
|
|
800
|
-
}
|
|
801
|
-
return response.status === 200;
|
|
802
|
-
};
|
|
803
|
-
const revokeAllOtherSessions = async () => {
|
|
804
|
-
const response = await fetch(getUrl("sessionsRevokeAll"), {
|
|
805
|
-
method: "POST",
|
|
806
|
-
headers: getAuthHeaders()
|
|
807
|
-
});
|
|
808
|
-
if (!response.ok) {
|
|
809
|
-
throw new Error(`Failed to revoke all other sessions: ${response.status} ${response.statusText}`);
|
|
810
|
-
}
|
|
811
|
-
return response.status === 200;
|
|
812
|
-
};
|
|
813
|
-
if (typeof document !== "undefined") {
|
|
814
|
-
document.addEventListener("visibilitychange", () => {
|
|
815
|
-
if (document.visibilityState === "visible" && currentSession.value) {
|
|
816
|
-
startTokenRefreshTimer();
|
|
817
|
-
checkAuthState();
|
|
818
|
-
} else if (document.visibilityState === "hidden") {
|
|
819
|
-
stopTokenRefreshTimer();
|
|
820
|
-
}
|
|
821
|
-
});
|
|
822
|
-
}
|
|
823
|
-
if (typeof window !== "undefined") {
|
|
824
|
-
window.addEventListener("storage", (event) => {
|
|
825
|
-
if (event.key === "strands_auth_session" || event.key === "strands_auth_user") {
|
|
826
|
-
if (!event.newValue && currentUser.value) {
|
|
827
|
-
handleAuthLoss();
|
|
828
|
-
}
|
|
829
|
-
}
|
|
830
|
-
});
|
|
831
|
-
}
|
|
832
|
-
const checkAuthState = () => {
|
|
833
|
-
if (typeof window === "undefined") return;
|
|
834
|
-
if (currentUser.value && currentSession.value) {
|
|
835
|
-
const storedSession = localStorage.getItem("strands_auth_session");
|
|
836
|
-
const storedUser = localStorage.getItem("strands_auth_user");
|
|
837
|
-
if (!storedSession || !storedUser) {
|
|
838
|
-
handleAuthLoss();
|
|
839
|
-
}
|
|
840
|
-
}
|
|
841
|
-
};
|
|
842
|
-
const cleanup = () => {
|
|
843
|
-
stopTokenRefreshTimer();
|
|
844
|
-
clearCache();
|
|
845
|
-
};
|
|
846
|
-
try {
|
|
847
|
-
const currentInstance = getCurrentInstance();
|
|
848
|
-
if (currentInstance) {
|
|
849
|
-
onUnmounted(cleanup);
|
|
850
|
-
}
|
|
851
|
-
} catch (e) {
|
|
852
|
-
}
|
|
853
|
-
if (!isInitialized.value) {
|
|
854
|
-
initialize();
|
|
855
|
-
}
|
|
856
|
-
return {
|
|
857
|
-
// State
|
|
858
|
-
user: computed(() => currentUser.value),
|
|
859
|
-
currentUser: computed(() => currentUser.value),
|
|
860
|
-
currentSession: computed(() => currentSession.value),
|
|
861
|
-
isAuthenticated,
|
|
862
|
-
isLoading: computed(() => isLoading.value || !isInitialized.value),
|
|
863
|
-
loading,
|
|
864
|
-
loadingMessage,
|
|
865
|
-
// Specific loading states
|
|
866
|
-
isInitializing,
|
|
867
|
-
isSigningIn,
|
|
868
|
-
isSigningUp,
|
|
869
|
-
isSigningOut,
|
|
870
|
-
isRefreshingToken,
|
|
871
|
-
isSendingMfaEmail,
|
|
872
|
-
isVerifyingMfa,
|
|
873
|
-
// MFA State
|
|
874
|
-
mfaRequired: computed(() => mfaRequired.value),
|
|
875
|
-
mfaSessionId: computed(() => mfaSessionId.value),
|
|
876
|
-
availableMfaMethods: computed(() => availableMfaMethods.value),
|
|
877
|
-
// Methods
|
|
878
|
-
signIn,
|
|
879
|
-
signUp,
|
|
880
|
-
signOut,
|
|
881
|
-
refreshToken,
|
|
882
|
-
fetchProfile,
|
|
883
|
-
updateProfile,
|
|
884
|
-
updateUserSettings,
|
|
885
|
-
changeEmail,
|
|
886
|
-
changeUsername,
|
|
887
|
-
getUsernameCooldown,
|
|
888
|
-
checkUsernameAvailability,
|
|
889
|
-
// Session management
|
|
890
|
-
getUserSessions,
|
|
891
|
-
getSessionStats,
|
|
892
|
-
revokeSession,
|
|
893
|
-
revokeAllOtherSessions,
|
|
894
|
-
initialize,
|
|
895
|
-
setAuthData,
|
|
896
|
-
verifyMfa,
|
|
897
|
-
sendMfaEmailCode,
|
|
898
|
-
getMfaWebAuthnChallenge,
|
|
899
|
-
registerHardwareKey,
|
|
900
|
-
completeHardwareKeyRegistration,
|
|
901
|
-
// Token management
|
|
902
|
-
startTokenRefreshTimer,
|
|
903
|
-
stopTokenRefreshTimer,
|
|
904
|
-
getAuthHeaders,
|
|
905
|
-
// Force re-initialization (useful for testing or navigation)
|
|
906
|
-
forceReInit: () => {
|
|
907
|
-
isInitialized.value = false;
|
|
908
|
-
loadingStates.value.initializing = true;
|
|
909
|
-
initialize();
|
|
910
|
-
}
|
|
911
|
-
};
|
|
912
|
-
}
|
|
913
|
-
export {
|
|
914
|
-
useStrandsAuth as u
|
|
915
|
-
};
|