From df0737e55afeb5c01eb39909605970d1d3a73ffc Mon Sep 17 00:00:00 2001 From: Melodi <62381002+Melodi17@users.noreply.github.com> Date: Sun, 13 Sep 2026 13:22:20 +1000 Subject: refactor: cleanup; feat: connection to agent --- .../Services/AgentAuthenticationHandler.cs | 79 ++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 src/marlin.web/Services/AgentAuthenticationHandler.cs (limited to 'src/marlin.web/Services/AgentAuthenticationHandler.cs') diff --git a/src/marlin.web/Services/AgentAuthenticationHandler.cs b/src/marlin.web/Services/AgentAuthenticationHandler.cs new file mode 100644 index 0000000..519d109 --- /dev/null +++ b/src/marlin.web/Services/AgentAuthenticationHandler.cs @@ -0,0 +1,79 @@ +namespace marlin.web.Services; + +using System.Security.Claims; +using System.Text.Encodings.Web; +using Data; +using Microsoft.AspNetCore.Authentication; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Options; +using Microsoft.Extensions.Primitives; + +public class AgentSchemeOptions : AuthenticationSchemeOptions +{ + public const string Name = "Agent"; + public string AgentHeader { get; set; } = "Agent"; + public string JobHeader { get; set; } = "Job"; +} + +public class AgentAuthenticationHandler : AuthenticationHandler +{ + private readonly ApplicationDbContext _dbContext; + + public AgentAuthenticationHandler( + IOptionsMonitor options, + ILoggerFactory logger, + UrlEncoder encoder, + ApplicationDbContext dbContext) : base(options, logger, encoder) + { + this._dbContext = dbContext; + } + + private bool TryReadPair(string headerName, out Guid id, out string token) + { + id = Guid.Empty; + token = string.Empty; + + if (!Request.Headers.TryGetValue(headerName, out StringValues header)) + return false; + + var parts = header.ToString().Split(' ', 2); + if (parts.Length != 2 || parts[1].Length == 0) + return false; + + token = parts[1]; + return Guid.TryParse(parts[0], out id); + } + + protected override async Task HandleAuthenticateAsync() + { + var claims = new List(); + + if (!TryReadPair(Options.AgentHeader, out var agentId, out var agentToken)) + return AuthenticateResult.NoResult(); + + var agent = await this._dbContext.Agents + .FirstOrDefaultAsync(x => x.Id == agentId && x.AuthToken == agentToken); + + if (agent == null) + return AuthenticateResult.Fail("Invalid agent credentials."); + + claims.Add(new Claim("agent_id", agent.Id.ToString())); + + if (TryReadPair(Options.JobHeader, out var jobId, out var jobToken)) + { + var job = await this._dbContext.Jobs + .FirstOrDefaultAsync(x => x.Id == jobId && x.AgentId == agent.Id && x.ClaimToken == jobToken); + + if (job == null) + return AuthenticateResult.Fail("Invalid job claim token."); + + claims.Add(new Claim("job_id", job.Id.ToString())); + } + + var identity = new ClaimsIdentity(claims, this.Scheme.Name); + var principal = new ClaimsPrincipal(identity); + var ticket = new AuthenticationTicket(principal, this.Scheme.Name); + + return AuthenticateResult.Success(ticket); + } +} -- cgit v1.3.1