Google login is working
This commit is contained in:
@@ -25,7 +25,6 @@ else if (_isAuthenticated)
|
|||||||
{
|
{
|
||||||
Console.WriteLine("AuthGuard: Checking authentication...");
|
Console.WriteLine("AuthGuard: Checking authentication...");
|
||||||
|
|
||||||
// ZAWSZE sprawdź localStorage przy inicjalizacji
|
|
||||||
_isAuthenticated = await AuthService.CheckAuthenticationAsync();
|
_isAuthenticated = await AuthService.CheckAuthenticationAsync();
|
||||||
_isLoading = false;
|
_isLoading = false;
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
@using Microsoft.Extensions.Configuration
|
@using Microsoft.Extensions.Configuration
|
||||||
@using Bimix.UI.Shared.Services
|
@using Bimix.UI.Shared.Services
|
||||||
@inject IJSRuntime JS
|
@inject IJSRuntime JS
|
||||||
@@ -41,10 +42,37 @@
|
|||||||
private bool _isLoading = false;
|
private bool _isLoading = false;
|
||||||
private string _errorMessage = string.Empty;
|
private string _errorMessage = string.Empty;
|
||||||
private static LoginCard? _instance;
|
private static LoginCard? _instance;
|
||||||
|
private bool _isInitialized = false;
|
||||||
|
|
||||||
protected override void OnInitialized()
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
|
{
|
||||||
|
if (firstRender)
|
||||||
{
|
{
|
||||||
_instance = this;
|
_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()
|
private async Task HandleGoogleSignIn()
|
||||||
@@ -55,55 +83,53 @@
|
|||||||
_errorMessage = string.Empty;
|
_errorMessage = string.Empty;
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
|
|
||||||
var clientId = Configuration["GoogleAuth:ClientId"];
|
await JS.InvokeVoidAsync("requestGoogleSignIn");
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(clientId))
|
|
||||||
{
|
|
||||||
throw new Exception("Google ClientId is not configured.");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
await JS.InvokeVoidAsync("initGoogleSignIn", clientId);
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_errorMessage = "Błąd podczas logownia. Spróbuj ponownie";
|
_errorMessage = "Błąd podczas logowania. Spróbuj ponownie";
|
||||||
_isLoading = false;
|
_isLoading = false;
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[JSInvokable]
|
[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)
|
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._isLoading = false;
|
||||||
_instance._errorMessage = string.Empty;
|
_instance._errorMessage = string.Empty;
|
||||||
|
|
||||||
_instance.NavigationManager.NavigateTo("/dashboard", replace:true);
|
_instance.NavigationManager.NavigateTo("/dashboard", replace: true);
|
||||||
|
|
||||||
await _instance.InvokeAsync(() => _instance.StateHasChanged());
|
await _instance.InvokeAsync(() => _instance.StateHasChanged());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[JSInvokable]
|
[JSInvokable]
|
||||||
public static async Task OnGoggleSignInError(string error)
|
public static async Task OnGoogleSignInError(string error)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"Google SignIn Error: {error}");
|
Console.WriteLine($"Google SignIn Error: {error}");
|
||||||
|
|
||||||
if (_instance != null)
|
if (_instance != null)
|
||||||
{
|
{
|
||||||
_instance._isLoading = false;
|
_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());
|
await _instance.InvokeAsync(() => _instance.StateHasChanged());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@@ -113,9 +139,4 @@
|
|||||||
background: rgba(255, 255, 255, 0.95);
|
background: rgba(255, 255, 255, 0.95);
|
||||||
backdrop-filter: blur(10px);
|
backdrop-filter: blur(10px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.google-signin-button {
|
|
||||||
width: 100%;
|
|
||||||
padding: 12px 24px;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
@@ -1,11 +1,19 @@
|
|||||||
|
using System.Text.Json;
|
||||||
using Microsoft.JSInterop;
|
using Microsoft.JSInterop;
|
||||||
|
|
||||||
namespace Bimix.UI.Shared.Services;
|
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
|
public class AuthService
|
||||||
{
|
{
|
||||||
private readonly IJSRuntime _jsRuntime;
|
private readonly IJSRuntime _jsRuntime;
|
||||||
private bool? _isAuthenticated;
|
private bool? _isAuthenticated;
|
||||||
|
private UserInfo? _userInfo = null;
|
||||||
|
|
||||||
public event Action<bool>? AuthenticationStateChanged;
|
public event Action<bool>? AuthenticationStateChanged;
|
||||||
|
|
||||||
@@ -15,14 +23,22 @@ public class AuthService
|
|||||||
}
|
}
|
||||||
|
|
||||||
public bool IsAuthenticated => _isAuthenticated ?? false;
|
public bool IsAuthenticated => _isAuthenticated ?? false;
|
||||||
|
public UserInfo? CurrentUser => _userInfo;
|
||||||
|
|
||||||
public async Task<bool> CheckAuthenticationAsync()
|
public async Task<bool> CheckAuthenticationAsync()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var token = await _jsRuntime.InvokeAsync<string?>("localStorage.getItem", "google_token");
|
var token = await _jsRuntime.InvokeAsync<string?>("localStorage.getItem", "google_token");
|
||||||
|
var userInfoJson = await _jsRuntime.InvokeAsync<string?>("localStorage.getItem", "user_info");
|
||||||
|
|
||||||
_isAuthenticated = !string.IsNullOrEmpty(token);
|
_isAuthenticated = !string.IsNullOrEmpty(token);
|
||||||
|
|
||||||
|
if (_isAuthenticated.Value && !string.IsNullOrEmpty(userInfoJson))
|
||||||
|
{
|
||||||
|
_userInfo = JsonSerializer.Deserialize<UserInfo>(userInfoJson);
|
||||||
|
}
|
||||||
|
|
||||||
Console.WriteLine($"AuthService.CheckAuthentication: token={(!string.IsNullOrEmpty(token) ? "EXISTS" : "NULL")}, isAuth={_isAuthenticated}");
|
Console.WriteLine($"AuthService.CheckAuthentication: token={(!string.IsNullOrEmpty(token) ? "EXISTS" : "NULL")}, isAuth={_isAuthenticated}");
|
||||||
|
|
||||||
return _isAuthenticated.Value;
|
return _isAuthenticated.Value;
|
||||||
@@ -31,17 +47,26 @@ public class AuthService
|
|||||||
{
|
{
|
||||||
Console.WriteLine($"AuthService.CheckAuthentication ERROR: {ex.Message}");
|
Console.WriteLine($"AuthService.CheckAuthentication ERROR: {ex.Message}");
|
||||||
_isAuthenticated = false;
|
_isAuthenticated = false;
|
||||||
|
_userInfo = null;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SetAuthenticationAsync(string token)
|
public async Task SetAuthenticationAsync(string token, UserInfo? userInfo = null)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await _jsRuntime.InvokeVoidAsync("localStorage.setItem", "google_token", token);
|
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;
|
_isAuthenticated = true;
|
||||||
Console.WriteLine($"AuthService.SetAuthentication: token saved, isAuth={_isAuthenticated}");
|
Console.WriteLine($"AuthService.SetAuthentication: token saved, user={_userInfo?.Email}");
|
||||||
AuthenticationStateChanged?.Invoke(true);
|
AuthenticationStateChanged?.Invoke(true);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -55,8 +80,10 @@ public class AuthService
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
await _jsRuntime.InvokeVoidAsync("localStorage.removeItem", "google_token");
|
await _jsRuntime.InvokeVoidAsync("localStorage.removeItem", "google_token");
|
||||||
|
await _jsRuntime.InvokeVoidAsync("localStorage.removeItem", "user_info");
|
||||||
_isAuthenticated = false;
|
_isAuthenticated = false;
|
||||||
Console.WriteLine($"AuthService.ClearAuthentication: token removed");
|
_userInfo = null;
|
||||||
|
Console.WriteLine($"AuthService.ClearAuthentication: token and user ingfo removed");
|
||||||
AuthenticationStateChanged?.Invoke(false);
|
AuthenticationStateChanged?.Invoke(false);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|||||||
@@ -1,35 +1,119 @@
|
|||||||
|
let googleClient = null;
|
||||||
|
let isSigningIn = false;
|
||||||
|
|
||||||
|
function waitForGoogleApi() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (window.google?.accounts?.oauth2) {
|
||||||
|
resolve(window.google);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
window.initGoogleSignIn = async function(clientId) {
|
||||||
|
if (googleClient) {
|
||||||
|
return googleClient;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!clientId) {
|
const google = await waitForGoogleApi();
|
||||||
throw new Error('ClientId is required');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inicjalizacja Google Sign-In z dynamicznym ClientId
|
googleClient = google.accounts.oauth2.initTokenClient({
|
||||||
google.accounts.id.initialize({
|
|
||||||
client_id: clientId,
|
client_id: clientId,
|
||||||
callback: handleGoogleResponse,
|
scope: 'email profile',
|
||||||
auto_select: false,
|
callback: async (tokenResponse) => {
|
||||||
cancel_on_tap_outside: true
|
try {
|
||||||
});
|
if (tokenResponse.error) {
|
||||||
|
console.error('Token response error:', tokenResponse.error);
|
||||||
|
await DotNet.invokeMethodAsync('Bimix.UI.Shared', 'OnGoogleSignInError',
|
||||||
|
tokenResponse.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Wyświetl popup logowania
|
const userInfo = await fetchUserInfo(tokenResponse.access_token);
|
||||||
google.accounts.id.prompt((notification) => {
|
if (!userInfo) return;
|
||||||
if (notification.isNotDisplayed() || notification.isSkippedMoment()) {
|
|
||||||
console.log('Google Sign-In popup not displayed');
|
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) {
|
} catch (error) {
|
||||||
console.error('Google Sign-In initialization error:', error);
|
console.error('Initiaxcrun xctrace list deviceslization error:', error);
|
||||||
DotNet.invokeMethodAsync('Bimix.UI.Shared', 'OnGoogleSignInError', error.message);
|
await DotNet.invokeMethodAsync('Bimix.UI.Shared', 'OnGoogleSignInError',
|
||||||
|
error.message || 'Failed to initialize Google Sign-In');
|
||||||
|
isSigningIn = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
function handleGoogleResponse(response) {
|
window.requestGoogleSignIn = async function() {
|
||||||
if (response.credential) {
|
if (isSigningIn) {
|
||||||
// Token otrzymany - wyślij do Blazor
|
console.log('Sign-in already in progress');
|
||||||
DotNet.invokeMethodAsync('Bimix.UI.Shared', 'OnGoogleSignInSuccess', response.credential);
|
return;
|
||||||
} else {
|
|
||||||
DotNet.invokeMethodAsync('Bimix.UI.Shared', 'OnGoogleSignInError', 'No credential received');
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
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();
|
||||||
|
};
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
|
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
|
||||||
<link href="_content/MudBlazor/MudBlazor.min.css" rel="stylesheet" />
|
<link href="_content/MudBlazor/MudBlazor.min.css" rel="stylesheet" />
|
||||||
<link href="Bimix.UI.Web.styles.css" rel="stylesheet" />
|
<link href="Bimix.UI.Web.styles.css" rel="stylesheet" />
|
||||||
|
<script src="https://accounts.google.com/gsi/client" async defer></script>
|
||||||
<HeadOutlet />
|
<HeadOutlet />
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
@@ -30,9 +30,7 @@
|
|||||||
|
|
||||||
<script src="_framework/blazor.web.js"></script>
|
<script src="_framework/blazor.web.js"></script>
|
||||||
<script src="_content/MudBlazor/MudBlazor.min.js"></script>
|
<script src="_content/MudBlazor/MudBlazor.min.js"></script>
|
||||||
<script src="https://accounts.google.com/gsi/client" async defer></script>
|
|
||||||
<script src="_content/Bimix.UI.Shared/js/auth.js"></script>
|
<script src="_content/Bimix.UI.Shared/js/auth.js"></script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
Reference in New Issue
Block a user