diff --git a/Webshop.Api/Controllers/Auth/AuthController.cs b/Webshop.Api/Controllers/Auth/AuthController.cs index 027af3a..c176a93 100644 --- a/Webshop.Api/Controllers/Auth/AuthController.cs +++ b/Webshop.Api/Controllers/Auth/AuthController.cs @@ -11,7 +11,7 @@ using Webshop.Application.Services.Auth; namespace Webshop.Api.Controllers.Auth { [ApiController] - [Route("api/v1/auth")] + [Route("api/v1/[controller]")] public class AuthController : ControllerBase { private readonly IAuthService _authService; diff --git a/Webshop.Api/appsettings.json b/Webshop.Api/appsettings.json index 336370f..f1aedb5 100644 --- a/Webshop.Api/appsettings.json +++ b/Webshop.Api/appsettings.json @@ -1,4 +1,7 @@ { + "ShopInfo": { + "Name": "tzbre Webshop" + }, "Logging": { "LogLevel": { "Default": "Information", diff --git a/Webshop.Application/Services/Auth/AuthService.cs b/Webshop.Application/Services/Auth/AuthService.cs index e44d5d5..86f2019 100644 --- a/Webshop.Application/Services/Auth/AuthService.cs +++ b/Webshop.Application/Services/Auth/AuthService.cs @@ -68,50 +68,6 @@ namespace Webshop.Application.Services.Auth return ServiceResult.Ok(); } - public async Task> LoginUserAsync(LoginRequestDto request) - { - var user = await _userManager.FindByEmailAsync(request.Email); - if (user == null || !await _userManager.CheckPasswordAsync(user, request.Password)) - { - return ServiceResult.Fail(ServiceResultType.Unauthorized, "Ungültige Anmeldeinformationen."); - } - - if (!await _userManager.IsEmailConfirmedAsync(user)) - { - return ServiceResult.Fail(ServiceResultType.Unauthorized, "E-Mail-Adresse wurde noch nicht bestätigt."); - } - - var roles = await _userManager.GetRolesAsync(user); - var token = GenerateJwtToken(user, roles); - - var response = new AuthResponseDto - { - IsAuthSuccessful = true, - Token = token, - UserId = user.Id, - Email = user.Email, - Roles = roles.ToList() - }; - return ServiceResult.Ok(response); - } - - public async Task> LoginAdminAsync(LoginRequestDto request) - { - var loginResult = await LoginUserAsync(request); - if (loginResult.Type != ServiceResultType.Success) - { - return loginResult; - } - - var user = await _userManager.FindByEmailAsync(request.Email); - if (user == null || !await _userManager.IsInRoleAsync(user, "Admin")) - { - return ServiceResult.Fail(ServiceResultType.Forbidden, "Keine Berechtigung für den Admin-Zugang."); - } - - return loginResult; - } - public async Task ConfirmEmailAsync(string userId, string token) { var user = await _userManager.FindByIdAsync(userId); @@ -127,15 +83,11 @@ namespace Webshop.Application.Services.Auth public async Task ResendEmailConfirmationAsync(string email) { var user = await _userManager.FindByEmailAsync(email); - if (user == null) + if (user == null || await _userManager.IsEmailConfirmedAsync(user)) { - // Aus Sicherheitsgründen nicht verraten, ob die E-Mail existiert + // Aus Sicherheitsgründen immer eine positive Antwort geben return ServiceResult.Ok(); } - if (await _userManager.IsEmailConfirmedAsync(user)) - { - return ServiceResult.Fail(ServiceResultType.InvalidInput, "E-Mail-Adresse ist bereits bestätigt."); - } await SendEmailConfirmationEmail(user); return ServiceResult.Ok(); @@ -151,15 +103,21 @@ namespace Webshop.Application.Services.Auth var token = await _userManager.GeneratePasswordResetTokenAsync(user); var encodedToken = HttpUtility.UrlEncode(token); - var clientUrl = _configuration["App:ClientUrl"]!; + var clientUrl = _configuration["App:ClientUrl"] ?? "http://localhost:3000"; var resetLink = $"{clientUrl}/reset-password?email={HttpUtility.UrlEncode(request.Email)}&token={encodedToken}"; - // << KORREKTUR: EmailMessage verwenden >> + var emailHtmlBody = await LoadAndFormatEmailTemplate( + titel: "Setzen Sie Ihr Passwort zurück", + haupttext: "Sie haben eine Anfrage zum Zurücksetzen Ihres Passworts gesendet. Klicken Sie auf den Button unten, um ein neues Passwort festzulegen.", + callToActionText: "Passwort zurücksetzen", + callToActionLink: resetLink + ); + var message = new EmailMessage(); message.To.Add(request.Email); message.From = _configuration["Resend:FromEmail"]!; message.Subject = "Anleitung zum Zurücksetzen Ihres Passworts"; - message.HtmlBody = $"

Passwort zurücksetzen

Bitte setzen Sie Ihr Passwort zurück, indem Sie auf diesen Link klicken: Passwort zurücksetzen

"; + message.HtmlBody = emailHtmlBody; await _resend.EmailSendAsync(message); return ServiceResult.Ok(); @@ -180,6 +138,8 @@ namespace Webshop.Application.Services.Auth : ServiceResult.Fail(ServiceResultType.InvalidInput, string.Join(" ", result.Errors.Select(e => e.Description))); } + // --- Private Helper-Methoden --- + private async Task SendEmailConfirmationEmail(ApplicationUser user) { var token = await _userManager.GenerateEmailConfirmationTokenAsync(user); @@ -187,15 +147,43 @@ namespace Webshop.Application.Services.Auth var clientUrl = _configuration["App:ClientUrl"]!; var confirmationLink = $"{clientUrl}/confirm-email?userId={user.Id}&token={encodedToken}"; - // << KORREKTUR: EmailMessage verwenden >> + var emailHtmlBody = await LoadAndFormatEmailTemplate( + titel: "Bestätigen Sie Ihre E-Mail-Adresse", + haupttext: "Vielen Dank für Ihre Registrierung! Bitte klicken Sie auf den Button unten, um Ihr Konto zu aktivieren.", + callToActionText: "Konto aktivieren", + callToActionLink: confirmationLink + ); + var message = new EmailMessage(); message.To.Add(user.Email); message.From = _configuration["Resend:FromEmail"]!; - message.Subject = "Bestätigen Sie Ihre E-Mail-Adresse"; - message.HtmlBody = $"

Willkommen!

Bitte bestätigen Sie Ihre E-Mail-Adresse, indem Sie auf diesen Link klicken: Bestätigen

"; + message.Subject = "Willkommen! Bitte bestätigen Sie Ihre E-Mail-Adresse"; + message.HtmlBody = emailHtmlBody; await _resend.EmailSendAsync(message); } + private async Task LoadAndFormatEmailTemplate(string titel, string haupttext, string callToActionText, string callToActionLink) + { + var templatePath = Path.Combine(AppContext.BaseDirectory, "Templates", "_EmailTemplate.html"); + + if (!File.Exists(templatePath)) + { + // Fallback, falls die Vorlagendatei nicht gefunden wird + return $"

{titel}

{haupttext}

{callToActionText}"; + } + + var template = await File.ReadAllTextAsync(templatePath); + + template = template.Replace("{{ShopName}}", _configuration["ShopInfo:Name"] ?? "Ihr Webshop"); + template = template.Replace("{{Titel}}", titel); + template = template.Replace("{{Haupttext}}", haupttext); + template = template.Replace("{{CallToActionText}}", callToActionText); + template = template.Replace("{{CallToActionLink}}", callToActionLink); + template = template.Replace("{{Jahr}}", DateTime.UtcNow.Year.ToString()); + + return template; + } + private string GenerateJwtToken(ApplicationUser user, IList roles) { var claims = new List @@ -225,5 +213,49 @@ namespace Webshop.Application.Services.Auth return new JwtSecurityTokenHandler().WriteToken(token); } + + public async Task> LoginAdminAsync(LoginRequestDto request) + { + var loginResult = await LoginUserAsync(request); + if (loginResult.Type != ServiceResultType.Success) + { + return loginResult; + } + + var user = await _userManager.FindByEmailAsync(request.Email); + if (user == null || !await _userManager.IsInRoleAsync(user, "Admin")) + { + return ServiceResult.Fail(ServiceResultType.Forbidden, "Keine Berechtigung für den Admin-Zugang."); + } + + return loginResult; + } + + public async Task> LoginUserAsync(LoginRequestDto request) + { + var user = await _userManager.FindByEmailAsync(request.Email); + if (user == null || !await _userManager.CheckPasswordAsync(user, request.Password)) + { + return ServiceResult.Fail(ServiceResultType.Unauthorized, "Ungültige Anmeldeinformationen."); + } + + if (!await _userManager.IsEmailConfirmedAsync(user)) + { + return ServiceResult.Fail(ServiceResultType.Unauthorized, "E-Mail-Adresse wurde noch nicht bestätigt."); + } + + var roles = await _userManager.GetRolesAsync(user); + var token = GenerateJwtToken(user, roles); + + var response = new AuthResponseDto + { + IsAuthSuccessful = true, + Token = token, + UserId = user.Id, + Email = user.Email, + Roles = roles.ToList() + }; + return ServiceResult.Ok(response); + } } } \ No newline at end of file diff --git a/Webshop.Infrastructure/Templates/_EmailTemplate.html b/Webshop.Infrastructure/Templates/_EmailTemplate.html new file mode 100644 index 0000000..ec63aa7 --- /dev/null +++ b/Webshop.Infrastructure/Templates/_EmailTemplate.html @@ -0,0 +1,36 @@ + + + + + + + + +
+
+

{{ShopName}}

+
+
+

{{Titel}}

+

{{Haupttext}}

+

+ {{CallToActionText}} +

+

Wenn der Button nicht funktioniert, können Sie auch diesen Link in Ihren Browser kopieren:

+

{{CallToActionLink}}

+
+ +
+ + \ No newline at end of file diff --git a/Webshop.Infrastructure/Webshop.Infrastructure.csproj b/Webshop.Infrastructure/Webshop.Infrastructure.csproj index 567e0e4..751bdd1 100644 --- a/Webshop.Infrastructure/Webshop.Infrastructure.csproj +++ b/Webshop.Infrastructure/Webshop.Infrastructure.csproj @@ -6,6 +6,10 @@ enable + + + +