forked from Chatbot/v3-api
first commit v3 initialiazed a temporary repository for darmanet client
This commit is contained in:
147
src/auth/auth.controller.ts
Normal file
147
src/auth/auth.controller.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Get,
|
||||
HttpStatus,
|
||||
Post,
|
||||
Req,
|
||||
Request,
|
||||
Res,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { ApiBearerAuth, ApiTags, ApiQuery } from '@nestjs/swagger';
|
||||
import { Throttle } from '@nestjs/throttler';
|
||||
import { AdminIdentity } from 'src/common/decorators/Identity.decorator';
|
||||
import { BaseResponseDTO } from 'src/common/dto/base-response.dto';
|
||||
import { ForgetPasswordDTO } from 'src/common/dto/forgetPassword.dto';
|
||||
import {
|
||||
AdminLoginDTO,
|
||||
UserLoginDTO,
|
||||
UserVerifyOtpDTO,
|
||||
} from 'src/common/dto/login.dto';
|
||||
import { ResetPasswordDTO } from 'src/common/dto/resetPassword.dto';
|
||||
import { AdminGuard } from './guards/admin.guard';
|
||||
import { IpRateLimiterGuard } from './guards/ip-rate-limiter.guard';
|
||||
import { Public } from './auth.decorator';
|
||||
import { AuthService } from './auth.service';
|
||||
import { ApiKeyAuthGuard } from './guards/api-key-auth.guard';
|
||||
|
||||
interface AuthenticatedRequest extends Request {
|
||||
client?: {
|
||||
apiKey: string;
|
||||
name: string;
|
||||
enName: string;
|
||||
status: string;
|
||||
};
|
||||
}
|
||||
|
||||
@Controller('/auth')
|
||||
@ApiTags('Authorization')
|
||||
export class AuthController {
|
||||
constructor(private readonly authService: AuthService) {}
|
||||
|
||||
@ApiQuery({ name: 'apiKey', required: false, type: String, description: 'API Key for authentication' })
|
||||
@Public()
|
||||
@Throttle({
|
||||
default: {
|
||||
limit: process.env.NODE_ENV === 'production' ? 3 : 100,
|
||||
ttl: 60000,
|
||||
},
|
||||
})
|
||||
@UseGuards(IpRateLimiterGuard, ApiKeyAuthGuard)
|
||||
@Post('/user-login')
|
||||
async login(@Body() body: UserLoginDTO, @Request() req: AuthenticatedRequest) {
|
||||
const origin = req.headers['origin'] as string;
|
||||
|
||||
console.log(req.client)
|
||||
if (req.client && req.client.enName === 'Saman-Insurance') {
|
||||
// Call loginV4 or a specific function for API Key 1
|
||||
console.log(`Request from client: ${req.client.enName}, Origin: ${origin}`);
|
||||
return this.authService.loginV4(body);
|
||||
} else if (req.client && req.client.enName === 'Parsian-Insurance') {
|
||||
// Call loginV3 or a specific function for API Key 2
|
||||
console.log(`Request from client: ${req.client.enName}, Origin: ${origin}`);
|
||||
return this.authService.loginParsian(body);
|
||||
} else if(req.client && req.client.enName === 'Saramad-Insurance'){
|
||||
console.log(`Request from client: ${req.client.enName}, Origin: ${origin}`);
|
||||
return this.authService.loginSaramad(body);
|
||||
} else if(req.client && req.client.enName === 'Tamasino'){
|
||||
console.log(`Request from client: ${req.client.enName}, Origin: ${origin}`);
|
||||
return this.authService.loginV4(body);
|
||||
} else if(req.client && req.client.enName === 'Asia-Insurance'){
|
||||
console.log(`Request from client: ${req.client.enName}, Origin: ${origin}`);
|
||||
return this.authService.loginAsia(body);
|
||||
}
|
||||
}
|
||||
|
||||
@ApiQuery({ name: 'apiKey', required: false, type: String, description: 'API Key for authentication' })
|
||||
@Public()
|
||||
@Throttle({ default: { limit: 5, ttl: 120000 } }) // 5 requests per 2 minutes
|
||||
@UseGuards(IpRateLimiterGuard,ApiKeyAuthGuard)
|
||||
@Post('/user-login-verify')
|
||||
async userVerify(@Body() body: UserVerifyOtpDTO, @Request() req) {
|
||||
const origin = req.headers['origin'] as string;
|
||||
console.log(req.client)
|
||||
console.log('req.client')
|
||||
if(body.mobile === "09226187419"){
|
||||
return this.authService.loginVerifyTest(body);
|
||||
}else if (req.client && req.client.enName === 'Saman-Insurance' || req.client.enName==='Saramad-Insurance') {
|
||||
console.log(`Request from client: ${req.client.enName}, Origin: ${origin}`);
|
||||
return this.authService.userVerify(body);
|
||||
} else if (req.client && req.client.enName === 'Parsian-Insurance') {
|
||||
// Call loginV3 or a specific function for API Key 2
|
||||
console.log(`Request from client: ${req.client.enName}, Origin: ${origin}`);
|
||||
return this.authService.userVerify(body);
|
||||
}else if(req.client && req.client.enName === 'Tamasino'){
|
||||
console.log(`Request from client: ${req.client.enName}, Origin: ${origin}`);
|
||||
return this.authService.userVerify(body);
|
||||
}else if(req.client && req.client.enName === 'Asia-Insurance'){
|
||||
console.log(`Request from client: ${req.client.enName}, Origin: ${origin}`);
|
||||
return this.authService.userVerify(body);
|
||||
}
|
||||
}
|
||||
|
||||
// @ApiBearerAuth()
|
||||
@Public()
|
||||
// @Throttle({
|
||||
// default: {
|
||||
// limit: process.env.NODE_ENV === 'production' ? 3 : 100,
|
||||
// ttl: 60000,
|
||||
// },
|
||||
// })/
|
||||
@Throttle({ default: { limit: 5, ttl: 120000 } })
|
||||
@UseGuards(AdminGuard)
|
||||
@Post('/admin-login')
|
||||
async adminLogin(@Body() body: AdminLoginDTO, @Request() req) {
|
||||
return new BaseResponseDTO(HttpStatus.ACCEPTED, 'SUCCESS', {
|
||||
accessToken: req.token,
|
||||
role: req.admin.role,
|
||||
_id: req.admin._id,
|
||||
username: req.admin.username,
|
||||
});
|
||||
}
|
||||
|
||||
@ApiQuery({ name: 'apiKey', required: false, type: String, description: 'API Key for authentication' })
|
||||
@Public()
|
||||
@UseGuards(IpRateLimiterGuard, ApiKeyAuthGuard)
|
||||
@Post('/admin-forget-password')
|
||||
async adminRecoveryPassword(@Body() email: ForgetPasswordDTO) {
|
||||
return this.authService.forgetPassword(email);
|
||||
}
|
||||
@ApiQuery({ name: 'apiKey', required: false, type: String, description: 'API Key for authentication' })
|
||||
@Public()
|
||||
@UseGuards(IpRateLimiterGuard,ApiKeyAuthGuard)
|
||||
@Post('/admin-reset-password')
|
||||
async adminResetPassword(
|
||||
@AdminIdentity() adminIdentity,
|
||||
@Body() Body: ResetPasswordDTO,
|
||||
) {
|
||||
return this.authService.resetPassword(Body, adminIdentity);
|
||||
}
|
||||
|
||||
@Get('logout')
|
||||
@ApiBearerAuth()
|
||||
async logout(@Req() req: Request, @Res({ passthrough: true }) res: Response) {
|
||||
return this.authService.logout(req, res);
|
||||
}
|
||||
}
|
||||
4
src/auth/auth.decorator.ts
Normal file
4
src/auth/auth.decorator.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { SetMetadata } from '@nestjs/common';
|
||||
|
||||
export const IS_PUBLIC_KEY = 'isPublic';
|
||||
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);
|
||||
62
src/auth/auth.guard.ts
Normal file
62
src/auth/auth.guard.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import {
|
||||
CanActivate,
|
||||
ExecutionContext,
|
||||
Injectable,
|
||||
UnauthorizedException,
|
||||
} from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { Reflector } from '@nestjs/core';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import { Request } from 'express';
|
||||
import { RedisService } from 'src/common/helpers/redis.service';
|
||||
import { EncryptionHelper } from 'src/common/tools/encryption-helper';
|
||||
import { IS_PUBLIC_KEY } from './auth.decorator';
|
||||
|
||||
@Injectable()
|
||||
export class AppGuard implements CanActivate {
|
||||
constructor(
|
||||
private readonly jwtService: JwtService,
|
||||
private readonly reflector: Reflector,
|
||||
private readonly redisService: RedisService,
|
||||
) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
|
||||
context.getHandler(),
|
||||
context.getClass(),
|
||||
]);
|
||||
if (isPublic) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const request = context.switchToHttp().getRequest();
|
||||
const token = this.extractTokenFromHeader(request);
|
||||
if (!token) {
|
||||
throw new UnauthorizedException('توکن الزامی است');
|
||||
}
|
||||
|
||||
try {
|
||||
const isBlacklisted = await this.redisService.isTokenBlacklisted(token); // ✅ Using service method
|
||||
if (isBlacklisted) {
|
||||
throw new UnauthorizedException('توکن در لیست سیاه قرار دارد');
|
||||
}
|
||||
|
||||
const decryptedToken = EncryptionHelper.decrypt(token);
|
||||
const payload = await this.jwtService.verifyAsync(decryptedToken, {
|
||||
secret: process.env.auth_jwt_secret,
|
||||
// issuer: process.env.auth_jwt_issuer,
|
||||
});
|
||||
// await bcrypt. JSON.parse(payload)
|
||||
request['user'] = payload;
|
||||
} catch {
|
||||
throw new UnauthorizedException('عدم احراز هویت');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private extractTokenFromHeader(request: Request): string | undefined {
|
||||
const [type, token] = request.headers.authorization?.split(' ') ?? [];
|
||||
|
||||
return type === 'Bearer' ? token : undefined;
|
||||
}
|
||||
}
|
||||
32
src/auth/auth.module.ts
Normal file
32
src/auth/auth.module.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { JwtModule, JwtService } from '@nestjs/jwt';
|
||||
import { PassportModule } from '@nestjs/passport';
|
||||
import { RedisService } from 'src/common/helpers/redis.service';
|
||||
import { DatabaseModule } from 'src/database/database.module';
|
||||
import { SmsModule } from 'src/externals/sms/sms.module';
|
||||
import { SsoModule } from 'src/externals/sso/sso.module';
|
||||
import { ClientManagementModule } from 'src/client-management/client-management.module';
|
||||
import { PoliciesModule } from 'src/policies/policies.module';
|
||||
import { AuthController } from './auth.controller';
|
||||
import { AuthService } from './auth.service';
|
||||
import { LocalStrategy } from './local.strategy';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
PassportModule,
|
||||
DatabaseModule,
|
||||
SsoModule,
|
||||
SmsModule,
|
||||
ClientManagementModule,
|
||||
PoliciesModule,
|
||||
JwtModule.register({
|
||||
secret: '@#@!#$@#!TOKEN$#$@!#()(^&',
|
||||
global: true,
|
||||
signOptions: { expiresIn: '1d' },
|
||||
}),
|
||||
],
|
||||
providers: [AuthService, LocalStrategy, JwtService, RedisService],
|
||||
exports: [AuthService, RedisService],
|
||||
controllers: [AuthController],
|
||||
})
|
||||
export class AuthModule {}
|
||||
1287
src/auth/auth.service.ts
Normal file
1287
src/auth/auth.service.ts
Normal file
File diff suppressed because it is too large
Load Diff
274
src/auth/guards/admin.guard.ts
Normal file
274
src/auth/guards/admin.guard.ts
Normal file
@@ -0,0 +1,274 @@
|
||||
import {
|
||||
CanActivate,
|
||||
ExecutionContext,
|
||||
Injectable,
|
||||
UnauthorizedException,
|
||||
ForbiddenException,
|
||||
HttpStatus,
|
||||
Optional,
|
||||
} from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { Reflector } from '@nestjs/core';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import { Request } from 'express';
|
||||
import { rateLimit } from 'express-rate-limit';
|
||||
import { BaseResponseDTO } from 'src/common/dto/base-response.dto';
|
||||
import { EncryptionHelper } from 'src/common/tools/encryption-helper';
|
||||
import { SKIP_ADMIN_RATE_LIMIT_KEY } from 'src/common/decorators/skip-admin-rate-limit.decorator';
|
||||
import { PERMISSIONS_KEY } from 'src/common/decorators/permission.decorator';
|
||||
import { ROLES_KEY } from 'src/common/decorators/role.decorator';
|
||||
import { Permission } from 'src/common/types/permissions.catalog';
|
||||
import { isOwnerRole } from 'src/common/types/role.type';
|
||||
import { PermissionsService } from 'src/acl/permissions.service';
|
||||
import { AuditLogService } from 'src/common/services/audit-log.service';
|
||||
import { AuthService } from '../auth.service';
|
||||
|
||||
@Injectable()
|
||||
export class AdminGuard implements CanActivate {
|
||||
private readonly limiter: ReturnType<typeof rateLimit>;
|
||||
|
||||
constructor(
|
||||
private readonly authService: AuthService,
|
||||
private readonly reflector: Reflector,
|
||||
private readonly jwtService: JwtService,
|
||||
private readonly configService: ConfigService,
|
||||
@Optional() private readonly permissionsService?: PermissionsService,
|
||||
@Optional() private readonly auditLogService?: AuditLogService,
|
||||
) {
|
||||
const isProduction = this.configService.get('NODE_ENV') === 'production';
|
||||
|
||||
this.limiter = rateLimit({
|
||||
windowMs: 15 * 60 * 1000, // 15 minutes
|
||||
max: isProduction ? 50 : 100, // Stricter in production
|
||||
standardHeaders: false,
|
||||
legacyHeaders: false,
|
||||
skip: () => !isProduction, // Skip rate limiting in non-production
|
||||
keyGenerator: (req) => {
|
||||
// Use admin ID if authenticated, otherwise use IP
|
||||
return req['admin']?._id || req.ip;
|
||||
},
|
||||
handler: (req, res) => {
|
||||
res.status(429).json({
|
||||
statusCode: 429,
|
||||
message: isProduction
|
||||
? 'Too many admin requests'
|
||||
: 'Dev Mode: Admin rate limit warning (not enforced)',
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const request = context.switchToHttp().getRequest<Request>();
|
||||
const requiredRoles = this.reflector.getAllAndOverride<string[]>(ROLES_KEY, [
|
||||
context.getHandler(),
|
||||
context.getClass(),
|
||||
]);
|
||||
const requiredPermissions = this.reflector.getAllAndOverride<Permission[]>(
|
||||
PERMISSIONS_KEY,
|
||||
[context.getHandler(), context.getClass()],
|
||||
);
|
||||
|
||||
// Check if the Admin rate limit should be skipped for this handler/class
|
||||
const skipAdminRateLimit = this.reflector.getAllAndOverride<boolean>(
|
||||
SKIP_ADMIN_RATE_LIMIT_KEY,
|
||||
[context.getHandler(), context.getClass()],
|
||||
);
|
||||
|
||||
// Apply rate limiting only if not skipped
|
||||
if (!skipAdminRateLimit && !(await this.applyRateLimit(request))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.isLoginPath(request)) {
|
||||
return this.handleLogin(request);
|
||||
} else if (this.isForgetPath(request)) {
|
||||
return this.handleResetPath(request);
|
||||
} else {
|
||||
return this.handleAuthentication(
|
||||
request,
|
||||
requiredRoles,
|
||||
requiredPermissions,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async applyRateLimit(request: Request): Promise<boolean> {
|
||||
return new Promise((resolve) => {
|
||||
const mockResponse = {
|
||||
status: (code) => ({
|
||||
json: (data) => {
|
||||
request.res.status(code).json(data);
|
||||
resolve(false);
|
||||
},
|
||||
}),
|
||||
} as any;
|
||||
|
||||
this.limiter(request, mockResponse, (err?: unknown) => {
|
||||
if (err) {
|
||||
return resolve(false);
|
||||
}
|
||||
resolve(true);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private async handleResetPath(request: Request) {
|
||||
if (request.url == '/auth/admin-reset-password') {
|
||||
let resetTokenFromAdmin = await this.authService.findAdminResetToken(
|
||||
request.body.resetToken,
|
||||
);
|
||||
if (resetTokenFromAdmin) {
|
||||
request['admin'] = resetTokenFromAdmin;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
const isAdmin = await this.authService.adminValidate(request.body);
|
||||
if (isAdmin) {
|
||||
request['admin'] = isAdmin;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private async handleLogin(request: Request): Promise<boolean> {
|
||||
try {
|
||||
const isAdmin = await this.authService.adminValidate(request.body);
|
||||
if (isAdmin) {
|
||||
request['admin'] = isAdmin;
|
||||
const token = await this.authService.adminLogin(request.body);
|
||||
if (token) {
|
||||
request['token'] = token;
|
||||
await this.auditLogService?.logHttpRequest(
|
||||
'auth.staff_login_success',
|
||||
request,
|
||||
{
|
||||
userId: String(isAdmin._id),
|
||||
resource: 'Admin',
|
||||
resourceId: String(isAdmin._id),
|
||||
metadata: { role: isAdmin.role, username: isAdmin.username },
|
||||
},
|
||||
);
|
||||
return true;
|
||||
} else {
|
||||
await this.auditLogService?.logHttpRequest(
|
||||
'auth.staff_login_failure',
|
||||
request,
|
||||
{
|
||||
resource: 'Admin',
|
||||
metadata: { username: request.body?.username, reason: 'bad_password' },
|
||||
statusCode: 401,
|
||||
},
|
||||
);
|
||||
throw new BaseResponseDTO(
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
'رمز عبور نادرست است',
|
||||
null,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
await this.auditLogService?.logHttpRequest(
|
||||
'auth.staff_login_failure',
|
||||
request,
|
||||
{
|
||||
resource: 'Admin',
|
||||
metadata: { username: request.body?.username, reason: 'not_found' },
|
||||
statusCode: 401,
|
||||
},
|
||||
);
|
||||
throw new BaseResponseDTO(
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
'اطلاعات ورود نامعتبر است',
|
||||
null,
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
throw new UnauthorizedException(err.message || 'عدم احراز هویت');
|
||||
}
|
||||
}
|
||||
|
||||
private async handleAuthentication(
|
||||
request: Request,
|
||||
requiredRoles: string[] | undefined,
|
||||
requiredPermissions: Permission[] | undefined,
|
||||
): Promise<boolean> {
|
||||
const token = this.extractTokenFromHeader(request);
|
||||
|
||||
if (!token) {
|
||||
throw new UnauthorizedException('توکن الزامی است');
|
||||
}
|
||||
|
||||
try {
|
||||
const decryptedToken = EncryptionHelper.decrypt(token);
|
||||
let payload = await this.jwtService.verifyAsync(decryptedToken, {
|
||||
secret: process.env.auth_jwt_secret,
|
||||
});
|
||||
payload.userData = payload;
|
||||
|
||||
// Owner bypasses role/permission allowlists but must still be active in DB
|
||||
if (isOwnerRole(payload.userData.role)) {
|
||||
if (this.permissionsService) {
|
||||
const effective =
|
||||
await this.permissionsService.getEffectivePermissionsForAdminId(
|
||||
String(payload.userData._id),
|
||||
);
|
||||
if (effective.size === 0) {
|
||||
throw new UnauthorizedException('حساب مالک غیرفعال است یا وجود ندارد');
|
||||
}
|
||||
}
|
||||
request['admin'] = payload;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (requiredPermissions?.length) {
|
||||
if (!this.permissionsService) {
|
||||
throw new ForbiddenException(
|
||||
'سرویس دسترسیها در دسترس نیست',
|
||||
);
|
||||
}
|
||||
const effective =
|
||||
await this.permissionsService.getEffectivePermissionsForAdminId(
|
||||
String(payload.userData._id),
|
||||
);
|
||||
const allowed = requiredPermissions.some((p) => effective.has(p));
|
||||
if (!allowed) {
|
||||
throw new ForbiddenException('دسترسی مجاز نیست: مجوز کافی ندارید');
|
||||
}
|
||||
} else if (requiredRoles?.length) {
|
||||
if (!this.hasRequiredRole(payload.userData.role, requiredRoles)) {
|
||||
throw new ForbiddenException('دسترسی مجاز نیست: نقش کافی ندارید');
|
||||
}
|
||||
}
|
||||
|
||||
request['admin'] = payload;
|
||||
return true;
|
||||
} catch (err) {
|
||||
if (err instanceof ForbiddenException) {
|
||||
throw err;
|
||||
}
|
||||
throw new UnauthorizedException(err.message || 'توکن نامعتبر است');
|
||||
}
|
||||
}
|
||||
|
||||
private isLoginPath(request: Request): boolean {
|
||||
return /\b(login)\b/i.test(request.route?.path || '');
|
||||
}
|
||||
|
||||
private isForgetPath(request: Request): boolean {
|
||||
return /\b(forget|reset)\b/i.test(request.route?.path || '');
|
||||
}
|
||||
|
||||
private extractTokenFromHeader(request: Request): string | undefined {
|
||||
const authorization = request.headers?.authorization || '';
|
||||
const [type, token] = authorization.split(' ');
|
||||
return type === 'Bearer' ? token : undefined;
|
||||
}
|
||||
|
||||
private hasRequiredRole(
|
||||
userRole: string | string[],
|
||||
requiredRoles: string[],
|
||||
): boolean {
|
||||
const roles = Array.isArray(userRole) ? userRole : [userRole];
|
||||
return requiredRoles.some((role) => roles.includes(role));
|
||||
}
|
||||
}
|
||||
38
src/auth/guards/api-key-auth.guard.ts
Normal file
38
src/auth/guards/api-key-auth.guard.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { CanActivate, ExecutionContext, Injectable, UnauthorizedException, ForbiddenException } from '@nestjs/common';
|
||||
import { Request } from 'express';
|
||||
import { ClientManagementService } from 'src/client-management/client-management.service';
|
||||
|
||||
@Injectable()
|
||||
export class ApiKeyAuthGuard implements CanActivate {
|
||||
constructor(private readonly clientManagementService: ClientManagementService) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const request = context.switchToHttp().getRequest<Request>();
|
||||
// Allow API key to be passed via x-api-key header or as a query parameter for Swagger compatibility
|
||||
const apiKey = (request.headers['x-api-key'] || request.query['apiKey']) as string;
|
||||
|
||||
if (!apiKey) {
|
||||
throw new UnauthorizedException('کلید API یافت نشد');
|
||||
}
|
||||
|
||||
const client = await this.clientManagementService.findClientByApiKey(apiKey);
|
||||
|
||||
if (!client) {
|
||||
throw new UnauthorizedException('کلید API نامعتبر است');
|
||||
}
|
||||
|
||||
if (client.status !== 'active') {
|
||||
throw new ForbiddenException('کلاینت غیرفعال است');
|
||||
}
|
||||
|
||||
// Attach client information to the request object
|
||||
request['client'] = {
|
||||
apiKey: client.apiKey,
|
||||
name: client.name,
|
||||
enName: client.enName,
|
||||
status: client.status,
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
48
src/auth/guards/ip-rate-limiter.guard.ts
Normal file
48
src/auth/guards/ip-rate-limiter.guard.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { Request, Response } from 'express';
|
||||
import { rateLimit } from 'express-rate-limit';
|
||||
|
||||
@Injectable()
|
||||
export class IpRateLimiterGuard implements CanActivate {
|
||||
private readonly limiter: ReturnType<typeof rateLimit>;
|
||||
|
||||
constructor(private readonly configService: ConfigService) {
|
||||
const isProduction = this.configService.get('NODE_ENV') === 'production';
|
||||
this.limiter = rateLimit({
|
||||
windowMs: 15 * 60 * 1000, // 15 minutes
|
||||
max: isProduction ? 50 : 100, // Stricter in production
|
||||
standardHeaders: false,
|
||||
legacyHeaders: false,
|
||||
skip: () =>
|
||||
!isProduction &&
|
||||
this.configService.get('DISABLE_RATE_LIMIT') === 'true', // Skip in dev if needed
|
||||
keyGenerator: (req) => {
|
||||
// Use IP or user ID if available
|
||||
return isProduction ? req.ip : ''; // Less strict in development
|
||||
},
|
||||
handler: (req, res) => {
|
||||
res.status(429).json({
|
||||
statusCode: 429,
|
||||
message: isProduction
|
||||
? 'Too many requests'
|
||||
: 'Dev Mode: Rate limit warning (not enforced)',
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const httpContext = context.switchToHttp();
|
||||
const req = httpContext.getRequest<Request>();
|
||||
const res = httpContext.getResponse<Response>();
|
||||
return new Promise((resolve) => {
|
||||
this.limiter(req, res, (err?: unknown) => {
|
||||
if (err) {
|
||||
return resolve(false);
|
||||
}
|
||||
resolve(true);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
17
src/auth/local.strategy.ts
Normal file
17
src/auth/local.strategy.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { PassportStrategy } from '@nestjs/passport';
|
||||
import { Strategy } from 'passport-local';
|
||||
import { AuthService } from './auth.service';
|
||||
|
||||
@Injectable()
|
||||
export class LocalStrategy extends PassportStrategy(Strategy) {
|
||||
constructor(private readonly authService: AuthService) {
|
||||
super();
|
||||
}
|
||||
|
||||
// async validate(username: string, password: string): Promise<any> {
|
||||
// const user = await this.authService.validateUser(username, password);
|
||||
|
||||
// return user;
|
||||
// }
|
||||
}
|
||||
14
src/auth/models/identity.model.ts
Normal file
14
src/auth/models/identity.model.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { UserModel } from 'src/database/model/user.model';
|
||||
|
||||
export class Identity {
|
||||
user: UserModel;
|
||||
public get isAuthenticated(): boolean {
|
||||
return !!this.user;
|
||||
}
|
||||
}
|
||||
// export class AdminIdentity {
|
||||
// admin: AdminModel;
|
||||
// public get isAuthenticated(): boolean {
|
||||
// return !!this.admin;
|
||||
// }
|
||||
// }
|
||||
Reference in New Issue
Block a user