From 78b65f148ed0b62ecf6d9969754d86de8c1d0d1b Mon Sep 17 00:00:00 2001 From: Gabriellvl Date: Fri, 18 Apr 2025 23:28:55 +0200 Subject: [PATCH] feat: question + answer checks --- .../middleware/auth/checks/question-checks.ts | 48 +++++++++++++++++++ backend/src/routes/answers.ts | 13 +++-- backend/src/routes/questions.ts | 15 ++++-- backend/src/services/answers.ts | 2 +- 4 files changed, 67 insertions(+), 11 deletions(-) create mode 100644 backend/src/middleware/auth/checks/question-checks.ts diff --git a/backend/src/middleware/auth/checks/question-checks.ts b/backend/src/middleware/auth/checks/question-checks.ts new file mode 100644 index 00000000..63b47e7b --- /dev/null +++ b/backend/src/middleware/auth/checks/question-checks.ts @@ -0,0 +1,48 @@ +import {authorize} from "./auth-checks"; +import {AuthenticationInfo} from "../authentication-info"; +import {AuthenticatedRequest} from "../authenticated-request"; +import {requireFields} from "../../../controllers/error-helper"; +import {getLearningObjectId, getQuestionId} from "../../../controllers/questions"; +import {fetchQuestion} from "../../../services/questions"; +import {FALLBACK_SEQ_NUM} from "../../../config"; +import {fetchAnswer} from "../../../services/answers"; + +export const onlyAllowAuthor = authorize( + (auth: AuthenticationInfo, req: AuthenticatedRequest) => req.body.author === auth.username +); + +export const onlyAllowAuthorRequest = authorize( + (auth: AuthenticationInfo, req: AuthenticatedRequest) => { + const hruid = req.params.hruid; + const version = req.params.version; + const language = req.query.lang as string; + const seq = req.params.seq; + requireFields({ hruid }); + + const learningObjectId = getLearningObjectId(hruid, version, language); + const questionId = getQuestionId(learningObjectId, seq); + + const question = await fetchQuestion(questionId); + + return question.author.username == auth.username; + } +); + +export const onlyAllowAuthorRequestAnswer = authorize( + (auth: AuthenticationInfo, req: AuthenticatedRequest) => { + const hruid = req.params.hruid; + const version = req.params.version; + const language = req.query.lang as string; + const seq = req.params.seq; + const seqAnswer = req.params.seqAnswer; + requireFields({ hruid }); + + const learningObjectId = getLearningObjectId(hruid, version, language); + const questionId = getQuestionId(learningObjectId, seq); + + const sequenceNumber = Number(seqAnswer) || FALLBACK_SEQ_NUM; + const answer = await fetchAnswer(questionId, sequenceNumber); + + return answer.author.username == auth.username; + } +); diff --git a/backend/src/routes/answers.ts b/backend/src/routes/answers.ts index b74f76a0..0f11c173 100644 --- a/backend/src/routes/answers.ts +++ b/backend/src/routes/answers.ts @@ -1,16 +1,19 @@ import express from 'express'; import { createAnswerHandler, deleteAnswerHandler, getAnswerHandler, getAllAnswersHandler, updateAnswerHandler } from '../controllers/answers.js'; +import {adminOnly, authenticatedOnly, teachersOnly} from "../middleware/auth/checks/auth-checks"; +import {onlyAllowAuthor, onlyAllowAuthorRequestAnswer} from "../middleware/auth/checks/question-checks"; + const router = express.Router({ mergeParams: true }); -router.get('/', getAllAnswersHandler); +router.get('/', adminOnly, getAllAnswersHandler); -router.post('/', createAnswerHandler); +router.post('/', teachersOnly, onlyAllowAuthor, createAnswerHandler); -router.get('/:seqAnswer', getAnswerHandler); +router.get('/:seqAnswer', authenticatedOnly, getAnswerHandler); -router.delete('/:seqAnswer', deleteAnswerHandler); +router.delete('/:seqAnswer', teachersOnly, onlyAllowAuthorRequestAnswer, deleteAnswerHandler); -router.put('/:seqAnswer', updateAnswerHandler); +router.put('/:seqAnswer', teachersOnly, onlyAllowAuthorRequestAnswer, updateAnswerHandler); export default router; diff --git a/backend/src/routes/questions.ts b/backend/src/routes/questions.ts index 5135c197..287a242b 100644 --- a/backend/src/routes/questions.ts +++ b/backend/src/routes/questions.ts @@ -1,20 +1,25 @@ import express from 'express'; import { createQuestionHandler, deleteQuestionHandler, getAllQuestionsHandler, getQuestionHandler } from '../controllers/questions.js'; import answerRoutes from './answers.js'; +import {adminOnly, authenticatedOnly, studentsOnly} from "../middleware/auth/checks/auth-checks"; +import {updateAnswerHandler} from "../controllers/answers"; +import {onlyAllowAuthor, onlyAllowAuthorRequest} from "../middleware/auth/checks/question-checks"; const router = express.Router({ mergeParams: true }); // Query language // Root endpoint used to search objects -router.get('/', getAllQuestionsHandler); +router.get('/', adminOnly, getAllQuestionsHandler); -router.post('/', createQuestionHandler); - -router.delete('/:seq', deleteQuestionHandler); +router.post('/', studentsOnly, onlyAllowAuthor, createQuestionHandler); // Information about a question with id -router.get('/:seq', getQuestionHandler); +router.get('/:seq', authenticatedOnly, getQuestionHandler); // TODO every body in group + teachers? + +router.delete('/:seq', studentsOnly, onlyAllowAuthorRequest, deleteQuestionHandler); + +router.put("/:seq", studentsOnly, onlyAllowAuthorRequest, updateAnswerHandler); router.use('/:seq/answers', answerRoutes); diff --git a/backend/src/services/answers.ts b/backend/src/services/answers.ts index ab603883..7ec5773a 100644 --- a/backend/src/services/answers.ts +++ b/backend/src/services/answers.ts @@ -34,7 +34,7 @@ export async function createAnswer(questionId: QuestionId, answerData: AnswerDat return mapToAnswerDTO(answer); } -async function fetchAnswer(questionId: QuestionId, sequenceNumber: number): Promise { +export async function fetchAnswer(questionId: QuestionId, sequenceNumber: number): Promise { const answerRepository = getAnswerRepository(); const question = await fetchQuestion(questionId); const answer = await answerRepository.findAnswer(question, sequenceNumber);