aboutsummaryrefslogtreecommitdiff
path: root/plugins/providers
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/providers')
-rw-r--r--plugins/providers/VNLib.Plugins.Essentials.Auth.Auth0/README.md15
-rw-r--r--plugins/providers/VNLib.Plugins.Essentials.Auth.Auth0/build.readme.md11
-rw-r--r--plugins/providers/VNLib.Plugins.Essentials.Auth.Auth0/src/Auth0Portal.cs67
-rw-r--r--plugins/providers/VNLib.Plugins.Essentials.Auth.Auth0/src/Endpoints/LoginEndpoint.cs191
-rw-r--r--plugins/providers/VNLib.Plugins.Essentials.Auth.Auth0/src/Endpoints/LogoutEndpoint.cs70
-rw-r--r--plugins/providers/VNLib.Plugins.Essentials.Auth.Auth0/src/VNLib.Plugins.Essentials.Auth.Auth0.csproj48
-rw-r--r--plugins/providers/VNLib.Plugins.Essentials.Auth.Discord/README.md15
-rw-r--r--plugins/providers/VNLib.Plugins.Essentials.Auth.Discord/build.readme.md11
-rw-r--r--plugins/providers/VNLib.Plugins.Essentials.Auth.Discord/src/DiscordPortal.cs65
-rw-r--r--plugins/providers/VNLib.Plugins.Essentials.Auth.Discord/src/Endpoint/DiscordOauth.cs148
-rw-r--r--plugins/providers/VNLib.Plugins.Essentials.Auth.Discord/src/VNLib.Plugins.Essentials.Auth.Discord.csproj48
-rw-r--r--plugins/providers/VNLib.Plugins.Essentials.Auth.Github/README.md15
-rw-r--r--plugins/providers/VNLib.Plugins.Essentials.Auth.Github/build.readme.md11
-rw-r--r--plugins/providers/VNLib.Plugins.Essentials.Auth.Github/src/Endpoint/GitHubOauth.cs213
-rw-r--r--plugins/providers/VNLib.Plugins.Essentials.Auth.Github/src/GithubPortal.cs65
-rw-r--r--plugins/providers/VNLib.Plugins.Essentials.Auth.Github/src/VNLib.Plugins.Essentials.Auth.Github.csproj48
16 files changed, 1041 insertions, 0 deletions
diff --git a/plugins/providers/VNLib.Plugins.Essentials.Auth.Auth0/README.md b/plugins/providers/VNLib.Plugins.Essentials.Auth.Auth0/README.md
new file mode 100644
index 0000000..1cacc6b
--- /dev/null
+++ b/plugins/providers/VNLib.Plugins.Essentials.Auth.Auth0/README.md
@@ -0,0 +1,15 @@
+# VNLib.Plugins.Essentials.Auth.Auth0
+*A runtime asset library that provides enterprise Auth0 OAuth authentication to your application that is using the [Auth.Social](../../VNLib.Plugins.Essentials.Auth.Social) plugin*
+
+## Builds
+Debug build w/ symbols & xml docs, release builds, NuGet packages, and individually packaged source code are available on my website (link below).
+
+## Docs and Guides
+Documentation, specifications, and setup guides are available on my website.
+
+[Docs and Articles](https://www.vaughnnugent.com/resources/software/articles?tags=docs,_VNLib.Plugins.Essentials.Auth.Auth0)
+[Builds and Source](https://www.vaughnnugent.com/resources/software/modules/Plugins.Essentials)
+[Nuget Feeds](https://www.vaughnnugent.com/resources/software/modules)
+
+## License
+Source files in for this project are licensed to you under the GNU Affero General Public License (or any later version). See the LICENSE files for more information. \ No newline at end of file
diff --git a/plugins/providers/VNLib.Plugins.Essentials.Auth.Auth0/build.readme.md b/plugins/providers/VNLib.Plugins.Essentials.Auth.Auth0/build.readme.md
new file mode 100644
index 0000000..4533b0d
--- /dev/null
+++ b/plugins/providers/VNLib.Plugins.Essentials.Auth.Auth0/build.readme.md
@@ -0,0 +1,11 @@
+VNLib.Plugins.Essentials.Auth.Auth0 Copyright © 2024 Vaughn Nugent
+Contact: Vaughn Nugent vnpublic[at]proton.me
+
+LICENSE:
+You should have recieved a copy of the GNU Affero General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+In the future there will be more information for installtion in this file, but for now go to my website
+
+https://www.vaughnnugent.com/resources/software/articles?tags=docs,_VNLib.Plugins.Essentials.Auth.Auth0
+
+Thank you! \ No newline at end of file
diff --git a/plugins/providers/VNLib.Plugins.Essentials.Auth.Auth0/src/Auth0Portal.cs b/plugins/providers/VNLib.Plugins.Essentials.Auth.Auth0/src/Auth0Portal.cs
new file mode 100644
index 0000000..0ae92f4
--- /dev/null
+++ b/plugins/providers/VNLib.Plugins.Essentials.Auth.Auth0/src/Auth0Portal.cs
@@ -0,0 +1,67 @@
+/*
+* Copyright (c) 2024 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Essentials.Auth.Auth0
+* File: Auth0Portal.cs
+*
+* Auth0Portal.cs is part of VNLib.Plugins.Essentials.Auth.Auth0 which is
+* part of the larger VNLib collection of libraries and utilities.
+*
+* VNLib.Plugins.Essentials.Auth.Auth0 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.Auth.Auth0 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.Plugins.Extensions.Loading;
+using VNLib.Plugins.Extensions.Loading.Routing;
+using VNLib.Plugins.Essentials.Auth.Social;
+
+using VNLib.Plugins.Essentials.Auth.Auth0.Endpoints;
+
+namespace VNLib.Plugins.Essentials.Auth.Auth0
+{
+
+ [ServiceExport]
+ [ConfigurationName(ConfigKey)]
+ public sealed class Auth0Portal : IOAuthProvider
+ {
+ internal const string ConfigKey = "auth0";
+
+ private readonly LoginEndpoint _loginEndpoint;
+ private readonly LogoutEndpoint _logoutEndpoint;
+
+ public Auth0Portal(PluginBase plugin, IConfigScope config)
+ {
+ //Init the login endpoint
+ _loginEndpoint = plugin.Route<LoginEndpoint>();
+ _logoutEndpoint = plugin.Route<LogoutEndpoint>();
+ }
+
+ ///<inheritdoc/>
+ public SocialOAuthPortal[] GetPortals()
+ {
+
+ //Return the Auth0 portal
+ return [
+ new SocialOAuthPortal(
+ ConfigKey,
+ _loginEndpoint,
+ _logoutEndpoint
+ )
+ ];
+
+ }
+ }
+}
diff --git a/plugins/providers/VNLib.Plugins.Essentials.Auth.Auth0/src/Endpoints/LoginEndpoint.cs b/plugins/providers/VNLib.Plugins.Essentials.Auth.Auth0/src/Endpoints/LoginEndpoint.cs
new file mode 100644
index 0000000..52be461
--- /dev/null
+++ b/plugins/providers/VNLib.Plugins.Essentials.Auth.Auth0/src/Endpoints/LoginEndpoint.cs
@@ -0,0 +1,191 @@
+/*
+* Copyright (c) 2024 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Essentials.Auth.Auth0
+* File: LoginEndpoint.cs
+*
+* LoginEndpoint.cs is part of VNLib.Plugins.Essentials.Auth.Auth0 which is
+* part of the larger VNLib collection of libraries and utilities.
+*
+* VNLib.Plugins.Essentials.Auth.Auth0 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.Auth.Auth0 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.Linq;
+using System.Text.Json;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Collections.Generic;
+
+using RestSharp;
+
+using VNLib.Hashing;
+using VNLib.Hashing.IdentityUtility;
+using VNLib.Utils.Logging;
+using VNLib.Plugins.Essentials.Accounts;
+using VNLib.Plugins.Extensions.Loading;
+using VNLib.Net.Rest.Client.Construction;
+using VNLib.Plugins.Essentials.Auth.Social;
+
+/*
+ * Provides specialized login for Auth0 identity managment system. Auth0 apis use JWT tokens
+ * and JWK signing keys. Keys are downloaded when the plugin is first loaded and cached for
+ * the lifetime of the plugin. The keys are used to verify the JWT token and extract the user
+ */
+
+namespace VNLib.Plugins.Essentials.Auth.Auth0.Endpoints
+{
+
+ [ConfigurationName(Auth0Portal.ConfigKey)]
+ internal sealed class LoginEndpoint : SocialOauthBase
+ {
+ private readonly IAsyncLazy<ReadOnlyJsonWebKey[]> Auth0VerificationJwk;
+ private readonly bool VerifyEmail;
+
+ public LoginEndpoint(PluginBase plugin, IConfigScope config) : base(plugin, config)
+ {
+ string keyUrl = config["key_url"].GetString() ?? throw new KeyNotFoundException("Missing Auth0 'key_url' from config");
+
+ //Define the key endpoint
+ SiteAdapter.DefineSingleEndpoint()
+ .WithEndpoint<GetKeyRequest>()
+ .WithUrl(keyUrl)
+ .WithMethod(Method.Get)
+ .WithHeader("Accept", "application/json")
+ .OnResponse((r, res) => res.ThrowIfError());
+
+ //Check for email verification
+ VerifyEmail = config.TryGetValue("verified_email", out JsonElement el) && el.GetBoolean();
+
+ //Get certificate on background thread
+ Auth0VerificationJwk = Task.Run(GetRsaCertificate).AsLazy();
+ }
+
+ private async Task<ReadOnlyJsonWebKey[]> GetRsaCertificate()
+ {
+ try
+ {
+ Log.Debug("Getting Auth0 signing keys");
+
+ //rent client from pool
+ RestResponse response = await SiteAdapter.ExecuteAsync(new GetKeyRequest());
+
+ //Get response as doc
+ using JsonDocument doc = JsonDocument.Parse(response.RawBytes);
+
+ //Create a new jwk from each key element in the response
+ ReadOnlyJsonWebKey[] keys = doc.RootElement.GetProperty("keys")
+ .EnumerateArray()
+ .Select(static k => new ReadOnlyJsonWebKey(k))
+ .ToArray();
+
+ Log.Debug("Found {count} Auth0 signing keys", keys.Length);
+
+ return keys;
+ }
+ catch (Exception e)
+ {
+ Log.Error(e, "Failed to get Auth0 signing keys");
+ throw;
+ }
+ }
+
+ /*
+ * Auth0 uses the format "platoform|{user_id}" for the user id so it should match the
+ * external platofrm as github and discord endoints also
+ */
+
+ private static string GetUserIdFromPlatform(string userName)
+ {
+ return ManagedHash.ComputeHash(userName, HashAlg.SHA1, HashEncodingMode.Hexadecimal);
+ }
+
+
+ private static readonly Task<UserLoginData?> EmptyLoginData = Task.FromResult<UserLoginData?>(null);
+ private static readonly Task<AccountData?> EmptyUserData = Task.FromResult<AccountData?>(null);
+
+ ///<inheritdoc/>
+ protected override Task<UserLoginData?> GetLoginDataAsync(IOAuthAccessState clientAccess, CancellationToken cancellation)
+ {
+ //recover the identity token
+ using JsonWebToken jwt = JsonWebToken.Parse(clientAccess.IdToken);
+
+ //Verify the token against the first signing key
+ if (!jwt.VerifyFromJwk(Auth0VerificationJwk.Value[0]))
+ {
+ return EmptyLoginData;
+ }
+
+ using JsonDocument userData = jwt.GetPayload();
+
+ int iat = userData.RootElement.GetProperty("iat").GetInt32();
+ int exp = userData.RootElement.GetProperty("exp").GetInt32();
+
+ string userId = userData.RootElement.GetProperty("sub").GetString() ?? throw new Exception("Missing sub in jwt");
+ string audience = userData.RootElement.GetProperty("aud").GetString() ?? throw new Exception("Missing aud in jwt");
+ string issuer = userData.RootElement.GetProperty("iss").GetString() ?? throw new Exception("Missing iss in jwt");
+
+ if (exp < DateTimeOffset.UtcNow.ToUnixTimeSeconds())
+ {
+ //Expired
+ return EmptyLoginData;
+ }
+
+ //Verify audience matches client id
+ if (!Config.ClientID.Value.Equals(audience, StringComparison.Ordinal))
+ {
+ //Invalid audience
+ return EmptyLoginData;
+ }
+
+ return Task.FromResult<UserLoginData?>(new UserLoginData()
+ {
+ UserId = GetUserIdFromPlatform(userId)
+ });
+ }
+
+ /*
+ * Account data may be recovered from the identity token
+ * and it happens after a call to GetLoginData so
+ * we do not need to re-verify the token
+ */
+ ///<inheritdoc/>
+ protected override Task<AccountData?> GetAccountDataAsync(IOAuthAccessState clientAccess, CancellationToken cancellationToken)
+ {
+ //Parse token again to get the user data
+ using JsonWebToken jwt = JsonWebToken.Parse(clientAccess.IdToken);
+
+ using JsonDocument userData = jwt.GetPayload();
+
+ //Confirm email is verified
+ if (!userData.RootElement.GetProperty("email_verified").GetBoolean() && VerifyEmail)
+ {
+ return EmptyUserData;
+ }
+
+ string fullName = userData.RootElement.GetProperty("name").GetString() ?? " ";
+
+ return Task.FromResult<AccountData?>(new AccountData()
+ {
+ EmailAddress = userData.RootElement.GetProperty("email").GetString(),
+ First = fullName.Split(' ').FirstOrDefault(),
+ Last = fullName.Split(' ').LastOrDefault(),
+ });
+ }
+
+ private sealed record class GetKeyRequest()
+ { }
+ }
+}
diff --git a/plugins/providers/VNLib.Plugins.Essentials.Auth.Auth0/src/Endpoints/LogoutEndpoint.cs b/plugins/providers/VNLib.Plugins.Essentials.Auth.Auth0/src/Endpoints/LogoutEndpoint.cs
new file mode 100644
index 0000000..497357a
--- /dev/null
+++ b/plugins/providers/VNLib.Plugins.Essentials.Auth.Auth0/src/Endpoints/LogoutEndpoint.cs
@@ -0,0 +1,70 @@
+/*
+* Copyright (c) 2024 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Essentials.Auth.Auth0
+* File: LogoutEndpoint.cs
+*
+* LogoutEndpoint.cs is part of VNLib.Plugins.Essentials.Auth.Auth0 which is
+* part of the larger VNLib collection of libraries and utilities.
+*
+* VNLib.Plugins.Essentials.Auth.Auth0 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.Auth.Auth0 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 VNLib.Utils;
+
+using VNLib.Plugins.Extensions.Loading;
+using VNLib.Plugins.Essentials.Accounts;
+using VNLib.Plugins.Essentials.Endpoints;
+using VNLib.Plugins.Essentials.Extensions;
+
+
+namespace VNLib.Plugins.Essentials.Auth.Auth0.Endpoints
+{
+ [ConfigurationName(Auth0Portal.ConfigKey)]
+ internal sealed class LogoutEndpoint : ProtectedWebEndpoint
+ {
+ private readonly IAsyncLazy<string> ReturnUrl;
+
+ public LogoutEndpoint(PluginBase plugin, IConfigScope config)
+ {
+ string returnToUrl = config.GetRequiredProperty("return_to_url", p => p.GetString()!);
+ string logoutUrl = config.GetRequiredProperty("logout_url", p => p.GetString()!);
+ string path = config.GetRequiredProperty("path", p => p.GetString()!);
+
+ InitPathAndLog($"{path}/logout", plugin.Log);
+
+ //Build the return url once the client id is available
+ ReturnUrl = plugin.GetSecretAsync("auth0_client_id").ToLazy(sr =>
+ {
+ return $"{logoutUrl}?client_id={sr.Result.ToString()}&returnTo={returnToUrl}";
+ });
+ }
+
+ protected override ERRNO PreProccess(HttpEntity entity)
+ {
+ //Client required to be fully authorized
+ return base.PreProccess(entity)
+ && entity.IsClientAuthorized(AuthorzationCheckLevel.Critical);
+ }
+
+ protected override VfReturnType Post(HttpEntity entity)
+ {
+ //Invalidate the login before redirecting the client
+ entity.InvalidateLogin();
+ entity.Redirect(RedirectType.Temporary, ReturnUrl.Value);
+ return VfReturnType.VirtualSkip;
+ }
+ }
+} \ No newline at end of file
diff --git a/plugins/providers/VNLib.Plugins.Essentials.Auth.Auth0/src/VNLib.Plugins.Essentials.Auth.Auth0.csproj b/plugins/providers/VNLib.Plugins.Essentials.Auth.Auth0/src/VNLib.Plugins.Essentials.Auth.Auth0.csproj
new file mode 100644
index 0000000..2beb64f
--- /dev/null
+++ b/plugins/providers/VNLib.Plugins.Essentials.Auth.Auth0/src/VNLib.Plugins.Essentials.Auth.Auth0.csproj
@@ -0,0 +1,48 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <Nullable>enable</Nullable>
+ <TargetFramework>net8.0</TargetFramework>
+ <RootNamespace>VNLib.Plugins.Essentials.Auth.Auth0</RootNamespace>
+ <AssemblyName>VNLib.Plugins.Essentials.Auth.Auth0</AssemblyName>
+ <GenerateDocumentationFile>True</GenerateDocumentationFile>
+ <AnalysisLevel>latest-all</AnalysisLevel>
+ <NeutralLanguage>en-US</NeutralLanguage>
+ <EnableDynamicLoading>true</EnableDynamicLoading>
+ </PropertyGroup>
+
+ <PropertyGroup>
+ <Authors>Vaughn Nugent</Authors>
+ <Company>Vaughn Nugent</Company>
+ <Product>VNLib.Plugins.Essentials.Auth.Auth0</Product>
+ <PackageId>VNLib.Plugins.Essentials.Auth.Auth0</PackageId>
+ <Description>A runtime asset library that adds Auth0 social OAuth autentication integration with Auth.Social plugin library</Description>
+ <Copyright>Copyright © 2024 Vaughn Nugent</Copyright>
+ <PackageProjectUrl>https://www.vaughnnugent.com/resources/software/modules/Plugins.Essentials</PackageProjectUrl>
+ <RepositoryUrl>https://Auth0.com/VnUgE/Plugins.Essentials/tree/master/plugins/providers/VNLib.Plugins.Essentials.Auth.Auth0</RepositoryUrl>
+ <PackageReadmeFile>README.md</PackageReadmeFile>
+ <PackageLicenseFile>LICENSE</PackageLicenseFile>
+ <PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance>
+ </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>
+ <ProjectReference Include="..\..\..\..\plugins\VNLib.Plugins.Essentials.Auth.Social\src\VNLib.Plugins.Essentials.Auth.Social.csproj" />
+ </ItemGroup>
+
+ <Target Condition="'$(BuildingInsideVisualStudio)' == true" Name="PostBuild" AfterTargets="PostBuildEvent">
+ <Exec Command="start xcopy &quot;$(TargetDir)&quot; &quot;$(SolutionDir)devplugins\RuntimeAssets\$(TargetName)&quot; /E /Y /R" />
+ </Target>
+
+</Project>
diff --git a/plugins/providers/VNLib.Plugins.Essentials.Auth.Discord/README.md b/plugins/providers/VNLib.Plugins.Essentials.Auth.Discord/README.md
new file mode 100644
index 0000000..fd523ad
--- /dev/null
+++ b/plugins/providers/VNLib.Plugins.Essentials.Auth.Discord/README.md
@@ -0,0 +1,15 @@
+# VNLib.Plugins.Essentials.Auth.Discord
+*A runtime asset library that provides Discord OAuth authentication to your application that is using the [Auth.Social](../../VNLib.Plugins.Essentials.Auth.Social) plugin*
+
+## Builds
+Debug build w/ symbols & xml docs, release builds, NuGet packages, and individually packaged source code are available on my website (link below).
+
+## Docs and Guides
+Documentation, specifications, and setup guides are available on my website.
+
+[Docs and Articles](https://www.vaughnnugent.com/resources/software/articles?tags=docs,_VNLib.Plugins.Essentials.Auth.Discord)
+[Builds and Source](https://www.vaughnnugent.com/resources/software/modules/Plugins.Essentials)
+[Nuget Feeds](https://www.vaughnnugent.com/resources/software/modules)
+
+## License
+Source files in for this project are licensed to you under the GNU Affero General Public License (or any later version). See the LICENSE files for more information. \ No newline at end of file
diff --git a/plugins/providers/VNLib.Plugins.Essentials.Auth.Discord/build.readme.md b/plugins/providers/VNLib.Plugins.Essentials.Auth.Discord/build.readme.md
new file mode 100644
index 0000000..3098245
--- /dev/null
+++ b/plugins/providers/VNLib.Plugins.Essentials.Auth.Discord/build.readme.md
@@ -0,0 +1,11 @@
+VNLib.Plugins.Essentials.Auth.Discord Copyright © 2024 Vaughn Nugent
+Contact: Vaughn Nugent vnpublic[at]proton.me
+
+LICENSE:
+You should have recieved a copy of the GNU Affero General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+In the future there will be more information for installtion in this file, but for now go to my website
+
+https://www.vaughnnugent.com/resources/software/articles?tags=docs,_VNLib.Plugins.Essentials.Auth.Discord
+
+Thank you! \ No newline at end of file
diff --git a/plugins/providers/VNLib.Plugins.Essentials.Auth.Discord/src/DiscordPortal.cs b/plugins/providers/VNLib.Plugins.Essentials.Auth.Discord/src/DiscordPortal.cs
new file mode 100644
index 0000000..5b0503e
--- /dev/null
+++ b/plugins/providers/VNLib.Plugins.Essentials.Auth.Discord/src/DiscordPortal.cs
@@ -0,0 +1,65 @@
+/*
+* Copyright (c) 2024 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Essentials.Auth.Discord
+* File: DiscordPortal.cs
+*
+* DiscordPortal.cs is part of VNLib.Plugins.Essentials.Auth.Discord which is
+* part of the larger VNLib collection of libraries and utilities.
+*
+* VNLib.Plugins.Essentials.Auth.Discord 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.Auth.Discord 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.Plugins.Extensions.Loading;
+using VNLib.Plugins.Extensions.Loading.Routing;
+using VNLib.Plugins.Essentials.Auth.Social;
+
+using VNLib.Plugins.Essentials.Auth.Discord.Endpoints;
+
+namespace VNLib.Plugins.Essentials.Auth.Discord
+{
+
+ [ServiceExport]
+ [ConfigurationName(ConfigKey)]
+ public sealed class DiscordPortal : IOAuthProvider
+ {
+ internal const string ConfigKey = "discord";
+
+ private readonly DiscordOauth _loginEndpoint;
+
+ public DiscordPortal(PluginBase plugin, IConfigScope config)
+ {
+ //Init the login endpoint
+ _loginEndpoint = plugin.Route<DiscordOauth>();
+ }
+
+ ///<inheritdoc/>
+ public SocialOAuthPortal[] GetPortals()
+ {
+
+ //Return the Discord portal
+ return [
+ new SocialOAuthPortal(
+ ConfigKey,
+ _loginEndpoint,
+ null
+ )
+ ];
+
+ }
+ }
+}
diff --git a/plugins/providers/VNLib.Plugins.Essentials.Auth.Discord/src/Endpoint/DiscordOauth.cs b/plugins/providers/VNLib.Plugins.Essentials.Auth.Discord/src/Endpoint/DiscordOauth.cs
new file mode 100644
index 0000000..4aa7a64
--- /dev/null
+++ b/plugins/providers/VNLib.Plugins.Essentials.Auth.Discord/src/Endpoint/DiscordOauth.cs
@@ -0,0 +1,148 @@
+/*
+* Copyright (c) 2024 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Essentials.Auth.Discord
+* File: DiscordOauth.cs
+*
+* DiscordOauth.cs is part of VNLib.Plugins.Essentials.Auth.Discord which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Plugins.Essentials.Auth.Discord 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.Auth.Discord 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;
+using VNLib.Plugins.Essentials.Auth.Social;
+
+namespace VNLib.Plugins.Essentials.Auth.Discord.Endpoints
+{
+ [ConfigurationName(DiscordPortal.ConfigKey)]
+ 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/providers/VNLib.Plugins.Essentials.Auth.Discord/src/VNLib.Plugins.Essentials.Auth.Discord.csproj b/plugins/providers/VNLib.Plugins.Essentials.Auth.Discord/src/VNLib.Plugins.Essentials.Auth.Discord.csproj
new file mode 100644
index 0000000..d64ebe6
--- /dev/null
+++ b/plugins/providers/VNLib.Plugins.Essentials.Auth.Discord/src/VNLib.Plugins.Essentials.Auth.Discord.csproj
@@ -0,0 +1,48 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <Nullable>enable</Nullable>
+ <TargetFramework>net8.0</TargetFramework>
+ <RootNamespace>VNLib.Plugins.Essentials.Auth.Discord</RootNamespace>
+ <AssemblyName>VNLib.Plugins.Essentials.Auth.Discord</AssemblyName>
+ <GenerateDocumentationFile>True</GenerateDocumentationFile>
+ <AnalysisLevel>latest-all</AnalysisLevel>
+ <NeutralLanguage>en-US</NeutralLanguage>
+ <EnableDynamicLoading>true</EnableDynamicLoading>
+ </PropertyGroup>
+
+ <PropertyGroup>
+ <Authors>Vaughn Nugent</Authors>
+ <Company>Vaughn Nugent</Company>
+ <Product>VNLib.Plugins.Essentials.Auth.Discord</Product>
+ <PackageId>VNLib.Plugins.Essentials.Auth.Discord</PackageId>
+ <Description>A runtime asset library that adds Discord social OAuth autentication integration with Auth.Social plugin library</Description>
+ <Copyright>Copyright © 2024 Vaughn Nugent</Copyright>
+ <PackageProjectUrl>https://www.vaughnnugent.com/resources/software/modules/Plugins.Essentials</PackageProjectUrl>
+ <RepositoryUrl>https://Discord.com/VnUgE/Plugins.Essentials/tree/master/plugins/providers/VNLib.Plugins.Essentials.Auth.Discord</RepositoryUrl>
+ <PackageReadmeFile>README.md</PackageReadmeFile>
+ <PackageLicenseFile>LICENSE</PackageLicenseFile>
+ <PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance>
+ </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>
+ <ProjectReference Include="..\..\..\..\plugins\VNLib.Plugins.Essentials.Auth.Social\src\VNLib.Plugins.Essentials.Auth.Social.csproj" />
+ </ItemGroup>
+
+ <Target Condition="'$(BuildingInsideVisualStudio)' == true" Name="PostBuild" AfterTargets="PostBuildEvent">
+ <Exec Command="start xcopy &quot;$(TargetDir)&quot; &quot;$(SolutionDir)devplugins\RuntimeAssets\$(TargetName)&quot; /E /Y /R" />
+ </Target>
+
+</Project>
diff --git a/plugins/providers/VNLib.Plugins.Essentials.Auth.Github/README.md b/plugins/providers/VNLib.Plugins.Essentials.Auth.Github/README.md
new file mode 100644
index 0000000..c4c91dd
--- /dev/null
+++ b/plugins/providers/VNLib.Plugins.Essentials.Auth.Github/README.md
@@ -0,0 +1,15 @@
+# VNLib.Plugins.Essentials.Auth.Github
+*A runtime asset library that provides Github OAuth authentication to your application that is using the [Auth.Social](../../VNLib.Plugins.Essentials.Auth.Social) plugin*
+
+## Builds
+Debug build w/ symbols & xml docs, release builds, NuGet packages, and individually packaged source code are available on my website (link below).
+
+## Docs and Guides
+Documentation, specifications, and setup guides are available on my website.
+
+[Docs and Articles](https://www.vaughnnugent.com/resources/software/articles?tags=docs,_VNLib.Plugins.Essentials.Auth.Github)
+[Builds and Source](https://www.vaughnnugent.com/resources/software/modules/Plugins.Essentials)
+[Nuget Feeds](https://www.vaughnnugent.com/resources/software/modules)
+
+## License
+Source files in for this project are licensed to you under the GNU Affero General Public License (or any later version). See the LICENSE files for more information. \ No newline at end of file
diff --git a/plugins/providers/VNLib.Plugins.Essentials.Auth.Github/build.readme.md b/plugins/providers/VNLib.Plugins.Essentials.Auth.Github/build.readme.md
new file mode 100644
index 0000000..3b7c356
--- /dev/null
+++ b/plugins/providers/VNLib.Plugins.Essentials.Auth.Github/build.readme.md
@@ -0,0 +1,11 @@
+VNLib.Plugins.Essentials.Auth.Github Copyright © 2024 Vaughn Nugent
+Contact: Vaughn Nugent vnpublic[at]proton.me
+
+LICENSE:
+You should have recieved a copy of the GNU Affero General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+In the future there will be more information for installtion in this file, but for now go to my website
+
+https://www.vaughnnugent.com/resources/software/articles?tags=docs,_VNLib.Plugins.Essentials.Auth.Github
+
+Thank you! \ No newline at end of file
diff --git a/plugins/providers/VNLib.Plugins.Essentials.Auth.Github/src/Endpoint/GitHubOauth.cs b/plugins/providers/VNLib.Plugins.Essentials.Auth.Github/src/Endpoint/GitHubOauth.cs
new file mode 100644
index 0000000..b23188c
--- /dev/null
+++ b/plugins/providers/VNLib.Plugins.Essentials.Auth.Github/src/Endpoint/GitHubOauth.cs
@@ -0,0 +1,213 @@
+/*
+* Copyright (c) 2024 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Essentials.Auth.Github
+* File: GitHubOauth.cs
+*
+* GitHubOauth.cs is part ofVNLib.Plugins.Essentials.Auth.Githubwhich is part of the larger
+* VNLib collection of libraries and utilities.
+*
+*VNLib.Plugins.Essentials.Auth.Githubis 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.Auth.Githubis 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.Utils.Logging;
+using VNLib.Plugins.Essentials.Accounts;
+using VNLib.Plugins.Extensions.Loading;
+using VNLib.Net.Rest.Client.Construction;
+using VNLib.Plugins.Essentials.Auth.Social;
+
+namespace VNLib.Plugins.Essentials.Auth.Github.Endpoints
+{
+ [ConfigurationName(GithubPortal.ConfigKey)]
+ 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/providers/VNLib.Plugins.Essentials.Auth.Github/src/GithubPortal.cs b/plugins/providers/VNLib.Plugins.Essentials.Auth.Github/src/GithubPortal.cs
new file mode 100644
index 0000000..99b0ebf
--- /dev/null
+++ b/plugins/providers/VNLib.Plugins.Essentials.Auth.Github/src/GithubPortal.cs
@@ -0,0 +1,65 @@
+/*
+* Copyright (c) 2024 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Essentials.Auth.Github
+* File: GithubPortal.cs
+*
+* GithubPortal.cs is part ofVNLib.Plugins.Essentials.Auth.Githubwhich is
+* part of the larger VNLib collection of libraries and utilities.
+*
+*VNLib.Plugins.Essentials.Auth.Githubis 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.Auth.Githubis 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.Plugins.Extensions.Loading;
+using VNLib.Plugins.Extensions.Loading.Routing;
+using VNLib.Plugins.Essentials.Auth.Social;
+
+using VNLib.Plugins.Essentials.Auth.Github.Endpoints;
+
+namespace VNLib.Plugins.Essentials.Auth.Github
+{
+
+ [ServiceExport]
+ [ConfigurationName(ConfigKey)]
+ public sealed class GithubPortal : IOAuthProvider
+ {
+ internal const string ConfigKey = "github";
+
+ private readonly GitHubOauth _loginEndpoint;
+
+ public GithubPortal(PluginBase plugin, IConfigScope config)
+ {
+ //Init the login endpoint
+ _loginEndpoint = plugin.Route<GitHubOauth>();
+ }
+
+ ///<inheritdoc/>
+ public SocialOAuthPortal[] GetPortals()
+ {
+
+ //Return the github portal
+ return [
+ new SocialOAuthPortal(
+ ConfigKey,
+ _loginEndpoint,
+ null
+ )
+ ];
+
+ }
+ }
+}
diff --git a/plugins/providers/VNLib.Plugins.Essentials.Auth.Github/src/VNLib.Plugins.Essentials.Auth.Github.csproj b/plugins/providers/VNLib.Plugins.Essentials.Auth.Github/src/VNLib.Plugins.Essentials.Auth.Github.csproj
new file mode 100644
index 0000000..e6643f9
--- /dev/null
+++ b/plugins/providers/VNLib.Plugins.Essentials.Auth.Github/src/VNLib.Plugins.Essentials.Auth.Github.csproj
@@ -0,0 +1,48 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <Nullable>enable</Nullable>
+ <TargetFramework>net8.0</TargetFramework>
+ <RootNamespace>VNLib.Plugins.Essentials.Auth.Github</RootNamespace>
+ <AssemblyName>VNLib.Plugins.Essentials.Auth.Github</AssemblyName>
+ <GenerateDocumentationFile>True</GenerateDocumentationFile>
+ <AnalysisLevel>latest-all</AnalysisLevel>
+ <NeutralLanguage>en-US</NeutralLanguage>
+ <EnableDynamicLoading>true</EnableDynamicLoading>
+ </PropertyGroup>
+
+ <PropertyGroup>
+ <Authors>Vaughn Nugent</Authors>
+ <Company>Vaughn Nugent</Company>
+ <Product>VNLib.Plugins.Essentials.Auth.Github</Product>
+ <PackageId>VNLib.Plugins.Essentials.Auth.Github</PackageId>
+ <Description>A runtime asset library that adds Github social OAuth autentication integration with Auth.Social plugin library</Description>
+ <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/providers/VNLib.Plugins.Essentials.Auth.Github</RepositoryUrl>
+ <PackageReadmeFile>README.md</PackageReadmeFile>
+ <PackageLicenseFile>LICENSE</PackageLicenseFile>
+ <PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance>
+ </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>
+ <ProjectReference Include="..\..\..\..\plugins\VNLib.Plugins.Essentials.Auth.Social\src\VNLib.Plugins.Essentials.Auth.Social.csproj" />
+ </ItemGroup>
+
+ <Target Condition="'$(BuildingInsideVisualStudio)' == true" Name="PostBuild" AfterTargets="PostBuildEvent">
+ <Exec Command="start xcopy &quot;$(TargetDir)&quot; &quot;$(SolutionDir)devplugins\RuntimeAssets\$(TargetName)&quot; /E /Y /R" />
+ </Target>
+
+</Project>