improve security: back port, base port, login guard, scripts

This commit is contained in:
iqubik
2026-04-03 02:23:05 +03:00
parent 41265ccf37
commit e23de664ac
26 changed files with 1172 additions and 221 deletions
+156 -4
View File
@@ -1,8 +1,10 @@
/* eslint-disable @typescript-eslint/unbound-method */
import { Test, TestingModule } from '@nestjs/testing';
import { HttpException } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { AuthController } from 'src/auth/auth.controller';
import { AuthService } from 'src/auth/auth.service';
import type { Request, Response } from 'express';
describe('AuthController', () => {
let controller: AuthController;
@@ -17,6 +19,7 @@ describe('AuthController', () => {
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [ConfigModule.forRoot()],
controllers: [AuthController],
providers: [
{
@@ -35,6 +38,18 @@ describe('AuthController', () => {
});
describe('login', () => {
const mockResponse = {
cookie: jest
.fn<ReturnType<Response['cookie']>, Parameters<Response['cookie']>>()
.mockReturnThis(),
clearCookie: jest
.fn<
ReturnType<Response['clearCookie']>,
Parameters<Response['clearCookie']>
>()
.mockReturnThis(),
} satisfies Pick<Response, 'cookie' | 'clearCookie'>;
it('должен вернуть access_token при успешной аутентификации', async () => {
const loginDto = { login: 'admin', password: 'password' };
const mockUser = { login: 'admin' };
@@ -43,7 +58,10 @@ describe('AuthController', () => {
mockAuthService.validateUser.mockResolvedValue(mockUser);
mockAuthService.login.mockReturnValue(mockToken);
const result = await controller.login(loginDto);
const result = await controller.login(
loginDto,
mockResponse as unknown as Response,
);
expect(result).toEqual(mockToken);
expect(authService.validateUser).toHaveBeenCalledWith(
@@ -51,6 +69,16 @@ describe('AuthController', () => {
'password',
);
expect(authService.login).toHaveBeenCalledWith(mockUser);
expect(mockResponse.cookie).toHaveBeenCalledWith(
'access_token',
'jwt-token',
expect.objectContaining({
httpOnly: true,
sameSite: 'lax',
maxAge: 86400000,
path: '/',
}),
);
});
it('должен бросить HttpException при неверных учётных данных', async () => {
@@ -58,11 +86,135 @@ describe('AuthController', () => {
mockAuthService.validateUser.mockResolvedValue(null);
await expect(controller.login(loginDto)).rejects.toThrow(HttpException);
await expect(
controller.login(loginDto, mockResponse as unknown as Response),
).rejects.toThrow(HttpException);
await expect(controller.login(loginDto)).rejects.toThrow(
'Неверный логин или пароль',
await expect(
controller.login(loginDto, mockResponse as unknown as Response),
).rejects.toThrow('Неверный логин или пароль');
expect(mockResponse.cookie).not.toHaveBeenCalled();
});
it('должен установить secure=true cookie в production режиме', async () => {
process.env.NODE_ENV = 'production';
const module: TestingModule = await Test.createTestingModule({
imports: [ConfigModule.forRoot()],
controllers: [AuthController],
providers: [
{
provide: AuthService,
useValue: mockAuthService,
},
],
}).compile();
const prodController = module.get<AuthController>(AuthController);
const loginDto = { login: 'admin', password: 'password' };
const mockUser = { login: 'admin' };
const mockToken = { access_token: 'jwt-token' };
mockAuthService.validateUser.mockResolvedValue(mockUser);
mockAuthService.login.mockReturnValue(mockToken);
await prodController.login(loginDto, mockResponse as unknown as Response);
expect(mockResponse.cookie).toHaveBeenCalledWith(
'access_token',
'jwt-token',
expect.objectContaining({
secure: true,
}),
);
delete process.env.NODE_ENV;
});
});
describe('logout', () => {
const mockResponse = {
cookie: jest
.fn<ReturnType<Response['cookie']>, Parameters<Response['cookie']>>()
.mockReturnThis(),
clearCookie: jest
.fn<
ReturnType<Response['clearCookie']>,
Parameters<Response['clearCookie']>
>()
.mockReturnThis(),
} satisfies Pick<Response, 'cookie' | 'clearCookie'>;
it('должен очистить access_token cookie и вернуть success', () => {
const mockRequest = {
cookies: { access_token: 'some-token' },
} as unknown as Request;
const result = controller.logout(
mockRequest,
mockResponse as unknown as Response,
);
expect(result).toEqual({ success: true });
expect(mockResponse.clearCookie).toHaveBeenCalledWith(
'access_token',
expect.objectContaining({
httpOnly: true,
sameSite: 'lax',
path: '/',
}),
);
});
it('должен очистить cookie даже если cookie не было в запросе', () => {
const mockRequest = {
cookies: {},
} as unknown as Request;
const result = controller.logout(
mockRequest,
mockResponse as unknown as Response,
);
expect(result).toEqual({ success: true });
expect(mockResponse.clearCookie).toHaveBeenCalledWith(
'access_token',
expect.objectContaining({
httpOnly: true,
sameSite: 'lax',
path: '/',
}),
);
});
it('должен установить secure=true для clearCookie в production режиме', async () => {
process.env.NODE_ENV = 'production';
const module: TestingModule = await Test.createTestingModule({
imports: [ConfigModule.forRoot()],
controllers: [AuthController],
providers: [
{
provide: AuthService,
useValue: mockAuthService,
},
],
}).compile();
const prodController = module.get<AuthController>(AuthController);
const mockRequest = {
cookies: { access_token: 'some-token' },
} as unknown as Request;
prodController.logout(mockRequest, mockResponse as unknown as Response);
expect(mockResponse.clearCookie).toHaveBeenCalledWith(
'access_token',
expect.objectContaining({
secure: true,
}),
);
delete process.env.NODE_ENV;
});
});