diff options
| author | Leitwolf11 | 2026-10-03 18:46:59 +0300 |
|---|---|---|
| committer | Leitwolf11 | 2026-10-03 18:46:59 +0300 |
| commit | b84a2216c26cc5392d0dc300b7a605ccb288d8c8 (patch) | |
| tree | 048ae623f49cc5f2f94234d747351f9e659f5267 /apps/api/Accounts/AccountEndpoints.cs | |
| parent | ec774db15976385a6d5ea6f48d3622fe507d3843 (diff) | |
| parent | cf444eb3bccf99c10f59ed5330b61c0397e0aeaa (diff) | |
| download | progmaweb-fix/api-without-google.tar.gz | |
Merge remote-tracking branch 'origin/main' into fix/api-without-googlefix/api-without-google
Diffstat (limited to 'apps/api/Accounts/AccountEndpoints.cs')
| -rw-r--r-- | apps/api/Accounts/AccountEndpoints.cs | 67 |
1 files changed, 67 insertions, 0 deletions
diff --git a/apps/api/Accounts/AccountEndpoints.cs b/apps/api/Accounts/AccountEndpoints.cs index 06f4997..c5be51c 100644 --- a/apps/api/Accounts/AccountEndpoints.cs +++ b/apps/api/Accounts/AccountEndpoints.cs @@ -7,10 +7,29 @@ using System.Security.Claims; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Google; +/// <summary>HTTP endpoints of the account service under <c>/api/v1/accounts</c>.</summary> +/// <remarks> +/// <list type="table"> +/// <item><term><c>POST /register</c></term><description>Creates a password account and signs it in.</description></item> +/// <item><term><c>POST /login</c></term><description>Signs in with email address and password.</description></item> +/// <item><term><c>POST /logout</c></term><description>Ends the current session.</description></item> +/// <item><term><c>GET /me</c></term><description>Returns the signed-in account.</description></item> +/// <item><term><c>GET /oauth/google/start</c></term><description>Begins sign-in with Google.</description></item> +/// <item><term><c>GET /oauth/google/complete</c></term><description>Finishes sign-in with Google.</description></item> +/// </list> +/// The endpoints that accept credentials share the <c>account-auth</c> rate limit. +/// </remarks> internal static class AccountEndpoints { + /// <summary> + /// Authentication scheme of the short-lived cookie that carries the Google identity between the provider callback + /// and <c>/oauth/google/complete</c>. + /// </summary> internal const string ExternalCookieScheme = "ProgmasoftExternal"; + /// <summary>Registers the account endpoints.</summary> + /// <param name="endpoints">The route builder of the application.</param> + /// <returns>The same route builder, for chaining.</returns> public static IEndpointRouteBuilder MapAccountEndpoints(this IEndpointRouteBuilder endpoints) { RouteGroupBuilder accounts = endpoints.MapGroup("/api/v1/accounts"); @@ -25,6 +44,15 @@ internal static class AccountEndpoints return endpoints; } + /// <summary>Begins sign-in with Google.</summary> + /// <param name="accountName"> + /// Account name to create when the Google identity is new; omitted when signing in to an existing account. + /// </param> + /// <param name="configuration">Application configuration with the Google client credentials.</param> + /// <returns> + /// A challenge that sends the browser to Google, a redirect back to registration when the name is invalid, or + /// 503 when Google sign-in is not configured. + /// </returns> private static IResult StartGoogleAsync(string? accountName, IConfiguration configuration) { if (string.IsNullOrWhiteSpace(configuration["Authentication:Google:ClientId"]) || @@ -54,6 +82,12 @@ internal static class AccountEndpoints return Results.Challenge(properties, [GoogleDefaults.AuthenticationScheme]); } + /// <summary>Finishes sign-in with Google after the provider callback.</summary> + /// <param name="accounts">Account service.</param> + /// <param name="sessions">Session service.</param> + /// <param name="context">The current request, holding the external cookie.</param> + /// <param name="cancellationToken">Cancels the operation.</param> + /// <returns>A redirect to the dashboard on success, or to the page that explains the failure.</returns> private static async Task<IResult> CompleteGoogleAsync( AccountService accounts, SessionService sessions, @@ -90,6 +124,13 @@ internal static class AccountEndpoints return Results.Redirect($"/{Uri.EscapeDataString(authentication.Account.AccountName)}/dashboard"); } + /// <summary>Creates a password account and opens a session for it.</summary> + /// <param name="request">The registration body.</param> + /// <param name="accounts">Account service.</param> + /// <param name="sessions">Session service.</param> + /// <param name="context">The current request; receives the session cookie.</param> + /// <param name="cancellationToken">Cancels the operation.</param> + /// <returns>201 with the account, or a validation problem that names each refused field.</returns> private static async Task<IResult> RegisterAsync( RegisterAccountRequest request, AccountService accounts, @@ -110,6 +151,15 @@ internal static class AccountEndpoints ToResponse(authentication.Account)); } + /// <summary>Signs in with email address and password.</summary> + /// <param name="request">The login body.</param> + /// <param name="accounts">Account service.</param> + /// <param name="sessions">Session service.</param> + /// <param name="context">The current request; receives the session cookie.</param> + /// <param name="cancellationToken">Cancels the operation.</param> + /// <returns> + /// 200 with the account, or 401 with one message that does not reveal whether the address or the password was wrong. + /// </returns> private static async Task<IResult> LoginAsync( LoginAccountRequest request, AccountService accounts, @@ -130,6 +180,11 @@ internal static class AccountEndpoints return Results.Ok(ToResponse(authentication.Account)); } + /// <summary>Ends the current session and deletes its cookie.</summary> + /// <param name="sessions">Session service.</param> + /// <param name="context">The current request.</param> + /// <param name="cancellationToken">Cancels the operation.</param> + /// <returns>204, also when no session existed.</returns> private static async Task<IResult> LogoutAsync( SessionService sessions, HttpContext context, @@ -147,6 +202,11 @@ internal static class AccountEndpoints return Results.NoContent(); } + /// <summary>Returns the account of the current session.</summary> + /// <param name="sessions">Session service.</param> + /// <param name="context">The current request.</param> + /// <param name="cancellationToken">Cancels the operation.</param> + /// <returns>200 with the account, or 401 when the request has no valid session.</returns> private static async Task<IResult> MeAsync( SessionService sessions, HttpContext context, @@ -157,6 +217,10 @@ internal static class AccountEndpoints return account is null ? Results.Unauthorized() : Results.Ok(ToResponse(account)); } + /// <summary>Writes the session cookie of a new session to the response.</summary> + /// <param name="context">The current request.</param> + /// <param name="sessions">Session service, which names the cookie.</param> + /// <param name="authentication">The session that was opened.</param> private static void WriteSessionCookie( HttpContext context, SessionService sessions, @@ -166,6 +230,9 @@ internal static class AccountEndpoints authentication.SessionToken, SessionService.CreateCookieOptions(authentication.ExpiresAt)); + /// <summary>Projects a stored account to its public view.</summary> + /// <param name="account">The stored account.</param> + /// <returns>The fields a client may see.</returns> private static AccountResponse ToResponse(AccountRecord account) => new(account.AccountName, account.Email, account.CreatedAt); } |