From fb770657316fe1c751d2879831d4dcb32a103556 Mon Sep 17 00:00:00 2001 From: the1mason Date: Tue, 7 Jan 2025 05:29:38 +0500 Subject: [PATCH] Added simple sessions --- src/FastBlog.Core/DependencyInjection.cs | 47 ++++++++++---- .../Repositories/SessionRepository.cs | 10 +-- .../Repositories/UserRepository.cs | 3 +- src/FastBlog.Core/Services/UserService.cs | 2 +- .../Controllers/UsersController.cs | 61 ++++++++++++++++++- .../Middlewares/SimpleAuthMiddleware.cs | 17 +++--- src/FastBlog.Web/Views/Files/Index.cshtml | 3 - src/FastBlog.Web/Views/Users/Login.cshtml | 35 +++++++++++ 8 files changed, 146 insertions(+), 32 deletions(-) create mode 100644 src/FastBlog.Web/Views/Users/Login.cshtml diff --git a/src/FastBlog.Core/DependencyInjection.cs b/src/FastBlog.Core/DependencyInjection.cs index 0fc7816..de5d005 100644 --- a/src/FastBlog.Core/DependencyInjection.cs +++ b/src/FastBlog.Core/DependencyInjection.cs @@ -3,7 +3,9 @@ using FastBlog.Core.Abstractions.Repositories.Blogs; using FastBlog.Core.Abstractions.Repositories.Files; using FastBlog.Core.Abstractions.Repositories.Users; using FastBlog.Core.Db; +using FastBlog.Core.Models; using FastBlog.Core.Models.Blogs; +using FastBlog.Core.Models.Users; using FastBlog.Core.Options; using FastBlog.Core.Repositories; using FastBlog.Core.Services; @@ -19,22 +21,22 @@ public static class DependencyInjection public static IServiceCollection AddCore(this IServiceCollection services, IConfiguration configuration) { // application - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); // options services.Configure(configuration.GetSection("FileStore")); services.Configure(configuration.GetSection("Sessions")); // infrastructure - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); // storage var connectionString = configuration.GetConnectionString("Sqlite"); @@ -58,8 +60,13 @@ public static class DependencyInjection await using var scope = provider.CreateAsyncScope(); var runner = scope.ServiceProvider.GetRequiredService(); runner.MigrateUp(); + await TryAddMainPage(scope.ServiceProvider); + await TryAddDefaultUser(scope.ServiceProvider); + } - var blogService = scope.ServiceProvider.GetRequiredService(); + private static async Task TryAddMainPage(IServiceProvider provider) + { + var blogService = provider.GetRequiredService(); var mainPage = await blogService.Get(null); if (mainPage is not null) return; @@ -79,6 +86,24 @@ public static class DependencyInjection } }); } + + private static async Task TryAddDefaultUser(this IServiceProvider provider) + { + var userService = provider.GetRequiredService(); + var existingUserResult = await userService.GetUsers(new PagedRequest(1, 0)); + if (existingUserResult.Amount is 1) + return; + + var newUser = new NewUser + { + Username = "admin", + Password = "admin" + }; + + var addResult = await userService.CreateUser(newUser); + if (addResult is null) + throw new ApplicationException("Failed to create new user!"); + } private const string BlogDefaultBody = """ # Index Page diff --git a/src/FastBlog.Core/Repositories/SessionRepository.cs b/src/FastBlog.Core/Repositories/SessionRepository.cs index bf8a4a8..49405c9 100644 --- a/src/FastBlog.Core/Repositories/SessionRepository.cs +++ b/src/FastBlog.Core/Repositories/SessionRepository.cs @@ -25,9 +25,9 @@ public sealed class SessionRepository(SqliteConnectionFactory connectionFactory) using var connection = connectionFactory.Create(); const string sql = """ - INSERT INTO Sessions (Token, UserId, Expires, Active) - VALUES (@Token, @UserId, @Expires, @Active) - ON CONFLICT(Token) DO UPDATE SET Expires = @Expires + INSERT INTO Sessions (Token, UserId, CreatedAt, LastUsed, Active) + VALUES (@Token, @UserId, @CreatedAt, @LastUsed, @Active) + ON CONFLICT(Token) DO UPDATE SET LastUsed = @LastUsed RETURNING * """; return await connection.QueryFirstOrDefaultAsync(sql, session); @@ -39,12 +39,12 @@ public sealed class SessionRepository(SqliteConnectionFactory connectionFactory) const string sql = """ UPDATE Sessions - SET Expires = @Expires + SET LastUsed = @LastUsed WHERE Token = @Token RETURNING * """; return await connection.QueryFirstOrDefaultAsync(sql, - new { Token = token, Expires = DateTime.UtcNow.AddHours(1) }); + new { Token = token, LastUsed = DateTime.UtcNow }); } public async Task Deactivate(string token) diff --git a/src/FastBlog.Core/Repositories/UserRepository.cs b/src/FastBlog.Core/Repositories/UserRepository.cs index e763661..1d076df 100644 --- a/src/FastBlog.Core/Repositories/UserRepository.cs +++ b/src/FastBlog.Core/Repositories/UserRepository.cs @@ -34,7 +34,6 @@ public sealed class UserRepository(SqliteConnectionFactory connectionFactory) : const string sql = """ select COUNT(*) from Users; select * from Users - order by CreatedAt desc limit @Amount offset @Offset; """; using var connection = connectionFactory.Create(); @@ -47,7 +46,7 @@ public sealed class UserRepository(SqliteConnectionFactory connectionFactory) : public async Task CreateUser(NewUser newUser) { - const string sql = "insert into Users (username, passwordHash, email) values (@Username, @Password, @Email) returning *"; + const string sql = "insert into Users (username, passwordHash) values (@Username, @Password) returning *"; using var connection = connectionFactory.Create(); return await connection.QueryFirstAsync(sql, newUser); } diff --git a/src/FastBlog.Core/Services/UserService.cs b/src/FastBlog.Core/Services/UserService.cs index 63d2420..355de7f 100644 --- a/src/FastBlog.Core/Services/UserService.cs +++ b/src/FastBlog.Core/Services/UserService.cs @@ -7,7 +7,7 @@ namespace FastBlog.Core.Services; public sealed class UserService(IUserRepository repository) { public Task Get(int id) => repository.Get(id); - + public async Task GetByCredentials(string username, string password) { var user = await repository.GetByName(username); diff --git a/src/FastBlog.Web/Controllers/UsersController.cs b/src/FastBlog.Web/Controllers/UsersController.cs index ae2bd65..1f5d9b0 100644 --- a/src/FastBlog.Web/Controllers/UsersController.cs +++ b/src/FastBlog.Web/Controllers/UsersController.cs @@ -1,4 +1,6 @@ -using FastBlog.Core.Services; +using System.Diagnostics; +using System.Globalization; +using FastBlog.Core.Services; using Microsoft.AspNetCore.Mvc; namespace FastBlog.Web.Controllers; @@ -6,5 +8,62 @@ namespace FastBlog.Web.Controllers; public sealed class UsersController(UserService userService, SessionService sessionService) : Controller { + public sealed record LoginRequest(string? Login, string? Password); + public sealed record LoginModel(string? Login, string? Password, List Errors); + [HttpGet("/users/login")] + public async Task Login() + { + return View(new LoginModel(null, null, [])); + } + + [HttpPost("/users/login")] + public async Task Login(LoginRequest request) + { + List errors = []; + // validation + if (string.IsNullOrWhiteSpace(request.Login) + || request.Login.Length < 3 || request.Login.Length > 24) + { + errors.Add("Login is required and should be 3 to 24 symbols"); + } + + if (string.IsNullOrWhiteSpace(request.Password) + || request.Password.Length < 3 || request.Password.Length > 64) + { + errors.Add("Password is required and should be 3 to 64 symbols"); + } + + if (errors.Count > 0) + { + return View(new LoginModel(request.Login, request.Password, errors)); + } + + Debug.Assert(request.Login is not null); + Debug.Assert(request.Password is not null); + + // execute + var session = await sessionService.Authenticate(request.Login, request.Password); + + if (session is null) + { + errors.Add("Unknown user or incorrect password"); + return View(new LoginModel(request.Login, request.Password, errors)); + } + + UpdateCookie(HttpContext, "session-t", session.Token); + return Redirect("/"); + } + + private static void UpdateCookie(HttpContext context, string key, string value) + { + context.Response.Cookies.Delete(key); + context.Response.Cookies.Append(key, value, new CookieOptions() + { + HttpOnly = true, + SameSite = SameSiteMode.Strict, + IsEssential = true + // TODO: more options w/ configs, ill think bout that later + }); + } } \ No newline at end of file diff --git a/src/FastBlog.Web/Middlewares/SimpleAuthMiddleware.cs b/src/FastBlog.Web/Middlewares/SimpleAuthMiddleware.cs index 0f0e20c..5b0d2e8 100644 --- a/src/FastBlog.Web/Middlewares/SimpleAuthMiddleware.cs +++ b/src/FastBlog.Web/Middlewares/SimpleAuthMiddleware.cs @@ -1,5 +1,4 @@ -using FastBlog.Core.Models; -using FastBlog.Core.Models.Users; +using FastBlog.Core.Models.Users; using FastBlog.Core.Services; namespace FastBlog.Web.Middlewares; @@ -9,26 +8,26 @@ public class SimpleAuthMiddleware(SessionService service, RequestDelegate next) public async Task Invoke(HttpContext context) { var meta = context.GetEndpoint()?.Metadata.GetMetadata(); - var session = context.Request.Cookies["session"]; - - if (session is null && meta is not null) + var sessionToken = context.Request.Cookies["session-t"]; + + if (sessionToken is null && meta is not null) { - context.Response.Redirect("/"); + context.Response.Redirect("/user/login"); return; } - if (session is null && meta is null) + if (sessionToken is null && meta is null) { await next(context); return; } - var result = await service.Get(session!); + var result = await service.Get(sessionToken!); if (result.IsError) { context.Response.Cookies.Delete("session"); - context.Response.Redirect("/"); + context.Response.Redirect("/user/login"); return; } diff --git a/src/FastBlog.Web/Views/Files/Index.cshtml b/src/FastBlog.Web/Views/Files/Index.cshtml index 3771808..540235e 100644 --- a/src/FastBlog.Web/Views/Files/Index.cshtml +++ b/src/FastBlog.Web/Views/Files/Index.cshtml @@ -31,9 +31,6 @@

- - @Html.RadioButtonFor() - diff --git a/src/FastBlog.Web/Views/Users/Login.cshtml b/src/FastBlog.Web/Views/Users/Login.cshtml new file mode 100644 index 0000000..bf87e89 --- /dev/null +++ b/src/FastBlog.Web/Views/Users/Login.cshtml @@ -0,0 +1,35 @@ +@model FastBlog.Web.Controllers.UsersController.LoginModel + +@{ + ViewBag.Title = "Login"; +} + +
+
+
+ + +
+
+ + +
+
+ +
+ @foreach (var error in Model.Errors) + { +
error
+ } +
+
\ No newline at end of file