set up an authorization session using tokens and cookies
parent
7d8a207728
commit
2c06038b0e
19
.env
19
.env
|
|
@ -1,9 +1,26 @@
|
|||
#Прометеус
|
||||
#PROMETHEUS_API=http://192.168.2.34:9090/api/v1
|
||||
|
||||
#FRONTEND_URL=192.168.2.39:5173
|
||||
|
||||
#Постгресс
|
||||
#DB_HOST=192.168.2.37
|
||||
#DB_PORT=5432
|
||||
#DB_USER=trust
|
||||
#DB_PASSWORD=kaiqolzp2a4aH
|
||||
#DB_NAME=trust-db
|
||||
#DB_NAME=trust-db
|
||||
|
||||
|
||||
#JWT
|
||||
#JWT_SECRET=x7F!2p9L#q1$z0*8R5vYgMnBk
|
||||
#JWT_SECRET=x7Fcdp9L#q1$z0*8R5vYgMnBk
|
||||
|
||||
#COOKIE
|
||||
# Для production
|
||||
#COOKIE_SECURE=true
|
||||
#COOKIE_SAME_SITE=strict
|
||||
|
||||
# Для development
|
||||
# COOKIE_SECURE=false
|
||||
# COOKIE_SAME_SITE=lax
|
||||
|
||||
|
|
|
|||
|
|
@ -36,7 +36,14 @@
|
|||
"@types/bcrypt": "^5.0.2",
|
||||
"socket.io": "^4.8.1",
|
||||
"@nestjs/websockets": "11.0.12",
|
||||
"@nestjs/platform-socket.io": "11.0.12"
|
||||
"@nestjs/platform-socket.io": "11.0.12",
|
||||
"passport": "^0.7.0",
|
||||
"passport-jwt": "^4.0.1",
|
||||
"cookie-parser": "^1.4.7",
|
||||
"@types/passport-jwt": "^4.0.1",
|
||||
"@types/cookie-parser": "^1.4.8",
|
||||
"@nestjs/jwt": "^11.0.0",
|
||||
"@nestjs/passport": "^11.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3.2.0",
|
||||
|
|
|
|||
|
|
@ -1,16 +1,82 @@
|
|||
import { Controller, Post, Body, UnauthorizedException } from '@nestjs/common';
|
||||
import { Controller, Post, Get, Body, Res, Req, UnauthorizedException, UseGuards } from '@nestjs/common';
|
||||
import { AuthService } from './auth.service';
|
||||
import { Response, Request } from 'express';
|
||||
import { JwtAuthGuard } from './jwt-auth.guard';
|
||||
import { Logger } from '@nestjs/common/services';
|
||||
|
||||
@Controller('auth')
|
||||
export class AuthController {
|
||||
constructor(private authService: AuthService) { }
|
||||
private readonly logger = new Logger(AuthController.name);
|
||||
|
||||
@Post('login')
|
||||
async login(@Body() body: { login: string; password: string }) {
|
||||
const user = await this.authService.validateUser(body.login, body.password);
|
||||
if (!user) {
|
||||
throw new UnauthorizedException('Неверный логин или пароль');
|
||||
}
|
||||
return { success: true, user };
|
||||
}
|
||||
}
|
||||
constructor(private authService: AuthService) { }
|
||||
|
||||
@Get('check')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
async checkAuth(@Req() req: Request) {
|
||||
this.logger.debug(`Check auth request. Cookies: ${JSON.stringify(req.cookies)}`);
|
||||
this.logger.debug(`Check auth request. Headers: ${JSON.stringify(req.headers)}`);
|
||||
|
||||
if (!req.user) {
|
||||
this.logger.warn('Unauthorized access attempt');
|
||||
throw new UnauthorizedException('Пользователь не аутентифицирован');
|
||||
}
|
||||
|
||||
// Явно указываем тип для req.user
|
||||
const user = req.user as { userId: number; username: string; login?: string };
|
||||
const userWithoutPassword = { ...user };
|
||||
|
||||
this.logger.log(`User authenticated: ${user.username}`);
|
||||
return {
|
||||
isAuthenticated: true,
|
||||
user: userWithoutPassword
|
||||
};
|
||||
}
|
||||
|
||||
@Post('login')
|
||||
async login(
|
||||
@Body() body: { login: string; password: string },
|
||||
@Res({ passthrough: true }) res: Response,
|
||||
@Req() req: Request
|
||||
) {
|
||||
this.logger.debug(`Login attempt for user: ${body.login}`);
|
||||
this.logger.debug(`Request cookies: ${JSON.stringify(req.cookies)}`);
|
||||
this.logger.debug(`Request headers: ${JSON.stringify(req.headers)}`);
|
||||
|
||||
const user = await this.authService.validateUser(body.login, body.password);
|
||||
if (!user) {
|
||||
this.logger.warn(`Failed login attempt for user: ${body.login}`);
|
||||
throw new UnauthorizedException('Неверный логин или пароль');
|
||||
}
|
||||
|
||||
const { access_token } = await this.authService.login(user);
|
||||
|
||||
res.cookie('access_token', access_token, {
|
||||
httpOnly: true,
|
||||
secure: process.env.COOKIE_SECURE === 'true',
|
||||
sameSite: (process.env.COOKIE_SAME_SITE as 'strict' | 'lax' | 'none') || 'strict',
|
||||
maxAge: 3600000,
|
||||
path: '/',
|
||||
});
|
||||
|
||||
this.logger.log(`User ${body.login} successfully logged in`);
|
||||
this.logger.debug(`Set cookie: access_token=${access_token.substring(0, 10)}...`);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
user: {
|
||||
id: user.id,
|
||||
login: user.login
|
||||
},
|
||||
access_token
|
||||
};
|
||||
}
|
||||
|
||||
@Post('logout')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
async logout(@Res({ passthrough: true }) res: Response, @Req() req: Request) {
|
||||
const user = req.user as { userId: number; username: string };
|
||||
this.logger.log(`User ${user.username} logging out`);
|
||||
res.clearCookie('access_token');
|
||||
return { success: true };
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
@Injectable()
|
||||
export class AuthGuard implements CanActivate {
|
||||
canActivate(
|
||||
context: ExecutionContext,
|
||||
): boolean | Promise<boolean> | Observable<boolean> {
|
||||
const request = context.switchToHttp().getRequest();
|
||||
return !!request.user; // Проверка что пользователь аутентифицирован
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,27 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { Module, MiddlewareConsumer } from '@nestjs/common'; // Добавлен импорт MiddlewareConsumer
|
||||
import { JwtModule } from '@nestjs/jwt';
|
||||
import { PassportModule } from '@nestjs/passport';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { AuthService } from './auth.service';
|
||||
import { AuthController } from './auth.controller';
|
||||
import { User } from './user.entity';
|
||||
import { JwtStrategy } from './jwt.strategy';
|
||||
import * as cookieParser from 'cookie-parser';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([User])],
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([User]),
|
||||
PassportModule,
|
||||
JwtModule.register({
|
||||
secret: process.env.JWT_SECRET || 'your-secret-key',
|
||||
signOptions: { expiresIn: '1h' },
|
||||
}),
|
||||
],
|
||||
controllers: [AuthController],
|
||||
providers: [AuthService],
|
||||
providers: [AuthService, JwtStrategy],
|
||||
})
|
||||
export class AuthModule { }
|
||||
export class AuthModule {
|
||||
configure(consumer: MiddlewareConsumer) {
|
||||
consumer.apply(cookieParser()).forRoutes('*');
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { User } from './user.entity';
|
||||
|
|
@ -8,20 +9,26 @@ export class AuthService {
|
|||
constructor(
|
||||
@InjectRepository(User)
|
||||
private usersRepository: Repository<User>,
|
||||
private jwtService: JwtService,
|
||||
) { }
|
||||
|
||||
async validateUser(login: string, password: string): Promise<any> {
|
||||
|
||||
// Ищем пользователя по login
|
||||
const user = await this.usersRepository.findOne({ where: { login } });
|
||||
|
||||
// Проверяем, что нашли пользователя и пароль совпадает
|
||||
if (user && user.password === password) {
|
||||
|
||||
const { password, ...result } = user;
|
||||
return result;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async login(user: any) {
|
||||
const payload = {
|
||||
username: user.login, // Используем username в payload
|
||||
sub: user.id
|
||||
};
|
||||
return {
|
||||
access_token: this.jwtService.sign(payload),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
|
||||
import { Observable } from 'rxjs';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
|
||||
@Injectable()
|
||||
export class JwtAuthGuard implements CanActivate {
|
||||
constructor(private jwtService: JwtService) {}
|
||||
|
||||
canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
|
||||
const request = context.switchToHttp().getRequest();
|
||||
try {
|
||||
const token = this.extractToken(request);
|
||||
if (!token) return false;
|
||||
|
||||
const payload = this.jwtService.verify(token);
|
||||
request.user = payload;
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private extractToken(request): string | null {
|
||||
return request.cookies?.access_token ||
|
||||
request.headers.authorization?.split(' ')[1] ||
|
||||
null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { PassportStrategy } from '@nestjs/passport';
|
||||
import { ExtractJwt, Strategy } from 'passport-jwt';
|
||||
import { Request } from 'express';
|
||||
|
||||
@Injectable()
|
||||
export class JwtStrategy extends PassportStrategy(Strategy) {
|
||||
constructor() {
|
||||
super({
|
||||
jwtFromRequest: ExtractJwt.fromExtractors([
|
||||
(request: Request) => {
|
||||
return request?.cookies?.access_token ||
|
||||
request?.headers?.authorization?.split(' ')[1];
|
||||
},
|
||||
]),
|
||||
ignoreExpiration: false,
|
||||
secretOrKey: process.env.JWT_SECRET || 'your-secret-key',
|
||||
});
|
||||
}
|
||||
|
||||
async validate(payload: any) {
|
||||
return {
|
||||
userId: payload.sub,
|
||||
username: payload.username,
|
||||
login: payload.username // Добавляем для совместимости
|
||||
};
|
||||
}
|
||||
}
|
||||
13
src/main.ts
13
src/main.ts
|
|
@ -1,20 +1,23 @@
|
|||
import { NestFactory } from '@nestjs/core';
|
||||
import { AppModule } from './app.module';
|
||||
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
|
||||
// Установка глобального префикса для всех маршрутов
|
||||
app.setGlobalPrefix('api');
|
||||
|
||||
//настройка CORS
|
||||
|
||||
// Настройка CORS
|
||||
app.enableCors({
|
||||
origin: '*',
|
||||
origin: [process.env.FRONTEND_URL, "http://dev.msf.enode"], //|| 'http://192.168.2.39:5173', // Точный URL фронтенда
|
||||
credentials: true,
|
||||
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS',
|
||||
allowedHeaders: 'Content-Type, Authorization',
|
||||
allowedHeaders: 'Content-Type, Authorization, X-Requested-With',
|
||||
exposedHeaders: 'Authorization',
|
||||
preflightContinue: false,
|
||||
optionsSuccessStatus: 204
|
||||
});
|
||||
|
||||
await app.listen(process.env.PORT ?? 3000);
|
||||
}
|
||||
bootstrap();
|
||||
|
|
@ -4,14 +4,14 @@ import { PrometheusService } from './prometheus.service';
|
|||
import { Logger } from '@nestjs/common';
|
||||
|
||||
@WebSocketGateway({
|
||||
/*
|
||||
cors: {
|
||||
origin: '*', // В production укажите конкретные домены
|
||||
methods: ['GET', 'POST'],
|
||||
credentials: true
|
||||
}, */
|
||||
cors: {
|
||||
origin: process.env.FRONTEND_URL || 'http://192.168.2.39:5173', // Тот же origin что и в основном CORS
|
||||
methods: ['GET', 'POST'],
|
||||
credentials: true
|
||||
},
|
||||
namespace: '/api/metrics-ws'
|
||||
})
|
||||
|
||||
export class MetricsGateway implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect {
|
||||
@WebSocketServer() server: Server;
|
||||
private readonly logger = new Logger(MetricsGateway.name);
|
||||
|
|
|
|||
Loading…
Reference in New Issue