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
054e761baa
commit
69ba8c9567
2 changed files with 29 additions and 16 deletions
|
@ -29,7 +29,7 @@ const idpConfigs = {
|
||||||
/**
|
/**
|
||||||
* Express middleware which verifies the JWT Bearer token if one is given in the request.
|
* Express middleware which verifies the JWT Bearer token if one is given in the request.
|
||||||
*/
|
*/
|
||||||
export const authenticateUser = expressjwt({
|
const verifyJwtToken = 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) {
|
||||||
throw new Error("Invalid token");
|
throw new Error("Invalid token");
|
||||||
|
@ -50,18 +50,19 @@ export const authenticateUser = expressjwt({
|
||||||
},
|
},
|
||||||
audience: getEnvVar(EnvVars.IdpAudience),
|
audience: getEnvVar(EnvVars.IdpAudience),
|
||||||
algorithms: ["RS256"],
|
algorithms: ["RS256"],
|
||||||
credentialsRequired: false
|
credentialsRequired: false,
|
||||||
|
requestProperty: "jwtPayload"
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get an object with information about the authenticated user from a given authenticated request.
|
* Get an object with information about the authenticated user from a given authenticated request.
|
||||||
*/
|
*/
|
||||||
function getAuthenticationInfo(req: AuthenticatedRequest): AuthenticationInfo | undefined {
|
function getAuthenticationInfo(req: AuthenticatedRequest): AuthenticationInfo | undefined {
|
||||||
if (!req.auth) {
|
console.log("hi");
|
||||||
|
if (!req.jwtPayload) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
let issuer = req.jwtPayload.iss;
|
||||||
let issuer = req.auth.issuer;
|
|
||||||
let accountType: "student" | "teacher";
|
let accountType: "student" | "teacher";
|
||||||
|
|
||||||
if (issuer === idpConfigs.student.issuer) {
|
if (issuer === idpConfigs.student.issuer) {
|
||||||
|
@ -71,29 +72,38 @@ function getAuthenticationInfo(req: AuthenticatedRequest): AuthenticationInfo |
|
||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
accountType: accountType,
|
accountType: accountType,
|
||||||
username: req.auth["preferred_username"]!,
|
username: req.jwtPayload["preferred_username"]!,
|
||||||
name: req.auth["name"],
|
name: req.jwtPayload["name"],
|
||||||
firstName: req.auth["given_name"],
|
firstName: req.jwtPayload["given_name"],
|
||||||
lastName: req.auth["family_name"],
|
lastName: req.jwtPayload["family_name"],
|
||||||
email: req.auth["email"],
|
email: req.jwtPayload["email"],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the AuthenticationInfo object with the information about the current authentication to the request in order
|
||||||
|
* to avoid that the routers have to deal with the JWT token.
|
||||||
|
*/
|
||||||
|
const addAuthenticationInfo = (req: AuthenticatedRequest, res: express.Response, next: express.NextFunction) => {
|
||||||
|
req.auth = getAuthenticationInfo(req);
|
||||||
|
next();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const authenticateUser = [verifyJwtToken, addAuthenticationInfo];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Middleware which rejects unauthenticated users (with HTTP 401) and authenticated users which do not fulfill
|
* Middleware which rejects unauthenticated users (with HTTP 401) and authenticated users which do not fulfill
|
||||||
* the given access condition.
|
* the given access condition.
|
||||||
* @param accessCondition Predicate over the current AuthenticationInfo. Access is only granted when this evaluates
|
* @param accessCondition Predicate over the current AuthenticationInfo. Access is only granted when this evaluates
|
||||||
* to true.
|
* to true.
|
||||||
*/
|
*/
|
||||||
const authorize = (accessCondition: (auth: AuthenticationInfo) => boolean) => {
|
export const authorize = (accessCondition: (auth: AuthenticationInfo) => boolean) => {
|
||||||
return (req: AuthenticatedRequest, res: express.Response, next: express.NextFunction): void => {
|
return (req: AuthenticatedRequest, res: express.Response, next: express.NextFunction): void => {
|
||||||
let authInfo = getAuthenticationInfo(req);
|
if (!req.auth) {
|
||||||
if (!authInfo) {
|
|
||||||
res.status(401).json({ message: "Unauthorized" });
|
res.status(401).json({ message: "Unauthorized" });
|
||||||
} else if (!accessCondition(authInfo)) {
|
} else if (!accessCondition(req.auth)) {
|
||||||
res.status(403).json({ message: "Forbidden" });
|
res.status(403).json({ message: "Forbidden" });
|
||||||
} else {
|
} else {
|
||||||
next();
|
next();
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
import { Request } from "express";
|
import { Request } from "express";
|
||||||
import { JwtPayload } from "jsonwebtoken";
|
import { JwtPayload } from "jsonwebtoken";
|
||||||
|
import {AuthenticationInfo} from "./authentication-info";
|
||||||
|
|
||||||
export interface AuthenticatedRequest extends Request {
|
export interface AuthenticatedRequest extends Request {
|
||||||
auth?: JwtPayload; // Optional, as req.auth might be undefined if authentication is optional
|
// Properties are optional since the user is not necessarily authenticated.
|
||||||
|
jwtPayload?: JwtPayload;
|
||||||
|
auth?: AuthenticationInfo;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue