add BadgeEditor user role

This commit is contained in:
Holly Stubbs 2024-09-29 21:35:36 +01:00
parent 23a206ce99
commit ee9f50c87f
Signed by: tgpholly
GPG key ID: B8583C4B7D18119E
6 changed files with 46 additions and 23 deletions

View file

@ -105,7 +105,7 @@ export default class AdminController_Auth$Admin extends Controller {
return this.redirectToAction("parties"); return this.redirectToAction("parties");
} }
public async Badges_Get() { public async Badges_Get_Auth$BadgeEditor() {
const adminBadgesViewModel: AdminBadgesViewModel = { const adminBadgesViewModel: AdminBadgesViewModel = {
badges: await BadgeService.LoadAll() badges: await BadgeService.LoadAll()
}; };
@ -113,7 +113,7 @@ export default class AdminController_Auth$Admin extends Controller {
return this.view(adminBadgesViewModel); return this.view(adminBadgesViewModel);
} }
public async Badge_Get(adminBadgeViewModel: AdminBadgeViewModel) { public async Badge_Get_Auth$BadgeEditor(adminBadgeViewModel: AdminBadgeViewModel) {
const badge = adminBadgeViewModel.id ? await BadgeService.LoadBadge(parseInt(adminBadgeViewModel.id)) : null; const badge = adminBadgeViewModel.id ? await BadgeService.LoadBadge(parseInt(adminBadgeViewModel.id)) : null;
if (typeof(adminBadgeViewModel.id) !== "undefined" && badge) { if (typeof(adminBadgeViewModel.id) !== "undefined" && badge) {
adminBadgeViewModel.name = badge.Name; adminBadgeViewModel.name = badge.Name;
@ -130,7 +130,7 @@ export default class AdminController_Auth$Admin extends Controller {
return this.view(adminBadgeViewModel); return this.view(adminBadgeViewModel);
} }
public async Badge_Post(adminBadgeViewModel: AdminBadgeViewModel) { public async Badge_Post_Auth$BadgeEditor(adminBadgeViewModel: AdminBadgeViewModel) {
if (typeof(adminBadgeViewModel.id) === "undefined") { if (typeof(adminBadgeViewModel.id) === "undefined") {
return this.badRequest(); return this.badRequest();
} }
@ -140,7 +140,7 @@ export default class AdminController_Auth$Admin extends Controller {
return this.redirectToAction("badges"); return this.redirectToAction("badges");
} }
public async DeleteBadge_Get(adminDeleteBadgeModel: AdminDeleteBadgeModel) { public async DeleteBadge_Get_Auth$BadgeEditor(adminDeleteBadgeModel: AdminDeleteBadgeModel) {
if (typeof(adminDeleteBadgeModel.id) === "undefined" || typeof(adminDeleteBadgeModel.id) !== "string") { if (typeof(adminDeleteBadgeModel.id) === "undefined" || typeof(adminDeleteBadgeModel.id) !== "string") {
return this.badRequest(); return this.badRequest();
} }

View file

@ -15,13 +15,14 @@ export default abstract class Controller {
const methods = Object.getOwnPropertyNames(Object.getPrototypeOf(this)); const methods = Object.getOwnPropertyNames(Object.getPrototypeOf(this));
const rawControllerParts = this.constructor.name.split("_"); const rawControllerParts = this.constructor.name.split("_");
const controllerName = rawControllerParts.splice(0, 1)[0].replace("Controller", "").toLowerCase(); const controllerName = rawControllerParts.splice(0, 1)[0].replace("Controller", "").toLowerCase();
let controllerAuthLevel: UserLevel | undefined; const controllerAuthLevels: Array<UserLevel> = [];
const actionAuthLevels: { [ key : string ]: Array<UserLevel> } = {};
for (const prop of rawControllerParts) { for (const prop of rawControllerParts) {
if (prop.startsWith("Auth")) { if (prop.startsWith("Auth")) {
const userLevel = prop.split("$")[1]; const userLevel = prop.split("$")[1];
// @ts-ignore // @ts-ignore
controllerAuthLevel = UserLevel[userLevel]; controllerAuthLevels.push(UserLevel[userLevel]);
Console.printInfo(`Set Auth level requirement for ${this.constructor.name} to ${userLevel}`); Console.printInfo(`Set Auth level requirement for ${this.constructor.name} to ${userLevel}`);
} }
} }
@ -32,7 +33,7 @@ export default abstract class Controller {
} }
const params = method.split("_"); const params = method.split("_");
const methodNameRaw = params.splice(0, 1)[0] const methodNameRaw = params.splice(0, 1)[0];
const methodName = methodNameRaw.toLowerCase(); const methodName = methodNameRaw.toLowerCase();
const doAuth = !params.includes("AllowAnonymous"); const doAuth = !params.includes("AllowAnonymous");
@ -44,18 +45,38 @@ export default abstract class Controller {
if (doAuth && session === undefined) { if (doAuth && session === undefined) {
return res.redirect(`/account/login?returnTo=${encodeURIComponent(req.url)}`); return res.redirect(`/account/login?returnTo=${encodeURIComponent(req.url)}`);
} }
if (session !== undefined && controllerAuthLevel !== undefined && controllerAuthLevel !== session.userLevel) { const methodAuthCheck = actionAuthLevels[`${controllerName}_${methodName}_${req.method.toLowerCase()}`];
let wasMethodMatch = false;
if (methodAuthCheck && session !== undefined) {
for (const auth of methodAuthCheck) {
if (auth === session.userLevel) {
wasMethodMatch = true;
}
}
}
if (!wasMethodMatch && session !== undefined && controllerAuthLevels.length > 0) {
let hasLevelMatch = false;
for (const level of controllerAuthLevels) {
if (level === session.userLevel) {
hasLevelMatch = true;
}
}
if (!hasLevelMatch) {
return res.status(403).send("Forbidden"); return res.status(403).send("Forbidden");
} }
}
const requestCtx = new RequestCtx(req, res, controllerName, methodName, session); const requestCtx = new RequestCtx(req, res, controllerName, methodName, session);
controllerRequestHandler.bind(requestCtx)(req.method === "GET" ? req.query : req.body); controllerRequestHandler.bind(requestCtx)(req.method === "GET" ? req.query : req.body);
} }
let funcMethods:Array<string> = []; let funcMethods:Array<string> = [];
let thisMethodHttpMethod = "";
for (const param of params) { for (const param of params) {
//console.log(param);
if (param === "Get" || param === "Post" || param === "Put") { if (param === "Get" || param === "Post" || param === "Put") {
funcMethods.push(param); funcMethods.push(param);
thisMethodHttpMethod = param.toLowerCase();
// @ts-ignore // @ts-ignore
Controller.FastifyInstance[param.toLowerCase()](`/${controllerName}/${methodName === "index" ? "" : methodName}`, requestHandler); Controller.FastifyInstance[param.toLowerCase()](`/${controllerName}/${methodName === "index" ? "" : methodName}`, requestHandler);
Console.printInfo(`Registered ${this.constructor.name}.${method} to "/${controllerName}/${methodName === "index" ? "" : methodName}" as ${param}`); Console.printInfo(`Registered ${this.constructor.name}.${method} to "/${controllerName}/${methodName === "index" ? "" : methodName}" as ${param}`);
@ -67,6 +88,15 @@ export default abstract class Controller {
Controller.FastifyInstance[param.toLowerCase()](`/${controllerName}`, requestHandler); Controller.FastifyInstance[param.toLowerCase()](`/${controllerName}`, requestHandler);
Console.printInfo(`Registered ${this.constructor.name}.${method} to "/${controllerName}" as ${param}`); Console.printInfo(`Registered ${this.constructor.name}.${method} to "/${controllerName}" as ${param}`);
} }
} else if (param.startsWith("Auth")) {
const nameWithMethod = `${controllerName}_${methodName}_${thisMethodHttpMethod}`;
const userLevel = param.split("$")[1];
if (!(nameWithMethod in actionAuthLevels)) {
actionAuthLevels[nameWithMethod] = [];
}
// @ts-ignore
actionAuthLevels[nameWithMethod].push(UserLevel[userLevel]);
Console.printInfo(`Set Auth level requirement for ${this.constructor.name}.${method} to ${userLevel}`);
} }
} }

View file

@ -1,5 +1,6 @@
export enum UserLevel { export enum UserLevel {
Unknown = 0, Unknown = 0,
User = 10, User = 10,
BadgeEditor = 20,
Admin = 999 Admin = 999
} }

View file

@ -124,7 +124,6 @@ export default class UserService {
await UserPartyRepo.insertUpdate(userParty); await UserPartyRepo.insertUpdate(userParty);
} catch (e) { } catch (e) {
Console.printError(`MultiProbe server service error:\n${e}`); Console.printError(`MultiProbe server service error:\n${e}`);
console.log(e);
throw e; throw e;
} }
} }
@ -134,7 +133,6 @@ export default class UserService {
await UserPartyRepo.deactivateAll(currentUserId); await UserPartyRepo.deactivateAll(currentUserId);
} catch (e) { } catch (e) {
Console.printError(`MultiProbe server service error:\n${e}`); Console.printError(`MultiProbe server service error:\n${e}`);
console.log(e);
throw e; throw e;
} }
} }

View file

@ -37,6 +37,7 @@
<select class="form-select" name="userLevel" required> <select class="form-select" name="userLevel" required>
<option value="" disabled <%= userLevel === "0" ? "selected" : "" %>>Please select...</option> <option value="" disabled <%= userLevel === "0" ? "selected" : "" %>>Please select...</option>
<option value="10" <%= userLevel === "10" ? "selected" : "" %>>User</option> <option value="10" <%= userLevel === "10" ? "selected" : "" %>>User</option>
<option value="20" <%= userLevel === "20" ? "selected" : "" %>>Badge Editor</option>
<option value="999" <%= userLevel === "999" ? "selected" : "" %>>Admin</option> <option value="999" <%= userLevel === "999" ? "selected" : "" %>>Admin</option>
</select> </select>
</div> </div>
@ -50,16 +51,4 @@
</div> </div>
</form> </form>
<script>
const imageImg = document.querySelector("#imageImg");
const imageUrl = document.querySelector("#imageUrl");
imageUrl.addEventListener("change", () => {
if (imageUrl.value.trim() === ""){
imageImg.src = "/img/missing.png";
} else {
imageImg.src = imageUrl.value;
}
});
</script>
<%- include("../base/footer") %> <%- include("../base/footer") %>

View file

@ -12,6 +12,11 @@
<a class="btn btn-primary btn-lg me-2" href="/party/create">Create Party</a> <a class="btn btn-primary btn-lg me-2" href="/party/create">Create Party</a>
<a class="btn btn-primary btn-lg" href="/party/join">Join Party</a> <a class="btn btn-primary btn-lg" href="/party/join">Join Party</a>
</div> </div>
<% if (user.UserLevel === UserLevel.BadgeEditor) { %>
<div class="mt-3">
<a class="btn btn-primary btn-lg" href="/admin/badges">Badge Management</a>
</div>
<% } %>
<% if (user.UserLevel === UserLevel.Admin) { %> <% if (user.UserLevel === UserLevel.Admin) { %>
<div class="mt-3"> <div class="mt-3">
<a class="btn btn-primary btn-lg" href="/admin">Admin Dashboard</a> <a class="btn btn-primary btn-lg" href="/admin">Admin Dashboard</a>