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
@@ -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.');
|
||||
});
|
||||
Reference in new issue
Block a user