From a8510fb835dcc5e1142d700164ce5a4bd44e1a25 Mon Sep 17 00:00:00 2001 From: vman Date: Sun, 30 Oct 2022 02:28:12 -0400 Subject: Add project files. --- .../Endpoints/AccessTokenEndpoint.cs | 109 +++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 Libs/VNLib.Plugins.Essentials.Sessions.OAuth/Endpoints/AccessTokenEndpoint.cs (limited to 'Libs/VNLib.Plugins.Essentials.Sessions.OAuth/Endpoints/AccessTokenEndpoint.cs') diff --git a/Libs/VNLib.Plugins.Essentials.Sessions.OAuth/Endpoints/AccessTokenEndpoint.cs b/Libs/VNLib.Plugins.Essentials.Sessions.OAuth/Endpoints/AccessTokenEndpoint.cs new file mode 100644 index 0000000..271328a --- /dev/null +++ b/Libs/VNLib.Plugins.Essentials.Sessions.OAuth/Endpoints/AccessTokenEndpoint.cs @@ -0,0 +1,109 @@ +using System; +using System.Net; + +using VNLib.Utils.Memory; +using VNLib.Plugins.Essentials.Oauth; +using VNLib.Plugins.Essentials.Extensions; +using VNLib.Plugins.Extensions.Validation; +using VNLib.Plugins.Extensions.Loading; +using VNLib.Plugins.Extensions.Loading.Sql; + + +namespace VNLib.Plugins.Essentials.Sessions.OAuth.Endpoints +{ + /// + /// Grants authorization to OAuth2 clients to protected resources + /// with access tokens + /// + internal sealed class AccessTokenEndpoint : ResourceEndpointBase + { + + private readonly Lazy TokenStore; + private readonly Applications Applications; + + //override protection settings to allow most connections to authenticate + protected override ProtectionSettings EndpointProtectionSettings { get; } = new() + { + BrowsersOnly = false, + SessionsRequired = false, + VerifySessionCors = false + }; + + public AccessTokenEndpoint(string path, PluginBase pbase, Lazy tokenStore) + { + InitPathAndLog(path, pbase.Log); + TokenStore = tokenStore; + Applications = new(pbase.GetContextOptions(), pbase.GetPasswords()); + } + + protected override async ValueTask PostAsync(HttpEntity entity) + { + //Check for refresh token + if (entity.RequestArgs.IsArgumentSet("grant_type", "refresh_token")) + { + //process a refresh token + } + //Check for grant_type parameter from the request body + if (!entity.RequestArgs.IsArgumentSet("grant_type", "client_credentials")) + { + entity.CloseResponseError(HttpStatusCode.BadRequest, ErrorType.InvalidClient, "Invalid grant type"); + //Default to bad request + return VfReturnType.VirtualSkip; + } + //Get client id and secret (and make sure theyre not empty + if (entity.RequestArgs.TryGetNonEmptyValue("client_id", out string? clientId) && + entity.RequestArgs.TryGetNonEmptyValue("client_secret", out string? secret)) + { + if (!ValidatorExtensions.OnlyAlphaNumRegx.IsMatch(clientId)) + { + //respond with error message + entity.CloseResponseError(HttpStatusCode.UnprocessableEntity, ErrorType.InvalidRequest, "Invalid client_id"); + return VfReturnType.VirtualSkip; + } + if (!ValidatorExtensions.OnlyAlphaNumRegx.IsMatch(secret)) + { + //respond with error message + entity.CloseResponseError(HttpStatusCode.UnprocessableEntity, ErrorType.InvalidRequest, "Invalid client_secret"); + return VfReturnType.VirtualSkip; + } + //Convert the clientid and secret to lowercase + clientId = clientId.ToLower(); + secret = secret.ToLower(); + //Convert secret to private string + PrivateString secretPv = new(secret, false); + //Get the application from apps store + UserApplication? app = await Applications.VerifyAppAsync(clientId, secretPv); + if (app == null) + { + //App was not found or the credentials do not match + entity.CloseResponseError(HttpStatusCode.UnprocessableEntity, ErrorType.InvalidClient, "The client credentials are invalid"); + return VfReturnType.VirtualSkip; + } + //Create a new session + IOAuth2TokenResult? result = await TokenStore.Value.CreateAccessTokenAsync(entity.Entity, app, entity.EventCancellation); + if (result == null) + { + entity.CloseResponseError(HttpStatusCode.ServiceUnavailable, ErrorType.TemporarilyUnabavailable, "You have reached the maximum number of valid tokens for this application"); + return VfReturnType.VirtualSkip; + } + //Create the new response message + OauthTokenResponseMessage tokenMessage = new() + { + AccessToken = result.AccessToken, + + //set expired as seconds in int form + Expires = result.ExpiresSeconds, + RefreshToken = result.RefreshToken, + TokenType = result.TokenType + }; + //Respond with the token message + entity.CloseResponseJson(HttpStatusCode.OK, tokenMessage); + return VfReturnType.VirtualSkip; + + } + //respond with error message + entity.CloseResponseError(HttpStatusCode.UnprocessableEntity, ErrorType.InvalidClient, "The request was missing required arguments"); + return VfReturnType.VirtualSkip; + } + } +} \ No newline at end of file -- cgit