feat: Implement Authentik group synchronization and add confirmation dialogs for service management

This commit is contained in:
Matt Batchelder
2026-03-04 21:33:29 -05:00
parent 56d48b6062
commit 9493bdb9df
19 changed files with 556 additions and 21 deletions

View File

@@ -95,7 +95,7 @@ public class AuthentikService : IAuthentikService
}
// ── 4. Create application linked to provider ──────────────────
await CreateApplicationAsync(client, baseUrl, instanceAbbrev, slug, providerId, ct);
await CreateApplicationAsync(client, baseUrl, instanceAbbrev, slug, providerId, instanceBaseUrl, ct);
}
// ── 5. Ensure provider has a signing keypair (required for metadata) ──
@@ -175,6 +175,62 @@ public class AuthentikService : IAuthentikService
}).OrderBy(k => k.Name).ToList() ?? new();
}
/// <inheritdoc />
public async Task<List<AuthentikGroupItem>> ListGroupsAsync(
string? overrideUrl = null, string? overrideApiKey = null,
CancellationToken ct = default)
{
var (_, client) = await CreateAuthenticatedClientAsync(overrideUrl, overrideApiKey);
var groups = new List<AuthentikGroupItem>();
var nextUrl = "/api/v3/core/groups/?page_size=200";
while (!string.IsNullOrEmpty(nextUrl))
{
var resp = await client.GetAsync(nextUrl, ct);
resp.EnsureSuccessStatusCode();
var body = await resp.Content.ReadAsStringAsync(ct);
using var doc = JsonDocument.Parse(body);
var root = doc.RootElement;
if (root.TryGetProperty("results", out var results) && results.ValueKind == JsonValueKind.Array)
{
foreach (var g in results.EnumerateArray())
{
var pk = g.TryGetProperty("pk", out var pkProp) ? pkProp.GetString() ?? "" : "";
var name = g.TryGetProperty("name", out var nProp) ? nProp.GetString() ?? "" : "";
var memberCount = g.TryGetProperty("users_obj", out var usersObj) && usersObj.ValueKind == JsonValueKind.Array
? usersObj.GetArrayLength()
: (g.TryGetProperty("users", out var users) && users.ValueKind == JsonValueKind.Array
? users.GetArrayLength()
: 0);
// Skip Authentik built-in groups (authentik Admins, etc.)
if (!string.IsNullOrEmpty(name) && !name.StartsWith("authentik ", StringComparison.OrdinalIgnoreCase))
{
groups.Add(new AuthentikGroupItem
{
Pk = pk,
Name = name,
MemberCount = memberCount,
});
}
}
}
// Handle pagination
nextUrl = root.TryGetProperty("pagination", out var pagination) &&
pagination.TryGetProperty("next", out var nextProp) &&
nextProp.ValueKind == JsonValueKind.Number
? $"/api/v3/core/groups/?page_size=200&page={nextProp.GetInt32()}"
: null;
}
_logger.LogInformation("[Authentik] Found {Count} group(s)", groups.Count);
return groups.OrderBy(g => g.Name).ToList();
}
// ─────────────────────────────────────────────────────────────────────────
// HTTP client setup
// ─────────────────────────────────────────────────────────────────────────
@@ -640,7 +696,7 @@ public class AuthentikService : IAuthentikService
private async Task CreateApplicationAsync(
HttpClient client, string baseUrl,
string abbrev, string slug, int providerId,
CancellationToken ct)
string instanceBaseUrl, CancellationToken ct)
{
_logger.LogInformation("[Authentik] Creating application '{Slug}' linked to provider {ProviderId}", slug, providerId);
@@ -649,6 +705,7 @@ public class AuthentikService : IAuthentikService
["name"] = $"OTS Signs — {abbrev.ToUpperInvariant()}",
["slug"] = slug,
["provider"] = providerId,
["meta_launch_url"] = instanceBaseUrl.TrimEnd('/'),
};
var jsonBody = JsonSerializer.Serialize(payload);