feat(altcha): add Svrnty.CQRS.Altcha.MinimalApi challenge endpoint
Single helper extension: MapSvrntyAltchaChallenge() exposes
GET /api/altcha/challenge (configurable prefix) that fetches a fresh
challenge from IAltchaChallengeProvider and projects it onto the
JSON shape the altcha widget v3 expects from its challengeurl —
{ algorithm, challenge, salt, signature, maxnumber } in lowercase.
AllowAnonymous on purpose: the whole point is gating mutations from
unauthenticated callers, so the challenge endpoint must be reachable
without credentials.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
4446288bb6
commit
891894d136
28
Svrnty.CQRS.Altcha.MinimalApi/AltchaChallengeDto.cs
Normal file
28
Svrnty.CQRS.Altcha.MinimalApi/AltchaChallengeDto.cs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Svrnty.CQRS.Altcha.MinimalApi;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// JSON projection of <see cref="Svrnty.CQRS.Altcha.Abstractions.AltchaChallenge"/>
|
||||||
|
/// in the exact shape the
|
||||||
|
/// <a href="https://altcha.org/docs/v2/widget-v3/">altcha widget v3</a>
|
||||||
|
/// expects from a <c>challengeurl</c> response. Property names are
|
||||||
|
/// lowercased and <c>challenge</c> (no underscore) to match the widget.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class AltchaChallengeDto
|
||||||
|
{
|
||||||
|
[JsonPropertyName("algorithm")]
|
||||||
|
public required string Algorithm { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("challenge")]
|
||||||
|
public required string Challenge { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("salt")]
|
||||||
|
public required string Salt { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("signature")]
|
||||||
|
public required string Signature { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("maxnumber")]
|
||||||
|
public required uint MaxNumber { get; init; }
|
||||||
|
}
|
||||||
@ -0,0 +1,50 @@
|
|||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Routing;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Svrnty.CQRS.Altcha.Abstractions;
|
||||||
|
|
||||||
|
namespace Svrnty.CQRS.Altcha.MinimalApi;
|
||||||
|
|
||||||
|
public static class EndpointRouteBuilderExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Maps <c>GET {routePrefix}</c> (default <c>/api/altcha/challenge</c>)
|
||||||
|
/// returning a fresh challenge in the JSON shape the
|
||||||
|
/// <a href="https://altcha.org/docs/v2/widget-v3/">altcha widget</a>
|
||||||
|
/// consumes via its <c>challengeurl</c> attribute.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Requires an <see cref="IAltchaChallengeProvider"/> to be registered
|
||||||
|
/// (typically by <c>AddSvrntyAltchaGrpcVerifier(...)</c>). The endpoint
|
||||||
|
/// allows anonymous access — the whole point is gating mutations from
|
||||||
|
/// unauthenticated callers, so the challenge endpoint must be reachable
|
||||||
|
/// without credentials.
|
||||||
|
/// </remarks>
|
||||||
|
public static IEndpointRouteBuilder MapSvrntyAltchaChallenge(
|
||||||
|
this IEndpointRouteBuilder endpoints,
|
||||||
|
string routePrefix = "/api/altcha/challenge")
|
||||||
|
{
|
||||||
|
endpoints.MapGet(routePrefix, async (
|
||||||
|
IAltchaChallengeProvider provider,
|
||||||
|
CancellationToken cancellationToken) =>
|
||||||
|
{
|
||||||
|
var challenge = await provider.CreateAsync(cancellationToken);
|
||||||
|
return Results.Ok(new AltchaChallengeDto
|
||||||
|
{
|
||||||
|
Algorithm = challenge.Algorithm,
|
||||||
|
Challenge = challenge.Challenge,
|
||||||
|
Salt = challenge.Salt,
|
||||||
|
Signature = challenge.Signature,
|
||||||
|
MaxNumber = challenge.MaxNumber
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.AllowAnonymous()
|
||||||
|
.WithName("Altcha_Challenge_Get")
|
||||||
|
.WithTags("Altcha")
|
||||||
|
.Produces<AltchaChallengeDto>(200)
|
||||||
|
.Produces(503);
|
||||||
|
|
||||||
|
return endpoints;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,37 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<IsAotCompatible>false</IsAotCompatible>
|
||||||
|
<LangVersion>14</LangVersion>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
|
||||||
|
<Company>Svrnty</Company>
|
||||||
|
<Authors>David Lebee, Mathias Beaulieu-Duncan</Authors>
|
||||||
|
<PackageIcon>icon.png</PackageIcon>
|
||||||
|
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||||
|
<RepositoryUrl>https://git.openharbor.io/svrnty/dotnet-cqrs</RepositoryUrl>
|
||||||
|
<RepositoryType>git</RepositoryType>
|
||||||
|
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||||
|
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||||
|
|
||||||
|
<DebugType>portable</DebugType>
|
||||||
|
<DebugSymbols>true</DebugSymbols>
|
||||||
|
<IncludeSymbols>true</IncludeSymbols>
|
||||||
|
<IncludeSource>true</IncludeSource>
|
||||||
|
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="..\icon.png" Pack="true" PackagePath="" CopyToOutputDirectory="Always" />
|
||||||
|
<None Include="..\README.md" Pack="true" PackagePath="" CopyToOutputDirectory="Always" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Svrnty.CQRS.Altcha.Abstractions\Svrnty.CQRS.Altcha.Abstractions.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
@ -49,6 +49,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Svrnty.CQRS.Altcha", "Svrnt
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Svrnty.CQRS.Altcha.Grpc", "Svrnty.CQRS.Altcha.Grpc\Svrnty.CQRS.Altcha.Grpc.csproj", "{628DE10C-FCDB-418B-8341-FA246BBCF70E}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Svrnty.CQRS.Altcha.Grpc", "Svrnty.CQRS.Altcha.Grpc\Svrnty.CQRS.Altcha.Grpc.csproj", "{628DE10C-FCDB-418B-8341-FA246BBCF70E}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Svrnty.CQRS.Altcha.MinimalApi", "Svrnty.CQRS.Altcha.MinimalApi\Svrnty.CQRS.Altcha.MinimalApi.csproj", "{26B24C13-FA06-4611-A371-2B640B8066F2}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@ -299,6 +301,18 @@ Global
|
|||||||
{628DE10C-FCDB-418B-8341-FA246BBCF70E}.Release|x64.Build.0 = Release|Any CPU
|
{628DE10C-FCDB-418B-8341-FA246BBCF70E}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{628DE10C-FCDB-418B-8341-FA246BBCF70E}.Release|x86.ActiveCfg = Release|Any CPU
|
{628DE10C-FCDB-418B-8341-FA246BBCF70E}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{628DE10C-FCDB-418B-8341-FA246BBCF70E}.Release|x86.Build.0 = Release|Any CPU
|
{628DE10C-FCDB-418B-8341-FA246BBCF70E}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{26B24C13-FA06-4611-A371-2B640B8066F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{26B24C13-FA06-4611-A371-2B640B8066F2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{26B24C13-FA06-4611-A371-2B640B8066F2}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{26B24C13-FA06-4611-A371-2B640B8066F2}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{26B24C13-FA06-4611-A371-2B640B8066F2}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{26B24C13-FA06-4611-A371-2B640B8066F2}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{26B24C13-FA06-4611-A371-2B640B8066F2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{26B24C13-FA06-4611-A371-2B640B8066F2}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{26B24C13-FA06-4611-A371-2B640B8066F2}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{26B24C13-FA06-4611-A371-2B640B8066F2}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{26B24C13-FA06-4611-A371-2B640B8066F2}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{26B24C13-FA06-4611-A371-2B640B8066F2}.Release|x86.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user