chore: add server/auth.ts
This commit is contained in:
parent
893ad6558d
commit
0382d22c97
1 changed files with 129 additions and 0 deletions
129
server/auth.ts
Normal file
129
server/auth.ts
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
/**
|
||||
* Magic-link customer authentication.
|
||||
*
|
||||
* Flow:
|
||||
* 1. Customer submits email → POST /api/auth/magic-link
|
||||
* 2. Server stores a token (15-min TTL) on the customer record
|
||||
* 3. Email with link is sent: /account/verify?token=<token>
|
||||
* 4. GET /api/auth/verify?token=<token> validates token, sets session
|
||||
*
|
||||
* Session is stored in express-session (PostgreSQL-backed via connect-pg-simple).
|
||||
*/
|
||||
|
||||
import { db } from "./db";
|
||||
import { customers } from "../shared/schema";
|
||||
import { eq } from "drizzle-orm";
|
||||
import crypto from "crypto";
|
||||
import { sendMagicLink } from "./email";
|
||||
import type { Request, Response } from "express";
|
||||
|
||||
function generateToken(): string {
|
||||
return crypto.randomBytes(32).toString("hex");
|
||||
}
|
||||
|
||||
export async function requestMagicLink(req: Request, res: Response) {
|
||||
const { email } = req.body as { email?: string };
|
||||
if (!email || !email.includes("@")) {
|
||||
return res.status(400).json({ error: "Valid email required" });
|
||||
}
|
||||
|
||||
const normalEmail = email.toLowerCase().trim();
|
||||
const token = generateToken();
|
||||
const expiresAt = new Date(Date.now() + 15 * 60 * 1000);
|
||||
|
||||
let [customer] = await db
|
||||
.select({ id: customers.id })
|
||||
.from(customers)
|
||||
.where(eq(customers.email, normalEmail));
|
||||
|
||||
if (!customer) {
|
||||
const [created] = await db
|
||||
.insert(customers)
|
||||
.values({ email: normalEmail, magicLinkToken: token, magicLinkTokenExpiresAt: expiresAt })
|
||||
.returning({ id: customers.id });
|
||||
customer = created;
|
||||
} else {
|
||||
await db
|
||||
.update(customers)
|
||||
.set({ magicLinkToken: token, magicLinkTokenExpiresAt: expiresAt })
|
||||
.where(eq(customers.id, customer.id));
|
||||
}
|
||||
|
||||
const baseUrl = `${req.protocol}://${req.get("host")}`;
|
||||
await sendMagicLink(normalEmail, token, baseUrl);
|
||||
|
||||
res.json({ ok: true });
|
||||
}
|
||||
|
||||
export async function verifyMagicLink(req: Request, res: Response) {
|
||||
const { token } = req.query as { token?: string };
|
||||
if (!token) return res.status(400).json({ error: "Token required" });
|
||||
|
||||
const [customer] = await db
|
||||
.select()
|
||||
.from(customers)
|
||||
.where(eq(customers.magicLinkToken, token));
|
||||
|
||||
if (!customer || !customer.magicLinkTokenExpiresAt) {
|
||||
return res.status(401).json({ error: "Invalid or expired token" });
|
||||
}
|
||||
|
||||
if (new Date() > new Date(customer.magicLinkTokenExpiresAt)) {
|
||||
return res.status(401).json({ error: "Token expired" });
|
||||
}
|
||||
|
||||
await db
|
||||
.update(customers)
|
||||
.set({ magicLinkToken: null, magicLinkTokenExpiresAt: null })
|
||||
.where(eq(customers.id, customer.id));
|
||||
|
||||
(req.session as any).customerId = customer.id;
|
||||
|
||||
res.json({
|
||||
ok: true,
|
||||
customer: {
|
||||
id: customer.id,
|
||||
email: customer.email,
|
||||
firstName: customer.firstName,
|
||||
lastName: customer.lastName,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function requireCustomer(req: Request, res: Response, next: Function) {
|
||||
if (!(req.session as any).customerId) {
|
||||
return res.status(401).json({ error: "Authentication required" });
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
export function requireAdmin(req: Request, res: Response, next: Function) {
|
||||
if (!(req.session as any).adminUserId) {
|
||||
return res.status(401).json({ error: "Admin access required" });
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
export async function logout(req: Request, res: Response) {
|
||||
req.session.destroy(() => {
|
||||
res.json({ ok: true });
|
||||
});
|
||||
}
|
||||
|
||||
export async function me(req: Request, res: Response) {
|
||||
const customerId = (req.session as any).customerId;
|
||||
if (!customerId) return res.json({ customer: null });
|
||||
|
||||
const [customer] = await db
|
||||
.select({
|
||||
id: customers.id,
|
||||
email: customers.email,
|
||||
firstName: customers.firstName,
|
||||
lastName: customers.lastName,
|
||||
phone: customers.phone,
|
||||
})
|
||||
.from(customers)
|
||||
.where(eq(customers.id, customerId));
|
||||
|
||||
res.json({ customer: customer || null });
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue