From f7b9009215cb67803a05adcfbc497222a4dcfe69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Zieli=C5=84ski?= Date: Sun, 9 Nov 2025 19:39:52 +0100 Subject: [PATCH] App login is working --- .../DiunaBI.API/Controllers/AuthController.cs | 87 ++++---- src/Backend/DiunaBI.API/Program.cs | 7 + .../DiunaBI.API/Services/GoogleAuthService.cs | 60 +++++ .../DiunaBI.API/Services/JwtTokenService.cs | 84 +++++++ src/Backend/DiunaBI.API/appsettings.json | 4 +- src/Backend/DiunaBI.Domain/Entities/User.cs | 2 +- .../Components/Dashboard.razor | 43 +++- .../Components/LoginCard.razor | 51 +++-- .../DiunaBI.UI.Shared/MainLayout.razor | 2 +- .../DiunaBI.UI.Shared/Pages/LoginPage.razor | 2 +- .../DiunaBI.UI.Shared/Services/AuthService.cs | 142 ++++++++---- .../DiunaBI.UI.Shared/wwwroot/js/auth.js | 205 ++++++++---------- .../DiunaBI.UI.Web/Components/App.razor | 2 +- tools/http-tests/ProcessQueue.http | 2 +- 14 files changed, 466 insertions(+), 227 deletions(-) create mode 100644 src/Backend/DiunaBI.API/Services/GoogleAuthService.cs create mode 100644 src/Backend/DiunaBI.API/Services/JwtTokenService.cs diff --git a/src/Backend/DiunaBI.API/Controllers/AuthController.cs b/src/Backend/DiunaBI.API/Controllers/AuthController.cs index e0efd42..ed010ae 100644 --- a/src/Backend/DiunaBI.API/Controllers/AuthController.cs +++ b/src/Backend/DiunaBI.API/Controllers/AuthController.cs @@ -1,60 +1,51 @@ -using Google.Apis.Auth; -using Microsoft.AspNetCore.Mvc; -using Microsoft.IdentityModel.Tokens; -using System.IdentityModel.Tokens.Jwt; -using System.Security.Claims; -using System.Text; +using DiunaBI.API.Services; using DiunaBI.Domain.Entities; -using DiunaBI.Infrastructure.Data; -using Microsoft.EntityFrameworkCore; +using Microsoft.AspNetCore.Mvc; namespace DiunaBI.API.Controllers; [ApiController] [Route("api/[controller]")] -// [Authorize] -public class AuthController : Controller +public class AuthController( + GoogleAuthService googleAuthService, + JwtTokenService jwtTokenService, + ILogger logger) + : ControllerBase { - private readonly AppDbContext _db; - private readonly IConfiguration _configuration; - public AuthController( - AppDbContext db, IConfiguration configuration) - { _db = db; _configuration = configuration; } - - [HttpPost] - [Route("apiToken")] - public async Task ApiToken([FromBody] string credential) + [HttpPost("apiToken")] + public async Task ApiToken([FromBody] string idToken) { - var settings = new GoogleJsonWebSignature.ValidationSettings + try { - Audience = new List { _configuration.GetValue("GoogleClientId")! } - }; - var payload = await GoogleJsonWebSignature.ValidateAsync(credential, settings); - var user = _db.Users.AsNoTracking().FirstOrDefault(x => x.Email == payload.Email); - return user != null ? (IActionResult)Ok(JwtGenerator(user)) : Unauthorized(); - } - - private dynamic JwtGenerator(User user) - { - var key = Encoding.ASCII.GetBytes(_configuration.GetValue("Secret")!); - var expirationTime = DateTime.UtcNow.AddMinutes(5); - var tokenDescriptor = new SecurityTokenDescriptor - { - Subject = new ClaimsIdentity(new[] + if (string.IsNullOrEmpty(idToken)) { - new Claim("Id", Guid.NewGuid().ToString()), - new Claim(JwtRegisteredClaimNames.Sub, user.Id.ToString()), - new Claim(JwtRegisteredClaimNames.Jti, - Guid.NewGuid().ToString()) - }), - Expires = expirationTime, - SigningCredentials = new SigningCredentials - (new SymmetricSecurityKey(key), - SecurityAlgorithms.HmacSha512Signature) - }; - var tokenHandler = new JwtSecurityTokenHandler(); - var token = tokenHandler.CreateToken(tokenDescriptor); - var stringToken = tokenHandler.WriteToken(token); - return new { token = stringToken, id = user.Id, expirationTime }; + logger.LogWarning("Empty idToken received"); + return BadRequest("IdToken is required"); + } + + var (isValid, user, error) = await googleAuthService.ValidateGoogleTokenAsync(idToken); + + if (!isValid || user == null) + { + logger.LogWarning("Google token validation failed: {Error}", error); + return Unauthorized(); + } + + var jwt = jwtTokenService.GenerateToken(user); + + logger.LogInformation("User authenticated successfully: {Email}", user.Email); + + return Ok(new + { + token = jwt, + id = user.Id, + expirationTime = DateTime.UtcNow.AddDays(7) // z JwtSettings + }); + } + catch (Exception ex) + { + logger.LogError(ex, "Error during authentication"); + return StatusCode(500, "Internal server error"); + } } } \ No newline at end of file diff --git a/src/Backend/DiunaBI.API/Program.cs b/src/Backend/DiunaBI.API/Program.cs index 799aa23..9b861bf 100644 --- a/src/Backend/DiunaBI.API/Program.cs +++ b/src/Backend/DiunaBI.API/Program.cs @@ -4,6 +4,7 @@ using Microsoft.IdentityModel.Tokens; using System.IdentityModel.Tokens.Jwt; using System.Reflection; using System.Text; +using DiunaBI.API.Services; using DiunaBI.Infrastructure.Data; using DiunaBI.Infrastructure.Services; using Google.Apis.Sheets.v4; @@ -74,6 +75,9 @@ builder.Services.AddAuthentication(options => }; }); +builder.Services.AddScoped(); +builder.Services.AddScoped(); + // Google Sheets dependencies Console.WriteLine("Adding Google Sheets dependencies..."); builder.Services.AddSingleton(); @@ -159,6 +163,9 @@ app.UseAuthorization(); app.MapControllers(); +app.MapGet("/health", () => Results.Ok(new { status = "OK", timestamp = DateTime.UtcNow })) + .AllowAnonymous(); + app.Run(); if (app.Environment.IsProduction()) diff --git a/src/Backend/DiunaBI.API/Services/GoogleAuthService.cs b/src/Backend/DiunaBI.API/Services/GoogleAuthService.cs new file mode 100644 index 0000000..fc01fd5 --- /dev/null +++ b/src/Backend/DiunaBI.API/Services/GoogleAuthService.cs @@ -0,0 +1,60 @@ +using DiunaBI.Domain.Entities; +using DiunaBI.Infrastructure.Data; +using Google.Apis.Auth; +using Microsoft.EntityFrameworkCore; + +namespace DiunaBI.API.Services; + +public class GoogleAuthService(AppDbContext context, IConfiguration configuration, ILogger logger) +{ + private readonly AppDbContext _context = context; + private readonly IConfiguration _configuration = configuration; + private readonly ILogger _logger = logger; + + public async Task<(bool IsValid, User? user, string? error)> ValidateGoogleTokenAsync(string idToken) + { + try + { + var clientId = _configuration["GoogleAuth:ClientId"]; + if (string.IsNullOrEmpty(clientId)) + { + _logger.LogError("Google Auth Client Id is not configured"); + return (false, null, "Google Auth Client Id is not configured"); + } + + var payload = await GoogleJsonWebSignature.ValidateAsync(idToken, + new GoogleJsonWebSignature.ValidationSettings + { + Audience = new[] { clientId } + }); + + _logger.LogInformation("Google token validated for user: {Email}", payload.Email); + + var user = await _context.Users + .FirstOrDefaultAsync(x => x.Email == payload.Email); + + if (user == null) + { + _logger.LogError("User not found in DiunaBI database: {Email}", payload.Email); + return (false, null, "User not found in DiunaBI database"); + } + + user.UserName = payload.Name; + + await _context.SaveChangesAsync(); + + _logger.LogInformation("User logged in: {Email}", payload.Email); + + return (true, user, null); + } + catch (InvalidJwtException ex) + { + _logger.LogError(ex, "Invalid JWT token"); + return (false, null, "Invalid JWT token"); + } catch (Exception ex) + { + _logger.LogError(ex, "Error validating Google token"); + return (false, null, "Error validating Google token"); + } + } +} \ No newline at end of file diff --git a/src/Backend/DiunaBI.API/Services/JwtTokenService.cs b/src/Backend/DiunaBI.API/Services/JwtTokenService.cs new file mode 100644 index 0000000..f43ca2b --- /dev/null +++ b/src/Backend/DiunaBI.API/Services/JwtTokenService.cs @@ -0,0 +1,84 @@ +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using DiunaBI.Domain.Entities; +using Microsoft.IdentityModel.Tokens; + +namespace DiunaBI.API.Services; + +public class JwtTokenService(IConfiguration configuration, ILogger logger) +{ + private readonly IConfiguration _configuration = configuration; + private readonly ILogger _logger = logger; + + public string GenerateToken(User user) + { + var jwtSettings = _configuration.GetSection("JwtSettings"); + var securityKey = jwtSettings["SecurityKey"]; + var issuer = jwtSettings["Issuer"]; + var audience = jwtSettings["Audience"]; + var expiryDays = int.Parse(jwtSettings["ExpiryDays"] ?? "7"); + + var claims = new[] + { + new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()), + new Claim(ClaimTypes.Email, user.Email), + new Claim(ClaimTypes.Name, user.UserName), + new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), + new Claim(JwtRegisteredClaimNames.Iat, new DateTimeOffset(DateTime.UtcNow).ToUnixTimeSeconds().ToString(), + ClaimValueTypes.Integer64) + }; + + var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(securityKey)); + var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); + + var token = new JwtSecurityToken( + issuer: issuer, + audience: audience, + claims: claims, + expires: DateTime.UtcNow.AddDays(expiryDays), + signingCredentials: creds + ); + + var tokenString = new JwtSecurityTokenHandler().WriteToken(token); + + _logger.LogInformation("Generated JWT token for user: {Email}", user.Email); + + return tokenString; + } + + public ClaimsPrincipal? ValidateToken(string token) + { + try + { + var jwtSettings = _configuration.GetSection("JwtSettings"); + var secretKey = jwtSettings["SecretKey"]; + var issuer = jwtSettings["Issuer"]; + var audience = jwtSettings["Audience"]; + + var tokenHandler = new JwtSecurityTokenHandler(); + var key = Encoding.UTF8.GetBytes(secretKey); + + var validationParameters = new TokenValidationParameters + { + ValidateIssuer = true, + ValidateAudience = true, + ValidateLifetime = true, + ValidateIssuerSigningKey = true, + ValidIssuer = issuer, + ValidAudience = audience, + IssuerSigningKey = new SymmetricSecurityKey(key), + ClockSkew = TimeSpan.Zero + }; + + var principal = tokenHandler.ValidateToken(token, validationParameters, out _); + return principal; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error validating JWT token"); + return null; + } + } +} + diff --git a/src/Backend/DiunaBI.API/appsettings.json b/src/Backend/DiunaBI.API/appsettings.json index 4f1cdbd..241213c 100644 --- a/src/Backend/DiunaBI.API/appsettings.json +++ b/src/Backend/DiunaBI.API/appsettings.json @@ -50,7 +50,9 @@ "SQLDatabase": "#{db-connection-string}#" }, "InstanceName": "#{app-environment}#", - "GoogleClientId": "#{google-backend-login-client-id}#", + "GoogleAuth": { + "ClientId": "#{google-backend-login-client-id}#" + }, "Secret": "#{google-backend-login-secret}#", "apiKey": "#{api-key}#", "powerBI-user": "#{powerBI-user}#", diff --git a/src/Backend/DiunaBI.Domain/Entities/User.cs b/src/Backend/DiunaBI.Domain/Entities/User.cs index 6b51b94..fc24865 100644 --- a/src/Backend/DiunaBI.Domain/Entities/User.cs +++ b/src/Backend/DiunaBI.Domain/Entities/User.cs @@ -8,7 +8,7 @@ public class User #region Properties public Guid Id { get; init; } public string? Email { get; init; } - public string? UserName { get; init; } + public string? UserName { get; set; } public DateTime CreatedAt { get; init; } #endregion } \ No newline at end of file diff --git a/src/Backend/DiunaBI.UI.Shared/Components/Dashboard.razor b/src/Backend/DiunaBI.UI.Shared/Components/Dashboard.razor index d662978..dc9acbd 100644 --- a/src/Backend/DiunaBI.UI.Shared/Components/Dashboard.razor +++ b/src/Backend/DiunaBI.UI.Shared/Components/Dashboard.razor @@ -1,5 +1,44 @@ @page "/dashboard" +@using DiunaBI.UI.Shared.Services @using MudBlazor +@inject AuthService AuthService +@inject NavigationManager NavigationManager -Dashboard -

Tutaj znajdzie się panel ogólny aplikacji

\ No newline at end of file +Witaj w DiunaBI! + +@if (AuthService.IsAuthenticated && AuthService.CurrentUser != null) +{ + + + + @if (!string.IsNullOrEmpty(AuthService.CurrentUser.AvatarUrl)) + { + + Avatar + + } + else + { + + @(AuthService.CurrentUser.FullName.Length > 0 ? AuthService.CurrentUser.FullName.Substring(0, 1) : "?") + + } + + + @AuthService.CurrentUser.FullName + @AuthService.CurrentUser.Email + + + + + ✅ Zalogowano przez Google + + + +} +else +{ + + Nie jesteś zalogowany + +} \ No newline at end of file diff --git a/src/Backend/DiunaBI.UI.Shared/Components/LoginCard.razor b/src/Backend/DiunaBI.UI.Shared/Components/LoginCard.razor index 9136381..ef3087b 100644 --- a/src/Backend/DiunaBI.UI.Shared/Components/LoginCard.razor +++ b/src/Backend/DiunaBI.UI.Shared/Components/LoginCard.razor @@ -8,7 +8,7 @@