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");
}
public async Badges_Get() {
public async Badges_Get_Auth$BadgeEditor() {
const adminBadgesViewModel: AdminBadgesViewModel = {
badges: await BadgeService.LoadAll()
};
@ -113,7 +113,7 @@ export default class AdminController_Auth$Admin extends Controller {
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;
if (typeof(adminBadgeViewModel.id) !== "undefined" && badge) {
adminBadgeViewModel.name = badge.Name;
@ -130,7 +130,7 @@ export default class AdminController_Auth$Admin extends Controller {
return this.view(adminBadgeViewModel);
}
public async Badge_Post(adminBadgeViewModel: AdminBadgeViewModel) {
public async Badge_Post_Auth$BadgeEditor(adminBadgeViewModel: AdminBadgeViewModel) {
if (typeof(adminBadgeViewModel.id) === "undefined") {
return this.badRequest();
}
@ -140,7 +140,7 @@ export default class AdminController_Auth$Admin extends Controller {
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") {
return this.badRequest();
}

View file

@ -15,13 +15,14 @@ export default abstract class Controller {
const methods = Object.getOwnPropertyNames(Object.getPrototypeOf(this));
const rawControllerParts = this.constructor.name.split("_");
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) {
if (prop.startsWith("Auth")) {
const userLevel = prop.split("$")[1];
// @ts-ignore
controllerAuthLevel = UserLevel[userLevel];
controllerAuthLevels.push(UserLevel[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 methodNameRaw = params.splice(0, 1)[0]
const methodNameRaw = params.splice(0, 1)[0];
const methodName = methodNameRaw.toLowerCase();
const doAuth = !params.includes("AllowAnonymous");
@ -44,8 +45,25 @@ export default abstract class Controller {
if (doAuth && session === undefined) {
return res.redirect(`/account/login?returnTo=${encodeURIComponent(req.url)}`);
}
if (session !== undefined && controllerAuthLevel !== undefined && controllerAuthLevel !== session.userLevel) {
return res.status(403).send("Forbidden");
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");
}
}
const requestCtx = new RequestCtx(req, res, controllerName, methodName, session);
@ -53,9 +71,12 @@ export default abstract class Controller {
}
let funcMethods:Array<string> = [];
let thisMethodHttpMethod = "";
for (const param of params) {
//console.log(param);
if (param === "Get" || param === "Post" || param === "Put") {
funcMethods.push(param);
thisMethodHttpMethod = param.toLowerCase();
// @ts-ignore
Controller.FastifyInstance[param.toLowerCase()](`/${controllerName}/${methodName === "index" ? "" : methodName}`, requestHandler);
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);
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 {
Unknown = 0,
User = 10,
BadgeEditor = 20,
Admin = 999
}

View file

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

View file

@ -37,6 +37,7 @@
<select class="form-select" name="userLevel" required>
<option value="" disabled <%= userLevel === "0" ? "selected" : "" %>>Please select...</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>
</select>
</div>
@ -50,16 +51,4 @@
</div>
</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") %>

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" href="/party/join">Join Party</a>
</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) { %>
<div class="mt-3">
<a class="btn btn-primary btn-lg" href="/admin">Admin Dashboard</a>