From df7dc615532d3441f527374d18664c1a5a336de6 Mon Sep 17 00:00:00 2001 From: vnugent Date: Sun, 21 Jan 2024 21:24:55 -0500 Subject: optional path enforcement and patch baseURL relative inclusion --- ci/bundle/Taskfile.yaml | 2 +- lib/vnlib.browser/src/axios/index.ts | 19 ++++- .../src/Essentials.Accounts.json | 5 +- .../src/SecurityProvider/AccountSecProvider.cs | 85 ++++++++++++---------- 4 files changed, 69 insertions(+), 42 deletions(-) diff --git a/ci/bundle/Taskfile.yaml b/ci/bundle/Taskfile.yaml index f049bd5..9f7b5c5 100644 --- a/ci/bundle/Taskfile.yaml +++ b/ci/bundle/Taskfile.yaml @@ -45,7 +45,7 @@ tasks: OUT_NAME: 'PageRouter' #tar temp dir and put in output - - cmd: cd temp && tar -czf "../bin/{{.OUT_FILE_NAME}}-{{.BUILD_VERSION}}.tgz" . + - cmd: cd temp && tar -czf "../bin/release-bundle.tgz" . copy-plugin: desc: "copy a single plugin project to its output directory" diff --git a/lib/vnlib.browser/src/axios/index.ts b/lib/vnlib.browser/src/axios/index.ts index 2780102..6a2135f 100644 --- a/lib/vnlib.browser/src/axios/index.ts +++ b/lib/vnlib.browser/src/axios/index.ts @@ -36,8 +36,23 @@ const configureAxiosInternal = (instance: Axios, session: ISession, tokenHeader: // See if the current session is logged in if (tokenHeaderValue && loggedIn.value) { - // Get an otp for the request - config.headers[tokenHeaderValue] = await generateOneTimeToken(config.url!); + + const path = `${config.baseURL}${config.url}` + + //see if absolute url or relative + if(path.match(/https?:\/\//)){ + //Is absolute + const { pathname } = new URL(path); + + // Get an otp for the request + config.headers[tokenHeaderValue] = await generateOneTimeToken(pathname); + } + else{ + //Is relative + + // Get an otp for the request + config.headers[tokenHeaderValue] = await generateOneTimeToken(path); + } } // Return the config return config diff --git a/plugins/VNLib.Plugins.Essentials.Accounts/src/Essentials.Accounts.json b/plugins/VNLib.Plugins.Essentials.Accounts/src/Essentials.Accounts.json index 73529ee..8a345c5 100644 --- a/plugins/VNLib.Plugins.Essentials.Accounts/src/Essentials.Accounts.json +++ b/plugins/VNLib.Plugins.Essentials.Accounts/src/Essentials.Accounts.json @@ -89,6 +89,9 @@ "pubkey_cookie_name": "client-id", "pubkey_signing_key_size": 32, - "strict_origin": false + "strict_origin": false, + "strict_path": false, + + //"allowed_origins": [ ] } } \ No newline at end of file diff --git a/plugins/VNLib.Plugins.Essentials.Accounts/src/SecurityProvider/AccountSecProvider.cs b/plugins/VNLib.Plugins.Essentials.Accounts/src/SecurityProvider/AccountSecProvider.cs index 5847820..9f1d6ee 100644 --- a/plugins/VNLib.Plugins.Essentials.Accounts/src/SecurityProvider/AccountSecProvider.cs +++ b/plugins/VNLib.Plugins.Essentials.Accounts/src/SecurityProvider/AccountSecProvider.cs @@ -459,55 +459,58 @@ namespace VNLib.Plugins.Essentials.Accounts.SecurityProvider } } - //Check the subject (path) matches the request uri - if (data.RootElement.TryGetProperty("path", out JsonElement tokenPathEl) - && tokenPathEl.ValueKind == JsonValueKind.String) + if (_config.VerifyPath) { - - ReadOnlySpan unsafeUserPath = tokenPathEl.GetString(); - /* - * Query parameters are optional, so we need to check if the path contains a - * query, if so we can compare the entire path and query, otherwise we need to - * compare the path only - */ - if (unsafeUserPath.Contains("?", StringComparison.OrdinalIgnoreCase)) + //Check the subject (path) matches the request uri + if (data.RootElement.TryGetProperty("path", out JsonElement tokenPathEl) + && tokenPathEl.ValueKind == JsonValueKind.String) { - //Compare path and query when possible - string requestPath = entity.Server.RequestUri.PathAndQuery; - isValid &= unsafeUserPath.Equals(requestPath, StringComparison.OrdinalIgnoreCase); + ReadOnlySpan unsafeUserPath = tokenPathEl.GetString(); + /* + * Query parameters are optional, so we need to check if the path contains a + * query, if so we can compare the entire path and query, otherwise we need to + * compare the path only + */ + if (unsafeUserPath.Contains("?", StringComparison.OrdinalIgnoreCase)) + { + //Compare path and query when possible + string requestPath = entity.Server.RequestUri.PathAndQuery; + + isValid &= unsafeUserPath.Equals(requestPath, StringComparison.OrdinalIgnoreCase); - if (!isValid && _logger.IsEnabled(LogLevel.Debug)) + if (!isValid && _logger.IsEnabled(LogLevel.Debug)) + { + _logger.Debug("Client security OTP JWT path mismatch from {ip} : {current} != {token}", + entity.TrustedRemoteIp, + requestPath, + unsafeUserPath.ToString() + ); + } + } + else { - _logger.Debug("Client security OTP JWT path mismatch from {ip} : {current} != {token}", - entity.TrustedRemoteIp, - requestPath, - unsafeUserPath.ToString() - ); + //Use path only + string requestPath = entity.Server.RequestUri.LocalPath; + + //Compare path only + isValid &= unsafeUserPath.Equals(requestPath, StringComparison.OrdinalIgnoreCase); + + if (!isValid && _logger.IsEnabled(LogLevel.Debug)) + { + _logger.Debug("Client security OTP JWT path mismatch from {ip} : {current} != {token}", + entity.TrustedRemoteIp, + requestPath, + unsafeUserPath.ToString() + ); + } } } else { - //Use path only - string requestPath = entity.Server.RequestUri.LocalPath; - - //Compare path only - isValid &= unsafeUserPath.Equals(requestPath, StringComparison.OrdinalIgnoreCase); - - if (!isValid && _logger.IsEnabled(LogLevel.Debug)) - { - _logger.Debug("Client security OTP JWT path mismatch from {ip} : {current} != {token}", - entity.TrustedRemoteIp, - requestPath, - unsafeUserPath.ToString() - ); - } + isValid = false; } } - else - { - isValid = false; - } return isValid; } @@ -865,6 +868,12 @@ namespace VNLib.Plugins.Essentials.Accounts.SecurityProvider [JsonPropertyName("allowed_origins")] public string[]? AllowedOrigins { get; set; } + /// + /// Enforce strict path checking for the client's token + /// + [JsonPropertyName("strict_path")] + public bool VerifyPath { get; set; } = true; + void IOnConfigValidation.Validate() { //Validate the current instance -- cgit