improve security: back port, base port, login guard, scripts
This commit is contained in:
@@ -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;
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user