< Summary

Information
Class: Api.Auth.Mutations.LoginInstanceServicePayload
Assembly: Api
File(s): /home/runner/work/ProjectRead.ing/ProjectRead.ing/Api/Auth/Mutations/LoginInstanceService.cs
Line coverage
100%
Covered lines: 1
Uncovered lines: 0
Coverable lines: 1
Total lines: 87
Line coverage: 100%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%

File(s)

/home/runner/work/ProjectRead.ing/ProjectRead.ing/Api/Auth/Mutations/LoginInstanceService.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 System.Security.Cryptography;
 7using System.Text;
 8using System.Text.Unicode;
 9using Api.Auth.Models;
 10using Api.Auth.Utils;
 11using Api.Database;
 12using Api.Database.Utils;
 13using Api.Utils;
 14using FluentValidation;
 15using HotChocolate.Authorization;
 16using NodaTime;
 17
 18namespace Api.Auth.Mutations;
 19
 20/// <param name="CurrentSecond">Unix timestamp in seconds (GraphQL Long).</param>
 21public record LoginInstanceServiceInput(
 22    string AuthProof,
 23    string Salt,
 24    long CurrentSecond
 25);
 26
 127public record LoginInstanceServicePayload(string Token);
 28
 29[MutationType]
 30public static partial class LoginInstanceServiceMutations
 31{
 32    [AllowAnonymous]
 33    public static async Task<LoginInstanceServicePayload> LoginTheInstanceService(
 34        [Service] PGContext db,
 35        [Service] IEFTransactionDIAccessorService txAccessor,
 36        [Service] IConfiguration config,
 37        [Service] IValidator<LoginInstanceServiceInput> validator,
 38        LoginInstanceServiceInput input,
 39        CancellationToken ct
 40    )
 41    {
 42        await validator.ValidateOrThrowAsync(input, ct);
 43
 44        var proof = HKDF.DeriveKey(
 45            HashAlgorithmName.SHA256,
 46            Base64Url.DecodeFromChars(config["ProjectReadingSecretSeed"]),
 47            720 / 8,
 48            Base64Url.DecodeFromChars(input.Salt),
 49            Encoding.UTF8.GetBytes("The Instance Service Auth : " + input.CurrentSecond)
 50        );
 51
 52        if (Geralt.ConstantTime.Equals(proof, Base64Url.DecodeFromChars(input.AuthProof)))
 53        {
 54            string token = await AuthUtils.CreateRemoteServiceSession(
 55                RemoteService.TheInstanceServiceId,
 56                db
 57            );
 58            await db.SaveChangesAsync(ct);
 59
 60            await txAccessor.CommitTX(ct);
 61            return new(token);
 62        }
 63
 64        throw new GraphQLException(
 65            ErrorBuilder
 66                .New()
 67                .SetMessage("Invalid Credentials")
 68                .SetCode(ErrorCodes.INVALID_CREDS)
 69                .Build()
 70        );
 71    }
 72}
 73
 74public class LoginInstanceServiceInputValidator
 75    : AbstractValidator<LoginInstanceServiceInput>
 76{
 77    public LoginInstanceServiceInputValidator()
 78    {
 79        RuleFor(i => i.AuthProof).Must(str => Base64Url.IsValid(str)).Length(120);
 80
 81        RuleFor(i => i.Salt).Must(str => Base64Url.IsValid(str)).MinimumLength(22);
 82
 83        RuleFor(i => i.CurrentSecond)
 84            .LessThan((_) => Now().Plus(Duration.FromMinutes(5)).ToUnixTimeSeconds())
 85            .GreaterThan((_) => Now().Minus(Duration.FromMinutes(5)).ToUnixTimeSeconds());
 86    }
 87}

Methods/Properties

.ctor(string)