aboutsummaryrefslogtreecommitdiff
path: root/plugins/providers/VNLib.Plugins.Essentials.Auth.Github
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/providers/VNLib.Plugins.Essentials.Auth.Github')
-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
5 files changed, 352 insertions, 0 deletions
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>