/** * Check if login request is completed and create session if so * Public procedure - no authentication required * * Flow: * 1. Read rev.login_request_token cookie * 2. If token not found in DB (fake or expired): return { status: 'pending' } * 3. If valid login request: * - Check if expired: return { status: 'expired' } * - Check if not completed: return { status: 'pending' } * - If completed: * a. Create user_device record * b. Create session (trusted_mode = true) * c. Delete login_request row * d. Set session cookie, clear login_request cookie * e. Return { status: 'completed', redirectTo: '/dashboard' or '/auth/trust-device' } */ import { withTransaction } from "@reviq/db"; import { COOKIE_NAMES, COOKIE_OPTIONS, deleteCookie, getCookie, setCookie, } from "../../utils/cookies.js"; import { getGeoInfo, getUserAgent } from "../../utils/geo.js"; import { createSession, isDeviceTrusted, upsertUserDevice, } from "../../utils/session.js"; import { os } from "../base.js"; /** * Login if request is completed handler * Polls for login completion and creates session when ready */ export const loginIfRequestIsCompleted = os.auth.loginIfRequestIsCompleted.handler(async ({ context }) => { // Read login request token from cookie const loginRequestToken = getCookie( context.reqHeaders, COOKIE_NAMES.LOGIN_REQUEST_TOKEN, ); // No cookie - return pending (shouldn't happen in normal flow) if (!loginRequestToken) { return { status: "pending" as const }; } // Fetch login request from database by token const loginRequest = await context.db .selectFrom("login_requests") .select([ "id", "user_id", "device_fingerprint", "completed_at", "expires_at", ]) .where("token", "=", loginRequestToken) .executeTakeFirst(); // Login request not found - might have been deleted or invalid ID if (!loginRequest) { return { status: "pending" as const }; } // Check if expired if (new Date() > loginRequest.expires_at) { return { status: "expired" as const }; } // Check if not completed yet if (loginRequest.completed_at === null) { return { status: "pending" as const }; } // Login request is completed - create session const userId = loginRequest.user_id; const deviceFingerprint = loginRequest.device_fingerprint; // Device fingerprint should always be present, but handle null case defensively if (!deviceFingerprint) { return { status: "pending" as const }; } // Get current request info const geo = getGeoInfo(context.reqHeaders, context.clientIP); const userAgent = getUserAgent(context.reqHeaders); // Create session in transaction (atomic: device upsert + session + login_request delete) const { session, deviceTrusted } = await withTransaction( context.db, async (trx) => { // Upsert user device const deviceId = await upsertUserDevice( trx, userId, deviceFingerprint, geo, userAgent, ); // Check if device is already trusted const trusted = await isDeviceTrusted(trx, userId, deviceFingerprint); // Create session with trusted mode = true (email-confirmed login) const newSession = await createSession(trx, { userId, deviceId, trustedMode: true, geo, userAgent, }); // Delete the login request (it's been consumed) await trx .deleteFrom("login_requests") .where("id", "=", loginRequest.id) .execute(); return { session: newSession, deviceTrusted: trusted }; }, ); // Set session cookie setCookie( context.resHeaders, COOKIE_NAMES.SESSION_TOKEN, session.token, COOKIE_OPTIONS.session, ); // Clear login request cookie deleteCookie(context.resHeaders, COOKIE_NAMES.LOGIN_REQUEST_TOKEN); // Determine redirect path based on device trust status const redirectTo = deviceTrusted ? "/dashboard" : "/auth/trust-device"; return { status: "completed" as const, redirectTo, }; });