Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cookie compatibility between OWIN and Microsoft.AspNetCore.Authentication #435

Closed
Schoof-T opened this issue Nov 30, 2021 · 27 comments
Closed

Comments

@Schoof-T
Copy link

Schoof-T commented Nov 30, 2021

Hello

I have applications both running on ASP.NET MVC (4.7.2) and ASP.NET Core (Blazor, 5.0). I secure these with OpenIdConnect.
I would like the cookie to be compatible between these applications, as it currently stands these both create their own cookie and requires re-authentication.

Example: I connect to portal.domain.com (ASP.NET MVC 4.7.2), this requires an authentication flow and creates a cookie. I click through to application.portal.domain.com (ASP.NET Core (Blazor, 5.0) and this requires another authentication flow and creates a second cookie.
However if I click through to application2.portal.domain.com (ASP.NET MVC 4.7.2) (from portal.domain.com) this does not require a second authentication flow and re-uses the original cookie (as it should).

Are there specific settings that need to be adjusted so these cookies become compatible?

ASP.NET Authentication Code (Startup file)

            app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);

            app.UseCookieAuthentication(new CookieAuthenticationOptions
            {
                AuthenticationType = CookieAuthenticationDefaults.AuthenticationType,
                CookieSameSite = SameSiteMode.Lax,
#if !DEBUG
                CookieDomain = ConfigurationManager.AppSettings["OpenIdCookieDomain"]
#endif
            });

            app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions()
            {
                ClientId = ConfigurationManager.AppSettings["OpenIdClientId"],
                ClientSecret = ConfigurationManager.AppSettings["OpenIdClientSecret"],
                SignInAsAuthenticationType = CookieAuthenticationDefaults.AuthenticationType,
#if DEBUG
                RequireHttpsMetadata = false,
#endif
                Scope = "openid profile email",
                Authority = ConfigurationManager.AppSettings["OpenIdAuthority"],
                MetadataAddress = ConfigurationManager.AppSettings["OpenIdMetadata"],
                RedirectUri = ConfigurationManager.AppSettings["OpenIdRedirect"],
                ResponseType = OpenIdConnectResponseType.Code,
                RedeemCode = true,
                UsePkce = true,
            });

ASP.NET Core Authentication Code (Startup file)

 services.AddAuthentication(sharedOptions =>
            {
                sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                sharedOptions.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
            })
               .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
               {
#if !DEBUG
                   options.Cookie.Domain = Configuration["Security:CookieDomain"];
#endif
               })
               .AddOpenIdConnect("oidc", options =>
               {
                   options.Authority = Configuration["Security:Authority"];
                   options.MetadataAddress = Configuration["Security:MetadataAddress"];
                   options.AuthenticationMethod = OpenIdConnectRedirectBehavior.RedirectGet;
                   options.ClientId = Configuration["Security:ClientId"];
                   options.ClientSecret = Configuration["Security:ClientSecret"];
                   options.ResponseType = Configuration["Security:ResponseType"];
                   options.SaveTokens = true;
                   options.GetClaimsFromUserInfoEndpoint = true;
                   options.UseTokenLifetime = false;
                   options.Scope.Add("openid");
                   options.Scope.Add("profile");
                   options.Scope.Add("email");
                   options.UsePkce = true;
               });

Thanks in advance

@Tratcher
Copy link
Member

See dotnet/AspNetCore.Docs#21987

@Schoof-T
Copy link
Author

Schoof-T commented Dec 1, 2021

See dotnet/AspNetCore.Docs#21987

Thank you for the quick reply. I'm guessing my google skills weren't up to snuff.
I managed to get this working, but with a couple of issues.

Using the code listed for my .NET 4.x websites, this creates a very large cookie. Up to the point where I can no longer add any custom claims or I get the famed "HTTP Error 400. The size of the request headers is too long." exception. I'm trying to add about 50 claims, totaling up to about 80 claims. (I don't know if that is a lot, but it doesn't feel like it). The other claims we get from our identity provider.

            app.UseCookieAuthentication(new CookieAuthenticationOptions
            {
                AuthenticationType = CookieAuthenticationDefaults.AuthenticationType,
                CookieName = ".AspNet.SharedCookie",
                CookieSameSite = SameSiteMode.Lax,
                SlidingExpiration = true,
                ExpireTimeSpan = TimeSpan.FromMinutes(120),
                TicketDataFormat = new AspNetTicketDataFormat(
                    new DataProtectorShim(
                        DataProtectionProvider.Create(new DirectoryInfo(@"D:\\KeyDirectory"),
                        (builder) =>
                        {
                            builder.SetApplicationName("SharedCookieApp");
                        })
                        .CreateProtector(
                            "Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware",
                            "Cookies",
                            "v2"))),
                CookieManager = new Microsoft.Owin.Security.Interop.ChunkingCookieManager()
#if !DEBUG
                CookieDomain = ConfigurationManager.AppSettings["OpenIdCookieDomain"]
#endif
            });

            app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions()
            {
                ClientId = ConfigurationManager.AppSettings["OpenIdClientId"],
                ClientSecret = ConfigurationManager.AppSettings["OpenIdClientSecret"],
                Authority = ConfigurationManager.AppSettings["OpenIdAuthority"],
                MetadataAddress = ConfigurationManager.AppSettings["OpenIdMetadata"],
                SignInAsAuthenticationType = CookieAuthenticationDefaults.AuthenticationType,
                RedirectUri = ConfigurationManager.AppSettings["OpenIdRedirect"],
#if DEBUG
                RequireHttpsMetadata = false,
#endif
                Scope = "openid profile email",
                ResponseType = OpenIdConnectResponseType.Code,
                SaveTokens = true,
                UseTokenLifetime = false,
                RedeemCode = true,
                UsePkce = true,
            });

image

Adding the code to my .NET 5 application does not generate a cookie as big (still quite large).

            services.AddDataProtection()
                .PersistKeysToFileSystem(new DirectoryInfo(@"D:\\KeyDirectory"))
                .SetApplicationName("SharedCookieApp");

            services.AddAuthentication(sharedOptions =>
            {
                sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                sharedOptions.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
            })
               .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
               {
                   options.Cookie.Name = ".AspNet.SharedCookie";
                   options.Cookie.SameSite = SameSiteMode.Lax;
                   options.Cookie.Path = "/";
                   options.Cookie.HttpOnly = true;
                   options.Cookie.IsEssential = true;
                   options.ExpireTimeSpan = TimeSpan.FromMinutes(120);
#if !DEBUG
                   options.Cookie.Domain = Configuration["Security:CookieDomain"];
#endif
               })
               .AddOpenIdConnect("oidc", options =>
               {
                   options.ClientId = Configuration["Security:ClientId"];
                   options.ClientSecret = Configuration["Security:ClientSecret"];
                   options.Authority = Configuration["Security:Authority"];
                   options.MetadataAddress = Configuration["Security:MetadataAddress"];
                   options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                   options.AuthenticationMethod = OpenIdConnectRedirectBehavior.RedirectGet;
                   options.GetClaimsFromUserInfoEndpoint = true;

                   options.Scope.Add("openid");
                   options.Scope.Add("profile");
                   options.Scope.Add("email");
                   options.ResponseType = OpenIdConnectResponseType.Code;
                   options.SaveTokens = true;
                   options.UseTokenLifetime = false;
                   options.UsePkce = true;
               });

image

Is there any way to decrease the size of the cookies? I have noticed they have dramatically increased after we switched to using PCKE.

A second thing I've noticed is that adding the Nuget Microsoft.Owin.Security.Interop also install a huge amount of extra nuget packages. Is this really necessary? I don't seem to be using any of them in my Startup file.
image

@Tratcher
Copy link
Member

Tratcher commented Dec 1, 2021

50-80 claims is a lot to try to fit into a cookie.

https://docs.microsoft.com/en-us/aspnet/core/security/authentication/social/additional-claims?view=aspnetcore-6.0#map-user-data-keys-and-create-claims

If a large amount of user data is required for processing user requests:

  • Limit the number and size of user claims for request processing to only what the app requires.
  • Use a custom ITicketStore for the Cookie Authentication Middleware's SessionStore to store identity across requests. Preserve large quantities of identity information on the server while only sending a small session identifier key to the client.

https://github.com/dotnet/aspnetcore/blob/2af03e53f1fd6cb48c5dba9440064ccdb1060657/src/Security/Authentication/Cookies/src/CookieAuthenticationOptions.cs#L135

A second thing I've noticed is that adding the Nuget Microsoft.Owin.Security.Interop also install a huge amount of extra nuget packages. Is this really necessary? I don't seem to be using any of them in my Startup file.

Unfortunately those are required, they're all referenced indirectly from the interop code you're adding.

@Schoof-T
Copy link
Author

Schoof-T commented Dec 2, 2021

I couldn't find a working example for ITicketStore for ASP.NET and ASP.NET Core so I've opted to store my access rights in Session (HttpContext.Current.Session) for my ASP.NET applications and in MemoryCache (Microsoft.Extensions.Caching.Memory) for my Blazor applications. Does this seem like a good approach?

Unfortunately those are required, they're all referenced indirectly from the interop code you're adding.

Would you recommend updating them to the latest version or keeping them on a specific version? I'm asking because a lot of these nugets seem to be updated for .NET 6 and I'm thinking the Microsoft.Owin.Security.Interop was created with a specific version.

@Tratcher
Copy link
Member

Tratcher commented Dec 2, 2021

Don't use Session for auth data, it doesn't have the same lifetime and could leak after signout. You can use MemoryCache in both apps.

You can update to the latest patch version, but don't update to 6.0, there are breaking changes across major versions.

@Schoof-T
Copy link
Author

Schoof-T commented Dec 2, 2021

Don't use Session for auth data, it doesn't have the same lifetime and could leak after signout. You can use MemoryCache in both apps.

Okay thanks! Any suggestions for the lifespan settings of the MemoryCache? (I have load balanced websites as well, does that have any impact?)

Current settings are as follows

 var cacheExpiryOptions = new MemoryCacheEntryOptions
                {
                    AbsoluteExpiration = DateTime.Now.AddMinutes(60),
                    Priority = CacheItemPriority.High,
                    SlidingExpiration = TimeSpan.FromMinutes(5)
                };

You can update to the latest patch version, but don't update to 6.0, there are breaking changes across major versions.

What version would you suggest upgrading to? 2.x,, 3.x, 5.x, 6.x? Considering I am on .NET Framework 4.7.2

@Tratcher
Copy link
Member

Tratcher commented Dec 2, 2021

Oh, for load balanced sites you'd need to use IDistributedCache instead, and that will have its own lifetime settings. It's mainly a tradeoff of how much cache space you're willing to use vs how long you're willing to let people sit idle and stay signed in.

Stick with 2.1 if you're on .NET Framework, I think that's the last supported version of Microsoft.Owin.Security.Interop and you want the dependencies to be at the same major version.

@Schoof-T
Copy link
Author

Schoof-T commented Dec 3, 2021

So just to confirm, I have these nuggets at the following versions (not all had a 2.1 release).

  • System.Runtime.CompilerServices.Unsafe: 6.0.0 (has no 2.x release, lowest is 4.x)
  • Microsoft.AspNetCore.Cryptography.Internal: 2.1.1
  • Microsoft.AspNetCore.DataProtection.Abstractions: 2.1.1
  • System.Buffers: 4.5.1
  • System.Numeric.Vectors: 4.5.0
  • Microsoft.Extensions.Logging.Abstractions: 2.1.1
  • Microsoft.Extensions.Primitives: 2.1.1
  • Microsoft.Extensions.FileProviders.Abstractions: 2.1.1
  • System.Runtime.InteropServices.RuntimeInformation: 4.3
  • System.Security.Principal.Windows: 5.0.0 (has no 2.x release, lowest is 4.x)
  • System.Security.AccessControl: 6.0.0 (has no 2.x release, lowest is 4.x)
  • Microsoft.Win32.Registry: 5.0.0 (has no 2.x release, lowest is 4.x)
  • System.Security.Cryptography.Xml: 5.0.0 (has no 2.x release, lowest is 4.x)
  • Microsoft.Bcl.AsyncInterfaces: 6.0.0 (has no 2.x release, only 1.x and 6.x)
  • Microsoft.Extensions.DependencyInjection.Abstractions: 2.1.1
  • Microsoft.Extensions.DependencyInjection: 2.1.1
  • Microsoft.Extensions.Options: 2.1.1
  • System.ValueTuple: 4.5
  • Microsoft.Extensions.Configuration.Abstractions: 2.1.1
  • Microsoft.AspNetCore.Hosting.Abstractions: 2.1.1
  • Microsoft.AspNetCore.DataProtection: 2.1.1
  • Microsoft.AspNetCore.DataProtection.Extensions: 2.1.1

Does that seem okay?

@Tratcher
Copy link
Member

Tratcher commented Dec 3, 2021

I think you'll want the 4.x versions for the System dependencies, but if it works then I wouldn't sweat it too much.

@Schoof-T
Copy link
Author

Schoof-T commented Dec 6, 2021

Thanks again! I have another follow-up question.

I figured out why my cookie is so large and it's because I have set options.SaveTokens = true;. Does not using this option have a negative effect? What is the alternative?

@Tratcher
Copy link
Member

Tratcher commented Dec 6, 2021

You only need SaveTokens if you're going to make API calls on behalf of the user using those tokens. Even then, you can store those tokens in a local user database, they don't need to be in the cookie.

@Schoof-T
Copy link
Author

Schoof-T commented Dec 7, 2021

You only need SaveTokens if you're going to make API calls on behalf of the user using those tokens. Even then, you can store those tokens in a local user database, they don't need to be in the cookie.

Excellent, thanks a lot for all the help!

@Tratcher Tratcher closed this as completed Dec 7, 2021
@Schoof-T
Copy link
Author

Schoof-T commented Dec 14, 2021

@Tratcher I'm sorry for re-opening this, but after having disabled SaveTokens our Logout does not work any more. I get the exception "The endSession endpoint requires an id_token_hint parameter".

Logout Methods

HttpContext.GetOwinContext().Authentication.SignOut(CookieAuthenticationDefaults.AuthenticationType);
HttpContext.GetOwinContext().Authentication.SignOut("oidc");

When I try to get the IdToken now it is indeed null. When setting SaveTokens=true it does indeed return the token once again.

var idToken = ...GetTokenAsync(OpenIdConnectParameterNames.IdToken).Result;

I'm assuming I will have to save the IdToken elsewhere to be able to handle this (like in the database as you said), or is there another way around this? This IdToken is quite large. Can you set the IdToken Manually in the SignOut method? Where would I get the id_token from to store it somewhere?

@Tratcher
Copy link
Member

The default signout code doesn't set id_token_hint, where were you setting it?

OpenIdConnectMessage openIdConnectMessage = new OpenIdConnectMessage()
{
IssuerAddress = _configuration.EndSessionEndpoint ?? string.Empty,
RequestType = OpenIdConnectRequestType.Logout,
};

@Schoof-T
Copy link
Author

So I figured out this works with our ASP.NET (OWIN) Applications but not our ASP.NET Core Applications. I'm guessing they have a different implementation?

@Tratcher
Copy link
Member

Ah, AspNetCore does set the hint, though that shouldn't matter if the token isn't stored.
https://github.com/dotnet/aspnetcore/blob/a4fe58d890724b46b9d8aa7d3a135ca304cc26d6/src/Security/Authentication/OpenIdConnect/src/OpenIdConnectHandler.cs#L235

Do you have a trace of the signout request? Is id_token_hint present but empty? Otherwise I don't know why the remote provider would complain about the missing hint for core and not katana.

@Schoof-T
Copy link
Author

Schoof-T commented Dec 14, 2021

This is the signout request url?
.../auth/oauth2/connect/endSession?post_logout_redirect_uri=https%3A%2F%2Flocalhost%3A44320%2Fsignout-callback-oidc&state=CfDJ8GqjlgHph8lMhb0irgIJZtUMEBBFfYfYOfD7C98D0LuQqvrMiwO2jfCheEMFj2p9RBLFfk0IUN6QRQ14b938uP-CiKheQPAIaYevgvlOBw5Vrc7HNoGLzCNtX4PitbfPNQ873m1ZukqTXOl0BMCcqhZqxXq9aLAI1PItYyISR1BihCu-T_ntsJK-EIpXeD7ycQ&x-client-SKU=ID_NETSTANDARD2_0&x-client-ver=6.7.1.0

We are using ForgeRock and in their documentation the id_token_hint is required apparently.

I do wonder why it does work in OWIN, it might be because after the signout we do a redirect.


public class AccountController : Controller
    {
        [Authorize]
        public void Logout()
        {
            HttpContext.GetOwinContext().Authentication.SignOut(CookieAuthenticationDefaults.AuthenticationType);
            HttpContext.GetOwinContext().Authentication.SignOut("oidc");
            HttpContext.Response.Redirect(ConfigurationManager.AppSettings["OpenIdRedirectPostLogout"]);
        }
    }

@Tratcher
Copy link
Member

Oh, yeah, you're skipping the OIDC remote logout there, you're only logging out locally.

@Schoof-T
Copy link
Author

Oh, yeah, you're skipping the OIDC remote logout there, you're only logging out locally.

What would be the correct way to logout fully there, or is that not an issue? Maybe that could be a solution for the ASP.NET Core Application as well?

@Tratcher
Copy link
Member

Instead of adding your own redirect to OpenIdRedirectPostLogout, you pass that value to Signout in the AuthenticationProperties.RedirectUri.
See

AuthenticationProperties properties = signout.Properties;
if (properties != null && !string.IsNullOrEmpty(properties.RedirectUri))
{
openIdConnectMessage.PostLogoutRedirectUri = properties.RedirectUri;
}

You can also set it on the options.
else if (!string.IsNullOrWhiteSpace(Options.PostLogoutRedirectUri))
{
openIdConnectMessage.PostLogoutRedirectUri = Options.PostLogoutRedirectUri;
}

That said, you'll be back in the same position of needing the id token hint, storing the token in a database, and adding it to the signout in the events.

@Schoof-T
Copy link
Author

Thanks! That makes sense.
How do I add the id_token_hint to the signout? And how do I get the id_token_hint to starting storing it? I know SaveTokens does it automatically, but I can't seem to find how to do it manually.

@Tratcher
Copy link
Member

You'd need to hook into the events and update the OpenIdConnectMessage.

See
https://github.com/dotnet/aspnetcore/blob/5b742abe2fad7e19bb4ace67ef5fd5d20cff0dd3/src/Security/Authentication/OpenIdConnect/src/Events/OpenIdConnectEvents.cs#L39

public Func<RedirectToIdentityProviderNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions>, Task> RedirectToIdentityProvider { get; set; }

Storing and retrieving the token elsewhere is up to you.

@Schoof-T
Copy link
Author

Schoof-T commented Dec 15, 2021

Instead of adding your own redirect to OpenIdRedirectPostLogout, you pass that value to Signout in the AuthenticationProperties.RedirectUri. See

AuthenticationProperties properties = signout.Properties;
if (properties != null && !string.IsNullOrEmpty(properties.RedirectUri))
{
openIdConnectMessage.PostLogoutRedirectUri = properties.RedirectUri;
}

You can also set it on the options.

else if (!string.IsNullOrWhiteSpace(Options.PostLogoutRedirectUri))
{
openIdConnectMessage.PostLogoutRedirectUri = Options.PostLogoutRedirectUri;
}

That said, you'll be back in the same position of needing the id token hint, storing the token in a database, and adding it to the signout in the events.

I've tried to do this in the following ways but without success:

 public class AccountController : Controller
    {
        [Authorize]
        public void Logout()
        {
            HttpContext.GetOwinContext().Authentication.SignOut(CookieAuthenticationDefaults.AuthenticationType);
            HttpContext.GetOwinContext().Authentication.SignOut(new AuthenticationProperties { RedirectUri = ConfigurationManager.AppSettings["OpenIdRedirectPostLogout"] }, "oidc");
        }
    }
 app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions()
            {
                ClientId = ConfigurationManager.AppSettings["OpenIdClientId"],
                ClientSecret = ConfigurationManager.AppSettings["OpenIdClientSecret"],
                Authority = ConfigurationManager.AppSettings["OpenIdAuthority"],
                MetadataAddress = ConfigurationManager.AppSettings["OpenIdMetadata"],
                SignInAsAuthenticationType = CookieAuthenticationDefaults.AuthenticationType,
                PostLogoutRedirectUri = ConfigurationManager.AppSettings["OpenIdRedirectPostLogout"],
...

When I send the user to the page Account/Logout I just get a blank page.

You'd need to hook into the events and update the OpenIdConnectMessage.

See https://github.com/dotnet/aspnetcore/blob/5b742abe2fad7e19bb4ace67ef5fd5d20cff0dd3/src/Security/Authentication/OpenIdConnect/src/Events/OpenIdConnectEvents.cs#L39

public Func<RedirectToIdentityProviderNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions>, Task> RedirectToIdentityProvider { get; set; }

Storing and retrieving the token elsewhere is up to you.

This is also not the case for ASP.NET (Owin) applications right? That does not have an OnRedirectToIdentityProviderForSignOut .

@Tratcher
Copy link
Member

Owin/Katana has a shared RedirectToIdentityProvider and you need to check the message type:
https://docs.microsoft.com/en-us/dotnet/api/microsoft.identitymodel.protocols.openidconnect.openidconnectmessage.requesttype?view=azure-dotnet#Microsoft_IdentityModel_Protocols_OpenIdConnect_OpenIdConnectMessage_RequestType

HttpContext.GetOwinContext().Authentication.SignOut(new AuthenticationProperties { RedirectUri = ConfigurationManager.AppSettings["OpenIdRedirectPostLogout"] }, "oidc");

You didn't name your auth provider "oidc", it's using the default name OpenIdConnectAuthenticationDefaults.AuthenticationType "OpenIdConnect".

@Schoof-T
Copy link
Author

Schoof-T commented Dec 15, 2021

Owin/Katana has a shared RedirectToIdentityProvider and you need to check the message type: https://docs.microsoft.com/en-us/dotnet/api/microsoft.identitymodel.protocols.openidconnect.openidconnectmessage.requesttype?view=azure-dotnet#Microsoft_IdentityModel_Protocols_OpenIdConnect_OpenIdConnectMessage_RequestType

HttpContext.GetOwinContext().Authentication.SignOut(new AuthenticationProperties { RedirectUri = ConfigurationManager.AppSettings["OpenIdRedirectPostLogout"] }, "oidc");

You didn't name your auth provider "oidc", it's using the default name OpenIdConnectAuthenticationDefaults.AuthenticationType "OpenIdConnect".

Thank you! I'm almost there I think, I get redirected now, the token is filled in but I get the error "Not an RSA algorithm." from our identityprovider.

RedirectToIdentityProvider = n =>
                    {
                        if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.Logout)
                        {
                            var result = n.OwinContext.Authentication.AuthenticateAsync("Cookies").Result;
                            string token = result.Properties.Dictionary["access_token"];

                            if (!string.IsNullOrEmpty(token))
                            {
                                n.ProtocolMessage.IdTokenHint = token;
                            }
                        }
                        return Task.FromResult(0);
                    }

@Tratcher
Copy link
Member

Was that the right token?

@Schoof-T
Copy link
Author

Was that the right token?

It was not, my apologies! And thank you for all the help. :)

@ghost ghost locked as resolved and limited conversation to collaborators Jan 14, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants