Skip to content

Commit

Permalink
Create JWT auth middleware
Browse files Browse the repository at this point in the history
In order to verify a JWT and authorize access based on user roles. This
assumes user roles will be stateless and included in the `aud` claim of
the JWT
  • Loading branch information
Pauline Vos committed Nov 13, 2024
1 parent 44cc7c0 commit 940643a
Show file tree
Hide file tree
Showing 2 changed files with 110 additions and 0 deletions.
95 changes: 95 additions & 0 deletions src/auth/jwt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import {
ErrorType,
BadgeHubApiError,
NotAuthorizedError,
NotAuthenticatedError,
} from "../error";
import { isAdmin, isContributor, UserRole } from "./role";
import { jwtVerify, JWTPayload } from "jose";
import { Request } from "express";

// Middleware for routes that require a contributor role
const ensureAdminRouteMiddleware = (req, res, next) => {

Check failure on line 12 in src/auth/jwt.ts

View workflow job for this annotation

GitHub Actions / build

Parameter 'req' implicitly has an 'any' type.

Check failure on line 12 in src/auth/jwt.ts

View workflow job for this annotation

GitHub Actions / build

Parameter 'res' implicitly has an 'any' type.

Check failure on line 12 in src/auth/jwt.ts

View workflow job for this annotation

GitHub Actions / build

Parameter 'next' implicitly has an 'any' type.
handleMiddlewareCheck(req, res, isAdmin);

next();
};

// Middleware for routes that require a contributor role
const ensureContributorRouteMiddleware = (req, res, next) => {

Check failure on line 19 in src/auth/jwt.ts

View workflow job for this annotation

GitHub Actions / build

Parameter 'req' implicitly has an 'any' type.

Check failure on line 19 in src/auth/jwt.ts

View workflow job for this annotation

GitHub Actions / build

Parameter 'res' implicitly has an 'any' type.

Check failure on line 19 in src/auth/jwt.ts

View workflow job for this annotation

GitHub Actions / build

Parameter 'next' implicitly has an 'any' type.
handleMiddlewareCheck(req, res, isContributor);

next();
};

const handleMiddlewareCheck = (req: Request, res, assertion: Function) => {

Check failure on line 25 in src/auth/jwt.ts

View workflow job for this annotation

GitHub Actions / build

Parameter 'res' implicitly has an 'any' type.
try {
const token = decodeJwtFromRequest(req);
const role: UserRole[] = rolesFromJwtPayload(token);
assertion(role);
} catch (e) {
handleError(e, res);

Check failure on line 31 in src/auth/jwt.ts

View workflow job for this annotation

GitHub Actions / build

Argument of type 'unknown' is not assignable to parameter of type 'Error'.
}
};

const handleError = (err: Error, res) => {

Check failure on line 35 in src/auth/jwt.ts

View workflow job for this annotation

GitHub Actions / build

Parameter 'res' implicitly has an 'any' type.
if (err.name == ErrorType.NotAuthenticated) {
res.status(403).json({ error: err.message });
}
if (err.name == ErrorType.NotAuthenticated) {
res.status(401).json({ error: err.message });
}

res.status(500).json({ error: "Internal server error" });
};

const ensureAdmin = (role: UserRole) => {
if (!isAdmin(role)) {
throw NotAuthorizedError();
}
};

const ensureContributor = (role: UserRole) => {
if (!isContributor(role)) {
throw NotAuthorizedError();
}
};

const rolesFromJwtPayload = (payload: JWTPayload): UserRole[] => {
const aud = payload.aud;

if (!aud) {
return [];
}

if (aud instanceof String) {
return UserRole[aud];

Check failure on line 66 in src/auth/jwt.ts

View workflow job for this annotation

GitHub Actions / build

Element implicitly has an 'any' type because expression of type '(string | string[]) & String' can't be used to index type 'typeof UserRole'.
}

return aud.map((i: string) => {
return UserRole[i];
});
};

const decodeJwtFromRequest = (req): JWTPayload => {
const token = req.headers.authorization;

if (!token) {
throw NotAuthenticatedError("missing API token");
}

let payload: JWTPayload | string;
try {
payload = jwtVerify(token, process.env.JWT_SIGNING_KEY);
} catch (err) {
throw NotAuthenticatedError("API token invalid");
}

if (token instanceof String || !Object.hasOwn(token, "role")) {
throw NotAuthenticatedError("API token invalid");
}

return <JWTPayload>payload;
};

export { ensureAdminRouteMiddleware, ensureContributorRouteMiddleware };
15 changes: 15 additions & 0 deletions src/auth/role.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
enum UserRole {
Admin = "admin",
Contributor = "contributor",
ReadOnlyUser = "readonly",
}

const isAdmin = (role: UserRole): boolean => {
return role == UserRole.Admin;
};

const isContributor = (role: UserRole): boolean => {
return role == UserRole.Contributor || isAdmin(role);
};

export { UserRole, isAdmin, isContributor };

0 comments on commit 940643a

Please sign in to comment.