style: fix linting issues met Prettier

This commit is contained in:
Lint Action 2025-04-22 16:04:52 +00:00
parent 7f670030a7
commit 0c47546814
30 changed files with 233 additions and 262 deletions

View file

@ -1,11 +1,11 @@
import {envVars, getEnvVar} from '../../util/envVars.js';
import {expressjwt} from 'express-jwt';
import { envVars, getEnvVar } from '../../util/envVars.js';
import { expressjwt } from 'express-jwt';
import * as jwt from 'jsonwebtoken';
import {JwtPayload} from 'jsonwebtoken';
import { JwtPayload } from 'jsonwebtoken';
import jwksClient from 'jwks-rsa';
import * as express from 'express';
import {AuthenticatedRequest} from './authenticated-request.js';
import {AuthenticationInfo} from './authentication-info.js';
import { AuthenticatedRequest } from './authenticated-request.js';
import { AuthenticationInfo } from './authentication-info.js';
import { UnauthorizedException } from '../../exceptions/unauthorized-exception.js';
const JWKS_CACHE = true;

View file

@ -1,7 +1,7 @@
import { Request } from 'express';
import { JwtPayload } from 'jsonwebtoken';
import { AuthenticationInfo } from './authentication-info.js';
import * as core from "express-serve-static-core";
import * as core from 'express-serve-static-core';
export interface AuthenticatedRequest<
P = core.ParamsDictionary,
@ -9,7 +9,7 @@ export interface AuthenticatedRequest<
ReqBody = unknown,
ReqQuery = core.Query,
Locals extends Record<string, unknown> = Record<string, unknown>,
> extends Request<P,ResBody,ReqBody,ReqQuery,Locals> {
> extends Request<P, ResBody, ReqBody, ReqQuery, Locals> {
// Properties are optional since the user is not necessarily authenticated.
jwtPayload?: JwtPayload;
auth?: AuthenticationInfo;

View file

@ -1,7 +1,7 @@
import {authorize} from "./auth-checks";
import {fetchClass} from "../../../services/classes";
import {fetchAllGroups} from "../../../services/groups";
import {mapToUsername} from "../../../interfaces/user";
import { authorize } from './auth-checks';
import { fetchClass } from '../../../services/classes';
import { fetchAllGroups } from '../../../services/groups';
import { mapToUsername } from '../../../interfaces/user';
/**
* Expects the path to contain the path parameters 'classId' and 'id' (meaning the ID of the assignment).
@ -9,15 +9,12 @@ import {mapToUsername} from "../../../interfaces/user";
* - either teachers of the class the assignment was posted in,
* - or students in a group of the assignment.
*/
export const onlyAllowIfHasAccessToAssignment = authorize(
async (auth, req) => {
const { classid: classId, id: assignmentId } = req.params as { classid: string, id: number };
if (auth.accountType === "teacher") {
const clazz = await fetchClass(classId);
return clazz.teachers.map(mapToUsername).includes(auth.username);
}
const groups = await fetchAllGroups(classId, assignmentId);
return groups.some(group => group.members.map((member) => member.username).includes(auth.username) );
export const onlyAllowIfHasAccessToAssignment = authorize(async (auth, req) => {
const { classid: classId, id: assignmentId } = req.params as { classid: string; id: number };
if (auth.accountType === 'teacher') {
const clazz = await fetchClass(classId);
return clazz.teachers.map(mapToUsername).includes(auth.username);
}
);
const groups = await fetchAllGroups(classId, assignmentId);
return groups.some((group) => group.members.map((member) => member.username).includes(auth.username));
});

View file

@ -1,9 +1,9 @@
import {AuthenticationInfo} from "../authentication-info";
import {AuthenticatedRequest} from "../authenticated-request";
import * as express from "express";
import {UnauthorizedException} from "../../../exceptions/unauthorized-exception";
import {ForbiddenException} from "../../../exceptions/forbidden-exception";
import {RequestHandler} from "express";
import { AuthenticationInfo } from '../authentication-info';
import { AuthenticatedRequest } from '../authenticated-request';
import * as express from 'express';
import { UnauthorizedException } from '../../../exceptions/unauthorized-exception';
import { ForbiddenException } from '../../../exceptions/forbidden-exception';
import { RequestHandler } from 'express';
/**
* Middleware which rejects unauthenticated users (with HTTP 401) and authenticated users which do not fulfill
@ -11,13 +11,17 @@ import {RequestHandler} from "express";
* @param accessCondition Predicate over the current AuthenticationInfo. Access is only granted when this evaluates
* to true.
*/
export function authorize<P,ResBody,ReqBody,ReqQuery,Locals extends Record<string, unknown>>(
accessCondition: (auth: AuthenticationInfo, req: AuthenticatedRequest<P,ResBody,ReqBody,ReqQuery,Locals>) => boolean | Promise<boolean>
): RequestHandler<P,ResBody,ReqBody,ReqQuery,Locals> {
return async (req: AuthenticatedRequest<P,ResBody,ReqBody,ReqQuery,Locals>, _res: express.Response, next: express.NextFunction): Promise<void> => {
export function authorize<P, ResBody, ReqBody, ReqQuery, Locals extends Record<string, unknown>>(
accessCondition: (auth: AuthenticationInfo, req: AuthenticatedRequest<P, ResBody, ReqBody, ReqQuery, Locals>) => boolean | Promise<boolean>
): RequestHandler<P, ResBody, ReqBody, ReqQuery, Locals> {
return async (
req: AuthenticatedRequest<P, ResBody, ReqBody, ReqQuery, Locals>,
_res: express.Response,
next: express.NextFunction
): Promise<void> => {
if (!req.auth) {
throw new UnauthorizedException();
} else if (!await accessCondition(req.auth, req)) {
} else if (!(await accessCondition(req.auth, req))) {
throw new ForbiddenException();
} else {
next();

View file

@ -1,8 +1,8 @@
import {authorize} from "./auth-checks";
import {AuthenticationInfo} from "../authentication-info";
import {AuthenticatedRequest} from "../authenticated-request";
import {fetchClass} from "../../../services/classes";
import {mapToUsername} from "../../../interfaces/user";
import { authorize } from './auth-checks';
import { AuthenticationInfo } from '../authentication-info';
import { AuthenticatedRequest } from '../authenticated-request';
import { fetchClass } from '../../../services/classes';
import { mapToUsername } from '../../../interfaces/user';
async function teaches(teacherUsername: string, classId: string): Promise<boolean> {
const clazz = await fetchClass(classId);
@ -14,53 +14,45 @@ async function teaches(teacherUsername: string, classId: string): Promise<boolea
* Only allows requests whose username parameter is equal to the username of the user who is logged in and requests
* whose classId parameter references a class the logged-in user is a teacher of.
*/
export const onlyAllowStudentHimselfAndTeachersOfClass = authorize(
async (auth: AuthenticationInfo, req: AuthenticatedRequest) => {
if (req.params.username === auth.username) {
return true;
} else if (auth.accountType === "teacher") {
return teaches(auth.username, req.params.classId);
}
return false;
export const onlyAllowStudentHimselfAndTeachersOfClass = authorize(async (auth: AuthenticationInfo, req: AuthenticatedRequest) => {
if (req.params.username === auth.username) {
return true;
} else if (auth.accountType === 'teacher') {
return teaches(auth.username, req.params.classId);
}
);
return false;
});
/**
* Only let the request pass through if its path parameter "username" is the username of the currently logged-in
* teacher and the path parameter "classId" refers to a class the teacher teaches.
*/
export const onlyAllowTeacherOfClass = authorize(
async (auth: AuthenticationInfo, req: AuthenticatedRequest) =>
req.params.username === auth.username && teaches(auth.username, req.params.classId),
async (auth: AuthenticationInfo, req: AuthenticatedRequest) => req.params.username === auth.username && teaches(auth.username, req.params.classId)
);
/**
* Only let the request pass through if the class id in it refers to a class the current user is in (as a student
* or teacher)
*/
export const onlyAllowIfInClass = authorize(
async (auth: AuthenticationInfo, req: AuthenticatedRequest) => {
const classId = req.params.classId ?? req.params.classid ?? req.params.id;
const clazz = await fetchClass(classId);
if (auth.accountType === "teacher") {
return clazz.teachers.map(mapToUsername).includes(auth.username);
}
return clazz.students.map(mapToUsername).includes(auth.username);
export const onlyAllowIfInClass = authorize(async (auth: AuthenticationInfo, req: AuthenticatedRequest) => {
const classId = req.params.classId ?? req.params.classid ?? req.params.id;
const clazz = await fetchClass(classId);
if (auth.accountType === 'teacher') {
return clazz.teachers.map(mapToUsername).includes(auth.username);
}
);
return clazz.students.map(mapToUsername).includes(auth.username);
});
/**
* Only allows the request to pass if the 'class' property in its body is a class the current user is a member of.
*/
export const onlyAllowOwnClassInBody = authorize(
async (auth, req) => {
const classId = (req.body as {class: string})?.class;
const clazz = await fetchClass(classId);
export const onlyAllowOwnClassInBody = authorize(async (auth, req) => {
const classId = (req.body as { class: string })?.class;
const clazz = await fetchClass(classId);
if (auth.accountType === "teacher") {
return clazz.teachers.map(mapToUsername).includes(auth.username);
}
return clazz.students.map(mapToUsername).includes(auth.username);
if (auth.accountType === 'teacher') {
return clazz.teachers.map(mapToUsername).includes(auth.username);
}
);
return clazz.students.map(mapToUsername).includes(auth.username);
});

View file

@ -1,7 +1,7 @@
import {authorize} from "./auth-checks";
import {fetchClass} from "../../../services/classes";
import {fetchGroup} from "../../../services/groups";
import {mapToUsername} from "../../../interfaces/user";
import { authorize } from './auth-checks';
import { fetchClass } from '../../../services/classes';
import { fetchGroup } from '../../../services/groups';
import { mapToUsername } from '../../../interfaces/user';
/**
* Expects the path to contain the path parameters 'classid', 'assignmentid' and 'groupid'.
@ -9,17 +9,17 @@ import {mapToUsername} from "../../../interfaces/user";
* - either teachers of the class the assignment for the group was posted in,
* - or students in the group
*/
export const onlyAllowIfHasAccessToGroup = authorize(
async (auth, req) => {
const { classid: classId, assignmentid: assignmentId, groupid: groupId } =
req.params as { classid: string, assignmentid: number, groupid: number };
export const onlyAllowIfHasAccessToGroup = authorize(async (auth, req) => {
const {
classid: classId,
assignmentid: assignmentId,
groupid: groupId,
} = req.params as { classid: string; assignmentid: number; groupid: number };
if (auth.accountType === "teacher") {
const clazz = await fetchClass(classId);
return clazz.teachers.map(mapToUsername).includes(auth.username);
} // User is student
const group = await fetchGroup(classId, assignmentId, groupId);
return group.members.map(mapToUsername).includes(auth.username);
}
);
if (auth.accountType === 'teacher') {
const clazz = await fetchClass(classId);
return clazz.teachers.map(mapToUsername).includes(auth.username);
} // User is student
const group = await fetchGroup(classId, assignmentId, groupId);
return group.members.map(mapToUsername).includes(auth.username);
});

View file

@ -1,6 +1,6 @@
import {authorize} from "./auth-checks";
import {AuthenticationInfo} from "../authentication-info";
import {AuthenticatedRequest} from "../authenticated-request";
import { authorize } from './auth-checks';
import { AuthenticationInfo } from '../authentication-info';
import { AuthenticatedRequest } from '../authenticated-request';
/**
* Only allows requests whose learning path personalization query parameters ('forGroup' / 'assignmentNo' / 'classId')
@ -9,15 +9,12 @@ import {AuthenticatedRequest} from "../authenticated-request";
* - or set to a group the user is in,
* - or set to anything if the user is a teacher.
*/
export const onlyAllowPersonalizationForOwnGroup = authorize(
async (auth: AuthenticationInfo, req: AuthenticatedRequest) => {
const {forGroup, assignmentNo, classId} = req.params;
if (auth.accountType === "student" && forGroup && assignmentNo && classId) {
// TODO: groupNumber?
// Const group = await fetchGroup(Number(classId), Number(assignmentNo), )
return false;
}
return true;
export const onlyAllowPersonalizationForOwnGroup = authorize(async (auth: AuthenticationInfo, req: AuthenticatedRequest) => {
const { forGroup, assignmentNo, classId } = req.params;
if (auth.accountType === 'student' && forGroup && assignmentNo && classId) {
// TODO: groupNumber?
// Const group = await fetchGroup(Number(classId), Number(assignmentNo), )
return false;
}
);
return true;
});

View file

@ -1,72 +1,65 @@
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";
import {mapToUsername} from "../../../interfaces/user";
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';
import { mapToUsername } from '../../../interfaces/user';
export const onlyAllowAuthor = authorize(
(auth: AuthenticationInfo, req: AuthenticatedRequest) => (req.body as { author: string }).author === auth.username
);
export const onlyAllowAuthorRequest = authorize(
async (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 });
export const onlyAllowAuthorRequest = authorize(async (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 learningObjectId = getLearningObjectId(hruid, version, language);
const questionId = getQuestionId(learningObjectId, seq);
const question = await fetchQuestion(questionId);
const question = await fetchQuestion(questionId);
return question.author.username === auth.username;
}
);
return question.author.username === auth.username;
});
export const onlyAllowAuthorRequestAnswer = authorize(
async (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 });
export const onlyAllowAuthorRequestAnswer = authorize(async (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 learningObjectId = getLearningObjectId(hruid, version, language);
const questionId = getQuestionId(learningObjectId, seq);
const sequenceNumber = Number(seqAnswer) || FALLBACK_SEQ_NUM;
const answer = await fetchAnswer(questionId, sequenceNumber);
const sequenceNumber = Number(seqAnswer) || FALLBACK_SEQ_NUM;
const answer = await fetchAnswer(questionId, sequenceNumber);
return answer.author.username === auth.username;
}
);
return answer.author.username === auth.username;
});
export const onlyAllowIfHasAccessToQuestion = authorize(
async (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 });
export const onlyAllowIfHasAccessToQuestion = authorize(async (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 learningObjectId = getLearningObjectId(hruid, version, language);
const questionId = getQuestionId(learningObjectId, seq);
const question = await fetchQuestion(questionId);
const group = question.inGroup;
const question = await fetchQuestion(questionId);
const group = question.inGroup;
if (auth.accountType === "teacher") {
const cls = group.assignment.within; // TODO check if contains full objects
return cls.teachers.map(mapToUsername).includes(auth.username);
} // User is student
return group.members.map(mapToUsername).includes(auth.username);
}
);
if (auth.accountType === 'teacher') {
const cls = group.assignment.within; // TODO check if contains full objects
return cls.teachers.map(mapToUsername).includes(auth.username);
} // User is student
return group.members.map(mapToUsername).includes(auth.username);
});

View file

@ -1,29 +1,27 @@
import { languageMap } from "dwengo-1-common/util/language";
import { LearningObjectIdentifier } from "../../../entities/content/learning-object-identifier";
import { fetchSubmission } from "../../../services/submissions";
import { AuthenticatedRequest } from "../authenticated-request";
import { AuthenticationInfo } from "../authentication-info";
import { authorize } from "./auth-checks";
import { FALLBACK_LANG } from "../../../config";
import { mapToUsername } from "../../../interfaces/user";
import { languageMap } from 'dwengo-1-common/util/language';
import { LearningObjectIdentifier } from '../../../entities/content/learning-object-identifier';
import { fetchSubmission } from '../../../services/submissions';
import { AuthenticatedRequest } from '../authenticated-request';
import { AuthenticationInfo } from '../authentication-info';
import { authorize } from './auth-checks';
import { FALLBACK_LANG } from '../../../config';
import { mapToUsername } from '../../../interfaces/user';
export const onlyAllowSubmitter = authorize(
(auth: AuthenticationInfo, req: AuthenticatedRequest) => (req.body as { submitter: string }).submitter === auth.username
);
export const onlyAllowIfHasAccessToSubmission = authorize(
async (auth: AuthenticationInfo, req: AuthenticatedRequest) => {
const { hruid: lohruid, id: submissionNumber } = req.params;
const { language: lang, version: version } = req.query;
export const onlyAllowIfHasAccessToSubmission = authorize(async (auth: AuthenticationInfo, req: AuthenticatedRequest) => {
const { hruid: lohruid, id: submissionNumber } = req.params;
const { language: lang, version: version } = req.query;
const loId = new LearningObjectIdentifier(lohruid, languageMap[lang as string] ?? FALLBACK_LANG, Number(version))
const submission = await fetchSubmission(loId, Number(submissionNumber));
const loId = new LearningObjectIdentifier(lohruid, languageMap[lang as string] ?? FALLBACK_LANG, Number(version));
const submission = await fetchSubmission(loId, Number(submissionNumber));
if (auth.accountType === "teacher") {
// Dit kan niet werken om dat al deze objecten niet gepopulate zijn.
return submission.onBehalfOf.assignment.within.teachers.map(mapToUsername).includes(auth.username);
}
return submission.onBehalfOf.members.map(mapToUsername).includes(auth.username);
if (auth.accountType === 'teacher') {
// Dit kan niet werken om dat al deze objecten niet gepopulate zijn.
return submission.onBehalfOf.assignment.within.teachers.map(mapToUsername).includes(auth.username);
}
)
return submission.onBehalfOf.members.map(mapToUsername).includes(auth.username);
});

View file

@ -1,23 +1,17 @@
import {authorize} from "./auth-checks";
import {AuthenticationInfo} from "../authentication-info";
import {AuthenticatedRequest} from "../authenticated-request";
import { authorize } from './auth-checks';
import { AuthenticationInfo } from '../authentication-info';
import { AuthenticatedRequest } from '../authenticated-request';
export const onlyAllowSenderOrReceiver = authorize(
(auth: AuthenticationInfo, req: AuthenticatedRequest) =>
req.params.sender === auth.username || req.params.receiver === auth.username
(auth: AuthenticationInfo, req: AuthenticatedRequest) => req.params.sender === auth.username || req.params.receiver === auth.username
);
export const onlyAllowSender = authorize(
(auth: AuthenticationInfo, req: AuthenticatedRequest) =>
req.params.sender === auth.username
);
export const onlyAllowSender = authorize((auth: AuthenticationInfo, req: AuthenticatedRequest) => req.params.sender === auth.username);
export const onlyAllowSenderBody = authorize(
(auth: AuthenticationInfo, req: AuthenticatedRequest) =>
(req.body as { sender: string }).sender === auth.username
(auth: AuthenticationInfo, req: AuthenticatedRequest) => (req.body as { sender: string }).sender === auth.username
);
export const onlyAllowReceiverBody = authorize(
(auth: AuthenticationInfo, req: AuthenticatedRequest) =>
(req.body as { receiver: string }).receiver === auth.username
(auth: AuthenticationInfo, req: AuthenticatedRequest) => (req.body as { receiver: string }).receiver === auth.username
);

View file

@ -1,10 +1,8 @@
import {authorize} from "./auth-checks";
import {AuthenticationInfo} from "../authentication-info";
import {AuthenticatedRequest} from "../authenticated-request";
import { authorize } from './auth-checks';
import { AuthenticationInfo } from '../authentication-info';
import { AuthenticatedRequest } from '../authenticated-request';
/**
* Only allow the user whose username is in the path parameter "username" to access the endpoint.
*/
export const onlyAllowUserHimself = authorize(
(auth: AuthenticationInfo, req: AuthenticatedRequest) => req.params.username === auth.username
);
export const onlyAllowUserHimself = authorize((auth: AuthenticationInfo, req: AuthenticatedRequest) => req.params.username === auth.username);