Release v2.20.1: migrate authentication to Better Auth

This commit is contained in:
lzstealth committed 2026-10-25 18:19:55 +00:00
1 parent e9a53f577c
commit fba4f99ff7
92 files changed
+5085 -1943

No files matched your search

+128 -5
View File
@@ -8,6 +8,8 @@ const { validatePasswordStrength } = require('../src/auth');
test('user password reset redirects back to the selected user edit page', async () => {
const handlers = {};
let savedMustChangePassword = null;
let credentialPassword = null;
const app = {
post(path, ...routeHandlers) {
handlers[path] = routeHandlers;
@@ -17,11 +19,19 @@ test('user password reset redirects back to the selected user edit page', async
const deps = {
pool: {
async query(sql) {
if (sql.includes('SELECT id FROM a_users WHERE id = ? LIMIT 1')) {
return [[{ id: 7 }]];
async query(sql, params) {
if (sql.includes('SELECT id, username, must_change_password FROM a_users WHERE id = ? LIMIT 1')) {
return [[{ id: 7, username: 'user', must_change_password: 0 }]];
}
if (sql.includes('UPDATE a_users SET password_hash = ?, password_salt = ?, password_iterations = ?, modified_by = ? WHERE id = ?')) {
if (sql.includes('SELECT id, created_at, modified_at FROM a_users')) {
return [[{ id: 7, created_at: new Date(), modified_at: new Date() }]];
}
if (sql.includes('INSERT INTO a_accounts')) {
credentialPassword = params[3];
return [{ affectedRows: 1 }];
}
if (sql.includes('UPDATE a_users SET must_change_password = ?, modified_by = ? WHERE id = ?')) {
savedMustChangePassword = params[0];
return [{ affectedRows: 1 }];
}
if (sql.includes('DELETE FROM a_sessions WHERE user_id = ?')) {
@@ -59,7 +69,7 @@ test('user password reset redirects back to the selected user edit page', async
const req = {
params: { id: '7' },
body: { password: 'NewPassword!1', confirm_password: 'NewPassword!1' },
body: { password: 'NewPassword!1', confirm_password: 'NewPassword!1', must_change_password: '1' },
currentUser: { id: 1 }
};
const res = {
@@ -77,4 +87,117 @@ test('user password reset redirects back to the selected user edit page', async
await routeHandlers[1](req, res, () => {});
assert.equal(res.redirectedTo, '/settings/users/7/edit?message=Password%20updated.');
assert.equal(savedMustChangePassword, 1);
assert.match(credentialPassword, /^[a-f0-9]{32}:[a-f0-9]{128}$/);
});
test('admin unlinks another user two-factor data and revokes their sessions', async () => {
const handlers = {};
const queries = [];
let committed = false;
let auditedEvent = null;
const app = {
post(path, ...routeHandlers) { handlers[path] = routeHandlers; },
get() {}
};
const connection = {
async beginTransaction() {},
async query(sql, params) {
const text = String(sql);
queries.push([text, params]);
if (text.includes('SELECT id FROM a_users')) return [[{ id: 7 }]];
if (text.includes('SELECT id FROM a_user_two_factors')) return [[{ id: 'factor-7' }]];
return [{ affectedRows: 1 }];
},
async commit() { committed = true; },
async rollback() {},
release() {}
};
const deps = {
pool: {
async query() { return [[]]; },
async getConnection() { return connection; }
},
common: {},
pages: {},
formatDashboardDate: () => '',
getAuditUserId: () => 3,
recordRequestAuditEvent: async (_pool, _req, event) => { auditedEvent = event; },
validatePasswordStrength: () => '',
readArrayField: () => [],
rbacData: {
fetchRoles: async () => [],
fetchUsersWithRolesPage: async () => ({ users: [], totalItems: 0, currentPage: 1 }),
fetchUserWithRoles: async () => null,
fetchActiveUserSessions: async () => [],
syncUserRoles: async () => {}
},
requirePermission() { return (_req, _res, next) => next(); }
};
registerUsersRoutes(app, deps);
const response = { redirect(url) { this.redirectedTo = url; }, status() { return this; }, send(value) { this.body = value; } };
await handlers['/settings/users/:id/two-factor/unlink'][1]({ params: { id: '7' }, currentUser: { id: 3 } }, response, error => { throw error; });
assert.equal(committed, true);
assert.ok(queries.some(([sql, params]) => sql.includes('DELETE FROM a_user_two_factors WHERE user_id = ?') && params[0] === 7));
assert.ok(queries.some(([sql, params]) => sql.includes('UPDATE a_users SET two_factor_enabled = 0') && params[1] === 7));
assert.ok(queries.some(([sql, params]) => sql.includes('DELETE FROM a_sessions WHERE user_id = ?') && params[0] === 7));
assert.equal(auditedEvent.eventType, 'user.two_factor_unlinked');
assert.equal(response.redirectedTo, '/settings/users/7/edit?message=Two-step%20verification%20removed.%20The%20user%20must%20sign%20in%20again.');
});
test('admin unlinks a passkey only from the selected user and revokes their sessions', async () => {
const handlers = {};
const queries = [];
let committed = false;
let auditedEvent = null;
const app = {
post(path, ...routeHandlers) { handlers[path] = routeHandlers; },
get() {}
};
const connection = {
async beginTransaction() {},
async query(sql, params) {
const text = String(sql);
queries.push([text, params]);
if (text.includes('SELECT id FROM a_users')) return [[{ id: 7 }]];
if (text.includes('FROM a_user_passkeys')) return [[{ id: 'passkey-7' }]];
return [{ affectedRows: 1 }];
},
async commit() { committed = true; },
async rollback() {},
release() {}
};
const deps = {
pool: {
async query() { return [[]]; },
async getConnection() { return connection; }
},
common: {},
pages: {},
formatDashboardDate: () => '',
getAuditUserId: () => 3,
recordRequestAuditEvent: async (_pool, _req, event) => { auditedEvent = event; },
validatePasswordStrength: () => '',
readArrayField: () => [],
rbacData: {
fetchRoles: async () => [],
fetchUsersWithRolesPage: async () => ({ users: [], totalItems: 0, currentPage: 1 }),
fetchUserWithRoles: async () => null,
fetchActiveUserSessions: async () => [],
syncUserRoles: async () => {}
},
requirePermission() { return (_req, _res, next) => next(); }
};
registerUsersRoutes(app, deps);
const response = { redirect(url) { this.redirectedTo = url; }, status() { return this; }, send(value) { this.body = value; } };
await handlers['/settings/users/:id/passkeys/:passkeyId/unlink'][1]({ params: { id: '7', passkeyId: 'passkey-7' }, currentUser: { id: 3 } }, response, error => { throw error; });
assert.equal(committed, true);
assert.ok(queries.some(([sql, params]) => sql.includes('DELETE FROM a_user_passkeys WHERE id = ? AND user_id = ?') && params[0] === 'passkey-7' && params[1] === 7));
assert.ok(queries.some(([sql, params]) => sql.includes('DELETE FROM a_sessions WHERE user_id = ?') && params[0] === 7));
assert.equal(auditedEvent.eventType, 'user.passkey_unlinked');
assert.equal(response.redirectedTo, '/settings/users/7/edit?message=Passkey%20removed.%20The%20user%20must%20sign%20in%20again.');
});