< 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
0%
Covered lines: 0
Uncovered lines: 37
Coverable lines: 37
Total lines: 76
Line coverage: 0%
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%210%
CreateUserSession()100%210%
LogUserIn(...)100%210%
UpdateLastUsedForUserToken()0%7280%
.ctor(...)100%210%

File(s)

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

#LineLine coverage
 1// SPDX-FileCopyrightText: 2026 Alper Çelik <[email protected]>
 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
 026    public static string UserTokenPrefix => UserTokenPrefixName + PrefixSeparator;
 27
 28
 29    public static async Task<string> CreateUserSession(Guid userId, string sessionName, PGContext ctx)
 030    {
 31
 032        var apiToken = new byte[32];
 033        SecureRandom.Fill(apiToken);
 34
 035        Span<byte> tokenHash = stackalloc byte[32];
 036        BLAKE2b.ComputeHash(tokenHash, apiToken);
 37
 038        var dbToken = new UserToken
 039        {
 040            UserId = userId,
 041            TokenHash = tokenHash.ToArray(),
 042            Permissions = UserPermissionBits.All,
 043            CreationTime = SystemClock.Instance.GetCurrentInstant(),
 044        };
 45
 046        await ctx.UserTokens.AddAsync(dbToken);
 47
 048        return UserTokenPrefix + Base64Url.EncodeToString(apiToken);
 049    }
 50
 51    public static Ok<LoginResultDTO> LogUserIn(HttpContext ctx, string token)
 052    {
 053        ctx.Response.Cookies.Append(TokenCookieName, token, new CookieOptions
 054        {
 055            IsEssential = true,
 056            SameSite = SameSiteMode.Strict,
 057            Secure = ctx.Request.Scheme == "https",
 058            HttpOnly = true,
 059        });
 060        return TypedResults.Ok(new LoginResultDTO(token));
 061    }
 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
 075    public record LoginResultDTO(string AuthToken);
 76}