added middleware
This commit is contained in:
parent
f3d0659c39
commit
a5f76f01ad
|
|
@ -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')
|
||||||
|
|
|
||||||
|
|
@ -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,13 +13,10 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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')
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ export class CreateAttachmentDto extends BaseDto {
|
||||||
@IsUrl()
|
@IsUrl()
|
||||||
storageUrl: string;
|
storageUrl: string;
|
||||||
|
|
||||||
@IsUUID('4')
|
// @IsUUID('4')
|
||||||
ownerId: string;
|
// @IsOptional()
|
||||||
|
// ownerId?: string;
|
||||||
}
|
}
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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[] };
|
||||||
|
}
|
||||||
|
|
@ -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')
|
||||||
|
|
|
||||||
|
|
@ -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}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue