-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
Showing
2 changed files
with
110 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 GitHub Actions / build
Check failure on line 12 in src/auth/jwt.ts GitHub Actions / build
|
||
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 GitHub Actions / build
Check failure on line 19 in src/auth/jwt.ts GitHub Actions / build
|
||
handleMiddlewareCheck(req, res, isContributor); | ||
|
||
next(); | ||
}; | ||
|
||
const handleMiddlewareCheck = (req: Request, res, assertion: Function) => { | ||
try { | ||
const token = decodeJwtFromRequest(req); | ||
const role: UserRole[] = rolesFromJwtPayload(token); | ||
assertion(role); | ||
} catch (e) { | ||
handleError(e, res); | ||
} | ||
}; | ||
|
||
const handleError = (err: Error, res) => { | ||
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]; | ||
} | ||
|
||
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 }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }; |