feat: harden sessions and track downstream acknowledgements

This commit is contained in:
hectorzhao
2026-07-14 14:18:43 +08:00
parent 3d37adcc9f
commit 8c03663f24
43 changed files with 1733 additions and 150 deletions
+17 -4
View File
@@ -25,6 +25,17 @@ function createUsersMock(roleCode: string, overrides: Record<string, unknown> =
};
}
function createSessionsMock() {
const record = {
userId: 'user-1', portal: 'admin', sessionVersion: 0, createdAt: 1, lastActivityAt: 1,
lastAuthenticatedAt: 1, absoluteExpiresAt: Date.now() + 1000,
};
return {
create: jest.fn().mockResolvedValue({ token: 'opaque-session-token', record }),
publicSession: jest.fn().mockReturnValue({ idleTimeoutSeconds: 3600, absoluteExpiresAt: new Date(record.absoluteExpiresAt).toISOString() }),
};
}
async function loginWithCaptcha(service: AuthService, portal: 'admin' | 'client', password = 'secret1') {
const captcha = service.createCaptcha();
const answer = captcha.challenge.split('=')[0].split('+').map((part) => Number(part.trim())).reduce((sum, value) => sum + value, 0);
@@ -39,21 +50,23 @@ async function loginWithCaptcha(service: AuthService, portal: 'admin' | 'client'
describe('AuthService', () => {
it('allows platform admins to login admin portal', async () => {
const users = createUsersMock('platform_admin');
const service = new AuthService(users as never);
await expect(loginWithCaptcha(service, 'admin')).resolves.toEqual(expect.objectContaining({ portal: 'admin', accessToken: 'dev-token:user-1:0' }));
const sessions = createSessionsMock();
const service = new AuthService(users as never, sessions as never);
await expect(loginWithCaptcha(service, 'admin')).resolves.toEqual(expect.objectContaining({ portal: 'admin', sessionToken: 'opaque-session-token' }));
expect(users.recordLoginSuccess).toHaveBeenCalledWith('user-1');
expect(sessions.create).toHaveBeenCalledWith('user-1', 'admin', 0);
});
it('rejects enterprise admins on admin portal', async () => {
const users = createUsersMock('enterprise_admin');
const service = new AuthService(users as never);
const service = new AuthService(users as never, createSessionsMock() as never);
await expect(loginWithCaptcha(service, 'admin')).rejects.toBeInstanceOf(UnauthorizedException);
expect(users.recordLoginFailure).toHaveBeenCalledWith('user-1');
});
it('locks user after five failed password attempts', async () => {
const users = createUsersMock('platform_admin');
const service = new AuthService(users as never);
const service = new AuthService(users as never, createSessionsMock() as never);
for (let index = 0; index < 5; index += 1) {
await expect(loginWithCaptcha(service, 'admin', 'bad-password')).rejects.toBeInstanceOf(UnauthorizedException);
}