@strands.gg/accui 1.7.3 → 1.9.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/dist/accui.css +537 -161
- package/dist/nuxt/runtime/composables/useStrandsAuth.cjs.js +1 -1
- package/dist/nuxt/runtime/composables/useStrandsAuth.es.js +1 -1
- package/dist/nuxt/runtime/plugin.client.cjs.js +1 -1
- package/dist/nuxt/runtime/plugin.client.es.js +1 -1
- package/dist/nuxt/runtime/plugin.server.cjs.js +1 -1
- package/dist/nuxt/runtime/plugin.server.es.js +1 -1
- package/dist/nuxt-v4/runtime/composables/useStrandsAuth.cjs.js +1 -1
- package/dist/nuxt-v4/runtime/composables/useStrandsAuth.es.js +1 -1
- package/dist/nuxt-v4/runtime/plugin.client.cjs.js +1 -1
- package/dist/nuxt-v4/runtime/plugin.client.es.js +1 -1
- package/dist/nuxt-v4/runtime/plugin.server.cjs.js +1 -1
- package/dist/nuxt-v4/runtime/plugin.server.es.js +1 -1
- package/dist/strands-auth-ui.cjs.js +1108 -704
- package/dist/strands-auth-ui.cjs.js.map +1 -1
- package/dist/strands-auth-ui.es.js +1109 -705
- package/dist/strands-auth-ui.es.js.map +1 -1
- package/dist/{useStrandsAuth-CO9JEdxM.js → useStrandsAuth-BA8qEUcp.js} +110 -66
- package/dist/useStrandsAuth-BA8qEUcp.js.map +1 -0
- package/dist/{useStrandsAuth-z4jAu9Uv.cjs → useStrandsAuth-Co2lLH4X.cjs} +110 -66
- package/dist/useStrandsAuth-Co2lLH4X.cjs.map +1 -0
- package/dist/{useStrandsConfig-Bdk-g0jS.js → useStrandsConfig-Cxb360Os.js} +13 -3
- package/dist/useStrandsConfig-Cxb360Os.js.map +1 -0
- package/dist/{useStrandsConfig-CtmQtE7Y.cjs → useStrandsConfig-Dms13Zd0.cjs} +13 -3
- package/dist/useStrandsConfig-Dms13Zd0.cjs.map +1 -0
- package/package.json +5 -1
- package/dist/useStrandsAuth-CO9JEdxM.js.map +0 -1
- package/dist/useStrandsAuth-z4jAu9Uv.cjs.map +0 -1
- package/dist/useStrandsConfig-Bdk-g0jS.js.map +0 -1
- package/dist/useStrandsConfig-CtmQtE7Y.cjs.map +0 -1
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
import { ref, computed } from "vue";
|
|
2
|
-
import { u as useStrandsConfig } from "./useStrandsConfig-
|
|
2
|
+
import { u as useStrandsConfig } from "./useStrandsConfig-Cxb360Os.js";
|
|
3
|
+
const mapApiUserToFrontend = (apiUser) => {
|
|
4
|
+
return {
|
|
5
|
+
id: apiUser.id,
|
|
6
|
+
email: apiUser.email,
|
|
7
|
+
firstName: apiUser.first_name || apiUser.firstName || "",
|
|
8
|
+
lastName: apiUser.last_name || apiUser.lastName || "",
|
|
9
|
+
avatar: apiUser.avatar_url || apiUser.avatar,
|
|
10
|
+
mfaEnabled: apiUser.mfa_enabled ?? apiUser.mfaEnabled ?? false,
|
|
11
|
+
emailVerified: apiUser.email_verified ?? apiUser.emailVerified ?? false,
|
|
12
|
+
passwordUpdatedAt: apiUser.password_updated_at || apiUser.passwordUpdatedAt,
|
|
13
|
+
settings: apiUser.settings || {},
|
|
14
|
+
xp: apiUser.xp || 0,
|
|
15
|
+
level: apiUser.level || 1,
|
|
16
|
+
next_level_xp: apiUser.next_level_xp || apiUser.next_level_xp || 4,
|
|
17
|
+
username: apiUser.username,
|
|
18
|
+
usernameLastChangedAt: apiUser.username_last_changed_at || apiUser.usernameLastChangedAt,
|
|
19
|
+
createdAt: apiUser.created_at || apiUser.createdAt,
|
|
20
|
+
updatedAt: apiUser.updated_at || apiUser.updatedAt || (/* @__PURE__ */ new Date()).toISOString()
|
|
21
|
+
};
|
|
22
|
+
};
|
|
3
23
|
const { getUrl } = useStrandsConfig();
|
|
4
24
|
const currentUser = ref(null);
|
|
5
25
|
const currentSession = ref(null);
|
|
@@ -29,6 +49,18 @@ const mfaSessionId = ref(null);
|
|
|
29
49
|
const availableMfaMethods = ref([]);
|
|
30
50
|
let refreshTimer = null;
|
|
31
51
|
function useStrandsAuth() {
|
|
52
|
+
const getAuthHeaders = () => {
|
|
53
|
+
if (!currentSession.value?.accessToken) {
|
|
54
|
+
throw new Error("No access token available");
|
|
55
|
+
}
|
|
56
|
+
const headers = {
|
|
57
|
+
"Authorization": `Bearer ${currentSession.value.accessToken}`
|
|
58
|
+
};
|
|
59
|
+
if (currentSession.value?.refreshToken) {
|
|
60
|
+
headers["x-refresh-token"] = currentSession.value.refreshToken;
|
|
61
|
+
}
|
|
62
|
+
return headers;
|
|
63
|
+
};
|
|
32
64
|
const completeHardwareKeyRegistration = async (deviceId, credential, accessToken) => {
|
|
33
65
|
if (!currentSession.value?.accessToken) {
|
|
34
66
|
throw new Error("No access token available");
|
|
@@ -137,7 +169,7 @@ function useStrandsAuth() {
|
|
|
137
169
|
isSigningIn.value = false;
|
|
138
170
|
return authData;
|
|
139
171
|
}
|
|
140
|
-
setAuthData(authData);
|
|
172
|
+
await setAuthData(authData);
|
|
141
173
|
return authData;
|
|
142
174
|
} catch (error) {
|
|
143
175
|
throw error;
|
|
@@ -192,8 +224,25 @@ function useStrandsAuth() {
|
|
|
192
224
|
}
|
|
193
225
|
throw new Error(`Token refresh failed: ${response.status} ${response.statusText}`);
|
|
194
226
|
}
|
|
195
|
-
const
|
|
196
|
-
|
|
227
|
+
const tokenData = await response.json();
|
|
228
|
+
if (tokenData.user) {
|
|
229
|
+
currentUser.value = mapApiUserToFrontend(tokenData.user);
|
|
230
|
+
if (typeof window !== "undefined") {
|
|
231
|
+
localStorage.setItem("strands_auth_user", JSON.stringify(currentUser.value));
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
const newSession = {
|
|
235
|
+
accessToken: tokenData.access_token,
|
|
236
|
+
refreshToken: tokenData.refresh_token,
|
|
237
|
+
expiresAt: new Date(Date.now() + 5 * 60 * 1e3),
|
|
238
|
+
// 5 minutes from now
|
|
239
|
+
userId: tokenData.user?.id || currentUser.value?.id
|
|
240
|
+
};
|
|
241
|
+
currentSession.value = newSession;
|
|
242
|
+
if (typeof window !== "undefined") {
|
|
243
|
+
localStorage.setItem("strands_auth_session", JSON.stringify(newSession));
|
|
244
|
+
}
|
|
245
|
+
startTokenRefreshTimer();
|
|
197
246
|
return true;
|
|
198
247
|
} catch (error) {
|
|
199
248
|
await signOut();
|
|
@@ -221,24 +270,7 @@ function useStrandsAuth() {
|
|
|
221
270
|
}
|
|
222
271
|
}
|
|
223
272
|
const userData = await response.json();
|
|
224
|
-
currentUser.value =
|
|
225
|
-
id: userData.id,
|
|
226
|
-
email: userData.email,
|
|
227
|
-
firstName: userData.first_name,
|
|
228
|
-
lastName: userData.last_name,
|
|
229
|
-
avatar: userData.avatar_url,
|
|
230
|
-
mfaEnabled: userData.mfaEnabled || false,
|
|
231
|
-
emailVerified: userData.email_verified,
|
|
232
|
-
passwordUpdatedAt: userData.password_updated_at,
|
|
233
|
-
settings: userData.settings || {},
|
|
234
|
-
xp: userData.xp || 0,
|
|
235
|
-
level: userData.level || 1,
|
|
236
|
-
next_level_xp: userData.next_level_xp || 4,
|
|
237
|
-
username: userData.username,
|
|
238
|
-
usernameLastChangedAt: userData.username_last_changed_at,
|
|
239
|
-
createdAt: userData.created_at,
|
|
240
|
-
updatedAt: userData.updated_at || (/* @__PURE__ */ new Date()).toISOString()
|
|
241
|
-
};
|
|
273
|
+
currentUser.value = mapApiUserToFrontend(userData);
|
|
242
274
|
if (typeof window !== "undefined") {
|
|
243
275
|
localStorage.setItem("strands_auth_user", JSON.stringify(currentUser.value));
|
|
244
276
|
}
|
|
@@ -272,24 +304,9 @@ function useStrandsAuth() {
|
|
|
272
304
|
}
|
|
273
305
|
}
|
|
274
306
|
const updatedUserData = await response.json();
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
id: updatedUserData.id,
|
|
279
|
-
firstName: updatedUserData.first_name,
|
|
280
|
-
lastName: updatedUserData.last_name,
|
|
281
|
-
// Keep existing properties not returned by API
|
|
282
|
-
avatar: updatedUserData.avatar_url || currentUser.value.avatar,
|
|
283
|
-
mfaEnabled: currentUser.value.mfaEnabled,
|
|
284
|
-
email: currentUser.value.email,
|
|
285
|
-
// Keep existing email since we don't update it here
|
|
286
|
-
emailVerified: currentUser.value.emailVerified,
|
|
287
|
-
createdAt: updatedUserData.created_at || currentUser.value.createdAt,
|
|
288
|
-
updatedAt: updatedUserData.updated_at || (/* @__PURE__ */ new Date()).toISOString()
|
|
289
|
-
};
|
|
290
|
-
if (typeof window !== "undefined") {
|
|
291
|
-
localStorage.setItem("strands_auth_user", JSON.stringify(currentUser.value));
|
|
292
|
-
}
|
|
307
|
+
currentUser.value = mapApiUserToFrontend(updatedUserData);
|
|
308
|
+
if (typeof window !== "undefined") {
|
|
309
|
+
localStorage.setItem("strands_auth_user", JSON.stringify(currentUser.value));
|
|
293
310
|
}
|
|
294
311
|
return currentUser.value;
|
|
295
312
|
} finally {
|
|
@@ -320,15 +337,9 @@ function useStrandsAuth() {
|
|
|
320
337
|
}
|
|
321
338
|
}
|
|
322
339
|
const updatedUserData = await response.json();
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
settings: updatedUserData.settings,
|
|
327
|
-
updatedAt: updatedUserData.updated_at || (/* @__PURE__ */ new Date()).toISOString()
|
|
328
|
-
};
|
|
329
|
-
if (typeof window !== "undefined") {
|
|
330
|
-
localStorage.setItem("strands_auth_user", JSON.stringify(currentUser.value));
|
|
331
|
-
}
|
|
340
|
+
currentUser.value = mapApiUserToFrontend(updatedUserData);
|
|
341
|
+
if (typeof window !== "undefined") {
|
|
342
|
+
localStorage.setItem("strands_auth_user", JSON.stringify(currentUser.value));
|
|
332
343
|
}
|
|
333
344
|
return currentUser.value;
|
|
334
345
|
} finally {
|
|
@@ -406,7 +417,7 @@ function useStrandsAuth() {
|
|
|
406
417
|
mfaRequired.value = false;
|
|
407
418
|
mfaSessionId.value = null;
|
|
408
419
|
availableMfaMethods.value = [];
|
|
409
|
-
setAuthData(authData);
|
|
420
|
+
await setAuthData(authData);
|
|
410
421
|
return authData;
|
|
411
422
|
} finally {
|
|
412
423
|
isVerifyingMfa.value = false;
|
|
@@ -468,23 +479,10 @@ function useStrandsAuth() {
|
|
|
468
479
|
}
|
|
469
480
|
return response.json();
|
|
470
481
|
};
|
|
471
|
-
const setAuthData = (authResponse
|
|
482
|
+
const setAuthData = async (authResponse) => {
|
|
472
483
|
try {
|
|
473
484
|
if (authResponse.user) {
|
|
474
|
-
|
|
475
|
-
currentUser.value = {
|
|
476
|
-
...currentUser.value,
|
|
477
|
-
...authResponse.user,
|
|
478
|
-
// Preserve certain fields that might be missing in refresh response
|
|
479
|
-
avatar: authResponse.user.avatar || currentUser.value.avatar,
|
|
480
|
-
firstName: authResponse.user.firstName || currentUser.value.firstName,
|
|
481
|
-
lastName: authResponse.user.lastName || currentUser.value.lastName
|
|
482
|
-
};
|
|
483
|
-
} else {
|
|
484
|
-
currentUser.value = authResponse.user;
|
|
485
|
-
}
|
|
486
|
-
} else if (!isTokenRefresh) {
|
|
487
|
-
currentUser.value = null;
|
|
485
|
+
currentUser.value = mapApiUserToFrontend(authResponse.user);
|
|
488
486
|
}
|
|
489
487
|
const session = {
|
|
490
488
|
accessToken: authResponse.access_token,
|
|
@@ -633,6 +631,47 @@ function useStrandsAuth() {
|
|
|
633
631
|
}
|
|
634
632
|
return response.json();
|
|
635
633
|
};
|
|
634
|
+
const getUserSessions = async () => {
|
|
635
|
+
const response = await fetch(getUrl("sessions"), {
|
|
636
|
+
method: "GET",
|
|
637
|
+
headers: getAuthHeaders()
|
|
638
|
+
});
|
|
639
|
+
if (!response.ok) {
|
|
640
|
+
throw new Error(`Failed to get user sessions: ${response.status} ${response.statusText}`);
|
|
641
|
+
}
|
|
642
|
+
return response.json();
|
|
643
|
+
};
|
|
644
|
+
const getSessionStats = async () => {
|
|
645
|
+
const response = await fetch(getUrl("sessionsStats"), {
|
|
646
|
+
method: "GET",
|
|
647
|
+
headers: getAuthHeaders()
|
|
648
|
+
});
|
|
649
|
+
if (!response.ok) {
|
|
650
|
+
throw new Error(`Failed to get session stats: ${response.status} ${response.statusText}`);
|
|
651
|
+
}
|
|
652
|
+
return response.json();
|
|
653
|
+
};
|
|
654
|
+
const revokeSession = async (sessionId) => {
|
|
655
|
+
const url = getUrl("sessionRevoke").replace("{session_id}", encodeURIComponent(sessionId));
|
|
656
|
+
const response = await fetch(url, {
|
|
657
|
+
method: "POST",
|
|
658
|
+
headers: getAuthHeaders()
|
|
659
|
+
});
|
|
660
|
+
if (!response.ok) {
|
|
661
|
+
throw new Error(`Failed to revoke session: ${response.status} ${response.statusText}`);
|
|
662
|
+
}
|
|
663
|
+
return response.status === 200;
|
|
664
|
+
};
|
|
665
|
+
const revokeAllOtherSessions = async () => {
|
|
666
|
+
const response = await fetch(getUrl("sessionsRevokeAll"), {
|
|
667
|
+
method: "POST",
|
|
668
|
+
headers: getAuthHeaders()
|
|
669
|
+
});
|
|
670
|
+
if (!response.ok) {
|
|
671
|
+
throw new Error(`Failed to revoke all other sessions: ${response.status} ${response.statusText}`);
|
|
672
|
+
}
|
|
673
|
+
return response.status === 200;
|
|
674
|
+
};
|
|
636
675
|
if (!isInitialized.value) {
|
|
637
676
|
initialize();
|
|
638
677
|
}
|
|
@@ -669,6 +708,11 @@ function useStrandsAuth() {
|
|
|
669
708
|
changeUsername,
|
|
670
709
|
getUsernameCooldown,
|
|
671
710
|
checkUsernameAvailability,
|
|
711
|
+
// Session management
|
|
712
|
+
getUserSessions,
|
|
713
|
+
getSessionStats,
|
|
714
|
+
revokeSession,
|
|
715
|
+
revokeAllOtherSessions,
|
|
672
716
|
initialize,
|
|
673
717
|
setAuthData,
|
|
674
718
|
verifyMfa,
|
|
@@ -690,4 +734,4 @@ function useStrandsAuth() {
|
|
|
690
734
|
export {
|
|
691
735
|
useStrandsAuth as u
|
|
692
736
|
};
|
|
693
|
-
//# sourceMappingURL=useStrandsAuth-
|
|
737
|
+
//# sourceMappingURL=useStrandsAuth-BA8qEUcp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useStrandsAuth-BA8qEUcp.js","sources":["../../../apps/accounts-ui/src/vue/composables/useStrandsAuth.ts"],"sourcesContent":["import { ref, computed } from 'vue'\nimport type { User, Session, SessionInfo, SessionStats, SignInCredentials, SignUpData, AuthResponse, MfaDevice, MfaErrorResponse } from '../../types'\nimport { useStrandsConfig } from './useStrandsConfig'\n\n// Utility function to map API user data to frontend format\nconst mapApiUserToFrontend = (apiUser: any): User => {\n return {\n id: apiUser.id,\n email: apiUser.email,\n firstName: apiUser.first_name || apiUser.firstName || '',\n lastName: apiUser.last_name || apiUser.lastName || '',\n avatar: apiUser.avatar_url || apiUser.avatar,\n mfaEnabled: apiUser.mfa_enabled ?? apiUser.mfaEnabled ?? false,\n emailVerified: apiUser.email_verified ?? apiUser.emailVerified ?? false,\n passwordUpdatedAt: apiUser.password_updated_at || apiUser.passwordUpdatedAt,\n settings: apiUser.settings || {},\n xp: apiUser.xp || 0,\n level: apiUser.level || 1,\n next_level_xp: apiUser.next_level_xp || apiUser.next_level_xp || 4,\n username: apiUser.username,\n usernameLastChangedAt: apiUser.username_last_changed_at || apiUser.usernameLastChangedAt,\n createdAt: apiUser.created_at || apiUser.createdAt,\n updatedAt: apiUser.updated_at || apiUser.updatedAt || new Date().toISOString(),\n }\n}\n\nconst { getUrl } = useStrandsConfig()\n\nconst currentUser = ref<User | null>(null)\nconst currentSession = ref<Session | null>(null)\n\n// Specific loading states\nconst isInitializing = ref(true)\nconst isSigningIn = ref(false)\nconst isSigningUp = ref(false)\nconst isSigningOut = ref(false)\nconst isRefreshingToken = ref(false)\nconst isSendingMfaEmail = ref(false)\nconst isVerifyingMfa = ref(false)\nconst isLoadingProfile = ref(false)\n\n// Legacy loading states for backward compatibility\nconst loading = computed(() => isSigningIn.value || isSigningUp.value || isSigningOut.value || isRefreshingToken.value || isSendingMfaEmail.value || isVerifyingMfa.value || isLoadingProfile.value)\nconst isLoading = computed(() => isInitializing.value || loading.value)\n\n// Loading messages for better UX\nconst loadingMessage = computed(() => {\n if (isInitializing.value) return 'Checking authentication...'\n if (isSigningIn.value) return 'Signing you in...'\n if (isSigningUp.value) return 'Creating your account...'\n if (isSigningOut.value) return 'Signing you out...'\n if (isRefreshingToken.value) return 'Refreshing session...'\n if (isSendingMfaEmail.value) return 'Sending verification code...'\n if (isVerifyingMfa.value) return 'Verifying code...'\n return 'Loading...' // Fallback\n})\n\nconst isInitialized = ref(false)\n\n// MFA state\nconst mfaRequired = ref(false)\nconst mfaSessionId = ref<string | null>(null)\nconst availableMfaMethods = ref<MfaDevice[]>([])\n\n// Token refresh timer\nlet refreshTimer: NodeJS.Timeout | null = null\n\nexport function useStrandsAuth() {\n // Helper function to get authenticated headers with refresh token\n const getAuthHeaders = () => {\n if (!currentSession.value?.accessToken) {\n throw new Error('No access token available')\n }\n \n const headers: Record<string, string> = {\n 'Authorization': `Bearer ${currentSession.value.accessToken}`,\n }\n \n // Include refresh token for session management\n if (currentSession.value?.refreshToken) {\n headers['x-refresh-token'] = currentSession.value.refreshToken\n }\n \n return headers\n }\n /**\n * Complete hardware key (WebAuthn) registration for MFA\n * Returns result with backup codes on success\n */\n const completeHardwareKeyRegistration = async (deviceId: string, credential: any, accessToken: string) => {\n if (!currentSession.value?.accessToken) {\n throw new Error('No access token available')\n }\n\n const response = await fetch(getUrl('mfaHardwareCompleteRegistration'), {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${currentSession.value.accessToken}`\n },\n body: JSON.stringify({\n device_id: deviceId,\n credential: credential\n })\n })\n if (!response.ok) {\n const errorText = await response.text()\n let errorMessage = 'Failed to complete hardware key registration'\n \n try {\n const errorData = JSON.parse(errorText)\n errorMessage = errorData.message || errorData.error || errorText\n } catch {\n errorMessage = errorText || 'Failed to complete hardware key registration'\n }\n \n throw new Error(errorMessage)\n }\n return response.json()\n }\n /**\n * Register a hardware key (WebAuthn) for MFA\n * Returns { device_id, challenge } on success\n */\n const registerHardwareKey = async (deviceName: string, accessToken: string, deviceType: 'hardware' | 'passkey' = 'hardware') => {\n if (!currentSession.value?.accessToken) {\n throw new Error('No access token available')\n }\n \n const response = await fetch(getUrl('mfaHardwareStartRegistration'), {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${currentSession.value.accessToken}`,\n },\n body: JSON.stringify({ \n device_name: deviceName,\n device_type: deviceType \n })\n })\n if (!response.ok) {\n const errorText = await response.text()\n let errorMessage = 'Failed to start hardware key registration'\n \n try {\n const errorData = JSON.parse(errorText)\n errorMessage = errorData.message || errorData.error || errorText\n } catch {\n errorMessage = errorText || 'Failed to start hardware key registration'\n }\n \n throw new Error(errorMessage)\n }\n return response.json()\n }\n\n const isAuthenticated = computed(() => currentUser.value !== null)\n\n const signIn = async (credentials: SignInCredentials) => {\n isSigningIn.value = true\n try {\n // Reset MFA state at start of new sign-in attempt\n mfaRequired.value = false\n mfaSessionId.value = null\n availableMfaMethods.value = []\n \n // Make API call to sign-in endpoint\n const response = await fetch(getUrl('signIn'), {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(credentials),\n })\n\n if (!response.ok) {\n if (response.status === 401) {\n throw new Error('Invalid email or password')\n } else if (response.status === 403) {\n throw new Error('Please verify your email address before signing in')\n } else {\n throw new Error(`Sign in failed: ${response.status} ${response.statusText}`)\n }\n }\n\n const authData: AuthResponse = await response.json()\n \n // Check if MFA is required\n if (authData.mfa_required) {\n mfaRequired.value = true\n mfaSessionId.value = authData.mfa_session_id || null\n \n // Convert API MFA methods to the expected format\n const methods = (authData.available_mfa_methods || []).map((method: any) => {\n // Create better fallback names for different device types\n let fallbackName = `${method.device_type.charAt(0).toUpperCase() + method.device_type.slice(1)} Authentication`\n \n if (method.device_type === 'hardware') {\n // Better names for hardware keys\n fallbackName = method.device_name || 'Security Key' // Could be YubiKey, FIDO2 Key, etc.\n } else if (method.device_type === 'totp') {\n fallbackName = method.device_name || 'Authenticator App'\n } else if (method.device_type === 'email') {\n fallbackName = method.device_name || 'Email Verification'\n }\n \n return {\n id: method.device_id,\n device_type: method.device_type,\n device_name: method.device_name || fallbackName,\n is_active: true,\n created_at: new Date().toISOString(),\n last_used_at: method.last_used_at,\n // Pass through additional metadata if available\n credential_id: method.credential_id,\n device_info: method.device_info\n }\n })\n availableMfaMethods.value = methods\n \n // Set signIn loading to false immediately so MFA modal can show\n isSigningIn.value = false\n \n return authData // Return without storing full auth data\n }\n \n // Store the authentication data if MFA not required\n await setAuthData(authData)\n \n return authData\n } catch (error) {\n throw error\n } finally {\n isSigningIn.value = false\n }\n }\n\n const signUp = async (userData: SignUpData) => {\n isSigningUp.value = true\n try {\n // Integration point: Replace with actual auth SDK call\n // Example: const result = await authSDK.signUp(userData)\n \n throw new Error('Sign up not implemented - please integrate with auth SDK')\n } finally {\n isSigningUp.value = false\n }\n }\n\n const signOut = async () => {\n isSigningOut.value = true\n try {\n // Stop token refresh timer\n stopTokenRefreshTimer()\n \n // Clear user and session state\n currentUser.value = null\n currentSession.value = null\n \n // Clear MFA state\n mfaRequired.value = false\n mfaSessionId.value = null\n availableMfaMethods.value = []\n \n // Clear stored tokens\n if (typeof window !== 'undefined') {\n localStorage.removeItem('strands_auth_session')\n localStorage.removeItem('strands_auth_user')\n }\n } finally {\n isSigningOut.value = false\n }\n }\n\n const refreshToken = async () => {\n if (!currentSession.value?.refreshToken) {\n return false\n }\n \n \n isRefreshingToken.value = true\n try {\n const response = await fetch(getUrl('refresh'), {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n refresh_token: currentSession.value.refreshToken,\n }),\n })\n\n if (!response.ok) { \n if (response.status === 401) {\n // Refresh token is invalid/expired, sign out user\n await signOut()\n return false\n }\n throw new Error(`Token refresh failed: ${response.status} ${response.statusText}`)\n }\n\n const tokenData = await response.json()\n \n // Token refresh now returns full AuthResponse with user data\n if (tokenData.user) {\n // Update user data with fresh information from the server\n currentUser.value = mapApiUserToFrontend(tokenData.user)\n \n // Update localStorage with fresh user data\n if (typeof window !== 'undefined') {\n localStorage.setItem('strands_auth_user', JSON.stringify(currentUser.value))\n }\n }\n \n // Update session with fresh tokens\n const newSession: Session = {\n accessToken: tokenData.access_token,\n refreshToken: tokenData.refresh_token,\n expiresAt: new Date(Date.now() + 5 * 60 * 1000), // 5 minutes from now\n userId: tokenData.user?.id || currentUser.value?.id,\n }\n \n currentSession.value = newSession\n \n // Update localStorage with new session\n if (typeof window !== 'undefined') {\n localStorage.setItem('strands_auth_session', JSON.stringify(newSession))\n }\n \n // Restart token refresh timer\n startTokenRefreshTimer()\n \n return true\n } catch (error) {\n // On refresh failure, sign out the user\n await signOut()\n return false\n }\n }\n\n const fetchProfile = async (): Promise<User | null> => {\n if (!currentSession.value?.accessToken) {\n throw new Error('No access token available')\n }\n \n isLoadingProfile.value = true\n try {\n const response = await fetch(getUrl('profile'), {\n method: 'GET',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${currentSession.value.accessToken}`,\n },\n })\n\n if (!response.ok) {\n if (response.status === 401) {\n throw new Error('Authentication expired. Please sign in again.')\n } else {\n throw new Error(`Failed to fetch profile: ${response.status} ${response.statusText}`)\n }\n }\n\n const userData = await response.json()\n \n // Update the current user state using the mapping utility\n currentUser.value = mapApiUserToFrontend(userData)\n \n // Update localStorage\n if (typeof window !== 'undefined') {\n localStorage.setItem('strands_auth_user', JSON.stringify(currentUser.value))\n }\n \n return currentUser.value\n } finally {\n isLoadingProfile.value = false\n }\n }\n\n const updateProfile = async (profileData: Partial<User>) => {\n if (!currentSession.value?.accessToken) {\n throw new Error('No access token available')\n }\n \n isLoadingProfile.value = true\n try {\n const response = await fetch(getUrl('profile'), {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${currentSession.value.accessToken}`,\n },\n body: JSON.stringify({\n first_name: profileData.firstName,\n last_name: profileData.lastName,\n }),\n })\n\n if (!response.ok) {\n if (response.status === 401) {\n throw new Error('Authentication expired. Please sign in again.')\n } else {\n throw new Error(`Profile update failed: ${response.status} ${response.statusText}`)\n }\n }\n\n const updatedUserData = await response.json()\n \n // Update the current user state using the mapping utility\n currentUser.value = mapApiUserToFrontend(updatedUserData)\n \n // Update localStorage\n if (typeof window !== 'undefined') {\n localStorage.setItem('strands_auth_user', JSON.stringify(currentUser.value))\n }\n \n return currentUser.value\n } finally {\n isLoadingProfile.value = false\n }\n }\n\n const updateUserSettings = async (settings: any) => {\n if (!currentSession.value?.accessToken) {\n throw new Error('No access token available')\n }\n \n isLoadingProfile.value = true\n try {\n const response = await fetch(getUrl('settings'), {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${currentSession.value.accessToken}`,\n },\n body: JSON.stringify({\n settings: settings,\n }),\n })\n\n if (!response.ok) {\n if (response.status === 401) {\n throw new Error('Authentication expired. Please sign in again.')\n } else {\n throw new Error(`Settings update failed: ${response.status} ${response.statusText}`)\n }\n }\n\n const updatedUserData = await response.json()\n \n // Update the current user state using the mapping utility\n currentUser.value = mapApiUserToFrontend(updatedUserData)\n \n // Update localStorage\n if (typeof window !== 'undefined') {\n localStorage.setItem('strands_auth_user', JSON.stringify(currentUser.value))\n }\n \n return currentUser.value\n } finally {\n isLoadingProfile.value = false\n }\n }\n\n const changeEmail = async (newEmail: string, password: string) => {\n if (!currentSession.value?.accessToken) {\n throw new Error('No access token available')\n }\n \n isLoadingProfile.value = true\n try {\n const response = await fetch(getUrl('changeEmail'), {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${currentSession.value.accessToken}`,\n },\n body: JSON.stringify({\n new_email: newEmail,\n password: password,\n }),\n })\n\n if (!response.ok) {\n if (response.status === 401) {\n throw new Error('Authentication expired. Please sign in again.')\n } else {\n const errorData = await response.json().catch(() => ({}))\n throw new Error(errorData.message || `Email change failed: ${response.status} ${response.statusText}`)\n }\n }\n\n const result = await response.json()\n \n // For email change, we need to manually update some fields since API might not return full user\n if (currentUser.value) {\n currentUser.value = {\n ...currentUser.value,\n email: newEmail,\n emailVerified: false, // Email needs to be verified again\n updatedAt: new Date().toISOString(),\n }\n \n // Update localStorage\n if (typeof window !== 'undefined') {\n localStorage.setItem('strands_auth_user', JSON.stringify(currentUser.value))\n }\n }\n \n return result\n } finally {\n isLoadingProfile.value = false\n }\n }\n\n const verifyMfa = async (deviceId: string, code: string, isBackupCode = false) => {\n if (!mfaSessionId.value) {\n throw new Error('No MFA session available')\n }\n\n isVerifyingMfa.value = true\n try {\n const endpoint = isBackupCode ? getUrl('mfaBackupCodeVerify') : getUrl('mfaSigninVerify')\n const body = isBackupCode \n ? { mfa_session_id: mfaSessionId.value, backup_code: code }\n : { mfa_session_id: mfaSessionId.value, device_id: deviceId, code }\n\n const response = await fetch(endpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n })\n\n if (!response.ok) {\n const errorText = await response.text()\n let errorMessage = 'MFA verification failed'\n \n try {\n const errorData = JSON.parse(errorText)\n errorMessage = errorData.message || errorData.error || errorText\n } catch {\n // If parsing fails, use the raw text but clean it up\n errorMessage = errorText || 'MFA verification failed'\n }\n \n throw new Error(errorMessage)\n }\n\n const authData: AuthResponse = await response.json()\n \n // Clear MFA state\n mfaRequired.value = false\n mfaSessionId.value = null\n availableMfaMethods.value = []\n \n // Store the authentication data\n await setAuthData(authData)\n \n return authData\n } finally {\n isVerifyingMfa.value = false\n }\n }\n\n const sendMfaEmailCode = async (deviceId: string) => {\n if (!mfaSessionId.value) {\n throw new Error('No MFA session available')\n }\n\n isSendingMfaEmail.value = true\n try {\n const response = await fetch(getUrl('mfaSigninSendEmail'), {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n mfa_session_id: mfaSessionId.value,\n device_id: deviceId,\n }),\n })\n\n if (!response.ok) {\n const errorText = await response.text()\n let errorMessage = 'Failed to send MFA email code'\n \n try {\n const errorData = JSON.parse(errorText)\n errorMessage = errorData.message || errorData.error || errorText\n } catch {\n // If parsing fails, use the raw text but clean it up\n errorMessage = errorText || 'Failed to send MFA email code'\n }\n \n throw new Error(errorMessage)\n }\n\n const result = await response.json()\n return result\n } finally {\n isSendingMfaEmail.value = false\n }\n }\n\n const getMfaWebAuthnChallenge = async (deviceId: string) => {\n if (!mfaSessionId.value) {\n throw new Error('No MFA session available')\n }\n \n const response = await fetch(getUrl('mfaWebAuthnChallenge'), {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n mfa_session_id: mfaSessionId.value,\n device_id: deviceId\n }),\n })\n\n if (!response.ok) {\n const errorText = await response.text()\n let errorMessage = 'Failed to get WebAuthn challenge'\n \n try {\n const errorData = JSON.parse(errorText)\n errorMessage = errorData.message || errorData.error || errorText\n } catch {\n errorMessage = errorText || errorMessage\n }\n \n throw new Error(errorMessage)\n }\n\n return response.json()\n }\n\n const setAuthData = async (authResponse: AuthResponse) => {\n try {\n // Handle user data (only for sign-in/sign-up, not token refresh)\n if (authResponse.user) {\n currentUser.value = mapApiUserToFrontend(authResponse.user)\n }\n \n // Create session object\n const session: Session = {\n accessToken: authResponse.access_token,\n refreshToken: authResponse.refresh_token,\n expiresAt: new Date(Date.now() + 5 * 60 * 1000), // 5 minutes from now (matching API token expiry)\n userId: currentUser.value?.id || authResponse.user?.id,\n }\n \n currentSession.value = session\n \n // Store in localStorage for persistence\n if (typeof window !== 'undefined') {\n localStorage.setItem('strands_auth_session', JSON.stringify(session))\n if (currentUser.value) {\n localStorage.setItem('strands_auth_user', JSON.stringify(currentUser.value))\n }\n }\n \n // Start automatic token refresh\n startTokenRefreshTimer()\n } catch (error) {\n console.error('Error setting auth data:', error)\n // Silently handle auth data storage errors\n }\n }\n\n // Token refresh timer management\n const startTokenRefreshTimer = () => {\n // Clear existing timer\n if (refreshTimer) {\n clearTimeout(refreshTimer)\n }\n \n if (!currentSession.value) return\n \n // Calculate time until token expires (refresh 1 minute before expiry)\n const now = new Date()\n const expiresAt = currentSession.value.expiresAt\n const timeUntilRefresh = expiresAt.getTime() - now.getTime() - (1 * 60 * 1000) // 1 minute before expiry\n \n // If token is already expired or expires very soon, refresh immediately\n if (timeUntilRefresh <= 0) {\n refreshToken()\n return\n }\n \n // Set timer to refresh token\n refreshTimer = setTimeout(async () => {\n const success = await refreshToken()\n \n if (success) {\n // Schedule next refresh\n startTokenRefreshTimer()\n }\n }, timeUntilRefresh)\n }\n\n const stopTokenRefreshTimer = () => {\n if (refreshTimer) {\n clearTimeout(refreshTimer)\n refreshTimer = null\n }\n }\n\n const initialize = async () => {\n if (isInitialized.value) return\n \n isInitializing.value = true\n try {\n // Check for existing session/token in localStorage\n if (typeof window !== 'undefined') {\n const storedSession = localStorage.getItem('strands_auth_session')\n const storedUser = localStorage.getItem('strands_auth_user')\n \n if (storedSession && storedUser) {\n try {\n const session = JSON.parse(storedSession) as Session\n const user = JSON.parse(storedUser) as User\n \n // Convert expiresAt back to Date object\n session.expiresAt = new Date(session.expiresAt)\n \n // Check if session is still valid\n if (session.expiresAt > new Date()) {\n currentSession.value = session\n currentUser.value = user\n \n // Start token refresh timer for restored session\n startTokenRefreshTimer()\n } else {\n // Session expired, clear storage\n localStorage.removeItem('strands_auth_session')\n localStorage.removeItem('strands_auth_user')\n }\n } catch (error) {\n // Clear corrupted data\n localStorage.removeItem('strands_auth_session')\n localStorage.removeItem('strands_auth_user')\n }\n }\n }\n \n isInitialized.value = true\n \n // Add a small delay to ensure state is fully settled before hiding loading\n await new Promise(resolve => setTimeout(resolve, 50))\n } catch (error) {\n // Silently handle initialization errors\n } finally {\n isInitializing.value = false\n }\n }\n\n const changeUsername = async (newUsername: string) => {\n if (!currentSession.value?.accessToken) {\n throw new Error('No access token available')\n }\n \n isLoadingProfile.value = true\n try {\n const response = await fetch(getUrl('changeUsername'), {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${currentSession.value.accessToken}`,\n },\n body: JSON.stringify({\n username: newUsername,\n }),\n })\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({}))\n if (response.status === 409) {\n throw new Error('Username is already taken')\n } else if (errorData.cooldown_end) {\n 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()}`)\n }\n throw new Error(errorData.message || `Username change failed: ${response.status} ${response.statusText}`)\n }\n\n const result = await response.json()\n \n // For username change, we need to manually update some fields since API might not return full user\n if (currentUser.value) {\n currentUser.value = {\n ...currentUser.value,\n username: newUsername,\n usernameLastChangedAt: new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n }\n \n // Update localStorage\n if (typeof window !== 'undefined') {\n localStorage.setItem('strands_auth_user', JSON.stringify(currentUser.value))\n }\n }\n \n return result\n } finally {\n isLoadingProfile.value = false\n }\n }\n\n const getUsernameCooldown = async () => {\n if (!currentSession.value?.accessToken) {\n throw new Error('No access token available')\n }\n \n const response = await fetch(getUrl('usernameCooldown'), {\n method: 'GET',\n headers: {\n 'Authorization': `Bearer ${currentSession.value.accessToken}`,\n },\n })\n\n if (!response.ok) {\n throw new Error(`Failed to get username cooldown: ${response.status} ${response.statusText}`)\n }\n\n return response.json()\n }\n\n const checkUsernameAvailability = async (username: string) => {\n // Replace {username} in the URL\n const url = getUrl('checkUsernameAvailability').replace('{username}', encodeURIComponent(username))\n \n const response = await fetch(url, {\n method: 'GET',\n headers: {\n 'Content-Type': 'application/json',\n },\n })\n\n if (!response.ok) {\n throw new Error(`Failed to check username availability: ${response.status} ${response.statusText}`)\n }\n\n return response.json()\n }\n\n // Session management functions\n const getUserSessions = async () => {\n const response = await fetch(getUrl('sessions'), {\n method: 'GET',\n headers: getAuthHeaders(),\n })\n\n if (!response.ok) {\n throw new Error(`Failed to get user sessions: ${response.status} ${response.statusText}`)\n }\n\n return response.json()\n }\n\n const getSessionStats = async () => {\n const response = await fetch(getUrl('sessionsStats'), {\n method: 'GET',\n headers: getAuthHeaders(),\n })\n\n if (!response.ok) {\n throw new Error(`Failed to get session stats: ${response.status} ${response.statusText}`)\n }\n\n return response.json()\n }\n\n const revokeSession = async (sessionId: string) => {\n const url = getUrl('sessionRevoke').replace('{session_id}', encodeURIComponent(sessionId))\n \n const response = await fetch(url, {\n method: 'POST',\n headers: getAuthHeaders(),\n })\n\n if (!response.ok) {\n throw new Error(`Failed to revoke session: ${response.status} ${response.statusText}`)\n }\n\n return response.status === 200\n }\n\n const revokeAllOtherSessions = async () => {\n const response = await fetch(getUrl('sessionsRevokeAll'), {\n method: 'POST',\n headers: getAuthHeaders(),\n })\n\n if (!response.ok) {\n throw new Error(`Failed to revoke all other sessions: ${response.status} ${response.statusText}`)\n }\n\n return response.status === 200\n }\n\n // Auto-initialize on first use\n if (!isInitialized.value) {\n initialize()\n }\n\n return {\n // State\n user: computed(() => currentUser.value),\n currentUser: computed(() => currentUser.value),\n currentSession: computed(() => currentSession.value),\n isAuthenticated,\n isLoading: computed(() => isLoading.value || !isInitialized.value),\n loading: computed(() => loading.value),\n loadingMessage,\n \n // Specific loading states\n isInitializing,\n isSigningIn,\n isSigningUp,\n isSigningOut,\n isRefreshingToken,\n isSendingMfaEmail,\n isVerifyingMfa,\n \n // MFA State\n mfaRequired: computed(() => mfaRequired.value),\n mfaSessionId: computed(() => mfaSessionId.value),\n availableMfaMethods: computed(() => availableMfaMethods.value),\n\n // Methods\n signIn,\n signUp,\n signOut,\n refreshToken,\n fetchProfile,\n updateProfile,\n updateUserSettings,\n changeEmail,\n changeUsername,\n getUsernameCooldown,\n checkUsernameAvailability,\n // Session management\n getUserSessions,\n getSessionStats,\n revokeSession,\n revokeAllOtherSessions,\n initialize,\n setAuthData,\n verifyMfa,\n sendMfaEmailCode,\n getMfaWebAuthnChallenge,\n registerHardwareKey,\n completeHardwareKeyRegistration,\n \n // Token management\n startTokenRefreshTimer,\n stopTokenRefreshTimer,\n \n // Force re-initialization (useful for testing or navigation)\n forceReInit: () => {\n isInitialized.value = false\n isInitializing.value = true\n initialize()\n },\n }\n}"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,26 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
const vue = require("vue");
|
|
3
|
-
const useStrandsConfig = require("./useStrandsConfig-
|
|
3
|
+
const useStrandsConfig = require("./useStrandsConfig-Dms13Zd0.cjs");
|
|
4
|
+
const mapApiUserToFrontend = (apiUser) => {
|
|
5
|
+
return {
|
|
6
|
+
id: apiUser.id,
|
|
7
|
+
email: apiUser.email,
|
|
8
|
+
firstName: apiUser.first_name || apiUser.firstName || "",
|
|
9
|
+
lastName: apiUser.last_name || apiUser.lastName || "",
|
|
10
|
+
avatar: apiUser.avatar_url || apiUser.avatar,
|
|
11
|
+
mfaEnabled: apiUser.mfa_enabled ?? apiUser.mfaEnabled ?? false,
|
|
12
|
+
emailVerified: apiUser.email_verified ?? apiUser.emailVerified ?? false,
|
|
13
|
+
passwordUpdatedAt: apiUser.password_updated_at || apiUser.passwordUpdatedAt,
|
|
14
|
+
settings: apiUser.settings || {},
|
|
15
|
+
xp: apiUser.xp || 0,
|
|
16
|
+
level: apiUser.level || 1,
|
|
17
|
+
next_level_xp: apiUser.next_level_xp || apiUser.next_level_xp || 4,
|
|
18
|
+
username: apiUser.username,
|
|
19
|
+
usernameLastChangedAt: apiUser.username_last_changed_at || apiUser.usernameLastChangedAt,
|
|
20
|
+
createdAt: apiUser.created_at || apiUser.createdAt,
|
|
21
|
+
updatedAt: apiUser.updated_at || apiUser.updatedAt || (/* @__PURE__ */ new Date()).toISOString()
|
|
22
|
+
};
|
|
23
|
+
};
|
|
4
24
|
const { getUrl } = useStrandsConfig.useStrandsConfig();
|
|
5
25
|
const currentUser = vue.ref(null);
|
|
6
26
|
const currentSession = vue.ref(null);
|
|
@@ -30,6 +50,18 @@ const mfaSessionId = vue.ref(null);
|
|
|
30
50
|
const availableMfaMethods = vue.ref([]);
|
|
31
51
|
let refreshTimer = null;
|
|
32
52
|
function useStrandsAuth() {
|
|
53
|
+
const getAuthHeaders = () => {
|
|
54
|
+
if (!currentSession.value?.accessToken) {
|
|
55
|
+
throw new Error("No access token available");
|
|
56
|
+
}
|
|
57
|
+
const headers = {
|
|
58
|
+
"Authorization": `Bearer ${currentSession.value.accessToken}`
|
|
59
|
+
};
|
|
60
|
+
if (currentSession.value?.refreshToken) {
|
|
61
|
+
headers["x-refresh-token"] = currentSession.value.refreshToken;
|
|
62
|
+
}
|
|
63
|
+
return headers;
|
|
64
|
+
};
|
|
33
65
|
const completeHardwareKeyRegistration = async (deviceId, credential, accessToken) => {
|
|
34
66
|
if (!currentSession.value?.accessToken) {
|
|
35
67
|
throw new Error("No access token available");
|
|
@@ -138,7 +170,7 @@ function useStrandsAuth() {
|
|
|
138
170
|
isSigningIn.value = false;
|
|
139
171
|
return authData;
|
|
140
172
|
}
|
|
141
|
-
setAuthData(authData);
|
|
173
|
+
await setAuthData(authData);
|
|
142
174
|
return authData;
|
|
143
175
|
} catch (error) {
|
|
144
176
|
throw error;
|
|
@@ -193,8 +225,25 @@ function useStrandsAuth() {
|
|
|
193
225
|
}
|
|
194
226
|
throw new Error(`Token refresh failed: ${response.status} ${response.statusText}`);
|
|
195
227
|
}
|
|
196
|
-
const
|
|
197
|
-
|
|
228
|
+
const tokenData = await response.json();
|
|
229
|
+
if (tokenData.user) {
|
|
230
|
+
currentUser.value = mapApiUserToFrontend(tokenData.user);
|
|
231
|
+
if (typeof window !== "undefined") {
|
|
232
|
+
localStorage.setItem("strands_auth_user", JSON.stringify(currentUser.value));
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
const newSession = {
|
|
236
|
+
accessToken: tokenData.access_token,
|
|
237
|
+
refreshToken: tokenData.refresh_token,
|
|
238
|
+
expiresAt: new Date(Date.now() + 5 * 60 * 1e3),
|
|
239
|
+
// 5 minutes from now
|
|
240
|
+
userId: tokenData.user?.id || currentUser.value?.id
|
|
241
|
+
};
|
|
242
|
+
currentSession.value = newSession;
|
|
243
|
+
if (typeof window !== "undefined") {
|
|
244
|
+
localStorage.setItem("strands_auth_session", JSON.stringify(newSession));
|
|
245
|
+
}
|
|
246
|
+
startTokenRefreshTimer();
|
|
198
247
|
return true;
|
|
199
248
|
} catch (error) {
|
|
200
249
|
await signOut();
|
|
@@ -222,24 +271,7 @@ function useStrandsAuth() {
|
|
|
222
271
|
}
|
|
223
272
|
}
|
|
224
273
|
const userData = await response.json();
|
|
225
|
-
currentUser.value =
|
|
226
|
-
id: userData.id,
|
|
227
|
-
email: userData.email,
|
|
228
|
-
firstName: userData.first_name,
|
|
229
|
-
lastName: userData.last_name,
|
|
230
|
-
avatar: userData.avatar_url,
|
|
231
|
-
mfaEnabled: userData.mfaEnabled || false,
|
|
232
|
-
emailVerified: userData.email_verified,
|
|
233
|
-
passwordUpdatedAt: userData.password_updated_at,
|
|
234
|
-
settings: userData.settings || {},
|
|
235
|
-
xp: userData.xp || 0,
|
|
236
|
-
level: userData.level || 1,
|
|
237
|
-
next_level_xp: userData.next_level_xp || 4,
|
|
238
|
-
username: userData.username,
|
|
239
|
-
usernameLastChangedAt: userData.username_last_changed_at,
|
|
240
|
-
createdAt: userData.created_at,
|
|
241
|
-
updatedAt: userData.updated_at || (/* @__PURE__ */ new Date()).toISOString()
|
|
242
|
-
};
|
|
274
|
+
currentUser.value = mapApiUserToFrontend(userData);
|
|
243
275
|
if (typeof window !== "undefined") {
|
|
244
276
|
localStorage.setItem("strands_auth_user", JSON.stringify(currentUser.value));
|
|
245
277
|
}
|
|
@@ -273,24 +305,9 @@ function useStrandsAuth() {
|
|
|
273
305
|
}
|
|
274
306
|
}
|
|
275
307
|
const updatedUserData = await response.json();
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
id: updatedUserData.id,
|
|
280
|
-
firstName: updatedUserData.first_name,
|
|
281
|
-
lastName: updatedUserData.last_name,
|
|
282
|
-
// Keep existing properties not returned by API
|
|
283
|
-
avatar: updatedUserData.avatar_url || currentUser.value.avatar,
|
|
284
|
-
mfaEnabled: currentUser.value.mfaEnabled,
|
|
285
|
-
email: currentUser.value.email,
|
|
286
|
-
// Keep existing email since we don't update it here
|
|
287
|
-
emailVerified: currentUser.value.emailVerified,
|
|
288
|
-
createdAt: updatedUserData.created_at || currentUser.value.createdAt,
|
|
289
|
-
updatedAt: updatedUserData.updated_at || (/* @__PURE__ */ new Date()).toISOString()
|
|
290
|
-
};
|
|
291
|
-
if (typeof window !== "undefined") {
|
|
292
|
-
localStorage.setItem("strands_auth_user", JSON.stringify(currentUser.value));
|
|
293
|
-
}
|
|
308
|
+
currentUser.value = mapApiUserToFrontend(updatedUserData);
|
|
309
|
+
if (typeof window !== "undefined") {
|
|
310
|
+
localStorage.setItem("strands_auth_user", JSON.stringify(currentUser.value));
|
|
294
311
|
}
|
|
295
312
|
return currentUser.value;
|
|
296
313
|
} finally {
|
|
@@ -321,15 +338,9 @@ function useStrandsAuth() {
|
|
|
321
338
|
}
|
|
322
339
|
}
|
|
323
340
|
const updatedUserData = await response.json();
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
settings: updatedUserData.settings,
|
|
328
|
-
updatedAt: updatedUserData.updated_at || (/* @__PURE__ */ new Date()).toISOString()
|
|
329
|
-
};
|
|
330
|
-
if (typeof window !== "undefined") {
|
|
331
|
-
localStorage.setItem("strands_auth_user", JSON.stringify(currentUser.value));
|
|
332
|
-
}
|
|
341
|
+
currentUser.value = mapApiUserToFrontend(updatedUserData);
|
|
342
|
+
if (typeof window !== "undefined") {
|
|
343
|
+
localStorage.setItem("strands_auth_user", JSON.stringify(currentUser.value));
|
|
333
344
|
}
|
|
334
345
|
return currentUser.value;
|
|
335
346
|
} finally {
|
|
@@ -407,7 +418,7 @@ function useStrandsAuth() {
|
|
|
407
418
|
mfaRequired.value = false;
|
|
408
419
|
mfaSessionId.value = null;
|
|
409
420
|
availableMfaMethods.value = [];
|
|
410
|
-
setAuthData(authData);
|
|
421
|
+
await setAuthData(authData);
|
|
411
422
|
return authData;
|
|
412
423
|
} finally {
|
|
413
424
|
isVerifyingMfa.value = false;
|
|
@@ -469,23 +480,10 @@ function useStrandsAuth() {
|
|
|
469
480
|
}
|
|
470
481
|
return response.json();
|
|
471
482
|
};
|
|
472
|
-
const setAuthData = (authResponse
|
|
483
|
+
const setAuthData = async (authResponse) => {
|
|
473
484
|
try {
|
|
474
485
|
if (authResponse.user) {
|
|
475
|
-
|
|
476
|
-
currentUser.value = {
|
|
477
|
-
...currentUser.value,
|
|
478
|
-
...authResponse.user,
|
|
479
|
-
// Preserve certain fields that might be missing in refresh response
|
|
480
|
-
avatar: authResponse.user.avatar || currentUser.value.avatar,
|
|
481
|
-
firstName: authResponse.user.firstName || currentUser.value.firstName,
|
|
482
|
-
lastName: authResponse.user.lastName || currentUser.value.lastName
|
|
483
|
-
};
|
|
484
|
-
} else {
|
|
485
|
-
currentUser.value = authResponse.user;
|
|
486
|
-
}
|
|
487
|
-
} else if (!isTokenRefresh) {
|
|
488
|
-
currentUser.value = null;
|
|
486
|
+
currentUser.value = mapApiUserToFrontend(authResponse.user);
|
|
489
487
|
}
|
|
490
488
|
const session = {
|
|
491
489
|
accessToken: authResponse.access_token,
|
|
@@ -634,6 +632,47 @@ function useStrandsAuth() {
|
|
|
634
632
|
}
|
|
635
633
|
return response.json();
|
|
636
634
|
};
|
|
635
|
+
const getUserSessions = async () => {
|
|
636
|
+
const response = await fetch(getUrl("sessions"), {
|
|
637
|
+
method: "GET",
|
|
638
|
+
headers: getAuthHeaders()
|
|
639
|
+
});
|
|
640
|
+
if (!response.ok) {
|
|
641
|
+
throw new Error(`Failed to get user sessions: ${response.status} ${response.statusText}`);
|
|
642
|
+
}
|
|
643
|
+
return response.json();
|
|
644
|
+
};
|
|
645
|
+
const getSessionStats = async () => {
|
|
646
|
+
const response = await fetch(getUrl("sessionsStats"), {
|
|
647
|
+
method: "GET",
|
|
648
|
+
headers: getAuthHeaders()
|
|
649
|
+
});
|
|
650
|
+
if (!response.ok) {
|
|
651
|
+
throw new Error(`Failed to get session stats: ${response.status} ${response.statusText}`);
|
|
652
|
+
}
|
|
653
|
+
return response.json();
|
|
654
|
+
};
|
|
655
|
+
const revokeSession = async (sessionId) => {
|
|
656
|
+
const url = getUrl("sessionRevoke").replace("{session_id}", encodeURIComponent(sessionId));
|
|
657
|
+
const response = await fetch(url, {
|
|
658
|
+
method: "POST",
|
|
659
|
+
headers: getAuthHeaders()
|
|
660
|
+
});
|
|
661
|
+
if (!response.ok) {
|
|
662
|
+
throw new Error(`Failed to revoke session: ${response.status} ${response.statusText}`);
|
|
663
|
+
}
|
|
664
|
+
return response.status === 200;
|
|
665
|
+
};
|
|
666
|
+
const revokeAllOtherSessions = async () => {
|
|
667
|
+
const response = await fetch(getUrl("sessionsRevokeAll"), {
|
|
668
|
+
method: "POST",
|
|
669
|
+
headers: getAuthHeaders()
|
|
670
|
+
});
|
|
671
|
+
if (!response.ok) {
|
|
672
|
+
throw new Error(`Failed to revoke all other sessions: ${response.status} ${response.statusText}`);
|
|
673
|
+
}
|
|
674
|
+
return response.status === 200;
|
|
675
|
+
};
|
|
637
676
|
if (!isInitialized.value) {
|
|
638
677
|
initialize();
|
|
639
678
|
}
|
|
@@ -670,6 +709,11 @@ function useStrandsAuth() {
|
|
|
670
709
|
changeUsername,
|
|
671
710
|
getUsernameCooldown,
|
|
672
711
|
checkUsernameAvailability,
|
|
712
|
+
// Session management
|
|
713
|
+
getUserSessions,
|
|
714
|
+
getSessionStats,
|
|
715
|
+
revokeSession,
|
|
716
|
+
revokeAllOtherSessions,
|
|
673
717
|
initialize,
|
|
674
718
|
setAuthData,
|
|
675
719
|
verifyMfa,
|
|
@@ -689,4 +733,4 @@ function useStrandsAuth() {
|
|
|
689
733
|
};
|
|
690
734
|
}
|
|
691
735
|
exports.useStrandsAuth = useStrandsAuth;
|
|
692
|
-
//# sourceMappingURL=useStrandsAuth-
|
|
736
|
+
//# sourceMappingURL=useStrandsAuth-Co2lLH4X.cjs.map
|