diff --git a/src/Program.cs b/src/Program.cs index 42701ab..5bb8731 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -22,8 +22,8 @@ public static class Program { private static readonly string s_loginHtmlCache = File.ReadAllText(Path.Combine( AppDomain.CurrentDomain.BaseDirectory, "login.html")); - private static readonly string s_domain = Environment - .GetEnvironmentVariable("WEBAUTHN_DOMAIN") ?? "localhost"; + private static readonly string? s_domain = Environment + .GetEnvironmentVariable("WEBAUTHN_DOMAIN"); private static readonly string s_db = Environment .GetEnvironmentVariable("WEBAUTHN_DB") ?? "credentials.db"; private static readonly long s_lifetime = long.Parse(Environment @@ -34,21 +34,31 @@ public static class Program { private static readonly int s_port = int.Parse(Environment .GetEnvironmentVariable("WEBAUTHN_PORT") ?? "5000", CultureInfo.InvariantCulture); - private static readonly Fido2 s_fido2 = new(new Fido2Configuration { - ServerDomain = s_domain, - ServerName = "WebauthnProxy", - Origins = new(new[] { $"http{( - s_domain == "localhost" - ? string.Empty - : "s")}://{s_domain}{( - s_domain == "localhost" - ? $":{s_port}" - : string.Empty)}" }), - }); + private static readonly Dictionary s_fido2 = new(); private static readonly List s_keys = new(); private static string ConnectionString { get => $"Data Source={s_db}"; } + private static Fido2 GetFido2(HttpContext context) { + var origin = context.Request.Host.Value.Split(":").First(); + Console.WriteLine($"origin {origin}"); + if (!s_fido2.ContainsKey(origin)) { + s_fido2.Add(origin, new(new Fido2Configuration { + ServerDomain = origin, + ServerName = "WebauthnProxy", + Origins = new(new[] { $"http{( + origin == "localhost" + ? string.Empty + : "s")}://{origin}{( + origin == "localhost" + ? $":{s_port}" + : string.Empty)}" }), + })); + } + + return s_fido2[origin]; + } + public static void Main(string[] args) { var app = Initialize(args); app.UseSession(); @@ -82,20 +92,25 @@ public static class Program { if (context.Request.Cookies[COOKIE_NAME] == null) return Task.CompletedTask; - context.Response.Cookies.Append(COOKIE_NAME, string.Empty, new CookieOptions { + var cookieOpts = new CookieOptions { Path = "/", Secure = true, HttpOnly = true, - MaxAge = TimeSpan.Zero, - Domain = s_domain, - }); + SameSite = SameSiteMode.None, + MaxAge = TimeSpan.FromSeconds(s_lifetime), + }; + if (string.IsNullOrEmpty(s_domain)) { + cookieOpts.Domain = s_domain; + } + + context.Response.Cookies.Append(COOKIE_NAME, string.Empty, cookieOpts); context.Response.Redirect("/"); return Task.CompletedTask; }); app.MapPost("/auth/key", async (context) => { - var options = s_fido2.GetAssertionOptions( + var options = GetFido2(context).GetAssertionOptions( s_keys, UserVerificationRequirement.Discouraged); context.Session.SetString(SESS_ASSERTION_KEY, JsonSerializer.Serialize(options)); @@ -134,7 +149,7 @@ public static class Program { } } - var res = await s_fido2.MakeAssertionAsync( + var res = await GetFido2(context).MakeAssertionAsync( assertionResponse, opts, pubKey, 0, (_, _) => Task.FromResult(true)); if (res.Status != "ok") { context.Response.StatusCode = 401; @@ -142,17 +157,21 @@ public static class Program { return; } + var cookieOpts = new CookieOptions { + Path = "/", + Secure = true, + HttpOnly = true, + SameSite = SameSiteMode.None, + MaxAge = TimeSpan.FromSeconds(s_lifetime), + }; + if (string.IsNullOrEmpty(s_domain)) { + cookieOpts.Domain = s_domain; + } + context.Response.Cookies.Append( COOKIE_NAME, GenerateToken(connection), - new CookieOptions { - Path = "/", - Secure = true, - HttpOnly = true, - SameSite = SameSiteMode.None, - Domain = s_domain, - MaxAge = TimeSpan.FromSeconds(s_lifetime), - }); + cookieOpts); await context.Response.WriteAsJsonAsync(new { status = "ok" }); }); @@ -162,7 +181,7 @@ public static class Program { Name = "Default User", DisplayName = "Default User", }; - var options = s_fido2.RequestNewCredential( + var options = GetFido2(context).RequestNewCredential( user, new List(), AuthenticatorSelection.Default, @@ -190,7 +209,7 @@ public static class Program { var optsJson = context.Session.GetString(SESS_ATTESTATION_KEY); context.Session.Remove(SESS_ATTESTATION_KEY); var opts = CredentialCreateOptions.FromJson(optsJson); - var cred = await s_fido2.MakeNewCredentialAsync( + var cred = await GetFido2(context).MakeNewCredentialAsync( req.Response, opts, (_, _) => Task.FromResult(true)); var descriptor = new PublicKeyCredentialDescriptor( cred.Result!.CredentialId);