fix: harden tenant auth and quality gates

This commit is contained in:
hectorzhao
2026-08-28 11:44:16 +08:00
parent c3bf8af3e6
commit 2744690f9f
51 changed files with 1750 additions and 466 deletions
+11 -3
View File
@@ -1,6 +1,6 @@
import { UnauthorizedException } from '@nestjs/common';
import { AuthService } from './auth.service';
import { hashPassword } from '../users/users.service';
import { legacyHashPassword } from './password-hasher';
function createUsersMock(roleCode: string, overrides: Record<string, unknown> = {}) {
const user = {
@@ -10,7 +10,7 @@ function createUsersMock(roleCode: string, overrides: Record<string, unknown> =
email: 'user@example.com',
phone: '13800000000',
displayName: '用户',
passwordHash: hashPassword('secret1'),
passwordHash: legacyHashPassword('secret1'),
status: 'active',
deletedAt: null,
lockedUntil: null,
@@ -20,24 +20,32 @@ function createUsersMock(roleCode: string, overrides: Record<string, unknown> =
};
return {
findByLogin: jest.fn().mockResolvedValue(user),
verifyLoginPassword: jest.fn(async (_id: string, password: string) => password === 'secret1'),
recordLoginSuccess: jest.fn(),
recordLoginFailure: jest.fn(),
};
}
function createSessionsMock() {
const captchas = new Map<string, string>();
const failures = new Map<string, number>();
const record = {
userId: 'user-1', portal: 'admin', sessionVersion: 0, createdAt: 1, lastActivityAt: 1,
lastAuthenticatedAt: 1, absoluteExpiresAt: Date.now() + 1000,
};
return {
storeCaptcha: jest.fn(async (id: string, answer: string) => { captchas.set(id, answer); }),
consumeCaptcha: jest.fn(async (id: string) => { const answer = captchas.get(id) ?? null; captchas.delete(id); return answer; }),
isAnonymousLoginLocked: jest.fn(async (login: string) => (failures.get(login) ?? 0) >= 5),
recordAnonymousLoginFailure: jest.fn(async (login: string) => { const count = (failures.get(login) ?? 0) + 1; failures.set(login, count); return count; }),
clearAnonymousLoginFailures: jest.fn(async (login: string) => { failures.delete(login); }),
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 captcha = await service.createCaptcha();
const answer = captcha.challenge.split('=')[0].split('+').map((part) => Number(part.trim())).reduce((sum, value) => sum + value, 0);
return service.login({
login: 'user@example.com',