mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-25 17:57:06 +00:00
fix(rbac): enforce admin seat cap on promotion and harden last-admin and audit paths (#1266)
Promoting a user to admin now respects the per-tier admin seat limit the same way user creation does, closing a path that let an operator exceed the cap by creating an account and then editing its role to admin. The last-admin guard for demote and delete now runs the admin-count re-check and the write in a single transaction, so two concurrent admin changes can no longer race the admin count to zero and lock everyone out. Admin two-factor resets are now recorded once with their own audit summary instead of being mislabeled as a user creation by the audit middleware.
This commit is contained in:
@@ -555,6 +555,21 @@ describe('POST /api/users/:id/mfa/reset', () => {
|
||||
expect(db.getUserMfa(userId)).toBeUndefined();
|
||||
expect(db.getUser(userId)!.token_version).toBeGreaterThan(before);
|
||||
});
|
||||
|
||||
it('writes exactly one audit row, labeled as a reset and never as user creation', async () => {
|
||||
const db = DatabaseService.getInstance();
|
||||
const { userId } = await seedMfaUser('victim3', 'victim3pass123');
|
||||
const res = await request(app)
|
||||
.post(`/api/users/${userId}/mfa/reset`)
|
||||
.set('Authorization', `Bearer ${adminToken()}`);
|
||||
expect(res.status).toBe(200);
|
||||
|
||||
// The route no longer writes its own audit row; the middleware writes one.
|
||||
const { entries } = db.getAuditLogs({ search: `/users/${userId}/mfa/reset` });
|
||||
const resetRows = entries.filter((e) => e.path === `/api/users/${userId}/mfa/reset`);
|
||||
expect(resetRows).toHaveLength(1);
|
||||
expect(resetRows[0].summary).toBe(`Reset two-factor authentication: ${userId}`);
|
||||
});
|
||||
});
|
||||
|
||||
// ─── SSO bypass toggle ────────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user