aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLibravatar vnugent <public@vaughnnugent.com>2024-01-06 13:09:27 -0500
committerLibravatar vnugent <public@vaughnnugent.com>2024-01-06 13:09:27 -0500
commitf4c2c9e148374f462592c19e8ffd4db14672805d (patch)
tree9a22010b73e766a6381e9f5edce377c2e2f8811a
parent592669252fdcfd9ce3f443abbfa2cb9b3387a5d2 (diff)
initial migration to .net 8.0
-rw-r--r--LICENSE2
-rw-r--r--plugins/VNLib.Plugins.Essentials.Accounts.Admin/src/VNLib.Plugins.Essentials.Accounts.Admin.csproj2
-rw-r--r--plugins/VNLib.Plugins.Essentials.Accounts.Registration/src/VNLib.Plugins.Essentials.Accounts.Registration.csproj6
-rw-r--r--plugins/VNLib.Plugins.Essentials.Accounts/src/Endpoints/MFAEndpoint.cs2
-rw-r--r--plugins/VNLib.Plugins.Essentials.Accounts/src/VNLib.Plugins.Essentials.Accounts.csproj6
-rw-r--r--plugins/VNLib.Plugins.Essentials.Content.Routing/src/VNLib.Plugins.Essentials.Content.Routing.csproj4
-rw-r--r--plugins/VNLib.Plugins.Essentials.SocialOauth/README.md45
-rw-r--r--plugins/VNLib.Plugins.Essentials.SocialOauth/build.readme.md0
-rw-r--r--plugins/VNLib.Plugins.Essentials.SocialOauth/src/ClientAccessTokenState.cs48
-rw-r--r--plugins/VNLib.Plugins.Essentials.SocialOauth/src/ClientClaimManager.cs126
-rw-r--r--plugins/VNLib.Plugins.Essentials.SocialOauth/src/ClientRequestState.cs81
-rw-r--r--plugins/VNLib.Plugins.Essentials.SocialOauth/src/Endpoints/DiscordOauth.cs148
-rw-r--r--plugins/VNLib.Plugins.Essentials.SocialOauth/src/Endpoints/GitHubOauth.cs214
-rw-r--r--plugins/VNLib.Plugins.Essentials.SocialOauth/src/GetTokenRequest.cs34
-rw-r--r--plugins/VNLib.Plugins.Essentials.SocialOauth/src/IOAuthAccessState.cs57
-rw-r--r--plugins/VNLib.Plugins.Essentials.SocialOauth/src/LoginClaim.cs73
-rw-r--r--plugins/VNLib.Plugins.Essentials.SocialOauth/src/LoginUriBuilder.cs127
-rw-r--r--plugins/VNLib.Plugins.Essentials.SocialOauth/src/OAuthSiteAdapter.cs72
-rw-r--r--plugins/VNLib.Plugins.Essentials.SocialOauth/src/OauthClientConfig.cs148
-rw-r--r--plugins/VNLib.Plugins.Essentials.SocialOauth/src/SocialEntryPoint.cs67
-rw-r--r--plugins/VNLib.Plugins.Essentials.SocialOauth/src/SocialOauthBase.cs572
-rw-r--r--plugins/VNLib.Plugins.Essentials.SocialOauth/src/UserLoginData.cs34
-rw-r--r--plugins/VNLib.Plugins.Essentials.SocialOauth/src/VNLib.Plugins.Essentials.SocialOauth.csproj66
-rw-r--r--plugins/VNLib.Plugins.Essentials.SocialOauth/src/Validators/AccountDataValidator.cs83
-rw-r--r--plugins/VNLib.Plugins.Essentials.SocialOauth/src/Validators/LoginMessageValidation.cs65
25 files changed, 11 insertions, 2071 deletions
diff --git a/LICENSE b/LICENSE
index 982df24..1a24a8e 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2023 Vaughn Nugent
+Copyright (c) 2024 Vaughn Nugent
Contact information
Name: Vaughn Nugent
diff --git a/plugins/VNLib.Plugins.Essentials.Accounts.Admin/src/VNLib.Plugins.Essentials.Accounts.Admin.csproj b/plugins/VNLib.Plugins.Essentials.Accounts.Admin/src/VNLib.Plugins.Essentials.Accounts.Admin.csproj
index 16d7fe1..321b605 100644
--- a/plugins/VNLib.Plugins.Essentials.Accounts.Admin/src/VNLib.Plugins.Essentials.Accounts.Admin.csproj
+++ b/plugins/VNLib.Plugins.Essentials.Accounts.Admin/src/VNLib.Plugins.Essentials.Accounts.Admin.csproj
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
- <TargetFramework>net6.0</TargetFramework>
+ <TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AnalysisLevel>latest-all</AnalysisLevel>
diff --git a/plugins/VNLib.Plugins.Essentials.Accounts.Registration/src/VNLib.Plugins.Essentials.Accounts.Registration.csproj b/plugins/VNLib.Plugins.Essentials.Accounts.Registration/src/VNLib.Plugins.Essentials.Accounts.Registration.csproj
index 6107441..1f003a2 100644
--- a/plugins/VNLib.Plugins.Essentials.Accounts.Registration/src/VNLib.Plugins.Essentials.Accounts.Registration.csproj
+++ b/plugins/VNLib.Plugins.Essentials.Accounts.Registration/src/VNLib.Plugins.Essentials.Accounts.Registration.csproj
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
- <TargetFramework>net6.0</TargetFramework>
+ <TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>VNLib.Plugins.Essentials.Accounts.Registration</RootNamespace>
@@ -22,7 +22,7 @@
<Description>
*An essentials plugin that provides endpoints for registering, local user accounts via transactional emails
</Description>
- <Copyright>Copyright © 2023 Vaughn Nugent</Copyright>
+ <Copyright>Copyright © 2024 Vaughn Nugent</Copyright>
<PackageProjectUrl>https://www.vaughnnugent.com/resources/software/modules/Plugins.Essentials</PackageProjectUrl>
<RepositoryUrl>https://github.com/VnUgE/Plugins.Essentials/tree/master/plugins/VNLib.Plugins.Essentials.Accounts.Registration</RepositoryUrl>
</PropertyGroup>
@@ -44,7 +44,7 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="FluentValidation" Version="11.8.1" />
+ <PackageReference Include="FluentValidation" Version="11.9.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\..\core\lib\Plugins.Essentials\src\VNLib.Plugins.Essentials.csproj" />
diff --git a/plugins/VNLib.Plugins.Essentials.Accounts/src/Endpoints/MFAEndpoint.cs b/plugins/VNLib.Plugins.Essentials.Accounts/src/Endpoints/MFAEndpoint.cs
index a156ccc..2e102a3 100644
--- a/plugins/VNLib.Plugins.Essentials.Accounts/src/Endpoints/MFAEndpoint.cs
+++ b/plugins/VNLib.Plugins.Essentials.Accounts/src/Endpoints/MFAEndpoint.cs
@@ -294,7 +294,7 @@ namespace VNLib.Plugins.Essentials.Accounts.Endpoints
{
//dispose the output buffer
outputBuffer.Dispose();
- MemoryUtil.InitializeBlock(secretBuffer.AsSpan());
+ MemoryUtil.InitializeBlock(secretBuffer);
}
}
diff --git a/plugins/VNLib.Plugins.Essentials.Accounts/src/VNLib.Plugins.Essentials.Accounts.csproj b/plugins/VNLib.Plugins.Essentials.Accounts/src/VNLib.Plugins.Essentials.Accounts.csproj
index 97e99ee..f8f7083 100644
--- a/plugins/VNLib.Plugins.Essentials.Accounts/src/VNLib.Plugins.Essentials.Accounts.csproj
+++ b/plugins/VNLib.Plugins.Essentials.Accounts/src/VNLib.Plugins.Essentials.Accounts.csproj
@@ -2,7 +2,7 @@
<PropertyGroup>
<Nullable>enable</Nullable>
- <TargetFramework>net6.0</TargetFramework>
+ <TargetFramework>net8.0</TargetFramework>
<RootNamespace>VNLib.Plugins.Essentials.Accounts</RootNamespace>
<AssemblyName>Essentials.Accounts</AssemblyName>
<AnalysisLevel>latest-all</AnalysisLevel>
@@ -17,7 +17,7 @@
<Authors>Vaughn Nugent</Authors>
<Company>Vaughn Nugent</Company>
<Product>Essentials user accounts an web authentication plugin</Product>
- <Copyright>Copyright © 2023 Vaughn Nugent</Copyright>
+ <Copyright>Copyright © 2024 Vaughn Nugent</Copyright>
<PackageProjectUrl>https://www.vaughnnugent.com/resources/software/modules/Plugins.Essentials</PackageProjectUrl>
<RepositoryUrl>https://github.com/VnUgE/Plugins.Essentials/tree/master/plugins/VNLib.Plugins.Essentials.Accounts</RepositoryUrl>
<Description>An Essentials plugin that provides user accounts, authentication, security, account security with MFA, and public/key authentication</Description>
@@ -49,7 +49,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
- <PackageReference Include="FluentValidation" Version="11.8.1" />
+ <PackageReference Include="FluentValidation" Version="11.9.0" />
</ItemGroup>
<ItemGroup>
diff --git a/plugins/VNLib.Plugins.Essentials.Content.Routing/src/VNLib.Plugins.Essentials.Content.Routing.csproj b/plugins/VNLib.Plugins.Essentials.Content.Routing/src/VNLib.Plugins.Essentials.Content.Routing.csproj
index 71dab0d..df4c4cd 100644
--- a/plugins/VNLib.Plugins.Essentials.Content.Routing/src/VNLib.Plugins.Essentials.Content.Routing.csproj
+++ b/plugins/VNLib.Plugins.Essentials.Content.Routing/src/VNLib.Plugins.Essentials.Content.Routing.csproj
@@ -2,7 +2,7 @@
<PropertyGroup>
<Nullable>enable</Nullable>
- <TargetFramework>net6.0</TargetFramework>
+ <TargetFramework>net8.0</TargetFramework>
<RootNamespace>VNLib.Plugins.Essentials.Content.Routing</RootNamespace>
<AssemblyName>PageRouter</AssemblyName>
<NeutralLanguage>en-US</NeutralLanguage>
@@ -19,7 +19,7 @@
<Description>
An essetials framework IPageRouter dynamic page routing implementation
</Description>
- <Copyright>Copyright © 2023 Vaughn Nugent</Copyright>
+ <Copyright>Copyright © 2024 Vaughn Nugent</Copyright>
<PackageProjectUrl>https://www.vaughnnugent.com/resources/software/modules/Plugins.Essentials</PackageProjectUrl>
<RepositoryUrl>https://github.com/VnUgE/Plugins.Essentials/tree/master/plugins/VNLib.Plugins.Essentials.Content.Routing</RepositoryUrl>
</PropertyGroup>
diff --git a/plugins/VNLib.Plugins.Essentials.SocialOauth/README.md b/plugins/VNLib.Plugins.Essentials.SocialOauth/README.md
deleted file mode 100644
index 7a54c20..0000000
--- a/plugins/VNLib.Plugins.Essentials.SocialOauth/README.md
+++ /dev/null
@@ -1,45 +0,0 @@
-# VNLib.Plugins.Essentials.SocialOauth
-
-A basic external OAuth2 authentication plugin.
-
-## Plugin Mode
-
-This library exports an IPlugin type that may be loaded directly by a host application, or
-imported to provide base classes for creating OAuth2 authentication endpoints.
-
-By default, exports 2 endpoints for Github and Discord authentication. Configuration
-variables for either endpoint may be omitted or included to export endpoints.
-
-## Library Mode
-
-Exports SocialOAuthBase to provide a base class for creating OAuth2 authentication
-endpoints, that is compatible with the VNLib web client library authentication flow
-
-
-## Authentication Flow
-
-The authentication flow works similar to the local account mechanism with an extra step that helps
-guard against replay, and MITM attacks. When an request claim is made (request to login) from client
-side code (via put request), a browser id is request (for login flow) along with the clients encryption
-public key (same key as Essentials.Accounts requires). The public key is used to encrypted a derived
-redirect url, which includes a "secret" state token (OAuth2 standard state) that only the private-key
-holder should be able to recover. When decrypted, should be used to redirect the client's browser to
-the remote authentication server. Assuming the request is granted, the browser is redirected to the
-originating endpoint, and the nonce is used to recover the initial claim and the flow continues. The
-request should also include the required OAuth2 'code' parameter used to exchange for an access token.
-If the access token is granted, a nonce is generated, passed to the browser via a redirect query parameter
-which the browser code will use in a POST request to the endpoint to continue the flow. The nonce is
-used to recover the access token and original claim data (public key, browser id, etc), which is used
-to recover a user account, or optionally create a new account. Once complete, the user account is used
-to upgrade the session and grant authorization to the client. The public key (and browser id) is used
-from the initial claim to authorize the session, which should guard against MITM, replay, and forgery
-attacks. However this only works if we assume the clients private key has not been stolen, which is a
-much larger issue and should be addressed separately.
-
-## Diagram
-
-PUT -> { public_key, browser_id } -> server -> { result: "base64 encrypted redirect url"} ->
- OAuth2Server -> redirect -> "?code=some_code&state=decrypted_state_token"
-
-GET -> "?code=some_code&state=decrypted_state_token" -> server -> "?result=authorized&nonce=some_nonce"
-POST -> { nonce:"some_nonce" } -> server -> [authorization complete message] \ No newline at end of file
diff --git a/plugins/VNLib.Plugins.Essentials.SocialOauth/build.readme.md b/plugins/VNLib.Plugins.Essentials.SocialOauth/build.readme.md
deleted file mode 100644
index e69de29..0000000
--- a/plugins/VNLib.Plugins.Essentials.SocialOauth/build.readme.md
+++ /dev/null
diff --git a/plugins/VNLib.Plugins.Essentials.SocialOauth/src/ClientAccessTokenState.cs b/plugins/VNLib.Plugins.Essentials.SocialOauth/src/ClientAccessTokenState.cs
deleted file mode 100644
index 6a86cef..0000000
--- a/plugins/VNLib.Plugins.Essentials.SocialOauth/src/ClientAccessTokenState.cs
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
-* Copyright (c) 2023 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Essentials.SocialOauth
-* File: OAuthAccessState.cs
-*
-* OAuthAccessState.cs is part of VNLib.Plugins.Essentials.SocialOauth which
-* is part of the larger VNLib collection of libraries and utilities.
-*
-* VNLib.Plugins.Essentials.SocialOauth is free software: you can redistribute it and/or modify
-* it under the terms of the GNU Affero General Public License as
-* published by the Free Software Foundation, either version 3 of the
-* License, or (at your option) any later version.
-*
-* VNLib.Plugins.Essentials.SocialOauth is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-* GNU Affero General Public License for more details.
-*
-* You should have received a copy of the GNU Affero General Public License
-* along with this program. If not, see https://www.gnu.org/licenses/.
-*/
-
-using System;
-using System.Text.Json.Serialization;
-
-namespace VNLib.Plugins.Essentials.SocialOauth
-{
- public class OAuthAccessState : IOAuthAccessState
- {
- ///<inheritdoc/>
- [JsonPropertyName("access_token")]
- public string? Token { get; set; }
- ///<inheritdoc/>
- [JsonPropertyName("scope")]
- public string? Scope { get; set; }
- ///<inheritdoc/>
- [JsonPropertyName("token_type")]
- public string? Type { get; set; }
- ///<inheritdoc/>
- [JsonPropertyName("refresh_token")]
- public string? RefreshToken { get; set; }
- ///<inheritdoc/>
- [JsonPropertyName("id_token")]
- public string? IdToken { get; set; }
- }
-} \ No newline at end of file
diff --git a/plugins/VNLib.Plugins.Essentials.SocialOauth/src/ClientClaimManager.cs b/plugins/VNLib.Plugins.Essentials.SocialOauth/src/ClientClaimManager.cs
deleted file mode 100644
index 1e5a82e..0000000
--- a/plugins/VNLib.Plugins.Essentials.SocialOauth/src/ClientClaimManager.cs
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
-* Copyright (c) 2023 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Essentials.SocialOauth
-* File: ClientClaimManager.cs
-*
-* ClientClaimManager.cs is part of VNLib.Plugins.Essentials.SocialOauth which is part of the larger
-* VNLib collection of libraries and utilities.
-*
-* VNLib.Plugins.Essentials.SocialOauth is free software: you can redistribute it and/or modify
-* it under the terms of the GNU Affero General Public License as
-* published by the Free Software Foundation, either version 3 of the
-* License, or (at your option) any later version.
-*
-* VNLib.Plugins.Essentials.SocialOauth is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-* GNU Affero General Public License for more details.
-*
-* You should have received a copy of the GNU Affero General Public License
-* along with this program. If not, see https://www.gnu.org/licenses/.
-*/
-
-using System;
-using System.Diagnostics.CodeAnalysis;
-
-using VNLib.Hashing;
-using VNLib.Hashing.IdentityUtility;
-using VNLib.Utils;
-using VNLib.Utils.Memory;
-using VNLib.Utils.Extensions;
-using VNLib.Plugins.Essentials.Accounts;
-using VNLib.Plugins.Essentials.Extensions;
-
-namespace VNLib.Plugins.Essentials.SocialOauth
-{
- internal sealed record class ClientClaimManager(ICookieController Cookies)
- {
- const string SESSION_SIG_KEY_NAME = "soa.sig";
- const int SIGNING_KEY_SIZE = 32;
-
- public bool VerifyAndGetClaim(HttpEntity entity, [NotNullWhen(true)] out LoginClaim? claim)
- {
- claim = null;
-
- string? cookieValue = Cookies.GetCookie(entity);
-
- //Try to get the cookie
- if (cookieValue == null)
- {
- return false;
- }
-
- //Recover the signing key from the user's session
- string sigKey = entity.Session[SESSION_SIG_KEY_NAME];
- Span<byte> key = stackalloc byte[SIGNING_KEY_SIZE + 16];
-
- ERRNO keySize = VnEncoding.Base64UrlDecode(sigKey, key);
-
- if (keySize < 1)
- {
- return false;
- }
-
- try
- {
- //Try to parse the jwt
- using JsonWebToken jwt = JsonWebToken.Parse(cookieValue);
-
- //Verify the jwt
- if (!jwt.Verify(key[..(int)keySize], HashAlg.SHA256))
- {
- return false;
- }
-
- //Recover the clam from the jwt
- claim = jwt.GetPayload<LoginClaim>();
-
- //Verify the expiration time
- return claim.ExpirationSeconds > entity.RequestedTimeUtc.ToUnixTimeSeconds();
- }
- catch (FormatException)
- {
- //JWT was corrupted and could not be parsed
- return false;
- }
- finally
- {
- MemoryUtil.InitializeBlock(key);
- }
- }
-
- public void ClearClaimData(HttpEntity entity)
- {
- //Remove the upgrade cookie
- Cookies.ExpireCookie(entity, false);
-
- //Clear the signing key from the session
- entity.Session[SESSION_SIG_KEY_NAME] = null!;
- }
-
- public void SignAndSetCookie(HttpEntity entity, LoginClaim claim)
- {
- //Setup Jwt
- using JsonWebToken jwt = new();
-
- //Write claim body, we dont need a header
- jwt.WritePayload(claim, Statics.SR_OPTIONS);
-
- //Generate signing key
- byte[] sigKey = RandomHash.GetRandomBytes(SIGNING_KEY_SIZE);
-
- //Sign the jwt
- jwt.Sign(sigKey, HashAlg.SHA256);
-
- Cookies.SetCookie(entity, jwt.Compile());
-
- //Encode and store the signing key in the clien't session
- entity.Session[SESSION_SIG_KEY_NAME] = VnEncoding.ToBase64UrlSafeString(sigKey, false);
-
- //Clear the signing key
- MemoryUtil.InitializeBlock(sigKey.AsSpan());
- }
- }
-}
diff --git a/plugins/VNLib.Plugins.Essentials.SocialOauth/src/ClientRequestState.cs b/plugins/VNLib.Plugins.Essentials.SocialOauth/src/ClientRequestState.cs
deleted file mode 100644
index ba369c2..0000000
--- a/plugins/VNLib.Plugins.Essentials.SocialOauth/src/ClientRequestState.cs
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
-* Copyright (c) 2022 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Essentials.SocialOauth
-* File: ClientRequestState.cs
-*
-* ClientRequestState.cs is part of VNLib.Plugins.Essentials.SocialOauth which is part of the larger
-* VNLib collection of libraries and utilities.
-*
-* VNLib.Plugins.Essentials.SocialOauth is free software: you can redistribute it and/or modify
-* it under the terms of the GNU Affero General Public License as
-* published by the Free Software Foundation, either version 3 of the
-* License, or (at your option) any later version.
-*
-* VNLib.Plugins.Essentials.SocialOauth is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-* GNU Affero General Public License for more details.
-*
-* You should have received a copy of the GNU Affero General Public License
-* along with this program. If not, see https://www.gnu.org/licenses/.
-*/
-
-using System;
-using System.Security.Cryptography;
-
-using VNLib.Hashing;
-using VNLib.Utils;
-using VNLib.Utils.Memory;
-using VNLib.Utils.Memory.Caching;
-
-namespace VNLib.Plugins.Essentials.SocialOauth
-{
- internal sealed class ClientRequestState : ICacheable
- {
- private readonly ReadOnlyMemory<byte> _rawKey;
-
- /// <summary>
- /// The raw nonce state bytes
- /// </summary>
- public ReadOnlyMemory<byte> State { get; private set; }
-
- public ClientRequestState(ReadOnlySpan<char> keyChar, int nonceBytes)
- {
- //Get browser id
- _rawKey = Convert.FromHexString(keyChar);
- RecomputeState(nonceBytes);
- }
-
- /// <summary>
- /// Recomputes a nonce state and signature for the current
- /// connection
- /// </summary>
- /// <param name="nonceBytes">The size of the nonce (in bytes) to generate</param>
- public void RecomputeState(int nonceBytes)
- {
- //Get random nonce buffer
- State = RandomHash.GetRandomBytes(nonceBytes);
- }
- /// <summary>
- /// Computes the signature of the supplied data based on the original
- /// client state for this connection
- /// </summary>
- /// <param name="data"></param>
- /// <returns></returns>
- public ERRNO ComputeSignatureForClient(ReadOnlySpan<byte> data, Span<byte> output)
- {
- return HMACSHA512.TryHashData(_rawKey.Span, data, output, out int count) ? count : ERRNO.E_FAIL;
- }
-
- public DateTime Expires { get; set; }
- bool IEquatable<ICacheable>.Equals(ICacheable other) => ReferenceEquals(this, other);
- void ICacheable.Evicted()
- {
- //Zero secrets on eviction
- MemoryUtil.UnsafeZeroMemory(State);
- MemoryUtil.UnsafeZeroMemory(_rawKey);
- }
- }
-} \ No newline at end of file
diff --git a/plugins/VNLib.Plugins.Essentials.SocialOauth/src/Endpoints/DiscordOauth.cs b/plugins/VNLib.Plugins.Essentials.SocialOauth/src/Endpoints/DiscordOauth.cs
deleted file mode 100644
index f64d1c4..0000000
--- a/plugins/VNLib.Plugins.Essentials.SocialOauth/src/Endpoints/DiscordOauth.cs
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
-* Copyright (c) 2023 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Essentials.SocialOauth
-* File: DiscordOauth.cs
-*
-* DiscordOauth.cs is part of VNLib.Plugins.Essentials.SocialOauth which is part of the larger
-* VNLib collection of libraries and utilities.
-*
-* VNLib.Plugins.Essentials.SocialOauth is free software: you can redistribute it and/or modify
-* it under the terms of the GNU Affero General Public License as
-* published by the Free Software Foundation, either version 3 of the
-* License, or (at your option) any later version.
-*
-* VNLib.Plugins.Essentials.SocialOauth is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-* GNU Affero General Public License for more details.
-*
-* You should have received a copy of the GNU Affero General Public License
-* along with this program. If not, see https://www.gnu.org/licenses/.
-*/
-
-using System;
-using System.Text.Json;
-using System.Threading;
-using System.Threading.Tasks;
-using System.Text.Json.Serialization;
-
-using RestSharp;
-
-using VNLib.Hashing;
-using VNLib.Utils.Logging;
-using VNLib.Plugins.Essentials.Accounts;
-using VNLib.Plugins.Extensions.Loading;
-using VNLib.Net.Rest.Client.Construction;
-
-
-namespace VNLib.Plugins.Essentials.SocialOauth.Endpoints
-{
- [ConfigurationName("discord")]
- internal sealed class DiscordOauth : SocialOauthBase
- {
- public DiscordOauth(PluginBase plugin, IConfigScope config) : base(plugin, config)
- {
- //Define profile endpoint
- SiteAdapter.DefineSingleEndpoint()
- .WithEndpoint<DiscordProfileRequest>()
- .WithMethod(Method.Get)
- .WithUrl(Config.UserDataUrl)
- .WithHeader("Authorization", r => $"{r.AccessToken.Type} {r.AccessToken.Token}");
- }
-
- /*
- * Creates a user-id from the users discord username, that is repeatable
- * and matches the Auth0 social user-id format
- */
- private static string GetUserIdFromPlatform(string userName) => $"discord|{userName}";
-
-
- ///<inheritdoc/>
- protected override async Task<AccountData?> GetAccountDataAsync(IOAuthAccessState accessToken, CancellationToken cancellationToken)
- {
- //Get the user's profile
- UserProfile? profile = await GetUserProfileAssync(accessToken, cancellationToken);
-
- if (profile == null)
- {
- return null;
- }
-
- //Make sure the user's account is verified
- if (!profile.Verified)
- {
- return null;
- }
-
- return new()
- {
- EmailAddress = profile.EmailAddress,
- First = profile.Username,
- };
- }
-
- ///<inheritdoc/>
- protected override async Task<UserLoginData?> GetLoginDataAsync(IOAuthAccessState accessToken, CancellationToken cancellationToken)
- {
- //Get the user's profile
- UserProfile? profile = await GetUserProfileAssync(accessToken, cancellationToken);
-
- if(profile == null)
- {
- return null;
- }
-
- return new()
- {
- //Get unique user-id from the discord profile and sha1 hex hash to store in db
- UserId = GetUserIdFromPlatform(profile.UserID)
- };
- }
-
- private async Task<UserProfile?> GetUserProfileAssync(IOAuthAccessState accessToken, CancellationToken cancellationToken)
- {
- //Get the user's email address's
- DiscordProfileRequest req = new(accessToken);
- RestResponse response = await SiteAdapter.ExecuteAsync(req, cancellationToken);
-
- //Check response
- if (!response.IsSuccessful || response.Content == null)
- {
- Log.Debug("Discord user request responded with code {code}:{data}", response.StatusCode, response.Content);
- return null;
- }
-
- UserProfile? discordProfile = JsonSerializer.Deserialize<UserProfile>(response.RawBytes);
-
- if (string.IsNullOrWhiteSpace(discordProfile?.UserID))
- {
- Log.Debug("Discord user request responded with invalid response data {code}:{data}", response.StatusCode, response.Content);
- return null;
- }
-
- return discordProfile;
- }
-
- private sealed record class DiscordProfileRequest(IOAuthAccessState AccessToken)
- { }
-
- /*
- * Matches the profile endpoint (@me) json object
- */
- private sealed class UserProfile
- {
- [JsonPropertyName("username")]
- public string? Username { get; set; }
- [JsonPropertyName("id")]
- public string? UserID { get; set; }
- [JsonPropertyName("url")]
- public string? ProfileUrl { get; set; }
- [JsonPropertyName("verified")]
- public bool Verified { get; set; }
- [JsonPropertyName("email")]
- public string? EmailAddress { get; set; }
- }
- }
-} \ No newline at end of file
diff --git a/plugins/VNLib.Plugins.Essentials.SocialOauth/src/Endpoints/GitHubOauth.cs b/plugins/VNLib.Plugins.Essentials.SocialOauth/src/Endpoints/GitHubOauth.cs
deleted file mode 100644
index e8abf5a..0000000
--- a/plugins/VNLib.Plugins.Essentials.SocialOauth/src/Endpoints/GitHubOauth.cs
+++ /dev/null
@@ -1,214 +0,0 @@
-/*
-* Copyright (c) 2023 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Essentials.SocialOauth
-* File: GitHubOauth.cs
-*
-* GitHubOauth.cs is part of VNLib.Plugins.Essentials.SocialOauth which is part of the larger
-* VNLib collection of libraries and utilities.
-*
-* VNLib.Plugins.Essentials.SocialOauth is free software: you can redistribute it and/or modify
-* it under the terms of the GNU Affero General Public License as
-* published by the Free Software Foundation, either version 3 of the
-* License, or (at your option) any later version.
-*
-* VNLib.Plugins.Essentials.SocialOauth is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-* GNU Affero General Public License for more details.
-*
-* You should have received a copy of the GNU Affero General Public License
-* along with this program. If not, see https://www.gnu.org/licenses/.
-*/
-
-using System;
-using System.Threading;
-using System.Text.Json;
-using System.Threading.Tasks;
-using System.Collections.Generic;
-using System.Text.Json.Serialization;
-
-using RestSharp;
-
-using VNLib.Hashing;
-using VNLib.Utils.Logging;
-using VNLib.Plugins.Essentials.Accounts;
-using VNLib.Plugins.Extensions.Loading;
-using VNLib.Net.Rest.Client.Construction;
-
-
-namespace VNLib.Plugins.Essentials.SocialOauth.Endpoints
-{
- [ConfigurationName("github")]
- internal sealed partial class GitHubOauth : SocialOauthBase
- {
- private const string GITHUB_V3_ACCEPT = "application/vnd.github.v3+json";
-
- private readonly string UserEmailUrl;
-
-
- public GitHubOauth(PluginBase plugin, IConfigScope config) : base(plugin, config)
- {
- UserEmailUrl = config["user_email_url"].GetString() ?? throw new KeyNotFoundException("Missing required key 'user_email_url' for github configuration");
-
- //Define profile endpoint, gets users required profile information
- SiteAdapter.DefineSingleEndpoint()
- .WithEndpoint<GetProfileRequest>()
- .WithMethod(Method.Get)
- .WithUrl(Config.UserDataUrl)
- .WithHeader("Accept", GITHUB_V3_ACCEPT)
- .WithHeader("Authorization", at => $"{at.AccessToken.Type} {at.AccessToken.Token}");
-
- //Define email endpoint, gets users email address
- SiteAdapter.DefineSingleEndpoint()
- .WithEndpoint<GetEmailRequest>()
- .WithMethod(Method.Get)
- .WithUrl(UserEmailUrl)
- .WithHeader("Accept", GITHUB_V3_ACCEPT)
- .WithHeader("Authorization", at => $"{at.AccessToken.Type} {at.AccessToken.Token}");
- }
-
- /*
- * Creates a repeatable, and source specific user id for
- * GitHub users. This format is identical to the algorithim used
- * in the Auth0 Github connection, so it is compatible with Auth0
- */
- private static string GetUserIdFromPlatform(int userId) => $"github|{userId}";
-
-
- protected override async Task<UserLoginData?> GetLoginDataAsync(IOAuthAccessState accessToken, CancellationToken cancellationToken)
- {
- GetProfileRequest req = new(accessToken);
-
- //Exec the get for the profile
- RestResponse profResponse = await SiteAdapter.ExecuteAsync(req, cancellationToken);
-
- if (!profResponse.IsSuccessful || profResponse.RawBytes == null)
- {
- Log.Debug("Github login data attempt responded with status code {code}", profResponse.StatusCode);
- return null;
- }
-
- GithubProfile profile = JsonSerializer.Deserialize<GithubProfile>(profResponse.RawBytes)!;
-
- if (profile.ID < 100)
- {
- Log.Debug("Github login data attempt responded with empty or invalid response body", profResponse.StatusCode);
- return null;
- }
-
- //Return login data
- return new()
- {
- //User-id is just the SHA 1
- UserId = GetUserIdFromPlatform(profile.ID)
- };
- }
-
- protected override async Task<AccountData?> GetAccountDataAsync(IOAuthAccessState accessToken, CancellationToken cancellationToken = default)
- {
- AccountData? accountData = null;
-
- //Get the user's email address's
- GetEmailRequest request = new(accessToken);
-
- //get user's emails
- RestResponse getEmailResponse = await SiteAdapter.ExecuteAsync(request, cancellationToken);
-
- //Check status
- if (getEmailResponse.IsSuccessful && getEmailResponse.RawBytes != null)
- {
- //Filter emails addresses
- foreach (EmailContainer email in JsonSerializer.Deserialize<EmailContainer[]>(getEmailResponse.RawBytes)!)
- {
- //Capture the first primary email address and make sure its verified
- if (email.Primary && email.Verified)
- {
- accountData = new()
- {
- //store email on current profile
- EmailAddress = email.Email
- };
- goto Continue;
- }
- }
- //No primary email found
- return null;
- }
- else
- {
- Log.Debug("Github account data request failed but GH responded with status code {code}", getEmailResponse.StatusCode);
- return null;
- }
- Continue:
-
- //We need to get the user's profile again
- GetProfileRequest prof = new(accessToken);
-
- //Exec request against site adapter
- RestResponse profResponse = await SiteAdapter.ExecuteAsync(prof, cancellationToken);
-
- if (!profResponse.IsSuccessful || profResponse.RawBytes == null)
- {
- Log.Debug("Github account data request failed but GH responded with status code {code}", profResponse.StatusCode);
- return null;
- }
-
- //Deserialize the profile
- GithubProfile profile = JsonSerializer.Deserialize<GithubProfile>(profResponse.RawBytes)!;
-
- //Get the user's name from gh profile
- string[] names = profile.FullName!.Split(" ", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
-
- //setup the user's profile data
- accountData.First = names.Length > 0 ? names[0] : string.Empty;
- accountData.Last = names.Length > 1 ? names[1] : string.Empty;
- return accountData;
- }
-
- //Requests to get required data from github
-
- private sealed record class GetProfileRequest(IOAuthAccessState AccessToken)
- { }
-
- private sealed record class GetEmailRequest(IOAuthAccessState AccessToken)
- { }
-
- /*
- * Matches the json result from the
- */
- private sealed class GithubProfile
- {
- [JsonPropertyName("login")]
- public string? Username { get; set; }
- [JsonPropertyName("id")]
- public int ID { get; set; }
- [JsonPropertyName("node_id")]
- public string? NodeID { get; set; }
- [JsonPropertyName("avatar_url")]
- public string? AvatarUrl { get; set; }
- [JsonPropertyName("url")]
- public string? ProfileUrl { get; set; }
- [JsonPropertyName("type")]
- public string? Type { get; set; }
- [JsonPropertyName("name")]
- public string? FullName { get; set; }
- [JsonPropertyName("company")]
- public string? Company { get; set; }
- }
- /*
- * Matches the required data from the github email endpoint
- */
- private sealed class EmailContainer
- {
- [JsonPropertyName("email")]
- public string? Email { get; set; }
- [JsonPropertyName("primary")]
- public bool Primary { get; set; }
- [JsonPropertyName("verified")]
- public bool Verified { get; set; }
- }
-
- }
-} \ No newline at end of file
diff --git a/plugins/VNLib.Plugins.Essentials.SocialOauth/src/GetTokenRequest.cs b/plugins/VNLib.Plugins.Essentials.SocialOauth/src/GetTokenRequest.cs
deleted file mode 100644
index 6e7635e..0000000
--- a/plugins/VNLib.Plugins.Essentials.SocialOauth/src/GetTokenRequest.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
-* Copyright (c) 2023 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Essentials.SocialOauth
-* File: GetTokenRequest.cs
-*
-* GetTokenRequest.cs is part of VNLib.Plugins.Essentials.SocialOauth which is part of the larger
-* VNLib collection of libraries and utilities.
-*
-* VNLib.Plugins.Essentials.SocialOauth is free software: you can redistribute it and/or modify
-* it under the terms of the GNU Affero General Public License as
-* published by the Free Software Foundation, either version 3 of the
-* License, or (at your option) any later version.
-*
-* VNLib.Plugins.Essentials.SocialOauth is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-* GNU Affero General Public License for more details.
-*
-* You should have received a copy of the GNU Affero General Public License
-* along with this program. If not, see https://www.gnu.org/licenses/.
-*/
-
-namespace VNLib.Plugins.Essentials.SocialOauth
-{
- /// <summary>
- /// A request message to get an OAuth2 token from a code
- /// </summary>
- /// <param name="Code">The clients authentication code</param>
- /// <param name="RedirectUrl">The redirect url for current site</param>
- public sealed record class GetTokenRequest(string Code, string RedirectUrl)
- { }
-}
diff --git a/plugins/VNLib.Plugins.Essentials.SocialOauth/src/IOAuthAccessState.cs b/plugins/VNLib.Plugins.Essentials.SocialOauth/src/IOAuthAccessState.cs
deleted file mode 100644
index 888cc02..0000000
--- a/plugins/VNLib.Plugins.Essentials.SocialOauth/src/IOAuthAccessState.cs
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
-* Copyright (c) 2022 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Essentials.SocialOauth
-* File: IOAuthAccessState.cs
-*
-* IOAuthAccessState.cs is part of VNLib.Plugins.Essentials.SocialOauth which is part of the larger
-* VNLib collection of libraries and utilities.
-*
-* VNLib.Plugins.Essentials.SocialOauth is free software: you can redistribute it and/or modify
-* it under the terms of the GNU Affero General Public License as
-* published by the Free Software Foundation, either version 3 of the
-* License, or (at your option) any later version.
-*
-* VNLib.Plugins.Essentials.SocialOauth is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-* GNU Affero General Public License for more details.
-*
-* You should have received a copy of the GNU Affero General Public License
-* along with this program. If not, see https://www.gnu.org/licenses/.
-*/
-
-#nullable enable
-
-namespace VNLib.Plugins.Essentials.SocialOauth
-{
- /// <summary>
- /// An object that represents an OAuth2 access token in its
- /// standard form.
- /// </summary>
- public interface IOAuthAccessState
- {
- /// <summary>
- /// The OAuth2 access token
- /// </summary>
- public string? Token { get; set; }
- /// <summary>
- /// Token grant scope
- /// </summary>
- string? Scope { get; set; }
- /// <summary>
- /// The OAuth2 token type, usually 'Bearer'
- /// </summary>
- string? Type { get; set; }
- /// <summary>
- /// Optional refresh token
- /// </summary>
- string? RefreshToken { get; set; }
-
- /// <summary>
- /// Optional ID OIDC token
- /// </summary>
- string? IdToken { get; set; }
- }
-} \ No newline at end of file
diff --git a/plugins/VNLib.Plugins.Essentials.SocialOauth/src/LoginClaim.cs b/plugins/VNLib.Plugins.Essentials.SocialOauth/src/LoginClaim.cs
deleted file mode 100644
index fa425cc..0000000
--- a/plugins/VNLib.Plugins.Essentials.SocialOauth/src/LoginClaim.cs
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
-* Copyright (c) 2023 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Essentials.SocialOauth
-* File: LoginClaim.cs
-*
-* LoginClaim.cs is part of VNLib.Plugins.Essentials.SocialOauth which is part of the larger
-* VNLib collection of libraries and utilities.
-*
-* VNLib.Plugins.Essentials.SocialOauth is free software: you can redistribute it and/or modify
-* it under the terms of the GNU Affero General Public License as
-* published by the Free Software Foundation, either version 3 of the
-* License, or (at your option) any later version.
-*
-* VNLib.Plugins.Essentials.SocialOauth is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-* GNU Affero General Public License for more details.
-*
-* You should have received a copy of the GNU Affero General Public License
-* along with this program. If not, see https://www.gnu.org/licenses/.
-*/
-
-using System.Text.Json.Serialization;
-
-using VNLib.Hashing;
-using VNLib.Utils;
-using VNLib.Utils.Memory;
-using VNLib.Plugins.Essentials.Accounts;
-
-namespace VNLib.Plugins.Essentials.SocialOauth
-{
- internal sealed class LoginClaim : IClientSecInfo
- {
- [JsonPropertyName("exp")]
- public long ExpirationSeconds { get; set; }
-
- [JsonPropertyName("iat")]
- public long IssuedAtTime { get; set; }
-
- [JsonPropertyName("nonce")]
- public string? Nonce { get; set; }
-
- [JsonPropertyName("locallanguage")]
- public string? LocalLanguage { get; set; }
-
- [JsonPropertyName("pubkey")]
- public string? PublicKey { get; set; }
-
- [JsonPropertyName("clientid")]
- public string? ClientId { get; set; }
-
-
- public void ComputeNonce(int nonceSize)
- {
- //Alloc nonce buffer
- using UnsafeMemoryHandle<byte> buffer = MemoryUtil.UnsafeAlloc(nonceSize);
- try
- {
- //fill the buffer with random data
- RandomHash.GetRandomBytes(buffer.Span);
-
- //Base32-Encode nonce and save it
- Nonce = VnEncoding.ToBase64UrlSafeString(buffer.Span, false);
- }
- finally
- {
- MemoryUtil.InitializeBlock(buffer.Span);
- }
- }
- }
-}
diff --git a/plugins/VNLib.Plugins.Essentials.SocialOauth/src/LoginUriBuilder.cs b/plugins/VNLib.Plugins.Essentials.SocialOauth/src/LoginUriBuilder.cs
deleted file mode 100644
index 95334c6..0000000
--- a/plugins/VNLib.Plugins.Essentials.SocialOauth/src/LoginUriBuilder.cs
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
-* Copyright (c) 2023 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Essentials.SocialOauth
-* File: LoginUriBuilder.cs
-*
-* LoginUriBuilder.cs is part of VNLib.Plugins.Essentials.SocialOauth which is part of the larger
-* VNLib collection of libraries and utilities.
-*
-* VNLib.Plugins.Essentials.SocialOauth is free software: you can redistribute it and/or modify
-* it under the terms of the GNU Affero General Public License as
-* published by the Free Software Foundation, either version 3 of the
-* License, or (at your option) any later version.
-*
-* VNLib.Plugins.Essentials.SocialOauth is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-* GNU Affero General Public License for more details.
-*
-* You should have received a copy of the GNU Affero General Public License
-* along with this program. If not, see https://www.gnu.org/licenses/.
-*/
-
-using System;
-using System.Text;
-using System.Runtime.InteropServices;
-
-using VNLib.Utils;
-using VNLib.Utils.Memory;
-using VNLib.Utils.Extensions;
-using VNLib.Plugins.Essentials.Accounts;
-
-namespace VNLib.Plugins.Essentials.SocialOauth
-{
- /*
- * Construct the client's redirect url based on their login claim, which contains
- * a public key which can be used to encrypt the url so that only the client
- * private-key holder can decrypt the url and redirect themselves to the
- * target OAuth website.
- *
- * The result is an encrypted nonce that should guard against replay attacks and MITM
- */
-
- internal sealed record class LoginUriBuilder(OauthClientConfig Config)
- {
- private string? redirectUrl;
- private string? nonce;
- private Encoding _encoding = Encoding.UTF8;
-
- public LoginUriBuilder WithUrl(ReadOnlySpan<char> scheme, ReadOnlySpan<char> authority, ReadOnlySpan<char> path)
- {
- //Alloc stack buffer for url
- Span<char> buffer = stackalloc char[1024];
-
- //buffer writer for easier syntax
- ForwardOnlyWriter<char> writer = new(buffer);
- //first build the redirect url to re-encode it
- writer.Append(scheme);
- writer.Append("://");
- //Create redirect url (current page, default action is to authorize the client)
- writer.Append(authority);
- writer.Append(path);
- //url encode the redirect path and save it for later
- redirectUrl = Uri.EscapeDataString(writer.ToString());
-
- return this;
- }
-
- public LoginUriBuilder WithEncoding(Encoding encoding)
- {
- _encoding = encoding;
- return this;
- }
-
- public LoginUriBuilder WithNonce(string base32Nonce)
- {
- nonce = base32Nonce;
- return this;
- }
-
- public string Encrypt(HttpEntity client, IClientSecInfo secInfo)
- {
- //Alloc buffer and split it into binary and char buffers
- using UnsafeMemoryHandle<byte> buffer = MemoryUtil.UnsafeAllocNearestPage(8000);
-
- Span<byte> binBuffer = buffer.Span[2048..];
- Span<char> charBuffer = MemoryMarshal.Cast<byte, char>(buffer.Span[..2048]);
-
-
- /*
- * Build the character uri so we can encode it to binary,
- * encrypt it and return it to the client
- */
-
- ForwardOnlyWriter<char> writer = new(charBuffer);
-
- //Append the config redirect path
- writer.Append(Config.AccessCodeUrl.OriginalString);
- //begin query arguments
- writer.Append("&client_id=");
- writer.Append(Config.ClientID.Value);
- //add the redirect url
- writer.Append("&redirect_uri=");
- writer.Append(redirectUrl);
- //Append the state parameter
- writer.Append("&state=");
- writer.Append(nonce);
-
- //Collect the written character data
- ReadOnlySpan<char> url = writer.AsSpan();
-
- //Separate bin buffers for encryption and encoding
- Span<byte> encryptionBuffer = binBuffer[1024..];
- Span<byte> encodingBuffer = binBuffer[..1024];
-
- //Encode the url to binary
- int byteCount = _encoding.GetBytes(url, encodingBuffer);
-
- //Encrypt the binary data
- ERRNO count = client.TryEncryptClientData(secInfo, encodingBuffer[..byteCount], encryptionBuffer);
-
- //base64 encode the encrypted
- return Convert.ToBase64String(encryptionBuffer[0..(int)count]);
- }
- }
-}
diff --git a/plugins/VNLib.Plugins.Essentials.SocialOauth/src/OAuthSiteAdapter.cs b/plugins/VNLib.Plugins.Essentials.SocialOauth/src/OAuthSiteAdapter.cs
deleted file mode 100644
index ce4f08c..0000000
--- a/plugins/VNLib.Plugins.Essentials.SocialOauth/src/OAuthSiteAdapter.cs
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
-* Copyright (c) 2023 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Essentials.SocialOauth
-* File: OAuthSiteAdapter.cs
-*
-* OAuthSiteAdapter.cs is part of VNLib.Plugins.Essentials.SocialOauth which is part of the larger
-* VNLib collection of libraries and utilities.
-*
-* VNLib.Plugins.Essentials.SocialOauth is free software: you can redistribute it and/or modify
-* it under the terms of the GNU Affero General Public License as
-* published by the Free Software Foundation, either version 3 of the
-* License, or (at your option) any later version.
-*
-* VNLib.Plugins.Essentials.SocialOauth is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-* GNU Affero General Public License for more details.
-*
-* You should have received a copy of the GNU Affero General Public License
-* along with this program. If not, see https://www.gnu.org/licenses/.
-*/
-
-using System.Net;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-
-using RestSharp;
-
-using VNLib.Net.Rest.Client;
-using VNLib.Net.Rest.Client.Construction;
-
-namespace VNLib.Plugins.Essentials.SocialOauth
-{
- /// <summary>
- /// Provides strucutred http messaging to an OAuth2 site.
- /// </summary>
- public sealed class OAuthSiteAdapter : RestSiteAdapterBase
- {
- protected override RestClientPool Pool { get; }
-
- /// <summary>
- /// Initializes a new instance of the <see cref="OAuthSiteAdapter"/> class.
- /// </summary>
- public OAuthSiteAdapter()
- {
- RestClientOptions poolOptions = new()
- {
- MaxTimeout = 5000,
- AutomaticDecompression = DecompressionMethods.All,
- Encoding = Encoding.UTF8,
- //disable redirects, api should not redirect
- FollowRedirects = false,
- };
-
- //Configure rest client to comunications to main discord api
- Pool = new(10, poolOptions);
- }
-
- ///<inheritdoc/>
- public override void OnResponse(RestResponse response)
- { }
-
- ///<inheritdoc/>
- public override Task WaitAsync(CancellationToken cancellation = default)
- {
- return Task.CompletedTask;
- }
- }
-}
diff --git a/plugins/VNLib.Plugins.Essentials.SocialOauth/src/OauthClientConfig.cs b/plugins/VNLib.Plugins.Essentials.SocialOauth/src/OauthClientConfig.cs
deleted file mode 100644
index 4e14063..0000000
--- a/plugins/VNLib.Plugins.Essentials.SocialOauth/src/OauthClientConfig.cs
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
-* Copyright (c) 2023 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Essentials.SocialOauth
-* File: OauthClientConfig.cs
-*
-* OauthClientConfig.cs is part of VNLib.Plugins.Essentials.SocialOauth which is part of the larger
-* VNLib collection of libraries and utilities.
-*
-* VNLib.Plugins.Essentials.SocialOauth is free software: you can redistribute it and/or modify
-* it under the terms of the GNU Affero General Public License as
-* published by the Free Software Foundation, either version 3 of the
-* License, or (at your option) any later version.
-*
-* VNLib.Plugins.Essentials.SocialOauth is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-* GNU Affero General Public License for more details.
-*
-* You should have received a copy of the GNU Affero General Public License
-* along with this program. If not, see https://www.gnu.org/licenses/.
-*/
-
-using System;
-using System.Net;
-using System.Collections.Generic;
-
-using VNLib.Utils.Logging;
-using VNLib.Utils.Extensions;
-using VNLib.Plugins.Extensions.Loading;
-
-namespace VNLib.Plugins.Essentials.SocialOauth
-{
-
- /// <summary>
- /// Contains the standard configuration data for an OAuth2 endpoint
- /// defined by plugin configuration
- /// </summary>
- public sealed class OauthClientConfig
- {
-
- public OauthClientConfig(PluginBase plugin, IConfigScope config)
- {
- EndpointPath = config["path"].GetString() ?? throw new KeyNotFoundException($"Missing required key 'path' in config {config.ScopeName}");
-
- //Set discord account origin
- AccountOrigin = config["account_origin"].GetString() ?? throw new KeyNotFoundException($"Missing required key 'account_origin' in config {config.ScopeName}");
-
- //Get the auth and token urls
- string authUrl = config["authorization_url"].GetString() ?? throw new KeyNotFoundException($"Missing required key 'authorization_url' in config {config.ScopeName}");
- string tokenUrl = config["token_url"].GetString() ?? throw new KeyNotFoundException($"Missing required key 'token_url' in config {config.ScopeName}");
- string userUrl = config["user_data_url"].GetString() ?? throw new KeyNotFoundException($"Missing required key 'user_data_url' in config {config.ScopeName}");
- //Create the uris
- AccessCodeUrl = new(authUrl);
- AccessTokenUrl = new(tokenUrl);
- UserDataUrl = new(userUrl);
-
- AllowForLocalAccounts = config["allow_for_local"].GetBoolean();
- AllowRegistration = config["allow_registration"].GetBoolean();
- NonceByteSize = config["nonce_size"].GetUInt32();
- RandomPasswordSize = config["password_size"].GetInt32();
- InitClaimValidFor = config["claim_valid_for_sec"].GetTimeSpan(TimeParseType.Seconds);
-
- //Setup async lazy loaders for secrets
- ClientID = plugin.GetSecretAsync($"{config.ScopeName}_client_id")
- .ToLazy(static r => r.Result.ToString());
-
- ClientSecret = plugin.GetSecretAsync($"{config.ScopeName}_client_secret")
- .ToLazy(static r => r.Result.ToString());
-
- //Log the token server ip address for the user to verify
- if (plugin.Log.IsEnabled(LogLevel.Verbose))
- {
- _ = plugin.ObserveWork(async () =>
- {
- IPAddress[] addresses = await Dns.GetHostAddressesAsync(AccessTokenUrl.DnsSafeHost);
- plugin.Log.Verbose("Token server {host} resolves to {ip}", AccessTokenUrl.DnsSafeHost, addresses);
- });
- }
- }
-
- /// <summary>
- /// The client ID for the OAuth2 service
- /// </summary>
- public IAsyncLazy<string> ClientID { get; }
-
- /// <summary>
- /// The client secret for the OAuth2 service
- /// </summary>
- public IAsyncLazy<string> ClientSecret { get; }
-
-
- /// <summary>
- /// The user-account origin value. Specifies that the user account
- /// was created outside of the local account system
- /// </summary>
- public string AccountOrigin { get; }
-
- /// <summary>
- /// The URL to redirect the user to the OAuth2 service
- /// to begin the authentication process
- /// </summary>
- public Uri AccessCodeUrl { get; }
-
- /// <summary>
- /// The remote endoint to exchange codes for access tokens
- /// </summary>
- public Uri AccessTokenUrl { get; }
-
- /// <summary>
- /// The endpoint to get user-data object from
- /// </summary>
- public Uri UserDataUrl { get; }
-
- /// <summary>
- /// The endpoint route/path
- /// </summary>
- public string EndpointPath { get; }
-
- /// <summary>
- /// The size (in bytes) of the random generated nonce
- /// </summary>
- public uint NonceByteSize { get; }
-
- /// <summary>
- /// A value that specifies if locally created accounts are allowed
- /// to be logged in from an OAuth2 source
- /// </summary>
- public bool AllowForLocalAccounts { get; }
-
- /// <summary>
- /// A value that indicates if accounts that do not exist will be created
- /// and logged in immediatly, on successfull OAuth2 flow
- /// </summary>
- public bool AllowRegistration { get; }
-
- /// <summary>
- /// The size (in bytes) of the random password generated for new users
- /// </summary>
- public int RandomPasswordSize { get; }
-
- /// <summary>
- /// The initial time the login claim is valid for
- /// </summary>
- public TimeSpan InitClaimValidFor { get; }
- }
-}
diff --git a/plugins/VNLib.Plugins.Essentials.SocialOauth/src/SocialEntryPoint.cs b/plugins/VNLib.Plugins.Essentials.SocialOauth/src/SocialEntryPoint.cs
deleted file mode 100644
index 83e45c8..0000000
--- a/plugins/VNLib.Plugins.Essentials.SocialOauth/src/SocialEntryPoint.cs
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
-* Copyright (c) 2023 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Essentials.SocialOauth
-* File: SocialEntryPoint.cs
-*
-* SocialEntryPoint.cs is part of VNLib.Plugins.Essentials.SocialOauth which is part of the larger
-* VNLib collection of libraries and utilities.
-*
-* VNLib.Plugins.Essentials.SocialOauth is free software: you can redistribute it and/or modify
-* it under the terms of the GNU Affero General Public License as
-* published by the Free Software Foundation, either version 3 of the
-* License, or (at your option) any later version.
-*
-* VNLib.Plugins.Essentials.SocialOauth is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-* GNU Affero General Public License for more details.
-*
-* You should have received a copy of the GNU Affero General Public License
-* along with this program. If not, see https://www.gnu.org/licenses/.
-*/
-
-using System;
-
-using VNLib.Utils.Logging;
-using VNLib.Plugins.Essentials.SocialOauth.Endpoints;
-using VNLib.Plugins.Extensions.Loading;
-using VNLib.Plugins.Extensions.Loading.Routing;
-
-namespace VNLib.Plugins.Essentials.SocialOauth
-{
- public sealed class SocialEntryPoint : PluginBase
- {
-
- public override string PluginName => "Essentials.SocialOauth";
-
- protected override void OnLoad()
- {
- //Get the discord oauth config from the config file
- if (this.HasConfigForType<DiscordOauth>())
- {
- //Add the discord login endpoint
- this.Route<DiscordOauth>();
- Log.Information("Discord social OAuth authentication loaded");
- }
- if (this.HasConfigForType<GitHubOauth>())
- {
- //Add the github login endpoint
- this.Route<GitHubOauth>();
- Log.Information("Github social OAuth authentication loaded");
- }
- }
-
-
- protected override void OnUnLoad()
- {
- Log.Information("Plugin unloaded");
- }
-
- protected override void ProcessHostCommand(string cmd)
- {
- throw new NotImplementedException();
- }
- }
-} \ No newline at end of file
diff --git a/plugins/VNLib.Plugins.Essentials.SocialOauth/src/SocialOauthBase.cs b/plugins/VNLib.Plugins.Essentials.SocialOauth/src/SocialOauthBase.cs
deleted file mode 100644
index 561962a..0000000
--- a/plugins/VNLib.Plugins.Essentials.SocialOauth/src/SocialOauthBase.cs
+++ /dev/null
@@ -1,572 +0,0 @@
-/*
-* Copyright (c) 2023 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Essentials.SocialOauth
-* File: SocialOauthBase.cs
-*
-* SocialOauthBase.cs is part of VNLib.Plugins.Essentials.SocialOauth which is part of the larger
-* VNLib collection of libraries and utilities.
-*
-* VNLib.Plugins.Essentials.SocialOauth is free software: you can redistribute it and/or modify
-* it under the terms of the GNU Affero General Public License as
-* published by the Free Software Foundation, either version 3 of the
-* License, or (at your option) any later version.
-*
-* VNLib.Plugins.Essentials.SocialOauth is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-* GNU Affero General Public License for more details.
-*
-* You should have received a copy of the GNU Affero General Public License
-* along with this program. If not, see https://www.gnu.org/licenses/.
-*/
-
-using System;
-using System.Net;
-using System.Text.Json;
-using System.Threading;
-using System.Threading.Tasks;
-using System.Security.Cryptography;
-using System.Text.Json.Serialization;
-
-using FluentValidation;
-
-using RestSharp;
-
-using VNLib.Net.Http;
-using VNLib.Utils;
-using VNLib.Utils.Logging;
-using VNLib.Utils.Extensions;
-using VNLib.Net.Rest.Client.Construction;
-using VNLib.Plugins.Essentials.Users;
-using VNLib.Plugins.Essentials.Accounts;
-using VNLib.Plugins.Essentials.Endpoints;
-using VNLib.Plugins.Essentials.Extensions;
-using VNLib.Plugins.Essentials.SocialOauth.Validators;
-using VNLib.Plugins.Extensions.Loading;
-using VNLib.Plugins.Extensions.Validation;
-using VNLib.Plugins.Extensions.Loading.Users;
-
-using ContentType = VNLib.Net.Http.ContentType;
-
-namespace VNLib.Plugins.Essentials.SocialOauth
-{
-
- /// <summary>
- /// Provides a base class for derriving commong OAuth2 implicit authentication
- /// </summary>
- public abstract class SocialOauthBase : UnprotectedWebEndpoint
- {
- const string AUTH_ERROR_MESSAGE = "You have no pending authentication requests.";
-
- const string AUTH_GRANT_SESSION_NAME = "auth";
- const string SESSION_TOKEN_KEY_NAME = "soa.tkn";
- const string CLAIM_COOKIE_NAME = "extern-claim";
-
-
- /// <summary>
- /// The client configuration struct passed during base class construction
- /// </summary>
- protected virtual OauthClientConfig Config { get; }
-
- ///<inheritdoc/>
- protected override ProtectionSettings EndpointProtectionSettings { get; }
-
- /// <summary>
- /// The site adapter used to make requests to the OAuth2 provider
- /// </summary>
- protected OAuthSiteAdapter SiteAdapter { get; }
-
- /// <summary>
- /// The user manager used to create and manage user accounts
- /// </summary>
- protected IUserManager Users { get; }
-
- private readonly IValidator<LoginClaim> ClaimValidator;
- private readonly IValidator<string> NonceValidator;
- private readonly IValidator<AccountData> AccountDataValidator;
- private readonly ClientClaimManager _claims;
-
- protected SocialOauthBase(PluginBase plugin, IConfigScope config)
- {
- ClaimValidator = GetClaimValidator();
- NonceValidator = GetNonceValidator();
- AccountDataValidator = new AccountDataValidator();
-
- //Get the configuration element for the derrived type
- Config = plugin.CreateService<OauthClientConfig>(config);
-
- //Init endpoint
- InitPathAndLog(Config.EndpointPath, plugin.Log);
-
- Users = plugin.GetOrCreateSingleton<UserManager>();
-
-
- //Setup cookie controller and claim manager
- SingleCookieController cookies = new(CLAIM_COOKIE_NAME, Config.InitClaimValidFor)
- {
- Secure = true,
- HttpOnly = true,
- SameSite = CookieSameSite.None,
- Path = Path
- };
-
- _claims = new(cookies);
-
- //Define the site adapter
- SiteAdapter = new();
-
- //Define the the get-token request endpoint
- SiteAdapter.DefineSingleEndpoint()
- .WithEndpoint<GetTokenRequest>()
- .WithMethod(Method.Post)
- .WithUrl(Config.AccessTokenUrl)
- .WithHeader("Accept", HttpHelpers.GetContentTypeString(ContentType.Json))
- .WithParameter("client_id", c => Config.ClientID.Value)
- .WithParameter("client_secret", c => Config.ClientSecret.Value)
- .WithParameter("grant_type", "authorization_code")
- .WithParameter("code", r => r.Code)
- .WithParameter("redirect_uri", r => r.RedirectUrl);
- }
-
- private static IValidator<LoginClaim> GetClaimValidator()
- {
- InlineValidator<LoginClaim> val = new();
- val.RuleFor(static s => s.ClientId)
- .Length(10, 100)
- .WithMessage("Request is not valid")
- .AlphaNumericOnly()
- .WithMessage("Request is not valid");
-
- val.RuleFor(static s => s.PublicKey)
- .Length(50, 1024)
- .WithMessage("Request is not valid");
-
- val.RuleFor(static s => s.LocalLanguage)
- .Length(2, 10)
- .WithMessage("Request is not valid");
-
- return val;
- }
-
- private static IValidator<string> GetNonceValidator()
- {
- InlineValidator<string> val = new();
- val.RuleFor(static s => s)
- .Length(10, 200)
- //Nonces are base32, so only alpha num
- .AlphaNumeric();
- return val;
- }
-
- ///<inheritdoc/>
- protected override ERRNO PreProccess(HttpEntity entity)
- {
- if (!base.PreProccess(entity))
- {
- return false;
- }
-
- /*
- * Cross site checking is disabled because we need to allow cross site
- * for OAuth2 redirect flows
- */
- if (entity.Server.Method != HttpMethod.GET && entity.Server.IsCrossSite())
- {
- return false;
- }
-
- //Make sure the user is not logged in
- return !entity.IsClientAuthorized(AuthorzationCheckLevel.Any);
- }
-
- /// <summary>
- /// When derrived in a child class, exchanges an OAuth2 code grant type
- /// for an OAuth2 access token to make api requests
- /// </summary>
- /// <param name="ev"></param>
- /// <param name="code">The raw code from the remote OAuth2 granting server</param>
- /// <param name="cancellationToken">A token to cancel the operation</param>
- /// <returns>
- /// A task the resolves the <see cref="OAuthAccessState"/> that includes all relavent
- /// authorization data. Result may be null if authorzation is invalid or not granted
- /// </returns>
- protected virtual async Task<OAuthAccessState?> ExchangeCodeForTokenAsync(HttpEntity ev, string code, CancellationToken cancellationToken)
- {
- //Create new request object
- GetTokenRequest req = new(code, $"{ev.Server.RequestUri.Scheme}://{ev.Server.RequestUri.Authority}{Path}");
-
- //Execute request and attempt to recover the authorization response
- Oauth2TokenResult? response = await SiteAdapter.ExecuteAsync(req, cancellationToken).AsJson<Oauth2TokenResult>();
-
- if(response?.Error != null)
- {
- Log.Debug("Error result from {conf} code {code} description: {err}", Config.AccountOrigin, response.Error, response.ErrorDescription);
- return null;
- }
-
- return response;
- }
-
- /// <summary>
- /// Gets an object that represents the user's account data from the OAuth provider when
- /// creating a new user for the current platform
- /// </summary>
- /// <param name="clientAccess">The access state from the code/token exchange</param>
- /// <param name="cancellationToken">A token to cancel the operation</param>
- /// <returns>The user's account data, null if not account exsits on the remote site, and process cannot continue</returns>
- protected abstract Task<AccountData?> GetAccountDataAsync(IOAuthAccessState clientAccess, CancellationToken cancellationToken);
-
- /// <summary>
- /// Gets an object that represents the required information for logging-in a user (namley unique user-id)
- /// </summary>
- /// <param name="clientAccess">The authorization information granted from the OAuth2 authorization server</param>
- /// <param name="cancellation">A token to cancel the operation</param>
- /// <returns></returns>
- protected abstract Task<UserLoginData?> GetLoginDataAsync(IOAuthAccessState clientAccess, CancellationToken cancellation);
-
-
-
- /*
- * Claims are considered indempodent because they require no previous state
- * and will return a new secret authentication "token" (url + nonce) that
- * uniquely identifies the claim and authorization upgrade later
- */
-
- ///<inheritdoc/>
- protected override async ValueTask<VfReturnType> PutAsync(HttpEntity entity)
- {
- ValErrWebMessage webm = new();
-
- //Get the login message
- LoginClaim? claim = await entity.GetJsonFromFileAsync<LoginClaim>();
-
- if (webm.Assert(claim != null, "Emtpy message body"))
- {
- entity.CloseResponseJson(HttpStatusCode.BadRequest, webm);
- return VfReturnType.VirtualSkip;
- }
-
- //Validate the message
- if (!ClaimValidator.Validate(claim, webm))
- {
- entity.CloseResponseJson(HttpStatusCode.UnprocessableEntity, webm);
- return VfReturnType.VirtualSkip;
- }
-
- //Configure the login claim
- claim.IssuedAtTime = entity.RequestedTimeUtc.ToUnixTimeSeconds();
-
- //Set expiration time in seconds
- claim.ExpirationSeconds = entity.RequestedTimeUtc.Add(Config.InitClaimValidFor).ToUnixTimeMilliseconds();
-
- //Set nonce
- claim.ComputeNonce((int)Config.NonceByteSize);
-
- //Build the redirect uri
- webm.Result = new LoginUriBuilder(Config)
- .WithEncoding(entity.Server.Encoding)
- .WithUrl(entity.Server.RequestUri.Scheme, entity.Server.RequestUri.Authority, Path)
- .WithNonce(claim.Nonce!)
- .Encrypt(entity, claim);
-
- //Sign and set the claim cookie
- _claims.SignAndSetCookie(entity, claim);
-
- webm.Success = true;
- //Response
- return VirtualOk(entity, webm);
- }
-
- /*
- * Get method is invoked when the remote OAuth2 control has been passed back
- * to this server. If successful should include a code that grants authorization
- * and include a state variable that the client decrypted from an initial claim
- * to prove its identity
- */
-
- ///<inheritdoc/>
- protected override async ValueTask<VfReturnType> GetAsync(HttpEntity entity)
- {
- //Make sure state and code parameters are available
- if (entity.QueryArgs.TryGetNonEmptyValue("state", out string? state)
- && entity.QueryArgs.TryGetNonEmptyValue("code", out string? code))
- {
- //Disable refer headers when nonce is set
- entity.Server.Headers["Referrer-Policy"] = "no-referrer";
-
- //Check for security navigation headers. This should be a browser redirect,
- if (!entity.Server.IsNavigation() || !entity.Server.IsUserInvoked())
- {
- _claims.ClearClaimData(entity);
- //The connection was not a browser redirect
- entity.Redirect(RedirectType.Temporary, $"{Path}?result=bad_sec");
- return VfReturnType.VirtualSkip;
- }
-
- //Try to get the claim from the state parameter
- if (!_claims.VerifyAndGetClaim(entity, out LoginClaim? claim))
- {
- _claims.ClearClaimData(entity);
- entity.Redirect(RedirectType.Temporary, $"{Path}?result=expired");
- return VfReturnType.VirtualSkip;
- }
-
- //Confirm the nonce matches the claim
- if (string.CompareOrdinal(claim.Nonce, state) != 0)
- {
- _claims.ClearClaimData(entity);
- entity.Redirect(RedirectType.Temporary, $"{Path}?result=invalid");
- return VfReturnType.VirtualSkip;
- }
-
- //Exchange the OAuth code for a token (application specific)
- OAuthAccessState? token = await ExchangeCodeForTokenAsync(entity, code, entity.EventCancellation);
-
- //Token may be null
- if (token == null)
- {
- _claims.ClearClaimData(entity);
- entity.Redirect(RedirectType.Temporary, $"{Path}?result=invalid");
- return VfReturnType.VirtualSkip;
- }
-
- //Create the new nonce
- claim.ComputeNonce((int)Config.NonceByteSize);
-
- //Store access state in the user's session
- entity.Session.SetObject(SESSION_TOKEN_KEY_NAME, token);
-
- //Sign and set cookie
- _claims.SignAndSetCookie(entity, claim);
-
- //Prepare redirect
- entity.Redirect(RedirectType.Temporary, $"{Path}?result=authorized&nonce={claim.Nonce}");
- return VfReturnType.VirtualSkip;
- }
-
- //Check to see if there was an error code set
- if (entity.QueryArgs.TryGetNonEmptyValue("error", out string? errorCode))
- {
- _claims.ClearClaimData(entity);
- Log.Debug("{Type} error {err}:{des}", Config.AccountOrigin, errorCode, entity.QueryArgs["error_description"]);
- entity.Redirect(RedirectType.Temporary, $"{Path}?result=error");
- return VfReturnType.VirtualSkip;
- }
-
- return VfReturnType.ProcessAsFile;
- }
-
- /*
- * Post messages finalize a login from a nonce
- */
-
- ///<inheritdoc/>
- protected override async ValueTask<VfReturnType> PostAsync(HttpEntity entity)
- {
- ValErrWebMessage webm = new();
-
- //Get the finalization message
- using JsonDocument? request = await entity.GetJsonFromFileAsync();
-
- if (webm.Assert(request != null, "Request message is required"))
- {
- return VirtualClose(entity, webm, HttpStatusCode.BadRequest);
- }
-
- //Recover the nonce
- string? base32Nonce = request.RootElement.GetPropString("nonce");
-
- if(webm.Assert(base32Nonce != null, "Nonce parameter is required"))
- {
- return VirtualClose(entity, webm, HttpStatusCode.UnprocessableEntity);
- }
-
- //Validate nonce
- if (!NonceValidator.Validate(base32Nonce, webm))
- {
- return VirtualClose(entity, webm, HttpStatusCode.UnprocessableEntity);
- }
-
- //Recover the access token
- if (webm.Assert(_claims.VerifyAndGetClaim(entity, out LoginClaim? claim), AUTH_ERROR_MESSAGE))
- {
- return VirtualOk(entity, webm);
- }
-
- //We can clear the client's access claim
- _claims.ClearClaimData(entity);
-
- //Confirm nonce matches the client's nonce string
- bool nonceValid = string.CompareOrdinal(claim.Nonce, base32Nonce) == 0;
-
- if (webm.Assert(nonceValid, AUTH_ERROR_MESSAGE))
- {
- return VirtualOk(entity, webm);
- }
-
- //Safe to recover the access token
- IOAuthAccessState token = entity.Session.GetObject<OAuthAccessState>(SESSION_TOKEN_KEY_NAME);
-
- //get the user's login information (ie userid)
- UserLoginData? userLogin = await GetLoginDataAsync(token, entity.EventCancellation);
-
- if(webm.Assert(userLogin?.UserId != null, AUTH_ERROR_MESSAGE))
- {
- return VirtualOk(entity, webm);
- }
-
- //Convert the platform user-id to a database-safe user-id
- string computedId = Users.ComputeSafeUserId(userLogin.UserId!);
-
- //Fetch the user from the database
- IUser? user = await Users.GetUserFromIDAsync(computedId, entity.EventCancellation);
-
- /*
- * If a user is not found, we can optionally create a new user account
- * if the configuration allows it.
- */
- if(user == null)
- {
- //make sure registration is enabled
- if (webm.Assert(Config.AllowRegistration, AUTH_ERROR_MESSAGE))
- {
- return VirtualOk(entity, webm);
- }
-
- //Get the clients personal info to being login process
- AccountData? userAccount = await GetAccountDataAsync(token, entity.EventCancellation);
-
- if (webm.Assert(userAccount != null, AUTH_ERROR_MESSAGE))
- {
- return VirtualOk(entity, webm);
- }
-
- //Validate the account data
- if (webm.Assert(AccountDataValidator.Validate(userAccount).IsValid, AUTH_ERROR_MESSAGE))
- {
- return VirtualOk(entity, webm);
- }
-
- //See if user by email address exists
- user = await Users.GetUserFromEmailAsync(userAccount.EmailAddress!, entity.EventCancellation);
-
- if (user == null)
- {
- //Create the new user account
- UserCreationRequest creation = new()
- {
- EmailAddress = userAccount.EmailAddress!,
- InitialStatus = UserStatus.Active,
- };
-
- try
- {
- //Create the user with the specified email address, minimum privilage level, and an empty password
- user = await Users.CreateUserAsync(creation, computedId, entity.EventCancellation);
-
- //Store the new profile and origin
- user.SetProfile(userAccount);
- user.SetAccountOrigin(Config.AccountOrigin);
- }
- catch (UserCreationFailedException)
- {
- Log.Warn("Failed to create new user from new OAuth2 login, because a creation exception occured");
- webm.Result = "Please try again later";
- return VirtualOk(entity, webm);
- }
-
- //Skip check since we just created the user
- goto Authorize;
- }
-
- /*
- * User account already exists via email address but not
- * user-id
- */
- }
-
- //Make sure local accounts are allowed
- if (webm.Assert(!user.IsLocalAccount() || Config.AllowForLocalAccounts, AUTH_ERROR_MESSAGE))
- {
- return VirtualOk(entity, webm);
- }
-
- //Reactivate inactive accounts
- if (user.Status == UserStatus.Inactive)
- {
- user.Status = UserStatus.Active;
- }
-
- //Make sure the account is active
- if (webm.Assert(user.Status == UserStatus.Active, AUTH_ERROR_MESSAGE))
- {
- return VirtualOk(entity, webm);
- }
-
- Authorize:
-
- try
- {
- //Generate authoization
- entity.GenerateAuthorization(claim, user, webm);
-
- //Store the user current oauth information in the current session for others to digest
- entity.Session.SetObject($"{Config.AccountOrigin}.{AUTH_GRANT_SESSION_NAME}", token);
-
- //Send the username back to the client
- webm.Result = new AccountData()
- {
- EmailAddress = user.EmailAddress,
- };
-
- //Set the success flag
- webm.Success = true;
-
- //Write to log
- Log.Debug("Successful social login for user {uid}... from {ip}", user.UserID[..8], entity.TrustedRemoteIp);
-
- //release the user
- await user.ReleaseAsync();
- }
- catch (CryptographicException ce)
- {
- Log.Debug("Failed to generate authorization for {user}, error {err}", user.UserID, ce.Message);
- webm.Result = AUTH_ERROR_MESSAGE;
- }
- catch (OutOfMemoryException)
- {
- Log.Debug("Out of buffer space for token data encryption, for user {usr}, from ip {ip}", user.UserID, entity.TrustedRemoteIp);
- webm.Result = AUTH_ERROR_MESSAGE;
- }
- catch(UserUpdateException uue)
- {
- webm.Token = null;
- webm.Result = AUTH_ERROR_MESSAGE;
- webm.Success = false;
-
- //destroy any login data on failure
- entity.InvalidateLogin();
-
- Log.Error("Failed to update the user's account cause:\n{err}",uue);
- }
- finally
- {
- user.Dispose();
- }
- return VirtualOk(entity, webm);
- }
-
-
- sealed class Oauth2TokenResult: OAuthAccessState
- {
- [JsonPropertyName("error")]
- public string? Error { get; set; }
-
- [JsonPropertyName("error_description")]
- public string? ErrorDescription { get; set; }
- }
-
- }
-}
diff --git a/plugins/VNLib.Plugins.Essentials.SocialOauth/src/UserLoginData.cs b/plugins/VNLib.Plugins.Essentials.SocialOauth/src/UserLoginData.cs
deleted file mode 100644
index 93f9f12..0000000
--- a/plugins/VNLib.Plugins.Essentials.SocialOauth/src/UserLoginData.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
-* Copyright (c) 2023 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Essentials.SocialOauth
-* File: UserLoginData.cs
-*
-* UserLoginData.cs is part of VNLib.Plugins.Essentials.SocialOauth which is part of the larger
-* VNLib collection of libraries and utilities.
-*
-* VNLib.Plugins.Essentials.SocialOauth is free software: you can redistribute it and/or modify
-* it under the terms of the GNU Affero General Public License as
-* published by the Free Software Foundation, either version 3 of the
-* License, or (at your option) any later version.
-*
-* VNLib.Plugins.Essentials.SocialOauth is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-* GNU Affero General Public License for more details.
-*
-* You should have received a copy of the GNU Affero General Public License
-* along with this program. If not, see https://www.gnu.org/licenses/.
-*/
-
-using System.Text.Json.Serialization;
-
-namespace VNLib.Plugins.Essentials.SocialOauth
-{
- public class UserLoginData
- {
- [JsonPropertyName("user_id")]
- public string? UserId { get; set; }
- }
-}
diff --git a/plugins/VNLib.Plugins.Essentials.SocialOauth/src/VNLib.Plugins.Essentials.SocialOauth.csproj b/plugins/VNLib.Plugins.Essentials.SocialOauth/src/VNLib.Plugins.Essentials.SocialOauth.csproj
deleted file mode 100644
index 1b014cc..0000000
--- a/plugins/VNLib.Plugins.Essentials.SocialOauth/src/VNLib.Plugins.Essentials.SocialOauth.csproj
+++ /dev/null
@@ -1,66 +0,0 @@
-<Project Sdk="Microsoft.NET.Sdk">
-
- <PropertyGroup>
- <Nullable>enable</Nullable>
- <TargetFramework>net6.0</TargetFramework>
- <RootNamespace>VNLib.Plugins.Essentials.SocialOauth</RootNamespace>
- <AssemblyName>SocialOauth</AssemblyName>
- <GenerateDocumentationFile>True</GenerateDocumentationFile>
- <AnalysisLevel>latest-all</AnalysisLevel>
- <NeutralLanguage>en-US</NeutralLanguage>
- <EnableDynamicLoading>true</EnableDynamicLoading>
- </PropertyGroup>
-
- <PropertyGroup>
- <PackageId>VNLib.Plugins.Essentials.SocialOauth</PackageId>
- <Authors>Vaughn Nugent</Authors>
- <Company>Vaughn Nugent</Company>
- <Product>A basic external OAuth2 authentication plugin.</Product>
- <Copyright>Copyright © 2023 Vaughn Nugent</Copyright>
- <PackageProjectUrl>https://www.vaughnnugent.com/resources/software/modules/Plugins.Essentials</PackageProjectUrl>
- <RepositoryUrl>https://github.com/VnUgE/Plugins.Essentials/tree/master/plugins/VNLib.Plugins.Essentials.SocialOauth</RepositoryUrl>
- <Description>
- Essentials framework plugin for common OAuth2 web-based client authentication. Currently implements GitHub, Discord, and Auth0
- authentication flows.
- </Description>
- </PropertyGroup>
-
- <PropertyGroup>
- <PackageReadmeFile>README.md</PackageReadmeFile>
- <PackageLicenseFile>LICENSE</PackageLicenseFile>
- </PropertyGroup>
- <ItemGroup>
- <None Include="..\..\..\LICENSE">
- <Pack>True</Pack>
- <PackagePath>\</PackagePath>
- <CopyToOutputDirectory>Always</CopyToOutputDirectory>
- </None>
- <None Include="..\README.md">
- <Pack>True</Pack>
- <PackagePath>\</PackagePath>
- </None>
- </ItemGroup>
-
- <ItemGroup>
- <PackageReference Include="ErrorProne.NET.CoreAnalyzers" Version="0.1.2">
- <PrivateAssets>all</PrivateAssets>
- <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
- </PackageReference>
- <PackageReference Include="ErrorProne.NET.Structs" Version="0.1.2">
- <PrivateAssets>all</PrivateAssets>
- <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
- </PackageReference>
- </ItemGroup>
-
- <ItemGroup>
- <ProjectReference Include="..\..\..\..\..\core\lib\Net.Rest.Client\src\VNLib.Net.Rest.Client.csproj" />
- <ProjectReference Include="..\..\..\..\..\core\lib\Plugins.Essentials\src\VNLib.Plugins.Essentials.csproj" />
- <ProjectReference Include="..\..\..\..\Extensions\lib\VNLib.Plugins.Extensions.Loading\src\VNLib.Plugins.Extensions.Loading.csproj" />
- <ProjectReference Include="..\..\..\..\Extensions\lib\VNLib.Plugins.Extensions.Validation\src\VNLib.Plugins.Extensions.Validation.csproj" />
- </ItemGroup>
-
- <Target Condition="'$(BuildingInsideVisualStudio)' == true" Name="PostBuild" AfterTargets="PostBuildEvent">
- <Exec Command="start xcopy &quot;$(TargetDir)&quot; &quot;..\..\..\..\..\devplugins\$(TargetName)&quot; /E /Y /R" />
- </Target>
-
-</Project>
diff --git a/plugins/VNLib.Plugins.Essentials.SocialOauth/src/Validators/AccountDataValidator.cs b/plugins/VNLib.Plugins.Essentials.SocialOauth/src/Validators/AccountDataValidator.cs
deleted file mode 100644
index 0ccda69..0000000
--- a/plugins/VNLib.Plugins.Essentials.SocialOauth/src/Validators/AccountDataValidator.cs
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
-* Copyright (c) 2023 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Essentials.SocialOauth
-* File: AccountDataValidator.cs
-*
-* AccountDataValidator.cs is part of VNLib.Plugins.Essentials.SocialOauth which is part of the larger
-* VNLib collection of libraries and utilities.
-*
-* VNLib.Plugins.Essentials.SocialOauth is free software: you can redistribute it and/or modify
-* it under the terms of the GNU Affero General Public License as
-* published by the Free Software Foundation, either version 3 of the
-* License, or (at your option) any later version.
-*
-* VNLib.Plugins.Essentials.SocialOauth is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-* GNU Affero General Public License for more details.
-*
-* You should have received a copy of the GNU Affero General Public License
-* along with this program. If not, see https://www.gnu.org/licenses/.
-*/
-
-using FluentValidation;
-
-using VNLib.Plugins.Essentials.Accounts;
-using VNLib.Plugins.Extensions.Validation;
-
-namespace VNLib.Plugins.Essentials.SocialOauth.Validators
-{
- internal class AccountDataValidator : AbstractValidator<AccountData>
- {
- public AccountDataValidator() : base()
- {
- RuleFor(t => t.EmailAddress)
- .NotEmpty()
- .WithMessage("Your account does not have an email address assigned to it");
-
- RuleFor(t => t.City)
- .MaximumLength(35)
- .AlphaOnly()
- .When(t => t.City?.Length > 0);
-
- RuleFor(t => t.Company)
- .MaximumLength(50)
- .SpecialCharacters()
- .When(t => t.Company?.Length > 0);
-
- //Require a first and last names to be set together
- When(t => t.First?.Length > 0 || t.Last?.Length > 0, () =>
- {
- RuleFor(t => t.First)
- .Length(1, 35)
- .AlphaOnly();
- RuleFor(t => t.Last)
- .Length(1, 35)
- .AlphaOnly();
- });
-
- RuleFor(t => t.PhoneNumber)
- .PhoneNumber()
- .When(t => t.PhoneNumber?.Length > 0)
- .OverridePropertyName("Phone");
-
- //State must be 2 characters for us states if set
- RuleFor(t => t.State)
- .Length(2)
- .When(t => t.State?.Length > 0);
-
- RuleFor(t => t.Street)
- .AlphaNumericOnly()
- .MaximumLength(50)
- .When(t => t.Street?.Length > 0);
-
- //Allow empty zip codes, but if one is defined, is must be less than 7 characters
- RuleFor(t => t.Zip)
- .NumericOnly()
- .MaximumLength(7)
- .When(t => t.Zip?.Length > 0);
- }
- }
-}
diff --git a/plugins/VNLib.Plugins.Essentials.SocialOauth/src/Validators/LoginMessageValidation.cs b/plugins/VNLib.Plugins.Essentials.SocialOauth/src/Validators/LoginMessageValidation.cs
deleted file mode 100644
index 3cf4e70..0000000
--- a/plugins/VNLib.Plugins.Essentials.SocialOauth/src/Validators/LoginMessageValidation.cs
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
-* Copyright (c) 2023 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Essentials.SocialOauth
-* File: LoginMessageValidation.cs
-*
-* LoginMessageValidation.cs is part of VNLib.Plugins.Essentials.SocialOauth which is part of the larger
-* VNLib collection of libraries and utilities.
-*
-* VNLib.Plugins.Essentials.SocialOauth is free software: you can redistribute it and/or modify
-* it under the terms of the GNU Affero General Public License as
-* published by the Free Software Foundation, either version 3 of the
-* License, or (at your option) any later version.
-*
-* VNLib.Plugins.Essentials.SocialOauth is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-* GNU Affero General Public License for more details.
-*
-* You should have received a copy of the GNU Affero General Public License
-* along with this program. If not, see https://www.gnu.org/licenses/.
-*/
-
-using System;
-
-using FluentValidation;
-
-using VNLib.Plugins.Essentials.Accounts;
-using VNLib.Plugins.Extensions.Validation;
-
-namespace VNLib.Plugins.Essentials.SocialOauth.Validators
-{
- internal class LoginMessageValidation : AbstractValidator<LoginMessage>
- {
- /*
- * A login message object is only used for common semantics within
- * the user-system so validation operations are different than a
- * normal login endpoint as named fields may be used differently
- */
- public LoginMessageValidation()
- {
- RuleFor(t => t.ClientId)
- .Length(10, 50)
- .WithMessage("Your browser is not sending required security information")
- .IllegalCharacters()
- .WithMessage("Your browser is not sending required security information");
-
- RuleFor(t => t.ClientPublicKey)
- .Length (50, 1000)
- .WithMessage("Your browser is not sending required security information")
- .IllegalCharacters()
- .WithMessage("Your browser is not sending required security information");
-
- //Password is only used for nonce tokens
- RuleFor(t => t.Password).NotEmpty();
-
- RuleFor(t => t.LocalLanguage)
- .NotEmpty()
- .WithMessage("Your language is not supported")
- .AlphaNumericOnly()
- .WithMessage("Your language is not supported");
- }
- }
-}