Read-only mirrors. Development, issues and pull requests are on GitHub.

summaryrefslogtreecommitdiffhomepage
path: root/apps/api
diff options
context:
space:
mode:
authorLeitwolf112026-10-23 06:01:51 +0300
committerLeitwolf112026-10-23 06:01:51 +0300
commita8d768876e7cff3e3eeaa6e310149ed7f5894db0 (patch)
tree3fd1babfea8ed8f842d9bdb6254de6a018eab088 /apps/api
parentcffc8d884ce3620b70c80a9ab98b8f1f03887402 (diff)
downloadprogmaweb-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.cs3
-rw-r--r--apps/api/OAuth/Developers/ApplicationRegistry.cs2
-rw-r--r--apps/api/OAuth/Developers/DeveloperEndpoints.cs13
-rw-r--r--apps/api/OAuth/Endpoints/AuthorizationEndpoints.cs7
-rw-r--r--apps/api/OAuth/OAuthRegistration.cs1
-rw-r--r--apps/api/OAuth/Pages/AllowedApplicationsEndpoints.cs2
-rw-r--r--apps/api/OAuth/Pages/SignInEndpoints.cs5
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,