This commit is contained in:
master
2026-02-04 19:59:20 +02:00
parent 557feefdc3
commit 5548cf83bf
1479 changed files with 53557 additions and 40339 deletions

View File

@@ -9,8 +9,10 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Platform.WebService.Constants;
using StellaOps.Platform.WebService.Contracts;
using StellaOps.Platform.WebService.Options;
using StellaOps.Platform.WebService.Services;
using System;
using System.Threading;
@@ -20,6 +22,9 @@ namespace StellaOps.Platform.WebService.Endpoints;
/// <summary>
/// Setup wizard API endpoints aligned to docs/setup/setup-wizard-ux.md.
/// All endpoints are AllowAnonymous because during initial setup the Authority
/// service is not running. When setup is already complete, the handlers
/// enforce auth via TryResolveContext before proceeding.
/// </summary>
public static class SetupEndpoints
{
@@ -35,6 +40,41 @@ public static class SetupEndpoints
return app;
}
/// <summary>
/// Resolves the request context, falling back to a bootstrap context during initial setup.
/// When setup is complete, requires authenticated context (returns error on failure).
/// </summary>
private static async Task<(PlatformRequestContext Context, IResult? Failure)> ResolveSetupContextAsync(
HttpContext httpContext,
PlatformRequestContextResolver resolver,
SetupStateDetector setupDetector,
IOptions<PlatformServiceOptions> options,
IEnvironmentSettingsStore envSettingsStore,
CancellationToken ct)
{
var dbSettings = await envSettingsStore.GetAllAsync(ct);
var setupState = setupDetector.Detect(options.Value.Storage, dbSettings);
if (setupState == "complete")
{
// Setup already done — require auth for re-configuration
if (!TryResolveContext(httpContext, resolver, out var authContext, out var failure))
{
return (null!, failure);
}
return (authContext!, null);
}
// During initial setup, resolve context best-effort
if (!resolver.TryResolve(httpContext, out var requestContext, out _))
{
// No tenant/auth available — use bootstrap context
requestContext = new PlatformRequestContext("setup", "setup-wizard", null);
}
return (requestContext!, null);
}
private static void MapSessionEndpoints(IEndpointRouteBuilder setup)
{
var sessions = setup.MapGroup("/sessions").WithTags("Setup Sessions");
@@ -44,16 +84,18 @@ public static class SetupEndpoints
HttpContext context,
PlatformRequestContextResolver resolver,
PlatformSetupService service,
SetupStateDetector setupDetector,
IOptions<PlatformServiceOptions> options,
IEnvironmentSettingsStore envSettingsStore,
CancellationToken ct) =>
{
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
{
return failure!;
}
var (requestContext, failure) = await ResolveSetupContextAsync(
context, resolver, setupDetector, options, envSettingsStore, ct);
if (failure is not null) return failure;
try
{
var result = await service.GetSessionAsync(requestContext!, ct).ConfigureAwait(false);
var result = await service.GetSessionAsync(requestContext, ct).ConfigureAwait(false);
if (result is null)
{
return Results.NotFound(CreateProblem(
@@ -67,7 +109,7 @@ public static class SetupEndpoints
{
return Results.BadRequest(CreateProblem("Invalid Operation", ex.Message, StatusCodes.Status400BadRequest));
}
}).RequireAuthorization(PlatformPolicies.SetupRead)
}).AllowAnonymous()
.WithName("GetSetupSession")
.Produces<SetupSessionResponse>(StatusCodes.Status200OK)
.Produces<ProblemDetails>(StatusCodes.Status404NotFound);
@@ -77,18 +119,20 @@ public static class SetupEndpoints
HttpContext context,
PlatformRequestContextResolver resolver,
PlatformSetupService service,
SetupStateDetector setupDetector,
IOptions<PlatformServiceOptions> options,
IEnvironmentSettingsStore envSettingsStore,
[FromBody] CreateSetupSessionRequest? request,
CancellationToken ct) =>
{
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
{
return failure!;
}
var (requestContext, failure) = await ResolveSetupContextAsync(
context, resolver, setupDetector, options, envSettingsStore, ct);
if (failure is not null) return failure;
try
{
var result = await service.CreateSessionAsync(
requestContext!,
requestContext,
request ?? new CreateSetupSessionRequest(),
ct).ConfigureAwait(false);
return Results.Created($"/api/v1/setup/sessions", result);
@@ -97,7 +141,7 @@ public static class SetupEndpoints
{
return Results.BadRequest(CreateProblem("Invalid Operation", ex.Message, StatusCodes.Status400BadRequest));
}
}).RequireAuthorization(PlatformPolicies.SetupWrite)
}).AllowAnonymous()
.WithName("CreateSetupSession")
.Produces<SetupSessionResponse>(StatusCodes.Status201Created)
.Produces<ProblemDetails>(StatusCodes.Status400BadRequest);
@@ -107,23 +151,25 @@ public static class SetupEndpoints
HttpContext context,
PlatformRequestContextResolver resolver,
PlatformSetupService service,
SetupStateDetector setupDetector,
IOptions<PlatformServiceOptions> options,
IEnvironmentSettingsStore envSettingsStore,
CancellationToken ct) =>
{
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
{
return failure!;
}
var (requestContext, failure) = await ResolveSetupContextAsync(
context, resolver, setupDetector, options, envSettingsStore, ct);
if (failure is not null) return failure;
try
{
var result = await service.ResumeOrCreateSessionAsync(requestContext!, ct).ConfigureAwait(false);
var result = await service.ResumeOrCreateSessionAsync(requestContext, ct).ConfigureAwait(false);
return Results.Ok(result);
}
catch (InvalidOperationException ex)
{
return Results.BadRequest(CreateProblem("Invalid Operation", ex.Message, StatusCodes.Status400BadRequest));
}
}).RequireAuthorization(PlatformPolicies.SetupWrite)
}).AllowAnonymous()
.WithName("ResumeSetupSession")
.Produces<SetupSessionResponse>(StatusCodes.Status200OK)
.Produces<ProblemDetails>(StatusCodes.Status400BadRequest);
@@ -133,18 +179,20 @@ public static class SetupEndpoints
HttpContext context,
PlatformRequestContextResolver resolver,
PlatformSetupService service,
SetupStateDetector setupDetector,
IOptions<PlatformServiceOptions> options,
IEnvironmentSettingsStore envSettingsStore,
[FromBody] FinalizeSetupSessionRequest? request,
CancellationToken ct) =>
{
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
{
return failure!;
}
var (requestContext, failure) = await ResolveSetupContextAsync(
context, resolver, setupDetector, options, envSettingsStore, ct);
if (failure is not null) return failure;
try
{
var result = await service.FinalizeSessionAsync(
requestContext!,
requestContext,
request ?? new FinalizeSetupSessionRequest(),
ct).ConfigureAwait(false);
return Results.Ok(result);
@@ -153,7 +201,7 @@ public static class SetupEndpoints
{
return Results.BadRequest(CreateProblem("Invalid Operation", ex.Message, StatusCodes.Status400BadRequest));
}
}).RequireAuthorization(PlatformPolicies.SetupWrite)
}).AllowAnonymous()
.WithName("FinalizeSetupSession")
.Produces<FinalizeSetupSessionResponse>(StatusCodes.Status200OK)
.Produces<ProblemDetails>(StatusCodes.Status400BadRequest);
@@ -168,13 +216,15 @@ public static class SetupEndpoints
HttpContext context,
PlatformRequestContextResolver resolver,
PlatformSetupService service,
SetupStateDetector setupDetector,
IOptions<PlatformServiceOptions> options,
IEnvironmentSettingsStore envSettingsStore,
[FromBody] ExecuteSetupStepRequest request,
CancellationToken ct) =>
{
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
{
return failure!;
}
var (requestContext, failure) = await ResolveSetupContextAsync(
context, resolver, setupDetector, options, envSettingsStore, ct);
if (failure is not null) return failure;
if (request is null)
{
@@ -186,14 +236,14 @@ public static class SetupEndpoints
try
{
var result = await service.ExecuteStepAsync(requestContext!, request, ct).ConfigureAwait(false);
var result = await service.ExecuteStepAsync(requestContext, request, ct).ConfigureAwait(false);
return Results.Ok(result);
}
catch (InvalidOperationException ex)
{
return Results.BadRequest(CreateProblem("Invalid Operation", ex.Message, StatusCodes.Status400BadRequest));
}
}).RequireAuthorization(PlatformPolicies.SetupWrite)
}).AllowAnonymous()
.WithName("ExecuteSetupStep")
.Produces<ExecuteSetupStepResponse>(StatusCodes.Status200OK)
.Produces<ProblemDetails>(StatusCodes.Status400BadRequest);
@@ -203,13 +253,15 @@ public static class SetupEndpoints
HttpContext context,
PlatformRequestContextResolver resolver,
PlatformSetupService service,
SetupStateDetector setupDetector,
IOptions<PlatformServiceOptions> options,
IEnvironmentSettingsStore envSettingsStore,
[FromBody] SkipSetupStepRequest request,
CancellationToken ct) =>
{
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
{
return failure!;
}
var (requestContext, failure) = await ResolveSetupContextAsync(
context, resolver, setupDetector, options, envSettingsStore, ct);
if (failure is not null) return failure;
if (request is null)
{
@@ -221,14 +273,14 @@ public static class SetupEndpoints
try
{
var result = await service.SkipStepAsync(requestContext!, request, ct).ConfigureAwait(false);
var result = await service.SkipStepAsync(requestContext, request, ct).ConfigureAwait(false);
return Results.Ok(result);
}
catch (InvalidOperationException ex)
{
return Results.BadRequest(CreateProblem("Invalid Operation", ex.Message, StatusCodes.Status400BadRequest));
}
}).RequireAuthorization(PlatformPolicies.SetupWrite)
}).AllowAnonymous()
.WithName("SkipSetupStep")
.Produces<SetupSessionResponse>(StatusCodes.Status200OK)
.Produces<ProblemDetails>(StatusCodes.Status400BadRequest);
@@ -240,19 +292,12 @@ public static class SetupEndpoints
// GET /api/v1/setup/definitions/steps - Get all step definitions
definitions.MapGet("/steps", async Task<IResult> (
HttpContext context,
PlatformRequestContextResolver resolver,
PlatformSetupService service,
CancellationToken ct) =>
{
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
{
return failure!;
}
var result = await service.GetStepDefinitionsAsync(ct).ConfigureAwait(false);
return Results.Ok(result);
}).RequireAuthorization(PlatformPolicies.SetupRead)
}).AllowAnonymous()
.WithName("GetSetupStepDefinitions")
.Produces<SetupStepDefinitionsResponse>(StatusCodes.Status200OK);
}