using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Hua.Todo.Application.CloudSync.Auth; using Hua.Todo.Application.CloudSync.Models; using Hua.Todo.Application.CloudSync.Services; namespace Hua.Todo.Application.CloudSync; /// /// 云同步服务端 API 路由注册扩展。 /// public static class CloudSyncEndpointExtensions { /// /// 映射云同步相关 API 端点。 /// /// Web 应用。 /// Web 应用。 public static WebApplication MapCloudSyncEndpoints(this WebApplication app) { var auth = app.MapGroup("/auth").WithTags("CloudSync - Auth"); auth.MapPost("/bootstrap", BootstrapAdminAsync).AllowAnonymous(); auth.MapPost("/login", LoginAsync).AllowAnonymous(); auth.MapPost("/step-up", StepUpAsync).AllowAnonymous(); var tasks = app.MapGroup("/tasks").WithTags("CloudSync - Tasks"); tasks.MapGet("/", GetTasksAsync).AllowAnonymous(); var sync = app.MapGroup("/sync").WithTags("CloudSync - Sync"); sync.MapPost("/", SyncAsync).AllowAnonymous(); var security = app.MapGroup("/security").WithTags("CloudSync - Security"); security.MapGet("/policy", GetPolicyAsync).AllowAnonymous(); security.MapPut("/policy", UpdatePolicyAsync).AllowAnonymous(); return app; } private static async Task BootstrapAdminAsync( BootstrapAdminRequest request, CloudAuthService authService, CancellationToken cancellationToken) { if (request == null || string.IsNullOrWhiteSpace(request.UserName) || string.IsNullOrWhiteSpace(request.Password)) { return CloudApiErrors.BadRequest("UserName and Password are required."); } var ok = await authService.BootstrapAdminAsync(request.UserName, request.Password, cancellationToken); if (!ok) { return CloudApiErrors.Forbidden("Bootstrap is not allowed (already initialized or invalid input)."); } return Results.Ok(); } private static async Task LoginAsync( LoginRequest request, CloudAuthService authService, CancellationToken cancellationToken) { if (request == null || string.IsNullOrWhiteSpace(request.UserName) || string.IsNullOrWhiteSpace(request.Password)) { return CloudApiErrors.BadRequest("UserName and Password are required."); } var response = await authService.LoginAsync(request.UserName, request.Password, TimeSpan.FromDays(7), cancellationToken); if (response == null) { return CloudApiErrors.Unauthorized("Invalid credentials."); } return Results.Json(response); } private static async Task StepUpAsync( StepUpRequest request, CloudAuthService authService, HttpContext httpContext, CancellationToken cancellationToken) { var sessionId = httpContext.User.GetSessionId(); if (sessionId == null) { return CloudApiErrors.Unauthorized(); } if (request == null || string.IsNullOrWhiteSpace(request.Password)) { return CloudApiErrors.BadRequest("Password is required."); } var expiresAt = await authService.StepUpAsync(sessionId.Value, request.Password, TimeSpan.FromMinutes(5), cancellationToken); if (!expiresAt.HasValue) { return CloudApiErrors.Unauthorized("Invalid credentials or session expired."); } return Results.Json(new StepUpResponse { StepUpExpiresAtUtc = expiresAt.Value }); } private static async Task GetTasksAsync( CloudTaskSyncService taskService, HttpContext httpContext, CancellationToken cancellationToken) { var userId = httpContext.User.GetUserId(); if (userId == null) { return CloudApiErrors.Unauthorized(); } if (!httpContext.User.HasPermission(CloudPermissions.TasksRead)) { return CloudApiErrors.Forbidden(); } var tasks = await taskService.GetTasksAsync(userId.Value, cancellationToken); return Results.Json(tasks); } private static async Task SyncAsync( SyncRequest request, CloudTaskSyncService taskService, HttpContext httpContext, CancellationToken cancellationToken) { var userId = httpContext.User.GetUserId(); if (userId == null) { return CloudApiErrors.Unauthorized(); } if (!httpContext.User.HasPermission(CloudPermissions.SyncWrite)) { return CloudApiErrors.Forbidden(); } if (!httpContext.User.HasStepUp()) { return CloudApiErrors.SecondFactorRequired(); } var response = await taskService.SyncAsync(userId.Value, request, cancellationToken); return Results.Json(response); } private static async Task GetPolicyAsync( SecurityPolicyService policyService, HttpContext httpContext, CancellationToken cancellationToken) { var userId = httpContext.User.GetUserId(); if (userId == null) { return CloudApiErrors.Unauthorized(); } if (!httpContext.User.HasPermission(CloudPermissions.PolicyRead)) { return CloudApiErrors.Forbidden(); } var policy = await policyService.GetPolicyAsync(userId.Value, cancellationToken); return Results.Json(policy); } private static async Task UpdatePolicyAsync( UpdateSecurityPolicyRequest request, SecurityPolicyService policyService, HttpContext httpContext, CancellationToken cancellationToken) { var userId = httpContext.User.GetUserId(); if (userId == null) { return CloudApiErrors.Unauthorized(); } if (!httpContext.User.HasPermission(CloudPermissions.PolicyWrite)) { return CloudApiErrors.Forbidden(); } if (!httpContext.User.HasStepUp()) { return CloudApiErrors.SecondFactorRequired(); } var policy = await policyService.UpdatePolicyAsync(userId.Value, request.AllowPersist, request.AllowSync, cancellationToken); return Results.Json(policy); } }