Skip to content
Open
29 changes: 29 additions & 0 deletions src/modules/Elsa.Studio.Login/Extensions/StringExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
namespace Elsa.Studio.Login.Extensions;

/// <summary>
/// Provides a set of extension methods for <see cref="string"/>.
/// </summary>
internal static class StringExtensions
{
/// <summary>
/// Ensures the string starts with the specified character when non-empty.
/// </summary>
public static string EnsureStartsWith(this string? value, string prefix)
{
if (string.IsNullOrEmpty(value))
return string.Empty;

return value.StartsWith(prefix) ? value : prefix + value;
}

/// <summary>
/// Ensures the string ends with the specified string when non-empty.
/// </summary>
public static string EnsureEndsWith(this string? value, string suffix)
{
if (string.IsNullOrEmpty(value))
return string.Empty;

return value.EndsWith(suffix) ? value : value + suffix;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ public class OpenIdConnectConfiguration
/// </summary>
public required string EndSessionEndpoint { get; set; }

/// <summary>
/// A prefix to insert before <c>/signin-oidc</c> when constructing the redirect_uri for the authorization request.
/// Useful for sub-path deployments behind a reverse proxy, e.g. setting this to <c>/workflow</c> produces
/// <c>https://myapp.com/workflow/signin-oidc</c>. The value must start with <c>/</c>. When not set the redirect_uri
/// defaults to <c>{origin}/signin-oidc</c>.
/// </summary>
Comment thread
IbrahimMNada marked this conversation as resolved.
public string? RedirectUriPrefix { get; set; }

/// <summary>
/// The client_id as which this application is registered with the authorization server
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Elsa.Studio.Login.Contracts;
using Elsa.Studio.Login.Contracts;
using Elsa.Studio.Login.Extensions;
using Elsa.Studio.Login.Models;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.WebUtilities;
Expand All @@ -21,7 +22,7 @@ public class OpenIdConnectAuthorizationService(IJwtAccessor jwtAccessor, IOption
public async Task RedirectToAuthorizationServer()
{
var config = configuration.Value;
var redirectUri = new Uri(navigationManager.Uri).GetLeftPart(UriPartial.Authority) + "/signin-oidc";
var redirectUri = new Uri(navigationManager.Uri).GetLeftPart(UriPartial.Authority) + config.RedirectUriPrefix.EnsureStartsWith("/") + "/signin-oidc";
string url = config.AuthEndpoint + $"?client_id={WebUtility.UrlEncode(config.ClientId)}&redirect_uri={WebUtility.UrlEncode(redirectUri)}&response_type=code&scope={WebUtility.UrlEncode(String.Join(' ', config.Scopes))}";
if (config.UsePkce)
{
Expand All @@ -40,7 +41,7 @@ public async Task RedirectToAuthorizationServer()
public async Task ReceiveAuthorizationCode(string code, string? state, CancellationToken cancellationToken)
{
var config = configuration.Value;
var redirectUri = new Uri(navigationManager.Uri).GetLeftPart(UriPartial.Authority) + "/signin-oidc";
var redirectUri = new Uri(navigationManager.Uri).GetLeftPart(UriPartial.Authority) + config.RedirectUriPrefix.EnsureStartsWith("/") + "/signin-oidc";

var formValues = new List<KeyValuePair<string, string>>
{
Expand All @@ -50,7 +51,7 @@ public async Task ReceiveAuthorizationCode(string code, string? state, Cancellat
new("redirect_uri", redirectUri)
};

if(!string.IsNullOrWhiteSpace(config.ClientSecret))
if (!string.IsNullOrWhiteSpace(config.ClientSecret))
{
formValues.Add(new KeyValuePair<string, string>("client_secret", config.ClientSecret));
}
Expand Down Expand Up @@ -132,7 +133,7 @@ private static async Task<string> ReadErrorSummaryAsync(HttpContent content, Can
if (summary.Length > MaxLoggedErrorLength)
summary = summary[..MaxLoggedErrorLength];

return truncated ? $"{summary}" : summary;
return truncated ? $"{summary}" : summary;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Corrupted ellipsis character

The original file (which had a UTF-8 BOM) contained the HORIZONTAL ELLIPSIS (U+2026) on this line. When the BOM was stripped in this PR, the multi-byte sequence for that character was corrupted to U+FFFD (the Unicode Replacement Character). At runtime, truncated error summaries logged by ReadErrorSummaryAsync will end with the replacement character <?> instead of , producing garbled log output. Replace the character literal on this line with the correct (U+2026), or use the escape \u2026.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/modules/Elsa.Studio.Login/Services/OpenIdConnectAuthorizationService.cs
Line: 136

Comment:
**Corrupted ellipsis character**

The original file (which had a UTF-8 BOM) contained the HORIZONTAL ELLIPSIS `` (U+2026) on this line. When the BOM was stripped in this PR, the multi-byte sequence for that character was corrupted to U+FFFD (the Unicode Replacement Character). At runtime, truncated error summaries logged by `ReadErrorSummaryAsync` will end with the replacement character `<?>` instead of ``, producing garbled log output. Replace the character literal on this line with the correct `` (U+2026), or use the escape `\u2026`.

How can I resolve this? If you propose a fix, please make it concise.

}

private static async Task<(string Content, bool Truncated)> ReadContentSnippetAsync(HttpContent content, CancellationToken cancellationToken)
Expand Down Expand Up @@ -178,4 +179,5 @@ private static async Task<string> ReadErrorSummaryAsync(HttpContent content, Can

return null;
}

}