From 71b73d5fcb384e09c8848a24b67d72b5c250dc86 Mon Sep 17 00:00:00 2001 From: dimitar Date: Tue, 1 Apr 2025 05:20:19 +0200 Subject: [PATCH] clean up and logOut --- backend/src/auth/auth.controller.ts | 108 ++++++++++-------- backend/src/auth/auth.service.ts | 20 ++-- backend/src/config/database.config.ts | 2 +- backend/src/documents/documents.controller.ts | 91 ++++++++------- backend/src/email/email.service.ts | 6 +- backend/src/s3/s3.service.ts | 51 ++++++--- frontend/src/services/api.js | 6 +- 7 files changed, 157 insertions(+), 127 deletions(-) diff --git a/backend/src/auth/auth.controller.ts b/backend/src/auth/auth.controller.ts index 934c6db..684ea0e 100644 --- a/backend/src/auth/auth.controller.ts +++ b/backend/src/auth/auth.controller.ts @@ -8,59 +8,59 @@ import { Request, Logger, BadRequestException, -} from '@nestjs/common'; -import { AuthService } from './auth.service'; -import { LoginDto } from '../dto/login.dto'; -import { CreateUserDto } from '../dto/create-user.dto'; -import { JwtAuthGuard } from './jwt-auth.guard'; -import { AdminGuard } from './admin.guard'; +} from "@nestjs/common"; +import { AuthService } from "./auth.service"; +import { LoginDto } from "../dto/login.dto"; +import { CreateUserDto } from "../dto/create-user.dto"; +import { JwtAuthGuard } from "./jwt-auth.guard"; +import { AdminGuard } from "./admin.guard"; //@UseGuards(JwtAuthGuard, AdminGuard) -@Controller('auth') +@Controller("auth") export class AuthController { private readonly logger = new Logger(AuthController.name); constructor(private authService: AuthService) { - this.logger.log('AuthController initialized'); + this.logger.log("AuthController initialized"); } - @Post('login') + @Post("login") async login(@Body() loginDto: LoginDto) { - this.logger.log('=== Login endpoint hit ==='); - this.logger.debug('Raw request body:', { + this.logger.log("=== Login endpoint hit ==="); + this.logger.debug("Raw request body:", { username: loginDto.username, email: loginDto.email, hasPassword: !!loginDto.password, }); const email = loginDto.getEmail(); - this.logger.debug('Normalized login request:', { + this.logger.debug("Normalized login request:", { email, hasPassword: !!loginDto.password, }); - + try { - this.logger.debug('Calling AuthService.validateUser...'); + this.logger.debug("Calling AuthService.validateUser..."); const user = await this.authService.validateUser( email, loginDto.password, ); - + if (!user) { this.logger.warn(`Login failed: Invalid credentials for ${email}`); - throw new UnauthorizedException('Invalid email or password'); + throw new UnauthorizedException("Invalid email or password"); } - - this.logger.debug('User validated successfully:', { + + this.logger.debug("User validated successfully:", { id: user.id, email: user.email, name: user.name, isAdmin: user.isAdmin, }); - this.logger.debug('Calling AuthService.login...'); + this.logger.debug("Calling AuthService.login..."); const result = await this.authService.login(user); - - this.logger.debug('Login successful, returning response:', { + + this.logger.debug("Login successful, returning response:", { hasAccessToken: !!result.access_token, user: { id: result.user.id, @@ -69,14 +69,14 @@ export class AuthController { isAdmin: result.user.isAdmin, }, }); - + return result; } catch (error) { if (error instanceof UnauthorizedException) { throw error; } - - this.logger.error('Login failed:', { + + this.logger.error("Login failed:", { error: error.message, stack: error.stack, body: { @@ -85,35 +85,35 @@ export class AuthController { hasPassword: !!loginDto.password, }, }); - throw new UnauthorizedException('Invalid email or password'); + throw new UnauthorizedException("Invalid email or password"); } } - @Post('register') + @Post("register") async register(@Body() createUserDto: CreateUserDto) { - console.log('=== Registration endpoint hit ==='); - this.logger.log('=== Registration endpoint hit ==='); - console.log('Registration request received:', createUserDto); - this.logger.log('Registration request received:', { + console.log("=== Registration endpoint hit ==="); + this.logger.log("=== Registration endpoint hit ==="); + console.log("Registration request received:", createUserDto); + this.logger.log("Registration request received:", { email: createUserDto.email, name: createUserDto.name, - hasPassword: !!createUserDto.password + hasPassword: !!createUserDto.password, }); try { - console.log('Calling AuthService.createUser...'); - this.logger.log('Calling AuthService.createUser...'); + console.log("Calling AuthService.createUser..."); + this.logger.log("Calling AuthService.createUser..."); const result = await this.authService.createUser(createUserDto); - console.log('Registration successful:', result); - this.logger.log('Registration successful:', { + console.log("Registration successful:", result); + this.logger.log("Registration successful:", { id: result.id, email: result.email, name: result.name, }); return result; } catch (error) { - console.error('Registration failed:', error); - this.logger.error('Registration failed:', { + console.error("Registration failed:", error); + this.logger.error("Registration failed:", { error: error.message, code: error.code, command: error.command, @@ -124,25 +124,25 @@ export class AuthController { } //@UseGuards(JwtAuthGuard) - @Post('create-admin') + @Post("create-admin") async createAdmin(@Body() createUserDto: CreateUserDto) { - this.logger.log('=== Create admin endpoint hit ==='); - this.logger.debug('Admin creation request received:', { + this.logger.log("=== Create admin endpoint hit ==="); + this.logger.debug("Admin creation request received:", { email: createUserDto.email, name: createUserDto.name, }); try { - this.logger.debug('Calling AuthService.createUser with isAdmin=true...'); + this.logger.debug("Calling AuthService.createUser with isAdmin=true..."); const result = await this.authService.createUser(createUserDto, true); - this.logger.debug('Admin creation successful:', { + this.logger.debug("Admin creation successful:", { id: result.id, email: result.email, name: result.name, }); return result; } catch (error) { - this.logger.error('Admin creation failed:', { + this.logger.error("Admin creation failed:", { error: error.message, stack: error.stack, }); @@ -151,29 +151,37 @@ export class AuthController { } @UseGuards(JwtAuthGuard) - @Get('user-info') + @Get("user-info") async getUserInfo(@Request() req) { return this.authService.getUserInfo(req.user.userId); } - @Post('forgot-password') + @Post("forgot-password") async forgotPassword(@Body() { email }: { email: string }) { if (!email) { - throw new BadRequestException('Email is required'); + throw new BadRequestException("Email is required"); } return this.authService.sendPasswordResetToken(email); } - @Post('reset-password') + @Post("reset-password") async resetPassword( - @Body() { token, newPassword }: { token: string; newPassword: string } + @Body() { token, newPassword }: { token: string; newPassword: string }, ) { if (!token || !newPassword) { - throw new BadRequestException('Token and new password are required'); + throw new BadRequestException("Token and new password are required"); } if (newPassword.length < 6) { - throw new BadRequestException('Password must be at least 6 characters long'); + throw new BadRequestException( + "Password must be at least 6 characters long", + ); } return this.authService.resetPasswordWithToken(token, newPassword); } + + @Post("logout") + async logout(@Request() req) { + await this.authService.logout(req.user.userId); + return { message: "Logged out successfully" }; + } } diff --git a/backend/src/auth/auth.service.ts b/backend/src/auth/auth.service.ts index 34b92f3..0fe58e3 100644 --- a/backend/src/auth/auth.service.ts +++ b/backend/src/auth/auth.service.ts @@ -221,17 +221,6 @@ export class AuthService { } } - // async getUserInfo(userId: number) { - // return this.prisma.user.findUnique({ - // where: { id: userId }, - // select: { - // id: true, - // name: true, - // email: true, - // isAdmin: true, - // }, - // }); - // } async getUserInfo(userId: number) { if (!userId) { throw new Error("User ID is required"); @@ -327,9 +316,9 @@ export class AuthService { where: { token, expiresAt: { - gt: new Date(), // Token must not be expired + gt: new Date(), }, - used: false, // Token must not be used + used: false, }, include: { user: true, @@ -386,4 +375,9 @@ export class AuthService { throw error; } } + + async logout(userId: string): Promise { + return console.log("User logged out:", userId); + this.logger.log("User logged out:", userId); + } } diff --git a/backend/src/config/database.config.ts b/backend/src/config/database.config.ts index 9e8b297..17152d9 100644 --- a/backend/src/config/database.config.ts +++ b/backend/src/config/database.config.ts @@ -1,5 +1,5 @@ export const getDatabaseUrl = () => { const url = process.env.DATABASE_URL; - console.log("Database URL:", url); // For debugging + console.log("Database URL:", url); return url; }; diff --git a/backend/src/documents/documents.controller.ts b/backend/src/documents/documents.controller.ts index 9d5c8ce..1597dc4 100644 --- a/backend/src/documents/documents.controller.ts +++ b/backend/src/documents/documents.controller.ts @@ -1,9 +1,21 @@ -import { Controller, Get, Param, Req, Res, UseGuards, Logger, Request, Post, UseInterceptors, UploadedFile, Body } from '@nestjs/common'; -import { Response } from 'express'; -import { DocumentsService } from './documents.service'; -import { JwtAuthGuard } from '../auth/jwt-auth.guard'; -import { S3Service } from '../s3/s3.service'; -import { FileInterceptor } from '@nestjs/platform-express'; +import { + Body, + Controller, + Get, + Logger, + Param, + Post, + Request, + Res, + UploadedFile, + UseGuards, + UseInterceptors, +} from "@nestjs/common"; +import { FileInterceptor } from "@nestjs/platform-express"; +import { Response } from "express"; +import { JwtAuthGuard } from "../auth/jwt-auth.guard"; +import { S3Service } from "../s3/s3.service"; +import { DocumentsService } from "./documents.service"; interface S3File { buffer: Buffer; @@ -12,28 +24,28 @@ interface S3File { fileName: string; } -@Controller('documents') +@Controller("documents") export class DocumentsController { private readonly logger = new Logger(DocumentsController.name); constructor( private readonly documentsService: DocumentsService, - private readonly s3Service: S3Service + private readonly s3Service: S3Service, ) { - this.logger.log('DocumentsController initialized'); + this.logger.log("DocumentsController initialized"); } - @Post('upload') + @Post("upload") @UseGuards(JwtAuthGuard) - @UseInterceptors(FileInterceptor('file')) + @UseInterceptors(FileInterceptor("file")) async uploadDocument( @UploadedFile() file: Express.Multer.File, - @Body('title') title: string, - @Body('sharedWithId') sharedWithId: string, + @Body("title") title: string, + @Body("sharedWithId") sharedWithId: string, @Request() req, ) { - this.logger.log('=== Document upload endpoint hit ==='); - this.logger.debug('Upload request received:', { + this.logger.log("=== Document upload endpoint hit ==="); + this.logger.debug("Upload request received:", { fileName: file?.originalname, fileSize: file?.size, title, @@ -48,16 +60,16 @@ export class DocumentsController { Number(sharedWithId), req.user.id, ); - - this.logger.debug('Document upload successful:', { + + this.logger.debug("Document upload successful:", { documentId: result.id, title: result.title, s3Key: result.s3Key, }); - + return result; } catch (error) { - this.logger.error('Document upload failed:', { + this.logger.error("Document upload failed:", { error: error.message, stack: error.stack, }); @@ -65,61 +77,62 @@ export class DocumentsController { } } - @Get('shared/:userId') + @Get("shared/:userId") @UseGuards(JwtAuthGuard) - async getSharedDocuments(@Param('userId') userId: string) { + async getSharedDocuments(@Param("userId") userId: string) { return this.documentsService.getClientDocuments(Number(userId)); } - @Get('download/:key') + @Get("download/:key") @UseGuards(JwtAuthGuard) async downloadDocument( - @Param('key') key: string, + @Param("key") key: string, @Request() req, - @Res() res: Response + @Res() res: Response, ) { try { this.logger.debug(`Download request for key: ${key}`); - + const decodedKey = decodeURIComponent(key); this.logger.debug(`Decoded key: ${decodedKey}`); // Get document from database first to verify access - const document = await this.documentsService.findDocumentByS3Key(decodedKey); - + const document = + await this.documentsService.findDocumentByS3Key(decodedKey); + if (!document) { - return res.status(404).json({ message: 'Document not found' }); + return res.status(404).json({ message: "Document not found" }); } // Verify user has access to this document const hasAccess = await this.documentsService.verifyDocumentAccess( document.id, - req.user.id + req.user.id, ); if (!hasAccess) { - return res.status(403).json({ message: 'Access denied' }); + return res.status(403).json({ message: "Access denied" }); } // Get the file from S3 const file = await this.s3Service.getFile(decodedKey); - + if (!file || !file.buffer) { - return res.status(404).json({ message: 'File not found in storage' }); + return res.status(404).json({ message: "File not found in storage" }); } res.set({ - 'Content-Type': file.contentType || 'application/octet-stream', - 'Content-Length': file.contentLength, - 'Content-Disposition': `attachment; filename="${encodeURIComponent(file.fileName)}"`, + "Content-Type": file.contentType || "application/octet-stream", + "Content-Length": file.contentLength, + "Content-Disposition": `attachment; filename="${encodeURIComponent(file.fileName)}"`, }); return res.send(file.buffer); } catch (error) { - this.logger.error('Download error:', error); - return res.status(500).json({ - message: 'Failed to download file', - error: error.message + this.logger.error("Download error:", error); + return res.status(500).json({ + message: "Failed to download file", + error: error.message, }); } } diff --git a/backend/src/email/email.service.ts b/backend/src/email/email.service.ts index 7372f77..a5b4256 100644 --- a/backend/src/email/email.service.ts +++ b/backend/src/email/email.service.ts @@ -9,7 +9,7 @@ export class EmailService { private readonly from: string; constructor(private configService: ConfigService) { - console.log("Initializing EmailService..."); // Direct console log for debugging + console.log("Initializing EmailService..."); this.logger.log("Initializing EmailService..."); // Load config @@ -88,7 +88,7 @@ export class EmailService { const info = await this.transporter.sendMail(mailOptions); - console.log("Email sent successfully:", info); // Direct console log + console.log("Email sent successfully:", info); this.logger.log("Email sent successfully:", { messageId: info.messageId, response: info.response, @@ -97,7 +97,7 @@ export class EmailService { envelope: info.envelope, }); } catch (error) { - console.error("Failed to send email:", error); // Direct console log + console.error("Failed to send email:", error); this.logger.error("Failed to send email:", { error: error.message, code: error.code, diff --git a/backend/src/s3/s3.service.ts b/backend/src/s3/s3.service.ts index 42e7e80..045aa25 100644 --- a/backend/src/s3/s3.service.ts +++ b/backend/src/s3/s3.service.ts @@ -1,7 +1,15 @@ -import { Injectable, Logger, InternalServerErrorException } from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; -import { S3Client, PutObjectCommand, GetObjectCommand } from '@aws-sdk/client-s3'; -import { S3File } from '../interfaces/s3-file.interface'; +import { + Injectable, + Logger, + InternalServerErrorException, +} from "@nestjs/common"; +import { ConfigService } from "@nestjs/config"; +import { + S3Client, + PutObjectCommand, + GetObjectCommand, +} from "@aws-sdk/client-s3"; +import { S3File } from "../interfaces/s3-file.interface"; @Injectable() export class S3Service { @@ -10,12 +18,14 @@ export class S3Service { constructor(private readonly configService: ConfigService) { this.s3Client = new S3Client({ - region: this.configService.get('AWS_REGION'), + region: this.configService.get("AWS_REGION"), credentials: { - accessKeyId: this.configService.get('AWS_ACCESS_KEY_ID'), - secretAccessKey: this.configService.get('AWS_SECRET_ACCESS_KEY'), + accessKeyId: this.configService.get("AWS_ACCESS_KEY_ID"), + secretAccessKey: this.configService.get( + "AWS_SECRET_ACCESS_KEY", + ), }, - endpoint: this.configService.get('AWS_ENDPOINT_URL'), + endpoint: this.configService.get("AWS_ENDPOINT_URL"), forcePathStyle: true, // Required for Contabo Object Storage }); } @@ -23,9 +33,9 @@ export class S3Service { async uploadFile(file: Express.Multer.File, folder: string): Promise { try { const key = `${folder}/${Date.now()}-${file.originalname}`; - + const command = new PutObjectCommand({ - Bucket: this.configService.get('AWS_S3_BUCKET_NAME'), + Bucket: this.configService.get("AWS_S3_BUCKET_NAME"), Key: key, Body: file.buffer, ContentType: file.mimetype, @@ -33,23 +43,25 @@ export class S3Service { await this.s3Client.send(command); this.logger.debug(`File uploaded successfully: ${key}`); - + return key; } catch (error) { this.logger.error(`Error uploading file to S3: ${error.message}`); - throw new InternalServerErrorException('Failed to upload file to storage'); + throw new InternalServerErrorException( + "Failed to upload file to storage", + ); } } async getFile(key: string): Promise { try { const command = new GetObjectCommand({ - Bucket: this.configService.get('AWS_S3_BUCKET_NAME'), + Bucket: this.configService.get("AWS_S3_BUCKET_NAME"), Key: key, }); const response = await this.s3Client.send(command); - + const chunks = []; for await (const chunk of response.Body as any) { chunks.push(chunk); @@ -58,15 +70,16 @@ export class S3Service { return { buffer, - contentType: response.ContentType || 'application/octet-stream', + contentType: response.ContentType || "application/octet-stream", contentLength: response.ContentLength || buffer.length, - fileName: key.split('/').pop() || 'download', + fileName: key.split("-").pop() || "download", + // fileName: key.split("-").pop() || "download", }; } catch (error) { this.logger.error(`Error getting file from S3: ${error.message}`); - throw new InternalServerErrorException('Failed to retrieve file from storage'); + throw new InternalServerErrorException( + "Failed to retrieve file from storage", + ); } } } - - \ No newline at end of file diff --git a/frontend/src/services/api.js b/frontend/src/services/api.js index 3da7324..7193730 100644 --- a/frontend/src/services/api.js +++ b/frontend/src/services/api.js @@ -48,7 +48,9 @@ export const downloadDocument = async (key) => { link.href = url; // Extract filename from key - const fileName = key.split("/").pop(); + const fileName = key.split("-").pop(); + // const fileName = key.split("/").pop(); + link.download = fileName; document.body.appendChild(link); @@ -106,7 +108,6 @@ export const getAllUsers = () => api.get("/admin/users"); export const resetUserPassword = (userId, newPassword) => api.post(`/admin/users/${userId}/reset-password`, { password: newPassword }); -// Password reset endpoints export const forgotPassword = (email) => api.post("/auth/forgot-password", { email }); export const resetPassword = (token, newPassword) => @@ -121,4 +122,5 @@ api.interceptors.response.use( return Promise.reject(error); }, ); +export const logout = () => api.post("/auth/logout"); export default api;