Password Policy/App rename/PM Scheduling Improvements

This commit is contained in:
2026-06-06 02:32:47 -05:00
parent dfd999b6a9
commit f024286b5e
59 changed files with 3814 additions and 299 deletions

View File

@@ -3,12 +3,13 @@ const bcrypt = require('bcryptjs');
const { audit, one } = require('../db');
const { publicUser, tokenFor } = require('../middleware/auth');
const { capabilitiesForRole } = require('../services/roles');
const { isLocked, registerFailure, registerSuccess, mustChangePassword } = require('../services/passwordPolicy');
const router = express.Router();
router.get('/public/bootstrap', (req, res) => {
res.json({
appName: 'MixedAssets',
appName: 'DepreCore',
setupComplete: Boolean(one('SELECT id FROM users LIMIT 1')),
database: 'sqlite'
});
@@ -17,11 +18,33 @@ router.get('/public/bootstrap', (req, res) => {
router.post('/auth/login', (req, res) => {
const { email, password } = req.body;
const user = one('SELECT * FROM users WHERE lower(email) = lower(?) AND status = ?', [email || '', 'active']);
if (!user || !bcrypt.compareSync(password || '', user.password_hash)) {
if (!user) return res.status(401).json({ error: 'Invalid email or password' });
// Locked accounts are refused before the password is even checked.
if (isLocked(user)) {
const minutes = Math.max(1, Math.ceil((new Date(user.locked_until).getTime() - Date.now()) / 60000));
return res.status(423).json({ error: `Account locked. Try again in ${minutes} minute(s).`, lockedUntil: user.locked_until });
}
if (!bcrypt.compareSync(password || '', user.password_hash)) {
const { locked, lockedUntil } = registerFailure(user);
audit(user.id, 'login_failed', 'user', user.id, null, { email: user.email });
if (locked) {
const minutes = Math.max(1, Math.ceil((new Date(lockedUntil).getTime() - Date.now()) / 60000));
return res.status(423).json({ error: `Too many failed attempts. Account locked for ${minutes} minute(s).`, lockedUntil });
}
return res.status(401).json({ error: 'Invalid email or password' });
}
registerSuccess(user);
audit(user.id, 'login', 'user', user.id, null, { email: user.email });
return res.json({ token: tokenFor(user), user: publicUser(user), capabilities: capabilitiesForRole(user.role) });
// Token is still issued so the user can call the self-service change endpoint when forced.
return res.json({
token: tokenFor(user),
user: publicUser(user),
capabilities: capabilitiesForRole(user.role),
mustChangePassword: mustChangePassword(user)
});
});
module.exports = router;