using Thalos.Bff.Application.Security.Oidc; namespace Thalos.Bff.Application.UnitTests; public class GoogleOidcFlowServiceTests { [Fact] public void BuildStartContext_WhenReturnUrlAllowed_PreservesCallerReturnUrl() { var service = new GoogleOidcFlowService(CreateOptions(), "state-signing-secret"); var context = service.BuildStartContext("https://furniture-display-demo.dream-views.com/dashboard", "tenant-1"); Assert.Equal("https://furniture-display-demo.dream-views.com/dashboard", context.Payload.ReturnUrl); Assert.Equal("tenant-1", context.Payload.TenantId); Assert.Contains("code_challenge_method=S256", context.AuthorizationUrl); Assert.Contains("state=", context.AuthorizationUrl); Assert.Contains("nonce=", context.AuthorizationUrl); } [Fact] public void BuildStartContext_WhenReturnUrlNotAllowed_UsesDefaultReturnUrl() { var service = new GoogleOidcFlowService(CreateOptions(), "state-signing-secret"); var context = service.BuildStartContext("https://malicious.example.com/redirect", "tenant-1"); Assert.Equal("https://auth.dream-views.com/", context.Payload.ReturnUrl); } [Fact] public void TryValidateCallbackState_WhenStateMatchesAndNotExpired_ReturnsTrue() { var service = new GoogleOidcFlowService(CreateOptions(), "state-signing-secret"); var start = service.BuildStartContext("https://auth.dream-views.com/", "tenant-1"); var ok = service.TryValidateCallbackState(start.EncodedState, start.Payload.State, out var payload); Assert.True(ok); Assert.Equal(start.Payload.CodeVerifier, payload.CodeVerifier); Assert.Equal(start.Payload.TenantId, payload.TenantId); } [Fact] public void TryValidateCallbackState_WhenStateTampered_ReturnsFalse() { var service = new GoogleOidcFlowService(CreateOptions(), "state-signing-secret"); var start = service.BuildStartContext("https://auth.dream-views.com/", "tenant-1"); var ok = service.TryValidateCallbackState(start.EncodedState, $"{start.Payload.State}-tamper", out _); Assert.False(ok); } [Fact] public void BuildFailureRedirectUrl_WhenReturnUrlAllowed_PreservesCallerHostAndAddsErrorContext() { var service = new GoogleOidcFlowService(CreateOptions(), "state-signing-secret"); var redirectUrl = service.BuildFailureRedirectUrl( "https://furniture-display-demo.dream-views.com/dashboard", "oidc_exchange_failed", "corr-123"); Assert.StartsWith("https://furniture-display-demo.dream-views.com/dashboard", redirectUrl, StringComparison.Ordinal); Assert.Contains("authError=oidc_exchange_failed", redirectUrl, StringComparison.Ordinal); Assert.Contains("correlationId=corr-123", redirectUrl, StringComparison.Ordinal); } [Fact] public void BuildFailureRedirectUrl_WhenReturnUrlNotAllowed_UsesDefaultReturnUrl() { var service = new GoogleOidcFlowService(CreateOptions(), "state-signing-secret"); var redirectUrl = service.BuildFailureRedirectUrl( "https://malicious.example.com/redirect", "oidc_state_invalid", "corr-456"); Assert.StartsWith("https://auth.dream-views.com/", redirectUrl, StringComparison.Ordinal); Assert.Contains("authError=oidc_state_invalid", redirectUrl, StringComparison.Ordinal); } private static GoogleOidcOptions CreateOptions() { return new GoogleOidcOptions( "client-id-1", "client-secret-1", "https://auth.dream-views.com/api/identity/oidc/google/callback", "https://accounts.google.com/o/oauth2/v2/auth", "https://oauth2.googleapis.com/token", "openid profile email", "https://auth.dream-views.com/", "demo-tenant", ["auth.dream-views.com", "furniture-display-demo.dream-views.com"], TimeSpan.FromMinutes(10)); } }