test: harden business boundary conditions
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import * as bcrypt from 'bcryptjs';
|
||||
import { AuthService } from './auth.service';
|
||||
|
||||
describe('AuthService — super admin identity', () => {
|
||||
describe('AuthService — authentication boundaries', () => {
|
||||
it('marks the preset 超管 role as super admin in the JWT payload', async () => {
|
||||
const userRepo = {
|
||||
findOne: jest.fn().mockResolvedValue({
|
||||
@@ -22,8 +22,29 @@ describe('AuthService — super admin identity', () => {
|
||||
|
||||
await service.login({ username: 'admin', password: 'secret' }, '127.0.0.1');
|
||||
|
||||
expect(jwtService.sign).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ isSuperAdmin: true }),
|
||||
expect(jwtService.sign).toHaveBeenCalledWith(expect.objectContaining({ isSuperAdmin: true }));
|
||||
});
|
||||
it('rejects an archived user even when the password is valid', async () => {
|
||||
const userRepo = {
|
||||
findOne: jest.fn().mockResolvedValue({
|
||||
id: 2,
|
||||
username: 'archived',
|
||||
passwordHash: await bcrypt.hash('secret', 4),
|
||||
isActive: true,
|
||||
isArchived: true,
|
||||
roles: [],
|
||||
}),
|
||||
save: jest.fn(),
|
||||
};
|
||||
const service = new AuthService(
|
||||
userRepo as never,
|
||||
{ sign: jest.fn() } as never,
|
||||
{ getUserPermissions: jest.fn() } as never,
|
||||
);
|
||||
|
||||
await expect(
|
||||
service.login({ username: 'archived', password: 'secret' }, '192.0.2.10'),
|
||||
).rejects.toThrow('账号已失效');
|
||||
expect(userRepo.save).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -38,7 +38,9 @@ export class AuthService {
|
||||
this.recordFailedAttempt(attemptKey);
|
||||
throw new UnauthorizedException('用户名或密码错误');
|
||||
}
|
||||
if (!user.isActive) throw new UnauthorizedException('账号已被禁用,请联系管理员');
|
||||
if (!user.isActive || user.isArchived) {
|
||||
throw new UnauthorizedException('账号已失效,请联系管理员');
|
||||
}
|
||||
const valid = await bcrypt.compare(dto.password, user.passwordHash);
|
||||
if (!valid) {
|
||||
this.recordFailedAttempt(attemptKey);
|
||||
|
||||
@@ -33,6 +33,23 @@ describe('JwtStrategy', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('recognizes the canonical super_admin role code even when the display name changes', async () => {
|
||||
const userRepo = {
|
||||
findOne: jest.fn().mockResolvedValue({
|
||||
id: 1,
|
||||
username: 'admin',
|
||||
isActive: true,
|
||||
isArchived: false,
|
||||
roles: [{ name: '系统管理员', code: 'super_admin', status: 1, permissions: [] }],
|
||||
}),
|
||||
};
|
||||
const strategy = new JwtStrategy(config as never, userRepo as never);
|
||||
|
||||
await expect(strategy.validate({ sub: 1 })).resolves.toEqual(
|
||||
expect.objectContaining({ isSuperAdmin: true }),
|
||||
);
|
||||
});
|
||||
|
||||
it.each([
|
||||
[{ id: 7, isActive: false, isArchived: false, roles: [] }],
|
||||
[{ id: 7, isActive: true, isArchived: true, roles: [] }],
|
||||
|
||||
@@ -47,7 +47,9 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
|
||||
for (const role of user.roles ?? []) {
|
||||
if (role.status !== 1) continue;
|
||||
roles.push(role.name);
|
||||
if (role.name === '超管' || role.name === 'super_admin') isSuperAdmin = true;
|
||||
if (role.name === '超管' || role.name === 'super_admin' || role.code === 'super_admin') {
|
||||
isSuperAdmin = true;
|
||||
}
|
||||
for (const permission of role.permissions ?? []) permissions.add(permission.code);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user