feat(backend): Endpoints voor studenten beschermd
This commit is contained in:
parent
bc60c18938
commit
6cb8a1b98f
7 changed files with 93 additions and 52 deletions
|
@ -1,13 +1,11 @@
|
||||||
import { envVars, getEnvVar } from '../../util/envVars.js';
|
import {envVars, getEnvVar} from '../../util/envVars.js';
|
||||||
import { expressjwt } from 'express-jwt';
|
import {expressjwt} from 'express-jwt';
|
||||||
import * as jwt from 'jsonwebtoken';
|
import * as jwt from 'jsonwebtoken';
|
||||||
import { JwtPayload } from 'jsonwebtoken';
|
import {JwtPayload} from 'jsonwebtoken';
|
||||||
import jwksClient from 'jwks-rsa';
|
import jwksClient from 'jwks-rsa';
|
||||||
import * as express from 'express';
|
import * as express from 'express';
|
||||||
import { AuthenticatedRequest } from './authenticated-request.js';
|
import {AuthenticatedRequest} from './authenticated-request.js';
|
||||||
import { AuthenticationInfo } from './authentication-info.js';
|
import {AuthenticationInfo} from './authentication-info.js';
|
||||||
import { UnauthorizedException } from '../../exceptions/unauthorized-exception.js';
|
|
||||||
import { ForbiddenException } from '../../exceptions/forbidden-exception.js';
|
|
||||||
|
|
||||||
const JWKS_CACHE = true;
|
const JWKS_CACHE = true;
|
||||||
const JWKS_RATE_LIMIT = true;
|
const JWKS_RATE_LIMIT = true;
|
||||||
|
@ -108,36 +106,3 @@ function addAuthenticationInfo(req: AuthenticatedRequest, _res: express.Response
|
||||||
}
|
}
|
||||||
|
|
||||||
export const authenticateUser = [verifyJwtToken, addAuthenticationInfo];
|
export const authenticateUser = [verifyJwtToken, addAuthenticationInfo];
|
||||||
|
|
||||||
/**
|
|
||||||
* Middleware which rejects unauthenticated users (with HTTP 401) and authenticated users which do not fulfill
|
|
||||||
* the given access condition.
|
|
||||||
* @param accessCondition Predicate over the current AuthenticationInfo. Access is only granted when this evaluates
|
|
||||||
* to true.
|
|
||||||
*/
|
|
||||||
export function authorize(accessCondition: (auth: AuthenticationInfo) => boolean) {
|
|
||||||
return (req: AuthenticatedRequest, _res: express.Response, next: express.NextFunction): void => {
|
|
||||||
if (!req.auth) {
|
|
||||||
throw new UnauthorizedException();
|
|
||||||
} else if (!accessCondition(req.auth)) {
|
|
||||||
throw new ForbiddenException();
|
|
||||||
} else {
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Middleware which rejects all unauthenticated users, but accepts all authenticated users.
|
|
||||||
*/
|
|
||||||
export const authenticatedOnly = authorize((_) => true);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Middleware which rejects requests from unauthenticated users or users that aren't students.
|
|
||||||
*/
|
|
||||||
export const studentsOnly = authorize((auth) => auth.accountType === 'student');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Middleware which rejects requests from unauthenticated users or users that aren't teachers.
|
|
||||||
*/
|
|
||||||
export const teachersOnly = authorize((auth) => auth.accountType === 'teacher');
|
|
||||||
|
|
38
backend/src/middleware/auth/checks/auth-checks.ts
Normal file
38
backend/src/middleware/auth/checks/auth-checks.ts
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
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";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Middleware which rejects unauthenticated users (with HTTP 401) and authenticated users which do not fulfill
|
||||||
|
* the given access condition.
|
||||||
|
* @param accessCondition Predicate over the current AuthenticationInfo. Access is only granted when this evaluates
|
||||||
|
* to true.
|
||||||
|
*/
|
||||||
|
export function authorize(
|
||||||
|
accessCondition: (auth: AuthenticationInfo, req: AuthenticatedRequest) => boolean | Promise<boolean>
|
||||||
|
) {
|
||||||
|
return async (req: AuthenticatedRequest, _res: express.Response, next: express.NextFunction): Promise<void> => {
|
||||||
|
if (!req.auth) {
|
||||||
|
throw new UnauthorizedException();
|
||||||
|
} else if (!await accessCondition(req.auth, req)) {
|
||||||
|
throw new ForbiddenException();
|
||||||
|
} else {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Middleware which rejects all unauthenticated users, but accepts all authenticated users.
|
||||||
|
*/
|
||||||
|
export const authenticatedOnly = authorize((_) => true);
|
||||||
|
/**
|
||||||
|
* Middleware which rejects requests from unauthenticated users or users that aren't students.
|
||||||
|
*/
|
||||||
|
export const studentsOnly = authorize((auth) => auth.accountType === 'student');
|
||||||
|
/**
|
||||||
|
* Middleware which rejects requests from unauthenticated users or users that aren't teachers.
|
||||||
|
*/
|
||||||
|
export const teachersOnly = authorize((auth) => auth.accountType === 'teacher');
|
22
backend/src/middleware/auth/checks/class-auth-checks.ts
Normal file
22
backend/src/middleware/auth/checks/class-auth-checks.ts
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import {authorize} from "./auth-checks";
|
||||||
|
import {AuthenticationInfo} from "../authentication-info";
|
||||||
|
import {AuthenticatedRequest} from "../authenticated-request";
|
||||||
|
import {getClassesByTeacher} from "../../../services/teachers";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To be used on a request with path parameters username and classId.
|
||||||
|
* 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") {
|
||||||
|
const classes: string[] = (await getClassesByTeacher(auth.username, false)) as string[];
|
||||||
|
return req.params.classId in classes;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
10
backend/src/middleware/auth/checks/user-auth-checks.ts
Normal file
10
backend/src/middleware/auth/checks/user-auth-checks.ts
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
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
|
||||||
|
);
|
|
@ -1,6 +1,7 @@
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
import { getFrontendAuthConfig } from '../controllers/auth.js';
|
import { getFrontendAuthConfig } from '../controllers/auth.js';
|
||||||
import { authenticatedOnly, studentsOnly, teachersOnly } from '../middleware/auth/auth.js';
|
import {authenticatedOnly, studentsOnly, teachersOnly} from "../middleware/auth/checks/auth-checks";
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
// Returns auth configuration for frontend
|
// Returns auth configuration for frontend
|
||||||
|
|
|
@ -5,15 +5,19 @@ import {
|
||||||
getStudentRequestHandler,
|
getStudentRequestHandler,
|
||||||
getStudentRequestsHandler,
|
getStudentRequestsHandler,
|
||||||
} from '../controllers/students.js';
|
} from '../controllers/students.js';
|
||||||
|
import {onlyAllowUserHimself} from "../middleware/auth/checks/user-auth-checks";
|
||||||
|
import {onlyAllowStudentHimselfAndTeachersOfClass} from "../middleware/auth/checks/class-auth-checks";
|
||||||
|
|
||||||
|
// Under /:username/joinRequests/
|
||||||
|
|
||||||
const router = express.Router({ mergeParams: true });
|
const router = express.Router({ mergeParams: true });
|
||||||
|
|
||||||
router.get('/', getStudentRequestsHandler);
|
router.get('/', onlyAllowUserHimself, getStudentRequestsHandler);
|
||||||
|
|
||||||
router.post('/', createStudentRequestHandler);
|
router.post('/', onlyAllowUserHimself, createStudentRequestHandler);
|
||||||
|
|
||||||
router.get('/:classId', getStudentRequestHandler);
|
router.get('/:classId', onlyAllowStudentHimselfAndTeachersOfClass, getStudentRequestHandler);
|
||||||
|
|
||||||
router.delete('/:classId', deleteClassJoinRequestHandler);
|
router.delete('/:classId', onlyAllowStudentHimselfAndTeachersOfClass, deleteClassJoinRequestHandler);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|
|
@ -11,6 +11,7 @@ import {
|
||||||
getStudentSubmissionsHandler,
|
getStudentSubmissionsHandler,
|
||||||
} from '../controllers/students.js';
|
} from '../controllers/students.js';
|
||||||
import joinRequestRouter from './student-join-requests.js';
|
import joinRequestRouter from './student-join-requests.js';
|
||||||
|
import {onlyAllowUserHimself} from "../middleware/auth/checks/user-auth-checks";
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
|
@ -19,25 +20,25 @@ router.get('/', getAllStudentsHandler);
|
||||||
|
|
||||||
router.post('/', createStudentHandler);
|
router.post('/', createStudentHandler);
|
||||||
|
|
||||||
router.delete('/:username', deleteStudentHandler);
|
router.delete('/:username', onlyAllowUserHimself, deleteStudentHandler);
|
||||||
|
|
||||||
// Information about a student's profile
|
// Information about a student's profile
|
||||||
router.get('/:username', getStudentHandler);
|
router.get('/:username', onlyAllowUserHimself, getStudentHandler);
|
||||||
|
|
||||||
// The list of classes a student is in
|
// The list of classes a student is in
|
||||||
router.get('/:username/classes', getStudentClassesHandler);
|
router.get('/:username/classes', onlyAllowUserHimself, getStudentClassesHandler);
|
||||||
|
|
||||||
// The list of submissions a student has made
|
// The list of submissions a student has made
|
||||||
router.get('/:username/submissions', getStudentSubmissionsHandler);
|
router.get('/:username/submissions', onlyAllowUserHimself, getStudentSubmissionsHandler);
|
||||||
|
|
||||||
// The list of assignments a student has
|
// The list of assignments a student has
|
||||||
router.get('/:username/assignments', getStudentAssignmentsHandler);
|
router.get('/:username/assignments', onlyAllowUserHimself, getStudentAssignmentsHandler);
|
||||||
|
|
||||||
// The list of groups a student is in
|
// The list of groups a student is in
|
||||||
router.get('/:username/groups', getStudentGroupsHandler);
|
router.get('/:username/groups', onlyAllowUserHimself, getStudentGroupsHandler);
|
||||||
|
|
||||||
// A list of questions a user has created
|
// A list of questions a user has created
|
||||||
router.get('/:username/questions', getStudentQuestionsHandler);
|
router.get('/:username/questions', onlyAllowUserHimself, getStudentQuestionsHandler);
|
||||||
|
|
||||||
router.use('/:username/joinRequests', joinRequestRouter);
|
router.use('/:username/joinRequests', joinRequestRouter);
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue