bugs
This commit is contained in:
@@ -3,6 +3,6 @@ import type { Request } from 'express';
|
||||
|
||||
|
||||
export const BusinessId = createParamDecorator((data: unknown, ctx: ExecutionContext): string => {
|
||||
const request = ctx.switchToHttp().getRequest<Request & { busId?: string }>();
|
||||
return request.busId || '';
|
||||
const request = ctx.switchToHttp().getRequest<Request & { businessId?: string }>();
|
||||
return request.businessId || '';
|
||||
});
|
||||
|
||||
@@ -15,7 +15,7 @@ import { IAdminTokenPayload } from '../interfaces/IToken-payload';
|
||||
|
||||
export interface AdminAuthRequest extends Request {
|
||||
adminId: string;
|
||||
restId: string;
|
||||
businessId: string;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
@@ -52,13 +52,13 @@ export class AdminAuthGuard implements CanActivate {
|
||||
secret,
|
||||
});
|
||||
|
||||
if (!payload.adminId || !payload.restId) {
|
||||
if (!payload.adminId || !payload.businessId) {
|
||||
this.logger.error('Invalid token payload structure', payload);
|
||||
throw new UnauthorizedException('Invalid token payload');
|
||||
}
|
||||
|
||||
request['adminId'] = payload.adminId;
|
||||
request['restId'] = payload.restId;
|
||||
request['businessId'] = payload.businessId;
|
||||
|
||||
// check if the user has the required permissions
|
||||
// const requiredPermissions =
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
import { CanActivate, ExecutionContext, Injectable, UnauthorizedException, Inject, Logger } from '@nestjs/common';
|
||||
import { Request } from 'express';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { ITokenPayload } from '../interfaces/IToken-payload';
|
||||
|
||||
export interface UserAuthRequest extends Request {
|
||||
userId: string;
|
||||
restId: string;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class AuthGuard implements CanActivate {
|
||||
private readonly logger = new Logger(AuthGuard.name);
|
||||
|
||||
constructor(
|
||||
@Inject(JwtService)
|
||||
private readonly jwtService: JwtService,
|
||||
@Inject(ConfigService)
|
||||
private readonly configService: ConfigService,
|
||||
) {}
|
||||
|
||||
async canActivate(context: ExecutionContext) {
|
||||
const request = context.switchToHttp().getRequest<UserAuthRequest>();
|
||||
|
||||
try {
|
||||
const secret = this.configService.getOrThrow<string>('JWT_SECRET');
|
||||
const token = this.extractTokenFromHeader(request);
|
||||
const slug = this.extractSlugFromHeader(request);
|
||||
if (!slug) {
|
||||
throw new UnauthorizedException('Slug is required');
|
||||
}
|
||||
if (!token) {
|
||||
throw new UnauthorizedException();
|
||||
}
|
||||
|
||||
try {
|
||||
const payload = await this.jwtService.verifyAsync<ITokenPayload>(token, {
|
||||
secret,
|
||||
});
|
||||
if (!payload.userId || !payload.restId) {
|
||||
this.logger.error('Invalid token payload structure', payload);
|
||||
throw new UnauthorizedException('Invalid token payload');
|
||||
}
|
||||
if (payload.slug !== slug) {
|
||||
throw new UnauthorizedException('Invalid slug');
|
||||
}
|
||||
request['userId'] = payload.userId;
|
||||
request['restId'] = payload.restId;
|
||||
} catch {
|
||||
throw new UnauthorizedException();
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (err) {
|
||||
if (err instanceof UnauthorizedException) {
|
||||
throw err;
|
||||
}
|
||||
this.logger.error('error in AuthGuard', err);
|
||||
throw new UnauthorizedException('Invalid or expired token');
|
||||
}
|
||||
}
|
||||
private extractTokenFromHeader(request: Request): string | undefined {
|
||||
const [type, token] = request.headers.authorization?.split(' ') ?? [];
|
||||
return type === 'Bearer' ? token : undefined;
|
||||
}
|
||||
private extractSlugFromHeader(request: Request): string | undefined {
|
||||
// Fastify normalizes headers to lowercase, so check both cases
|
||||
const slug = (request.headers['x-slug'] || request.headers['X-Slug']) as string;
|
||||
return slug;
|
||||
}
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
import { CanActivate, ExecutionContext, Injectable, Inject, Logger } from '@nestjs/common';
|
||||
import { Request } from 'express';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { ITokenPayload } from '../interfaces/IToken-payload';
|
||||
|
||||
export interface UserOptionalAuthRequest extends Request {
|
||||
userId?: string;
|
||||
restId?: string;
|
||||
slug?: string;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class OptionalAuthGuard implements CanActivate {
|
||||
private readonly logger = new Logger(OptionalAuthGuard.name);
|
||||
|
||||
constructor(
|
||||
@Inject(JwtService)
|
||||
private readonly jwtService: JwtService,
|
||||
@Inject(ConfigService)
|
||||
private readonly configService: ConfigService,
|
||||
) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const request = context.switchToHttp().getRequest<UserOptionalAuthRequest>();
|
||||
const token = this.extractTokenFromHeader(request);
|
||||
const slug = this.extractSlugFromHeader(request);
|
||||
|
||||
// If no token is provided, allow the request without authentication
|
||||
if (!slug) {
|
||||
console.log('No slug provided');
|
||||
this.logger.warn('No slug provided');
|
||||
return false;
|
||||
}
|
||||
if (!token) {
|
||||
request['slug'] = slug;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Try to verify the token if it exists
|
||||
try {
|
||||
const secret = this.configService.getOrThrow<string>('JWT_SECRET');
|
||||
const payload = await this.jwtService.verifyAsync<ITokenPayload>(token, {
|
||||
secret,
|
||||
});
|
||||
|
||||
// Validate payload structure
|
||||
if (!payload.userId || !payload.restId) {
|
||||
this.logger.warn('Invalid token payload structure', payload);
|
||||
// Allow request but don't set user info
|
||||
request['slug'] = slug;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Validate slug if provided
|
||||
if (slug && payload.slug !== slug) {
|
||||
this.logger.warn('Token slug does not match header slug', {
|
||||
tokenSlug: payload.slug,
|
||||
headerSlug: slug,
|
||||
});
|
||||
// Allow request but don't set user info
|
||||
request['slug'] = slug;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Token is valid, set user info
|
||||
request['userId'] = payload.userId;
|
||||
request['restId'] = payload.restId;
|
||||
request['slug'] = slug;
|
||||
return true;
|
||||
} catch (err) {
|
||||
// Token is invalid or expired, but allow the request anyway
|
||||
this.logger.debug('Token verification failed in OptionalAuthGuard', {
|
||||
error: err instanceof Error ? err.message : 'Unknown error',
|
||||
});
|
||||
// Set restId from slug
|
||||
|
||||
request['slug'] = slug;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private extractTokenFromHeader(request: Request): string | undefined {
|
||||
const [type, token] = request.headers.authorization?.split(' ') ?? [];
|
||||
return type === 'Bearer' ? token : undefined;
|
||||
}
|
||||
|
||||
private extractSlugFromHeader(request: Request): string | undefined {
|
||||
// Fastify normalizes headers to lowercase, so check both cases
|
||||
const slug = (request.headers['x-slug'] || request.headers['X-Slug']) as string;
|
||||
return slug;
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,4 @@
|
||||
export interface ITokenPayload {
|
||||
userId: string;
|
||||
restId: string;
|
||||
slug: string;
|
||||
}
|
||||
|
||||
export interface IAdminTokenPayload {
|
||||
export interface IAdminTokenPayload {
|
||||
adminId: string;
|
||||
restId: string;
|
||||
businessId: string;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Injectable, Logger, UnauthorizedException } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { AuthMessage } from '../../../common/enums/message.enum';
|
||||
import { RefreshToken } from '../entities/refresh-token.entity';
|
||||
import { IAdminTokenPayload, ITokenPayload } from '../interfaces/IToken-payload';
|
||||
import { IAdminTokenPayload } from '../interfaces/IToken-payload';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
@@ -26,7 +26,7 @@ export class TokensService {
|
||||
const refreshExpire = this.configService.getOrThrow<number>('REFRESH_TOKEN_EXPIRE');
|
||||
const accessExpire = this.configService.getOrThrow<number>('JWT_EXPIRATION_TIME');
|
||||
|
||||
const payload: IAdminTokenPayload = { adminId: adminId, restId: businessId }
|
||||
const payload: IAdminTokenPayload = { adminId, businessId }
|
||||
|
||||
const accessToken = await this.generateAccessToken(payload, accessExpire);
|
||||
const refreshToken = this.generateRefreshToken();
|
||||
@@ -40,7 +40,7 @@ export class TokensService {
|
||||
};
|
||||
}
|
||||
|
||||
private generateAccessToken(payload: ITokenPayload | IAdminTokenPayload, expiresIn: number) {
|
||||
private generateAccessToken(payload: IAdminTokenPayload, expiresIn: number) {
|
||||
// Ensure expiresIn is passed as a string with time unit for reliability
|
||||
// JWT library accepts: number (seconds) or string with unit (e.g., "3600s", "1h")
|
||||
// Using string format is more explicit and prevents unit confusion
|
||||
|
||||
@@ -6,8 +6,10 @@ import { FindCataloguesDto } from './dto/find-catalogues.dto';
|
||||
import { AdminAuthGuard } from '../auth/guards/adminAuth.guard';
|
||||
import { BusinessId } from 'src/common/decorators';
|
||||
import { AdminId } from 'src/common/decorators';
|
||||
import { ApiBearerAuth } from '@nestjs/swagger';
|
||||
|
||||
@Controller()
|
||||
@ApiBearerAuth()
|
||||
export class CatalogueController {
|
||||
constructor(private readonly catalogueService: CatalogueService) { }
|
||||
|
||||
@@ -33,8 +35,8 @@ export class CatalogueController {
|
||||
|
||||
@Get('admin/catalogue')
|
||||
@UseGuards(AdminAuthGuard)
|
||||
findAll(@Query() queryDto: FindCataloguesDto, @AdminId() adminId: string) {
|
||||
return this.catalogueService.findAllAsAdmin(adminId, queryDto);
|
||||
findAll(@Query() queryDto: FindCataloguesDto, @BusinessId() businessId: string) {
|
||||
return this.catalogueService.findAllAsAdmin(businessId, queryDto);
|
||||
}
|
||||
|
||||
@Get('admin/catalogue/:id')
|
||||
|
||||
@@ -31,8 +31,8 @@ export class CatalogueService {
|
||||
return this.catalogueRepository.findAllPaginated(slug, dto)
|
||||
}
|
||||
|
||||
async findAllAsAdmin(adminId: string, dto: FindCataloguesDto) {
|
||||
const business = await this.businessService.findByAdminIdOrFail(adminId)
|
||||
async findAllAsAdmin(businessId: string, dto: FindCataloguesDto) {
|
||||
const business = await this.businessService.findByIdOrFail(businessId)
|
||||
|
||||
return this.catalogueRepository.findAllPaginated(business.slug, dto)
|
||||
}
|
||||
|
||||
@@ -4,37 +4,14 @@ import { ApiBody, ApiConsumes, ApiOperation, ApiBearerAuth, ApiTags } from '@nes
|
||||
import { UploadMultipleFileDto, UploadSingleFileDto } from '../DTO/upload-file.dto';
|
||||
import { UploaderService } from '../providers/uploader.service';
|
||||
import { AdminAuthGuard } from '../../auth/guards/adminAuth.guard';
|
||||
import { AuthGuard } from '../../auth/guards/auth.guard';
|
||||
|
||||
@ApiTags('uploader')
|
||||
@Controller()
|
||||
@ApiBearerAuth()
|
||||
export class UploaderController {
|
||||
constructor(private readonly uploaderService: UploaderService) {}
|
||||
|
||||
@UseGuards(AuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@ApiOperation({ summary: 'Upload a file (admin)' })
|
||||
@ApiConsumes('multipart/form-data')
|
||||
@UseInterceptors(FileInterceptor('file'))
|
||||
@ApiBody({ type: UploadSingleFileDto })
|
||||
@Post('public/single-file')
|
||||
uploadFile(@UploadedFile() file: File) {
|
||||
return this.uploaderService.uploadFile(file);
|
||||
}
|
||||
|
||||
@UseGuards(AuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@ApiOperation({ summary: 'Uploads multiple files' })
|
||||
@ApiConsumes('multipart/form-data')
|
||||
@UseInterceptors(FilesInterceptor('files'))
|
||||
@ApiBody({ type: UploadMultipleFileDto })
|
||||
@Post('public/multi-file')
|
||||
uploadFiles(@UploadedFiles() files: File[]) {
|
||||
return this.uploaderService.uploadMultiple(files);
|
||||
}
|
||||
constructor(private readonly uploaderService: UploaderService) { }
|
||||
|
||||
@UseGuards(AdminAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@ApiOperation({ summary: 'Upload a file (admin)' })
|
||||
@ApiConsumes('multipart/form-data')
|
||||
@UseInterceptors(FileInterceptor('file'))
|
||||
@@ -45,7 +22,6 @@ export class UploaderController {
|
||||
}
|
||||
|
||||
@UseGuards(AdminAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@ApiOperation({ summary: 'Uploads multiple files' })
|
||||
@ApiConsumes('multipart/form-data')
|
||||
@UseInterceptors(FilesInterceptor('files'))
|
||||
|
||||
Reference in New Issue
Block a user