Google-SSO PWA for bistro employee clock-in/out, admin employee management, and stats with CSV export. Express + SQLite backend, React + Zustand frontend in the light-mono-tui design language. Multi-stage Dockerfile, compose.yaml for image-based deploys, nginx reverse-proxy template, and an OKF documentation bundle in docs/. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
65 lines
1.7 KiB
TypeScript
65 lines
1.7 KiB
TypeScript
import { Router } from "express";
|
|
import { z } from "zod";
|
|
import { verifyGoogleIdToken } from "../auth/google";
|
|
import { clearSession, issueSession } from "../auth/session";
|
|
import { requireAuth } from "../auth/middleware";
|
|
import { env } from "../env";
|
|
import { findActiveEmployeeByEmail, touchEmployeeName } from "../services/employees";
|
|
import type { SessionUser } from "../types";
|
|
|
|
export const authRouter = Router();
|
|
|
|
const loginSchema = z.object({ credential: z.string().min(1) });
|
|
|
|
authRouter.post("/google", async (req, res) => {
|
|
const parsed = loginSchema.safeParse(req.body);
|
|
if (!parsed.success) {
|
|
res.status(400).json({ error: "Missing Google credential" });
|
|
return;
|
|
}
|
|
|
|
let profile;
|
|
try {
|
|
profile = await verifyGoogleIdToken(parsed.data.credential);
|
|
} catch {
|
|
res.status(401).json({ error: "Invalid Google token" });
|
|
return;
|
|
}
|
|
|
|
if (!profile.emailVerified) {
|
|
res.status(403).json({ error: "Google account email is not verified" });
|
|
return;
|
|
}
|
|
|
|
const isAdmin = env.adminEmails.includes(profile.email);
|
|
const employee = findActiveEmployeeByEmail(profile.email);
|
|
|
|
if (!isAdmin && !employee) {
|
|
res.status(403).json({ error: "This account is not registered as an EatMe employee" });
|
|
return;
|
|
}
|
|
|
|
if (employee) {
|
|
touchEmployeeName(employee.id, profile.name);
|
|
}
|
|
|
|
const user: SessionUser = {
|
|
email: profile.email,
|
|
name: profile.name,
|
|
role: isAdmin ? "admin" : "employee",
|
|
employeeId: employee?.id ?? null,
|
|
};
|
|
|
|
issueSession(res, user);
|
|
res.json({ user });
|
|
});
|
|
|
|
authRouter.post("/logout", (_req, res) => {
|
|
clearSession(res);
|
|
res.status(204).end();
|
|
});
|
|
|
|
authRouter.get("/me", requireAuth, (req, res) => {
|
|
res.json({ user: req.user });
|
|
});
|