aboutsummaryrefslogtreecommitdiff
path: root/plugins/VNLib.Plugins.Essentials.SocialOauth/src/SocialOauthBase.cs
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/VNLib.Plugins.Essentials.SocialOauth/src/SocialOauthBase.cs')
-rw-r--r--plugins/VNLib.Plugins.Essentials.SocialOauth/src/SocialOauthBase.cs32
1 files changed, 25 insertions, 7 deletions
diff --git a/plugins/VNLib.Plugins.Essentials.SocialOauth/src/SocialOauthBase.cs b/plugins/VNLib.Plugins.Essentials.SocialOauth/src/SocialOauthBase.cs
index 79e3b1b..73c2ab5 100644
--- a/plugins/VNLib.Plugins.Essentials.SocialOauth/src/SocialOauthBase.cs
+++ b/plugins/VNLib.Plugins.Essentials.SocialOauth/src/SocialOauthBase.cs
@@ -139,6 +139,7 @@ namespace VNLib.Plugins.Essentials.SocialOauth
{
return false;
}
+
/*
* Cross site checking is disabled because we need to allow cross site
* for OAuth2 redirect flows
@@ -147,12 +148,9 @@ namespace VNLib.Plugins.Essentials.SocialOauth
{
return false;
}
+
//Make sure the user is not logged in
- if(entity.LoginCookieMatches() || entity.TokenMatches())
- {
- return false;
- }
- return true;
+ return !(entity.LoginCookieMatches() || entity.TokenMatches());
}
/// <summary>
@@ -219,7 +217,7 @@ namespace VNLib.Plugins.Essentials.SocialOauth
/// <returns></returns>
protected abstract Task<UserLoginData?> GetLoginDataAsync(IOAuthAccessState clientAccess, CancellationToken cancellation);
- class LoginClaim : ICacheable, INonce
+ sealed class LoginClaim : ICacheable, INonce
{
[JsonPropertyName("public_key")]
public string? PublicKey { get; set; }
@@ -282,12 +280,14 @@ namespace VNLib.Plugins.Essentials.SocialOauth
entity.Redirect(RedirectType.Temporary, $"{Path}?result=bad_sec");
return VfReturnType.VirtualSkip;
}
+
//Try to get the claim from the state parameter
if (ClaimStore.TryGetOrEvictRecord(state, out LoginClaim? claim) < 1)
{
entity.Redirect(RedirectType.Temporary, $"{Path}?result=expired");
return VfReturnType.VirtualSkip;
}
+
//Lock on the claim to prevent replay
lock (claim)
{
@@ -302,17 +302,21 @@ namespace VNLib.Plugins.Essentials.SocialOauth
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)
{
entity.Redirect(RedirectType.Temporary, $"{Path}?result=invalid");
return VfReturnType.VirtualSkip;
}
+
//Store claim info
token.PublicKey = claim.PublicKey;
token.ClientId = claim.ClientId;
+
//Generate the new nonce
string nonce = token.ComputeNonce((int)Config.NonceByteSize);
//Collect expired records
@@ -323,6 +327,7 @@ namespace VNLib.Plugins.Essentials.SocialOauth
entity.Redirect(RedirectType.Temporary, $"{Path}?result=authorized&nonce={nonce}");
return VfReturnType.VirtualSkip;
}
+
//Check to see if there was an error code set
if (entity.QueryArgs.TryGetNonEmptyValue("error", out string? errorCode))
{
@@ -330,6 +335,7 @@ namespace VNLib.Plugins.Essentials.SocialOauth
entity.Redirect(RedirectType.Temporary, $"{Path}?result=error");
return VfReturnType.VirtualSkip;
}
+
return VfReturnType.ProcessAsFile;
}
@@ -340,13 +346,16 @@ namespace VNLib.Plugins.Essentials.SocialOauth
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"))
{
entity.CloseResponseJson(HttpStatusCode.BadRequest, webm);
return VfReturnType.VirtualSkip;
}
+
//Recover the nonce
string? base32Nonce = request.RootElement.GetPropString("nonce");
if(webm.Assert(base32Nonce != null, message: "Nonce parameter is required"))
@@ -354,12 +363,14 @@ namespace VNLib.Plugins.Essentials.SocialOauth
entity.CloseResponseJson(HttpStatusCode.UnprocessableEntity, webm);
return VfReturnType.VirtualSkip;
}
+
//Validate nonce
if (!NonceValidator.Validate(base32Nonce, webm))
{
entity.CloseResponseJson(HttpStatusCode.UnprocessableEntity, webm);
return VfReturnType.VirtualSkip;
}
+
//Recover the access token
if (AuthorizationStore.TryGetOrEvictRecord(base32Nonce!, out OAuthAccessState? token) < 1)
{
@@ -367,6 +378,7 @@ namespace VNLib.Plugins.Essentials.SocialOauth
entity.CloseResponse(webm);
return VfReturnType.VirtualSkip;
}
+
bool valid;
//Valid token, now verify the nonce within the locked context
lock (token)
@@ -375,6 +387,7 @@ namespace VNLib.Plugins.Essentials.SocialOauth
//Evict (wipes nonce)
AuthorizationStore.EvictRecord(base32Nonce!);
}
+
if (webm.Assert(valid, AUTH_ERROR_MESSAGE))
{
entity.CloseResponse(webm);
@@ -546,12 +559,16 @@ namespace VNLib.Plugins.Essentials.SocialOauth
//Cleanup old records
ClaimStore.CollectRecords();
+
//Set nonce
string base32Nonce = claim.ComputeNonce((int)Config.NonceByteSize);
+
//build the redirect url
webm.Result = BuildUrl(base32Nonce, claim.PublicKey!, entity.IsSecure ? "https" : "http", entity.Server.RequestUri.Authority, entity.Server.Encoding);
+
//Store the claim
ClaimStore.StoreRecord(base32Nonce, claim, Config.LoginNonceLifetime);
+
webm.Success = true;
//Response
entity.CloseResponse(webm);
@@ -573,11 +590,12 @@ namespace VNLib.Plugins.Essentials.SocialOauth
using UnsafeMemoryHandle<byte> buffer = MemoryUtil.UnsafeAlloc<byte>(8192, true);
//get bin buffer slice
Span<byte> binBuffer = buffer.Span[1024..];
+ Span<byte> charBuffer = buffer.Span[..1024];
ReadOnlySpan<char> url;
{
//Get char buffer slice and cast to char
- Span<char> charBuf = MemoryMarshal.Cast<byte, char>(buffer.Span[..1024]);
+ Span<char> charBuf = MemoryMarshal.Cast<byte, char>(charBuffer);
//buffer writer for easier syntax
ForwardOnlyWriter<char> writer = new(charBuf);
//first build the redirect url to re-encode it