// Email/password auth + the sign-in page (which also hosts passkey + CoinPay buttons). import { Router } from "express"; import { page, footer, esc } from "../lib/html.mjs"; import { csrfInput, createSession, destroySession, takeNext } from "../lib/session.mjs"; import { hashPassword, verifyPassword } from "../lib/crypto.mjs"; import { createUserWithPassword, userByEmail } from "../lib/users.mjs"; import { dashboardHandler } from "./pages.mjs"; import { config } from "../config.mjs"; export const authRouter = Router(); function authPage(req, { error = "", mode = "in" } = {}) { const body = ` LSLogicSRCcredentials Share secrets, end-to-end encrypted ${error ? `${esc(error)}` : ""} ${csrfInput(req)} Email Password ${mode === "up" ? "Create account" : "Sign in"} ${mode === "up" ? `Already have an account? Sign in` : `New here? Create account`} or 🔑 Continue with a passkey ${config.coinpayLoginEnabled ? `◆ Continue with CoinPay` : `◆ Continue with CoinPay`} ${footer} `; return page({ title: "LogicSRC ▸ sign in", body }); } authRouter.get("/", (req, res) => { if (req.user) { const next = takeNext(req, res); if (next) return res.redirect(next); return dashboardHandler(req, res); // dashboard lives at the root } res.type("html").send(authPage(req, { mode: req.query.mode === "up" ? "up" : "in" })); }); authRouter.post("/auth/register", async (req, res) => { const email = String(req.body.email || "").trim().toLowerCase(); const password = String(req.body.password || ""); if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email)) return res.type("html").send(authPage(req, { mode: "up", error: "Enter a valid email." })); if (password.length < 8) return res.type("html").send(authPage(req, { mode: "up", error: "Password must be at least 8 characters." })); if (await userByEmail(email)) return res.type("html").send(authPage(req, { mode: "up", error: "That email already has an account — sign in." })); const user = await createUserWithPassword(email, hashPassword(password)); await createSession(res, user.id); res.redirect(takeNext(req, res) || "/"); }); authRouter.post("/auth/login", async (req, res) => { const email = String(req.body.email || "").trim().toLowerCase(); const password = String(req.body.password || ""); const user = await userByEmail(email); if (!user || !verifyPassword(password, user.password_hash)) { return res.type("html").send(authPage(req, { mode: "in", error: "Wrong email or password." })); } await createSession(res, user.id); res.redirect(takeNext(req, res) || "/"); }); authRouter.post("/auth/logout", async (req, res) => { await destroySession(req, res); res.redirect("/"); });
Share secrets, end-to-end encrypted
${mode === "up" ? `Already have an account? Sign in` : `New here? Create account`}