xpeditis2.0/apps/backend/src/application/auth/auth-session.spec.ts
2026-09-14 11:19:29 +02:00

70 lines
3.0 KiB
TypeScript

import { ConfigService } from '@nestjs/config';
import { JwtService } from '@nestjs/jwt';
import { Repository } from 'typeorm';
import { AuthService, JwtPayload } from './auth.service';
import { User, UserRole } from '@domain/entities/user.entity';
import { UserRepository } from '@domain/ports/out/user.repository';
import { OrganizationRepository } from '@domain/ports/out/organization.repository';
import { EmailPort } from '@domain/ports/out/email.port';
import { CachePort } from '@domain/ports/out/cache.port';
import { PasswordResetTokenOrmEntity } from '@infrastructure/persistence/typeorm/entities/password-reset-token.orm-entity';
import { SubscriptionService } from '../services/subscription.service';
jest.mock('argon2', () => ({ verify: jest.fn().mockResolvedValue(true) }));
describe('password-bound sessions', () => {
let user: User;
let auth: AuthService;
let jwt: JwtService;
beforeEach(() => {
user = User.create({
id: 'user-1',
organizationId: 'org-1',
email: 'test@example.org',
firstName: 'Test',
lastName: 'User',
role: UserRole.ADMIN,
passwordHash: 'old-salted-hash',
});
jwt = new JwtService({ secret: 'test-only-session-secret' });
auth = new AuthService(
{
findById: jest.fn(async () => user),
findByEmail: jest.fn(async () => user),
} as unknown as UserRepository,
{} as OrganizationRepository,
{} as EmailPort,
{ get: jest.fn(async () => null) } as unknown as CachePort,
{} as Repository<PasswordResetTokenOrmEntity>,
jwt,
new ConfigService({ JWT_SECRET: 'test-only-session-secret' }),
{} as SubscriptionService
);
});
it('rejects old access and refresh tokens after a password change, but accepts a new login', async () => {
const tokens = await auth.login(user.email, 'password');
const payload = jwt.verify<JwtPayload>(tokens.accessToken);
expect(await auth.validateUser(payload)).toBe(user);
expect(payload.credentialVersion).not.toContain(user.passwordHash);
user.updatePassword('new-salted-hash');
expect(await auth.validateUser(payload)).toBeNull();
await expect(auth.refreshAccessToken(tokens.refreshToken)).rejects.toThrow();
const fresh = await auth.login(user.email, 'new-password');
expect(await auth.validateUser(jwt.verify<JwtPayload>(fresh.accessToken))).toBe(user);
await expect(auth.refreshAccessToken(fresh.refreshToken)).resolves.toHaveProperty(
'accessToken'
);
});
it('preserves sessions after a profile change and rejects legacy or disabled sessions', async () => {
const tokens = await auth.login(user.email, 'password');
const payload = jwt.verify<JwtPayload>(tokens.accessToken);
user.updateFirstName('New name');
expect(await auth.validateUser(payload)).toBe(user);
expect(await auth.validateUser({ ...payload, credentialVersion: undefined })).toBeNull();
user.deactivate();
expect(await auth.validateUser(payload)).toBeNull();
});
});