diff options
| author | Leitwolf11 | 2026-10-23 06:01:51 +0300 |
|---|---|---|
| committer | Leitwolf11 | 2026-10-23 06:01:51 +0300 |
| commit | a8d768876e7cff3e3eeaa6e310149ed7f5894db0 (patch) | |
| tree | 3fd1babfea8ed8f842d9bdb6254de6a018eab088 /apps/api | |
| parent | cffc8d884ce3620b70c80a9ab98b8f1f03887402 (diff) | |
| download | progmaweb-a8d768876e7cff3e3eeaa6e310149ed7f5894db0.tar.gz | |
Document every OAuth member and namespace for the API reference
The documentation check of CI requires a comment on every API item and
a page for every namespace; 38 items of Progmasoft.OAuth had none. The
private handlers and helpers get summaries, MemoryOAuthDatabase its
field, and docs/api/namespaces describes Progmasoft.OAuth and its
Developers, Endpoints, Pages and Storage namespaces.
Verified: dotnet build in Release, dotnet docfx with warnings as
errors, and CheckDocumentation: all 353 API items are documented.
Diffstat (limited to 'apps/api')
| -rw-r--r-- | apps/api/OAuth/Developers/ApplicationPolicy.cs | 3 | ||||
| -rw-r--r-- | apps/api/OAuth/Developers/ApplicationRegistry.cs | 2 | ||||
| -rw-r--r-- | apps/api/OAuth/Developers/DeveloperEndpoints.cs | 13 | ||||
| -rw-r--r-- | apps/api/OAuth/Endpoints/AuthorizationEndpoints.cs | 7 | ||||
| -rw-r--r-- | apps/api/OAuth/OAuthRegistration.cs | 1 | ||||
| -rw-r--r-- | apps/api/OAuth/Pages/AllowedApplicationsEndpoints.cs | 2 | ||||
| -rw-r--r-- | apps/api/OAuth/Pages/SignInEndpoints.cs | 5 |
7 files changed, 33 insertions, 0 deletions
diff --git a/apps/api/OAuth/Developers/ApplicationPolicy.cs b/apps/api/OAuth/Developers/ApplicationPolicy.cs index 085d0b9..6c5cb1e 100644 --- a/apps/api/OAuth/Developers/ApplicationPolicy.cs +++ b/apps/api/OAuth/Developers/ApplicationPolicy.cs @@ -104,10 +104,12 @@ internal static class ApplicationPolicy .Distinct(StringComparer.Ordinal) .ToList(); + /// <summary>The rule for redirection addresses, as the refusal message states it.</summary> private static string AddressRule(bool isPublic) => isPublic ? "Use https://, http:// with a loopback address, or a private-use scheme such as com.example.app:/callback." : "Use https://, or http:// with a loopback address for development."; + /// <summary>Whether text is an absolute <c>https://</c> address without fragment or user information.</summary> private static bool IsHttpsAddress(string text) => text.Length <= MaximumUriLength && Uri.TryCreate(text, UriKind.Absolute, out Uri? uri) @@ -145,5 +147,6 @@ internal static class ApplicationPolicy return isPublic && uri.Scheme.Contains('.', StringComparison.Ordinal) && !refused.Contains(uri.Scheme); } + /// <summary>Whether an address has neither a fragment nor user information.</summary> private static bool IsClean(Uri uri) => uri.Fragment.Length == 0 && uri.UserInfo.Length == 0; } diff --git a/apps/api/OAuth/Developers/ApplicationRegistry.cs b/apps/api/OAuth/Developers/ApplicationRegistry.cs index 85eb322..f768b52 100644 --- a/apps/api/OAuth/Developers/ApplicationRegistry.cs +++ b/apps/api/OAuth/Developers/ApplicationRegistry.cs @@ -219,6 +219,7 @@ internal sealed class ApplicationRegistry( return true; } + /// <summary>Finds an application and its ownership row, when the account owns it.</summary> private async Task<(object application, ApplicationOwner owner)?> FindOwnedEntryAsync( Guid accountId, string clientId, @@ -293,6 +294,7 @@ internal sealed class ApplicationRegistry( descriptor.Requirements.Add(Requirements.Features.ProofKeyForCodeExchange); } + /// <summary>Reads an application into the view of the developer portal.</summary> private async Task<RegisteredApplication> DescribeAsync( object application, DateTimeOffset createdAt, diff --git a/apps/api/OAuth/Developers/DeveloperEndpoints.cs b/apps/api/OAuth/Developers/DeveloperEndpoints.cs index 895bd9f..460d17b 100644 --- a/apps/api/OAuth/Developers/DeveloperEndpoints.cs +++ b/apps/api/OAuth/Developers/DeveloperEndpoints.cs @@ -24,6 +24,7 @@ internal static class DeveloperEndpoints app.MapPost("/developers/{clientId}/delete", DeleteAsync).RequireRateLimiting("oauth"); } + /// <summary>Lists the applications of the signed-in account.</summary> private static async Task<IResult> ListAsync( HttpContext context, ApplicationRegistry registry, @@ -55,6 +56,7 @@ internal static class DeveloperEndpoints return HtmlPage.Render(context, "Your applications", body); } + /// <summary>Shows the empty registration form.</summary> private static async Task<IResult> NewAsync(HttpContext context) { if (await OAuthAccount.CurrentAsync(context) is null) @@ -67,6 +69,7 @@ internal static class DeveloperEndpoints isNew: true, new Dictionary<string, string>()); } + /// <summary>Registers an application from the submitted form.</summary> private static async Task<IResult> CreateAsync(HttpContext context, ApplicationRegistry registry) { if (await OAuthAccount.CurrentAsync(context) is not { } account) @@ -90,6 +93,7 @@ internal static class DeveloperEndpoints return RenderCredentials(context, "Application registered", result.Application, result.ClientSecret); } + /// <summary>Shows an application of the signed-in account with its settings.</summary> private static async Task<IResult> ShowAsync(HttpContext context, ApplicationRegistry registry, string clientId) { if (await OAuthAccount.CurrentAsync(context) is not { } account) @@ -103,6 +107,7 @@ internal static class DeveloperEndpoints : RenderDetails(context, application, new Dictionary<string, string>(), null); } + /// <summary>Saves the submitted settings of an application.</summary> private static async Task<IResult> UpdateAsync(HttpContext context, ApplicationRegistry registry, string clientId) { if (await OAuthAccount.CurrentAsync(context) is not { } account) @@ -131,6 +136,7 @@ internal static class DeveloperEndpoints StatusCodes.Status400BadRequest); } + /// <summary>Replaces the secret of an application and shows the new one once.</summary> private static async Task<IResult> RotateAsync(HttpContext context, ApplicationRegistry registry, string clientId) { if (await OAuthAccount.CurrentAsync(context) is not { } account) @@ -150,6 +156,7 @@ internal static class DeveloperEndpoints : RenderCredentials(context, "New client secret", application, secret); } + /// <summary>Deletes an application of the signed-in account.</summary> private static async Task<IResult> DeleteAsync(HttpContext context, ApplicationRegistry registry, string clientId) { if (await OAuthAccount.CurrentAsync(context) is not { } account) @@ -167,6 +174,7 @@ internal static class DeveloperEndpoints : NotFound(context); } + /// <summary>Reads the registration form into a submission.</summary> private static async Task<ApplicationInput> ReadAsync(HttpContext context) { IFormCollection form = await context.Request.ReadFormAsync(context.RequestAborted); @@ -180,6 +188,7 @@ internal static class DeveloperEndpoints form["allowClientCredentials"] == "on"); } + /// <summary>Shows the client identifier and, once, the client secret.</summary> private static IResult RenderCredentials( HttpContext context, string title, @@ -199,6 +208,7 @@ internal static class DeveloperEndpoints return HtmlPage.Render(context, title, body); } + /// <summary>Shows an application: its settings form, its secret and its deletion.</summary> private static IResult RenderDetails( HttpContext context, RegisteredApplication application, @@ -227,6 +237,7 @@ internal static class DeveloperEndpoints return HtmlPage.Render(context, application.DisplayName, body); } + /// <summary>Shows the settings form as a page.</summary> private static IResult RenderForm( HttpContext context, string title, @@ -239,6 +250,7 @@ internal static class DeveloperEndpoints FormContent(context, title, action, input, isNew, errors, null) + "<p><a href=\"/developers\">Back</a></p>", statusCode); + /// <summary>Builds the settings form with the messages of refused fields.</summary> private static string FormContent( HttpContext context, string title, @@ -281,6 +293,7 @@ internal static class DeveloperEndpoints return $"<h1>{HtmlPage.Encode(title)}</h1>" + HtmlPage.Form(context, action, fields); } + /// <summary>Answers for an application that is absent or belongs to another account.</summary> private static IResult NotFound(HttpContext context) => HtmlPage.Render(context, "Not found", "<h1>Not found</h1><p>No application of yours has this client identifier.</p>" + "<p><a href=\"/developers\">Back</a></p>", StatusCodes.Status404NotFound); diff --git a/apps/api/OAuth/Endpoints/AuthorizationEndpoints.cs b/apps/api/OAuth/Endpoints/AuthorizationEndpoints.cs index aa964da..54dd29a 100644 --- a/apps/api/OAuth/Endpoints/AuthorizationEndpoints.cs +++ b/apps/api/OAuth/Endpoints/AuthorizationEndpoints.cs @@ -38,6 +38,7 @@ internal static class AuthorizationEndpoints app.MapMethods("/connect/logout", ["GET", "POST"], (Delegate)LogoutAsync); } + /// <summary>Answers the authorization endpoint: sign-in, consent, and the authorization code.</summary> private static async Task<IResult> AuthorizeAsync( HttpContext context, IOpenIddictApplicationManager applications, @@ -122,6 +123,7 @@ internal static class AuthorizationEndpoints accounts); } + /// <summary>Shows the consent page for an authorization request.</summary> private static async Task<IResult> ConsentPageAsync( HttpContext context, OpenIddictRequest request, @@ -160,6 +162,7 @@ internal static class AuthorizationEndpoints return HtmlPage.Render(context, "Allow access", body); } + /// <summary>Answers the token endpoint for the authorization code, refresh token and client credentials grants.</summary> private static async Task<IResult> TokenAsync( HttpContext context, IOpenIddictApplicationManager applications, @@ -210,6 +213,7 @@ internal static class AuthorizationEndpoints return Forbid(Errors.UnsupportedGrantType, "This grant type is not supported."); } + /// <summary>Answers the user information endpoint with the claims the access token's scopes allow.</summary> private static IResult UserInfo(HttpContext context) { ClaimsPrincipal principal = context.User; @@ -232,6 +236,7 @@ internal static class AuthorizationEndpoints return Results.Ok(claims); } + /// <summary>Ends the sign-in and returns to the address the client registered, if any.</summary> private static async Task<IResult> LogoutAsync(HttpContext context) { await context.SignOutAsync(OAuthRegistration.CookieScheme); @@ -273,6 +278,7 @@ internal static class AuthorizationEndpoints return identity; } + /// <summary>Issues the tokens of an identity under an authorization.</summary> private static IResult Issue(ClaimsIdentity identity, string? authorizationId) { identity.SetAuthorizationId(authorizationId); @@ -280,6 +286,7 @@ internal static class AuthorizationEndpoints OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); } + /// <summary>Answers a protocol request with an OAuth error.</summary> private static IResult Forbid(string error, string description) => Results.Forbid( new AuthenticationProperties(new Dictionary<string, string?> { diff --git a/apps/api/OAuth/OAuthRegistration.cs b/apps/api/OAuth/OAuthRegistration.cs index d604d4c..4d2ac65 100644 --- a/apps/api/OAuth/OAuthRegistration.cs +++ b/apps/api/OAuth/OAuthRegistration.cs @@ -220,6 +220,7 @@ internal sealed class OAuthSchemaInitializer( /// <remarks>A shared in-memory SQLite database exists as long as one connection to it is open.</remarks> internal sealed class MemoryOAuthDatabase : IDisposable { + /// <summary>The connection whose lifetime is the lifetime of the database.</summary> private readonly SqliteConnection keeper; /// <summary>Opens the connection that holds the database.</summary> diff --git a/apps/api/OAuth/Pages/AllowedApplicationsEndpoints.cs b/apps/api/OAuth/Pages/AllowedApplicationsEndpoints.cs index f442b3c..19dec69 100644 --- a/apps/api/OAuth/Pages/AllowedApplicationsEndpoints.cs +++ b/apps/api/OAuth/Pages/AllowedApplicationsEndpoints.cs @@ -18,6 +18,7 @@ internal static class AllowedApplicationsEndpoints app.MapPost("/account/applications/{authorizationId}/revoke", RevokeAsync); } + /// <summary>Lists the applications the signed-in account has allowed.</summary> private static async Task<IResult> ListAsync( HttpContext context, IOpenIddictAuthorizationManager authorizations, @@ -63,6 +64,7 @@ internal static class AllowedApplicationsEndpoints return HtmlPage.Render(context, "Allowed applications", body); } + /// <summary>Withdraws an allowance and revokes the tokens issued under it.</summary> private static async Task<IResult> RevokeAsync( HttpContext context, IOpenIddictAuthorizationManager authorizations, diff --git a/apps/api/OAuth/Pages/SignInEndpoints.cs b/apps/api/OAuth/Pages/SignInEndpoints.cs index af739f1..3a6ea14 100644 --- a/apps/api/OAuth/Pages/SignInEndpoints.cs +++ b/apps/api/OAuth/Pages/SignInEndpoints.cs @@ -71,6 +71,7 @@ internal static class SignInEndpoints app.MapPost("/signout", (Delegate)SignOutAsync); } + /// <summary>Shows the home page of the OAuth host.</summary> private static async Task<IResult> HomeAsync(HttpContext context) { AccountRecord? account = await OAuthAccount.CurrentAsync(context); @@ -95,9 +96,11 @@ internal static class SignInEndpoints return HtmlPage.Render(context, "Progmasoft OAuth", body); } + /// <summary>Shows the sign-in form.</summary> private static IResult SignInPage(HttpContext context, IOptions<OAuthOptions> options, string? returnUrl) => RenderSignIn(context, options.Value, OAuthAccount.LocalReturnUrl(returnUrl), null, null); + /// <summary>Checks the submitted email address and password and signs the account in.</summary> private static async Task<IResult> SignInAsync( HttpContext context, AccountService accounts, @@ -127,6 +130,7 @@ internal static class SignInEndpoints return Results.Redirect(returnUrl); } + /// <summary>Ends the sign-in.</summary> private static async Task<IResult> SignOutAsync(HttpContext context) { if (!await OAuthAccount.IsGenuineFormAsync(context)) @@ -138,6 +142,7 @@ internal static class SignInEndpoints return Results.Redirect("/"); } + /// <summary>Shows the sign-in form with an optional message.</summary> private static IResult RenderSignIn( HttpContext context, OAuthOptions options, |