mirror of
https://github.com/R0m1k3/LogiFlow.git
synced 2026-10-11 17:27:31 +02:00
Modify production logic to prevent forced admin password resets on updates, allowing users to set and keep their own passwords unless explicitly overridden by an environment variable. Replit-Commit-Author: Agent Replit-Commit-Session-Id: b163d4c0-de5e-4f4e-a9c0-aed4c7049718 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/1957c339-2757-4d1f-8e92-e9f71a1ce58e/b163d4c0-de5e-4f4e-a9c0-aed4c7049718/kdCs97W
260 lines
8.1 KiB
TypeScript
260 lines
8.1 KiB
TypeScript
import passport from "passport";
|
|
import { Strategy as LocalStrategy } from "passport-local";
|
|
import type { Express } from "express";
|
|
import session from "express-session";
|
|
import { storage } from "./storage.js";
|
|
import connectPg from "connect-pg-simple";
|
|
import { scrypt, randomBytes, timingSafeEqual } from "crypto";
|
|
import { promisify } from "util";
|
|
|
|
const scryptAsync = promisify(scrypt);
|
|
|
|
console.log('🐳 PRODUCTION: Local auth configured with PostgreSQL sessions');
|
|
|
|
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) {
|
|
console.log('🔐 Production password comparison', { suppliedLength: supplied.length, storedFormat: stored?.substring(0, 10) + '...' });
|
|
|
|
// Try different password formats for backward compatibility
|
|
|
|
// 1. Try new format: hash.salt
|
|
if (stored && stored.includes('.')) {
|
|
const [hashed, salt] = stored.split(".");
|
|
if (hashed && salt) {
|
|
try {
|
|
const hashedBuf = Buffer.from(hashed, "hex");
|
|
const suppliedBuf = (await scryptAsync(supplied, salt, 64)) as Buffer;
|
|
const result = timingSafeEqual(hashedBuf, suppliedBuf);
|
|
if (result) {
|
|
console.log('✅ Password matched with hash.salt format');
|
|
return true;
|
|
}
|
|
} catch (error) {
|
|
console.log('⚠️ hash.salt format failed, trying other formats');
|
|
}
|
|
}
|
|
}
|
|
|
|
// 2. Try bcrypt format (common in existing systems)
|
|
if (stored && stored.startsWith('$2')) {
|
|
try {
|
|
const bcrypt = await import('bcrypt');
|
|
const result = await bcrypt.compare(supplied, stored);
|
|
if (result) {
|
|
console.log('✅ Password matched with bcrypt format');
|
|
return true;
|
|
}
|
|
} catch (error) {
|
|
console.log('⚠️ bcrypt comparison failed');
|
|
}
|
|
}
|
|
|
|
// 3. Try plain text (for development/migration)
|
|
if (stored === supplied) {
|
|
console.log('✅ Password matched with plain text format');
|
|
return true;
|
|
}
|
|
|
|
// 4. Try simple hash format (legacy)
|
|
try {
|
|
const crypto = await import('crypto');
|
|
const hash = crypto.createHash('sha256').update(supplied).digest('hex');
|
|
if (stored === hash) {
|
|
console.log('✅ Password matched with SHA256 format');
|
|
return true;
|
|
}
|
|
} catch (error) {
|
|
console.log('⚠️ SHA256 comparison failed');
|
|
}
|
|
|
|
console.log('❌ No password format matched');
|
|
return false;
|
|
}
|
|
|
|
async function createDefaultAdminUser() {
|
|
try {
|
|
// Check for force reset flag
|
|
const forceReset = process.env.FORCE_ADMIN_RESET === 'true';
|
|
if (forceReset) {
|
|
console.log('🔄 FORCE_ADMIN_RESET detected, deleting existing admin user...');
|
|
const existingAdmin = await storage.getUserByUsername('admin');
|
|
if (existingAdmin) {
|
|
await storage.deleteUser(existingAdmin.id);
|
|
console.log('✅ Existing admin user deleted');
|
|
}
|
|
}
|
|
|
|
const existingAdmin = await storage.getUserByUsername('admin');
|
|
if (!existingAdmin) {
|
|
const hashedPassword = await hashPassword('admin');
|
|
await storage.createUser({
|
|
id: 'admin_prod',
|
|
username: 'admin',
|
|
email: 'admin@logiflow.com',
|
|
firstName: 'Administrateur',
|
|
lastName: 'Production',
|
|
password: hashedPassword,
|
|
role: 'admin',
|
|
passwordChanged: false,
|
|
});
|
|
console.log('✅ Production admin user created: admin/admin');
|
|
} else {
|
|
console.log('✅ Production admin user found:', {
|
|
id: existingAdmin.id,
|
|
username: existingAdmin.username,
|
|
passwordFormat: existingAdmin.password ? 'present' : 'missing'
|
|
});
|
|
|
|
// Ne plus forcer la réinitialisation du mot de passe admin
|
|
// L'admin peut maintenant changer son mot de passe et il sera conservé
|
|
console.log('✅ Admin user exists, preserving current password');
|
|
|
|
// Seulement réinitialiser si explicitement demandé via FORCE_ADMIN_RESET
|
|
if (process.env.FORCE_ADMIN_RESET === 'true') {
|
|
console.log('🔄 FORCE_ADMIN_RESET env var set, resetting admin password...');
|
|
try {
|
|
const newHashedPassword = await hashPassword('admin');
|
|
await storage.updateUser(existingAdmin.id, {
|
|
password: newHashedPassword,
|
|
passwordChanged: false
|
|
});
|
|
console.log('✅ Admin password manually reset to: admin/admin');
|
|
} catch (error) {
|
|
console.error('❌ Failed to reset admin password:', (error as Error).message);
|
|
}
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('Error managing admin user:', error);
|
|
}
|
|
}
|
|
|
|
export function setupLocalAuth(app: Express) {
|
|
// Create admin user on startup
|
|
createDefaultAdminUser();
|
|
|
|
const PostgresSessionStore = connectPg(session);
|
|
const sessionStore = new PostgresSessionStore({
|
|
conString: process.env.DATABASE_URL,
|
|
createTableIfMissing: true,
|
|
tableName: 'session',
|
|
});
|
|
|
|
const sessionSettings: session.SessionOptions = {
|
|
secret: process.env.SESSION_SECRET || 'production-fallback-secret-key',
|
|
resave: false,
|
|
saveUninitialized: false,
|
|
store: sessionStore,
|
|
cookie: {
|
|
httpOnly: true,
|
|
secure: false, // Set to true with HTTPS proxy
|
|
maxAge: 24 * 60 * 60 * 1000, // 24 hours
|
|
},
|
|
};
|
|
|
|
app.set("trust proxy", 1);
|
|
app.use(session(sessionSettings));
|
|
app.use(passport.initialize());
|
|
app.use(passport.session());
|
|
|
|
passport.use(
|
|
new LocalStrategy(
|
|
{
|
|
usernameField: 'username',
|
|
passwordField: 'password',
|
|
},
|
|
async (username, password, done) => {
|
|
try {
|
|
const user = await storage.getUserByUsername(username);
|
|
if (!user || !user.password) {
|
|
return done(null, false, { message: 'Invalid credentials' });
|
|
}
|
|
|
|
const isValidPassword = await comparePasswords(password, user.password);
|
|
if (!isValidPassword) {
|
|
return done(null, false, { message: 'Invalid credentials' });
|
|
}
|
|
|
|
return done(null, user);
|
|
} catch (error) {
|
|
return done(error);
|
|
}
|
|
}
|
|
)
|
|
);
|
|
|
|
passport.serializeUser((user, done) => done(null, user.id));
|
|
passport.deserializeUser(async (id: string, done) => {
|
|
try {
|
|
const user = await storage.getUserWithGroups(id);
|
|
done(null, user);
|
|
} catch (error) {
|
|
done(error);
|
|
}
|
|
});
|
|
|
|
// Login route
|
|
app.post("/api/login", (req, res, next) => {
|
|
passport.authenticate("local", (err: any, user: any, info: any) => {
|
|
if (err) return next(err);
|
|
if (!user) {
|
|
return res.status(400).json({ message: info?.message || "Invalid credentials" });
|
|
}
|
|
|
|
req.login(user, (err) => {
|
|
if (err) return next(err);
|
|
res.json({
|
|
id: user.id,
|
|
username: user.username,
|
|
email: user.email,
|
|
firstName: user.firstName,
|
|
lastName: user.lastName,
|
|
role: user.role,
|
|
passwordChanged: user.passwordChanged
|
|
});
|
|
});
|
|
})(req, res, next);
|
|
});
|
|
|
|
// Logout route
|
|
app.post("/api/logout", (req: any, res: any, next: any) => {
|
|
req.logout((err: any) => {
|
|
if (err) return next(err);
|
|
res.json({ message: "Logout successful" });
|
|
});
|
|
});
|
|
|
|
// Get current user
|
|
app.get("/api/user", (req: any, res) => {
|
|
if (req.isAuthenticated && req.isAuthenticated()) {
|
|
res.json({
|
|
id: req.user.id,
|
|
username: req.user.username,
|
|
email: req.user.email,
|
|
firstName: req.user.firstName,
|
|
lastName: req.user.lastName,
|
|
role: req.user.role,
|
|
passwordChanged: req.user.passwordChanged
|
|
});
|
|
} else {
|
|
res.status(401).json({ message: "Not authenticated" });
|
|
}
|
|
});
|
|
|
|
// Check default credentials endpoint
|
|
app.get("/api/default-credentials-check", (req, res) => {
|
|
res.json({ hasDefaultCredentials: true });
|
|
});
|
|
}
|
|
|
|
export function requireAuth(req: any, res: any, next: any) {
|
|
if (req.isAuthenticated && req.isAuthenticated()) {
|
|
return next();
|
|
}
|
|
res.status(401).json({ message: "Authentication required" });
|
|
} |