implemented role and state based authorization
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
import { NextFunction, Request, Response } from "express";
|
||||
import { MemberState } from "../services/memberService";
|
||||
import { stat } from "fs";
|
||||
|
||||
export const requireLogin = function (req: Request, res: Response, next: NextFunction) {
|
||||
if (req.user?.id)
|
||||
@@ -7,8 +9,41 @@ export const requireLogin = function (req: Request, res: Response, next: NextFun
|
||||
res.sendStatus(401)
|
||||
}
|
||||
|
||||
function requireRole(roleName: string) {
|
||||
export function requireMemberState(state: MemberState) {
|
||||
return function (req: Request, res: Response, next: NextFunction) {
|
||||
|
||||
if (req.user?.state === state)
|
||||
next();
|
||||
else
|
||||
res.status(403).send("You must be a member of the 17th RBN to access this resource");
|
||||
}
|
||||
}
|
||||
|
||||
export function requireRole(requiredRoles: string | string[]) {
|
||||
// Normalize the input to always be an array of lowercase required roles
|
||||
const normalizedRequiredRoles: string[] = Array.isArray(requiredRoles)
|
||||
? requiredRoles.map(role => role.toLowerCase())
|
||||
: [requiredRoles.toLowerCase()];
|
||||
|
||||
const DEV_ROLE = 'dev';
|
||||
|
||||
return function (req: Request, res: Response, next: NextFunction) {
|
||||
if (!req.user || !req.user.roles) {
|
||||
// User is not authenticated or has no roles array
|
||||
return res.sendStatus(401);
|
||||
}
|
||||
|
||||
const userRolesLowercase = req.user.roles.map(role => role.name.toLowerCase());
|
||||
|
||||
// Check if the user has *any* of the required roles OR the 'dev' role
|
||||
const hasAccess = userRolesLowercase.some(userRole =>
|
||||
userRole === DEV_ROLE || normalizedRequiredRoles.includes(userRole)
|
||||
);
|
||||
|
||||
if (hasAccess) {
|
||||
return next();
|
||||
} else {
|
||||
// User is authenticated but does not have the necessary permissions
|
||||
return res.sendStatus(403);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import { ApplicationFull, CommentRow } from "@app/shared/types/application"
|
||||
import { assignUserToStatus } from '../services/statusService';
|
||||
import { Request, response, Response } from 'express';
|
||||
import { getUserRoles } from '../services/rolesService';
|
||||
import { requireLogin } from '../middleware/auth';
|
||||
import { requireLogin, requireRole } from '../middleware/auth';
|
||||
|
||||
//get CoC
|
||||
router.get('/coc', async (req: Request, res: Response) => {
|
||||
@@ -48,7 +48,7 @@ router.post('/', [requireLogin], async (req, res) => {
|
||||
});
|
||||
|
||||
// GET /application/all
|
||||
router.get('/all', [requireLogin], async (req, res) => {
|
||||
router.get('/all', [requireLogin, requireRole("Recruiter")], async (req, res) => {
|
||||
try {
|
||||
const rows = await getApplicationList();
|
||||
res.status(200).json(rows);
|
||||
@@ -124,22 +124,10 @@ router.get('/me/:id', [requireLogin], async (req: Request, res: Response) => {
|
||||
});
|
||||
|
||||
// GET /application/:id
|
||||
router.get('/:id', [requireLogin], async (req: Request, res: Response) => {
|
||||
router.get('/:id', [requireLogin, requireRole("Recruiter")], async (req: Request, res: Response) => {
|
||||
let appID = Number(req.params.id);
|
||||
let asAdmin = !!req.query.admin || false;
|
||||
let user = req.user.id;
|
||||
|
||||
//TODO: Replace this with bigger authorization system eventually
|
||||
if (asAdmin) {
|
||||
let allowed = (await getUserRoles(user)).some((role) =>
|
||||
role.name.toLowerCase() === 'dev' ||
|
||||
role.name.toLowerCase() === 'recruiter' ||
|
||||
role.name.toLowerCase() === 'administrator')
|
||||
console.log(allowed)
|
||||
if (!allowed) {
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
}
|
||||
try {
|
||||
const application = await getApplicationByID(appID);
|
||||
if (application === undefined)
|
||||
@@ -160,7 +148,7 @@ router.get('/:id', [requireLogin], async (req: Request, res: Response) => {
|
||||
});
|
||||
|
||||
// POST /application/approve/:id
|
||||
router.post('/approve/:id', [requireLogin], async (req: Request, res: Response) => {
|
||||
router.post('/approve/:id', [requireLogin, requireRole("Recruiter")], async (req: Request, res: Response) => {
|
||||
const appID = Number(req.params.id);
|
||||
const approved_by = req.user.id;
|
||||
|
||||
@@ -189,7 +177,7 @@ router.post('/approve/:id', [requireLogin], async (req: Request, res: Response)
|
||||
});
|
||||
|
||||
// POST /application/deny/:id
|
||||
router.post('/deny/:id', [requireLogin], async (req, res) => {
|
||||
router.post('/deny/:id', [requireLogin, requireRole("Recruiter")], async (req, res) => {
|
||||
const appID = req.params.id;
|
||||
|
||||
try {
|
||||
@@ -247,7 +235,7 @@ VALUES(?, ?, ?);`
|
||||
});
|
||||
|
||||
// POST /application/:id/comment
|
||||
router.post('/:id/adminComment', [requireLogin], async (req: Request, res: Response) => {
|
||||
router.post('/:id/adminComment', [requireLogin, requireRole("Recruiter")], async (req: Request, res: Response) => {
|
||||
const appID = req.params.id;
|
||||
const data = req.body.message;
|
||||
const user = req.user;
|
||||
|
||||
@@ -6,8 +6,11 @@ dotenv.config();
|
||||
const express = require('express');
|
||||
const { param } = require('./applications');
|
||||
const router = express.Router();
|
||||
import { Role } from '@app/shared/types/roles';
|
||||
import pool from '../db';
|
||||
import { requireLogin } from '../middleware/auth';
|
||||
import { getUserRoles } from '../services/rolesService';
|
||||
import { getUserState, MemberState } from '../services/memberService';
|
||||
const querystring = require('querystring');
|
||||
|
||||
|
||||
@@ -22,13 +25,13 @@ passport.use(new OpenIDConnectStrategy({
|
||||
scope: ['openid', 'profile']
|
||||
}, async function verify(issuer, sub, profile, jwtClaims, accessToken, refreshToken, params, cb) {
|
||||
|
||||
console.log('--- OIDC verify() called ---');
|
||||
console.log('issuer:', issuer);
|
||||
console.log('sub:', sub);
|
||||
// console.log('profile:', JSON.stringify(profile, null, 2));
|
||||
console.log('profile:', profile);
|
||||
console.log('id_token claims:', JSON.stringify(jwtClaims, null, 2));
|
||||
console.log('preferred_username:', jwtClaims?.preferred_username);
|
||||
// console.log('--- OIDC verify() called ---');
|
||||
// console.log('issuer:', issuer);
|
||||
// console.log('sub:', sub);
|
||||
// // console.log('profile:', JSON.stringify(profile, null, 2));
|
||||
// console.log('profile:', profile);
|
||||
// console.log('id_token claims:', JSON.stringify(jwtClaims, null, 2));
|
||||
// console.log('preferred_username:', jwtClaims?.preferred_username);
|
||||
|
||||
const con = await pool.getConnection();
|
||||
try {
|
||||
@@ -67,12 +70,6 @@ router.get('/login', (req, res, next) => {
|
||||
next();
|
||||
}, passport.authenticate('openidconnect'));
|
||||
|
||||
// router.get('/callback', (req, res, next) => {
|
||||
// passport.authenticate('openidconnect', {
|
||||
// successRedirect: req.session.redirectTo,
|
||||
// failureRedirect: process.env.CLIENT_URL
|
||||
// })
|
||||
// });
|
||||
|
||||
router.get('/callback', (req, res, next) => {
|
||||
const redirectURI = req.session.redirectTo;
|
||||
@@ -111,15 +108,17 @@ passport.serializeUser(function (user, cb) {
|
||||
passport.deserializeUser(function (user, cb) {
|
||||
process.nextTick(async function () {
|
||||
|
||||
const memberID = user.memberId;
|
||||
const memberID = user.memberId as number;
|
||||
|
||||
const con = await pool.getConnection();
|
||||
|
||||
var userData;
|
||||
var userData: { id: number, name: string, roles: Role[], state: MemberState };
|
||||
try {
|
||||
let userResults = await con.query(`SELECT id, name FROM members WHERE id = ?;`, [memberID])
|
||||
userData = userResults[0];
|
||||
|
||||
let userRoles = await getUserRoles(memberID);
|
||||
userData.roles = userRoles;
|
||||
userData.state = await getUserState(memberID);
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
} finally {
|
||||
@@ -129,5 +128,19 @@ passport.deserializeUser(function (user, cb) {
|
||||
});
|
||||
});
|
||||
|
||||
declare global {
|
||||
namespace Express {
|
||||
interface Request {
|
||||
user: {
|
||||
id: number;
|
||||
name: string;
|
||||
roles: Role[];
|
||||
state: MemberState;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
module.exports = router;
|
||||
@@ -5,7 +5,7 @@ import { Request, Response } from 'express';
|
||||
import pool from '../db';
|
||||
import { closeLOA, createNewLOA, getAllLOA, getLOAbyID, getLoaTypes, getUserLOA, setLOAExtension } from '../services/loaService';
|
||||
import { LOARequest } from '@app/shared/types/loa';
|
||||
import { requireLogin } from '../middleware/auth';
|
||||
import { requireLogin, requireRole } from '../middleware/auth';
|
||||
|
||||
router.use(requireLogin);
|
||||
|
||||
@@ -26,7 +26,7 @@ router.post("/", async (req: Request, res: Response) => {
|
||||
});
|
||||
|
||||
//admin posts LOA
|
||||
router.post("/admin", async (req: Request, res: Response) => {
|
||||
router.post("/admin", [requireRole("17th Administrator")], async (req: Request, res: Response) => {
|
||||
let LOARequest = req.body as LOARequest;
|
||||
LOARequest.created_by = req.user.id;
|
||||
LOARequest.filed_date = new Date();
|
||||
@@ -66,7 +66,7 @@ router.get("/history", async (req: Request, res: Response) => {
|
||||
}
|
||||
})
|
||||
|
||||
router.get('/all', async (req, res) => {
|
||||
router.get('/all', [requireRole("17th Administrator")], async (req, res) => {
|
||||
try {
|
||||
const result = await getAllLOA();
|
||||
res.status(200).json(result)
|
||||
@@ -104,7 +104,7 @@ router.post('/cancel/:id', async (req: Request, res: Response) => {
|
||||
})
|
||||
|
||||
//TODO: enforce admin only
|
||||
router.post('/adminCancel/:id', async (req: Request, res: Response) => {
|
||||
router.post('/adminCancel/:id', [requireRole("17th Administrator")], async (req: Request, res: Response) => {
|
||||
let closer = req.user.id;
|
||||
try {
|
||||
await closeLOA(Number(req.params.id), closer);
|
||||
@@ -116,7 +116,7 @@ router.post('/adminCancel/:id', async (req: Request, res: Response) => {
|
||||
})
|
||||
|
||||
// TODO: Enforce admin only
|
||||
router.post('/extend/:id', async (req: Request, res: Response) => {
|
||||
router.post('/extend/:id', [requireRole("17th Administrator")], async (req: Request, res: Response) => {
|
||||
const to: Date = req.body.to;
|
||||
|
||||
if (!to) {
|
||||
|
||||
@@ -2,15 +2,15 @@ const express = require('express');
|
||||
const router = express.Router();
|
||||
|
||||
import pool from '../db';
|
||||
import { requireLogin } from '../middleware/auth';
|
||||
import { requireLogin, requireMemberState, requireRole } from '../middleware/auth';
|
||||
import { getUserActiveLOA } from '../services/loaService';
|
||||
import { getUserData } from '../services/memberService';
|
||||
import { getUserData, MemberState } from '../services/memberService';
|
||||
import { getUserRoles } from '../services/rolesService';
|
||||
|
||||
router.use(requireLogin);
|
||||
|
||||
//get all users
|
||||
router.get('/', async (req, res) => {
|
||||
router.get('/', [requireMemberState(MemberState.Member)], async (req, res) => {
|
||||
try {
|
||||
const result = await pool.query(
|
||||
`SELECT
|
||||
|
||||
@@ -22,13 +22,8 @@ export async function setUserState(userID: number, state: MemberState) {
|
||||
return await pool.query(sql, [state, userID]);
|
||||
}
|
||||
|
||||
declare global {
|
||||
namespace Express {
|
||||
interface Request {
|
||||
user: {
|
||||
id: number;
|
||||
name: string;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
export async function getUserState(user: number): Promise<MemberState> {
|
||||
let out = await pool.query(`SELECT state FROM members WHERE id = ?`, [user]);
|
||||
console.log('hi')
|
||||
return (out[0].state as MemberState);
|
||||
}
|
||||
@@ -59,7 +59,9 @@ export async function postAdminChatMessage(message: any, post_id: number) {
|
||||
}
|
||||
|
||||
export async function getAllApplications(): Promise<ApplicationFull> {
|
||||
const res = await fetch(`${addr}/application/all`)
|
||||
const res = await fetch(`${addr}/application/all`, {
|
||||
credentials: 'include',
|
||||
})
|
||||
|
||||
if (res.ok) {
|
||||
return res.json()
|
||||
|
||||
@@ -4,7 +4,9 @@ import { Course, CourseAttendeeRole, CourseEventDetails, CourseEventSummary } fr
|
||||
const addr = import.meta.env.VITE_APIHOST;
|
||||
|
||||
export async function getTrainingReports(sortMode: string, search: string): Promise<CourseEventSummary[]> {
|
||||
const res = await fetch(`${addr}/courseEvent?sort=${sortMode}&search=${search}`);
|
||||
const res = await fetch(`${addr}/courseEvent?sort=${sortMode}&search=${search}`, {
|
||||
credentials: 'include',
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
return await res.json() as Promise<CourseEventSummary[]>;
|
||||
@@ -15,7 +17,9 @@ export async function getTrainingReports(sortMode: string, search: string): Prom
|
||||
}
|
||||
|
||||
export async function getTrainingReport(id: number): Promise<CourseEventDetails> {
|
||||
const res = await fetch(`${addr}/courseEvent/${id}`);
|
||||
const res = await fetch(`${addr}/courseEvent/${id}`, {
|
||||
credentials: 'include',
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
return await res.json() as Promise<CourseEventDetails>;
|
||||
@@ -26,10 +30,12 @@ export async function getTrainingReport(id: number): Promise<CourseEventDetails>
|
||||
}
|
||||
|
||||
export async function getAllTrainings(): Promise<Course[]> {
|
||||
const res = await fetch(`${addr}/course`);
|
||||
const res = await fetch(`${addr}/course`, {
|
||||
credentials: 'include',
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
return await res.json() as Promise<Course[]>;
|
||||
return await res.json() as Promise<Course[]>;
|
||||
} else {
|
||||
console.error("Something went wrong");
|
||||
throw new Error("Failed to load training list");
|
||||
@@ -37,7 +43,9 @@ export async function getAllTrainings(): Promise<Course[]> {
|
||||
}
|
||||
|
||||
export async function getAllAttendeeRoles(): Promise<CourseAttendeeRole[]> {
|
||||
const res = await fetch(`${addr}/course/roles`);
|
||||
const res = await fetch(`${addr}/course/roles`, {
|
||||
credentials: 'include',
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
return await res.json() as Promise<CourseAttendeeRole[]>;
|
||||
|
||||
Reference in New Issue
Block a user