152 lines
4.8 KiB
TypeScript
152 lines
4.8 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 } 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);
|
|
}
|
|
|
|
// CORREÇÃO: Session secret seguro - obrigatório em produção
|
|
const SESSION_SECRET = process.env.SESSION_SECRET ||
|
|
(process.env.NODE_ENV === 'production'
|
|
? (() => { throw new Error('SESSION_SECRET obrigatório em produção'); })()
|
|
: randomBytes(32).toString('hex'));
|
|
|
|
const sessionSettings: session.SessionOptions = {
|
|
secret: SESSION_SECRET,
|
|
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) => {
|
|
console.log(`[Auth] Tentativa de login: ${username}`);
|
|
try {
|
|
const user = await storage.getUserByUsername(username);
|
|
if (!user) {
|
|
console.log(`[Auth] Usuário não encontrado: ${username}`);
|
|
return done(null, false);
|
|
}
|
|
console.log(`[Auth] Usuário encontrado, verificando senha...`);
|
|
const validPassword = await comparePasswords(password, user.password);
|
|
if (!validPassword) {
|
|
console.log(`[Auth] Senha inválida para: ${username}`);
|
|
return done(null, false);
|
|
}
|
|
console.log(`[Auth] Login bem-sucedido: ${username}`);
|
|
return done(null, user);
|
|
} catch (error: any) {
|
|
console.error(`[Auth] Erro no login: ${error.message}`);
|
|
return done(error);
|
|
}
|
|
}),
|
|
);
|
|
|
|
passport.serializeUser((user, done) => {
|
|
console.log(`[Auth] Serializando usuário: ${user.id}`);
|
|
done(null, user.id);
|
|
});
|
|
passport.deserializeUser(async (id: string, done) => {
|
|
console.log(`[Auth] Desserializando usuário: ${id}`);
|
|
try {
|
|
const user = await storage.getUser(id);
|
|
if (user) {
|
|
console.log(`[Auth] Enriquecendo usuário: ${id}`);
|
|
const enrichedUser = await storage.getEnrichedUser(user);
|
|
console.log(`[Auth] Usuário desserializado: ${id}`);
|
|
done(null, enrichedUser);
|
|
} else {
|
|
console.log(`[Auth] Usuário não encontrado na desserialização: ${id}`);
|
|
done(null, null);
|
|
}
|
|
} catch (error: any) {
|
|
console.error(`[Auth] Erro na desserialização: ${error.message}`);
|
|
done(error);
|
|
}
|
|
});
|
|
|
|
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) => {
|
|
console.log(`[Auth] Post-login, enriquecendo usuário: ${req.user!.id}`);
|
|
try {
|
|
const enrichedUser = await storage.getEnrichedUser(req.user!);
|
|
console.log(`[Auth] Enviando resposta de login para: ${enrichedUser.username}`);
|
|
res.status(200).json(enrichedUser);
|
|
} catch (error: any) {
|
|
console.error(`[Auth] Erro ao enriquecer usuário no login: ${error.message}`);
|
|
res.status(500).json({ error: "Erro ao processar login" });
|
|
}
|
|
});
|
|
|
|
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);
|
|
});
|
|
}
|