aboutsummaryrefslogtreecommitdiff
path: root/plugins/VNLib.Plugins.Essentials.Auth.Social/src
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/VNLib.Plugins.Essentials.Auth.Social/src')
-rw-r--r--plugins/VNLib.Plugins.Essentials.Auth.Social/src/ClientClaimManager.cs2
-rw-r--r--plugins/VNLib.Plugins.Essentials.Auth.Social/src/LoginClaim.cs4
-rw-r--r--plugins/VNLib.Plugins.Essentials.Auth.Social/src/LoginUriBuilder.cs2
-rw-r--r--plugins/VNLib.Plugins.Essentials.Auth.Social/src/OAuthSiteAdapter.cs3
-rw-r--r--plugins/VNLib.Plugins.Essentials.Auth.Social/src/OauthClientConfig.cs38
-rw-r--r--plugins/VNLib.Plugins.Essentials.Auth.Social/src/SocialOauthBase.cs18
-rw-r--r--plugins/VNLib.Plugins.Essentials.Auth.Social/src/openid/OpenIdPortalConfig.cs46
-rw-r--r--plugins/VNLib.Plugins.Essentials.Auth.Social/src/openid/OpenIdProviderValidator.cs70
-rw-r--r--plugins/VNLib.Plugins.Essentials.Auth.Social/src/openid/OpenIdResolver.cs67
9 files changed, 217 insertions, 33 deletions
diff --git a/plugins/VNLib.Plugins.Essentials.Auth.Social/src/ClientClaimManager.cs b/plugins/VNLib.Plugins.Essentials.Auth.Social/src/ClientClaimManager.cs
index d078964..033f577 100644
--- a/plugins/VNLib.Plugins.Essentials.Auth.Social/src/ClientClaimManager.cs
+++ b/plugins/VNLib.Plugins.Essentials.Auth.Social/src/ClientClaimManager.cs
@@ -120,7 +120,7 @@ namespace VNLib.Plugins.Essentials.Auth.Social
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);
+ entity.Session[SESSION_SIG_KEY_NAME] = VnEncoding.Base64UrlEncode(sigKey, false);
//Clear the signing key
MemoryUtil.InitializeBlock(sigKey);
diff --git a/plugins/VNLib.Plugins.Essentials.Auth.Social/src/LoginClaim.cs b/plugins/VNLib.Plugins.Essentials.Auth.Social/src/LoginClaim.cs
index 30a51fa..5d1b2dc 100644
--- a/plugins/VNLib.Plugins.Essentials.Auth.Social/src/LoginClaim.cs
+++ b/plugins/VNLib.Plugins.Essentials.Auth.Social/src/LoginClaim.cs
@@ -65,11 +65,11 @@ namespace VNLib.Plugins.Essentials.Auth.Social
RandomHash.GetRandomBytes(buffer.Span);
//Base32-Encode nonce and save it
- Nonce = VnEncoding.ToBase64UrlSafeString(buffer.Span, false);
+ Nonce = VnEncoding.Base64UrlEncode(buffer.Span, includePadding: false);
}
finally
{
- MemoryUtil.InitializeBlock(buffer.Span);
+ MemoryUtil.InitializeBlock(ref buffer.GetReference(), buffer.IntLength);
}
}
}
diff --git a/plugins/VNLib.Plugins.Essentials.Auth.Social/src/LoginUriBuilder.cs b/plugins/VNLib.Plugins.Essentials.Auth.Social/src/LoginUriBuilder.cs
index 4ed6ffd..fc94d5a 100644
--- a/plugins/VNLib.Plugins.Essentials.Auth.Social/src/LoginUriBuilder.cs
+++ b/plugins/VNLib.Plugins.Essentials.Auth.Social/src/LoginUriBuilder.cs
@@ -96,7 +96,7 @@ namespace VNLib.Plugins.Essentials.Auth.Social
ForwardOnlyWriter<char> writer = new(charBuffer);
//Append the config redirect path
- writer.Append(Config.AccessCodeUrl.OriginalString);
+ writer.Append(Config.AuthorizationUrl.OriginalString);
//begin query arguments
writer.AppendSmall("&client_id=");
writer.Append(Config.ClientID.Value);
diff --git a/plugins/VNLib.Plugins.Essentials.Auth.Social/src/OAuthSiteAdapter.cs b/plugins/VNLib.Plugins.Essentials.Auth.Social/src/OAuthSiteAdapter.cs
index 37dd7e0..ef90185 100644
--- a/plugins/VNLib.Plugins.Essentials.Auth.Social/src/OAuthSiteAdapter.cs
+++ b/plugins/VNLib.Plugins.Essentials.Auth.Social/src/OAuthSiteAdapter.cs
@@ -22,6 +22,7 @@
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
+using System;
using System.Net;
using System.Text;
using System.Threading;
@@ -49,7 +50,7 @@ namespace VNLib.Plugins.Essentials.Auth.Social
{
RestClientOptions poolOptions = new()
{
- MaxTimeout = 5000,
+ Timeout = TimeSpan.FromSeconds(5),
AutomaticDecompression = DecompressionMethods.All,
Encoding = Encoding.UTF8,
//disable redirects, api should not redirect
diff --git a/plugins/VNLib.Plugins.Essentials.Auth.Social/src/OauthClientConfig.cs b/plugins/VNLib.Plugins.Essentials.Auth.Social/src/OauthClientConfig.cs
index a3b43ad..7efd4df 100644
--- a/plugins/VNLib.Plugins.Essentials.Auth.Social/src/OauthClientConfig.cs
+++ b/plugins/VNLib.Plugins.Essentials.Auth.Social/src/OauthClientConfig.cs
@@ -29,6 +29,8 @@ using System.Collections.Generic;
using VNLib.Utils.Logging;
using VNLib.Utils.Extensions;
using VNLib.Plugins.Extensions.Loading;
+using VNLib.Plugins.Essentials.Auth.Social.openid;
+using VNLib.Plugins.Extensions.Loading.Configuration;
namespace VNLib.Plugins.Essentials.Auth.Social
{
@@ -42,25 +44,25 @@ namespace VNLib.Plugins.Essentials.Auth.Social
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}");
+ EndpointPath = config.GetRequiredProperty("path", p => p.GetString()!);
+ AccountOrigin = config.GetRequiredProperty("account_origin", p => p.GetString()!);
+
+ OpenIdPortalConfig portalConf = config.Deserialze<OpenIdPortalConfig>()!;
+
+ Validate.NotNull(portalConf.AuthorizationEndpoint, $"Missing authorization endpoint for {config.ScopeName}");
+ Validate.NotNull(portalConf.TokenEndpoint, $"Missing token endpoint for {config.ScopeName}");
+ Validate.NotNull(portalConf.UserDataEndpoint, $"Missing user-data endpoint for {config.ScopeName}");
+
//Create the uris
- AccessCodeUrl = new(authUrl);
- AccessTokenUrl = new(tokenUrl);
- UserDataUrl = new(userUrl);
+ AuthorizationUrl = new(portalConf.AuthorizationEndpoint);
+ AccessTokenUrl = new(portalConf.TokenEndpoint);
+ UserDataUrl = new(portalConf.UserDataEndpoint);
- 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);
+ AllowForLocalAccounts = config.GetValueOrDefault("allow_for_local", p => p.GetBoolean(), false);
+ AllowRegistration = config.GetValueOrDefault("allow_registration", p => p.GetBoolean(), false);
+ NonceByteSize = config.GetRequiredProperty("nonce_size", p => p.GetUInt32());
+ RandomPasswordSize = config.GetRequiredProperty("password_size", p => p.GetInt32());
+ InitClaimValidFor = config.GetRequiredProperty("claim_valid_for_sec", p => p.GetTimeSpan(TimeParseType.Seconds));
//Setup async lazy loaders for secrets
ClientID = plugin.GetSecretAsync($"{config.ScopeName}_client_id")
@@ -101,7 +103,7 @@ namespace VNLib.Plugins.Essentials.Auth.Social
/// The URL to redirect the user to the OAuth2 service
/// to begin the authentication process
/// </summary>
- public Uri AccessCodeUrl { get; }
+ public Uri AuthorizationUrl { get; }
/// <summary>
/// The remote endoint to exchange codes for access tokens
diff --git a/plugins/VNLib.Plugins.Essentials.Auth.Social/src/SocialOauthBase.cs b/plugins/VNLib.Plugins.Essentials.Auth.Social/src/SocialOauthBase.cs
index 91bf147..f381fb8 100644
--- a/plugins/VNLib.Plugins.Essentials.Auth.Social/src/SocialOauthBase.cs
+++ b/plugins/VNLib.Plugins.Essentials.Auth.Social/src/SocialOauthBase.cs
@@ -63,7 +63,11 @@ namespace VNLib.Plugins.Essentials.Auth.Social
const string AUTH_GRANT_SESSION_NAME = "auth";
const string SESSION_TOKEN_KEY_NAME = "soa.tkn";
const string CLAIM_COOKIE_NAME = "extern-claim";
-
+
+ private static readonly IValidator<LoginClaim> ClaimValidator = GetClaimValidator();
+ private static readonly IValidator<string> NonceValidator = GetNonceValidator();
+ private static readonly AccountDataValidator AccountDataValidator = new ();
+
/// <summary>
/// The client configuration struct passed during base class construction
@@ -81,19 +85,13 @@ namespace VNLib.Plugins.Essentials.Auth.Social
/// <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;
+ protected IUserManager Users { get; }
+
+
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);
diff --git a/plugins/VNLib.Plugins.Essentials.Auth.Social/src/openid/OpenIdPortalConfig.cs b/plugins/VNLib.Plugins.Essentials.Auth.Social/src/openid/OpenIdPortalConfig.cs
new file mode 100644
index 0000000..97736ab
--- /dev/null
+++ b/plugins/VNLib.Plugins.Essentials.Auth.Social/src/openid/OpenIdPortalConfig.cs
@@ -0,0 +1,46 @@
+/*
+* Copyright (c) 2024 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Essentials.Auth.Social
+* File: OpenIdPortalConfig.cs
+*
+* OpenIdPortalConfig.cs is part of VNLib.Plugins.Essentials.Auth.Social which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Plugins.Essentials.Auth.Social 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.Social 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.Auth.Social.openid
+{
+ public sealed class OpenIdPortalConfig
+ {
+ [JsonPropertyName("issuer")]
+ public string IssuerUrl { get; set; }
+
+ [JsonPropertyName("authorization_endpoint")]
+ public string AuthorizationEndpoint { get; set; }
+
+ [JsonPropertyName("token_endpoint")]
+ public string TokenEndpoint { get; set; }
+
+ [JsonPropertyName("userinfo_endpoint")]
+ public string UserDataEndpoint { get; set; }
+
+ [JsonPropertyName("jwks_uri")]
+ public string KeysEndpoint { get; set; }
+ }
+}
diff --git a/plugins/VNLib.Plugins.Essentials.Auth.Social/src/openid/OpenIdProviderValidator.cs b/plugins/VNLib.Plugins.Essentials.Auth.Social/src/openid/OpenIdProviderValidator.cs
new file mode 100644
index 0000000..bde0a88
--- /dev/null
+++ b/plugins/VNLib.Plugins.Essentials.Auth.Social/src/openid/OpenIdProviderValidator.cs
@@ -0,0 +1,70 @@
+/*
+* Copyright (c) 2024 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Essentials.Auth.Social
+* File: OauthClientConfig.cs
+*
+* OauthClientConfig.cs is part of VNLib.Plugins.Essentials.Auth.Social which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Plugins.Essentials.Auth.Social 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.Social 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;
+
+namespace VNLib.Plugins.Essentials.Auth.Social.openid
+{
+ public sealed class OpenIdProviderValidator : AbstractValidator<OpenIdPortalConfig>
+ {
+ public OpenIdProviderValidator(string discoveryUrl)
+ {
+ /*
+ * Discovery url will be compared to make sure the
+ * host is on the same domain as the issuer
+ */
+ Uri discUrl = new(discoveryUrl);
+
+ RuleFor(i => i.IssuerUrl)
+ .Matches($"^{discUrl.Scheme}://{discUrl.Host}")
+ .WithMessage("Issuer must be on the same domain as the discovery url");
+
+ RuleFor(i => i.AuthorizationEndpoint)
+ .NotEmpty()
+ .WithMessage("Authorization endpoint is required")
+ .Matches($"^{discUrl.Scheme}://{discUrl.Host}")
+ .WithMessage("Authorization endpoint must be on the same domain as the discovery url");
+
+ RuleFor(i => i.TokenEndpoint)
+ .NotEmpty()
+ .WithMessage("Token endpoint is required")
+ .Matches($"^{discUrl.Scheme}://{discUrl.Host}")
+ .WithMessage("Token endpoint must be on the same domain as the discovery url");
+
+ RuleFor(i => i.UserDataEndpoint)
+ .NotEmpty()
+ .WithMessage("User data endpoint is required")
+ .Matches($"^{discUrl.Scheme}://{discUrl.Host}")
+ .WithMessage("User data endpoint must be on the same domain as the discovery url");
+
+ RuleFor(i => i.KeysEndpoint)
+ .NotEmpty()
+ .WithMessage("Keys endpoint is required")
+ .Matches($"^{discUrl.Scheme}://{discUrl.Host}")
+ .WithMessage("Keys endpoint must be on the same domain as the discovery url");
+ }
+ }
+}
diff --git a/plugins/VNLib.Plugins.Essentials.Auth.Social/src/openid/OpenIdResolver.cs b/plugins/VNLib.Plugins.Essentials.Auth.Social/src/openid/OpenIdResolver.cs
new file mode 100644
index 0000000..652b622
--- /dev/null
+++ b/plugins/VNLib.Plugins.Essentials.Auth.Social/src/openid/OpenIdResolver.cs
@@ -0,0 +1,67 @@
+/*
+* Copyright (c) 2024 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Essentials.Auth.Social
+* File: OauthClientConfig.cs
+*
+* OauthClientConfig.cs is part of VNLib.Plugins.Essentials.Auth.Social which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Plugins.Essentials.Auth.Social 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.Social 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.Threading.Tasks;
+using System.Threading;
+using VNLib.Net.Rest.Client.Construction;
+using RestSharp;
+
+namespace VNLib.Plugins.Essentials.Auth.Social.openid
+{
+ /// <summary>
+ /// Resolves the openid connect configuration from a discovery url
+ /// </summary>
+ public sealed class OpenIdResolver
+ {
+ private readonly RestSiteAdapterBase _defaultAdapter = RestSiteAdapterBase.CreateSimpleAdapter();
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OpenIdResolver"/> class
+ /// </summary>
+ public OpenIdResolver()
+ {
+ _defaultAdapter.DefineSingleEndpoint()
+ .WithEndpoint<DiscoveryRequest>()
+ .WithMethod(Method.Get)
+ .WithUrl(m => m.DiscoUrl)
+ .WithHeader("Accept", "application/json")
+ .OnResponse((r, rr) => rr.ThrowIfError());
+ }
+
+ /// <summary>
+ /// Resolves the openid connect configuration from the discovery url
+ /// </summary>
+ /// <param name="discoveryUrl">The openid connect discovery url</param>
+ /// <param name="cancellation">A token to cancel the resolution operation</param>
+ /// <returns>A task that resolves the openid connect configuration data</returns>
+ public Task<OpenIdPortalConfig?> ResolveAsync(string discoveryUrl, CancellationToken cancellation)
+ {
+ return _defaultAdapter.ExecuteAsync(entity: new DiscoveryRequest(discoveryUrl), cancellation)
+ .AsJson<OpenIdPortalConfig>();
+ }
+
+ sealed record class DiscoveryRequest(string DiscoUrl)
+ { }
+ }
+}