Release v2.20.1: migrate authentication to Better Auth
This commit is contained in:
1 parent
e9a53f577c
commit
fba4f99ff7
92 files changed
+5085
-1943
No files matched your search
+149
-89
@@ -1,33 +1,50 @@
|
||||
// Admin account route registration and profile helpers.
|
||||
|
||||
const { fetchAppSettings } = require('#src/data/app-settings');
|
||||
const { formatAccountEmailExpiry, renderAccountEmailTemplate } = require('#src/data/account-email-templates');
|
||||
const { formatUserAgentLabel } = require('#src/data/audit-log');
|
||||
const { createSyntheticAuthEmail, toVisibleEmail, mapAuthMethod } = require('#src/data/auth-accounts');
|
||||
|
||||
module.exports = function registerAccountRoutes(app, deps) {
|
||||
const pool = deps.pool;
|
||||
const common = deps.common;
|
||||
const pages = deps.pages;
|
||||
const betterAuth = deps.betterAuth;
|
||||
const fromNodeHeaders = deps.fromNodeHeaders;
|
||||
const formatDashboardDate = deps.formatDashboardDate;
|
||||
const getAuditUserId = deps.getAuditUserId;
|
||||
const verifyPassword = deps.verifyPassword;
|
||||
const hashPassword = deps.hashPassword;
|
||||
const validatePasswordStrength = deps.validatePasswordStrength;
|
||||
const createUserSession = deps.createUserSession;
|
||||
const setSessionCookie = deps.setSessionCookie;
|
||||
const getSessionMaxAgeMs = deps.getSessionMaxAgeMs;
|
||||
const clearSessionCookie = deps.clearSessionCookie;
|
||||
const parseCookies = deps.parseCookies;
|
||||
const hashSessionToken = deps.hashSessionToken;
|
||||
const sessionCookieName = deps.sessionCookieName;
|
||||
const recordRequestAuditEvent = deps.recordRequestAuditEvent;
|
||||
const sendAccountEmail = deps.sendAccountEmail;
|
||||
const createOneTimeToken = deps.createOneTimeToken;
|
||||
|
||||
async function verifyCurrentPassword(req, password) {
|
||||
try {
|
||||
const result = await betterAuth.api.verifyPassword({
|
||||
body: { password: password },
|
||||
headers: fromNodeHeaders(req.headers)
|
||||
});
|
||||
return !result || result.status !== false;
|
||||
} catch (_error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function getPasswordRequirements() {
|
||||
const settings = await fetchAppSettings(pool);
|
||||
return buildPasswordRequirements(settings);
|
||||
}
|
||||
|
||||
async function getAuthMethods(userId) {
|
||||
const [rows] = await pool.query('SELECT provider_id FROM a_accounts WHERE user_id = ? ORDER BY created_at ASC', [userId]);
|
||||
return (rows || []).map(function (row) { return mapAuthMethod(row.provider_id); }).filter(Boolean);
|
||||
}
|
||||
|
||||
async function getPasskeys(userId) {
|
||||
const [rows] = await pool.query('SELECT id, name, created_at FROM a_user_passkeys WHERE user_id = ? ORDER BY created_at ASC', [userId]);
|
||||
return rows || [];
|
||||
}
|
||||
|
||||
function isPasswordChangeRequired(req) {
|
||||
return Boolean(req && (req.authenticatedUser && req.authenticatedUser.mustChangePassword || req.currentUser && req.currentUser.mustChangePassword));
|
||||
}
|
||||
@@ -51,67 +68,112 @@ module.exports = function registerAccountRoutes(app, deps) {
|
||||
};
|
||||
}
|
||||
|
||||
function getSessionMetadata(req) {
|
||||
const forwardedAddress = String(req && req.headers && req.headers['x-forwarded-for'] || '').split(',')[0].trim();
|
||||
return {
|
||||
ipAddress: forwardedAddress || String(req && req.ip || req && req.socket && req.socket.remoteAddress || 'unknown').trim(),
|
||||
userAgent: req && req.headers && req.headers['user-agent']
|
||||
};
|
||||
}
|
||||
async function clearPendingEmailWithoutActiveOtp(user) {
|
||||
const pendingEmail = String(user && user.pending_email || '').trim().toLowerCase();
|
||||
if (!pendingEmail) return;
|
||||
const email = String(user.email || '').trim().toLowerCase();
|
||||
const identifier = pendingEmail === email
|
||||
? 'email-verification-otp-' + email
|
||||
: 'change-email-otp-' + email + '-' + pendingEmail;
|
||||
|
||||
function getRequestOrigin(req) {
|
||||
const forwardedProto = String(req.headers['x-forwarded-proto'] || req.protocol || 'http').split(',')[0].trim();
|
||||
const forwardedHost = String(req.headers['x-forwarded-host'] || req.headers.host || '').split(',')[0].trim();
|
||||
return forwardedHost ? forwardedProto + '://' + forwardedHost : '';
|
||||
await pool.query('DELETE FROM a_verifications WHERE identifier = ? AND expires_at <= NOW()', [identifier]);
|
||||
const [result] = await pool.query(
|
||||
`UPDATE a_users
|
||||
SET pending_email = NULL
|
||||
WHERE id = ?
|
||||
AND pending_email = ?
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM a_verifications
|
||||
WHERE identifier = ? AND expires_at > NOW()
|
||||
)`,
|
||||
[user.id, user.pending_email, identifier]
|
||||
);
|
||||
if (result && result.affectedRows) user.pending_email = null;
|
||||
}
|
||||
|
||||
app.post('/account/email', async function (req, res, next) {
|
||||
try {
|
||||
const email = String(req.body.email || '').trim().toLowerCase();
|
||||
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) return res.status(400).send('Email address is invalid.');
|
||||
const [rows] = await pool.query('SELECT pending_email FROM a_users WHERE id = ? LIMIT 1', [req.currentUser.id]);
|
||||
const email = String(rows && rows[0] && rows[0].pending_email || '').trim().toLowerCase();
|
||||
if (!email) return res.redirect('/account?message=' + encodeURIComponent('There is no pending email change.'));
|
||||
const settings = await fetchAppSettings(pool);
|
||||
if (!settings['email.smtp_enabled'] || typeof sendAccountEmail !== 'function') return res.status(400).send('Email delivery is not configured.');
|
||||
const token = createOneTimeToken();
|
||||
const expiryMinutes = Number(settings['email.verification_expiry_minutes']);
|
||||
await pool.query('UPDATE a_users SET pending_email = ?, pending_email_token_hash = ?, pending_email_expires_at = DATE_ADD(NOW(), INTERVAL ' + expiryMinutes + ' MINUTE) WHERE id = ?', [email, hashSessionToken(token), req.currentUser.id]);
|
||||
const url = getRequestOrigin(req) + '/verify-email?token=' + encodeURIComponent(token);
|
||||
await sendAccountEmail(settings, Object.assign({ to: email }, renderAccountEmailTemplate(settings['email.verification_subject'], settings['email.verification_body'], { url: url, username: req.currentUser.username, display_name: req.currentUser.name, email: email, expiry_time: formatAccountEmailExpiry(expiryMinutes, 'minutes'), action_alignment: settings['email.verification_button_alignment'], action_label: settings['email.verification_button_text'] })));
|
||||
if (typeof recordRequestAuditEvent === 'function') await recordRequestAuditEvent(pool, req, { category: 'users', eventType: 'user.email_change_requested', actorUserId: req.currentUser.id, targetType: 'user', targetId: req.currentUser.id, targetLabel: email });
|
||||
res.redirect('/account?message=' + encodeURIComponent('Check your new email address for a verification link.'));
|
||||
if (!settings['email.smtp_enabled']) return res.status(400).send('Email delivery is not configured.');
|
||||
await betterAuth.api.requestEmailChangeEmailOTP({ body: { newEmail: email }, headers: fromNodeHeaders(req.headers) });
|
||||
res.redirect('/account?message=' + encodeURIComponent('A verification code was sent to your new email address.'));
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/account', function (req, res) {
|
||||
fetchAppSettings(pool).then(function (settings) {
|
||||
app.post('/account/email/send-verification', async function (req, res, next) {
|
||||
try {
|
||||
const email = String(req.currentUser.email || '').trim().toLowerCase();
|
||||
if (!email) return res.redirect('/account?message=' + encodeURIComponent('Add an email address before requesting a verification code.'));
|
||||
const settings = await fetchAppSettings(pool);
|
||||
if (!settings['email.smtp_enabled']) return res.status(400).send('Email delivery is not configured.');
|
||||
await pool.query('UPDATE a_users SET pending_email = ? WHERE id = ?', [email, req.currentUser.id]);
|
||||
await betterAuth.api.sendVerificationOTP({ body: { email: email, type: 'email-verification' } });
|
||||
res.redirect('/account?message=' + encodeURIComponent('A verification code was sent to your email address.'));
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/account/email/verify', async function (req, res, next) {
|
||||
try {
|
||||
const otp = String(req.body.otp || '').trim();
|
||||
const [rows] = await pool.query('SELECT email, email_verified, pending_email FROM a_users WHERE id = ? LIMIT 1', [req.currentUser.id]);
|
||||
const user = rows && rows[0];
|
||||
if (!user) return res.status(404).send('User not found.');
|
||||
const pendingEmail = String(user.pending_email || '').trim().toLowerCase();
|
||||
if (pendingEmail) {
|
||||
if (pendingEmail === String(user.email || '').trim().toLowerCase()) {
|
||||
await betterAuth.api.verifyEmailOTP({ body: { email: pendingEmail, otp: otp } });
|
||||
} else {
|
||||
await betterAuth.api.changeEmailEmailOTP({ body: { newEmail: pendingEmail, otp: otp }, headers: fromNodeHeaders(req.headers) });
|
||||
}
|
||||
await pool.query('UPDATE a_users SET email = ?, email_verified = 1, pending_email = NULL WHERE id = ?', [pendingEmail, req.currentUser.id]);
|
||||
if (typeof recordRequestAuditEvent === 'function') await recordRequestAuditEvent(pool, req, { category: 'security', eventType: 'email.verification_completed', targetType: 'user', targetId: req.currentUser.id, targetLabel: pendingEmail });
|
||||
return res.redirect('/account?message=' + encodeURIComponent('Email address verified.'));
|
||||
}
|
||||
res.status(400).send('No email change is awaiting verification. Request a new code and try again.');
|
||||
} catch (error) {
|
||||
res.status(400).send('The email code is invalid or expired. Request a new code and try again.');
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/account', async function (req, res) {
|
||||
try {
|
||||
await clearPendingEmailWithoutActiveOtp(req.currentUser);
|
||||
const settings = await fetchAppSettings(pool);
|
||||
const passwordRequirements = buildPasswordRequirements(settings);
|
||||
const authMethods = await getAuthMethods(req.currentUser.id);
|
||||
const passkeys = await getPasskeys(req.currentUser.id);
|
||||
if (!settings['security.allow_user_session_revocation']) {
|
||||
return res.send(pages.renderAccountPage(req.currentUser, req.query.message ? String(req.query.message) : '', '/dashboard', false, [], passwordRequirements));
|
||||
return res.send(pages.renderAccountPage(req.currentUser, req.query.message ? String(req.query.message) : '', '/dashboard', false, [], passwordRequirements, authMethods, passkeys));
|
||||
}
|
||||
const cookies = parseCookies(req.headers.cookie || '');
|
||||
const tokenHash = cookies[sessionCookieName] ? hashSessionToken(cookies[sessionCookieName]) : '';
|
||||
return pool.query(
|
||||
const sessionToken = String(req.betterAuthSession && req.betterAuthSession.session && req.betterAuthSession.session.token || cookies[sessionCookieName] || '');
|
||||
const result = await pool.query(
|
||||
'SELECT id, session_hash, ip_address, user_agent, created_at, last_used_at, expires_at FROM a_sessions WHERE user_id = ? AND expires_at > NOW() ORDER BY last_used_at DESC',
|
||||
[req.currentUser.id]
|
||||
).then(function (result) {
|
||||
const rows = result[0] || [];
|
||||
const sessions = rows.map(function (session) {
|
||||
return {
|
||||
id: Number(session.id),
|
||||
isCurrent: Boolean(tokenHash && session.session_hash === tokenHash),
|
||||
ipAddress: String(session.ip_address || 'Unknown'),
|
||||
userAgent: formatUserAgentLabel(session.user_agent) || 'Unknown browser',
|
||||
createdAtLabel: formatDashboardDate(session.created_at),
|
||||
lastUsedAtLabel: formatDashboardDate(session.last_used_at),
|
||||
expiresAtLabel: formatDashboardDate(session.expires_at)
|
||||
};
|
||||
});
|
||||
res.send(pages.renderAccountPage(req.currentUser, req.query.message ? String(req.query.message) : '', '/dashboard', true, sessions, passwordRequirements));
|
||||
);
|
||||
const rows = result[0] || [];
|
||||
const sessions = rows.map(function (session) {
|
||||
return {
|
||||
id: Number(session.id),
|
||||
isCurrent: Boolean(sessionToken && session.session_hash === sessionToken),
|
||||
ipAddress: String(session.ip_address || 'Unknown'),
|
||||
userAgent: formatUserAgentLabel(session.user_agent) || 'Unknown browser',
|
||||
createdAtLabel: formatDashboardDate(session.created_at),
|
||||
lastUsedAtLabel: formatDashboardDate(session.last_used_at),
|
||||
expiresAtLabel: formatDashboardDate(session.expires_at)
|
||||
};
|
||||
});
|
||||
}).catch(function (error) {
|
||||
res.send(pages.renderAccountPage(req.currentUser, req.query.message ? String(req.query.message) : '', '/dashboard', true, sessions, passwordRequirements, authMethods, passkeys));
|
||||
} catch (error) {
|
||||
res.status(500).send(error.message || 'Unable to load account settings.');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/account/password', async function (req, res, next) {
|
||||
@@ -137,37 +199,39 @@ module.exports = function registerAccountRoutes(app, deps) {
|
||||
if (email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) return res.status(400).send('Email address is invalid.');
|
||||
if (!currentPassword) return res.status(400).send('Current password is required.');
|
||||
|
||||
const [rows] = await pool.query('SELECT id, username, name, email, password_hash, password_salt, password_iterations FROM a_users WHERE id = ? LIMIT 1', [req.currentUser.id]);
|
||||
const [rows] = await pool.query('SELECT id, username, name, email, pending_email FROM a_users WHERE id = ? LIMIT 1', [req.currentUser.id]);
|
||||
const user = rows[0] || null;
|
||||
if (!user) return res.status(404).send('User not found.');
|
||||
if (!verifyPassword(currentPassword, user)) return res.status(400).send('Current password is incorrect.');
|
||||
if (!(await verifyCurrentPassword(req, currentPassword))) return res.status(400).send('Current password is incorrect.');
|
||||
if (username !== user.username && await common.fetchDuplicateName(pool, 'a_users', username, user.id, 'username')) return res.status(400).send('That username already exists.');
|
||||
if (name !== user.name && await common.fetchDuplicateName(pool, 'a_users', name, user.id)) return res.status(400).send('That name already exists.');
|
||||
|
||||
const confirmedEmail = String(user.email || '').trim().toLowerCase();
|
||||
const confirmedEmail = toVisibleEmail(user.email).trim().toLowerCase();
|
||||
const pendingEmail = String(user.pending_email || '').trim().toLowerCase();
|
||||
const emailChanged = email !== confirmedEmail && email !== pendingEmail;
|
||||
const pendingEmailCleared = Boolean(pendingEmail && email === confirmedEmail);
|
||||
const settings = emailChanged ? await fetchAppSettings(pool) : null;
|
||||
if (emailChanged && email && (!settings['email.smtp_enabled'] || typeof sendAccountEmail !== 'function')) return res.status(400).send('Email delivery is not configured.');
|
||||
if (emailChanged && email && !settings['email.smtp_enabled']) return res.status(400).send('Email delivery is not configured.');
|
||||
|
||||
const actorId = getAuditUserId(req);
|
||||
await pool.query('UPDATE a_users SET username = ?, name = ?, modified_by = ? WHERE id = ?', [username, name, actorId, user.id]);
|
||||
if (emailChanged || pendingEmailCleared) {
|
||||
if (!email) {
|
||||
await pool.query('UPDATE a_users SET email = NULL, email_verified_at = NULL, pending_email = NULL, pending_email_token_hash = NULL, pending_email_expires_at = NULL WHERE id = ?', [user.id]);
|
||||
await pool.query('UPDATE a_users SET email = ?, email_verified = 0, pending_email = NULL WHERE id = ?', [createSyntheticAuthEmail(user.id), user.id]);
|
||||
} else if (pendingEmailCleared) {
|
||||
await pool.query('UPDATE a_users SET pending_email = NULL, pending_email_token_hash = NULL, pending_email_expires_at = NULL WHERE id = ?', [user.id]);
|
||||
await pool.query('UPDATE a_users SET pending_email = NULL WHERE id = ?', [user.id]);
|
||||
} else {
|
||||
const token = createOneTimeToken();
|
||||
const expiryMinutes = Number(settings['email.verification_expiry_minutes']);
|
||||
await pool.query('UPDATE a_users SET pending_email = ?, pending_email_token_hash = ?, pending_email_expires_at = DATE_ADD(NOW(), INTERVAL ' + expiryMinutes + ' MINUTE) WHERE id = ?', [email, hashSessionToken(token), user.id]);
|
||||
const url = getRequestOrigin(req) + '/verify-email?token=' + encodeURIComponent(token);
|
||||
await sendAccountEmail(settings, Object.assign({ to: email }, renderAccountEmailTemplate(settings['email.verification_subject'], settings['email.verification_body'], { url: url, username: req.currentUser.username, display_name: req.currentUser.name, email: email, expiry_time: formatAccountEmailExpiry(expiryMinutes, 'minutes'), action_alignment: settings['email.verification_button_alignment'], action_label: settings['email.verification_button_text'] })));
|
||||
await pool.query('UPDATE a_users SET pending_email = ? WHERE id = ?', [email, user.id]);
|
||||
try {
|
||||
await betterAuth.api.requestEmailChangeEmailOTP({ body: { newEmail: email }, headers: fromNodeHeaders(req.headers) });
|
||||
} catch (error) {
|
||||
await pool.query('UPDATE a_users SET pending_email = NULL WHERE id = ?', [user.id]);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (emailChanged && typeof recordRequestAuditEvent === 'function') await recordRequestAuditEvent(pool, req, { category: 'users', eventType: email ? 'user.email_change_requested' : 'user.email_cleared', actorUserId: user.id, targetType: 'user', targetId: user.id, targetLabel: email || user.email, details: { previousEmail: confirmedEmail || null, email: email || null, verificationRequired: Boolean(email) } });
|
||||
res.redirect('/account?message=' + encodeURIComponent(emailChanged && email ? 'Account details updated. Check your new email address for a verification link.' : 'Account details updated.'));
|
||||
res.redirect('/account?message=' + encodeURIComponent(emailChanged && email ? 'Account details updated. A verification code was sent to your new email address.' : 'Account details updated.'));
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
@@ -184,13 +248,13 @@ module.exports = function registerAccountRoutes(app, deps) {
|
||||
return res.redirect('/account?message=' + encodeURIComponent('Signing out other sessions is disabled by an administrator.'));
|
||||
}
|
||||
const cookies = parseCookies(req.headers.cookie || '');
|
||||
const token = cookies[sessionCookieName];
|
||||
const token = req.betterAuthSession && req.betterAuthSession.session && req.betterAuthSession.session.token || cookies[sessionCookieName];
|
||||
if (!token) {
|
||||
return res.redirect('/account?message=' + encodeURIComponent('Current session could not be identified.'));
|
||||
}
|
||||
const [result] = await pool.query(
|
||||
'DELETE FROM a_sessions WHERE id = ? AND user_id = ? AND session_hash <> ?',
|
||||
[sessionId, req.currentUser.id, hashSessionToken(token)]
|
||||
[sessionId, req.currentUser.id, token]
|
||||
);
|
||||
if (result && result.affectedRows && typeof recordRequestAuditEvent === 'function') {
|
||||
await recordRequestAuditEvent(pool, req, {
|
||||
@@ -216,9 +280,9 @@ module.exports = function registerAccountRoutes(app, deps) {
|
||||
return res.redirect('/account?message=' + encodeURIComponent('Signing out other sessions is disabled by an administrator.'));
|
||||
}
|
||||
const cookies = parseCookies(req.headers.cookie || '');
|
||||
const token = cookies[sessionCookieName];
|
||||
const token = req.betterAuthSession && req.betterAuthSession.session && req.betterAuthSession.session.token || cookies[sessionCookieName];
|
||||
if (token) {
|
||||
await pool.query('DELETE FROM a_sessions WHERE user_id = ? AND session_hash <> ?', [req.currentUser.id, hashSessionToken(token)]);
|
||||
await pool.query('DELETE FROM a_sessions WHERE user_id = ? AND session_hash <> ?', [req.currentUser.id, token]);
|
||||
}
|
||||
if (typeof recordRequestAuditEvent === 'function') {
|
||||
await recordRequestAuditEvent(pool, req, {
|
||||
@@ -266,10 +330,10 @@ module.exports = function registerAccountRoutes(app, deps) {
|
||||
const currentPassword = String(req.body.current_password || '');
|
||||
if (!username) return res.status(400).send('Username is required.');
|
||||
if (!currentPassword) return res.status(400).send('Current password is required.');
|
||||
const [rows] = await pool.query('SELECT id, username, password_hash, password_salt, password_iterations FROM a_users WHERE id = ? LIMIT 1', [req.currentUser.id]);
|
||||
const [rows] = await pool.query('SELECT id, username FROM a_users WHERE id = ? LIMIT 1', [req.currentUser.id]);
|
||||
const user = rows[0] || null;
|
||||
if (!user) return res.status(404).send('User not found.');
|
||||
if (!verifyPassword(currentPassword, user)) return res.status(400).send('Current password is incorrect.');
|
||||
if (!(await verifyCurrentPassword(req, currentPassword))) return res.status(400).send('Current password is incorrect.');
|
||||
const [existingRows] = await pool.query('SELECT id FROM a_users WHERE username = ? AND id <> ? LIMIT 1', [username, user.id]);
|
||||
if (existingRows.length) return res.status(400).send('That username already exists.');
|
||||
await pool.query('UPDATE a_users SET username = ?, modified_by = ? WHERE id = ?', [username, getAuditUserId(req), user.id]);
|
||||
@@ -285,13 +349,7 @@ module.exports = function registerAccountRoutes(app, deps) {
|
||||
const currentPassword = String(req.body.current_password || '');
|
||||
const newPassword = String(req.body.new_password || '');
|
||||
const confirmPassword = String(req.body.confirm_password || '');
|
||||
|
||||
const [rows] = await pool.query('SELECT id, name, username, password_hash, password_salt, password_iterations FROM a_users WHERE id = ? LIMIT 1', [req.currentUser.id]);
|
||||
const user = rows[0] || null;
|
||||
if (!user) {
|
||||
return res.status(404).send('User not found.');
|
||||
}
|
||||
if (!verifyPassword(currentPassword, user)) {
|
||||
if (!currentPassword) {
|
||||
return sendPasswordError(req, res, 'Current password is incorrect.');
|
||||
}
|
||||
const passwordStrengthMessage = validatePasswordStrength(newPassword, await getPasswordRequirements());
|
||||
@@ -302,28 +360,30 @@ module.exports = function registerAccountRoutes(app, deps) {
|
||||
return sendPasswordError(req, res, 'New passwords do not match.');
|
||||
}
|
||||
|
||||
const passwordRecord = hashPassword(newPassword);
|
||||
await pool.query(
|
||||
'UPDATE a_users SET password_hash = ?, password_salt = ?, password_iterations = ?, must_change_password = 0, modified_by = ? WHERE id = ?',
|
||||
[passwordRecord.hash, passwordRecord.salt, passwordRecord.iterations, getAuditUserId(req), user.id]
|
||||
);
|
||||
await pool.query('DELETE FROM a_sessions WHERE user_id = ?', [user.id]);
|
||||
try {
|
||||
await betterAuth.api.changePassword({
|
||||
body: { currentPassword: currentPassword, newPassword: newPassword, revokeOtherSessions: true },
|
||||
headers: fromNodeHeaders(req.headers)
|
||||
});
|
||||
} catch (_error) {
|
||||
return sendPasswordError(req, res, 'Current password is incorrect.');
|
||||
}
|
||||
await pool.query('UPDATE a_users SET must_change_password = 0, modified_by = ? WHERE id = ?', [getAuditUserId(req), req.currentUser.id]);
|
||||
await pool.query('DELETE FROM a_sessions WHERE user_id = ?', [req.currentUser.id]);
|
||||
if (typeof recordRequestAuditEvent === 'function') {
|
||||
await recordRequestAuditEvent(pool, req, {
|
||||
category: 'security',
|
||||
eventType: 'password.changed',
|
||||
actorUserId: user.id,
|
||||
actorUserId: req.currentUser.id,
|
||||
targetType: 'user',
|
||||
targetId: user.id,
|
||||
targetLabel: user.username,
|
||||
targetId: req.currentUser.id,
|
||||
targetLabel: req.currentUser.username,
|
||||
details: { source: 'account' }
|
||||
});
|
||||
}
|
||||
|
||||
const sessionMaxAgeMs = typeof getSessionMaxAgeMs === 'function' ? await getSessionMaxAgeMs() : undefined;
|
||||
const token = await createUserSession(pool, user.id, sessionMaxAgeMs, getSessionMetadata(req));
|
||||
setSessionCookie(res, token, sessionMaxAgeMs);
|
||||
res.redirect('/account?message=' + encodeURIComponent('Password updated.'));
|
||||
if (typeof clearSessionCookie === 'function') clearSessionCookie(res);
|
||||
res.redirect('/login?message=' + encodeURIComponent('Password updated. Sign in again.'));
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user