89 lines
3.2 KiB
TypeScript
89 lines
3.2 KiB
TypeScript
import { ForbiddenException, Logger } from '@nestjs/common';
|
|
import { User, UserRole } from '@domain/entities/user.entity';
|
|
import { UserRepository } from '@domain/ports/out/user.repository';
|
|
import { UsersController } from './users.controller';
|
|
import { SubscriptionService } from '../services/subscription.service';
|
|
import { UserPayload } from '../decorators/current-user.decorator';
|
|
import { UserRole as DtoUserRole } from '../dto/user.dto';
|
|
|
|
describe('administrator target protection', () => {
|
|
it('does not log a temporary password when creating an account', async () => {
|
|
const log = jest.spyOn(Logger.prototype, 'log').mockImplementation(() => undefined);
|
|
const warn = jest.spyOn(Logger.prototype, 'warn').mockImplementation(() => undefined);
|
|
try {
|
|
const controller = new UsersController(
|
|
{
|
|
findByEmail: async () => null,
|
|
save: async (user: User) => user,
|
|
} as unknown as UserRepository,
|
|
{} as SubscriptionService,
|
|
{} as never,
|
|
{ assertKeepsAnActiveAdmin: jest.fn() } as never,
|
|
{ sendUserInvitation: jest.fn().mockResolvedValue(undefined) } as never,
|
|
{ findById: jest.fn().mockResolvedValue(null) } as never
|
|
);
|
|
await controller.createUser(
|
|
{
|
|
email: 'new@example.org',
|
|
firstName: 'New',
|
|
lastName: 'User',
|
|
organizationId: 'org-1',
|
|
role: DtoUserRole.USER,
|
|
password: 'test-only-Temporary-password-42',
|
|
},
|
|
{
|
|
id: 'admin',
|
|
email: 'admin@example.org',
|
|
role: 'ADMIN',
|
|
organizationId: 'org-1',
|
|
firstName: 'A',
|
|
lastName: 'B',
|
|
}
|
|
);
|
|
expect(JSON.stringify([...log.mock.calls, ...warn.mock.calls])).not.toContain(
|
|
'test-only-Temporary-password-42'
|
|
);
|
|
} finally {
|
|
log.mockRestore();
|
|
warn.mockRestore();
|
|
}
|
|
});
|
|
const actor: UserPayload = {
|
|
id: 'manager',
|
|
role: 'MANAGER',
|
|
organizationId: 'org-1',
|
|
email: 'manager@example.org',
|
|
firstName: 'Test',
|
|
lastName: 'Manager',
|
|
};
|
|
it.each([UserRole.ADMIN, UserRole.USER])('enforces target hierarchy for %s', async role => {
|
|
const user = User.create({
|
|
id: 'target',
|
|
role,
|
|
organizationId: actor.organizationId,
|
|
email: 'target@example.org',
|
|
firstName: 'Original',
|
|
lastName: 'User',
|
|
passwordHash: 'test-hash',
|
|
});
|
|
const save = jest.fn(async () => user);
|
|
const controller = new UsersController(
|
|
{ findById: jest.fn(async () => user), save } as unknown as UserRepository,
|
|
{} as SubscriptionService,
|
|
{} as never,
|
|
{ assertKeepsAnActiveAdmin: jest.fn() } as never,
|
|
{ sendUserInvitation: jest.fn().mockResolvedValue(undefined) } as never,
|
|
{ findById: jest.fn().mockResolvedValue(null) } as never
|
|
);
|
|
const result = controller.updateUser(user.id, { firstName: 'Changed' }, actor);
|
|
if (role === UserRole.ADMIN) {
|
|
await expect(result).rejects.toBeInstanceOf(ForbiddenException);
|
|
expect(save).not.toHaveBeenCalled();
|
|
expect(user.firstName).toBe('Original');
|
|
} else {
|
|
await expect(result).resolves.toHaveProperty('firstName', 'Changed');
|
|
expect(save).toHaveBeenCalled();
|
|
}
|
|
});
|
|
});
|