From a312da919c3fa20d01842a60d6c200eedd7c9f3e Mon Sep 17 00:00:00 2001 From: kjh2064 Date: Thu, 9 Jul 2026 13:31:54 +0900 Subject: [PATCH] Restore admin layout and harden login --- src/TaxBaik.Web/Pages/Admin/Login.cshtml | 14 ++- src/TaxBaik.Web/Pages/Admin/Login.cshtml.cs | 36 ++++++- src/TaxBaik.Web/Pages/Admin/Logout.cshtml | 19 ++-- .../Pages/Admin/_AdminLayout.cshtml | 100 +++++++++++++++--- .../Pages/Shared/_AdminLayout.cshtml | 26 +++++ src/TaxBaik.Web/Program.cs | 27 +++++ 6 files changed, 194 insertions(+), 28 deletions(-) create mode 100644 src/TaxBaik.Web/Pages/Shared/_AdminLayout.cshtml diff --git a/src/TaxBaik.Web/Pages/Admin/Login.cshtml b/src/TaxBaik.Web/Pages/Admin/Login.cshtml index 833c0d0..1510d1b 100644 --- a/src/TaxBaik.Web/Pages/Admin/Login.cshtml +++ b/src/TaxBaik.Web/Pages/Admin/Login.cshtml @@ -1,7 +1,8 @@ @page "/admin/login" @model TaxBaik.Web.Pages.Admin.LoginModel +@inject VersionInfo VersionInfo @{ - Layout = null; + Layout = "~/Pages/Shared/_AdminLayout.cshtml"; ViewData["Title"] = "관리자 로그인"; ViewData["Description"] = "관리자 로그인 페이지입니다."; ViewData["CanonicalUrl"] = $"{Request.Scheme}://{Request.Host}/admin/login"; @@ -27,6 +28,11 @@ +
+ 현재 버전 + v@(VersionInfo.Version) · @VersionInfo.Built +
+ @if (!string.IsNullOrWhiteSpace(Model.ErrorMessage)) { @@ -49,8 +55,8 @@
- - + +
+ +
+ +
+ +
+

+ +

+ +
+
+

+ +

+
+ +
+
+
+

+ +

+ +
+ + + -
- @RenderBody() +
+
+
+ @RenderBody() +
+
diff --git a/src/TaxBaik.Web/Pages/Shared/_AdminLayout.cshtml b/src/TaxBaik.Web/Pages/Shared/_AdminLayout.cshtml new file mode 100644 index 0000000..e80f222 --- /dev/null +++ b/src/TaxBaik.Web/Pages/Shared/_AdminLayout.cshtml @@ -0,0 +1,26 @@ +@inject VersionInfo VersionInfo + + + + + + @(ViewData["Title"] ?? "TaxBaik Admin") + + + + + + + @RenderBody() + + + @await RenderSectionAsync("Scripts", required: false) + + diff --git a/src/TaxBaik.Web/Program.cs b/src/TaxBaik.Web/Program.cs index eac5847..9178261 100644 --- a/src/TaxBaik.Web/Program.cs +++ b/src/TaxBaik.Web/Program.cs @@ -70,6 +70,19 @@ builder.Services.AddRateLimiter(options => AutoReplenishment = true }); }); + options.AddPolicy("admin-login", httpContext => + { + var ip = httpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown"; + return RateLimitPartition.GetFixedWindowLimiter( + partitionKey: $"admin-login:{ip}", + factory: _ => new FixedWindowRateLimiterOptions + { + PermitLimit = 5, + Window = TimeSpan.FromMinutes(1), + QueueLimit = 0, + AutoReplenishment = true + }); + }); }); // Razor Pages @@ -319,6 +332,20 @@ app.UseRateLimiter(); app.UseAuthentication(); app.UseAuthorization(); app.UseAntiforgery(); +app.Use(async (context, next) => +{ + context.Response.Headers["X-Content-Type-Options"] = "nosniff"; + context.Response.Headers["X-Frame-Options"] = "DENY"; + context.Response.Headers["Referrer-Policy"] = "strict-origin-when-cross-origin"; + context.Response.Headers["Permissions-Policy"] = "camera=(), microphone=(), geolocation=()"; + context.Response.Headers["X-Permitted-Cross-Domain-Policies"] = "none"; + if (!context.Response.Headers.ContainsKey("Content-Security-Policy")) + { + context.Response.Headers["Content-Security-Policy"] = "default-src 'self'; img-src 'self' data: https:; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com data:; script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://www.googletagmanager.com; connect-src 'self' https:; frame-ancestors 'none'; base-uri 'self'; form-action 'self';"; + } + + await next(); +}); // Razor Pages + 정적 자산 매핑 app.MapHealthChecks("/healthz");