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); } }