feat(backend): Generic authentication checks.
Added support for deciding based on any predicate about the current AuthenticationInfo whether or not a request will be accepted.
This commit is contained in:
parent
be667c7c53
commit
054e761baa
2 changed files with 71 additions and 18 deletions
|
@ -5,6 +5,7 @@ import jwksClient from 'jwks-rsa';
|
||||||
import * as express from "express";
|
import * as express from "express";
|
||||||
import * as jwt from "jsonwebtoken";
|
import * as jwt from "jsonwebtoken";
|
||||||
import {AuthenticatedRequest} from "./authenticated-request.js";
|
import {AuthenticatedRequest} from "./authenticated-request.js";
|
||||||
|
import {AuthenticationInfo} from "./authentication-info";
|
||||||
|
|
||||||
function createJwksClient(uri: string): jwksClient.JwksClient {
|
function createJwksClient(uri: string): jwksClient.JwksClient {
|
||||||
return jwksClient({
|
return jwksClient({
|
||||||
|
@ -25,6 +26,9 @@ const idpConfigs = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Express middleware which verifies the JWT Bearer token if one is given in the request.
|
||||||
|
*/
|
||||||
export const authenticateUser = expressjwt({
|
export const authenticateUser = expressjwt({
|
||||||
secret: async (_: express.Request, token: jwt.Jwt | undefined) => {
|
secret: async (_: express.Request, token: jwt.Jwt | undefined) => {
|
||||||
if (!token?.payload || !(token.payload as JwtPayload).iss) {
|
if (!token?.payload || !(token.payload as JwtPayload).iss) {
|
||||||
|
@ -49,27 +53,65 @@ export const authenticateUser = expressjwt({
|
||||||
credentialsRequired: false
|
credentialsRequired: false
|
||||||
});
|
});
|
||||||
|
|
||||||
const authorizeRole = (studentsAllowed: boolean, teachersAllowed: boolean) => {
|
/**
|
||||||
return (req: AuthenticatedRequest, res: express.Response, next: express.NextFunction): void => {
|
* Get an object with information about the authenticated user from a given authenticated request.
|
||||||
|
*/
|
||||||
|
function getAuthenticationInfo(req: AuthenticatedRequest): AuthenticationInfo | undefined {
|
||||||
if (!req.auth) {
|
if (!req.auth) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let issuer = req.auth.issuer;
|
||||||
|
let accountType: "student" | "teacher";
|
||||||
|
|
||||||
|
if (issuer === idpConfigs.student.issuer) {
|
||||||
|
accountType = "student";
|
||||||
|
} else if (issuer === idpConfigs.teacher.issuer) {
|
||||||
|
accountType = "teacher";
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
accountType: accountType,
|
||||||
|
username: req.auth["preferred_username"]!,
|
||||||
|
name: req.auth["name"],
|
||||||
|
firstName: req.auth["given_name"],
|
||||||
|
lastName: req.auth["family_name"],
|
||||||
|
email: req.auth["email"],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
const authorize = (accessCondition: (auth: AuthenticationInfo) => boolean) => {
|
||||||
|
return (req: AuthenticatedRequest, res: express.Response, next: express.NextFunction): void => {
|
||||||
|
let authInfo = getAuthenticationInfo(req);
|
||||||
|
if (!authInfo) {
|
||||||
res.status(401).json({ message: "Unauthorized" });
|
res.status(401).json({ message: "Unauthorized" });
|
||||||
return;
|
} else if (!accessCondition(authInfo)) {
|
||||||
|
res.status(403).json({ message: "Forbidden" });
|
||||||
|
} else {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const issuer = req.auth.iss;
|
/**
|
||||||
if (issuer === idpConfigs.student.issuer && !studentsAllowed) {
|
* Middleware which rejects all unauthenticated users, but accepts all authenticated users.
|
||||||
res.status(403).json({ message: "Students not allowed" });
|
*/
|
||||||
return;
|
export const authenticatedOnly = authorize(_ => true);
|
||||||
}
|
|
||||||
if (issuer === idpConfigs.teacher.issuer && !teachersAllowed) {
|
|
||||||
res.status(403).json({ message: "Teachers not allowed" });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
next(); // User is allowed
|
/**
|
||||||
};
|
* Middleware which rejects requests from unauthenticated users or users that aren't students.
|
||||||
};
|
*/
|
||||||
|
export const studentsOnly = authorize(auth => auth.accountType === "student");
|
||||||
|
|
||||||
export const authenticatedOnly = authorizeRole(true, true);
|
/**
|
||||||
export const studentsOnly = authorizeRole(true, false);
|
* Middleware which rejects requests from unauthenticated users or users that aren't teachers.
|
||||||
export const teachersOnly = authorizeRole(false, true);
|
*/
|
||||||
|
export const teachersOnly = authorize(auth => auth.accountType === "teacher");
|
||||||
|
|
11
backend/src/middleware/auth/authentication-info.ts
Normal file
11
backend/src/middleware/auth/authentication-info.ts
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
/**
|
||||||
|
* Object with information about the user who is currently logged in.
|
||||||
|
*/
|
||||||
|
export type AuthenticationInfo = {
|
||||||
|
accountType: "student" | "teacher",
|
||||||
|
username: string,
|
||||||
|
name?: string,
|
||||||
|
firstName?: string,
|
||||||
|
lastName?: string,
|
||||||
|
email?: string
|
||||||
|
};
|
Loading…
Add table
Add a link
Reference in a new issue