diff --git a/RR3CommunityServer/Pages/Admin.cshtml.cs b/RR3CommunityServer/Pages/Admin.cshtml.cs index 64332e9..ff9a69a 100644 --- a/RR3CommunityServer/Pages/Admin.cshtml.cs +++ b/RR3CommunityServer/Pages/Admin.cshtml.cs @@ -1,10 +1,12 @@ using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.AspNetCore.Authorization; using Microsoft.EntityFrameworkCore; using RR3CommunityServer.Data; using static RR3CommunityServer.Data.RR3DbContext; namespace RR3CommunityServer.Pages; +[Authorize] public class AdminModel : PageModel { private readonly RR3DbContext _context; diff --git a/RR3CommunityServer/Pages/Catalog.cshtml.cs b/RR3CommunityServer/Pages/Catalog.cshtml.cs index 8a092a3..6d1faba 100644 --- a/RR3CommunityServer/Pages/Catalog.cshtml.cs +++ b/RR3CommunityServer/Pages/Catalog.cshtml.cs @@ -1,11 +1,13 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.AspNetCore.Authorization; using Microsoft.EntityFrameworkCore; using RR3CommunityServer.Data; using static RR3CommunityServer.Data.RR3DbContext; namespace RR3CommunityServer.Pages; +[Authorize] public class CatalogModel : PageModel { private readonly RR3DbContext _context; diff --git a/RR3CommunityServer/Pages/DeviceSettings.cshtml.cs b/RR3CommunityServer/Pages/DeviceSettings.cshtml.cs index d53d7a3..4d7b02b 100644 --- a/RR3CommunityServer/Pages/DeviceSettings.cshtml.cs +++ b/RR3CommunityServer/Pages/DeviceSettings.cshtml.cs @@ -1,11 +1,13 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.AspNetCore.Authorization; using Microsoft.EntityFrameworkCore; using RR3CommunityServer.Data; using RR3CommunityServer.Models; namespace RR3CommunityServer.Pages; +[Authorize] public class DeviceSettingsModel : PageModel { private readonly RR3DbContext _context; diff --git a/RR3CommunityServer/Pages/Login.cshtml b/RR3CommunityServer/Pages/Login.cshtml new file mode 100644 index 0000000..9a411ca --- /dev/null +++ b/RR3CommunityServer/Pages/Login.cshtml @@ -0,0 +1,163 @@ +@page +@model RR3CommunityServer.Pages.LoginModel +@{ + ViewData["Title"] = "Login"; + Layout = null; +} + + + + + + + Login - RR3 Community Server + + + +
+ + + @if (!string.IsNullOrEmpty(Model.ErrorMessage)) + { +
+ @Model.ErrorMessage +
+ } + +
+
+ + +
+ +
+ + +
+ + +
+ + +
+ + diff --git a/RR3CommunityServer/Pages/Login.cshtml.cs b/RR3CommunityServer/Pages/Login.cshtml.cs new file mode 100644 index 0000000..a0b2759 --- /dev/null +++ b/RR3CommunityServer/Pages/Login.cshtml.cs @@ -0,0 +1,86 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Cookies; +using System.Security.Claims; +using RR3CommunityServer.Services; +using RR3CommunityServer.Models; + +namespace RR3CommunityServer.Pages; + +public class LoginModel : PageModel +{ + private readonly IAuthService _authService; + private readonly ILogger _logger; + + public LoginModel(IAuthService authService, ILogger logger) + { + _authService = authService; + _logger = logger; + } + + [BindProperty] + public string Username { get; set; } = string.Empty; + + [BindProperty] + public string Password { get; set; } = string.Empty; + + public string? ErrorMessage { get; set; } + + public void OnGet() + { + // If already logged in, redirect to admin panel + if (User.Identity?.IsAuthenticated == true) + { + Response.Redirect("/admin"); + } + } + + public async Task OnPostAsync() + { + if (string.IsNullOrWhiteSpace(Username) || string.IsNullOrWhiteSpace(Password)) + { + ErrorMessage = "Username and password are required"; + return Page(); + } + + var loginRequest = new LoginRequest + { + UsernameOrEmail = Username, + Password = Password + }; + + var (success, response, error) = await _authService.LoginAsync(loginRequest); + + if (!success || response == null) + { + ErrorMessage = error ?? "Invalid username or password"; + _logger.LogWarning("Failed login attempt for: {Username}", Username); + return Page(); + } + + // Create authentication cookie + var claims = new List + { + new Claim(ClaimTypes.NameIdentifier, response.AccountId.ToString()), + new Claim(ClaimTypes.Name, response.Username), + new Claim(ClaimTypes.Email, response.Email) + }; + + var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme); + var authProperties = new AuthenticationProperties + { + IsPersistent = true, // Remember me + ExpiresUtc = response.ExpiresAt + }; + + await HttpContext.SignInAsync( + CookieAuthenticationDefaults.AuthenticationScheme, + new ClaimsPrincipal(claimsIdentity), + authProperties); + + _logger.LogInformation("User logged in to admin panel: {Username}", response.Username); + + return RedirectToPage("/Admin"); + } +} diff --git a/RR3CommunityServer/Pages/Logout.cshtml.cs b/RR3CommunityServer/Pages/Logout.cshtml.cs new file mode 100644 index 0000000..d90d1c3 --- /dev/null +++ b/RR3CommunityServer/Pages/Logout.cshtml.cs @@ -0,0 +1,27 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Cookies; + +namespace RR3CommunityServer.Pages; + +public class LogoutModel : PageModel +{ + private readonly ILogger _logger; + + public LogoutModel(ILogger logger) + { + _logger = logger; + } + + public async Task OnGetAsync() + { + var username = User.Identity?.Name ?? "Unknown"; + + await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); + + _logger.LogInformation("User logged out: {Username}", username); + + return RedirectToPage("/Login"); + } +} diff --git a/RR3CommunityServer/Pages/Purchases.cshtml.cs b/RR3CommunityServer/Pages/Purchases.cshtml.cs index 522f70b..7201e93 100644 --- a/RR3CommunityServer/Pages/Purchases.cshtml.cs +++ b/RR3CommunityServer/Pages/Purchases.cshtml.cs @@ -1,11 +1,13 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.AspNetCore.Authorization; using Microsoft.EntityFrameworkCore; using RR3CommunityServer.Data; using static RR3CommunityServer.Data.RR3DbContext; namespace RR3CommunityServer.Pages; +[Authorize] public class PurchasesModel : PageModel { private readonly RR3DbContext _context; diff --git a/RR3CommunityServer/Pages/Register.cshtml b/RR3CommunityServer/Pages/Register.cshtml new file mode 100644 index 0000000..cb808d6 --- /dev/null +++ b/RR3CommunityServer/Pages/Register.cshtml @@ -0,0 +1,208 @@ +@page +@model RR3CommunityServer.Pages.RegisterModel +@{ + ViewData["Title"] = "Register"; + Layout = null; +} + + + + + + + Register - RR3 Community Server + + + +
+ + +
+ Starting Resources:
+ • 100,000 Gold
+ • 500,000 Cash
+ • Access to admin panel +
+ + @if (!string.IsNullOrEmpty(Model.ErrorMessage)) + { +
+ @Model.ErrorMessage +
+ } + + @if (!string.IsNullOrEmpty(Model.SuccessMessage)) + { +
+ @Model.SuccessMessage +
+ } + +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ + +
+ + +
+ + diff --git a/RR3CommunityServer/Pages/Register.cshtml.cs b/RR3CommunityServer/Pages/Register.cshtml.cs new file mode 100644 index 0000000..48fac2e --- /dev/null +++ b/RR3CommunityServer/Pages/Register.cshtml.cs @@ -0,0 +1,110 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Cookies; +using System.Security.Claims; +using RR3CommunityServer.Services; +using RR3CommunityServer.Models; + +namespace RR3CommunityServer.Pages; + +public class RegisterModel : PageModel +{ + private readonly IAuthService _authService; + private readonly ILogger _logger; + + public RegisterModel(IAuthService authService, ILogger logger) + { + _authService = authService; + _logger = logger; + } + + [BindProperty] + public string Username { get; set; } = string.Empty; + + [BindProperty] + public string Email { get; set; } = string.Empty; + + [BindProperty] + public string Password { get; set; } = string.Empty; + + [BindProperty] + public string ConfirmPassword { get; set; } = string.Empty; + + public string? ErrorMessage { get; set; } + public string? SuccessMessage { get; set; } + + public void OnGet() + { + // If already logged in, redirect to admin panel + if (User.Identity?.IsAuthenticated == true) + { + Response.Redirect("/admin"); + } + } + + public async Task OnPostAsync() + { + if (string.IsNullOrWhiteSpace(Username) || string.IsNullOrWhiteSpace(Email) || + string.IsNullOrWhiteSpace(Password) || string.IsNullOrWhiteSpace(ConfirmPassword)) + { + ErrorMessage = "All fields are required"; + return Page(); + } + + var registerRequest = new RegisterRequest + { + Username = Username, + Email = Email, + Password = Password, + ConfirmPassword = ConfirmPassword + }; + + var (success, token, error) = await _authService.RegisterAsync(registerRequest); + + if (!success || string.IsNullOrEmpty(token)) + { + ErrorMessage = error ?? "Registration failed"; + _logger.LogWarning("Failed registration attempt for: {Username}", Username); + return Page(); + } + + _logger.LogInformation("New account registered: {Username} ({Email})", Username, Email); + + // Auto-login after registration + var loginRequest = new LoginRequest + { + UsernameOrEmail = Username, + Password = Password + }; + + var (loginSuccess, response, loginError) = await _authService.LoginAsync(loginRequest); + + if (loginSuccess && response != null) + { + var claims = new List + { + new Claim(ClaimTypes.NameIdentifier, response.AccountId.ToString()), + new Claim(ClaimTypes.Name, response.Username), + new Claim(ClaimTypes.Email, response.Email) + }; + + var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme); + var authProperties = new AuthenticationProperties + { + IsPersistent = true, + ExpiresUtc = response.ExpiresAt + }; + + await HttpContext.SignInAsync( + CookieAuthenticationDefaults.AuthenticationScheme, + new ClaimsPrincipal(claimsIdentity), + authProperties); + + return RedirectToPage("/Admin"); + } + + SuccessMessage = "Account created successfully! Please login."; + return RedirectToPage("/Login"); + } +} diff --git a/RR3CommunityServer/Pages/Rewards.cshtml.cs b/RR3CommunityServer/Pages/Rewards.cshtml.cs index 39896f3..49b891e 100644 --- a/RR3CommunityServer/Pages/Rewards.cshtml.cs +++ b/RR3CommunityServer/Pages/Rewards.cshtml.cs @@ -1,11 +1,13 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.AspNetCore.Authorization; using Microsoft.EntityFrameworkCore; using RR3CommunityServer.Data; using static RR3CommunityServer.Data.RR3DbContext; namespace RR3CommunityServer.Pages; +[Authorize] public class RewardsModel : PageModel { private readonly RR3DbContext _context; diff --git a/RR3CommunityServer/Pages/Sessions.cshtml.cs b/RR3CommunityServer/Pages/Sessions.cshtml.cs index 58c314f..5bbd608 100644 --- a/RR3CommunityServer/Pages/Sessions.cshtml.cs +++ b/RR3CommunityServer/Pages/Sessions.cshtml.cs @@ -1,11 +1,13 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.AspNetCore.Authorization; using Microsoft.EntityFrameworkCore; using RR3CommunityServer.Data; using static RR3CommunityServer.Data.RR3DbContext; namespace RR3CommunityServer.Pages; +[Authorize] public class SessionsModel : PageModel { private readonly RR3DbContext _context; diff --git a/RR3CommunityServer/Pages/Settings.cshtml.cs b/RR3CommunityServer/Pages/Settings.cshtml.cs index 2cb1e6e..1c772c1 100644 --- a/RR3CommunityServer/Pages/Settings.cshtml.cs +++ b/RR3CommunityServer/Pages/Settings.cshtml.cs @@ -1,10 +1,12 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.AspNetCore.Authorization; using Microsoft.EntityFrameworkCore; using RR3CommunityServer.Data; namespace RR3CommunityServer.Pages; +[Authorize] public class SettingsModel : PageModel { private readonly RR3DbContext _context; diff --git a/RR3CommunityServer/Pages/Users.cshtml.cs b/RR3CommunityServer/Pages/Users.cshtml.cs index f5947e7..6614181 100644 --- a/RR3CommunityServer/Pages/Users.cshtml.cs +++ b/RR3CommunityServer/Pages/Users.cshtml.cs @@ -1,11 +1,13 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.AspNetCore.Authorization; using Microsoft.EntityFrameworkCore; using RR3CommunityServer.Data; using static RR3CommunityServer.Data.RR3DbContext; namespace RR3CommunityServer.Pages; +[Authorize] public class UsersModel : PageModel { private readonly RR3DbContext _context; diff --git a/RR3CommunityServer/Pages/_Layout.cshtml b/RR3CommunityServer/Pages/_Layout.cshtml index e2368ea..a2a4e6d 100644 --- a/RR3CommunityServer/Pages/_Layout.cshtml +++ b/RR3CommunityServer/Pages/_Layout.cshtml @@ -108,6 +108,14 @@ API + diff --git a/RR3CommunityServer/Program.cs b/RR3CommunityServer/Program.cs index e72d0ac..7f3f50b 100644 --- a/RR3CommunityServer/Program.cs +++ b/RR3CommunityServer/Program.cs @@ -1,4 +1,5 @@ using Microsoft.EntityFrameworkCore; +using Microsoft.AspNetCore.Authentication.Cookies; using RR3CommunityServer.Data; using RR3CommunityServer.Services; using RR3CommunityServer.Middleware; @@ -8,6 +9,20 @@ var builder = WebApplication.CreateBuilder(args); // Add services to the container builder.Services.AddControllers(); builder.Services.AddRazorPages(); // Add Razor Pages support + +// Add cookie authentication +builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) + .AddCookie(options => + { + options.LoginPath = "/Login"; + options.LogoutPath = "/Logout"; + options.AccessDeniedPath = "/Login"; + options.ExpireTimeSpan = TimeSpan.FromDays(30); + options.SlidingExpiration = true; + }); + +builder.Services.AddAuthorization(); + builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); @@ -53,16 +68,19 @@ using (var scope = app.Services.CreateScope()) app.UseHttpsRedirection(); app.UseCors(); +// Authentication & Authorization +app.UseAuthentication(); +app.UseAuthorization(); + // Custom middleware app.UseMiddleware(); app.UseMiddleware(); -app.UseAuthorization(); app.MapControllers(); app.MapRazorPages(); // Add Razor Pages routing -// Redirect root to admin panel -app.MapGet("/", () => Results.Redirect("/admin")); +// Redirect root to login page +app.MapGet("/", () => Results.Redirect("/Login")); Console.WriteLine("╔══════════════════════════════════════════════════════════╗"); Console.WriteLine("║ Real Racing 3 Community Server - RUNNING ║");