From 14d748de80c51c262d353a99a4d1dcf802feadda Mon Sep 17 00:00:00 2001 From: Josh-Heaps Date: Mon, 9 Feb 2026 13:52:59 -0700 Subject: [PATCH] Add user roles and admin page --- .../Database/012_user_roles.sql | 10 ++++++++ Media.JoshHeaps.Net/Pages/Admin.cshtml | 24 +++++++++++++++++++ Media.JoshHeaps.Net/Pages/Admin.cshtml.cs | 20 ++++++++++++++++ .../Pages/AuthenticatedPageModel.cs | 9 +++++++ 4 files changed, 63 insertions(+) create mode 100644 Media.JoshHeaps.Net/Database/012_user_roles.sql create mode 100644 Media.JoshHeaps.Net/Pages/Admin.cshtml create mode 100644 Media.JoshHeaps.Net/Pages/Admin.cshtml.cs diff --git a/Media.JoshHeaps.Net/Database/012_user_roles.sql b/Media.JoshHeaps.Net/Database/012_user_roles.sql new file mode 100644 index 0000000..127b589 --- /dev/null +++ b/Media.JoshHeaps.Net/Database/012_user_roles.sql @@ -0,0 +1,10 @@ +-- User roles table for role-based access control +CREATE TABLE IF NOT EXISTS app.user_roles ( + id BIGSERIAL PRIMARY KEY, + user_id BIGINT NOT NULL REFERENCES app.users(id), + role VARCHAR(50) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT uq_user_role UNIQUE (user_id, role) +); + +CREATE INDEX IF NOT EXISTS idx_user_roles_user_id ON app.user_roles(user_id); diff --git a/Media.JoshHeaps.Net/Pages/Admin.cshtml b/Media.JoshHeaps.Net/Pages/Admin.cshtml new file mode 100644 index 0000000..842cfcb --- /dev/null +++ b/Media.JoshHeaps.Net/Pages/Admin.cshtml @@ -0,0 +1,24 @@ +@page +@model Media.JoshHeaps.Net.Pages.AdminModel +@{ + ViewData["Title"] = "Admin"; + Layout = "_Layout"; +} + +
+
+
+ + + + + + +

Admin

+
+
+ Profile + Logout +
+
+
diff --git a/Media.JoshHeaps.Net/Pages/Admin.cshtml.cs b/Media.JoshHeaps.Net/Pages/Admin.cshtml.cs new file mode 100644 index 0000000..7fd447f --- /dev/null +++ b/Media.JoshHeaps.Net/Pages/Admin.cshtml.cs @@ -0,0 +1,20 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Media.JoshHeaps.Net.Pages +{ + public class AdminModel(DbExecutor dbExecutor) : AuthenticatedPageModel + { + private readonly DbExecutor _dbExecutor = dbExecutor; + + public async Task OnGetAsync() + { + RequireAuthentication(); + LoadUserSession(); + + var denied = await RequireRole("admin", _dbExecutor); + if (denied != null) return denied; + + return Page(); + } + } +} diff --git a/Media.JoshHeaps.Net/Pages/AuthenticatedPageModel.cs b/Media.JoshHeaps.Net/Pages/AuthenticatedPageModel.cs index 81d6bd9..b2fa5a7 100644 --- a/Media.JoshHeaps.Net/Pages/AuthenticatedPageModel.cs +++ b/Media.JoshHeaps.Net/Pages/AuthenticatedPageModel.cs @@ -5,6 +5,15 @@ namespace Media.JoshHeaps.Net.Pages; public abstract class AuthenticatedPageModel : PageModel { + protected async Task RequireRole(string role, DbExecutor dbExecutor) + { + var hasRole = await dbExecutor.ExecuteAsync( + "SELECT EXISTS(SELECT 1 FROM app.user_roles WHERE user_id = @UserId AND role = @Role)", + new { UserId, Role = role }); + + return hasRole ? null : NotFound(); + } + public long UserId { get; private set; } protected string Username { get; private set; } = string.Empty; protected string Email { get; private set; } = string.Empty;