feat: Implement PostgreSQL repositories for various entities
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled

- Added BootstrapInviteRepository for managing bootstrap invites.
- Added ClientRepository for handling OAuth/OpenID clients.
- Introduced LoginAttemptRepository for logging login attempts.
- Created OidcTokenRepository for managing OpenIddict tokens and refresh tokens.
- Implemented RevocationExportStateRepository for persisting revocation export state.
- Added RevocationRepository for managing revocations.
- Introduced ServiceAccountRepository for handling service accounts.
This commit is contained in:
master
2025-12-11 17:48:25 +02:00
parent 1995883476
commit ab22181e8b
82 changed files with 5153 additions and 2261 deletions

View File

@@ -71,8 +71,10 @@ internal static class AirGapEndpoints
var budget = request.StalenessBudget ?? StalenessBudget.Default;
var now = timeProvider.GetUtcNow();
var state = await service.SealAsync(tenantId, request.PolicyHash!, anchor, budget, now, cancellationToken);
var status = new AirGapStatus(state, stalenessCalculator.Evaluate(anchor, budget, now), now);
var state = await service.SealAsync(tenantId, request.PolicyHash!, anchor, budget, now, request.ContentBudgets, cancellationToken);
var staleness = stalenessCalculator.Evaluate(anchor, budget, now);
var contentStaleness = stalenessCalculator.EvaluateContent(anchor, state.ContentBudgets, now);
var status = new AirGapStatus(state, staleness, contentStaleness, now);
telemetry.RecordSeal(tenantId, status);
return Results.Ok(AirGapStatusResponse.FromStatus(status));
}
@@ -86,8 +88,10 @@ internal static class AirGapEndpoints
CancellationToken cancellationToken)
{
var tenantId = ResolveTenant(httpContext);
var state = await service.UnsealAsync(tenantId, timeProvider.GetUtcNow(), cancellationToken);
var status = new AirGapStatus(state, StalenessEvaluation.Unknown, timeProvider.GetUtcNow());
var now = timeProvider.GetUtcNow();
var state = await service.UnsealAsync(tenantId, now, cancellationToken);
var emptyContentStaleness = new Dictionary<string, StalenessEvaluation>(StringComparer.OrdinalIgnoreCase);
var status = new AirGapStatus(state, StalenessEvaluation.Unknown, emptyContentStaleness, now);
telemetry.RecordUnseal(tenantId, status);
return Results.Ok(AirGapStatusResponse.FromStatus(status));
}

View File

@@ -11,7 +11,9 @@ public sealed record AirGapStatusResponse(
TimeAnchor TimeAnchor,
StalenessEvaluation Staleness,
long DriftSeconds,
long DriftBaselineSeconds,
long SecondsRemaining,
IReadOnlyDictionary<string, ContentStalenessEntry> ContentStaleness,
DateTimeOffset LastTransitionAt,
DateTimeOffset EvaluatedAt)
{
@@ -23,7 +25,30 @@ public sealed record AirGapStatusResponse(
status.State.TimeAnchor,
status.Staleness,
status.Staleness.AgeSeconds,
status.State.DriftBaselineSeconds,
status.Staleness.SecondsRemaining,
BuildContentStaleness(status.ContentStaleness),
status.State.LastTransitionAt,
status.EvaluatedAt);
private static IReadOnlyDictionary<string, ContentStalenessEntry> BuildContentStaleness(
IReadOnlyDictionary<string, StalenessEvaluation> evaluations)
{
var result = new Dictionary<string, ContentStalenessEntry>(StringComparer.OrdinalIgnoreCase);
foreach (var kvp in evaluations)
{
result[kvp.Key] = ContentStalenessEntry.FromEvaluation(kvp.Value);
}
return result;
}
}
public sealed record ContentStalenessEntry(
long AgeSeconds,
long SecondsRemaining,
bool IsWarning,
bool IsBreach)
{
public static ContentStalenessEntry FromEvaluation(StalenessEvaluation eval) =>
new(eval.AgeSeconds, eval.SecondsRemaining, eval.IsWarning, eval.IsBreach);
}

View File

@@ -11,4 +11,10 @@ public sealed class SealRequest
public TimeAnchor? TimeAnchor { get; set; }
public StalenessBudget? StalenessBudget { get; set; }
/// <summary>
/// Optional per-content staleness budgets (advisories, vex, policy).
/// Falls back to StalenessBudget when not provided.
/// </summary>
public Dictionary<string, StalenessBudget>? ContentBudgets { get; set; }
}