diff --git a/apps/web/src/lib/stores/auth.svelte.ts b/apps/web/src/lib/stores/auth.svelte.ts index e455071..57255de 100644 --- a/apps/web/src/lib/stores/auth.svelte.ts +++ b/apps/web/src/lib/stores/auth.svelte.ts @@ -1,7 +1,7 @@ // ============================================================================ -// SIMPLE AUTH STORE +// AUTH STORE WITH PASSWORD SUPPORT // ============================================================================ -// Simplified Svelte 5 Runes auth store for local-only P2P app +// Svelte 5 Runes auth store with password authentication // ============================================================================ import type { AuthState, UserSession } from "$auth/index"; @@ -10,6 +10,51 @@ import type { AuthState, UserSession } from "$auth/index"; let currentSession: UserSession | null = $state(null); let authState: AuthState = $state({ status: "unauthenticated" }); +// Store for user credentials (username -> password hash) +const USER_STORAGE_KEY = "locanote_users"; + +// ============================================================================ +// PASSWORD HASHING (Simple hash for local-only app) +// ============================================================================ + +async function hashPassword(password: string): Promise { + const encoder = new TextEncoder(); + const data = encoder.encode(password); + const hashBuffer = await crypto.subtle.digest("SHA-256", data); + const hashArray = Array.from(new Uint8Array(hashBuffer)); + return hashArray.map((b) => b.toString(16).padStart(2, "0")).join(""); +} + +// ============================================================================ +// USER STORAGE +// ============================================================================ + +interface StoredUser { + username: string; + passwordHash: string; + createdAt: number; +} + +function getStoredUsers(): Record { + if (typeof window === "undefined") return {}; + const stored = localStorage.getItem(USER_STORAGE_KEY); + return stored ? JSON.parse(stored) : {}; +} + +function saveUser(username: string, passwordHash: string) { + const users = getStoredUsers(); + users[username.toLowerCase()] = { + username, + passwordHash, + createdAt: Date.now(), + }; + localStorage.setItem(USER_STORAGE_KEY, JSON.stringify(users)); +} + +function getUser(username: string): StoredUser | undefined { + return getStoredUsers()[username.toLowerCase()]; +} + // ============================================================================ // METHODS // ============================================================================ @@ -37,8 +82,11 @@ export const auth = { return authState; }, - // Login - login(username: string): { success: boolean; error?: string } { + // Login with password + async login( + username: string, + password: string, + ): Promise<{ success: boolean; error?: string }> { if (!username || username.trim().length < 2) { return { success: false, @@ -46,6 +94,30 @@ export const auth = { }; } + if (!password || password.length < 6) { + return { + success: false, + error: "Password must be at least 6 characters", + }; + } + + // Verify user exists and password matches + const user = getUser(username); + if (!user) { + return { + success: false, + error: "Invalid username or password", + }; + } + + const passwordHash = await hashPassword(password); + if (passwordHash !== user.passwordHash) { + return { + success: false, + error: "Invalid username or password", + }; + } + const userId = `${username.trim().toLowerCase().replace(/\s+/g, "_")}_${Date.now()}`; const session: UserSession = { userId, @@ -61,9 +133,60 @@ export const auth = { return { success: true }; }, - // Register (same as login for MVP) - register(username: string): { success: boolean; error?: string } { - return this.login(username); + // Register with password + async register( + username: string, + password: string, + confirmPassword: string, + ): Promise<{ success: boolean; error?: string }> { + if (!username || username.trim().length < 2) { + return { + success: false, + error: "Username must be at least 2 characters", + }; + } + + if (!password || password.length < 6) { + return { + success: false, + error: "Password must be at least 6 characters", + }; + } + + if (password !== confirmPassword) { + return { + success: false, + error: "Passwords do not match", + }; + } + + // Check if user already exists + const existingUser = getUser(username); + if (existingUser) { + return { + success: false, + error: "Username already exists", + }; + } + + // Hash and store password + const passwordHash = await hashPassword(password); + saveUser(username, passwordHash); + + // Create session + const userId = `${username.trim().toLowerCase().replace(/\s+/g, "_")}_${Date.now()}`; + const session: UserSession = { + userId, + username: username.trim(), + loggedInAt: Date.now(), + expiresAt: Date.now() + 30 * 24 * 60 * 60 * 1000, + }; + + localStorage.setItem("locanote_session", JSON.stringify(session)); + currentSession = session; + authState = { status: "authenticated", session }; + + return { success: true }; }, // Logout diff --git a/apps/web/src/routes/+page.svelte b/apps/web/src/routes/+page.svelte index 4c52580..81d2966 100644 --- a/apps/web/src/routes/+page.svelte +++ b/apps/web/src/routes/+page.svelte @@ -5,8 +5,11 @@ LANDING PAGE - Beautiful Glass Design