summaryrefslogtreecommitdiff
path: root/src/marlin.web/Services/AgentAuthenticationHandler.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/marlin.web/Services/AgentAuthenticationHandler.cs')
-rw-r--r--src/marlin.web/Services/AgentAuthenticationHandler.cs79
1 files changed, 79 insertions, 0 deletions
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<AgentSchemeOptions>
+{
+ private readonly ApplicationDbContext _dbContext;
+
+ public AgentAuthenticationHandler(
+ IOptionsMonitor<AgentSchemeOptions> 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<AuthenticateResult> HandleAuthenticateAsync()
+ {
+ var claims = new List<Claim>();
+
+ 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);
+ }
+}