import { defineStore } from 'pinia' import { ref } from 'vue' import { authService } from '../services/auth.service' import { nip98Service } from '../services/nip98.service' import { accountManager } from '../lib/accounts' import type { ApiUser } from '../types/api' import { USE_MOCK } from '../utils/mock' /** Returns true when the error looks like a network / connection failure */ function isConnectionError(error: any): boolean { const msg = error?.message?.toLowerCase() || '' return ( msg.includes('unable to connect') || msg.includes('network error') || msg.includes('failed to fetch') || msg.includes('econnrefused') ) } /** Build a mock Nostr user with filmmaker profile + subscription */ function buildMockNostrUser(pubkey: string) { const mockUserId = 'mock-nostr-user-' + pubkey.slice(0, 8) return { id: mockUserId, email: `${pubkey.slice(0, 8)}@nostr.local`, legalName: 'Nostr User', nostrPubkey: pubkey, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), filmmaker: { id: 'mock-filmmaker-' + pubkey.slice(0, 8), userId: mockUserId, professionalName: 'Nostr Filmmaker', bio: 'Independent filmmaker and content creator.', }, subscriptions: [{ id: 'mock-sub-cinephile', userId: mockUserId, tier: 'cinephile' as const, status: 'active' as const, currentPeriodStart: new Date().toISOString(), currentPeriodEnd: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(), cancelAtPeriodEnd: false, stripePriceId: 'mock-price-cinephile', stripeCustomerId: 'mock-customer-' + pubkey.slice(0, 8), }], } } /** Build a mock Cognito user with subscription */ function buildMockCognitoUser(email: string, legalName?: string) { const username = email.split('@')[0] return { id: 'mock-user-' + username, email, legalName: legalName || username.charAt(0).toUpperCase() + username.slice(1), createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), subscriptions: [{ id: 'mock-sub-cinephile', userId: 'mock-user-' + username, tier: 'cinephile' as const, status: 'active' as const, currentPeriodStart: new Date().toISOString(), currentPeriodEnd: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(), cancelAtPeriodEnd: false, stripePriceId: 'mock-price-cinephile', stripeCustomerId: 'mock-customer-' + username, }], } } export type AuthType = 'cognito' | 'nostr' | null export interface AuthState { user: ApiUser | null authType: AuthType isAuthenticated: boolean nostrPubkey: string | null cognitoToken: string | null isLoading: boolean } /** * Authentication Store * Manages user authentication state with dual Cognito/Nostr support */ export const useAuthStore = defineStore('auth', () => { // State const user = ref(null) const authType = ref(null) const isAuthenticated = ref(false) const nostrPubkey = ref(null) const cognitoToken = ref(null) const isLoading = ref(false) /** * Initialize auth state from stored tokens. * In dev/mock mode, reconstructs the mock user directly from * sessionStorage rather than calling the (non-existent) backend API. */ async function initialize() { // Bail out early if already authenticated — prevents // re-initialization from wiping state on subsequent navigations if (isAuthenticated.value && user.value) { return } isLoading.value = true try { const storedCognitoToken = sessionStorage.getItem('auth_token') const storedNostrToken = sessionStorage.getItem('nostr_token') const storedPubkey = sessionStorage.getItem('nostr_pubkey') if (!storedCognitoToken && !storedNostrToken) { return // Nothing stored — not logged in } // Helper: restore mock session from sessionStorage const restoreAsMock = () => { if (storedNostrToken && storedPubkey) { user.value = buildMockNostrUser(storedPubkey) nostrPubkey.value = storedPubkey authType.value = 'nostr' isAuthenticated.value = true } else if (storedCognitoToken) { user.value = buildMockCognitoUser('dev@local', 'Dev User') cognitoToken.value = storedCognitoToken authType.value = 'cognito' isAuthenticated.value = true } } if (USE_MOCK) { restoreAsMock() return } // Real mode: validate session with backend API. // For Nostr sessions, skip the Cognito-only validate-session endpoint // and go straight to /auth/me which uses HybridAuthGuard. try { if (storedNostrToken && storedPubkey) { // Nostr session: restore nip98Service state then fetch user profile nip98Service.storeTokens( storedNostrToken, sessionStorage.getItem('indeehub_api_refresh') ?? sessionStorage.getItem('refresh_token') ?? '', ) await fetchCurrentUser() nostrPubkey.value = storedPubkey authType.value = 'nostr' isAuthenticated.value = true } else if (storedCognitoToken) { // Cognito session: use legacy validate-session const isValid = await authService.validateSession() if (isValid) { await fetchCurrentUser() authType.value = 'cognito' cognitoToken.value = storedCognitoToken isAuthenticated.value = true } else { await logout() } } } catch (apiError: any) { if (isConnectionError(apiError)) { console.warn('Backend not reachable — falling back to mock session.') restoreAsMock() } else { // Token likely expired or invalid console.warn('Session validation failed:', apiError.message) if (accountManager.active) { // Still have a Nostr signer — try re-authenticating restoreAsMock() } else { await logout() } } } } catch (error) { console.error('Failed to initialize auth:', error) await logout() } finally { isLoading.value = false } } /** * Login with email and password (Cognito) */ async function loginWithCognito(email: string, password: string) { isLoading.value = true // Mock Cognito login helper const mockLogin = () => { const mockUser = buildMockCognitoUser(email) console.log('🔧 Using mock Cognito authentication') cognitoToken.value = 'mock-jwt-token-' + Date.now() authType.value = 'cognito' user.value = mockUser isAuthenticated.value = true sessionStorage.setItem('auth_token', cognitoToken.value) sessionStorage.setItem('refresh_token', 'mock-refresh-token') return { accessToken: cognitoToken.value, idToken: 'mock-id-token', refreshToken: 'mock-refresh-token', expiresIn: 3600, } } try { if (USE_MOCK) { await new Promise(resolve => setTimeout(resolve, 500)) return mockLogin() } // Real API call const response = await authService.login({ email, password }) cognitoToken.value = response.accessToken authType.value = 'cognito' await fetchCurrentUser() isAuthenticated.value = true return response } catch (error: any) { if (isConnectionError(error)) { console.warn('Backend not reachable — falling back to mock Cognito login.') return mockLogin() } throw error } finally { isLoading.value = false } } /** * Login with Nostr signature */ async function loginWithNostr(pubkey: string, signature: string, event: any) { isLoading.value = true // Mock Nostr login helper const mockLogin = () => { const mockUser = buildMockNostrUser(pubkey) console.warn('🔧 Using mock Nostr authentication (backend not available)') nostrPubkey.value = pubkey authType.value = 'nostr' user.value = mockUser isAuthenticated.value = true sessionStorage.setItem('nostr_token', 'mock-nostr-token-' + pubkey.slice(0, 16)) sessionStorage.setItem('nostr_pubkey', pubkey) return { token: 'mock-nostr-token', user: mockUser } } try { if (USE_MOCK) { await new Promise(resolve => setTimeout(resolve, 500)) return mockLogin() } // Real API call — creates NIP-98 signed session via the active // Nostr account in accountManager (set by useAccounts before this call) const response = await authService.createNostrSession({ pubkey, signature, event, }) nostrPubkey.value = pubkey authType.value = 'nostr' sessionStorage.setItem('nostr_pubkey', pubkey) isAuthenticated.value = true // Backend returns JWT tokens but no user object. // Fetch the user profile with the new access token. try { await fetchCurrentUser() } catch { // User may not exist in DB yet — create a minimal local representation user.value = buildMockNostrUser(pubkey) } return response } catch (error: any) { if (isConnectionError(error)) { console.warn('Backend not reachable — falling back to mock Nostr login.') return mockLogin() } throw error } finally { isLoading.value = false } } /** * Register new user */ async function register(email: string, password: string, legalName: string) { isLoading.value = true // Mock registration helper const mockRegister = () => { const mockUser = buildMockCognitoUser(email, legalName) console.warn('🔧 Using mock registration (backend not available)') cognitoToken.value = 'mock-jwt-token-' + Date.now() authType.value = 'cognito' user.value = mockUser isAuthenticated.value = true sessionStorage.setItem('auth_token', cognitoToken.value) sessionStorage.setItem('refresh_token', 'mock-refresh-token') return { accessToken: cognitoToken.value, idToken: 'mock-id-token', refreshToken: 'mock-refresh-token', expiresIn: 3600, } } try { if (USE_MOCK) { await new Promise(resolve => setTimeout(resolve, 500)) return mockRegister() } // Real API call const response = await authService.register({ email, password, legalName, }) cognitoToken.value = response.accessToken authType.value = 'cognito' await fetchCurrentUser() isAuthenticated.value = true return response } catch (error: any) { if (isConnectionError(error)) { console.warn('Backend not reachable — falling back to mock registration.') return mockRegister() } throw error } finally { isLoading.value = false } } /** * Fetch current user data */ async function fetchCurrentUser() { try { const userData = await authService.getCurrentUser() user.value = userData if (userData.nostrPubkey) { nostrPubkey.value = userData.nostrPubkey } return userData } catch (error) { console.error('Failed to fetch user:', error) throw error } } /** * Logout user */ async function logout() { await authService.logout() nip98Service.clearSession() user.value = null authType.value = null isAuthenticated.value = false nostrPubkey.value = null cognitoToken.value = null } /** * Link Nostr pubkey to account */ async function linkNostr(pubkey: string, signature: string) { try { const updatedUser = await authService.linkNostrPubkey(pubkey, signature) user.value = updatedUser nostrPubkey.value = pubkey return updatedUser } catch (error) { throw error } } /** * Unlink Nostr pubkey from account */ async function unlinkNostr() { try { const updatedUser = await authService.unlinkNostrPubkey() user.value = updatedUser nostrPubkey.value = null return updatedUser } catch (error) { throw error } } /** * Check if user is filmmaker */ function isFilmmaker(): boolean { return !!user.value?.filmmaker } /** * Check if user has active subscription */ function hasActiveSubscription(): boolean { if (!user.value?.subscriptions) return false return user.value.subscriptions.some((sub) => sub.status === 'active') } return { // State user, authType, isAuthenticated, nostrPubkey, cognitoToken, isLoading, // Actions initialize, loginWithCognito, loginWithNostr, register, fetchCurrentUser, logout, linkNostr, unlinkNostr, // Getters isFilmmaker, hasActiveSubscription, } })