Failing Stuff
This commit is contained in:
Jess Rogerson
2026-05-21 14:17:04 +10:00
6 changed files with 136 additions and 5 deletions
+63
View File
@@ -0,0 +1,63 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Mvc;
using System.Security.Claims;
namespace NoticeBoard.Controllers;
public class AccountController : Controller
{
private readonly IConfiguration _config;
public AccountController(IConfiguration config)
{
_config = config;
}
[HttpGet]
public IActionResult Login(string? returnUrl = null)
{
if (User.Identity?.IsAuthenticated == true)
return RedirectToAction("Index", "Admin");
ViewBag.ReturnUrl = returnUrl;
return View();
}
[HttpPost]
public async Task<IActionResult> Login(string username, string password, string? returnUrl = null)
{
var adminUser = _config["Admin:Username"] ?? "admin";
var adminPass = _config["Admin:Password"] ?? "admin";
if (username == adminUser && password == adminPass)
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, username),
new Claim(ClaimTypes.Role, "Admin")
};
var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
var principal = new ClaimsPrincipal(identity);
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal);
if (!string.IsNullOrEmpty(returnUrl) && Url.IsLocalUrl(returnUrl))
return Redirect(returnUrl);
return RedirectToAction("Index", "Admin");
}
ViewBag.Error = "Invalid username or password.";
ViewBag.ReturnUrl = returnUrl;
return View();
}
[HttpGet]
public async Task<IActionResult> Logout()
{
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
return RedirectToAction("Login");
}
}
+2
View File
@@ -1,9 +1,11 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using NoticeBoard.Data; using NoticeBoard.Data;
namespace NoticeBoard.Controllers; namespace NoticeBoard.Controllers;
[Authorize]
public class AdminController : Controller public class AdminController : Controller
{ {
private readonly AppDbContext _db; private readonly AppDbContext _db;
+2
View File
@@ -1,3 +1,4 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using NoticeBoard.Data; using NoticeBoard.Data;
@@ -5,6 +6,7 @@ using NoticeBoard.Models;
namespace NoticeBoard.Controllers; namespace NoticeBoard.Controllers;
[Authorize]
public class SlidesController : Controller public class SlidesController : Controller
{ {
private readonly AppDbContext _db; private readonly AppDbContext _db;
+19 -4
View File
@@ -1,4 +1,5 @@
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Authentication.Cookies;
using NoticeBoard.Data; using NoticeBoard.Data;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
@@ -11,9 +12,19 @@ builder.Services.AddDbContext<AppDbContext>(options =>
builder.Services.AddControllersWithViews(); builder.Services.AddControllersWithViews();
builder.Services.AddHttpClient(); builder.Services.AddHttpClient();
// Cookie authentication for admin panel
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.LoginPath = "/account/login";
options.LogoutPath = "/account/logout";
options.ExpireTimeSpan = TimeSpan.FromHours(12);
options.SlidingExpiration = true;
});
var app = builder.Build(); var app = builder.Build();
// Auto-create database on startup (use Migrate() if using EF migrations) // Auto-create database on startup
using (var scope = app.Services.CreateScope()) using (var scope = app.Services.CreateScope())
{ {
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>(); var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
@@ -28,12 +39,19 @@ if (!app.Environment.IsDevelopment())
app.UseStaticFiles(); app.UseStaticFiles();
app.UseRouting(); app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
// Ensure uploads directory exists // Ensure uploads directory exists
var uploadsPath = Path.Combine(app.Environment.WebRootPath, "uploads"); var uploadsPath = Path.Combine(app.Environment.WebRootPath, "uploads");
if (!Directory.Exists(uploadsPath)) if (!Directory.Exists(uploadsPath))
Directory.CreateDirectory(uploadsPath); Directory.CreateDirectory(uploadsPath);
app.MapControllerRoute(
name: "account",
pattern: "account/{action=Login}",
defaults: new { controller = "Account" });
app.MapControllerRoute( app.MapControllerRoute(
name: "admin", name: "admin",
pattern: "admin/{action=Index}/{id?}", pattern: "admin/{action=Index}/{id?}",
@@ -54,20 +72,17 @@ app.MapControllerRoute(
pattern: "api/{action}/{id?}", pattern: "api/{action}/{id?}",
defaults: new { controller = "Api" }); defaults: new { controller = "Api" });
// Display route: /{slug} — must be last to act as catch-all
app.MapControllerRoute( app.MapControllerRoute(
name: "display", name: "display",
pattern: "d/{slug}", pattern: "d/{slug}",
defaults: new { controller = "Display", action = "Show" }); defaults: new { controller = "Display", action = "Show" });
// Also support root-level slugs
app.MapControllerRoute( app.MapControllerRoute(
name: "display-root", name: "display-root",
pattern: "{slug}", pattern: "{slug}",
defaults: new { controller = "Display", action = "Show" }, defaults: new { controller = "Display", action = "Show" },
constraints: new { slug = new NoticeBoard.Routing.DeviceSlugConstraint() }); constraints: new { slug = new NoticeBoard.Routing.DeviceSlugConstraint() });
// Default route goes to admin
app.MapControllerRoute( app.MapControllerRoute(
name: "default", name: "default",
pattern: "", pattern: "",
+45
View File
@@ -0,0 +1,45 @@
@{
Layout = null;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Sign In — Scratching Post</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" />
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" rel="stylesheet" />
<style>
body { background: #f4f6f9; min-height: 100vh; display: flex; align-items: center; justify-content: center; font-family: 'Segoe UI', -apple-system, BlinkMacSystemFont, Roboto, sans-serif; }
.login-card { width: 100%; max-width: 400px; }
.login-brand { text-align: center; margin-bottom: 2em; }
.login-brand i { font-size: 2.5em; color: #d48806; }
.login-brand h1 { font-size: 1.6em; font-weight: 700; margin: 0.3em 0 0; color: #1a1a2e; }
.login-brand p { color: #6b7280; font-size: 0.85em; }
</style>
</head>
<body>
<div class="login-card">
<div class="login-brand">
<i class="bi bi-sun"></i>
<h1>Sunbeam</h1>
<p>Scratching Post Admin</p>
</div>
<div class="card shadow-sm">
<div class="card-body p-4">
@if (ViewBag.Error != null)
{
<div class="alert alert-danger py-2"><i class="bi bi-exclamation-circle me-1"></i>@ViewBag.Error</div>
}
<form method="post" asp-action="Login">
@if (ViewBag.ReturnUrl != null) { <input type="hidden" name="returnUrl" value="@ViewBag.ReturnUrl" /> }
<div class="mb-3"><label class="form-label">Username</label><input type="text" name="username" class="form-control" required autofocus /></div>
<div class="mb-4"><label class="form-label">Password</label><input type="password" name="password" class="form-control" required /></div>
<button type="submit" class="btn btn-primary w-100"><i class="bi bi-box-arrow-in-right me-1"></i>Sign In</button>
</form>
</div>
</div>
<p class="text-center text-muted mt-3" style="font-size:0.8em;">© Jess Rogerson — 2026</p>
</div>
</body>
</html>
+5 -1
View File
@@ -6,5 +6,9 @@
} }
}, },
"AllowedHosts": "*", "AllowedHosts": "*",
"MaxUploadSizeMB": 20 "MaxUploadSizeMB": 20,
"Admin": {
"Username": "admin",
"Password": "ScratchingPost2026!"
}
} }