< Summary

Information
Class: Api.Auth.Utils.LoginUtils
Assembly: Api
File(s): /home/runner/work/ProjectRead.ing/ProjectRead.ing/Api/Auth/Utils/LoginUtils.cs
Line coverage
72%
Covered lines: 27
Uncovered lines: 10
Coverable lines: 37
Total lines: 76
Line coverage: 72.9%
Branch coverage
0%
Covered branches: 0
Total branches: 8
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
get_UserTokenPrefix()100%11100%
CreateUserSession()100%11100%
LogUserIn(...)100%11100%
UpdateLastUsedForUserToken()0%7280%
.ctor(...)100%11100%

File(s)

/home/runner/work/ProjectRead.ing/ProjectRead.ing/Api/Auth/Utils/LoginUtils.cs

#LineLine coverage
 1// SPDX-FileCopyrightText: 2026 Alper Çelik <alper@alper-celik.dev>
 2//
 3// SPDX-License-Identifier: AGPL-3.0-or-later
 4
 5using System.Buffers.Text;
 6
 7using Api.Auth.Models;
 8using Api.Database;
 9
 10using Geralt;
 11
 12using Microsoft.AspNetCore.Http.HttpResults;
 13using Microsoft.EntityFrameworkCore;
 14
 15using NodaTime;
 16
 17namespace Api.Auth.Utils;
 18
 19public static class LoginUtils
 20{
 21
 22    public const char PrefixSeparator = '_';
 23    public const string UserTokenPrefixName = "user";
 24    public const string TokenCookieName = "auth_token";
 25
 126    public static string UserTokenPrefix => UserTokenPrefixName + PrefixSeparator;
 27
 28
 29    public static async Task<string> CreateUserSession(Guid userId, string sessionName, PGContext ctx)
 130    {
 31
 132        var apiToken = new byte[32];
 133        SecureRandom.Fill(apiToken);
 34
 135        Span<byte> tokenHash = stackalloc byte[32];
 136        BLAKE2b.ComputeHash(tokenHash, apiToken);
 37
 138        var dbToken = new UserToken
 139        {
 140            UserId = userId,
 141            TokenHash = tokenHash.ToArray(),
 142            Permissions = UserPermissionBits.All,
 143            CreationTime = SystemClock.Instance.GetCurrentInstant(),
 144        };
 45
 146        await ctx.UserTokens.AddAsync(dbToken);
 47
 148        return UserTokenPrefix + Base64Url.EncodeToString(apiToken);
 149    }
 50
 51    public static Ok<LoginResultDTO> LogUserIn(HttpContext ctx, string token)
 152    {
 153        ctx.Response.Cookies.Append(TokenCookieName, token, new CookieOptions
 154        {
 155            IsEssential = true,
 156            SameSite = SameSiteMode.Strict,
 157            Secure = ctx.Request.Scheme == "https",
 158            HttpOnly = true,
 159        });
 160        return TypedResults.Ok(new LoginResultDTO(token));
 161    }
 62
 63    public static async Task UpdateLastUsedForUserToken(UserToken token, PGContext ctx)
 064    {
 065        var currentTime = SystemClock.Instance.GetCurrentInstant();
 066        if (token.LastUsed is null ||
 067                token.LastUsed + Duration.FromMinutes(1) <= currentTime)
 068        {
 069            await ctx.UserTokens.Where(ut => ut.TokenHash.SequenceEqual(token.TokenHash))
 070                .ExecuteUpdateAsync(setter =>
 071                        setter.SetProperty(ut => ut.LastUsed, currentTime));
 072        }
 073    }
 74
 175    public record LoginResultDTO(string AuthToken);
 76}