📄 MatDenDagen/Services/AdminAuthService.cs
using System;
using System.ComponentModel.DataAnnotations;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace MatDenDagen.Services;

public sealed class AdminAuthService(
    ILogger<AdminAuthService> logger,
    IOptions<AdminAuthOptions> options,
    TimeProvider timeProvider,
    IHttpContextAccessor httpContextAccessor
)
{
    private readonly string hash = options.Value.Hash;
    private readonly TimeSpan loginTime = options.Value.LoginTime;

    public bool ValidatePassword(string password) => BCrypt.Net.BCrypt.Verify(password, hash);

    public async Task SignIn()
    {
        if (httpContextAccessor.HttpContext is not { } httpContext)
        {
            logger.LogError("No HttpContext when signing in.");
            return;
        }

        var now = timeProvider.GetUtcNow();
        var authProperties = new AuthenticationProperties
        {
            IsPersistent = true,
            AllowRefresh = true,
            IssuedUtc = now,
            ExpiresUtc = now + loginTime,
        };

        await httpContext.SignInAsync(
            CookieAuthenticationDefaults.AuthenticationScheme,
            new ClaimsPrincipal(
                new ClaimsIdentity([new(ClaimTypes.Role, "Admin")], CookieAuthenticationDefaults.AuthenticationScheme)
            ),
            authProperties
        );
    }

    public async Task SignOut()
    {
        if (httpContextAccessor.HttpContext is not { } httpContext)
        {
            logger.LogError("No HttpContext when signing out.");
            return;
        }
        await httpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
    }
}

public sealed class AdminAuthOptions
{
    [Required]
    public required string Hash { get; set; }

    [Required]
    public required TimeSpan LoginTime { get; set; }
}

[OptionsValidator]
public sealed partial class AdminAuthOptionsValidator : IValidateOptions<AdminAuthOptions>;

public static class AdminAuthServiceCollectionExtensions
{
    public static IServiceCollection AddAdminService(this IServiceCollection services)
    {
        services.AddOptions<AdminAuthOptions>().BindConfiguration("Admin").ValidateOnStart();
        services.AddTransient<IValidateOptions<AdminAuthOptions>, AdminAuthOptionsValidator>();

        services.AddHttpContextAccessor();

        services.TryAddSingleton(TimeProvider.System);
        services.AddTransient<AdminAuthService>();

        return services;
    }
}