aboutsummaryrefslogtreecommitdiff
path: root/plugins
diff options
context:
space:
mode:
authorLibravatar vnugent <public@vaughnnugent.com>2023-03-25 14:25:21 -0400
committerLibravatar vnugent <public@vaughnnugent.com>2023-03-25 14:25:21 -0400
commit1dc1ac2e53f25528aacd1510da928d5f56e3dad7 (patch)
treece8ed59ec116fe615f4bdb70099ac641da3f0153 /plugins
parent78901f761e5b8358d02d1841bee4c60d97c94760 (diff)
Defer cors to host/middleware/user code
Diffstat (limited to 'plugins')
-rw-r--r--plugins/VNLib.Plugins.Essentials.Accounts/src/Endpoints/LoginEndpoint.cs14
-rw-r--r--plugins/VNLib.Plugins.Essentials.Accounts/src/Endpoints/PkiLoginEndpoint.cs12
-rw-r--r--plugins/VNLib.Plugins.Essentials.Accounts/src/SecurityProvider/AccountSecProvider.cs77
-rw-r--r--plugins/VNLib.Plugins.Essentials.Content.Routing/src/Router.cs6
-rw-r--r--plugins/VNLib.Plugins.Essentials.SocialOauth/src/SocialOauthBase.cs9
5 files changed, 84 insertions, 34 deletions
diff --git a/plugins/VNLib.Plugins.Essentials.Accounts/src/Endpoints/LoginEndpoint.cs b/plugins/VNLib.Plugins.Essentials.Accounts/src/Endpoints/LoginEndpoint.cs
index ea6bab1..062ed93 100644
--- a/plugins/VNLib.Plugins.Essentials.Accounts/src/Endpoints/LoginEndpoint.cs
+++ b/plugins/VNLib.Plugins.Essentials.Accounts/src/Endpoints/LoginEndpoint.cs
@@ -53,7 +53,7 @@ namespace VNLib.Plugins.Essentials.Accounts.Endpoints
[ConfigurationName("login_endpoint")]
internal sealed class LoginEndpoint : UnprotectedWebEndpoint
{
- public const string INVALID_MESSAGE = "Please check your email or password.";
+ public const string INVALID_MESSAGE = "Please check your email or password. You may get locked out.";
public const string LOCKED_ACCOUNT_MESSAGE = "You have been timed out, please try again later";
public const string MFA_ERROR_MESSAGE = "Invalid or expired request.";
@@ -159,7 +159,7 @@ namespace VNLib.Plugins.Essentials.Accounts.Endpoints
}
//Inc failed login count
- user.FailedLoginIncrement();
+ user.FailedLoginIncrement(entity.RequestedTimeUtc);
webm.Result = INVALID_MESSAGE;
Cleanup:
@@ -181,8 +181,10 @@ namespace VNLib.Plugins.Essentials.Accounts.Endpoints
{
return false;
}
- //Reset flc for account
- user.FailedLoginCount(0);
+
+ //Reset flc for account, either the user will be authorized, or the mfa will be triggered, but the flc should be reset
+ user.ClearFailedLoginCount();
+
try
{
if (user.Status == UserStatus.Active)
@@ -342,7 +344,7 @@ namespace VNLib.Plugins.Essentials.Accounts.Endpoints
{
webm.Result = "Please check your code.";
//Increment flc and update the user in the store
- user.FailedLoginIncrement();
+ user.FailedLoginIncrement(entity.RequestedTimeUtc);
return;
}
//Valid, complete
@@ -401,7 +403,7 @@ namespace VNLib.Plugins.Essentials.Accounts.Endpoints
if (flc.LastModified.Add(FailedCountTimeout) < now)
{
//clear flc flag
- user.FailedLoginCount(0);
+ user.ClearFailedLoginCount();
return false;
}
diff --git a/plugins/VNLib.Plugins.Essentials.Accounts/src/Endpoints/PkiLoginEndpoint.cs b/plugins/VNLib.Plugins.Essentials.Accounts/src/Endpoints/PkiLoginEndpoint.cs
index 06ccd60..e7c8a86 100644
--- a/plugins/VNLib.Plugins.Essentials.Accounts/src/Endpoints/PkiLoginEndpoint.cs
+++ b/plugins/VNLib.Plugins.Essentials.Accounts/src/Endpoints/PkiLoginEndpoint.cs
@@ -120,7 +120,7 @@ namespace VNLib.Plugins.Essentials.Accounts.Endpoints
JsonWebToken jwt;
try
{
- //We can try to recover the jwt data
+ //We can try to recover the jwt data, if the data is invalid,
jwt = JsonWebToken.Parse(login.LoginJwt);
}
catch (KeyNotFoundException)
@@ -197,7 +197,7 @@ namespace VNLib.Plugins.Essentials.Accounts.Endpoints
if (webm.Assert(user.PKIVerifyUserJWT(jwt, authInfo.KeyId) == true, INVALID_MESSAGE))
{
//increment flc on invalid signature
- user.FailedLoginIncrement();
+ user.FailedLoginIncrement(entity.RequestedTimeUtc);
await user.ReleaseAsync();
entity.CloseResponse(webm);
@@ -399,7 +399,7 @@ namespace VNLib.Plugins.Essentials.Accounts.Endpoints
if (flc.LastModified.AddSeconds(_config.FailedCountTimeoutSec) < now)
{
//clear flc flag
- user.FailedLoginCount(0);
+ user.ClearFailedLoginCount();
return false;
}
@@ -430,7 +430,11 @@ namespace VNLib.Plugins.Essentials.Accounts.Endpoints
RuleFor(l => l.LoginJwt)
.NotEmpty()
.MinimumLength(50)
- .IllegalCharacters();
+ //Token should not contain illegal chars, only base64url + '.'
+ .IllegalCharacters()
+ //Make sure the jwt contains exacly 2 '.' chracters
+ .Must(static l => l.Where(static c => c == '.').Count() == 2)
+ .WithMessage("Your credential is not a valid Json Web Token");
}
}
diff --git a/plugins/VNLib.Plugins.Essentials.Accounts/src/SecurityProvider/AccountSecProvider.cs b/plugins/VNLib.Plugins.Essentials.Accounts/src/SecurityProvider/AccountSecProvider.cs
index 728bc42..eadebcc 100644
--- a/plugins/VNLib.Plugins.Essentials.Accounts/src/SecurityProvider/AccountSecProvider.cs
+++ b/plugins/VNLib.Plugins.Essentials.Accounts/src/SecurityProvider/AccountSecProvider.cs
@@ -153,21 +153,18 @@ namespace VNLib.Plugins.Essentials.Accounts.SecurityProvider
return false;
}
- switch (level)
+ //Reconcile cookies on request
+ ReconcileCookies(entity);
+
+ return level switch
{
//Accept the client token or the cookie as any/medium
- case AuthorzationCheckLevel.Any:
- case AuthorzationCheckLevel.Medium:
- return VerifyLoginCookie(entity) || VerifyClientToken(entity);
-
+ AuthorzationCheckLevel.Any or AuthorzationCheckLevel.Medium => VerifyLoginCookie(entity) || VerifyClientToken(entity),
//Critical requires that the client cookie is set and the token is set
- case AuthorzationCheckLevel.Critical:
- return VerifyLoginCookie(entity) && VerifyClientToken(entity);
-
+ AuthorzationCheckLevel.Critical => VerifyLoginCookie(entity) && VerifyClientToken(entity),
//Default to false condition
- default:
- return false;
- }
+ _ => false,
+ };
}
IClientAuthorization IAccountSecurityProvider.ReAuthorizeClient(HttpEntity entity)
@@ -366,6 +363,21 @@ namespace VNLib.Plugins.Essentials.Accounts.SecurityProvider
#endregion
#region Cookies
+
+ private void ReconcileCookies(HttpEntity entity)
+ {
+ //Only handle cookies if session is loaded and is a web based session
+ if (!entity.Session.IsSet || entity.Session.SessionType != SessionType.Web)
+ {
+ return;
+ }
+
+ //If the session is new, or not supposed to be logged in, clear the login cookies if they were set
+ if (entity.Session.IsNew || string.IsNullOrEmpty(entity.Session.LoginHash) || string.IsNullOrEmpty(entity.Session.Token))
+ {
+ ExpireCookies(entity);
+ }
+ }
private bool VerifyLoginCookie(HttpEntity entity)
{
@@ -389,11 +401,11 @@ namespace VNLib.Plugins.Essentials.Accounts.SecurityProvider
//Alloc buffer for decoding the base64 signatures
- using UnsafeMemoryHandle<byte> buffer = MemoryUtil.UnsafeAllocNearestPage<byte>(2 * entity.Session.LoginHash.Length, true);
+ using UnsafeMemoryHandle<byte> buffer = MemoryUtil.UnsafeAllocNearestPage<byte>(2 * _config.LoginCookieSize, true);
//Slice up buffers
Span<byte> cookieBuffer = buffer.Span[.._config.LoginCookieSize];
- Span<byte> sessionBuffer = buffer.Span.Slice(_config.LoginCookieSize, _config.LoginCookieSize);
+ Span<byte> sessionBuffer = buffer.AsSpan(_config.LoginCookieSize, _config.LoginCookieSize);
//Convert cookie and session hash value
if (Convert.TryFromBase64Chars(cookie, cookieBuffer, out int cookieBytesWriten)
@@ -405,6 +417,8 @@ namespace VNLib.Plugins.Essentials.Accounts.SecurityProvider
return true;
}
}
+ //Clear login cookie if failed
+ ExpireCookies(entity);
return false;
}
@@ -413,17 +427,48 @@ namespace VNLib.Plugins.Essentials.Accounts.SecurityProvider
//Expire login cookie if set
if (entity.Server.RequestCookies.ContainsKey(_config.LoginCookieName))
{
- entity.Server.ExpireCookie(_config.LoginCookieName, sameSite: CookieSameSite.SameSite);
+ HttpCookie pkCookie = new(_config.LoginCookieName, string.Empty)
+ {
+ Domain = _config.CookieDomain,
+ Path = _config.CookiePath,
+ ValidFor = TimeSpan.Zero,
+ SameSite = CookieSameSite.SameSite,
+ HttpOnly = true,
+ Secure = true
+ };
+
+ entity.Server.SetCookie(in pkCookie);
}
//Expire the LI cookie if set
if (entity.Server.RequestCookies.ContainsKey(_config.ClientStatusCookieName))
{
- entity.Server.ExpireCookie(_config.ClientStatusCookieName, sameSite: CookieSameSite.SameSite);
+ HttpCookie pkCookie = new(_config.ClientStatusCookieName, string.Empty)
+ {
+ Domain = _config.CookieDomain,
+ Path = _config.CookiePath,
+ ValidFor = TimeSpan.Zero,
+ SameSite = CookieSameSite.SameSite,
+ HttpOnly = true,
+ Secure = true
+ };
+
+ entity.Server.SetCookie(in pkCookie);
}
//Expire pupkey cookie
if (entity.Server.RequestCookies.ContainsKey(_config.PubKeyCookieName))
{
- entity.Server.ExpireCookie(_config.PubKeyCookieName, sameSite: CookieSameSite.SameSite);
+ //Init exipiration cookie
+ HttpCookie pkCookie = new(_config.PubKeyCookieName, string.Empty)
+ {
+ Domain = _config.CookieDomain,
+ Path = _config.CookiePath,
+ ValidFor = TimeSpan.Zero,
+ SameSite = CookieSameSite.SameSite,
+ HttpOnly = true,
+ Secure = true
+ };
+
+ entity.Server.SetCookie(in pkCookie);
}
}
diff --git a/plugins/VNLib.Plugins.Essentials.Content.Routing/src/Router.cs b/plugins/VNLib.Plugins.Essentials.Content.Routing/src/Router.cs
index 31f1e0e..fdf5f59 100644
--- a/plugins/VNLib.Plugins.Essentials.Content.Routing/src/Router.cs
+++ b/plugins/VNLib.Plugins.Essentials.Content.Routing/src/Router.cs
@@ -54,6 +54,12 @@ namespace VNLib.Plugins.Essentials.Content.Routing
RouteTable = new();
}
+ public Router(PluginBase plugin, IConfigScope config)
+ {
+ Store = new(plugin.GetContextOptions());
+ RouteTable = new();
+ }
+
///<inheritdoc/>
public async ValueTask<FileProcessArgs> RouteAsync(HttpEntity entity)
{
diff --git a/plugins/VNLib.Plugins.Essentials.SocialOauth/src/SocialOauthBase.cs b/plugins/VNLib.Plugins.Essentials.SocialOauth/src/SocialOauthBase.cs
index db61dd0..9d6232c 100644
--- a/plugins/VNLib.Plugins.Essentials.SocialOauth/src/SocialOauthBase.cs
+++ b/plugins/VNLib.Plugins.Essentials.SocialOauth/src/SocialOauthBase.cs
@@ -80,14 +80,7 @@ namespace VNLib.Plugins.Essentials.SocialOauth
protected virtual OauthClientConfig Config { get; }
///<inheritdoc/>
- protected override ProtectionSettings EndpointProtectionSettings { get; } = new()
- {
- /*
- * Disable cross site checking because the OAuth2 flow requires
- * cross site when redirecting the client back
- */
- DisableCrossSiteDenied = true
- };
+ protected override ProtectionSettings EndpointProtectionSettings { get; } = new();
/// <summary>
/// The resst client connection pool