diff --git a/Bimix.UI.Shared/Components/AuthGuard.razor b/Bimix.UI.Shared/Components/AuthGuard.razor index 225a102..06d437f 100644 --- a/Bimix.UI.Shared/Components/AuthGuard.razor +++ b/Bimix.UI.Shared/Components/AuthGuard.razor @@ -25,7 +25,6 @@ else if (_isAuthenticated) { Console.WriteLine("AuthGuard: Checking authentication..."); - // ZAWSZE sprawdź localStorage przy inicjalizacji _isAuthenticated = await AuthService.CheckAuthenticationAsync(); _isLoading = false; diff --git a/Bimix.UI.Shared/Components/LoginCard.razor b/Bimix.UI.Shared/Components/LoginCard.razor index db146c1..6ce8c58 100644 --- a/Bimix.UI.Shared/Components/LoginCard.razor +++ b/Bimix.UI.Shared/Components/LoginCard.razor @@ -1,3 +1,4 @@ + @using Microsoft.Extensions.Configuration @using Bimix.UI.Shared.Services @inject IJSRuntime JS @@ -41,10 +42,37 @@ private bool _isLoading = false; private string _errorMessage = string.Empty; private static LoginCard? _instance; + private bool _isInitialized = false; - protected override void OnInitialized() + protected override async Task OnAfterRenderAsync(bool firstRender) { - _instance = this; + if (firstRender) + { + _instance = this; + await InitializeGoogleSignIn(); + } + } + + private async Task InitializeGoogleSignIn() + { + try + { + if (_isInitialized) return; + + var clientId = Configuration["GoogleAuth:ClientId"]; + if (string.IsNullOrEmpty(clientId)) + { + throw new Exception("Google ClientId is not configured."); + } + + await JS.InvokeVoidAsync("initGoogleSignIn", clientId); + _isInitialized = true; + } + catch (Exception ex) + { + _errorMessage = "Błąd inicjalizacji Google Sign-In."; + Console.Error.WriteLine($"Google Sign-In initialization error: {ex.Message}"); + } } private async Task HandleGoogleSignIn() @@ -55,55 +83,53 @@ _errorMessage = string.Empty; StateHasChanged(); - var clientId = Configuration["GoogleAuth:ClientId"]; - - if (string.IsNullOrEmpty(clientId)) - { - throw new Exception("Google ClientId is not configured."); - } - - - await JS.InvokeVoidAsync("initGoogleSignIn", clientId); + await JS.InvokeVoidAsync("requestGoogleSignIn"); } catch (Exception ex) { - _errorMessage = "Błąd podczas logownia. Spróbuj ponownie"; + _errorMessage = "Błąd podczas logowania. Spróbuj ponownie"; _isLoading = false; StateHasChanged(); } } [JSInvokable] - public static async Task OnGoogleSignInSuccess(string idToken) + public static async Task OnGoogleSignInSuccess(string accessToken, string fullName, string email, string avatarUrl) { - Console.WriteLine($"Google ID Token: {idToken}"); + Console.WriteLine($"Google Sign-In Success: {email}"); if (_instance != null) { - await _instance.AuthService.SetAuthenticationAsync(idToken); + var userInfo = new UserInfo + { + FullName = fullName, + Email = email, + AvatarUrl = avatarUrl + }; + + await _instance.AuthService.SetAuthenticationAsync(accessToken, userInfo); _instance._isLoading = false; _instance._errorMessage = string.Empty; - _instance.NavigationManager.NavigateTo("/dashboard", replace:true); + _instance.NavigationManager.NavigateTo("/dashboard", replace: true); await _instance.InvokeAsync(() => _instance.StateHasChanged()); } } [JSInvokable] - public static async Task OnGoggleSignInError(string error) + public static async Task OnGoogleSignInError(string error) { Console.WriteLine($"Google SignIn Error: {error}"); if (_instance != null) { _instance._isLoading = false; - _instance._errorMessage = "Błąd logowanie Google. Spróbuj ponownie"; + _instance._errorMessage = "Błąd logowania Google. Spróbuj ponownie"; await _instance.InvokeAsync(() => _instance.StateHasChanged()); } } - } \ No newline at end of file diff --git a/Bimix.UI.Shared/Services/AuthService.cs b/Bimix.UI.Shared/Services/AuthService.cs index cb99e6b..0099663 100644 --- a/Bimix.UI.Shared/Services/AuthService.cs +++ b/Bimix.UI.Shared/Services/AuthService.cs @@ -1,11 +1,19 @@ +using System.Text.Json; using Microsoft.JSInterop; namespace Bimix.UI.Shared.Services; +public class UserInfo +{ + public string FullName { get; set; } = string.Empty; + public string Email { get; set; } = string.Empty; + public string AvatarUrl { get; set; } = string.Empty; +} public class AuthService { private readonly IJSRuntime _jsRuntime; - private bool? _isAuthenticated; + private bool? _isAuthenticated; + private UserInfo? _userInfo = null; public event Action? AuthenticationStateChanged; @@ -15,13 +23,21 @@ public class AuthService } public bool IsAuthenticated => _isAuthenticated ?? false; + public UserInfo? CurrentUser => _userInfo; public async Task CheckAuthenticationAsync() { try { var token = await _jsRuntime.InvokeAsync("localStorage.getItem", "google_token"); + var userInfoJson = await _jsRuntime.InvokeAsync("localStorage.getItem", "user_info"); + _isAuthenticated = !string.IsNullOrEmpty(token); + + if (_isAuthenticated.Value && !string.IsNullOrEmpty(userInfoJson)) + { + _userInfo = JsonSerializer.Deserialize(userInfoJson); + } Console.WriteLine($"AuthService.CheckAuthentication: token={(!string.IsNullOrEmpty(token) ? "EXISTS" : "NULL")}, isAuth={_isAuthenticated}"); @@ -31,17 +47,26 @@ public class AuthService { Console.WriteLine($"AuthService.CheckAuthentication ERROR: {ex.Message}"); _isAuthenticated = false; + _userInfo = null; return false; } } - public async Task SetAuthenticationAsync(string token) + public async Task SetAuthenticationAsync(string token, UserInfo? userInfo = null) { try { await _jsRuntime.InvokeVoidAsync("localStorage.setItem", "google_token", token); + + if (userInfo != null) + { + _userInfo = userInfo; + var userInfoJson = JsonSerializer.Serialize(userInfo); + await _jsRuntime.InvokeVoidAsync("localStorage.setItem", "user_info", userInfoJson); + } + _isAuthenticated = true; - Console.WriteLine($"AuthService.SetAuthentication: token saved, isAuth={_isAuthenticated}"); + Console.WriteLine($"AuthService.SetAuthentication: token saved, user={_userInfo?.Email}"); AuthenticationStateChanged?.Invoke(true); } catch (Exception ex) @@ -55,8 +80,10 @@ public class AuthService try { await _jsRuntime.InvokeVoidAsync("localStorage.removeItem", "google_token"); + await _jsRuntime.InvokeVoidAsync("localStorage.removeItem", "user_info"); _isAuthenticated = false; - Console.WriteLine($"AuthService.ClearAuthentication: token removed"); + _userInfo = null; + Console.WriteLine($"AuthService.ClearAuthentication: token and user ingfo removed"); AuthenticationStateChanged?.Invoke(false); } catch (Exception ex) diff --git a/Bimix.UI.Shared/wwwroot/js/auth.js b/Bimix.UI.Shared/wwwroot/js/auth.js index 3ed0254..5fb6860 100644 --- a/Bimix.UI.Shared/wwwroot/js/auth.js +++ b/Bimix.UI.Shared/wwwroot/js/auth.js @@ -1,35 +1,119 @@ -window.initGoogleSignIn = async function(clientId) { - try { - if (!clientId) { - throw new Error('ClientId is required'); +let googleClient = null; +let isSigningIn = false; + +function waitForGoogleApi() { + return new Promise((resolve, reject) => { + if (window.google?.accounts?.oauth2) { + resolve(window.google); + return; } - // Inicjalizacja Google Sign-In z dynamicznym ClientId - google.accounts.id.initialize({ - client_id: clientId, - callback: handleGoogleResponse, - auto_select: false, - cancel_on_tap_outside: true - }); + const maxAttempts = 20; + let attempts = 0; + + const checkGoogle = setInterval(() => { + attempts++; + if (window.google?.accounts?.oauth2) { + clearInterval(checkGoogle); + resolve(window.google); + } else if (attempts >= maxAttempts) { + clearInterval(checkGoogle); + reject(new Error('Google OAuth2 API failed to load within the timeout period')); + } + }, 100); + }); +} - // Wyświetl popup logowania - google.accounts.id.prompt((notification) => { - if (notification.isNotDisplayed() || notification.isSkippedMoment()) { - console.log('Google Sign-In popup not displayed'); +async function handleAuthError(error, context = '') { + const errorMessage = error?.message || error?.type || error?.toString() || 'Unknown error'; + const fullError = `${context}: ${errorMessage}`; + console.error('Google Auth Error:', { context, error, fullError }); + await DotNet.invokeMethodAsync('Bimix.UI.Shared', 'OnGoogleSignInError', fullError); +} + +async function fetchUserInfo(accessToken) { + const response = await fetch('https://www.googleapis.com/oauth2/v3/userinfo', { + headers: { 'Authorization': `Bearer ${accessToken}` } + }); + + if (!response.ok) { + const errorText = await response.text(); + console.error('Failed to fetch user info:', errorText); + await DotNet.invokeMethodAsync('Bimix.UI.Shared', 'OnGoogleSignInError', + `Failed to fetch user info: HTTP ${response.status}`); + return null; + } + + return await response.json(); +} + +window.initGoogleSignIn = async function(clientId) { + if (googleClient) { + return googleClient; + } + + try { + const google = await waitForGoogleApi(); + + googleClient = google.accounts.oauth2.initTokenClient({ + client_id: clientId, + scope: 'email profile', + callback: async (tokenResponse) => { + try { + if (tokenResponse.error) { + console.error('Token response error:', tokenResponse.error); + await DotNet.invokeMethodAsync('Bimix.UI.Shared', 'OnGoogleSignInError', + tokenResponse.error); + return; + } + + const userInfo = await fetchUserInfo(tokenResponse.access_token); + if (!userInfo) return; + + await DotNet.invokeMethodAsync('Bimix.UI.Shared', 'OnGoogleSignInSuccess', + tokenResponse.access_token, + userInfo.name || '', + userInfo.email || '', + userInfo.picture || '' + ); + } catch (error) { + console.error('Callback error:', error); + await DotNet.invokeMethodAsync('Bimix.UI.Shared', 'OnGoogleSignInError', + error.message || 'Unknown callback error'); + } finally { + isSigningIn = false; + } + }, + error_callback: async (error) => { + console.error('OAuth flow error:', error); + await DotNet.invokeMethodAsync('Bimix.UI.Shared', 'OnGoogleSignInError', + error.type || 'OAuth flow error'); + isSigningIn = false; } }); + return googleClient; } catch (error) { - console.error('Google Sign-In initialization error:', error); - DotNet.invokeMethodAsync('Bimix.UI.Shared', 'OnGoogleSignInError', error.message); + console.error('Initiaxcrun xctrace list deviceslization error:', error); + await DotNet.invokeMethodAsync('Bimix.UI.Shared', 'OnGoogleSignInError', + error.message || 'Failed to initialize Google Sign-In'); + isSigningIn = false; } }; -function handleGoogleResponse(response) { - if (response.credential) { - // Token otrzymany - wyślij do Blazor - DotNet.invokeMethodAsync('Bimix.UI.Shared', 'OnGoogleSignInSuccess', response.credential); - } else { - DotNet.invokeMethodAsync('Bimix.UI.Shared', 'OnGoogleSignInError', 'No credential received'); +window.requestGoogleSignIn = async function() { + if (isSigningIn) { + console.log('Sign-in already in progress'); + return; } -} + + if (!googleClient) { + console.error('Google Sign-In not initialized'); + await DotNet.invokeMethodAsync('Bimix.UI.Shared', 'OnGoogleSignInError', + 'Google Sign-In not initialized. Call initGoogleSignIn first.'); + return; + } + + isSigningIn = true; + googleClient.requestAccessToken(); +}; \ No newline at end of file diff --git a/Bimix.UI.Web/Components/App.razor b/Bimix.UI.Web/Components/App.razor index c0ae221..2b804f5 100644 --- a/Bimix.UI.Web/Components/App.razor +++ b/Bimix.UI.Web/Components/App.razor @@ -9,7 +9,7 @@ - + @@ -30,9 +30,7 @@ - - \ No newline at end of file