import passport from "passport"; import { Strategy as LocalStrategy } from "passport-local"; import { Express } from "express"; import session from "express-session"; import { scrypt, randomBytes, timingSafeEqual } from "crypto"; import { promisify } from "util"; import { storage } from "./storage"; import { User as SelectUser } from "@shared/schema"; declare global { namespace Express { interface User extends SelectUser { tenantId?: number; } } } const scryptAsync = promisify(scrypt); async function hashPassword(password: string) { const salt = randomBytes(16).toString("hex"); const buf = (await scryptAsync(password, salt, 64)) as Buffer; return `${buf.toString("hex")}.${salt}`; } async function comparePasswords(supplied: string, stored: string) { const [hashed, salt] = stored.split("."); const hashedBuf = Buffer.from(hashed, "hex"); const suppliedBuf = (await scryptAsync(supplied, salt, 64)) as Buffer; return timingSafeEqual(hashedBuf, suppliedBuf); } const sessionSettings: session.SessionOptions = { secret: process.env.SESSION_SECRET || "arcadia-browser-secret-key-2024", resave: false, saveUninitialized: false, store: storage.sessionStore, cookie: { secure: process.env.NODE_ENV === "production", httpOnly: true, maxAge: 24 * 60 * 60 * 1000, }, }; export const sessionMiddleware = session(sessionSettings); export function setupAuth(app: Express) { app.set("trust proxy", 1); app.use(sessionMiddleware); app.use(passport.initialize()); app.use(passport.session()); passport.use( new LocalStrategy(async (username, password, done) => { const user = await storage.getUserByUsername(username); if (!user || !(await comparePasswords(password, user.password))) { return done(null, false); } else { return done(null, user); } }), ); passport.serializeUser((user, done) => done(null, user.id)); passport.deserializeUser(async (id: string, done) => { const user = await storage.getUser(id); if (user) { const enrichedUser = await storage.getEnrichedUser(user); done(null, enrichedUser); } else { done(null, null); } }); app.post("/api/register", async (req, res, next) => { try { const existingUser = await storage.getUserByUsername(req.body.username); if (existingUser) { return res.status(400).send("Username already exists"); } const user = await storage.createUser({ ...req.body, password: await hashPassword(req.body.password), }); req.login(user, async (err) => { if (err) return next(err); const enrichedUser = await storage.getEnrichedUser(user); res.status(201).json(enrichedUser); }); } catch (error) { next(error); } }); app.post("/api/login", passport.authenticate("local"), async (req, res) => { const enrichedUser = await storage.getEnrichedUser(req.user!); res.status(200).json(enrichedUser); }); app.post("/api/logout", (req, res, next) => { req.logout((err) => { if (err) return next(err); res.sendStatus(200); }); }); app.get("/api/user", (req, res) => { if (!req.isAuthenticated()) return res.sendStatus(401); res.json(req.user); }); }