< Summary

Information
Class: Api.Auth.Utils.AuthUtils
Assembly: Api
File(s): /home/runner/work/ProjectRead.ing/ProjectRead.ing/Api/Auth/Utils/AuthUtils.cs
Line coverage
75%
Covered lines: 45
Uncovered lines: 15
Coverable lines: 60
Total lines: 118
Line coverage: 75%
Branch coverage
75%
Covered branches: 6
Total branches: 8
Branch coverage: 75%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
get_TokenPrefixesNames()100%11100%
get_UserTokenPrefix()100%11100%
get_RemoteServiceTokenPrefix()100%11100%
CreateRemoteServiceSession()100%11100%
CreateUserSession()100%11100%
CanAdminRegister()100%11100%
LogUserIn(...)100%210%
UpdateLastUsedTokens<T>(...)75%88100%
.ctor(...)100%210%

File(s)

/home/runner/work/ProjectRead.ing/ProjectRead.ing/Api/Auth/Utils/AuthUtils.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;
 6using Api.Auth.Models;
 7using Api.Database;
 8using Api.Database.Utils;
 9using Geralt;
 10using LinqKit;
 11using Microsoft.AspNetCore.Http.HttpResults;
 12using Microsoft.EntityFrameworkCore;
 13using NodaTime;
 14
 15namespace Api.Auth.Utils;
 16
 17public static class AuthUtils
 18{
 19    public const char PrefixSeparator = '_';
 20
 21    public const string UserTokenPrefixName = "user";
 22    public const string RemoteServiceTokenPrefixName = "service";
 23    public static List<string> TokenPrefixesNames =>
 124        [UserTokenPrefixName, RemoteServiceTokenPrefixName];
 25    public const string TokenCookieName = "auth_token";
 26
 127    public static string UserTokenPrefix => UserTokenPrefixName + PrefixSeparator;
 28    public static string RemoteServiceTokenPrefix =>
 129        RemoteServiceTokenPrefixName + PrefixSeparator;
 30
 31    // see https://www.rfc-editor.org/rfc/rfc9106.html#name-recommendations
 32    public const int ARGON2ID_ITER = 3;
 33    public const int ARGON2ID_MEM_BYTES = 64 * 1024 * 1024;
 34
 35    public static async Task<string> CreateRemoteServiceSession(
 36        Guid serviceId,
 37        PGContext db
 38    )
 139    {
 140        byte[] apiToken = new byte[32];
 141        SecureRandom.Fill(apiToken);
 42
 143        byte[] tokenHash = new byte[32];
 144        BLAKE2b.ComputeHash(tokenHash, apiToken);
 45
 146        var serviceToken = new RemoteServiceToken
 147        {
 148            RemoteServiceId = serviceId,
 149            TokenHash = tokenHash,
 150            CreationTime = Now(),
 151        };
 52
 153        await db.RemoteServiceTokens.AddAsync(serviceToken);
 54
 155        return RemoteServiceTokenPrefix + Base64Url.EncodeToString(apiToken);
 156    }
 57
 58    public static async Task<string> CreateUserSession(
 59        Guid userId,
 60        string sessionName,
 61        PGContext ctx
 62    )
 163    {
 164        var apiToken = new byte[32];
 165        SecureRandom.Fill(apiToken);
 66
 167        byte[] tokenHash = new byte[32];
 168        BLAKE2b.ComputeHash(tokenHash, apiToken);
 69
 170        var dbToken = new UserTokenEF
 171        {
 172            UserId = userId,
 173            SessionName = sessionName,
 174            TokenHash = tokenHash,
 175            Permissions = UserPermissionBits.All,
 176            CreationTime = Now(),
 177        };
 78
 179        await ctx.UserTokens.AddAsync(dbToken);
 80
 181        return UserTokenPrefix + Base64Url.EncodeToString(apiToken);
 182    }
 83
 84    public static async Task<bool> CanAdminRegister(PGContext db) =>
 185        !await db.Users.AnyAsync(u => u.Admin == true);
 86
 87    public static Ok<LoginResultDTO> LogUserIn(HttpContext ctx, string token)
 088    {
 089        ctx.Response.Cookies.Append(
 090            TokenCookieName,
 091            token,
 092            new CookieOptions
 093            {
 094                IsEssential = true,
 095                SameSite = SameSiteMode.Strict,
 096                Secure = ctx.Request.Scheme == "https",
 097                HttpOnly = true,
 098            }
 099        );
 0100        return TypedResults.Ok(new LoginResultDTO(token));
 0101    }
 102
 103    public static T UpdateLastUsedTokens<T>(T token)
 104        where T : class, ITokenTime
 1105    {
 1106        var currentTime = SystemClock.Instance.GetCurrentInstant();
 1107        if (
 1108            token.LastUsed is null
 1109            || token.LastUsed + Duration.FromMinutes(10) <= currentTime
 1110        )
 1111        {
 1112            token.LastUsed = currentTime;
 1113        }
 1114        return token;
 1115    }
 116
 0117    public record LoginResultDTO(string AuthToken);
 118}