added middleware

This commit is contained in:
OkaykOrhmn 2025-07-24 09:24:37 +03:30
parent f3d0659c39
commit a5f76f01ad
10 changed files with 75 additions and 69 deletions

View File

@ -1,8 +1,9 @@
import { Controller, Get, Post, Patch, Delete, Param, Body, UsePipes, ValidationPipe, Query, ParseUUIDPipe, HttpCode, HttpStatus } from '@nestjs/common'; import { Controller, Get, Post, Patch, Delete, Param, Body, UsePipes, ValidationPipe, Query, ParseUUIDPipe, HttpCode, HttpStatus, Request } from '@nestjs/common';
import { AnswerService } from './answer.service'; import { AnswerService } from './answer.service';
import { CreateAnswerDto } from './dto/create-answer.dto'; import { CreateAnswerDto } from './dto/create-answer.dto';
import { UpdateAnswerDto } from './dto/update-answer.dto'; import { UpdateAnswerDto } from './dto/update-answer.dto';
import { Answer } from './entity/answer.entity'; import { Answer } from './entity/answer.entity';
import { AuthRequest } from '../middleware/jwtMiddleware';
@Controller('answers') @Controller('answers')
export class AnswerController { export class AnswerController {
@ -10,8 +11,8 @@ export class AnswerController {
@Post() @Post()
@UsePipes(new ValidationPipe({ transform: true, whitelist: true })) @UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
async create(@Body() body: CreateAnswerDto): Promise<Answer> { async create(@Body() body: CreateAnswerDto, @Request() req: AuthRequest): Promise<Answer> {
return this.answerService.create(body); return this.answerService.create(body, req.user);
} }
@Get('findAll') @Get('findAll')

View File

@ -1,11 +1,10 @@
import { Injectable, InternalServerErrorException, NotFoundException } from '@nestjs/common'; import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { Answer } from './entity/answer.entity'; import { Answer } from './entity/answer.entity';
import { CreateAnswerDto } from './dto/create-answer.dto'; import { CreateAnswerDto } from './dto/create-answer.dto';
import { UpdateAnswerDto } from './dto/update-answer.dto'; import { UpdateAnswerDto } from './dto/update-answer.dto';
import { CreateAttachmentDto } from '../attachment/dto/create_attachment.dto'; import { AuthRequest } from '../middleware/jwtMiddleware';
import { Attachment } from '../attachment/entity/attachment.entity';
@Injectable() @Injectable()
export class AnswerService { export class AnswerService {
@ -14,18 +13,15 @@ export class AnswerService {
private readonly answerRepository: Repository<Answer>, private readonly answerRepository: Repository<Answer>,
) {} ) {}
async create(data: CreateAnswerDto, token: any): Promise<Answer> { async create(data: CreateAnswerDto, user: AuthRequest['user']): Promise<Answer> {
if (!token || !token.sub) {
throw new InternalServerErrorException('Failed to extract sub from token');
}
const answer = this.answerRepository.create({ const answer = this.answerRepository.create({
...data, ...data,
participantId: token.sub, // token.sub is userId, but we want participantId ?! participantId: user.sub,
}); });
return await this.answerRepository.save(answer); return await this.answerRepository.save(answer);
} }
async findAll( page = 1, limit = 10 ): Promise<{ async findAll(page = 1, limit = 10): Promise<{
docs: Answer[]; docs: Answer[];
totalDocs: number; totalDocs: number;
limit: number; limit: number;

View File

@ -1,8 +1,9 @@
import { Controller, Get, Post, Patch, Delete, Param, Body, UsePipes, ValidationPipe, Query, ParseUUIDPipe, HttpCode, HttpStatus } from '@nestjs/common'; import { Controller, Get, Post, Patch, Delete, Param, Body, UsePipes, ValidationPipe, Query, ParseUUIDPipe, HttpCode, HttpStatus, Request } from '@nestjs/common';
import { AttachmentService } from './attachment.service'; import { AttachmentService } from './attachment.service';
import { CreateAttachmentDto } from './dto/create_attachment.dto'; import { CreateAttachmentDto } from './dto/create_attachment.dto';
import { UpdateAttachmentDto } from './dto/update_attachment.dto'; import { UpdateAttachmentDto } from './dto/update_attachment.dto';
import { Attachment } from './entity/attachment.entity'; import { Attachment } from './entity/attachment.entity';
import { AuthRequest } from '../middleware/jwtMiddleware';
@Controller('attachments') @Controller('attachments')
export class AttachmentController { export class AttachmentController {
@ -10,8 +11,8 @@ export class AttachmentController {
@Post() @Post()
@UsePipes(new ValidationPipe({ transform: true, whitelist: true })) @UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
async create(@Body() body: CreateAttachmentDto): Promise<Attachment> { async create(@Body() body: CreateAttachmentDto, @Request() req: AuthRequest): Promise<Attachment> {
return this.attachmentService.create(body); return this.attachmentService.create(body, req.user);
} }
@Get('findAll') @Get('findAll')

View File

@ -1,11 +1,10 @@
import { Injectable, InternalServerErrorException, NotFoundException } from '@nestjs/common'; import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { Attachment } from './entity/attachment.entity'; import { Attachment } from './entity/attachment.entity';
import { CreateAttachmentDto } from './dto/create_attachment.dto'; import { CreateAttachmentDto } from './dto/create_attachment.dto';
import { UpdateAttachmentDto } from './dto/update_attachment.dto'; import { UpdateAttachmentDto } from './dto/update_attachment.dto';
import axios from 'axios'; import { AuthRequest } from '../middleware/jwtMiddleware';
import { decode } from 'jsonwebtoken';
@Injectable() @Injectable()
export class AttachmentService { export class AttachmentService {
@ -14,15 +13,10 @@ export class AttachmentService {
private readonly attachmentRepository: Repository<Attachment>, private readonly attachmentRepository: Repository<Attachment>,
) {} ) {}
async create(data: CreateAttachmentDto, token: any): Promise<Attachment> { async create(data: CreateAttachmentDto, user: AuthRequest['user']): Promise<Attachment> {
// Extract sub from decoded token
if (!token || !token.sub) {
throw new InternalServerErrorException('Failed to extract sub from token');
}
// Create the attachment with ownerId set to token.sub
const attachment = this.attachmentRepository.create({ const attachment = this.attachmentRepository.create({
...data, ...data,
ownerId: token.sub, // Set ownerId to the token's sub ownerId: user.sub,
}); });
return await this.attachmentRepository.save(attachment); return await this.attachmentRepository.save(attachment);
} }

View File

@ -14,6 +14,7 @@ export class CreateAttachmentDto extends BaseDto {
@IsUrl() @IsUrl()
storageUrl: string; storageUrl: string;
@IsUUID('4') // @IsUUID('4')
ownerId: string; // @IsOptional()
// ownerId?: string;
} }

View File

@ -45,7 +45,7 @@ export class FormResultController {
} }
@Get(':id/refresh') @Get(':id/refresh')
async getFormStatistics( // updates data in database and then returns async getFormStatistics( // updates data in database and then returns it
@Param('formId', new ParseUUIDPipe()) formId: string, @Param('formId', new ParseUUIDPipe()) formId: string,
): Promise<FormResult> { ): Promise<FormResult> {
return this.formResultService.getFormStatistics(formId); return this.formResultService.getFormStatistics(formId);

View File

@ -1,9 +1,11 @@
import { NestFactory } from '@nestjs/core'; import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module'; import { AppModule } from './app.module';
import { JwtMiddleware } from './middleware/jwtMiddleware';
async function bootstrap() { async function bootstrap() {
const app = await NestFactory.create(AppModule); const app = await NestFactory.create(AppModule);
app.enableCors({ origin: 'http://localhost:3001' }); app.enableCors({ origin: 'http://localhost:3001' });
app.use(new JwtMiddleware().use);
await app.listen(process.env.PORT ?? 3000); await app.listen(process.env.PORT ?? 3000);
} }
bootstrap(); bootstrap();

View File

@ -0,0 +1,38 @@
import { Injectable, NestMiddleware, UnauthorizedException } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import * as jwt from 'jsonwebtoken';
import { validate as isValidUUID } from 'uuid';
@Injectable()
export class JwtMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
throw new UnauthorizedException('Missing or invalid Authorization header');
}
const token = authHeader.split(' ')[1];
try {
const decoded = jwt.decode(token) as any;
if (!decoded || !decoded.sub) {
throw new UnauthorizedException('Invalid token: missing sub');
}
if (!isValidUUID(decoded.sub)) {
throw new UnauthorizedException('Invalid token: sub is not a valid UUIDv4');
}
const { sub, realm_access, resource_access } = decoded;
const roles = [ // extract the role you wanna use in guards
...(realm_access?.roles || []),
// ...(resource_access?.account?.roles || []),
];
req['user'] = { sub, roles };
next();
} catch (error) {
throw new UnauthorizedException('Invalid token');
}
}
}
export interface AuthRequest extends Request {
user: { sub: string; roles: string[] };
}

View File

@ -1,8 +1,9 @@
import { Controller, Get, Post, Patch, Delete, Param, Body, UsePipes, ValidationPipe, Query } from '@nestjs/common'; import { Controller, Get, Post, Patch, Delete, Param, Body, UsePipes, ValidationPipe, Query, Request } from '@nestjs/common';
import { ParticipantService } from './participant.service'; import { ParticipantService } from './participant.service';
import { CreateParticipantDto } from './dto/create-participant.dto'; import { CreateParticipantDto } from './dto/create-participant.dto';
import { UpdateParticipantDto } from './dto/update-participant.dto'; import { UpdateParticipantDto } from './dto/update-participant.dto';
import { Participant } from './entity/participant.entity'; import { Participant } from './entity/participant.entity';
import { AuthRequest } from '../middleware/jwtMiddleware';
@Controller('participants') @Controller('participants')
export class ParticipantController { export class ParticipantController {
@ -10,8 +11,8 @@ export class ParticipantController {
@Post() @Post()
@UsePipes(new ValidationPipe({ transform: true })) @UsePipes(new ValidationPipe({ transform: true }))
async create(@Body() body: CreateParticipantDto): Promise<Participant> { async create(@Body() body: CreateParticipantDto, @Request() req: AuthRequest): Promise<Participant> {
return this.participantService.create(body); return this.participantService.create(body, req.user);
} }
@Get('findAll') @Get('findAll')

View File

@ -1,11 +1,10 @@
import { Injectable, InternalServerErrorException } from '@nestjs/common'; import { Injectable, InternalServerErrorException, ConflictException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { CreateParticipantDto } from './dto/create-participant.dto'; import { CreateParticipantDto } from './dto/create-participant.dto';
import { Participant } from './entity/participant.entity'; import { Participant } from './entity/participant.entity';
import { UpdateParticipantDto } from './dto/update-participant.dto'; import { UpdateParticipantDto } from './dto/update-participant.dto';
import axios from 'axios'; import { AuthRequest } from '../middleware/jwtMiddleware';
import { decode } from 'jsonwebtoken';
@Injectable() @Injectable()
export class ParticipantService { export class ParticipantService {
@ -14,51 +13,24 @@ export class ParticipantService {
private readonly participantRepository: Repository<Participant>, private readonly participantRepository: Repository<Participant>,
) {} ) {}
async create(data: CreateParticipantDto): Promise<Participant> { async create(data: CreateParticipantDto, user: AuthRequest['user']): Promise<Participant> {
try { try {
// Here we send a sample request, in final implementation, there'll be no need for that, we'll get the decoded token const existingParticipant = await this.participantRepository.findOne({ where: { userId: user.sub } });
const authResponse = await axios.post(
'https://auth.didvan.com/realms/didvan/protocol/openid-connect/token',
new URLSearchParams({
client_id: 'didvan-app',
username: 'bob',
password: 'developer_password',
grant_type: 'password',
}),
{
headers: {
'content-type': 'application/x-www-form-urlencoded',
},
},
);
// Decode the access token to extract the 'sub' claim
const token = authResponse.data.access_token;
const decodedToken: any = decode(token);
if (!decodedToken || !decodedToken.sub) {
throw new InternalServerErrorException('Failed to decode token or extract sub');
}
// Check if participant with this sub already exists
const existingParticipant = await this.participantRepository.findOne({ where: { id: decodedToken.sub } });
if (existingParticipant) { if (existingParticipant) {
throw new InternalServerErrorException(`Participant with ID ${decodedToken.sub} already exists`); throw new ConflictException(`Participant with userId ${user.sub} already exists`);
} }
// Create the participant with the 'sub' as the ID
const participant = this.participantRepository.create({ const participant = this.participantRepository.create({
...data, ...data,
// id: , let it assign a random id userId: user.sub,
userId: decodedToken.sub, // Set userId to the same sub role: data.role || 'user',
metadata: data.metadata
? { entries: data.metadata.entries }
: { entries: [] },
status: data.status || 'active',
}); });
// Save the participant to the database
return await this.participantRepository.save(participant); return await this.participantRepository.save(participant);
} catch (error) { } catch (error) {
if (error instanceof ConflictException) {
throw error;
}
throw new InternalServerErrorException(`Failed to create participant: ${error.message}`); throw new InternalServerErrorException(`Failed to create participant: ${error.message}`);
} }
} }