import type {AuthUser} from '@/features/auth/types/auth'; /** * The mock user directory. Server-only — nothing here is ever bundled to the * client, because the only importers are route handlers. * * ── On the plaintext passwords ──────────────────────────────────────────── * They are plaintext BECAUSE this is a fixture, and the fixture is the one * place where that is safe: it never leaves the server, and there is no real * credential in it. What matters architecturally is that `verifyCredentials` * below is the ONLY function that ever sees a password. When a real backend * arrives, that function's body becomes an HTTP call (or an argon2/bcrypt * comparison against a user table) and every other file in the app is * untouched — including the login form, which already only knows how to ask. * * Do not add a "demo" user that accepts any password. The whole point of this * layer is that wrong credentials fail. */ interface MockUserRecord extends AuthUser { password: string; } const USERS: MockUserRecord[] = [ { id: 'usr_owner_01', email: 'aravind@nealre.in', name: 'Aravind', role: 'owner', organisation: 'Loyaly Retail Pvt Ltd', password: 'admin123', }, { id: 'usr_owner_02', email: 'aravind@nearle.in', name: 'Aravind', role: 'owner', organisation: 'Loyaly Retail Pvt Ltd', password: 'admin123', }, { id: 'usr_mgr_01', email: 'priya.sharma@loyaly.ai', name: 'Priya Sharma', role: 'manager', organisation: 'Loyaly Retail Pvt Ltd', password: 'indiranagar@01', }, { id: 'usr_analyst_01', email: 'analyst@loyaly.ai', name: 'Rahul Menon', role: 'analyst', organisation: 'Loyaly Retail Pvt Ltd', password: 'reports@2026', }, ]; /** Public projection — the record minus the credential. */ function toAuthUser(record: MockUserRecord): AuthUser { // Field-by-field rather than a rest-spread that drops `password`: a spread // silently carries anything added to the record later, and this projection // is the boundary that keeps credentials out of every response. return { id: record.id, email: record.email, name: record.name, role: record.role, organisation: record.organisation, }; } export type CredentialCheck = | {outcome: 'ok'; user: AuthUser} | {outcome: 'unknown_email'} | {outcome: 'wrong_password'}; /** * The single credential-checking seam. * * Note the two distinct failure outcomes. That is a deliberate product choice * — the brief asks for "wrong email" and "wrong password" to read differently * — and it is worth knowing that it also makes the login form a user * enumeration oracle: an attacker can discover which addresses have accounts. * If that trade stops being acceptable, collapse both branches at the ROUTE * (map them to one 'invalid_credentials' message) rather than here, so the * distinction stays available to audit logging. */ export function verifyCredentials( email: string, password: string, ): CredentialCheck { const record = USERS.find( (u) => u.email.toLowerCase() === email.trim().toLowerCase(), ); if (!record) return {outcome: 'unknown_email'}; if (record.password !== password) return {outcome: 'wrong_password'}; return {outcome: 'ok', user: toAuthUser(record)}; } /** Used by the session endpoint to re-resolve a cookie subject to a user. */ export function findUserById(id: string): AuthUser | null { const record = USERS.find((u) => u.id === id); return record ? toAuthUser(record) : null; }