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, User } from "./storage"; declare global { namespace Express { interface User { id: number; username: string; email: string; fullName?: string; createdAt: Date; } } } 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(password: string, hashedPassword: string) { const [hash, salt] = hashedPassword.split("."); const buf = (await scryptAsync(password, salt, 64)) as Buffer; return timingSafeEqual(buf, Buffer.from(hash, "hex")); } export function setupAuth(app: Express) { const sessionSettings: session.SessionOptions = { secret: process.env.SESSION_SECRET || "pilateswithfadia-secret-key", resave: false, saveUninitialized: false, store: storage.sessionStore, cookie: { maxAge: 1000 * 60 * 60 * 24, // 1 day httpOnly: true, secure: process.env.NODE_ENV === "production", sameSite: "lax" } }; app.set("trust proxy", 1); app.use(session(sessionSettings)); 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: number, done) => { const user = await storage.getUser(id); done(null, user); }); app.post("/api/register", async (req, res, next) => { try { const { username, email, password, fullName } = req.body; // Basic validation if (!username || !email || !password) { return res.status(400).json({ message: "Username, email, and password are required" }); } if (password.length < 8) { return res.status(400).json({ message: "Password must be at least 8 characters" }); } // Check if username already exists const existingUserByUsername = await storage.getUserByUsername(username); if (existingUserByUsername) { return res.status(400).json({ message: "Username already exists" }); } // Check if email already exists const existingUserByEmail = await storage.getUserByEmail(email); if (existingUserByEmail) { return res.status(400).json({ message: "Email already in use" }); } const user = await storage.createUser({ username, email, password: await hashPassword(password), fullName }); req.login(user, (err) => { if (err) return next(err); // Return user without password const { password, ...userWithoutPassword } = user; res.status(201).json(userWithoutPassword); }); } catch (error) { res.status(400).json({ message: "Invalid registration data", error }); } }); app.post("/api/login", (req, res, next) => { passport.authenticate("local", (err: Error, user: User) => { if (err) return next(err); if (!user) { return res.status(401).json({ message: "Invalid username or password" }); } req.login(user, (err) => { if (err) return next(err); // Return user without password const { password, ...userWithoutPassword } = user; res.status(200).json(userWithoutPassword); }); })(req, res, next); }); app.post("/api/logout", (req, res, next) => { req.logout((err) => { if (err) return next(err); res.sendStatus(200); }); }); app.get("/api/me", (req, res) => { if (req.isAuthenticated()) { const { password, ...userWithoutPassword } = req.user as User; res.json(userWithoutPassword); } else { res.status(401).json({ message: "Not authenticated" }); } }); }