clean up and logOut

This commit is contained in:
dimitar 2025-04-01 05:20:19 +02:00
parent b38261e387
commit 71b73d5fcb
7 changed files with 157 additions and 127 deletions

View File

@ -8,38 +8,38 @@ import {
Request, Request,
Logger, Logger,
BadRequestException, BadRequestException,
} from '@nestjs/common'; } from "@nestjs/common";
import { AuthService } from './auth.service'; import { AuthService } from "./auth.service";
import { LoginDto } from '../dto/login.dto'; import { LoginDto } from "../dto/login.dto";
import { CreateUserDto } from '../dto/create-user.dto'; import { CreateUserDto } from "../dto/create-user.dto";
import { JwtAuthGuard } from './jwt-auth.guard'; import { JwtAuthGuard } from "./jwt-auth.guard";
import { AdminGuard } from './admin.guard'; import { AdminGuard } from "./admin.guard";
//@UseGuards(JwtAuthGuard, AdminGuard) //@UseGuards(JwtAuthGuard, AdminGuard)
@Controller('auth') @Controller("auth")
export class AuthController { export class AuthController {
private readonly logger = new Logger(AuthController.name); private readonly logger = new Logger(AuthController.name);
constructor(private authService: AuthService) { constructor(private authService: AuthService) {
this.logger.log('AuthController initialized'); this.logger.log("AuthController initialized");
} }
@Post('login') @Post("login")
async login(@Body() loginDto: LoginDto) { async login(@Body() loginDto: LoginDto) {
this.logger.log('=== Login endpoint hit ==='); this.logger.log("=== Login endpoint hit ===");
this.logger.debug('Raw request body:', { this.logger.debug("Raw request body:", {
username: loginDto.username, username: loginDto.username,
email: loginDto.email, email: loginDto.email,
hasPassword: !!loginDto.password, hasPassword: !!loginDto.password,
}); });
const email = loginDto.getEmail(); const email = loginDto.getEmail();
this.logger.debug('Normalized login request:', { this.logger.debug("Normalized login request:", {
email, email,
hasPassword: !!loginDto.password, hasPassword: !!loginDto.password,
}); });
try { try {
this.logger.debug('Calling AuthService.validateUser...'); this.logger.debug("Calling AuthService.validateUser...");
const user = await this.authService.validateUser( const user = await this.authService.validateUser(
email, email,
loginDto.password, loginDto.password,
@ -47,20 +47,20 @@ export class AuthController {
if (!user) { if (!user) {
this.logger.warn(`Login failed: Invalid credentials for ${email}`); 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, id: user.id,
email: user.email, email: user.email,
name: user.name, name: user.name,
isAdmin: user.isAdmin, isAdmin: user.isAdmin,
}); });
this.logger.debug('Calling AuthService.login...'); this.logger.debug("Calling AuthService.login...");
const result = await this.authService.login(user); 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, hasAccessToken: !!result.access_token,
user: { user: {
id: result.user.id, id: result.user.id,
@ -76,7 +76,7 @@ export class AuthController {
throw error; throw error;
} }
this.logger.error('Login failed:', { this.logger.error("Login failed:", {
error: error.message, error: error.message,
stack: error.stack, stack: error.stack,
body: { body: {
@ -85,35 +85,35 @@ export class AuthController {
hasPassword: !!loginDto.password, 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) { async register(@Body() createUserDto: CreateUserDto) {
console.log('=== Registration endpoint hit ==='); console.log("=== Registration endpoint hit ===");
this.logger.log('=== Registration endpoint hit ==='); this.logger.log("=== Registration endpoint hit ===");
console.log('Registration request received:', createUserDto); console.log("Registration request received:", createUserDto);
this.logger.log('Registration request received:', { this.logger.log("Registration request received:", {
email: createUserDto.email, email: createUserDto.email,
name: createUserDto.name, name: createUserDto.name,
hasPassword: !!createUserDto.password hasPassword: !!createUserDto.password,
}); });
try { try {
console.log('Calling AuthService.createUser...'); console.log("Calling AuthService.createUser...");
this.logger.log('Calling AuthService.createUser...'); this.logger.log("Calling AuthService.createUser...");
const result = await this.authService.createUser(createUserDto); const result = await this.authService.createUser(createUserDto);
console.log('Registration successful:', result); console.log("Registration successful:", result);
this.logger.log('Registration successful:', { this.logger.log("Registration successful:", {
id: result.id, id: result.id,
email: result.email, email: result.email,
name: result.name, name: result.name,
}); });
return result; return result;
} catch (error) { } catch (error) {
console.error('Registration failed:', error); console.error("Registration failed:", error);
this.logger.error('Registration failed:', { this.logger.error("Registration failed:", {
error: error.message, error: error.message,
code: error.code, code: error.code,
command: error.command, command: error.command,
@ -124,25 +124,25 @@ export class AuthController {
} }
//@UseGuards(JwtAuthGuard) //@UseGuards(JwtAuthGuard)
@Post('create-admin') @Post("create-admin")
async createAdmin(@Body() createUserDto: CreateUserDto) { async createAdmin(@Body() createUserDto: CreateUserDto) {
this.logger.log('=== Create admin endpoint hit ==='); this.logger.log("=== Create admin endpoint hit ===");
this.logger.debug('Admin creation request received:', { this.logger.debug("Admin creation request received:", {
email: createUserDto.email, email: createUserDto.email,
name: createUserDto.name, name: createUserDto.name,
}); });
try { 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); const result = await this.authService.createUser(createUserDto, true);
this.logger.debug('Admin creation successful:', { this.logger.debug("Admin creation successful:", {
id: result.id, id: result.id,
email: result.email, email: result.email,
name: result.name, name: result.name,
}); });
return result; return result;
} catch (error) { } catch (error) {
this.logger.error('Admin creation failed:', { this.logger.error("Admin creation failed:", {
error: error.message, error: error.message,
stack: error.stack, stack: error.stack,
}); });
@ -151,29 +151,37 @@ export class AuthController {
} }
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@Get('user-info') @Get("user-info")
async getUserInfo(@Request() req) { async getUserInfo(@Request() req) {
return this.authService.getUserInfo(req.user.userId); return this.authService.getUserInfo(req.user.userId);
} }
@Post('forgot-password') @Post("forgot-password")
async forgotPassword(@Body() { email }: { email: string }) { async forgotPassword(@Body() { email }: { email: string }) {
if (!email) { if (!email) {
throw new BadRequestException('Email is required'); throw new BadRequestException("Email is required");
} }
return this.authService.sendPasswordResetToken(email); return this.authService.sendPasswordResetToken(email);
} }
@Post('reset-password') @Post("reset-password")
async resetPassword( async resetPassword(
@Body() { token, newPassword }: { token: string; newPassword: string } @Body() { token, newPassword }: { token: string; newPassword: string },
) { ) {
if (!token || !newPassword) { 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) { 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); return this.authService.resetPasswordWithToken(token, newPassword);
} }
@Post("logout")
async logout(@Request() req) {
await this.authService.logout(req.user.userId);
return { message: "Logged out successfully" };
}
} }

View File

@ -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) { async getUserInfo(userId: number) {
if (!userId) { if (!userId) {
throw new Error("User ID is required"); throw new Error("User ID is required");
@ -327,9 +316,9 @@ export class AuthService {
where: { where: {
token, token,
expiresAt: { expiresAt: {
gt: new Date(), // Token must not be expired gt: new Date(),
}, },
used: false, // Token must not be used used: false,
}, },
include: { include: {
user: true, user: true,
@ -386,4 +375,9 @@ export class AuthService {
throw error; throw error;
} }
} }
async logout(userId: string): Promise<void> {
return console.log("User logged out:", userId);
this.logger.log("User logged out:", userId);
}
} }

View File

@ -1,5 +1,5 @@
export const getDatabaseUrl = () => { export const getDatabaseUrl = () => {
const url = process.env.DATABASE_URL; const url = process.env.DATABASE_URL;
console.log("Database URL:", url); // For debugging console.log("Database URL:", url);
return url; return url;
}; };

View File

@ -1,9 +1,21 @@
import { Controller, Get, Param, Req, Res, UseGuards, Logger, Request, Post, UseInterceptors, UploadedFile, Body } from '@nestjs/common'; import {
import { Response } from 'express'; Body,
import { DocumentsService } from './documents.service'; Controller,
import { JwtAuthGuard } from '../auth/jwt-auth.guard'; Get,
import { S3Service } from '../s3/s3.service'; Logger,
import { FileInterceptor } from '@nestjs/platform-express'; 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 { interface S3File {
buffer: Buffer; buffer: Buffer;
@ -12,28 +24,28 @@ interface S3File {
fileName: string; fileName: string;
} }
@Controller('documents') @Controller("documents")
export class DocumentsController { export class DocumentsController {
private readonly logger = new Logger(DocumentsController.name); private readonly logger = new Logger(DocumentsController.name);
constructor( constructor(
private readonly documentsService: DocumentsService, 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) @UseGuards(JwtAuthGuard)
@UseInterceptors(FileInterceptor('file')) @UseInterceptors(FileInterceptor("file"))
async uploadDocument( async uploadDocument(
@UploadedFile() file: Express.Multer.File, @UploadedFile() file: Express.Multer.File,
@Body('title') title: string, @Body("title") title: string,
@Body('sharedWithId') sharedWithId: string, @Body("sharedWithId") sharedWithId: string,
@Request() req, @Request() req,
) { ) {
this.logger.log('=== Document upload endpoint hit ==='); this.logger.log("=== Document upload endpoint hit ===");
this.logger.debug('Upload request received:', { this.logger.debug("Upload request received:", {
fileName: file?.originalname, fileName: file?.originalname,
fileSize: file?.size, fileSize: file?.size,
title, title,
@ -49,7 +61,7 @@ export class DocumentsController {
req.user.id, req.user.id,
); );
this.logger.debug('Document upload successful:', { this.logger.debug("Document upload successful:", {
documentId: result.id, documentId: result.id,
title: result.title, title: result.title,
s3Key: result.s3Key, s3Key: result.s3Key,
@ -57,7 +69,7 @@ export class DocumentsController {
return result; return result;
} catch (error) { } catch (error) {
this.logger.error('Document upload failed:', { this.logger.error("Document upload failed:", {
error: error.message, error: error.message,
stack: error.stack, stack: error.stack,
}); });
@ -65,18 +77,18 @@ export class DocumentsController {
} }
} }
@Get('shared/:userId') @Get("shared/:userId")
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
async getSharedDocuments(@Param('userId') userId: string) { async getSharedDocuments(@Param("userId") userId: string) {
return this.documentsService.getClientDocuments(Number(userId)); return this.documentsService.getClientDocuments(Number(userId));
} }
@Get('download/:key') @Get("download/:key")
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
async downloadDocument( async downloadDocument(
@Param('key') key: string, @Param("key") key: string,
@Request() req, @Request() req,
@Res() res: Response @Res() res: Response,
) { ) {
try { try {
this.logger.debug(`Download request for key: ${key}`); this.logger.debug(`Download request for key: ${key}`);
@ -85,41 +97,42 @@ export class DocumentsController {
this.logger.debug(`Decoded key: ${decodedKey}`); this.logger.debug(`Decoded key: ${decodedKey}`);
// Get document from database first to verify access // Get document from database first to verify access
const document = await this.documentsService.findDocumentByS3Key(decodedKey); const document =
await this.documentsService.findDocumentByS3Key(decodedKey);
if (!document) { 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 // Verify user has access to this document
const hasAccess = await this.documentsService.verifyDocumentAccess( const hasAccess = await this.documentsService.verifyDocumentAccess(
document.id, document.id,
req.user.id req.user.id,
); );
if (!hasAccess) { if (!hasAccess) {
return res.status(403).json({ message: 'Access denied' }); return res.status(403).json({ message: "Access denied" });
} }
// Get the file from S3 // Get the file from S3
const file = await this.s3Service.getFile(decodedKey); const file = await this.s3Service.getFile(decodedKey);
if (!file || !file.buffer) { 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({ res.set({
'Content-Type': file.contentType || 'application/octet-stream', "Content-Type": file.contentType || "application/octet-stream",
'Content-Length': file.contentLength, "Content-Length": file.contentLength,
'Content-Disposition': `attachment; filename="${encodeURIComponent(file.fileName)}"`, "Content-Disposition": `attachment; filename="${encodeURIComponent(file.fileName)}"`,
}); });
return res.send(file.buffer); return res.send(file.buffer);
} catch (error) { } catch (error) {
this.logger.error('Download error:', error); this.logger.error("Download error:", error);
return res.status(500).json({ return res.status(500).json({
message: 'Failed to download file', message: "Failed to download file",
error: error.message error: error.message,
}); });
} }
} }

View File

@ -9,7 +9,7 @@ export class EmailService {
private readonly from: string; private readonly from: string;
constructor(private configService: ConfigService) { constructor(private configService: ConfigService) {
console.log("Initializing EmailService..."); // Direct console log for debugging console.log("Initializing EmailService...");
this.logger.log("Initializing EmailService..."); this.logger.log("Initializing EmailService...");
// Load config // Load config
@ -88,7 +88,7 @@ export class EmailService {
const info = await this.transporter.sendMail(mailOptions); 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:", { this.logger.log("Email sent successfully:", {
messageId: info.messageId, messageId: info.messageId,
response: info.response, response: info.response,
@ -97,7 +97,7 @@ export class EmailService {
envelope: info.envelope, envelope: info.envelope,
}); });
} catch (error) { } 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:", { this.logger.error("Failed to send email:", {
error: error.message, error: error.message,
code: error.code, code: error.code,

View File

@ -1,7 +1,15 @@
import { Injectable, Logger, InternalServerErrorException } from '@nestjs/common'; import {
import { ConfigService } from '@nestjs/config'; Injectable,
import { S3Client, PutObjectCommand, GetObjectCommand } from '@aws-sdk/client-s3'; Logger,
import { S3File } from '../interfaces/s3-file.interface'; 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() @Injectable()
export class S3Service { export class S3Service {
@ -10,12 +18,14 @@ export class S3Service {
constructor(private readonly configService: ConfigService) { constructor(private readonly configService: ConfigService) {
this.s3Client = new S3Client({ this.s3Client = new S3Client({
region: this.configService.get<string>('AWS_REGION'), region: this.configService.get<string>("AWS_REGION"),
credentials: { credentials: {
accessKeyId: this.configService.get<string>('AWS_ACCESS_KEY_ID'), accessKeyId: this.configService.get<string>("AWS_ACCESS_KEY_ID"),
secretAccessKey: this.configService.get<string>('AWS_SECRET_ACCESS_KEY'), secretAccessKey: this.configService.get<string>(
"AWS_SECRET_ACCESS_KEY",
),
}, },
endpoint: this.configService.get<string>('AWS_ENDPOINT_URL'), endpoint: this.configService.get<string>("AWS_ENDPOINT_URL"),
forcePathStyle: true, // Required for Contabo Object Storage forcePathStyle: true, // Required for Contabo Object Storage
}); });
} }
@ -25,7 +35,7 @@ export class S3Service {
const key = `${folder}/${Date.now()}-${file.originalname}`; const key = `${folder}/${Date.now()}-${file.originalname}`;
const command = new PutObjectCommand({ const command = new PutObjectCommand({
Bucket: this.configService.get<string>('AWS_S3_BUCKET_NAME'), Bucket: this.configService.get<string>("AWS_S3_BUCKET_NAME"),
Key: key, Key: key,
Body: file.buffer, Body: file.buffer,
ContentType: file.mimetype, ContentType: file.mimetype,
@ -37,14 +47,16 @@ export class S3Service {
return key; return key;
} catch (error) { } catch (error) {
this.logger.error(`Error uploading file to S3: ${error.message}`); 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<S3File> { async getFile(key: string): Promise<S3File> {
try { try {
const command = new GetObjectCommand({ const command = new GetObjectCommand({
Bucket: this.configService.get<string>('AWS_S3_BUCKET_NAME'), Bucket: this.configService.get<string>("AWS_S3_BUCKET_NAME"),
Key: key, Key: key,
}); });
@ -58,15 +70,16 @@ export class S3Service {
return { return {
buffer, buffer,
contentType: response.ContentType || 'application/octet-stream', contentType: response.ContentType || "application/octet-stream",
contentLength: response.ContentLength || buffer.length, contentLength: response.ContentLength || buffer.length,
fileName: key.split('/').pop() || 'download', fileName: key.split("-").pop() || "download",
// fileName: key.split("-").pop() || "download",
}; };
} catch (error) { } catch (error) {
this.logger.error(`Error getting file from S3: ${error.message}`); 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",
);
} }
} }
} }

View File

@ -48,7 +48,9 @@ export const downloadDocument = async (key) => {
link.href = url; link.href = url;
// Extract filename from key // Extract filename from key
const fileName = key.split("/").pop(); const fileName = key.split("-").pop();
// const fileName = key.split("/").pop();
link.download = fileName; link.download = fileName;
document.body.appendChild(link); document.body.appendChild(link);
@ -106,7 +108,6 @@ export const getAllUsers = () => api.get("/admin/users");
export const resetUserPassword = (userId, newPassword) => export const resetUserPassword = (userId, newPassword) =>
api.post(`/admin/users/${userId}/reset-password`, { password: newPassword }); api.post(`/admin/users/${userId}/reset-password`, { password: newPassword });
// Password reset endpoints
export const forgotPassword = (email) => export const forgotPassword = (email) =>
api.post("/auth/forgot-password", { email }); api.post("/auth/forgot-password", { email });
export const resetPassword = (token, newPassword) => export const resetPassword = (token, newPassword) =>
@ -121,4 +122,5 @@ api.interceptors.response.use(
return Promise.reject(error); return Promise.reject(error);
}, },
); );
export const logout = () => api.post("/auth/logout");
export default api; export default api;