refactor(auth): centralize auth stores — 21 apps use createManaAuthStore factory

Created createManaAuthStore in @manacore/shared-auth-stores that replaces
~350 lines of duplicated auth.svelte.ts per app with a ~10 line factory call.

The factory handles: SSO, passkeys, 2FA, magic links, token management,
password reset, sign up/in/out — everything the old stores did.

Each app only provides devBackendPort and optional onAuthenticated callback.

Before: 21 apps × ~350 lines = 6,800 lines of duplicated auth code
After:  21 apps × ~10 lines  = 182 lines total (97% reduction)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-03-28 18:04:27 +01:00
parent e35bbcd09b
commit e7bcb230be
45 changed files with 430 additions and 6802 deletions

View file

@ -46,6 +46,7 @@
"@manacore/shared-api-client": "workspace:*",
"@manacore/shared-app-onboarding": "workspace:*",
"@manacore/shared-auth": "workspace:*",
"@manacore/shared-auth-stores": "workspace:*",
"@manacore/shared-auth-ui": "workspace:*",
"@manacore/shared-branding": "workspace:*",
"@manacore/shared-error-tracking": "workspace:*",

View file

@ -1,364 +1,9 @@
/**
* Auth Store - Manages authentication state using Svelte 5 runes
* Uses Mana Core Auth
* Auth Store uses centralized Mana auth factory.
*/
import { browser } from '$app/environment';
import { initializeWebAuth, type UserData, type AuthServiceInterface } from '@manacore/shared-auth';
import { createManaAuthStore } from '@manacore/shared-auth-stores';
// Default URLs for local development only
const DEV_AUTH_URL = 'http://localhost:3001';
const DEV_BACKEND_URL = 'http://localhost:3014';
// Get auth URL dynamically at runtime - fallback for SSR and client
function getAuthUrl(): string {
if (browser && typeof window !== 'undefined') {
const injectedUrl = (window as unknown as { __PUBLIC_MANA_CORE_AUTH_URL__?: string })
.__PUBLIC_MANA_CORE_AUTH_URL__;
if (injectedUrl) return injectedUrl;
return import.meta.env.DEV ? DEV_AUTH_URL : '';
}
return process.env.PUBLIC_MANA_CORE_AUTH_URL || DEV_AUTH_URL;
}
// Get backend URL dynamically at runtime
function getBackendUrl(): string {
if (browser && typeof window !== 'undefined') {
const injectedUrl = (window as unknown as { __PUBLIC_BACKEND_URL__?: string })
.__PUBLIC_BACKEND_URL__;
if (injectedUrl) return injectedUrl;
return import.meta.env.DEV ? DEV_BACKEND_URL : '';
}
return process.env.PUBLIC_BACKEND_URL || DEV_BACKEND_URL;
}
// Lazy initialization to avoid SSR issues with localStorage
let _authService: AuthServiceInterface | null = null;
let _tokenManager: ReturnType<typeof initializeWebAuth>['tokenManager'] | null = null;
function getAuthService() {
if (!browser) return null;
if (!_authService) {
const auth = initializeWebAuth({
baseUrl: getAuthUrl(),
backendUrl: getBackendUrl(),
});
_authService = auth.authService;
_tokenManager = auth.tokenManager;
}
return _authService;
}
function getTokenManager() {
if (!browser) return null;
// Ensure auth service is initialized first
getAuthService();
return _tokenManager;
}
// State
let user = $state<UserData | null>(null);
let loading = $state(true);
let initialized = $state(false);
export const authStore = {
// Getters
get user() {
return user;
},
get loading() {
return loading;
},
get isAuthenticated() {
return !!user;
},
get initialized() {
return initialized;
},
/**
* Initialize auth state from stored tokens
* Also tries SSO if no local tokens exist (cross-domain authentication)
*/
async initialize() {
if (initialized) return;
const authService = getAuthService();
if (!authService) {
initialized = true;
loading = false;
return;
}
loading = true;
try {
// First, check if we have valid local tokens
let authenticated = await authService.isAuthenticated();
// If not authenticated locally, try SSO (shared session cookie)
if (!authenticated) {
console.log('No local tokens, trying SSO...');
const ssoResult = await authService.trySSO();
if (ssoResult.success) {
console.log('SSO successful, user authenticated via shared session');
authenticated = true;
}
}
if (authenticated) {
const userData = await authService.getUserFromToken();
user = userData;
}
initialized = true;
} catch (error) {
console.error('Failed to initialize auth:', error);
user = null;
} finally {
loading = false;
}
},
/**
/**
* Check if passkeys are available in this browser
*/
async verifyTwoFactor(code: string, trustDevice?: boolean) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
const result = await authService.verifyTwoFactor(code, trustDevice);
if (result.success) {
const userData = await authService.getUserFromToken();
user = userData;
}
return result;
},
async verifyBackupCode(code: string) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
const result = await authService.verifyBackupCode(code);
if (result.success) {
const userData = await authService.getUserFromToken();
user = userData;
}
return result;
},
async sendMagicLink(email: string) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
return authService.sendMagicLink(email);
},
isPasskeyAvailable(): boolean {
const authService = getAuthService();
if (!authService) return false;
return authService.isPasskeyAvailable();
},
/**
* Sign in with a passkey
*/
async signInWithPasskey() {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const result = await authService.signInWithPasskey();
if (!result.success) {
return { success: false, error: result.error || 'Passkey authentication failed' };
}
// Get user data from token
const userData = await authService.getUserFromToken();
user = userData;
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
/**
* Sign in with email and password
*/
async signIn(email: string, password: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const result = await authService.signIn(email, password);
if (!result.success) {
return { success: false, error: result.error || 'Login failed' };
}
// Get user data from token
const userData = await authService.getUserFromToken();
user = userData;
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
/**
* Sign up with email and password
*/
async signUp(email: string, password: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server', needsVerification: false };
}
try {
// Pass the current app URL for post-verification redirect
const sourceAppUrl = browser ? window.location.origin : undefined;
const result = await authService.signUp(email, password, sourceAppUrl);
if (!result.success) {
return { success: false, error: result.error || 'Signup failed', needsVerification: false };
}
// Mana Core Auth requires separate login after signup
if (result.needsVerification) {
return { success: true, needsVerification: true };
}
// Auto sign in after successful signup
const signInResult = await this.signIn(email, password);
return { ...signInResult, needsVerification: false };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage, needsVerification: false };
}
},
/**
* Sign out
*/
async signOut() {
const authService = getAuthService();
if (!authService) {
user = null;
initialized = false;
return;
}
try {
await authService.signOut();
} catch (error) {
console.error('Sign out error:', error);
} finally {
// Always clear auth state, even if the remote sign-out fails
user = null;
initialized = false;
}
},
/**
* Send password reset email
*/
async resetPassword(email: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
// Pass current app origin so user is redirected back here after clicking email link
const redirectTo = browser ? window.location.origin : undefined;
const result = await authService.forgotPassword(email, redirectTo);
if (!result.success) {
return { success: false, error: result.error || 'Password reset failed' };
}
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
/**
* Reset password with token (from email link)
*/
async resetPasswordWithToken(token: string, newPassword: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const result = await authService.resetPassword(token, newPassword);
if (!result.success) {
return { success: false, error: result.error || 'Password reset failed' };
}
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
/**
* Resend verification email
*/
async resendVerificationEmail(email: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
// Pass the current app URL for post-verification redirect
const sourceAppUrl = browser ? window.location.origin : undefined;
const result = await authService.resendVerificationEmail(email, sourceAppUrl);
if (!result.success) {
return { success: false, error: result.error || 'Failed to resend verification email' };
}
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
/**
* Get access token for API calls (raw token, no refresh)
* @deprecated Use getValidToken() instead for automatic refresh
*/
async getAccessToken() {
const authService = getAuthService();
if (!authService) {
return null;
}
return await authService.getAppToken();
},
/**
* Get a valid access token for API calls
* Automatically refreshes if the token is expired or about to expire
*/
async getValidToken(): Promise<string | null> {
const tokenManager = getTokenManager();
if (!tokenManager) {
return null;
}
return await tokenManager.getValidToken();
},
};
export const authStore = createManaAuthStore({
devBackendPort: 3014,
});

View file

@ -36,6 +36,7 @@
"@manacore/shared-api-client": "workspace:*",
"@manacore/shared-app-onboarding": "workspace:*",
"@manacore/shared-auth": "workspace:*",
"@manacore/shared-auth-stores": "workspace:*",
"@manacore/shared-auth-ui": "workspace:*",
"@manacore/local-store": "workspace:*",
"@manacore/shared-branding": "workspace:*",

View file

@ -1,376 +1,9 @@
/**
* Auth Store - Manages authentication state using Svelte 5 runes
* Now using Mana Core Auth instead of Supabase Auth
* Auth Store uses centralized Mana auth factory.
*/
import { browser } from '$app/environment';
import { initializeWebAuth, type UserData, type AuthServiceInterface } from '@manacore/shared-auth';
import { createManaAuthStore } from '@manacore/shared-auth-stores';
// Default URLs for local development only
const DEV_AUTH_URL = 'http://localhost:3001';
const DEV_BACKEND_URL = 'http://localhost:3002';
// Get auth URL dynamically at runtime - fallback for SSR and client
function getAuthUrl(): string {
if (browser && typeof window !== 'undefined') {
const injectedUrl = (window as unknown as { __PUBLIC_MANA_CORE_AUTH_URL__?: string })
.__PUBLIC_MANA_CORE_AUTH_URL__;
if (injectedUrl) return injectedUrl;
return import.meta.env.DEV ? DEV_AUTH_URL : '';
}
return process.env.PUBLIC_MANA_CORE_AUTH_URL || DEV_AUTH_URL;
}
// Get backend URL dynamically at runtime
function getBackendUrl(): string {
if (browser && typeof window !== 'undefined') {
const injectedUrl = (window as unknown as { __PUBLIC_BACKEND_URL__?: string })
.__PUBLIC_BACKEND_URL__;
if (injectedUrl) return injectedUrl;
return import.meta.env.DEV ? DEV_BACKEND_URL : '';
}
return process.env.PUBLIC_BACKEND_URL || DEV_BACKEND_URL;
}
// Lazy initialization to avoid SSR issues with localStorage
let _authService: AuthServiceInterface | null = null;
let _tokenManager: ReturnType<typeof initializeWebAuth>['tokenManager'] | null = null;
function getAuthService() {
if (!browser) return null;
if (!_authService) {
const auth = initializeWebAuth({
baseUrl: getAuthUrl(),
backendUrl: getBackendUrl(), // Enables automatic token refresh on 401 responses
});
_authService = auth.authService;
_tokenManager = auth.tokenManager;
}
return _authService;
}
function getTokenManager() {
if (!browser) return null;
// Ensure auth service is initialized first
getAuthService();
return _tokenManager;
}
// State
let user = $state<UserData | null>(null);
let loading = $state(true);
let initialized = $state(false);
export const authStore = {
// Getters
get user() {
return user;
},
get loading() {
return loading;
},
get isAuthenticated() {
return !!user;
},
get initialized() {
return initialized;
},
/**
* Initialize auth state from stored tokens
* Also tries SSO if no local tokens exist (cross-domain authentication)
*/
async initialize() {
if (initialized) return;
const authService = getAuthService();
if (!authService) {
initialized = true;
loading = false;
return;
}
loading = true;
try {
// First, check if we have valid local tokens
let authenticated = await authService.isAuthenticated();
// If not authenticated locally, try SSO (shared session cookie)
if (!authenticated) {
console.log('No local tokens, trying SSO...');
const ssoResult = await authService.trySSO();
if (ssoResult.success) {
console.log('SSO successful, user authenticated via shared session');
authenticated = true;
}
}
if (authenticated) {
const userData = await authService.getUserFromToken();
user = userData;
}
initialized = true;
} catch (error) {
console.error('Failed to initialize auth:', error);
user = null;
} finally {
loading = false;
}
},
/**
/**
* Check if passkeys are available in this browser
*/
async verifyTwoFactor(code: string, trustDevice?: boolean) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
const result = await authService.verifyTwoFactor(code, trustDevice);
if (result.success) {
const userData = await authService.getUserFromToken();
user = userData;
}
return result;
},
async verifyBackupCode(code: string) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
const result = await authService.verifyBackupCode(code);
if (result.success) {
const userData = await authService.getUserFromToken();
user = userData;
}
return result;
},
async sendMagicLink(email: string) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
return authService.sendMagicLink(email);
},
isPasskeyAvailable(): boolean {
const authService = getAuthService();
if (!authService) return false;
return authService.isPasskeyAvailable();
},
/**
* Sign in with a passkey
*/
async signInWithPasskey() {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const result = await authService.signInWithPasskey();
if (!result.success) {
return { success: false, error: result.error || 'Passkey authentication failed' };
}
// Get user data from token
const userData = await authService.getUserFromToken();
user = userData;
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
/**
* Sign in with email and password
*/
async signIn(email: string, password: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const result = await authService.signIn(email, password);
if (!result.success) {
return { success: false, error: result.error || 'Login failed' };
}
// Get user data from token
const userData = await authService.getUserFromToken();
user = userData;
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
/**
* Sign up with email and password
*/
async signUp(email: string, password: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server', needsVerification: false };
}
try {
// Pass the current app URL for post-verification redirect
const sourceAppUrl = browser ? window.location.origin : undefined;
const result = await authService.signUp(email, password, sourceAppUrl);
if (!result.success) {
return { success: false, error: result.error || 'Signup failed', needsVerification: false };
}
// Mana Core Auth requires separate login after signup
if (result.needsVerification) {
return { success: true, needsVerification: true };
}
// Auto sign in after successful signup
const signInResult = await this.signIn(email, password);
return { ...signInResult, needsVerification: false };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage, needsVerification: false };
}
},
/**
* Sign out
*/
async signOut() {
const authService = getAuthService();
if (!authService) {
user = null;
return;
}
try {
await authService.signOut();
user = null;
} catch (error) {
console.error('Sign out error:', error);
// Clear user even if sign out fails
user = null;
}
},
/**
* Send password reset email
*/
async resetPassword(email: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const redirectTo = browser ? window.location.origin : undefined;
const result = await authService.forgotPassword(email, redirectTo);
if (!result.success) {
return { success: false, error: result.error || 'Password reset failed' };
}
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
/**
* Reset password with token (from reset email link)
*/
async resetPasswordWithToken(token: string, newPassword: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const result = await authService.resetPassword(token, newPassword);
if (!result.success) {
return { success: false, error: result.error || 'Failed to reset password' };
}
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
/**
* Resend verification email
*/
async resendVerificationEmail(email: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const sourceAppUrl = browser ? window.location.origin : undefined;
const result = await authService.resendVerificationEmail(email, sourceAppUrl);
if (!result.success) {
return { success: false, error: result.error || 'Failed to resend verification email' };
}
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
/**
* Get user credit balance
*/
async getCredits() {
const authService = getAuthService();
if (!authService) {
return null;
}
try {
const credits = await authService.getUserCredits();
return credits;
} catch (error) {
console.error('Failed to get credits:', error);
return null;
}
},
/**
* Get access token for API calls (raw token, no refresh)
* @deprecated Use getValidToken() instead for automatic refresh
*/
async getAccessToken() {
const authService = getAuthService();
if (!authService) {
return null;
}
return await authService.getAppToken();
},
/**
* Get a valid access token for API calls
* Automatically refreshes if the token is expired or about to expire
*/
async getValidToken(): Promise<string | null> {
const tokenManager = getTokenManager();
if (!tokenManager) {
return null;
}
return await tokenManager.getValidToken();
},
};
export const authStore = createManaAuthStore({
devBackendPort: 3002,
});

View file

@ -32,6 +32,7 @@
},
"dependencies": {
"@manacore/shared-auth": "workspace:*",
"@manacore/shared-auth-stores": "workspace:*",
"@manacore/shared-auth-ui": "workspace:*",
"@manacore/local-store": "workspace:*",
"@manacore/shared-branding": "workspace:*",

View file

@ -1,297 +1,9 @@
/**
* Auth Store - Manages authentication state using Svelte 5 runes
* Uses Mana Core Auth
* Auth Store uses centralized Mana auth factory.
*/
import { browser } from '$app/environment';
import { initializeWebAuth } from '@manacore/shared-auth';
import type { UserData } from '@manacore/shared-auth';
import { createManaAuthStore } from '@manacore/shared-auth-stores';
// Default URLs for local development only
const DEV_AUTH_URL = 'http://localhost:3001';
const DEV_BACKEND_URL = 'http://localhost:3025';
function getAuthUrl(): string {
if (browser && typeof window !== 'undefined') {
const injectedUrl = (window as unknown as { __PUBLIC_MANA_CORE_AUTH_URL__?: string })
.__PUBLIC_MANA_CORE_AUTH_URL__;
if (injectedUrl) return injectedUrl;
return import.meta.env.DEV ? DEV_AUTH_URL : '';
}
return process.env.PUBLIC_MANA_CORE_AUTH_URL || DEV_AUTH_URL;
}
function getBackendUrl(): string {
if (browser && typeof window !== 'undefined') {
const injectedUrl = (window as unknown as { __PUBLIC_BACKEND_URL__?: string })
.__PUBLIC_BACKEND_URL__;
if (injectedUrl) return injectedUrl;
return import.meta.env.DEV ? DEV_BACKEND_URL : '';
}
return process.env.PUBLIC_BACKEND_URL || DEV_BACKEND_URL;
}
let _authService: AuthServiceInterface | null = null;
let _tokenManager: ReturnType<typeof initializeWebAuth>['tokenManager'] | null = null;
function getAuthService() {
if (!browser) return null;
if (!_authService) {
const auth = initializeWebAuth({
baseUrl: getAuthUrl(),
backendUrl: getBackendUrl(),
});
_authService = auth.authService;
_tokenManager = auth.tokenManager;
}
return _authService;
}
function getTokenManager() {
if (!browser) return null;
getAuthService();
return _tokenManager;
}
let user = $state<UserData | null>(null);
let loading = $state(true);
let initialized = $state(false);
export const authStore = {
get user() {
return user;
},
get loading() {
return loading;
},
get isAuthenticated() {
return !!user;
},
get initialized() {
return initialized;
},
async initialize() {
if (initialized) return;
const authService = getAuthService();
if (!authService) {
initialized = true;
loading = false;
return;
}
loading = true;
try {
let authenticated = await authService.isAuthenticated();
if (!authenticated) {
const ssoResult = await authService.trySSO();
if (ssoResult.success) {
authenticated = true;
}
}
if (authenticated) {
const userData = await authService.getUserFromToken();
user = userData;
}
initialized = true;
} catch (error) {
console.error('Failed to initialize auth:', error);
user = null;
} finally {
loading = false;
}
},
async verifyTwoFactor(code: string, trustDevice?: boolean) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
const result = await authService.verifyTwoFactor(code, trustDevice);
if (result.success) {
const userData = await authService.getUserFromToken();
user = userData;
}
return result;
},
async verifyBackupCode(code: string) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
const result = await authService.verifyBackupCode(code);
if (result.success) {
const userData = await authService.getUserFromToken();
user = userData;
}
return result;
},
async sendMagicLink(email: string) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
return authService.sendMagicLink(email);
},
isPasskeyAvailable(): boolean {
const authService = getAuthService();
if (!authService) return false;
return authService.isPasskeyAvailable();
},
async signInWithPasskey() {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const result = await authService.signInWithPasskey();
if (!result.success) {
return { success: false, error: result.error || 'Passkey authentication failed' };
}
const userData = await authService.getUserFromToken();
user = userData;
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
async signIn(email: string, password: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const result = await authService.signIn(email, password);
if (!result.success) {
return { success: false, error: result.error || 'Login failed' };
}
const userData = await authService.getUserFromToken();
user = userData;
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
async signUp(email: string, password: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server', needsVerification: false };
}
try {
const sourceAppUrl = browser ? window.location.origin : undefined;
const result = await authService.signUp(email, password, sourceAppUrl);
if (!result.success) {
return { success: false, error: result.error || 'Signup failed', needsVerification: false };
}
if (result.needsVerification) {
return { success: true, needsVerification: true };
}
const signInResult = await this.signIn(email, password);
return { ...signInResult, needsVerification: false };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage, needsVerification: false };
}
},
async signOut() {
const authService = getAuthService();
if (!authService) {
user = null;
return;
}
try {
await authService.signOut();
user = null;
} catch (error) {
console.error('Sign out error:', error);
user = null;
}
},
async resetPassword(email: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const redirectTo = browser ? window.location.origin : undefined;
const result = await authService.forgotPassword(email, redirectTo);
if (!result.success) {
return { success: false, error: result.error || 'Password reset failed' };
}
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
/**
* Reset password with token (from reset email link)
*/
async resetPasswordWithToken(token: string, newPassword: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const result = await authService.resetPassword(token, newPassword);
if (!result.success) {
return { success: false, error: result.error || 'Failed to reset password' };
}
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
async getAccessToken() {
const authService = getAuthService();
if (!authService) return null;
return await authService.getAppToken();
},
async getValidToken(): Promise<string | null> {
const tokenManager = getTokenManager();
if (!tokenManager) return null;
return await tokenManager.getValidToken();
},
async resendVerificationEmail(email: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const sourceAppUrl = browser ? window.location.origin : undefined;
const result = await authService.resendVerificationEmail(email, sourceAppUrl);
if (!result.success) {
return { success: false, error: result.error || 'Failed to send verification email' };
}
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
};
export const authStore = createManaAuthStore({
devBackendPort: 3025,
});

View file

@ -40,6 +40,7 @@
"@manacore/shared-api-client": "workspace:*",
"@manacore/shared-app-onboarding": "workspace:*",
"@manacore/shared-auth": "workspace:*",
"@manacore/shared-auth-stores": "workspace:*",
"@manacore/shared-auth-ui": "workspace:*",
"@manacore/shared-branding": "workspace:*",
"@manacore/shared-error-tracking": "workspace:*",

View file

@ -1,358 +1,9 @@
/**
* Auth Store - Manages authentication state using Svelte 5 runes
* Uses Mana Core Auth
* Auth Store uses centralized Mana auth factory.
*/
import { browser } from '$app/environment';
import { initializeWebAuth, type UserData, type AuthServiceInterface } from '@manacore/shared-auth';
import { createManaAuthStore } from '@manacore/shared-auth-stores';
// Default URLs for local development only
const DEV_AUTH_URL = 'http://localhost:3001';
const DEV_BACKEND_URL = 'http://localhost:3017';
// Get auth URL dynamically at runtime - fallback for SSR and client
function getAuthUrl(): string {
if (browser && typeof window !== 'undefined') {
const injectedUrl = (window as unknown as { __PUBLIC_MANA_CORE_AUTH_URL__?: string })
.__PUBLIC_MANA_CORE_AUTH_URL__;
if (injectedUrl) return injectedUrl;
return import.meta.env.DEV ? DEV_AUTH_URL : '';
}
return process.env.PUBLIC_MANA_CORE_AUTH_URL || DEV_AUTH_URL;
}
// Get backend URL dynamically at runtime
function getBackendUrl(): string {
if (browser && typeof window !== 'undefined') {
const injectedUrl = (window as unknown as { __PUBLIC_BACKEND_URL__?: string })
.__PUBLIC_BACKEND_URL__;
if (injectedUrl) return injectedUrl;
return import.meta.env.DEV ? DEV_BACKEND_URL : '';
}
return process.env.PUBLIC_BACKEND_URL || DEV_BACKEND_URL;
}
// Lazy initialization to avoid SSR issues with localStorage
let _authService: AuthServiceInterface | null = null;
let _tokenManager: ReturnType<typeof initializeWebAuth>['tokenManager'] | null = null;
function getAuthService() {
if (!browser) return null;
if (!_authService) {
const auth = initializeWebAuth({
baseUrl: getAuthUrl(),
backendUrl: getBackendUrl(), // Enables automatic token refresh on 401 responses
});
_authService = auth.authService;
_tokenManager = auth.tokenManager;
}
return _authService;
}
function getTokenManager() {
if (!browser) return null;
// Ensure auth service is initialized first
getAuthService();
return _tokenManager;
}
// State
let user = $state<UserData | null>(null);
let loading = $state(true);
let initialized = $state(false);
export const authStore = {
// Getters
get user() {
return user;
},
get loading() {
return loading;
},
get isAuthenticated() {
return !!user;
},
get initialized() {
return initialized;
},
/**
* Initialize auth state from stored tokens
* Also tries SSO if no local tokens exist (cross-domain authentication)
*/
async initialize() {
if (initialized) return;
const authService = getAuthService();
if (!authService) {
initialized = true;
loading = false;
return;
}
loading = true;
try {
// First, check if we have valid local tokens
let authenticated = await authService.isAuthenticated();
// If not authenticated locally, try SSO (shared session cookie)
if (!authenticated) {
console.log('No local tokens, trying SSO...');
const ssoResult = await authService.trySSO();
if (ssoResult.success) {
console.log('SSO successful, user authenticated via shared session');
authenticated = true;
}
}
if (authenticated) {
const userData = await authService.getUserFromToken();
user = userData;
}
initialized = true;
} catch (error) {
console.error('Failed to initialize auth:', error);
user = null;
} finally {
loading = false;
}
},
/**
/**
* Check if passkeys are available in this browser
*/
async verifyTwoFactor(code: string, trustDevice?: boolean) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
const result = await authService.verifyTwoFactor(code, trustDevice);
if (result.success) {
const userData = await authService.getUserFromToken();
user = userData;
}
return result;
},
async verifyBackupCode(code: string) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
const result = await authService.verifyBackupCode(code);
if (result.success) {
const userData = await authService.getUserFromToken();
user = userData;
}
return result;
},
async sendMagicLink(email: string) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
return authService.sendMagicLink(email);
},
isPasskeyAvailable(): boolean {
const authService = getAuthService();
if (!authService) return false;
return authService.isPasskeyAvailable();
},
/**
* Sign in with a passkey
*/
async signInWithPasskey() {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const result = await authService.signInWithPasskey();
if (!result.success) {
return { success: false, error: result.error || 'Passkey authentication failed' };
}
// Get user data from token
const userData = await authService.getUserFromToken();
user = userData;
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
/**
* Sign in with email and password
*/
async signIn(email: string, password: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const result = await authService.signIn(email, password);
if (!result.success) {
return { success: false, error: result.error || 'Login failed' };
}
// Get user data from token
const userData = await authService.getUserFromToken();
user = userData;
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
/**
* Sign up with email and password
*/
async signUp(email: string, password: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server', needsVerification: false };
}
try {
// Pass the current app URL for post-verification redirect
const sourceAppUrl = browser ? window.location.origin : undefined;
const result = await authService.signUp(email, password, sourceAppUrl);
if (!result.success) {
return { success: false, error: result.error || 'Signup failed', needsVerification: false };
}
// Mana Core Auth requires separate login after signup
if (result.needsVerification) {
return { success: true, needsVerification: true };
}
// Auto sign in after successful signup
const signInResult = await this.signIn(email, password);
return { ...signInResult, needsVerification: false };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage, needsVerification: false };
}
},
/**
* Sign out
*/
async signOut() {
const authService = getAuthService();
if (!authService) {
user = null;
return;
}
try {
await authService.signOut();
user = null;
} catch (error) {
console.error('Sign out error:', error);
// Clear user even if sign out fails
user = null;
}
},
/**
* Send password reset email
*/
async resetPassword(email: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const redirectTo = browser ? window.location.origin : undefined;
const result = await authService.forgotPassword(email, redirectTo);
if (!result.success) {
return { success: false, error: result.error || 'Password reset failed' };
}
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
/**
* Reset password with token (from reset email link)
*/
async resetPasswordWithToken(token: string, newPassword: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const result = await authService.resetPassword(token, newPassword);
if (!result.success) {
return { success: false, error: result.error || 'Failed to reset password' };
}
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
/**
* Resend verification email
*/
async resendVerificationEmail(email: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const sourceAppUrl = browser ? window.location.origin : undefined;
const result = await authService.resendVerificationEmail(email, sourceAppUrl);
if (!result.success) {
return { success: false, error: result.error || 'Failed to resend verification email' };
}
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
/**
* Get access token for API calls (raw token, no refresh)
* @deprecated Use getValidToken() instead for automatic refresh
*/
async getAccessToken() {
const authService = getAuthService();
if (!authService) {
return null;
}
return await authService.getAppToken();
},
/**
* Get a valid access token for API calls
* Automatically refreshes if the token is expired or about to expire
*/
async getValidToken(): Promise<string | null> {
const tokenManager = getTokenManager();
if (!tokenManager) {
return null;
}
return await tokenManager.getValidToken();
},
};
export const authStore = createManaAuthStore({
devBackendPort: 3017,
});

View file

@ -38,6 +38,7 @@
"@manacore/shared-api-client": "workspace:*",
"@manacore/shared-app-onboarding": "workspace:*",
"@manacore/shared-auth": "workspace:*",
"@manacore/shared-auth-stores": "workspace:*",
"@manacore/shared-auth-ui": "workspace:*",
"@manacore/shared-branding": "workspace:*",
"@manacore/shared-error-tracking": "workspace:*",

View file

@ -1,358 +1,9 @@
/**
* Auth Store - Manages authentication state using Svelte 5 runes
* Uses Mana Core Auth
* Auth Store uses centralized Mana auth factory.
*/
import { browser } from '$app/environment';
import { initializeWebAuth, type UserData, type AuthServiceInterface } from '@manacore/shared-auth';
import { createManaAuthStore } from '@manacore/shared-auth-stores';
// Default URLs for local development only
const DEV_AUTH_URL = 'http://localhost:3001';
const DEV_BACKEND_URL = 'http://localhost:3015';
// Get auth URL dynamically at runtime - fallback for SSR and client
function getAuthUrl(): string {
if (browser && typeof window !== 'undefined') {
const injectedUrl = (window as unknown as { __PUBLIC_MANA_CORE_AUTH_URL__?: string })
.__PUBLIC_MANA_CORE_AUTH_URL__;
if (injectedUrl) return injectedUrl;
return import.meta.env.DEV ? DEV_AUTH_URL : '';
}
return process.env.PUBLIC_MANA_CORE_AUTH_URL || DEV_AUTH_URL;
}
// Get backend URL dynamically at runtime
function getBackendUrl(): string {
if (browser && typeof window !== 'undefined') {
const injectedUrl = (window as unknown as { __PUBLIC_BACKEND_URL__?: string })
.__PUBLIC_BACKEND_URL__;
if (injectedUrl) return injectedUrl;
return import.meta.env.DEV ? DEV_BACKEND_URL : '';
}
return process.env.PUBLIC_BACKEND_URL || DEV_BACKEND_URL;
}
// Lazy initialization to avoid SSR issues with localStorage
let _authService: AuthServiceInterface | null = null;
let _tokenManager: ReturnType<typeof initializeWebAuth>['tokenManager'] | null = null;
function getAuthService() {
if (!browser) return null;
if (!_authService) {
const auth = initializeWebAuth({
baseUrl: getAuthUrl(),
backendUrl: getBackendUrl(), // Enables automatic token refresh on 401 responses
});
_authService = auth.authService;
_tokenManager = auth.tokenManager;
}
return _authService;
}
function getTokenManager() {
if (!browser) return null;
// Ensure auth service is initialized first
getAuthService();
return _tokenManager;
}
// State
let user = $state<UserData | null>(null);
let loading = $state(true);
let initialized = $state(false);
export const authStore = {
// Getters
get user() {
return user;
},
get loading() {
return loading;
},
get isAuthenticated() {
return !!user;
},
get initialized() {
return initialized;
},
/**
* Initialize auth state from stored tokens
* Also tries SSO if no local tokens exist (cross-domain authentication)
*/
async initialize() {
if (initialized) return;
const authService = getAuthService();
if (!authService) {
initialized = true;
loading = false;
return;
}
loading = true;
try {
// First, check if we have valid local tokens
let authenticated = await authService.isAuthenticated();
// If not authenticated locally, try SSO (shared session cookie)
if (!authenticated) {
console.log('No local tokens, trying SSO...');
const ssoResult = await authService.trySSO();
if (ssoResult.success) {
console.log('SSO successful, user authenticated via shared session');
authenticated = true;
}
}
if (authenticated) {
const userData = await authService.getUserFromToken();
user = userData;
}
initialized = true;
} catch (error) {
console.error('Failed to initialize auth:', error);
user = null;
} finally {
loading = false;
}
},
/**
/**
* Check if passkeys are available in this browser
*/
async verifyTwoFactor(code: string, trustDevice?: boolean) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
const result = await authService.verifyTwoFactor(code, trustDevice);
if (result.success) {
const userData = await authService.getUserFromToken();
user = userData;
}
return result;
},
async verifyBackupCode(code: string) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
const result = await authService.verifyBackupCode(code);
if (result.success) {
const userData = await authService.getUserFromToken();
user = userData;
}
return result;
},
async sendMagicLink(email: string) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
return authService.sendMagicLink(email);
},
isPasskeyAvailable(): boolean {
const authService = getAuthService();
if (!authService) return false;
return authService.isPasskeyAvailable();
},
/**
* Sign in with a passkey
*/
async signInWithPasskey() {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const result = await authService.signInWithPasskey();
if (!result.success) {
return { success: false, error: result.error || 'Passkey authentication failed' };
}
// Get user data from token
const userData = await authService.getUserFromToken();
user = userData;
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
/**
* Sign in with email and password
*/
async signIn(email: string, password: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const result = await authService.signIn(email, password);
if (!result.success) {
return { success: false, error: result.error || 'Login failed' };
}
// Get user data from token
const userData = await authService.getUserFromToken();
user = userData;
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
/**
* Sign up with email and password
*/
async signUp(email: string, password: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server', needsVerification: false };
}
try {
// Pass the current app URL for post-verification redirect
const sourceAppUrl = browser ? window.location.origin : undefined;
const result = await authService.signUp(email, password, sourceAppUrl);
if (!result.success) {
return { success: false, error: result.error || 'Signup failed', needsVerification: false };
}
// Mana Core Auth requires separate login after signup
if (result.needsVerification) {
return { success: true, needsVerification: true };
}
// Auto sign in after successful signup
const signInResult = await this.signIn(email, password);
return { ...signInResult, needsVerification: false };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage, needsVerification: false };
}
},
/**
* Sign out
*/
async signOut() {
const authService = getAuthService();
if (!authService) {
user = null;
return;
}
try {
await authService.signOut();
user = null;
} catch (error) {
console.error('Sign out error:', error);
// Clear user even if sign out fails
user = null;
}
},
/**
* Send password reset email
*/
async resetPassword(email: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const redirectTo = browser ? window.location.origin : undefined;
const result = await authService.forgotPassword(email, redirectTo);
if (!result.success) {
return { success: false, error: result.error || 'Password reset failed' };
}
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
/**
* Reset password with token (from reset email link)
*/
async resetPasswordWithToken(token: string, newPassword: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const result = await authService.resetPassword(token, newPassword);
if (!result.success) {
return { success: false, error: result.error || 'Failed to reset password' };
}
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
/**
* Resend verification email
*/
async resendVerificationEmail(email: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const sourceAppUrl = browser ? window.location.origin : undefined;
const result = await authService.resendVerificationEmail(email, sourceAppUrl);
if (!result.success) {
return { success: false, error: result.error || 'Failed to resend verification email' };
}
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
/**
* Get access token for API calls (raw token, no refresh)
* @deprecated Use getValidToken() instead for automatic refresh
*/
async getAccessToken() {
const authService = getAuthService();
if (!authService) {
return null;
}
return await authService.getAppToken();
},
/**
* Get a valid access token for API calls
* Automatically refreshes if the token is expired or about to expire
*/
async getValidToken(): Promise<string | null> {
const tokenManager = getTokenManager();
if (!tokenManager) {
return null;
}
return await tokenManager.getValidToken();
},
};
export const authStore = createManaAuthStore({
devBackendPort: 3015,
});

View file

@ -35,6 +35,7 @@
"@manacore/shared-api-client": "workspace:*",
"@manacore/shared-app-onboarding": "workspace:*",
"@manacore/shared-auth": "workspace:*",
"@manacore/shared-auth-stores": "workspace:*",
"@manacore/shared-auth-ui": "workspace:*",
"@manacore/local-store": "workspace:*",
"@manacore/shared-branding": "workspace:*",

View file

@ -1,309 +1,9 @@
/**
* Auth Store - Manages authentication state using Svelte 5 runes
* Uses Mana Core Auth
* Auth Store uses centralized Mana auth factory.
*/
import { browser } from '$app/environment';
import { initializeWebAuth, type UserData, type AuthServiceInterface } from '@manacore/shared-auth';
import { createManaAuthStore } from '@manacore/shared-auth-stores';
// Default URLs for local development only
const DEV_AUTH_URL = 'http://localhost:3001';
const DEV_BACKEND_URL = 'http://localhost:3020';
function getAuthUrl(): string {
if (browser && typeof window !== 'undefined') {
const injectedUrl = (window as unknown as { __PUBLIC_MANA_CORE_AUTH_URL__?: string })
.__PUBLIC_MANA_CORE_AUTH_URL__;
if (injectedUrl) return injectedUrl;
return import.meta.env.DEV ? DEV_AUTH_URL : '';
}
return process.env.PUBLIC_MANA_CORE_AUTH_URL || DEV_AUTH_URL;
}
function getBackendUrl(): string {
if (browser && typeof window !== 'undefined') {
const injectedUrl = (window as unknown as { __PUBLIC_BACKEND_URL__?: string })
.__PUBLIC_BACKEND_URL__;
if (injectedUrl) return injectedUrl;
return import.meta.env.DEV ? DEV_BACKEND_URL : '';
}
return process.env.PUBLIC_BACKEND_URL || DEV_BACKEND_URL;
}
// Lazy initialization to avoid SSR issues with localStorage
let _authService: AuthServiceInterface | null = null;
let _tokenManager: ReturnType<typeof initializeWebAuth>['tokenManager'] | null = null;
function getAuthService() {
if (!browser) return null;
if (!_authService) {
const auth = initializeWebAuth({
baseUrl: getAuthUrl(),
backendUrl: getBackendUrl(),
});
_authService = auth.authService;
_tokenManager = auth.tokenManager;
}
return _authService;
}
function getTokenManager() {
if (!browser) return null;
getAuthService();
return _tokenManager;
}
// State
let user = $state<UserData | null>(null);
let loading = $state(true);
let initialized = $state(false);
export const authStore = {
get user() {
return user;
},
get loading() {
return loading;
},
get isAuthenticated() {
return !!user;
},
get initialized() {
return initialized;
},
async initialize() {
if (initialized) return;
const authService = getAuthService();
if (!authService) {
initialized = true;
loading = false;
return;
}
loading = true;
try {
let authenticated = await authService.isAuthenticated();
if (!authenticated) {
const ssoResult = await authService.trySSO();
if (ssoResult.success) {
authenticated = true;
}
}
if (authenticated) {
const userData = await authService.getUserFromToken();
user = userData;
}
initialized = true;
} catch (error) {
console.error('Failed to initialize auth:', error);
user = null;
} finally {
loading = false;
}
},
async verifyTwoFactor(code: string, trustDevice?: boolean) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
const result = await authService.verifyTwoFactor(code, trustDevice);
if (result.success) {
const userData = await authService.getUserFromToken();
user = userData;
}
return result;
},
async verifyBackupCode(code: string) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
const result = await authService.verifyBackupCode(code);
if (result.success) {
const userData = await authService.getUserFromToken();
user = userData;
}
return result;
},
async sendMagicLink(email: string) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
return authService.sendMagicLink(email);
},
isPasskeyAvailable(): boolean {
const authService = getAuthService();
if (!authService) return false;
return authService.isPasskeyAvailable();
},
async signInWithPasskey() {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const result = await authService.signInWithPasskey();
if (!result.success) {
return { success: false, error: result.error || 'Passkey authentication failed' };
}
const userData = await authService.getUserFromToken();
user = userData;
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
async signIn(email: string, password: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const result = await authService.signIn(email, password);
if (!result.success) {
return { success: false, error: result.error || 'Login failed' };
}
const userData = await authService.getUserFromToken();
user = userData;
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
async signUp(email: string, password: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server', needsVerification: false };
}
try {
const sourceAppUrl = browser ? window.location.origin : undefined;
const result = await authService.signUp(email, password, sourceAppUrl);
if (!result.success) {
return { success: false, error: result.error || 'Signup failed', needsVerification: false };
}
if (result.needsVerification) {
return { success: true, needsVerification: true };
}
const signInResult = await this.signIn(email, password);
return { ...signInResult, needsVerification: false };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage, needsVerification: false };
}
},
async signOut() {
const authService = getAuthService();
if (!authService) {
user = null;
return;
}
try {
await authService.signOut();
user = null;
} catch (error) {
console.error('Sign out error:', error);
user = null;
}
},
async resetPassword(email: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const redirectTo = browser ? window.location.origin : undefined;
const result = await authService.forgotPassword(email, redirectTo);
if (!result.success) {
return { success: false, error: result.error || 'Password reset failed' };
}
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
/**
* Reset password with token (from reset email link)
*/
async resetPasswordWithToken(token: string, newPassword: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const result = await authService.resetPassword(token, newPassword);
if (!result.success) {
return { success: false, error: result.error || 'Failed to reset password' };
}
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
async resendVerificationEmail(email: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const sourceAppUrl = browser ? window.location.origin : undefined;
const result = await authService.resendVerificationEmail(email, sourceAppUrl);
if (!result.success) {
return { success: false, error: result.error || 'Failed to resend verification email' };
}
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
async getAccessToken() {
const authService = getAuthService();
if (!authService) {
return null;
}
return await authService.getAppToken();
},
async getValidToken(): Promise<string | null> {
const tokenManager = getTokenManager();
if (!tokenManager) {
return null;
}
return await tokenManager.getValidToken();
},
};
export const authStore = createManaAuthStore({
devBackendPort: 3020,
});

View file

@ -30,6 +30,7 @@
},
"dependencies": {
"@manacore/shared-auth": "workspace:*",
"@manacore/shared-auth-stores": "workspace:*",
"@manacore/shared-branding": "workspace:*",
"@manacore/shared-error-tracking": "workspace:*",
"@manacore/shared-icons": "workspace:*",

View file

@ -1,237 +1,7 @@
/**
* Auth Store - Manages authentication state using Svelte 5 runes
* Uses Mana Core Auth
* Auth Store uses centralized Mana auth factory.
*/
import { browser } from '$app/environment';
import { initializeWebAuth, type UserData, type AuthServiceInterface } from '@manacore/shared-auth';
import { createManaAuthStore } from '@manacore/shared-auth-stores';
const DEV_AUTH_URL = 'http://localhost:3001';
function getAuthUrl(): string {
if (browser && typeof window !== 'undefined') {
const injectedUrl = (window as unknown as { __PUBLIC_MANA_CORE_AUTH_URL__?: string })
.__PUBLIC_MANA_CORE_AUTH_URL__;
if (injectedUrl) return injectedUrl;
return import.meta.env.DEV ? DEV_AUTH_URL : '';
}
return process.env.PUBLIC_MANA_CORE_AUTH_URL || DEV_AUTH_URL;
}
let _authService: AuthServiceInterface | null = null;
let _tokenManager: ReturnType<typeof initializeWebAuth>['tokenManager'] | null = null;
function getAuthService() {
if (!browser) return null;
if (!_authService) {
const auth = initializeWebAuth({ baseUrl: getAuthUrl() });
_authService = auth.authService;
_tokenManager = auth.tokenManager;
}
return _authService;
}
function getTokenManager() {
if (!browser) return null;
getAuthService();
return _tokenManager;
}
let user = $state<UserData | null>(null);
let loading = $state(true);
let initialized = $state(false);
export const authStore = {
get user() {
return user;
},
get loading() {
return loading;
},
get isAuthenticated() {
return !!user;
},
get initialized() {
return initialized;
},
async initialize() {
if (initialized) return;
const authService = getAuthService();
if (!authService) {
initialized = true;
loading = false;
return;
}
loading = true;
try {
let authenticated = await authService.isAuthenticated();
if (!authenticated) {
const ssoResult = await authService.trySSO();
if (ssoResult.success) authenticated = true;
}
if (authenticated) {
const userData = await authService.getUserFromToken();
user = userData;
}
initialized = true;
} catch (error) {
console.error('Failed to initialize auth:', error);
user = null;
} finally {
loading = false;
}
},
async signIn(email: string, password: string) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
try {
const result = await authService.signIn(email, password);
if (!result.success) return { success: false, error: result.error || 'Login failed' };
const userData = await authService.getUserFromToken();
user = userData;
return { success: true };
} catch (error) {
return { success: false, error: error instanceof Error ? error.message : 'Unknown error' };
}
},
async signUp(email: string, password: string) {
const authService = getAuthService();
if (!authService)
return { success: false, error: 'Auth not available on server', needsVerification: false };
try {
const sourceAppUrl = browser ? window.location.origin : undefined;
const result = await authService.signUp(email, password, sourceAppUrl);
if (!result.success)
return { success: false, error: result.error || 'Signup failed', needsVerification: false };
if (result.needsVerification) return { success: true, needsVerification: true };
const signInResult = await authStore.signIn(email, password);
return { ...signInResult, needsVerification: false };
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error',
needsVerification: false,
};
}
},
async signOut() {
const authService = getAuthService();
if (!authService) {
user = null;
return;
}
try {
await authService.signOut();
} catch (error) {
console.error('Sign out error:', error);
}
user = null;
},
async resetPassword(email: string) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
try {
const redirectTo = browser ? window.location.origin : undefined;
const result = await authService.forgotPassword(email, redirectTo);
return result.success
? { success: true }
: { success: false, error: result.error || 'Password reset failed' };
} catch (error) {
return { success: false, error: error instanceof Error ? error.message : 'Unknown error' };
}
},
async resetPasswordWithToken(token: string, newPassword: string) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
try {
const result = await authService.resetPassword(token, newPassword);
return result.success
? { success: true }
: { success: false, error: result.error || 'Failed to reset password' };
} catch (error) {
return { success: false, error: error instanceof Error ? error.message : 'Unknown error' };
}
},
async verifyTwoFactor(code: string, trustDevice?: boolean) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
const result = await authService.verifyTwoFactor(code, trustDevice);
if (result.success) {
const userData = await authService.getUserFromToken();
user = userData;
}
return result;
},
async verifyBackupCode(code: string) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
const result = await authService.verifyBackupCode(code);
if (result.success) {
const userData = await authService.getUserFromToken();
user = userData;
}
return result;
},
async sendMagicLink(email: string) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
return authService.sendMagicLink(email);
},
isPasskeyAvailable(): boolean {
const authService = getAuthService();
if (!authService) return false;
return authService.isPasskeyAvailable();
},
async signInWithPasskey() {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
try {
const result = await authService.signInWithPasskey();
if (!result.success)
return { success: false, error: result.error || 'Passkey authentication failed' };
const userData = await authService.getUserFromToken();
user = userData;
return { success: true };
} catch (error) {
return { success: false, error: error instanceof Error ? error.message : 'Unknown error' };
}
},
async getAccessToken() {
const authService = getAuthService();
if (!authService) return null;
return await authService.getAppToken();
},
async getValidToken(): Promise<string | null> {
const tokenManager = getTokenManager();
if (!tokenManager) return null;
return await tokenManager.getValidToken();
},
async resendVerificationEmail(email: string) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
try {
const sourceAppUrl = browser ? window.location.origin : undefined;
const result = await authService.resendVerificationEmail(email, sourceAppUrl);
return result.success
? { success: true }
: { success: false, error: result.error || 'Failed to resend verification email' };
} catch (error) {
return { success: false, error: error instanceof Error ? error.message : 'Unknown error' };
}
},
};
export const authStore = createManaAuthStore();

View file

@ -45,6 +45,7 @@
"@manacore/credits": "workspace:^",
"@manacore/qr-export": "workspace:*",
"@manacore/shared-auth": "workspace:*",
"@manacore/shared-auth-stores": "workspace:*",
"@manacore/shared-auth-ui": "workspace:*",
"@manacore/shared-branding": "workspace:*",
"@manacore/shared-config": "workspace:*",

View file

@ -1,412 +1,7 @@
/**
* Auth Store - Manages authentication state using Svelte 5 runes
* Uses Mana Core Auth
* Auth Store uses centralized Mana auth factory.
*/
import { browser } from '$app/environment';
import { initializeWebAuth, type UserData, type AuthServiceInterface } from '@manacore/shared-auth';
import { createManaAuthStore } from '@manacore/shared-auth-stores';
// Default URL for local development only
const DEV_AUTH_URL = 'http://localhost:3001';
// Get auth URL dynamically at runtime - fallback for SSR and client
function getAuthUrl(): string {
if (browser && typeof window !== 'undefined') {
const injectedUrl = (window as unknown as { __PUBLIC_MANA_CORE_AUTH_URL__?: string })
.__PUBLIC_MANA_CORE_AUTH_URL__;
if (injectedUrl) return injectedUrl;
return import.meta.env.DEV ? DEV_AUTH_URL : '';
}
return process.env.PUBLIC_MANA_CORE_AUTH_URL || DEV_AUTH_URL;
}
// Lazy initialization to avoid SSR issues with localStorage
let _authService: AuthServiceInterface | null = null;
let _tokenManager: ReturnType<typeof initializeWebAuth>['tokenManager'] | null = null;
function getAuthService() {
if (!browser) return null;
if (!_authService) {
const auth = initializeWebAuth({ baseUrl: getAuthUrl() });
_authService = auth.authService;
_tokenManager = auth.tokenManager;
}
return _authService;
}
function getTokenManager() {
if (!browser) return null;
// Ensure auth service is initialized first
getAuthService();
return _tokenManager;
}
// State
let user = $state<UserData | null>(null);
let loading = $state(true);
let initialized = $state(false);
export const authStore = {
// Getters
get user() {
return user;
},
get loading() {
return loading;
},
get isAuthenticated() {
return !!user;
},
get initialized() {
return initialized;
},
/**
* Initialize auth state from stored tokens
* Also tries SSO if no local tokens exist (cross-domain authentication)
*/
async initialize() {
if (initialized) return;
const authService = getAuthService();
if (!authService) {
initialized = true;
loading = false;
return;
}
loading = true;
try {
// First, check if we have valid local tokens
let authenticated = await authService.isAuthenticated();
// If not authenticated locally, try SSO (shared session cookie)
if (!authenticated) {
const ssoResult = await authService.trySSO();
if (ssoResult.success) {
authenticated = true;
}
}
if (authenticated) {
const userData = await authService.getUserFromToken();
user = userData;
}
initialized = true;
} catch (error) {
console.error('Failed to initialize auth:', error);
user = null;
} finally {
loading = false;
}
},
async sendMagicLink(email: string) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
return authService.sendMagicLink(email);
},
/**
* Check if passkeys are available in this browser
*/
isPasskeyAvailable(): boolean {
const authService = getAuthService();
if (!authService) return false;
return authService.isPasskeyAvailable();
},
/**
* Sign in with a passkey
*/
async signInWithPasskey() {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const result = await authService.signInWithPasskey();
if (!result.success) {
return { success: false, error: result.error || 'Passkey authentication failed' };
}
// Get user data from token
const userData = await authService.getUserFromToken();
user = userData;
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
async enableTwoFactor(password: string) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available' };
return authService.enableTwoFactor(password);
},
async disableTwoFactor(password: string) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available' };
return authService.disableTwoFactor(password);
},
async verifyTwoFactor(code: string, trustDevice?: boolean) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available' };
const result = await authService.verifyTwoFactor(code, trustDevice);
if (result.success) {
const userData = await authService.getUserFromToken();
user = userData;
}
return result;
},
async verifyBackupCode(code: string) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available' };
const result = await authService.verifyBackupCode(code);
if (result.success) {
const userData = await authService.getUserFromToken();
user = userData;
}
return result;
},
async generateBackupCodes(password: string) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available' };
return authService.generateBackupCodes(password);
},
async registerPasskey(friendlyName?: string) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available' };
return authService.registerPasskey(friendlyName);
},
async listPasskeys() {
const authService = getAuthService();
if (!authService) return [];
return authService.listPasskeys();
},
async deletePasskey(passkeyId: string) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available' };
return authService.deletePasskey(passkeyId);
},
async renamePasskey(passkeyId: string, friendlyName: string) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available' };
return authService.renamePasskey(passkeyId, friendlyName);
},
async getSecurityEvents() {
const authService = getAuthService();
if (!authService) return [];
return authService.getSecurityEvents();
},
async listSessions() {
const authService = getAuthService();
if (!authService) return [];
return authService.listSessions();
},
async revokeSession(sessionId: string) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available' };
return authService.revokeSession(sessionId);
},
/**
* Sign in with email and password
*/
async signIn(email: string, password: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const result = await authService.signIn(email, password);
if (!result.success) {
return { success: false, error: result.error || 'Login failed' };
}
// Get user data from token
const userData = await authService.getUserFromToken();
user = userData;
// Mark as initialized so initialize() doesn't override
initialized = true;
loading = false;
// Update token manager state to reflect valid token
if (_tokenManager) {
await _tokenManager.getValidToken();
}
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
/**
* Sign up with email and password
* @param email User email
* @param password User password
*/
async signUp(email: string, password: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server', needsVerification: false };
}
try {
// Pass the current app URL for post-verification redirect
const sourceAppUrl = browser ? window.location.origin : undefined;
const result = await authService.signUp(email, password, sourceAppUrl);
if (!result.success) {
return { success: false, error: result.error || 'Signup failed', needsVerification: false };
}
// Mana Core Auth requires separate login after signup
if (result.needsVerification) {
return { success: true, needsVerification: true };
}
// Auto sign in after successful signup
const signInResult = await this.signIn(email, password);
return { ...signInResult, needsVerification: false };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage, needsVerification: false };
}
},
/**
* Sign out
*/
async signOut() {
const authService = getAuthService();
if (!authService) {
user = null;
return;
}
try {
await authService.signOut();
user = null;
} catch (error) {
console.error('Sign out error:', error);
// Clear user even if sign out fails
user = null;
}
},
/**
* Send password reset email
*/
async forgotPassword(email: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const redirectTo = browser ? window.location.origin : undefined;
const result = await authService.forgotPassword(email, redirectTo);
if (!result.success) {
return { success: false, error: result.error || 'Password reset failed' };
}
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
/**
* Reset password with token
*/
async resetPassword(token: string, newPassword: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const result = await authService.resetPassword(token, newPassword);
if (!result.success) {
return { success: false, error: result.error || 'Password reset failed' };
}
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
/**
* Resend verification email
*/
async resendVerificationEmail(email: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const sourceAppUrl = browser ? window.location.origin : undefined;
const result = await authService.resendVerificationEmail(email, sourceAppUrl);
if (!result.success) {
return { success: false, error: result.error || 'Failed to resend verification email' };
}
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
/**
* Get access token for API calls (raw token, no refresh)
* @deprecated Use getValidToken() instead for automatic refresh
*/
async getAccessToken() {
const authService = getAuthService();
if (!authService) {
return null;
}
return await authService.getAppToken();
},
/**
* Get a valid access token for API calls
* Automatically refreshes if the token is expired or about to expire
*/
async getValidToken(): Promise<string | null> {
const tokenManager = getTokenManager();
if (!tokenManager) {
return null;
}
return await tokenManager.getValidToken();
},
};
export const authStore = createManaAuthStore();

View file

@ -33,6 +33,7 @@
"@manacore/local-store": "workspace:*",
"@manacore/shared-app-onboarding": "workspace:*",
"@manacore/shared-auth": "workspace:*",
"@manacore/shared-auth-stores": "workspace:*",
"@manacore/shared-auth-ui": "workspace:*",
"@manacore/shared-branding": "workspace:*",
"@manacore/shared-config": "workspace:*",

View file

@ -1,205 +1,7 @@
import { browser } from '$app/environment';
import type { ManaUser } from '$lib/types/auth';
import { authService, tokenManager } from '$lib/auth';
import type { UserData } from '$lib/auth';
// Svelte 5 runes-based auth store
let user = $state<ManaUser | null>(null);
let loading = $state(true);
/**
* Convert UserData from shared-auth to ManaUser
* Auth Store uses centralized Mana auth factory.
*/
function toManaUser(userData: UserData | null): ManaUser | null {
if (!userData) return null;
return {
id: userData.id,
email: userData.email,
role: userData.role,
};
}
export const authStore = {
get user() {
return user;
},
get loading() {
return loading;
},
get isAuthenticated() {
return !!user;
},
import { createManaAuthStore } from '@manacore/shared-auth-stores';
/**
* Initialize auth state from stored tokens
* Also tries SSO if no local tokens exist (cross-domain authentication)
*/
async initialize() {
loading = true;
try {
// First, check if we have valid local tokens
let isAuth = await authService.isAuthenticated();
// If not authenticated locally, try SSO (shared session cookie)
if (!isAuth) {
console.log('No local tokens, trying SSO...');
const ssoResult = await authService.trySSO();
if (ssoResult.success) {
console.log('SSO successful, user authenticated via shared session');
isAuth = true;
}
}
if (isAuth) {
const userData = await authService.getUserFromToken();
user = toManaUser(userData);
}
} catch (error) {
console.error('Failed to initialize auth:', error);
user = null;
} finally {
loading = false;
}
},
/**
* Set user
*/
setUser(newUser: ManaUser | null) {
user = newUser;
},
/**
* Sign out
*/
async signOut() {
try {
await authService.signOut();
user = null;
} catch (error) {
console.error('Sign out failed:', error);
}
},
/**
* Check authentication status
*/
async checkAuth() {
const isAuth = await authService.isAuthenticated();
if (!isAuth) {
user = null;
return false;
}
return true;
},
async verifyTwoFactor(code: string, trustDevice?: boolean) {
const result = await authService.verifyTwoFactor(code, trustDevice);
if (result.success) {
const userData = await authService.getUserFromToken();
user = toManaUser(userData);
}
return result;
},
async verifyBackupCode(code: string) {
const result = await authService.verifyBackupCode(code);
if (result.success) {
const userData = await authService.getUserFromToken();
user = toManaUser(userData);
}
return result;
},
async sendMagicLink(email: string) {
return authService.sendMagicLink(email);
},
isPasskeyAvailable(): boolean {
return authService.isPasskeyAvailable();
},
async signInWithPasskey() {
const result = await authService.signInWithPasskey();
if (result.success) {
const userData = await authService.getUserFromToken();
user = toManaUser(userData);
}
return result;
},
/**
/**
* Sign in with email and password
*/
async signIn(email: string, password: string) {
const result = await authService.signIn(email, password);
if (result.success) {
const userData = await authService.getUserFromToken();
user = toManaUser(userData);
}
return result;
},
/**
* Sign up with email and password
*/
async signUp(email: string, password: string) {
// Pass the current app URL for post-verification redirect
const sourceAppUrl = browser ? window.location.origin : undefined;
const result = await authService.signUp(email, password, sourceAppUrl);
if (result.success && !result.needsVerification) {
const userData = await authService.getUserFromToken();
user = toManaUser(userData);
}
return result;
},
/**
* Send password reset email
*/
async forgotPassword(email: string) {
const redirectTo = browser ? window.location.origin : undefined;
return authService.forgotPassword(email, redirectTo);
},
/**
* Reset password with token (from reset email link)
*/
async resetPasswordWithToken(token: string, newPassword: string) {
try {
const result = await authService.resetPassword(token, newPassword);
if (!result.success) {
return { success: false, error: result.error || 'Failed to reset password' };
}
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
/**
* Resend verification email
*/
async resendVerificationEmail(email: string) {
const sourceAppUrl = browser ? window.location.origin : undefined;
return authService.resendVerificationEmail(email, sourceAppUrl);
},
/**
* Get access token for API calls (raw token, no refresh)
* @deprecated Use getValidToken() instead for automatic refresh
*/
async getAccessToken(): Promise<string | null> {
return await authService.getAppToken();
},
/**
* Get a valid access token for API calls
* Automatically refreshes if the token is expired or about to expire
*/
async getValidToken(): Promise<string | null> {
return await tokenManager.getValidToken();
},
};
export const authStore = createManaAuthStore();

View file

@ -38,6 +38,7 @@
"dependencies": {
"@manacore/shared-api-client": "workspace:*",
"@manacore/shared-auth": "workspace:*",
"@manacore/shared-auth-stores": "workspace:*",
"@manacore/shared-auth-ui": "workspace:*",
"@manacore/local-store": "workspace:*",
"@manacore/shared-branding": "workspace:*",

View file

@ -1,312 +1,9 @@
/**
* Auth Store - Manages authentication state using Svelte 5 runes
* Uses Mana Core Auth
* Auth Store uses centralized Mana auth factory.
*/
import { browser } from '$app/environment';
import { initializeWebAuth } from '@manacore/shared-auth';
import type { UserData } from '@manacore/shared-auth';
import { createManaAuthStore } from '@manacore/shared-auth-stores';
// Default URLs for local development only
const DEV_AUTH_URL = 'http://localhost:3001';
const DEV_BACKEND_URL = 'http://localhost:3010';
function getAuthUrl(): string {
if (browser && typeof window !== 'undefined') {
const injectedUrl = (window as unknown as { __PUBLIC_MANA_CORE_AUTH_URL__?: string })
.__PUBLIC_MANA_CORE_AUTH_URL__;
if (injectedUrl) return injectedUrl;
return import.meta.env.DEV ? DEV_AUTH_URL : '';
}
return process.env.PUBLIC_MANA_CORE_AUTH_URL || DEV_AUTH_URL;
}
function getBackendUrl(): string {
if (browser && typeof window !== 'undefined') {
const injectedUrl = (window as unknown as { __PUBLIC_BACKEND_URL__?: string })
.__PUBLIC_BACKEND_URL__;
if (injectedUrl) return injectedUrl;
return import.meta.env.DEV ? DEV_BACKEND_URL : '';
}
return process.env.PUBLIC_BACKEND_URL || DEV_BACKEND_URL;
}
// Lazy initialization to avoid SSR issues with localStorage
let _authService: AuthServiceInterface | null = null;
let _tokenManager: ReturnType<typeof initializeWebAuth>['tokenManager'] | null = null;
function getAuthService() {
if (!browser) return null;
if (!_authService) {
const auth = initializeWebAuth({
baseUrl: getAuthUrl(),
backendUrl: getBackendUrl(),
});
_authService = auth.authService;
_tokenManager = auth.tokenManager;
}
return _authService;
}
function getTokenManager() {
if (!browser) return null;
getAuthService();
return _tokenManager;
}
// State
let user = $state<UserData | null>(null);
let loading = $state(true);
let initialized = $state(false);
export const authStore = {
get user() {
return user;
},
get isLoading() {
return loading;
},
get isAuthenticated() {
return !!user;
},
get initialized() {
return initialized;
},
async initialize() {
if (initialized) return;
const authService = getAuthService();
if (!authService) {
initialized = true;
loading = false;
return;
}
loading = true;
try {
let authenticated = await authService.isAuthenticated();
if (!authenticated) {
const ssoResult = await authService.trySSO();
if (ssoResult.success) {
authenticated = true;
}
}
if (authenticated) {
const userData = await authService.getUserFromToken();
user = userData;
}
initialized = true;
} catch (error) {
console.error('Failed to initialize auth:', error);
user = null;
} finally {
loading = false;
}
},
async verifyTwoFactor(code: string, trustDevice?: boolean) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
const result = await authService.verifyTwoFactor(code, trustDevice);
if (result.success) {
const userData = await authService.getUserFromToken();
user = userData;
}
return result;
},
async verifyBackupCode(code: string) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
const result = await authService.verifyBackupCode(code);
if (result.success) {
const userData = await authService.getUserFromToken();
user = userData;
}
return result;
},
async sendMagicLink(email: string) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
return authService.sendMagicLink(email);
},
isPasskeyAvailable(): boolean {
const authService = getAuthService();
if (!authService) return false;
return authService.isPasskeyAvailable();
},
async signInWithPasskey() {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const result = await authService.signInWithPasskey();
if (!result.success) {
return { success: false, error: result.error || 'Passkey authentication failed' };
}
const userData = await authService.getUserFromToken();
user = userData;
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
async signIn(email: string, password: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const result = await authService.signIn(email, password);
if (!result.success) {
return { success: false, error: result.error || 'Login failed' };
}
const userData = await authService.getUserFromToken();
user = userData;
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
async signUp(email: string, password: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server', needsVerification: false };
}
try {
const sourceAppUrl = browser ? window.location.origin : undefined;
const result = await authService.signUp(email, password, sourceAppUrl);
if (!result.success) {
return { success: false, error: result.error || 'Signup failed', needsVerification: false };
}
if (result.needsVerification) {
return { success: true, needsVerification: true };
}
const signInResult = await this.signIn(email, password);
return { ...signInResult, needsVerification: false };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage, needsVerification: false };
}
},
async signOut() {
const authService = getAuthService();
if (!authService) {
user = null;
return;
}
try {
await authService.signOut();
user = null;
} catch (error) {
console.error('Sign out error:', error);
user = null;
}
},
async resendVerificationEmail(email: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const sourceAppUrl = browser ? window.location.origin : undefined;
const result = await authService.resendVerificationEmail(email, sourceAppUrl);
if (!result.success) {
return { success: false, error: result.error || 'Failed to resend verification email' };
}
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
async resetPassword(email: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const redirectTo = browser ? window.location.origin : undefined;
const result = await authService.forgotPassword(email, redirectTo);
if (!result.success) {
return { success: false, error: result.error || 'Failed to send reset email' };
}
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
async resetPasswordWithToken(token: string, newPassword: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const result = await authService.resetPassword(token, newPassword);
if (!result.success) {
return { success: false, error: result.error || 'Failed to reset password' };
}
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
async getValidToken(): Promise<string | null> {
const tokenManager = getTokenManager();
if (!tokenManager) {
return null;
}
return await tokenManager.getValidToken();
},
async getAuthHeaders(): Promise<Record<string, string>> {
const tokenManager = getTokenManager();
if (!tokenManager) return {};
const token = await tokenManager.getValidToken();
if (token) {
return { Authorization: `Bearer ${token}` };
}
return {};
},
};
export const authStore = createManaAuthStore({
devBackendPort: 3010,
});

View file

@ -41,6 +41,7 @@
"@nutriphi/shared": "workspace:*",
"@manacore/shared-api-client": "workspace:*",
"@manacore/shared-auth": "workspace:*",
"@manacore/shared-auth-stores": "workspace:*",
"@manacore/shared-auth-ui": "workspace:*",
"@manacore/shared-branding": "workspace:*",
"@manacore/shared-error-tracking": "workspace:*",

View file

@ -1,353 +1,9 @@
/**
* Auth Store - Manages authentication state using Svelte 5 runes
* Uses Mana Core Auth
* Auth Store uses centralized Mana auth factory.
*/
import { browser } from '$app/environment';
import { initializeWebAuth } from '@manacore/shared-auth';
import type { UserData } from '@manacore/shared-auth';
import { createManaAuthStore } from '@manacore/shared-auth-stores';
// Default URLs for local development only
const DEV_AUTH_URL = 'http://localhost:3001';
const DEV_BACKEND_URL = 'http://localhost:3023';
function getAuthUrl(): string {
if (browser && typeof window !== 'undefined') {
const injectedUrl = (window as unknown as { __PUBLIC_MANA_CORE_AUTH_URL__?: string })
.__PUBLIC_MANA_CORE_AUTH_URL__;
if (injectedUrl) return injectedUrl;
return import.meta.env.DEV ? DEV_AUTH_URL : '';
}
return process.env.PUBLIC_MANA_CORE_AUTH_URL || DEV_AUTH_URL;
}
function getBackendUrl(): string {
if (browser && typeof window !== 'undefined') {
const injectedUrl = (window as unknown as { __PUBLIC_BACKEND_URL__?: string })
.__PUBLIC_BACKEND_URL__;
if (injectedUrl) return injectedUrl;
return import.meta.env.DEV ? DEV_BACKEND_URL : '';
}
return process.env.PUBLIC_BACKEND_URL || DEV_BACKEND_URL;
}
// Lazy initialization to avoid SSR issues with localStorage
let _authService: AuthServiceInterface | null = null;
let _tokenManager: ReturnType<typeof initializeWebAuth>['tokenManager'] | null = null;
function getAuthService() {
if (!browser) return null;
if (!_authService) {
const auth = initializeWebAuth({
baseUrl: getAuthUrl(),
backendUrl: getBackendUrl(),
});
_authService = auth.authService;
_tokenManager = auth.tokenManager;
}
return _authService;
}
function getTokenManager() {
if (!browser) return null;
getAuthService();
return _tokenManager;
}
// State
let user = $state<UserData | null>(null);
let loading = $state(true);
let initialized = $state(false);
export const authStore = {
// Getters
get user() {
return user;
},
get loading() {
return loading;
},
get isAuthenticated() {
return !!user;
},
get initialized() {
return initialized;
},
/**
* Initialize auth state from stored tokens
* Also tries SSO if no local tokens exist (cross-domain authentication)
*/
async initialize() {
if (initialized) return;
const authService = getAuthService();
if (!authService) {
initialized = true;
loading = false;
return;
}
loading = true;
try {
// First, check if we have valid local tokens
let authenticated = await authService.isAuthenticated();
// If not authenticated locally, try SSO (shared session cookie)
if (!authenticated) {
console.log('No local tokens, trying SSO...');
const ssoResult = await authService.trySSO();
if (ssoResult.success) {
console.log('SSO successful, user authenticated via shared session');
authenticated = true;
}
}
if (authenticated) {
const userData = await authService.getUserFromToken();
user = userData;
}
initialized = true;
} catch (error) {
console.error('Failed to initialize auth:', error);
user = null;
} finally {
loading = false;
}
},
/**
/**
* Check if passkeys are available in this browser
*/
async verifyTwoFactor(code: string, trustDevice?: boolean) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
const result = await authService.verifyTwoFactor(code, trustDevice);
if (result.success) {
const userData = await authService.getUserFromToken();
user = userData;
}
return result;
},
async verifyBackupCode(code: string) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
const result = await authService.verifyBackupCode(code);
if (result.success) {
const userData = await authService.getUserFromToken();
user = userData;
}
return result;
},
async sendMagicLink(email: string) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
return authService.sendMagicLink(email);
},
isPasskeyAvailable(): boolean {
const authService = getAuthService();
if (!authService) return false;
return authService.isPasskeyAvailable();
},
/**
* Sign in with a passkey
*/
async signInWithPasskey() {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const result = await authService.signInWithPasskey();
if (!result.success) {
return { success: false, error: result.error || 'Passkey authentication failed' };
}
// Get user data from token
const userData = await authService.getUserFromToken();
user = userData;
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
/**
* Sign in with email and password
*/
async signIn(email: string, password: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const result = await authService.signIn(email, password);
if (!result.success) {
return { success: false, error: result.error || 'Login failed' };
}
// Get user data from token
const userData = await authService.getUserFromToken();
user = userData;
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
/**
* Sign up with email and password
*/
async signUp(email: string, password: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server', needsVerification: false };
}
try {
// Pass the current app URL for post-verification redirect
const sourceAppUrl = browser ? window.location.origin : undefined;
const result = await authService.signUp(email, password, sourceAppUrl);
if (!result.success) {
return { success: false, error: result.error || 'Signup failed', needsVerification: false };
}
if (result.needsVerification) {
return { success: true, needsVerification: true };
}
// Auto sign in after successful signup
const signInResult = await this.signIn(email, password);
return { ...signInResult, needsVerification: false };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage, needsVerification: false };
}
},
/**
* Sign out
*/
async signOut() {
const authService = getAuthService();
if (!authService) {
user = null;
return;
}
try {
await authService.signOut();
user = null;
} catch (error) {
console.error('Sign out error:', error);
user = null;
}
},
/**
* Send password reset email
*/
async resetPassword(email: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const redirectTo = browser ? window.location.origin : undefined;
const result = await authService.forgotPassword(email, redirectTo);
if (!result.success) {
return { success: false, error: result.error || 'Password reset failed' };
}
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
/**
* Reset password with token (from reset email link)
*/
async resetPasswordWithToken(token: string, newPassword: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const result = await authService.resetPassword(token, newPassword);
if (!result.success) {
return { success: false, error: result.error || 'Failed to reset password' };
}
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
/**
* Resend verification email
*/
async resendVerificationEmail(email: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const sourceAppUrl = browser ? window.location.origin : undefined;
const result = await authService.resendVerificationEmail(email, sourceAppUrl);
if (!result.success) {
return { success: false, error: result.error || 'Failed to resend verification email' };
}
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
/**
* Get access token for API calls
*/
async getAccessToken() {
const authService = getAuthService();
if (!authService) {
return null;
}
return await authService.getAppToken();
},
/**
* Get a valid access token for API calls
* Automatically refreshes if the token is expired or about to expire
*/
async getValidToken(): Promise<string | null> {
const tokenManager = getTokenManager();
if (!tokenManager) {
return null;
}
return await tokenManager.getValidToken();
},
};
export const authStore = createManaAuthStore({
devBackendPort: 3023,
});

View file

@ -32,6 +32,7 @@
"dependencies": {
"@manacore/shared-api-client": "workspace:*",
"@manacore/shared-auth": "workspace:*",
"@manacore/shared-auth-stores": "workspace:*",
"@manacore/shared-auth-ui": "workspace:*",
"@manacore/local-store": "workspace:*",
"@manacore/shared-branding": "workspace:*",

View file

@ -1,312 +1,9 @@
/**
* Auth Store - Manages authentication state using Svelte 5 runes
* Uses Mana Core Auth
* Auth Store uses centralized Mana auth factory.
*/
import { browser } from '$app/environment';
import { initializeWebAuth } from '@manacore/shared-auth';
import type { UserData } from '@manacore/shared-auth';
import { createManaAuthStore } from '@manacore/shared-auth-stores';
// Default URLs for local development only
const DEV_AUTH_URL = 'http://localhost:3001';
const DEV_BACKEND_URL = 'http://localhost:3019';
function getAuthUrl(): string {
if (browser && typeof window !== 'undefined') {
const injectedUrl = (window as unknown as { __PUBLIC_MANA_CORE_AUTH_URL__?: string })
.__PUBLIC_MANA_CORE_AUTH_URL__;
if (injectedUrl) return injectedUrl;
return import.meta.env.DEV ? DEV_AUTH_URL : '';
}
return process.env.PUBLIC_MANA_CORE_AUTH_URL || DEV_AUTH_URL;
}
function getBackendUrl(): string {
if (browser && typeof window !== 'undefined') {
const injectedUrl = (window as unknown as { __PUBLIC_BACKEND_URL__?: string })
.__PUBLIC_BACKEND_URL__;
if (injectedUrl) return injectedUrl;
return import.meta.env.DEV ? DEV_BACKEND_URL : '';
}
return process.env.PUBLIC_BACKEND_URL || DEV_BACKEND_URL;
}
// Lazy initialization to avoid SSR issues with localStorage
let _authService: AuthServiceInterface | null = null;
let _tokenManager: ReturnType<typeof initializeWebAuth>['tokenManager'] | null = null;
function getAuthService() {
if (!browser) return null;
if (!_authService) {
const auth = initializeWebAuth({
baseUrl: getAuthUrl(),
backendUrl: getBackendUrl(),
});
_authService = auth.authService;
_tokenManager = auth.tokenManager;
}
return _authService;
}
function getTokenManager() {
if (!browser) return null;
getAuthService();
return _tokenManager;
}
// State
let user = $state<UserData | null>(null);
let loading = $state(true);
let initialized = $state(false);
export const authStore = {
get user() {
return user;
},
get loading() {
return loading;
},
get isAuthenticated() {
return !!user;
},
get initialized() {
return initialized;
},
async initialize() {
if (initialized) return;
const authService = getAuthService();
if (!authService) {
initialized = true;
loading = false;
return;
}
loading = true;
try {
let authenticated = await authService.isAuthenticated();
if (!authenticated) {
console.log('No local tokens, trying SSO...');
const ssoResult = await authService.trySSO();
if (ssoResult.success) {
console.log('SSO successful');
authenticated = true;
}
}
if (authenticated) {
const userData = await authService.getUserFromToken();
user = userData;
}
initialized = true;
} catch (error) {
console.error('Failed to initialize auth:', error);
user = null;
} finally {
loading = false;
}
},
async verifyTwoFactor(code: string, trustDevice?: boolean) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
const result = await authService.verifyTwoFactor(code, trustDevice);
if (result.success) {
const userData = await authService.getUserFromToken();
user = userData;
}
return result;
},
async verifyBackupCode(code: string) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
const result = await authService.verifyBackupCode(code);
if (result.success) {
const userData = await authService.getUserFromToken();
user = userData;
}
return result;
},
async sendMagicLink(email: string) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
return authService.sendMagicLink(email);
},
isPasskeyAvailable(): boolean {
const authService = getAuthService();
if (!authService) return false;
return authService.isPasskeyAvailable();
},
async signInWithPasskey() {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const result = await authService.signInWithPasskey();
if (!result.success) {
return { success: false, error: result.error || 'Passkey authentication failed' };
}
const userData = await authService.getUserFromToken();
user = userData;
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
async signIn(email: string, password: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const result = await authService.signIn(email, password);
if (!result.success) {
return { success: false, error: result.error || 'Login failed' };
}
const userData = await authService.getUserFromToken();
user = userData;
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
async signUp(email: string, password: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server', needsVerification: false };
}
try {
const sourceAppUrl = browser ? window.location.origin : undefined;
const result = await authService.signUp(email, password, sourceAppUrl);
if (!result.success) {
return { success: false, error: result.error || 'Signup failed', needsVerification: false };
}
if (result.needsVerification) {
return { success: true, needsVerification: true };
}
const signInResult = await this.signIn(email, password);
return { ...signInResult, needsVerification: false };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage, needsVerification: false };
}
},
async signOut() {
const authService = getAuthService();
if (!authService) {
user = null;
return;
}
try {
await authService.signOut();
user = null;
} catch (error) {
console.error('Sign out error:', error);
user = null;
}
},
async resetPassword(email: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const redirectTo = browser ? window.location.origin : undefined;
const result = await authService.forgotPassword(email, redirectTo);
if (!result.success) {
return { success: false, error: result.error || 'Password reset failed' };
}
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
/**
* Reset password with token (from reset email link)
*/
async resetPasswordWithToken(token: string, newPassword: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const result = await authService.resetPassword(token, newPassword);
if (!result.success) {
return { success: false, error: result.error || 'Failed to reset password' };
}
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
async resendVerificationEmail(email: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const sourceAppUrl = browser ? window.location.origin : undefined;
const result = await authService.resendVerificationEmail(email, sourceAppUrl);
if (!result.success) {
return { success: false, error: result.error || 'Failed to resend verification email' };
}
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
async getAccessToken() {
const authService = getAuthService();
if (!authService) {
return null;
}
return await authService.getAppToken();
},
async getValidToken(): Promise<string | null> {
const tokenManager = getTokenManager();
if (!tokenManager) {
return null;
}
return await tokenManager.getValidToken();
},
};
export const authStore = createManaAuthStore({
devBackendPort: 3019,
});

View file

@ -19,6 +19,7 @@
"@manacore/shared-api-client": "workspace:*",
"@manacore/shared-app-onboarding": "workspace:*",
"@manacore/shared-auth": "workspace:*",
"@manacore/shared-auth-stores": "workspace:*",
"@manacore/shared-auth-ui": "workspace:*",
"@manacore/shared-branding": "workspace:*",
"@manacore/shared-error-tracking": "workspace:*",

View file

@ -1,351 +1,9 @@
/**
* Auth Store - Manages authentication state using Svelte 5 runes
* Uses Mana Core Auth
* Auth Store uses centralized Mana auth factory.
*/
import { browser } from '$app/environment';
import { initializeWebAuth, type UserData, type AuthServiceInterface } from '@manacore/shared-auth';
import { createManaAuthStore } from '@manacore/shared-auth-stores';
// Default URLs for local development only
const DEV_AUTH_URL = 'http://localhost:3001';
const DEV_BACKEND_URL = 'http://localhost:3006';
// Get auth URL dynamically at runtime - fallback for SSR and client
function getAuthUrl(): string {
if (browser && typeof window !== 'undefined') {
const injectedUrl = (window as unknown as { __PUBLIC_MANA_CORE_AUTH_URL__?: string })
.__PUBLIC_MANA_CORE_AUTH_URL__;
if (injectedUrl) return injectedUrl;
return import.meta.env.DEV ? DEV_AUTH_URL : '';
}
return process.env.PUBLIC_MANA_CORE_AUTH_URL || DEV_AUTH_URL;
}
// Get backend URL dynamically at runtime
function getBackendUrl(): string {
if (browser && typeof window !== 'undefined') {
const injectedUrl = (window as unknown as { __PUBLIC_BACKEND_URL__?: string })
.__PUBLIC_BACKEND_URL__;
if (injectedUrl) return injectedUrl;
return import.meta.env.DEV ? DEV_BACKEND_URL : '';
}
return process.env.PUBLIC_BACKEND_URL || DEV_BACKEND_URL;
}
// Lazy initialization to avoid SSR issues with localStorage
let _authService: AuthServiceInterface | null = null;
let _tokenManager: ReturnType<typeof initializeWebAuth>['tokenManager'] | null = null;
function getAuthService() {
if (!browser) return null;
if (!_authService) {
const auth = initializeWebAuth({
baseUrl: getAuthUrl(),
backendUrl: getBackendUrl(),
});
_authService = auth.authService;
_tokenManager = auth.tokenManager;
}
return _authService;
}
function getTokenManager() {
if (!browser) return null;
getAuthService();
return _tokenManager;
}
// State using Svelte 5 runes
let user = $state<UserData | null>(null);
let loading = $state(true);
let initialized = $state(false);
export const authStore = {
get user() {
return user;
},
get loading() {
return loading;
},
get isAuthenticated() {
return !!user;
},
get initialized() {
return initialized;
},
/**
* Initialize auth state from stored tokens
* Also tries SSO if no local tokens exist (cross-domain authentication)
*/
async initialize() {
if (initialized) return;
const authService = getAuthService();
if (!authService) {
initialized = true;
loading = false;
return;
}
loading = true;
try {
// First, check if we have valid local tokens
let authenticated = await authService.isAuthenticated();
// If not authenticated locally, try SSO (shared session cookie)
if (!authenticated) {
console.log('No local tokens, trying SSO...');
const ssoResult = await authService.trySSO();
if (ssoResult.success) {
console.log('SSO successful, user authenticated via shared session');
authenticated = true;
}
}
if (authenticated) {
const userData = await authService.getUserFromToken();
user = userData;
}
initialized = true;
} catch (error) {
console.error('Failed to initialize auth:', error);
user = null;
} finally {
loading = false;
}
},
/**
/**
* Check if passkeys are available in this browser
*/
async verifyTwoFactor(code: string, trustDevice?: boolean) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
const result = await authService.verifyTwoFactor(code, trustDevice);
if (result.success) {
const userData = await authService.getUserFromToken();
user = userData;
}
return result;
},
async verifyBackupCode(code: string) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
const result = await authService.verifyBackupCode(code);
if (result.success) {
const userData = await authService.getUserFromToken();
user = userData;
}
return result;
},
async sendMagicLink(email: string) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
return authService.sendMagicLink(email);
},
isPasskeyAvailable(): boolean {
const authService = getAuthService();
if (!authService) return false;
return authService.isPasskeyAvailable();
},
/**
* Sign in with a passkey
*/
async signInWithPasskey() {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const result = await authService.signInWithPasskey();
if (!result.success) {
return { success: false, error: result.error || 'Passkey authentication failed' };
}
// Get user data from token
const userData = await authService.getUserFromToken();
user = userData;
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
/**
* Sign in with email and password
*/
async signIn(email: string, password: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const result = await authService.signIn(email, password);
if (!result.success) {
return { success: false, error: result.error || 'Login failed' };
}
const userData = await authService.getUserFromToken();
user = userData;
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
/**
* Sign up with email and password
*/
async signUp(email: string, password: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server', needsVerification: false };
}
try {
const sourceAppUrl = browser ? window.location.origin : undefined;
const result = await authService.signUp(email, password, sourceAppUrl);
if (!result.success) {
return { success: false, error: result.error || 'Signup failed', needsVerification: false };
}
if (result.needsVerification) {
return { success: true, needsVerification: true };
}
const signInResult = await this.signIn(email, password);
return { ...signInResult, needsVerification: false };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage, needsVerification: false };
}
},
/**
* Sign out
*/
async signOut() {
const authService = getAuthService();
if (!authService) {
user = null;
return;
}
try {
await authService.signOut();
user = null;
} catch (error) {
console.error('Sign out error:', error);
user = null;
}
},
/**
* Send password reset email
*/
async resetPassword(email: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const redirectTo = browser ? window.location.origin : undefined;
const result = await authService.forgotPassword(email, redirectTo);
if (!result.success) {
return { success: false, error: result.error || 'Password reset failed' };
}
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
/**
* Reset password with token (from reset email link)
*/
async resetPasswordWithToken(token: string, newPassword: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const result = await authService.resetPassword(token, newPassword);
if (!result.success) {
return { success: false, error: result.error || 'Failed to reset password' };
}
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
/**
* Resend verification email
*/
async resendVerificationEmail(email: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const sourceAppUrl = browser ? window.location.origin : undefined;
const result = await authService.resendVerificationEmail(email, sourceAppUrl);
if (!result.success) {
return { success: false, error: result.error || 'Failed to resend verification email' };
}
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
/**
* Get access token for API calls (raw token, no refresh)
* @deprecated Use getValidToken() instead for automatic refresh
*/
async getAccessToken() {
const authService = getAuthService();
if (!authService) {
return null;
}
return await authService.getAppToken();
},
/**
* Get a valid access token for API calls
* Automatically refreshes if the token is expired or about to expire
*/
async getValidToken(): Promise<string | null> {
const tokenManager = getTokenManager();
if (!tokenManager) {
return null;
}
return await tokenManager.getValidToken();
},
};
export const authStore = createManaAuthStore({
devBackendPort: 3006,
});

View file

@ -32,6 +32,7 @@
"dependencies": {
"@manacore/shared-api-client": "workspace:*",
"@manacore/shared-auth": "workspace:*",
"@manacore/shared-auth-stores": "workspace:*",
"@manacore/shared-auth-ui": "workspace:*",
"@manacore/shared-branding": "workspace:*",
"@manacore/shared-error-tracking": "workspace:*",

View file

@ -1,317 +1,9 @@
/**
* Auth Store - Manages authentication state using Svelte 5 runes
* Uses Mana Core Auth
* Auth Store uses centralized Mana auth factory.
*/
import { browser } from '$app/environment';
import { initializeWebAuth } from '@manacore/shared-auth';
import type { UserData } from '@manacore/shared-auth';
import { createManaAuthStore } from '@manacore/shared-auth-stores';
// Default URLs for local development only
const DEV_AUTH_URL = 'http://localhost:3001';
const DEV_BACKEND_URL = 'http://localhost:3022';
function getAuthUrl(): string {
if (browser && typeof window !== 'undefined') {
const injectedUrl = (window as unknown as { __PUBLIC_MANA_CORE_AUTH_URL__?: string })
.__PUBLIC_MANA_CORE_AUTH_URL__;
if (injectedUrl) return injectedUrl;
return import.meta.env.DEV ? DEV_AUTH_URL : '';
}
return process.env.PUBLIC_MANA_CORE_AUTH_URL || DEV_AUTH_URL;
}
function getBackendUrl(): string {
if (browser && typeof window !== 'undefined') {
const injectedUrl = (window as unknown as { __PUBLIC_BACKEND_URL__?: string })
.__PUBLIC_BACKEND_URL__;
if (injectedUrl) return injectedUrl;
return import.meta.env.DEV ? DEV_BACKEND_URL : '';
}
return process.env.PUBLIC_BACKEND_URL || DEV_BACKEND_URL;
}
// Lazy initialization to avoid SSR issues
let _authService: AuthServiceInterface | null = null;
let _tokenManager: ReturnType<typeof initializeWebAuth>['tokenManager'] | null = null;
function getAuthService() {
if (!browser) return null;
if (!_authService) {
const auth = initializeWebAuth({
baseUrl: getAuthUrl(),
backendUrl: getBackendUrl(),
});
_authService = auth.authService;
_tokenManager = auth.tokenManager;
}
return _authService;
}
function getTokenManager() {
if (!browser) return null;
getAuthService();
return _tokenManager;
}
// State
let user = $state<UserData | null>(null);
let loading = $state(true);
let initialized = $state(false);
export const authStore = {
get user() {
return user;
},
get loading() {
return loading;
},
get isAuthenticated() {
return !!user;
},
get initialized() {
return initialized;
},
/**
* Initialize auth state from stored tokens
* Also tries SSO if no local tokens exist (cross-domain authentication)
*/
async initialize() {
if (initialized) return;
const authService = getAuthService();
if (!authService) {
initialized = true;
loading = false;
return;
}
loading = true;
try {
// First, check if we have valid local tokens
let authenticated = await authService.isAuthenticated();
// If not authenticated locally, try SSO (shared session cookie)
if (!authenticated) {
console.log('No local tokens, trying SSO...');
const ssoResult = await authService.trySSO();
if (ssoResult.success) {
console.log('SSO successful, user authenticated via shared session');
authenticated = true;
}
}
if (authenticated) {
const userData = await authService.getUserFromToken();
user = userData;
}
initialized = true;
} catch (error) {
console.error('Failed to initialize auth:', error);
user = null;
} finally {
loading = false;
}
},
async verifyTwoFactor(code: string, trustDevice?: boolean) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
const result = await authService.verifyTwoFactor(code, trustDevice);
if (result.success) {
const userData = await authService.getUserFromToken();
user = userData;
}
return result;
},
async verifyBackupCode(code: string) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
const result = await authService.verifyBackupCode(code);
if (result.success) {
const userData = await authService.getUserFromToken();
user = userData;
}
return result;
},
async sendMagicLink(email: string) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
return authService.sendMagicLink(email);
},
isPasskeyAvailable(): boolean {
const authService = getAuthService();
if (!authService) return false;
return authService.isPasskeyAvailable();
},
async signInWithPasskey() {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const result = await authService.signInWithPasskey();
if (!result.success) {
return { success: false, error: result.error || 'Passkey authentication failed' };
}
const userData = await authService.getUserFromToken();
user = userData;
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
async signIn(email: string, password: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const result = await authService.signIn(email, password);
if (!result.success) {
return { success: false, error: result.error || 'Login failed' };
}
const userData = await authService.getUserFromToken();
user = userData;
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
async signUp(email: string, password: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server', needsVerification: false };
}
try {
// Pass the current app URL for post-verification redirect
const sourceAppUrl = browser ? window.location.origin : undefined;
const result = await authService.signUp(email, password, sourceAppUrl);
if (!result.success) {
return { success: false, error: result.error || 'Signup failed', needsVerification: false };
}
if (result.needsVerification) {
return { success: true, needsVerification: true };
}
const signInResult = await this.signIn(email, password);
return { ...signInResult, needsVerification: false };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage, needsVerification: false };
}
},
async signOut() {
const authService = getAuthService();
if (!authService) {
user = null;
return;
}
try {
await authService.signOut();
user = null;
} catch (error) {
console.error('Sign out error:', error);
user = null;
}
},
/**
* Send password reset email
*/
async resetPassword(email: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const redirectTo = browser ? window.location.origin : undefined;
const result = await authService.forgotPassword(email, redirectTo);
if (!result.success) {
return { success: false, error: result.error || 'Password reset failed' };
}
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
/**
* Reset password with token (from reset email link)
*/
async resetPasswordWithToken(token: string, newPassword: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const result = await authService.resetPassword(token, newPassword);
if (!result.success) {
return { success: false, error: result.error || 'Failed to reset password' };
}
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
async getValidToken(): Promise<string | null> {
const tokenManager = getTokenManager();
if (!tokenManager) {
return null;
}
return await tokenManager.getValidToken();
},
/**
* Resend verification email
*/
async resendVerificationEmail(email: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const sourceAppUrl = browser ? window.location.origin : undefined;
const result = await authService.resendVerificationEmail(email, sourceAppUrl);
if (!result.success) {
return { success: false, error: result.error || 'Failed to resend verification email' };
}
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
};
export const authStore = createManaAuthStore({
devBackendPort: 3022,
});

View file

@ -1,236 +1,7 @@
import { browser } from '$app/environment';
import { goto } from '$app/navigation';
import { initializeWebAuth, type AuthService, type UserData } from '@manacore/shared-auth';
/**
* Auth Store uses centralized Mana auth factory.
*/
// Default URLs for local development only
const DEV_AUTH_URL = 'http://localhost:3001';
const DEV_BACKEND_URL = 'http://localhost:3025';
import { createManaAuthStore } from '@manacore/shared-auth-stores';
function getAuthUrl(): string {
if (browser && typeof window !== 'undefined') {
const injectedUrl = (window as unknown as { __PUBLIC_MANA_CORE_AUTH_URL__?: string })
.__PUBLIC_MANA_CORE_AUTH_URL__;
if (injectedUrl) return injectedUrl;
return import.meta.env.DEV ? DEV_AUTH_URL : '';
}
return process.env.PUBLIC_MANA_CORE_AUTH_URL || DEV_AUTH_URL;
}
function getBackendUrl(): string {
if (browser && typeof window !== 'undefined') {
const injectedUrl = (window as unknown as { __PUBLIC_MANA_LLM_URL__?: string })
.__PUBLIC_MANA_LLM_URL__;
if (injectedUrl) return injectedUrl;
return import.meta.env.DEV ? DEV_BACKEND_URL : '';
}
return process.env.PUBLIC_MANA_LLM_URL || DEV_BACKEND_URL;
}
let user = $state<UserData | null>(null);
let loading = $state(true);
let initialized = $state(false);
let _authService: AuthService | null = null;
let _tokenManager: ReturnType<typeof initializeWebAuth>['tokenManager'] | null = null;
function getAuthService(): AuthService | null {
if (!browser) return null;
if (!_authService) {
const auth = initializeWebAuth({
baseUrl: getAuthUrl(),
backendUrl: getBackendUrl(),
});
_authService = auth.authService;
_tokenManager = auth.tokenManager;
}
return _authService;
}
function getTokenManager() {
if (!browser) return null;
getAuthService();
return _tokenManager;
}
export const authStore = {
get user() {
return user;
},
get loading() {
return loading;
},
get initialized() {
return initialized;
},
get isAuthenticated() {
return !!user;
},
async initialize() {
if (initialized || !browser) return;
loading = true;
try {
const authService = getAuthService();
if (authService) {
const currentUser = await authService.getUserFromToken();
user = currentUser;
}
} catch (error) {
console.error('Auth initialization failed:', error);
user = null;
} finally {
loading = false;
initialized = true;
}
},
async verifyTwoFactor(code: string, trustDevice?: boolean) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
const result = await authService.verifyTwoFactor(code, trustDevice);
if (result.success) {
const userData = await authService.getUserFromToken();
user = userData;
}
return result;
},
async verifyBackupCode(code: string) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
const result = await authService.verifyBackupCode(code);
if (result.success) {
const userData = await authService.getUserFromToken();
user = userData;
}
return result;
},
async sendMagicLink(email: string) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
return authService.sendMagicLink(email);
},
isPasskeyAvailable(): boolean {
const authService = getAuthService();
if (!authService) return false;
return authService.isPasskeyAvailable();
},
async signInWithPasskey() {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const result = await authService.signInWithPasskey();
if (!result.success) {
return { success: false, error: result.error || 'Passkey authentication failed' };
}
const userData = await authService.getUserFromToken();
user = userData;
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
async signIn(email: string, password: string) {
const authService = getAuthService();
if (!authService) throw new Error('Auth not initialized');
const result = await authService.signIn(email, password);
if (result.success) {
const currentUser = await authService.getUserFromToken();
user = currentUser;
}
return result;
},
async signUp(email: string, password: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available', needsVerification: false };
}
try {
const sourceAppUrl = browser ? window.location.origin : undefined;
const result = await authService.signUp(email, password, sourceAppUrl);
if (!result.success) {
return { success: false, error: result.error || 'Signup failed', needsVerification: false };
}
if (result.needsVerification) {
return { success: true, needsVerification: true };
}
// Auto sign in after successful signup
const signInResult = await this.signIn(email, password);
return { ...signInResult, needsVerification: false };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage, needsVerification: false };
}
},
async signOut() {
const authService = getAuthService();
if (authService) {
await authService.signOut();
}
user = null;
goto('/login');
},
async resetPassword(email: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const redirectTo = browser ? window.location.origin : undefined;
const result = await authService.forgotPassword(email, redirectTo);
if (!result.success) {
return { success: false, error: result.error || 'Password reset failed' };
}
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
async getValidToken(): Promise<string | null> {
const tokenManager = getTokenManager();
if (!tokenManager) return null;
return await tokenManager.getValidToken();
},
async resendVerificationEmail(email: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available' };
}
try {
const sourceAppUrl = typeof window !== 'undefined' ? window.location.origin : undefined;
const result = await authService.resendVerificationEmail(email, sourceAppUrl);
if (!result.success) {
return { success: false, error: result.error || 'Failed to resend verification email' };
}
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
};
export const authStore = createManaAuthStore();

View file

@ -33,6 +33,7 @@
"dependencies": {
"@manacore/shared-app-onboarding": "workspace:*",
"@manacore/shared-auth": "workspace:*",
"@manacore/shared-auth-stores": "workspace:*",
"@manacore/shared-auth-ui": "workspace:*",
"@manacore/shared-branding": "workspace:*",
"@manacore/shared-error-tracking": "workspace:*",

View file

@ -1,353 +1,9 @@
/**
* Auth Store - Manages authentication state using Svelte 5 runes
* Uses Mana Core Auth
* Auth Store uses centralized Mana auth factory.
*/
import { browser } from '$app/environment';
import { initializeWebAuth, type UserData, type AuthServiceInterface } from '@manacore/shared-auth';
import { createManaAuthStore } from '@manacore/shared-auth-stores';
// Default URLs for local development only
const DEV_AUTH_URL = 'http://localhost:3001';
const DEV_BACKEND_URL = 'http://localhost:3008';
// Get auth URL dynamically at runtime - fallback for SSR and client
function getAuthUrl(): string {
if (browser && typeof window !== 'undefined') {
const injectedUrl = (window as unknown as { __PUBLIC_MANA_CORE_AUTH_URL__?: string })
.__PUBLIC_MANA_CORE_AUTH_URL__;
if (injectedUrl) return injectedUrl;
return import.meta.env.DEV ? DEV_AUTH_URL : '';
}
return process.env.PUBLIC_MANA_CORE_AUTH_URL || DEV_AUTH_URL;
}
// Get backend URL dynamically at runtime
function getBackendUrl(): string {
if (browser && typeof window !== 'undefined') {
const injectedUrl = (window as unknown as { __PUBLIC_BACKEND_URL__?: string })
.__PUBLIC_BACKEND_URL__;
if (injectedUrl) return injectedUrl;
return import.meta.env.DEV ? DEV_BACKEND_URL : '';
}
return process.env.PUBLIC_BACKEND_URL || DEV_BACKEND_URL;
}
// Lazy initialization to avoid SSR issues with localStorage
let _authService: AuthServiceInterface | null = null;
let _tokenManager: ReturnType<typeof initializeWebAuth>['tokenManager'] | null = null;
function getAuthService() {
if (!browser) return null;
if (!_authService) {
const auth = initializeWebAuth({
baseUrl: getAuthUrl(),
backendUrl: getBackendUrl(),
});
_authService = auth.authService;
_tokenManager = auth.tokenManager;
}
return _authService;
}
function getTokenManager() {
if (!browser) return null;
getAuthService();
return _tokenManager;
}
// State
let user = $state<UserData | null>(null);
let loading = $state(true);
let initialized = $state(false);
export const auth = {
// Getters
get user() {
return user;
},
get isLoading() {
return loading;
},
get isAuthenticated() {
return !!user;
},
get initialized() {
return initialized;
},
/**
* Initialize auth state from stored tokens
* Also tries SSO if no local tokens exist (cross-domain authentication)
*/
async init() {
if (initialized) return;
const authService = getAuthService();
if (!authService) {
initialized = true;
loading = false;
return;
}
loading = true;
try {
let authenticated = await authService.isAuthenticated();
if (!authenticated) {
console.log('No local tokens, trying SSO...');
const ssoResult = await authService.trySSO();
if (ssoResult.success) {
console.log('SSO successful, user authenticated via shared session');
authenticated = true;
}
}
if (authenticated) {
const userData = await authService.getUserFromToken();
user = userData;
}
initialized = true;
} catch (error) {
console.error('Failed to initialize auth:', error);
user = null;
} finally {
loading = false;
}
},
/**
/**
* Check if passkeys are available in this browser
*/
async verifyTwoFactor(code: string, trustDevice?: boolean) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
const result = await authService.verifyTwoFactor(code, trustDevice);
if (result.success) {
const userData = await authService.getUserFromToken();
user = userData;
}
return result;
},
async verifyBackupCode(code: string) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
const result = await authService.verifyBackupCode(code);
if (result.success) {
const userData = await authService.getUserFromToken();
user = userData;
}
return result;
},
async sendMagicLink(email: string) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
return authService.sendMagicLink(email);
},
isPasskeyAvailable(): boolean {
const authService = getAuthService();
if (!authService) return false;
return authService.isPasskeyAvailable();
},
/**
* Sign in with a passkey
*/
async signInWithPasskey() {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const result = await authService.signInWithPasskey();
if (!result.success) {
return { success: false, error: result.error || 'Passkey authentication failed' };
}
// Get user data from token
const userData = await authService.getUserFromToken();
user = userData;
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
/**
* Sign in with email and password
*/
async login(email: string, password: string): Promise<{ success: boolean; error?: string }> {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const result = await authService.signIn(email, password);
if (!result.success) {
return { success: false, error: result.error || 'Login failed' };
}
const userData = await authService.getUserFromToken();
user = userData;
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
/**
* Sign up with email and password
*/
async register(
email: string,
password: string
): Promise<{ success: boolean; error?: string; needsVerification?: boolean }> {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server', needsVerification: false };
}
try {
const sourceAppUrl = browser ? window.location.origin : undefined;
const result = await authService.signUp(email, password, sourceAppUrl);
if (!result.success) {
return { success: false, error: result.error || 'Signup failed', needsVerification: false };
}
if (result.needsVerification) {
return { success: true, needsVerification: true };
}
const signInResult = await this.login(email, password);
return { ...signInResult, needsVerification: false };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage, needsVerification: false };
}
},
/**
* Sign out
*/
async logout() {
const authService = getAuthService();
if (!authService) {
user = null;
return;
}
try {
await authService.signOut();
user = null;
} catch (error) {
console.error('Sign out error:', error);
user = null;
}
},
/**
* Send password reset email
*/
async forgotPassword(email: string): Promise<{ success: boolean; error?: string }> {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const redirectTo = browser ? window.location.origin : undefined;
const result = await authService.forgotPassword(email, redirectTo);
if (!result.success) {
return { success: false, error: result.error || 'Password reset failed' };
}
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
/**
* Reset password with token (from reset email link)
*/
async resetPasswordWithToken(token: string, newPassword: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const result = await authService.resetPassword(token, newPassword);
if (!result.success) {
return { success: false, error: result.error || 'Failed to reset password' };
}
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
/**
* Get access token for API calls
* @deprecated Use getValidToken() instead for automatic refresh
*/
async getAccessToken() {
const authService = getAuthService();
if (!authService) {
return null;
}
return await authService.getAppToken();
},
/**
* Get a valid access token for API calls
* Automatically refreshes if the token is expired or about to expire
*/
async getValidToken(): Promise<string | null> {
const tokenManager = getTokenManager();
if (!tokenManager) {
return null;
}
return await tokenManager.getValidToken();
},
/**
* Resend verification email
*/
async resendVerificationEmail(email: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const sourceAppUrl = browser ? window.location.origin : undefined;
const result = await authService.resendVerificationEmail(email, sourceAppUrl);
if (!result.success) {
return { success: false, error: result.error || 'Failed to resend verification email' };
}
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
};
export const authStore = createManaAuthStore({
devBackendPort: 3008,
});

View file

@ -34,6 +34,7 @@
"dependencies": {
"@manacore/shared-api-client": "workspace:*",
"@manacore/shared-auth": "workspace:*",
"@manacore/shared-auth-stores": "workspace:*",
"@manacore/shared-types": "workspace:*",
"@manacore/shared-utils": "workspace:*",
"@manacore/shared-auth-ui": "workspace:*",

View file

@ -1,311 +1,9 @@
/**
* Auth Store - Manages authentication state using Svelte 5 runes
* Uses Mana Core Auth
* Auth Store uses centralized Mana auth factory.
*/
import { browser } from '$app/environment';
import { initializeWebAuth } from '@manacore/shared-auth';
import type { UserData } from '@manacore/shared-auth';
import { createManaAuthStore } from '@manacore/shared-auth-stores';
// Default URLs for local development only
const DEV_AUTH_URL = 'http://localhost:3001';
const DEV_BACKEND_URL = 'http://localhost:3011';
function getAuthUrl(): string {
if (browser && typeof window !== 'undefined') {
const injectedUrl = (window as unknown as { __PUBLIC_MANA_CORE_AUTH_URL__?: string })
.__PUBLIC_MANA_CORE_AUTH_URL__;
if (injectedUrl) return injectedUrl;
return import.meta.env.DEV ? DEV_AUTH_URL : '';
}
return process.env.PUBLIC_MANA_CORE_AUTH_URL || DEV_AUTH_URL;
}
function getBackendUrl(): string {
if (browser && typeof window !== 'undefined') {
const injectedUrl = (window as unknown as { __PUBLIC_BACKEND_URL__?: string })
.__PUBLIC_BACKEND_URL__;
if (injectedUrl) return injectedUrl;
return import.meta.env.DEV ? DEV_BACKEND_URL : '';
}
return process.env.PUBLIC_BACKEND_URL || DEV_BACKEND_URL;
}
let _authService: AuthServiceInterface | null = null;
let _tokenManager: ReturnType<typeof initializeWebAuth>['tokenManager'] | null = null;
function getAuthService() {
if (!browser) return null;
if (!_authService) {
const auth = initializeWebAuth({
baseUrl: getAuthUrl(),
backendUrl: getBackendUrl(),
});
_authService = auth.authService;
_tokenManager = auth.tokenManager;
}
return _authService;
}
function getTokenManager() {
if (!browser) return null;
getAuthService();
return _tokenManager;
}
let user = $state<UserData | null>(null);
let loading = $state(true);
let initialized = $state(false);
export const authStore = {
get user() {
return user;
},
get loading() {
return loading;
},
get isAuthenticated() {
return !!user;
},
get initialized() {
return initialized;
},
/**
* Initialize auth state from stored tokens
* Also tries SSO if no local tokens exist (cross-domain authentication)
*/
async initialize() {
if (initialized) return;
const authService = getAuthService();
if (!authService) {
initialized = true;
loading = false;
return;
}
loading = true;
try {
// First, check if we have valid local tokens
let authenticated = await authService.isAuthenticated();
// If not authenticated locally, try SSO (shared session cookie)
if (!authenticated) {
console.log('No local tokens, trying SSO...');
const ssoResult = await authService.trySSO();
if (ssoResult.success) {
console.log('SSO successful, user authenticated via shared session');
authenticated = true;
}
}
if (authenticated) {
const userData = await authService.getUserFromToken();
user = userData;
}
initialized = true;
} catch (error) {
console.error('Failed to initialize auth:', error);
user = null;
} finally {
loading = false;
}
},
async verifyTwoFactor(code: string, trustDevice?: boolean) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
const result = await authService.verifyTwoFactor(code, trustDevice);
if (result.success) {
const userData = await authService.getUserFromToken();
user = userData;
}
return result;
},
async verifyBackupCode(code: string) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
const result = await authService.verifyBackupCode(code);
if (result.success) {
const userData = await authService.getUserFromToken();
user = userData;
}
return result;
},
async sendMagicLink(email: string) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
return authService.sendMagicLink(email);
},
isPasskeyAvailable(): boolean {
const authService = getAuthService();
if (!authService) return false;
return authService.isPasskeyAvailable();
},
async signInWithPasskey() {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const result = await authService.signInWithPasskey();
if (!result.success) {
return { success: false, error: result.error || 'Passkey authentication failed' };
}
const userData = await authService.getUserFromToken();
user = userData;
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
async signIn(email: string, password: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const result = await authService.signIn(email, password);
if (!result.success) {
return { success: false, error: result.error || 'Login failed' };
}
const userData = await authService.getUserFromToken();
user = userData;
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
async signUp(email: string, password: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server', needsVerification: false };
}
try {
const sourceAppUrl = browser ? window.location.origin : undefined;
const result = await authService.signUp(email, password, sourceAppUrl);
if (!result.success) {
return { success: false, error: result.error || 'Signup failed', needsVerification: false };
}
if (result.needsVerification) {
return { success: true, needsVerification: true };
}
const signInResult = await this.signIn(email, password);
return { ...signInResult, needsVerification: false };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage, needsVerification: false };
}
},
async signOut() {
const authService = getAuthService();
if (!authService) {
user = null;
return;
}
try {
await authService.signOut();
user = null;
} catch (error) {
console.error('Sign out error:', error);
user = null;
}
},
async resetPassword(email: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const redirectTo = browser ? window.location.origin : undefined;
const result = await authService.forgotPassword(email, redirectTo);
if (!result.success) {
return { success: false, error: result.error || 'Password reset failed' };
}
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
/**
* Reset password with token (from reset email link)
*/
async resetPasswordWithToken(token: string, newPassword: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const result = await authService.resetPassword(token, newPassword);
if (!result.success) {
return { success: false, error: result.error || 'Failed to reset password' };
}
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
async getValidToken(): Promise<string | null> {
const tokenManager = getTokenManager();
if (!tokenManager) {
return null;
}
return await tokenManager.getValidToken();
},
/**
* Resend verification email
*/
async resendVerificationEmail(email: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const sourceAppUrl = browser ? window.location.origin : undefined;
const result = await authService.resendVerificationEmail(email, sourceAppUrl);
if (!result.success) {
return { success: false, error: result.error || 'Failed to resend verification email' };
}
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
};
export const authStore = createManaAuthStore({
devBackendPort: 3011,
});

View file

@ -36,6 +36,7 @@
"dependencies": {
"@manacore/shared-api-client": "workspace:*",
"@manacore/shared-auth": "workspace:*",
"@manacore/shared-auth-stores": "workspace:*",
"@manacore/shared-auth-ui": "workspace:*",
"@manacore/shared-branding": "workspace:*",
"@manacore/shared-error-tracking": "workspace:*",

View file

@ -1,331 +1,9 @@
/**
* Auth Store - Manages authentication state using Svelte 5 runes
* Uses Mana Core Auth
* Auth Store uses centralized Mana auth factory.
*/
import { browser } from '$app/environment';
import { initializeWebAuth, type UserData, type AuthServiceInterface } from '@manacore/shared-auth';
import { apiClient } from '$lib/api/client';
import { createManaAuthStore } from '@manacore/shared-auth-stores';
const DEV_AUTH_URL = 'http://localhost:3001';
const DEV_BACKEND_URL = 'http://localhost:3024';
function getAuthUrl(): string {
if (browser && typeof window !== 'undefined') {
const injectedUrl = (window as unknown as { __PUBLIC_MANA_CORE_AUTH_URL__?: string })
.__PUBLIC_MANA_CORE_AUTH_URL__;
if (injectedUrl) return injectedUrl;
return import.meta.env.DEV ? DEV_AUTH_URL : '';
}
return process.env.PUBLIC_MANA_CORE_AUTH_URL || DEV_AUTH_URL;
}
function getBackendUrl(): string {
if (browser && typeof window !== 'undefined') {
const injectedUrl = (window as unknown as { __PUBLIC_BACKEND_URL__?: string })
.__PUBLIC_BACKEND_URL__;
if (injectedUrl) return injectedUrl;
return import.meta.env.DEV ? DEV_BACKEND_URL : '';
}
return process.env.PUBLIC_BACKEND_URL || DEV_BACKEND_URL;
}
let _authService: AuthServiceInterface | null = null;
let _tokenManager: ReturnType<typeof initializeWebAuth>['tokenManager'] | null = null;
function getAuthService() {
if (!browser) return null;
if (!_authService) {
const auth = initializeWebAuth({
baseUrl: getAuthUrl(),
backendUrl: getBackendUrl(),
});
_authService = auth.authService;
_tokenManager = auth.tokenManager;
}
return _authService;
}
function getTokenManager() {
if (!browser) return null;
getAuthService();
return _tokenManager;
}
let user = $state<UserData | null>(null);
let loading = $state(true);
let initialized = $state(false);
export const authStore = {
get user() {
return user;
},
get loading() {
return loading;
},
get isAuthenticated() {
return !!user;
},
get initialized() {
return initialized;
},
/**
* Initialize auth state from stored tokens
* Also tries SSO if no local tokens exist (cross-domain authentication)
*/
async initialize() {
if (initialized) return;
const authService = getAuthService();
if (!authService) {
initialized = true;
loading = false;
return;
}
loading = true;
try {
// First, check if we have valid local tokens
let authenticated = await authService.isAuthenticated();
// If not authenticated locally, try SSO (shared session cookie)
if (!authenticated) {
console.log('No local tokens, trying SSO...');
const ssoResult = await authService.trySSO();
if (ssoResult.success) {
console.log('SSO successful, user authenticated via shared session');
authenticated = true;
}
}
if (authenticated) {
const userData = await authService.getUserFromToken();
user = userData;
const token = await authService.getAppToken();
if (token) {
apiClient.setAccessToken(token);
}
}
initialized = true;
} catch (error) {
console.error('Failed to initialize auth:', error);
user = null;
} finally {
loading = false;
}
},
async verifyTwoFactor(code: string, trustDevice?: boolean) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
const result = await authService.verifyTwoFactor(code, trustDevice);
if (result.success) {
const userData = await authService.getUserFromToken();
user = userData;
}
return result;
},
async verifyBackupCode(code: string) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
const result = await authService.verifyBackupCode(code);
if (result.success) {
const userData = await authService.getUserFromToken();
user = userData;
}
return result;
},
async sendMagicLink(email: string) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
return authService.sendMagicLink(email);
},
isPasskeyAvailable(): boolean {
const authService = getAuthService();
if (!authService) return false;
return authService.isPasskeyAvailable();
},
async signInWithPasskey() {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const result = await authService.signInWithPasskey();
if (!result.success) {
return { success: false, error: result.error || 'Passkey authentication failed' };
}
const userData = await authService.getUserFromToken();
user = userData;
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
async signIn(email: string, password: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const result = await authService.signIn(email, password);
if (!result.success) {
return { success: false, error: result.error || 'Login failed' };
}
const userData = await authService.getUserFromToken();
user = userData;
const token = await authService.getAppToken();
if (token) {
apiClient.setAccessToken(token);
}
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
async signUp(email: string, password: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server', needsVerification: false };
}
try {
const sourceAppUrl = browser ? window.location.origin : undefined;
const result = await authService.signUp(email, password, sourceAppUrl);
if (!result.success) {
return { success: false, error: result.error || 'Signup failed', needsVerification: false };
}
if (result.needsVerification) {
return { success: true, needsVerification: true };
}
const signInResult = await this.signIn(email, password);
return { ...signInResult, needsVerification: false };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage, needsVerification: false };
}
},
async signOut() {
const authService = getAuthService();
if (!authService) {
user = null;
apiClient.setAccessToken(null);
return;
}
try {
await authService.signOut();
user = null;
apiClient.setAccessToken(null);
} catch (error) {
console.error('Sign out error:', error);
user = null;
apiClient.setAccessToken(null);
}
},
async resetPassword(email: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const redirectTo = browser ? window.location.origin : undefined;
const result = await authService.forgotPassword(email, redirectTo);
if (!result.success) {
return { success: false, error: result.error || 'Password reset failed' };
}
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
/**
* Reset password with token (from reset email link)
*/
async resetPasswordWithToken(token: string, newPassword: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const result = await authService.resetPassword(token, newPassword);
if (!result.success) {
return { success: false, error: result.error || 'Failed to reset password' };
}
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
async getAccessToken() {
const authService = getAuthService();
if (!authService) {
return null;
}
return await authService.getAppToken();
},
async getValidToken(): Promise<string | null> {
const tokenManager = getTokenManager();
if (!tokenManager) {
return null;
}
return await tokenManager.getValidToken();
},
/**
* Resend verification email
*/
async resendVerificationEmail(email: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const sourceAppUrl = browser ? window.location.origin : undefined;
const result = await authService.resendVerificationEmail(email, sourceAppUrl);
if (!result.success) {
return { success: false, error: result.error || 'Failed to resend verification email' };
}
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
};
export const authStore = createManaAuthStore({
devBackendPort: 3024,
});

View file

@ -40,6 +40,7 @@
"@manacore/shared-api-client": "workspace:*",
"@manacore/shared-app-onboarding": "workspace:*",
"@manacore/shared-auth": "workspace:*",
"@manacore/shared-auth-stores": "workspace:*",
"@manacore/shared-auth-ui": "workspace:*",
"@manacore/shared-branding": "workspace:*",
"@manacore/shared-error-tracking": "workspace:*",

View file

@ -1,349 +1,9 @@
/**
* Auth Store - Manages authentication state using Svelte 5 runes
* Uses Mana Core Auth
* Auth Store uses centralized Mana auth factory.
*/
import { browser } from '$app/environment';
import { initializeWebAuth, type UserData, type AuthServiceInterface } from '@manacore/shared-auth';
import { createManaAuthStore } from '@manacore/shared-auth-stores';
// Default URLs for local development only
const DEV_AUTH_URL = 'http://localhost:3001';
const DEV_BACKEND_URL = 'http://localhost:3016';
// Get auth URL dynamically at runtime - fallback for SSR and client
function getAuthUrl(): string {
if (browser && typeof window !== 'undefined') {
const injectedUrl = (window as unknown as { __PUBLIC_MANA_CORE_AUTH_URL__?: string })
.__PUBLIC_MANA_CORE_AUTH_URL__;
if (injectedUrl) return injectedUrl;
return import.meta.env.DEV ? DEV_AUTH_URL : '';
}
return process.env.PUBLIC_MANA_CORE_AUTH_URL || DEV_AUTH_URL;
}
// Get backend URL dynamically at runtime
function getBackendUrl(): string {
if (browser && typeof window !== 'undefined') {
const injectedUrl = (window as unknown as { __PUBLIC_BACKEND_URL__?: string })
.__PUBLIC_BACKEND_URL__;
if (injectedUrl) return injectedUrl;
return import.meta.env.DEV ? DEV_BACKEND_URL : '';
}
return process.env.PUBLIC_BACKEND_URL || DEV_BACKEND_URL;
}
// Lazy initialization to avoid SSR issues with localStorage
let _authService: AuthServiceInterface | null = null;
let _tokenManager: ReturnType<typeof initializeWebAuth>['tokenManager'] | null = null;
function getAuthService() {
if (!browser) return null;
if (!_authService) {
const auth = initializeWebAuth({
baseUrl: getAuthUrl(),
backendUrl: getBackendUrl(),
});
_authService = auth.authService;
_tokenManager = auth.tokenManager;
}
return _authService;
}
function getTokenManager() {
if (!browser) return null;
getAuthService();
return _tokenManager;
}
// State
let user = $state<UserData | null>(null);
let loading = $state(true);
let initialized = $state(false);
export const authStore = {
get user() {
return user;
},
get loading() {
return loading;
},
get isAuthenticated() {
return !!user;
},
get initialized() {
return initialized;
},
/**
* Initialize auth state from stored tokens
* Also tries SSO if no local tokens exist (cross-domain authentication)
*/
async initialize() {
if (initialized) return;
const authService = getAuthService();
if (!authService) {
initialized = true;
loading = false;
return;
}
loading = true;
try {
let authenticated = await authService.isAuthenticated();
if (!authenticated) {
console.log('No local tokens, trying SSO...');
const ssoResult = await authService.trySSO();
if (ssoResult.success) {
console.log('SSO successful, user authenticated via shared session');
authenticated = true;
}
}
if (authenticated) {
const userData = await authService.getUserFromToken();
user = userData;
}
initialized = true;
} catch (error) {
console.error('Failed to initialize auth:', error);
user = null;
} finally {
loading = false;
}
},
/**
/**
* Check if passkeys are available in this browser
*/
async verifyTwoFactor(code: string, trustDevice?: boolean) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
const result = await authService.verifyTwoFactor(code, trustDevice);
if (result.success) {
const userData = await authService.getUserFromToken();
user = userData;
}
return result;
},
async verifyBackupCode(code: string) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
const result = await authService.verifyBackupCode(code);
if (result.success) {
const userData = await authService.getUserFromToken();
user = userData;
}
return result;
},
async sendMagicLink(email: string) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
return authService.sendMagicLink(email);
},
isPasskeyAvailable(): boolean {
const authService = getAuthService();
if (!authService) return false;
return authService.isPasskeyAvailable();
},
/**
* Sign in with a passkey
*/
async signInWithPasskey() {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const result = await authService.signInWithPasskey();
if (!result.success) {
return { success: false, error: result.error || 'Passkey authentication failed' };
}
// Get user data from token
const userData = await authService.getUserFromToken();
user = userData;
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
/**
* Sign in with email and password
*/
async signIn(email: string, password: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const result = await authService.signIn(email, password);
if (!result.success) {
return { success: false, error: result.error || 'Login failed' };
}
const userData = await authService.getUserFromToken();
user = userData;
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
/**
* Sign up with email and password
*/
async signUp(email: string, password: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server', needsVerification: false };
}
try {
const sourceAppUrl = browser ? window.location.origin : undefined;
const result = await authService.signUp(email, password, sourceAppUrl);
if (!result.success) {
return { success: false, error: result.error || 'Signup failed', needsVerification: false };
}
if (result.needsVerification) {
return { success: true, needsVerification: true };
}
const signInResult = await this.signIn(email, password);
return { ...signInResult, needsVerification: false };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage, needsVerification: false };
}
},
/**
* Sign out
*/
async signOut() {
const authService = getAuthService();
if (!authService) {
user = null;
return;
}
try {
await authService.signOut();
user = null;
} catch (error) {
console.error('Sign out error:', error);
user = null;
}
},
/**
* Send password reset email
*/
async resetPassword(email: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const redirectTo = browser ? window.location.origin : undefined;
const result = await authService.forgotPassword(email, redirectTo);
if (!result.success) {
return { success: false, error: result.error || 'Password reset failed' };
}
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
/**
* Reset password with token (from reset email link)
*/
async resetPasswordWithToken(token: string, newPassword: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const result = await authService.resetPassword(token, newPassword);
if (!result.success) {
return { success: false, error: result.error || 'Failed to reset password' };
}
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
/**
* Resend verification email
*/
async resendVerificationEmail(email: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const sourceAppUrl = browser ? window.location.origin : undefined;
const result = await authService.resendVerificationEmail(email, sourceAppUrl);
if (!result.success) {
return { success: false, error: result.error || 'Failed to resend verification email' };
}
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
/**
* Get access token for API calls (raw token, no refresh)
* @deprecated Use getValidToken() instead for automatic refresh
*/
async getAccessToken() {
const authService = getAuthService();
if (!authService) {
return null;
}
return await authService.getAppToken();
},
/**
* Get a valid access token for API calls
* Automatically refreshes if the token is expired or about to expire
*/
async getValidToken(): Promise<string | null> {
const tokenManager = getTokenManager();
if (!tokenManager) {
return null;
}
return await tokenManager.getValidToken();
},
};
export const authStore = createManaAuthStore({
devBackendPort: 3016,
});

View file

@ -43,6 +43,7 @@
"@manacore/shared-api-client": "workspace:*",
"@manacore/shared-app-onboarding": "workspace:*",
"@manacore/shared-auth": "workspace:*",
"@manacore/shared-auth-stores": "workspace:*",
"@manacore/shared-auth-ui": "workspace:*",
"@manacore/shared-branding": "workspace:*",
"@manacore/shared-error-tracking": "workspace:*",

View file

@ -1,385 +1,9 @@
/**
* Auth Store - Manages authentication state using Svelte 5 runes
* Uses Mana Core Auth
* Auth Store uses centralized Mana auth factory.
*/
import { browser } from '$app/environment';
import { initializeWebAuth, type UserData, type AuthServiceInterface } from '@manacore/shared-auth';
import { apiClient } from '$lib/api/client';
import { todoStore } from '$lib/data/local-store';
import { createManaAuthStore } from '@manacore/shared-auth-stores';
// Default URLs for local development only
const DEV_AUTH_URL = 'http://localhost:3001';
const DEV_BACKEND_URL = 'http://localhost:3018';
// Get auth URL dynamically at runtime - fallback for SSR and client
function getAuthUrl(): string {
if (browser && typeof window !== 'undefined') {
// Client-side: use injected window variable (set by hooks.server.ts)
const injectedUrl = (window as unknown as { __PUBLIC_MANA_CORE_AUTH_URL__?: string })
.__PUBLIC_MANA_CORE_AUTH_URL__;
// Only use localhost fallback in development
if (injectedUrl) return injectedUrl;
return import.meta.env.DEV ? DEV_AUTH_URL : '';
}
// Server-side (SSR): use Docker internal URL for container-to-container communication
return process.env.PUBLIC_MANA_CORE_AUTH_URL || DEV_AUTH_URL;
}
// Get backend URL dynamically at runtime
function getBackendUrl(): string {
if (browser && typeof window !== 'undefined') {
const injectedUrl = (window as unknown as { __PUBLIC_BACKEND_URL__?: string })
.__PUBLIC_BACKEND_URL__;
// Only use localhost fallback in development
if (injectedUrl) return injectedUrl;
return import.meta.env.DEV ? DEV_BACKEND_URL : '';
}
return process.env.PUBLIC_BACKEND_URL || DEV_BACKEND_URL;
}
// Lazy initialization to avoid SSR issues with localStorage
let _authService: AuthServiceInterface | null = null;
let _tokenManager: ReturnType<typeof initializeWebAuth>['tokenManager'] | null = null;
function getAuthService() {
if (!browser) return null;
if (!_authService) {
const auth = initializeWebAuth({
baseUrl: getAuthUrl(),
backendUrl: getBackendUrl(), // Enables automatic token refresh on 401 responses
});
_authService = auth.authService;
_tokenManager = auth.tokenManager;
}
return _authService;
}
function getTokenManager() {
if (!browser) return null;
// Ensure auth service is initialized first
getAuthService();
return _tokenManager;
}
// State
let user = $state<UserData | null>(null);
let loading = $state(true);
let initialized = $state(false);
export const authStore = {
// Getters
get user() {
return user;
},
get loading() {
return loading;
},
get isAuthenticated() {
return !!user;
},
get initialized() {
return initialized;
},
/**
* Initialize auth state from stored tokens
* Also tries SSO if no local tokens exist (cross-domain authentication)
*/
async initialize() {
if (initialized) return;
const authService = getAuthService();
if (!authService) {
initialized = true;
loading = false;
return;
}
loading = true;
try {
// First, check if we have valid local tokens
let authenticated = await authService.isAuthenticated();
// If not authenticated locally, try SSO (shared session cookie)
if (!authenticated) {
console.log('No local tokens, trying SSO...');
const ssoResult = await authService.trySSO();
if (ssoResult.success) {
console.log('SSO successful, user authenticated via shared session');
authenticated = true;
}
}
if (authenticated) {
const userData = await authService.getUserFromToken();
user = userData;
// Set token for API client
const token = await authService.getAppToken();
if (token) {
apiClient.setAccessToken(token);
}
}
initialized = true;
} catch (error) {
console.error('Failed to initialize auth:', error);
user = null;
} finally {
loading = false;
}
},
/**
/**
* Check if passkeys are available in this browser
*/
async verifyTwoFactor(code: string, trustDevice?: boolean) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
const result = await authService.verifyTwoFactor(code, trustDevice);
if (result.success) {
const userData = await authService.getUserFromToken();
user = userData;
}
return result;
},
async verifyBackupCode(code: string) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
const result = await authService.verifyBackupCode(code);
if (result.success) {
const userData = await authService.getUserFromToken();
user = userData;
}
return result;
},
async sendMagicLink(email: string) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
return authService.sendMagicLink(email);
},
isPasskeyAvailable(): boolean {
const authService = getAuthService();
if (!authService) return false;
return authService.isPasskeyAvailable();
},
/**
* Sign in with a passkey
*/
async signInWithPasskey() {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const result = await authService.signInWithPasskey();
if (!result.success) {
return { success: false, error: result.error || 'Passkey authentication failed' };
}
// Get user data from token
const userData = await authService.getUserFromToken();
user = userData;
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
/**
* Sign in with email and password
*/
async signIn(email: string, password: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const result = await authService.signIn(email, password);
if (!result.success) {
return { success: false, error: result.error || 'Login failed' };
}
// Get user data from token
const userData = await authService.getUserFromToken();
user = userData;
// Set token for API client
const token = await authService.getAppToken();
if (token) {
apiClient.setAccessToken(token);
}
// Start syncing local data to server
todoStore.startSync(() => authStore.getValidToken());
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
/**
* Sign up with email and password
*/
async signUp(email: string, password: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server', needsVerification: false };
}
try {
// Pass the current app URL for post-verification redirect
const sourceAppUrl = browser ? window.location.origin : undefined;
const result = await authService.signUp(email, password, sourceAppUrl);
if (!result.success) {
return { success: false, error: result.error || 'Signup failed', needsVerification: false };
}
// Mana Core Auth requires separate login after signup
if (result.needsVerification) {
return { success: true, needsVerification: true };
}
// Auto sign in after successful signup
const signInResult = await this.signIn(email, password);
return { ...signInResult, needsVerification: false };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage, needsVerification: false };
}
},
/**
* Sign out
*/
async signOut() {
// Stop syncing before clearing auth
todoStore.stopSync();
const authService = getAuthService();
if (!authService) {
user = null;
apiClient.setAccessToken(null);
return;
}
try {
await authService.signOut();
user = null;
apiClient.setAccessToken(null);
} catch (error) {
console.error('Sign out error:', error);
// Clear user even if sign out fails
user = null;
apiClient.setAccessToken(null);
}
},
/**
* Send password reset email
*/
async resetPassword(email: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const redirectTo = browser ? window.location.origin : undefined;
const result = await authService.forgotPassword(email, redirectTo);
if (!result.success) {
return { success: false, error: result.error || 'Password reset failed' };
}
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
/**
* Reset password with token (from reset email link)
*/
async resetPasswordWithToken(token: string, newPassword: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const result = await authService.resetPassword(token, newPassword);
if (!result.success) {
return { success: false, error: result.error || 'Failed to reset password' };
}
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
/**
* Get access token for API calls (raw token, no refresh)
* @deprecated Use getValidToken() instead for automatic refresh
*/
async getAccessToken() {
const authService = getAuthService();
if (!authService) {
return null;
}
return await authService.getAppToken();
},
/**
* Get a valid access token for API calls
* Automatically refreshes if the token is expired or about to expire
*/
async getValidToken(): Promise<string | null> {
const tokenManager = getTokenManager();
if (!tokenManager) {
return null;
}
return await tokenManager.getValidToken();
},
/**
* Resend verification email
*/
async resendVerificationEmail(email: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const sourceAppUrl = browser ? window.location.origin : undefined;
const result = await authService.resendVerificationEmail(email, sourceAppUrl);
if (!result.success) {
return { success: false, error: result.error || 'Failed to resend verification email' };
}
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
};
export const authStore = createManaAuthStore({
devBackendPort: 3018,
});

View file

@ -35,6 +35,7 @@
"@manacore/local-store": "workspace:*",
"@manacore/shared-api-client": "workspace:*",
"@manacore/shared-auth": "workspace:*",
"@manacore/shared-auth-stores": "workspace:*",
"@manacore/shared-auth-ui": "workspace:*",
"@manacore/shared-branding": "workspace:*",
"@manacore/shared-error-tracking": "workspace:*",

View file

@ -1,358 +1,10 @@
/**
* Auth Store - Manages authentication state using Svelte 5 runes
* Uses Mana Core Auth
* Auth Store uses centralized Mana auth factory.
* All auth logic lives in @manacore/shared-auth-stores.
*/
import { browser } from '$app/environment';
import { initializeWebAuth, type UserData, type AuthServiceInterface } from '@manacore/shared-auth';
import { createManaAuthStore } from '@manacore/shared-auth-stores';
// Default URLs for local development only
const DEV_AUTH_URL = 'http://localhost:3001';
const DEV_BACKEND_URL = 'http://localhost:3007';
// Get auth URL dynamically at runtime - fallback for SSR and client
function getAuthUrl(): string {
if (browser && typeof window !== 'undefined') {
const injectedUrl = (window as unknown as { __PUBLIC_MANA_CORE_AUTH_URL__?: string })
.__PUBLIC_MANA_CORE_AUTH_URL__;
if (injectedUrl) return injectedUrl;
return import.meta.env.DEV ? DEV_AUTH_URL : '';
}
return process.env.PUBLIC_MANA_CORE_AUTH_URL || DEV_AUTH_URL;
}
// Get backend URL dynamically at runtime
function getBackendUrl(): string {
if (browser && typeof window !== 'undefined') {
const injectedUrl = (window as unknown as { __PUBLIC_BACKEND_URL__?: string })
.__PUBLIC_BACKEND_URL__;
if (injectedUrl) return injectedUrl;
return import.meta.env.DEV ? DEV_BACKEND_URL : '';
}
return process.env.PUBLIC_BACKEND_URL || DEV_BACKEND_URL;
}
// Lazy initialization to avoid SSR issues with localStorage
let _authService: AuthServiceInterface | null = null;
let _tokenManager: ReturnType<typeof initializeWebAuth>['tokenManager'] | null = null;
function getAuthService() {
if (!browser) return null;
if (!_authService) {
const auth = initializeWebAuth({
baseUrl: getAuthUrl(),
backendUrl: getBackendUrl(), // Enables automatic token refresh on 401 responses
});
_authService = auth.authService;
_tokenManager = auth.tokenManager;
}
return _authService;
}
function getTokenManager() {
if (!browser) return null;
// Ensure auth service is initialized first
getAuthService();
return _tokenManager;
}
// State
let user = $state<UserData | null>(null);
let loading = $state(true);
let initialized = $state(false);
export const authStore = {
// Getters
get user() {
return user;
},
get loading() {
return loading;
},
get isAuthenticated() {
return !!user;
},
get initialized() {
return initialized;
},
/**
* Initialize auth state from stored tokens
* Also tries SSO if no local tokens exist (cross-domain authentication)
*/
async initialize() {
if (initialized) return;
const authService = getAuthService();
if (!authService) {
initialized = true;
loading = false;
return;
}
loading = true;
try {
// First, check if we have valid local tokens
let authenticated = await authService.isAuthenticated();
// If not authenticated locally, try SSO (shared session cookie)
if (!authenticated) {
console.log('No local tokens, trying SSO...');
const ssoResult = await authService.trySSO();
if (ssoResult.success) {
console.log('SSO successful, user authenticated via shared session');
authenticated = true;
}
}
if (authenticated) {
const userData = await authService.getUserFromToken();
user = userData;
}
initialized = true;
} catch (error) {
console.error('Failed to initialize auth:', error);
user = null;
} finally {
loading = false;
}
},
/**
/**
* Check if passkeys are available in this browser
*/
async verifyTwoFactor(code: string, trustDevice?: boolean) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
const result = await authService.verifyTwoFactor(code, trustDevice);
if (result.success) {
const userData = await authService.getUserFromToken();
user = userData;
}
return result;
},
async verifyBackupCode(code: string) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
const result = await authService.verifyBackupCode(code);
if (result.success) {
const userData = await authService.getUserFromToken();
user = userData;
}
return result;
},
async sendMagicLink(email: string) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
return authService.sendMagicLink(email);
},
isPasskeyAvailable(): boolean {
const authService = getAuthService();
if (!authService) return false;
return authService.isPasskeyAvailable();
},
/**
* Sign in with a passkey
*/
async signInWithPasskey() {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const result = await authService.signInWithPasskey();
if (!result.success) {
return { success: false, error: result.error || 'Passkey authentication failed' };
}
// Get user data from token
const userData = await authService.getUserFromToken();
user = userData;
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
/**
* Sign in with email and password
*/
async signIn(email: string, password: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const result = await authService.signIn(email, password);
if (!result.success) {
return { success: false, error: result.error || 'Login failed' };
}
// Get user data from token
const userData = await authService.getUserFromToken();
user = userData;
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
/**
* Sign up with email and password
*/
async signUp(email: string, password: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server', needsVerification: false };
}
try {
// Pass the current app URL for post-verification redirect
const sourceAppUrl = browser ? window.location.origin : undefined;
const result = await authService.signUp(email, password, sourceAppUrl);
if (!result.success) {
return { success: false, error: result.error || 'Signup failed', needsVerification: false };
}
// Mana Core Auth requires separate login after signup
if (result.needsVerification) {
return { success: true, needsVerification: true };
}
// Auto sign in after successful signup
const signInResult = await this.signIn(email, password);
return { ...signInResult, needsVerification: false };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage, needsVerification: false };
}
},
/**
* Sign out
*/
async signOut() {
const authService = getAuthService();
if (!authService) {
user = null;
return;
}
try {
await authService.signOut();
user = null;
} catch (error) {
console.error('Sign out error:', error);
// Clear user even if sign out fails
user = null;
}
},
/**
* Send password reset email
*/
async resetPassword(email: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const redirectTo = browser ? window.location.origin : undefined;
const result = await authService.forgotPassword(email, redirectTo);
if (!result.success) {
return { success: false, error: result.error || 'Password reset failed' };
}
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
/**
* Reset password with token (from reset email link)
*/
async resetPasswordWithToken(token: string, newPassword: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const result = await authService.resetPassword(token, newPassword);
if (!result.success) {
return { success: false, error: result.error || 'Failed to reset password' };
}
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
/**
* Get access token for API calls (raw token, no refresh)
* @deprecated Use getValidToken() instead for automatic refresh
*/
async getAccessToken() {
const authService = getAuthService();
if (!authService) {
return null;
}
return await authService.getAppToken();
},
/**
* Get a valid access token for API calls
* Automatically refreshes if the token is expired or about to expire
*/
async getValidToken(): Promise<string | null> {
const tokenManager = getTokenManager();
if (!tokenManager) {
return null;
}
return await tokenManager.getValidToken();
},
/**
* Resend verification email
*/
async resendVerificationEmail(email: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const sourceAppUrl = browser ? window.location.origin : undefined;
const result = await authService.resendVerificationEmail(email, sourceAppUrl);
if (!result.success) {
return { success: false, error: result.error || 'Failed to send verification email' };
}
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
};
export const authStore = createManaAuthStore({
devBackendPort: 3007,
});

View file

@ -29,6 +29,7 @@
"typescript": "^5.0.0"
},
"dependencies": {
"@manacore/shared-auth": "workspace:*",
"@manacore/shared-types": "workspace:*"
}
}

View file

@ -0,0 +1,301 @@
/**
* Mana Auth Store Factory
*
* Creates a complete auth store using @manacore/shared-auth.
* Replaces the ~350 lines of duplicated auth.svelte.ts in each app
* with a single factory call.
*
* @example
* ```ts
* // apps/todo/apps/web/src/lib/stores/auth.svelte.ts
* import { createManaAuthStore } from '@manacore/shared-auth-stores';
*
* export const authStore = createManaAuthStore({
* devBackendPort: 3031,
* });
* ```
*
* @example With post-login callback
* ```ts
* import { createManaAuthStore } from '@manacore/shared-auth-stores';
* import { apiClient } from '$lib/api/client';
*
* export const authStore = createManaAuthStore({
* devBackendPort: 3030,
* onAuthenticated: async (authService) => {
* const token = await authService.getAppToken();
* if (token) apiClient.setAccessToken(token);
* },
* });
* ```
*/
import { browser } from '$app/environment';
import {
initializeWebAuth,
type UserData,
type AuthServiceInterface,
} from '@manacore/shared-auth';
export interface ManaAuthStoreConfig {
/** Dev backend port (e.g. 3031 for todo). Only used in development. */
devBackendPort?: number;
/** Dev auth port. Defaults to 3001. */
devAuthPort?: number;
/** Callback after successful authentication (sign in, SSO, 2FA). */
onAuthenticated?: (authService: AuthServiceInterface) => void | Promise<void>;
/** Callback after sign out. */
onSignOut?: () => void | Promise<void>;
}
export function createManaAuthStore(config: ManaAuthStoreConfig = {}) {
const devAuthUrl = `http://localhost:${config.devAuthPort ?? 3001}`;
const devBackendUrl = config.devBackendPort
? `http://localhost:${config.devBackendPort}`
: '';
// URL resolution (runtime, not build-time)
function getAuthUrl(): string {
if (browser && typeof window !== 'undefined') {
const injected = (window as unknown as { __PUBLIC_MANA_CORE_AUTH_URL__?: string })
.__PUBLIC_MANA_CORE_AUTH_URL__;
if (injected) return injected;
return import.meta.env.DEV ? devAuthUrl : '';
}
return process.env.PUBLIC_MANA_CORE_AUTH_URL || devAuthUrl;
}
function getBackendUrl(): string {
if (browser && typeof window !== 'undefined') {
const injected = (window as unknown as { __PUBLIC_BACKEND_URL__?: string })
.__PUBLIC_BACKEND_URL__;
if (injected) return injected;
return import.meta.env.DEV ? devBackendUrl : '';
}
return process.env.PUBLIC_BACKEND_URL || devBackendUrl;
}
// Lazy init (SSR-safe)
let _authService: AuthServiceInterface | null = null;
let _tokenManager: ReturnType<typeof initializeWebAuth>['tokenManager'] | null = null;
function getAuthService() {
if (!browser) return null;
if (!_authService) {
const backendUrl = getBackendUrl();
const auth = initializeWebAuth({
baseUrl: getAuthUrl(),
...(backendUrl ? { backendUrl } : {}),
});
_authService = auth.authService;
_tokenManager = auth.tokenManager;
}
return _authService;
}
function getTokenManager() {
if (!browser) return null;
getAuthService();
return _tokenManager;
}
async function handleAuthenticated() {
if (config.onAuthenticated) {
const svc = getAuthService();
if (svc) await config.onAuthenticated(svc);
}
}
// Reactive state
let user = $state<UserData | null>(null);
let loading = $state(true);
let initialized = $state(false);
return {
// Getters
get user() { return user; },
get loading() { return loading; },
get isAuthenticated() { return !!user; },
get initialized() { return initialized; },
async initialize() {
if (initialized) return;
const authService = getAuthService();
if (!authService) {
initialized = true;
loading = false;
return;
}
loading = true;
try {
let authenticated = await authService.isAuthenticated();
if (!authenticated) {
const ssoResult = await authService.trySSO();
if (ssoResult.success) authenticated = true;
}
if (authenticated) {
user = await authService.getUserFromToken();
await handleAuthenticated();
}
initialized = true;
} catch (error) {
console.error('Failed to initialize auth:', error);
user = null;
} finally {
loading = false;
}
},
// 2FA
async verifyTwoFactor(code: string, trustDevice?: boolean) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
const result = await authService.verifyTwoFactor(code, trustDevice);
if (result.success) {
user = await authService.getUserFromToken();
await handleAuthenticated();
}
return result;
},
async verifyBackupCode(code: string) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
const result = await authService.verifyBackupCode(code);
if (result.success) {
user = await authService.getUserFromToken();
await handleAuthenticated();
}
return result;
},
// Magic Link
async sendMagicLink(email: string) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
return authService.sendMagicLink(email);
},
// Passkeys
isPasskeyAvailable(): boolean {
const authService = getAuthService();
if (!authService) return false;
return authService.isPasskeyAvailable();
},
async signInWithPasskey() {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
try {
const result = await authService.signInWithPasskey();
if (!result.success) return { success: false, error: result.error || 'Passkey authentication failed' };
user = await authService.getUserFromToken();
await handleAuthenticated();
return { success: true };
} catch (error) {
return { success: false, error: error instanceof Error ? error.message : 'Unknown error' };
}
},
// Sign In
async signIn(email: string, password: string) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
try {
const result = await authService.signIn(email, password);
if (!result.success) return { success: false, error: result.error || 'Login failed' };
user = await authService.getUserFromToken();
await handleAuthenticated();
return { success: true };
} catch (error) {
return { success: false, error: error instanceof Error ? error.message : 'Unknown error' };
}
},
// Sign Up
async signUp(email: string, password: string) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server', needsVerification: false };
try {
const sourceAppUrl = browser ? window.location.origin : undefined;
const result = await authService.signUp(email, password, sourceAppUrl);
if (!result.success) return { success: false, error: result.error || 'Signup failed', needsVerification: false };
if (result.needsVerification) return { success: true, needsVerification: true };
const signInResult = await this.signIn(email, password);
return { ...signInResult, needsVerification: false };
} catch (error) {
return { success: false, error: error instanceof Error ? error.message : 'Unknown error', needsVerification: false };
}
},
// Sign Out
async signOut() {
const authService = getAuthService();
if (!authService) { user = null; return; }
try {
await authService.signOut();
user = null;
if (config.onSignOut) await config.onSignOut();
} catch (error) {
console.error('Sign out error:', error);
user = null;
}
},
// Password Reset
async resetPassword(email: string) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
try {
const redirectTo = browser ? window.location.origin : undefined;
const result = await authService.forgotPassword(email, redirectTo);
if (!result.success) return { success: false, error: result.error || 'Password reset failed' };
return { success: true };
} catch (error) {
return { success: false, error: error instanceof Error ? error.message : 'Unknown error' };
}
},
async resetPasswordWithToken(token: string, newPassword: string) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
try {
const result = await authService.resetPassword(token, newPassword);
if (!result.success) return { success: false, error: result.error || 'Failed to reset password' };
return { success: true };
} catch (error) {
return { success: false, error: error instanceof Error ? error.message : 'Unknown error' };
}
},
// Token access
async getAccessToken() {
const authService = getAuthService();
if (!authService) return null;
return await authService.getAppToken();
},
async getValidToken(): Promise<string | null> {
const tokenManager = getTokenManager();
if (!tokenManager) return null;
return await tokenManager.getValidToken();
},
// Email verification
async resendVerificationEmail(email: string) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available on server' };
try {
const sourceAppUrl = browser ? window.location.origin : undefined;
const result = await authService.resendVerificationEmail(email, sourceAppUrl);
if (!result.success) return { success: false, error: result.error || 'Failed to send verification email' };
return { success: true };
} catch (error) {
return { success: false, error: error instanceof Error ? error.message : 'Unknown error' };
}
},
};
}
export type ManaAuthStore = ReturnType<typeof createManaAuthStore>;

View file

@ -27,6 +27,8 @@
*/
// Factory functions
export { createManaAuthStore } from './createManaAuthStore.svelte';
export type { ManaAuthStoreConfig, ManaAuthStore } from './createManaAuthStore.svelte';
export { createAuthStore } from './createAuthStore.svelte';
export { createSupabaseAuthStore } from './createSupabaseAuthStore.svelte';
export type {

6
pnpm-lock.yaml generated
View file

@ -4485,6 +4485,9 @@ importers:
'@manacore/shared-auth':
specifier: workspace:*
version: link:../../../../packages/shared-auth
'@manacore/shared-auth-stores':
specifier: workspace:*
version: link:../../../../packages/shared-auth-stores
'@manacore/shared-auth-ui':
specifier: workspace:*
version: link:../../../../packages/shared-auth-ui
@ -4829,6 +4832,9 @@ importers:
packages/shared-auth-stores:
dependencies:
'@manacore/shared-auth':
specifier: workspace:*
version: link:../shared-auth
'@manacore/shared-types':
specifier: workspace:*
version: link:../shared-types