pwf-website-new/server/auth.ts

132 lines
4.0 KiB
TypeScript

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, insertUserSchema } from "@shared/schema";
declare global {
namespace Express {
interface User extends SelectUser {}
}
}
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);
}
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 validatedUser = insertUserSchema.parse(req.body);
// Check if username already exists
const existingUserByUsername = await storage.getUserByUsername(validatedUser.username);
if (existingUserByUsername) {
return res.status(400).json({ message: "Username already exists" });
}
// Check if email already exists
const existingUserByEmail = await storage.getUserByEmail(validatedUser.email);
if (existingUserByEmail) {
return res.status(400).json({ message: "Email already in use" });
}
const user = await storage.createUser({
...validatedUser,
password: await hashPassword(validatedUser.password),
});
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: SelectUser) => {
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/user", (req, res) => {
if (!req.isAuthenticated()) return res.sendStatus(401);
// Return user without password
const { password, ...userWithoutPassword } = req.user;
res.json(userWithoutPassword);
});
}