From fba87a1694638d9fcb81d3bd91b38372e7a44c73 Mon Sep 17 00:00:00 2001 From: "Tizian.Breuch" Date: Fri, 5 Sep 2025 10:47:43 +0200 Subject: [PATCH] resend integration --- .../Controllers/Auth/AuthController.cs | 131 ++++++--- Webshop.Api/Program.cs | 8 +- Webshop.Api/appsettings.json | 9 +- .../DTOs/Auth/ForgotPasswordRequestDto.cs | 12 + .../DTOs/Auth/ResetPasswordDto.cs | 23 ++ .../DTOs/Email/ChangeEmailRequestDto.cs | 4 +- .../ResendEmailConfirmationRequestDto.cs | 3 +- Webshop.Application/ServiceResult.cs | 4 +- .../Services/Auth/AuthService.cs | 249 +++++++++--------- .../Services/Auth/IAuthService.cs | 15 +- 10 files changed, 283 insertions(+), 175 deletions(-) create mode 100644 Webshop.Application/DTOs/Auth/ForgotPasswordRequestDto.cs create mode 100644 Webshop.Application/DTOs/Auth/ResetPasswordDto.cs diff --git a/Webshop.Api/Controllers/Auth/AuthController.cs b/Webshop.Api/Controllers/Auth/AuthController.cs index 6325205..027af3a 100644 --- a/Webshop.Api/Controllers/Auth/AuthController.cs +++ b/Webshop.Api/Controllers/Auth/AuthController.cs @@ -1,92 +1,149 @@ -using Microsoft.AspNetCore.Mvc; +// src/Webshop.Api/Controllers/Auth/AuthController.cs +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using System.Threading.Tasks; +using Webshop.Application; using Webshop.Application.DTOs.Auth; using Webshop.Application.DTOs.Email; using Webshop.Application.Services.Auth; -using Microsoft.AspNetCore.Authorization; -using System.ComponentModel.DataAnnotations; -using Webshop.Application.Services.Customers; -namespace Webshop.Api.Controllers.Auth // Beachten Sie den Namespace +namespace Webshop.Api.Controllers.Auth { [ApiController] - [Route("api/v1/auth/[controller]")] // z.B. /api/v1/auth + [Route("api/v1/auth")] public class AuthController : ControllerBase { private readonly IAuthService _authService; - private readonly ICustomerService _customerService; - - public AuthController(IAuthService authService, ICustomerService customerService) + public AuthController(IAuthService authService) { _authService = authService; - _customerService = customerService; } - [HttpPost("register")] [AllowAnonymous] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task Register([FromBody] RegisterRequestDto request) { if (!ModelState.IsValid) return BadRequest(ModelState); + var result = await _authService.RegisterUserAsync(request); - // Wenn Registrierung erfolgreich, aber E-Mail-Bestätigung aussteht, sollte der Token leer sein - if (result.IsAuthSuccessful && string.IsNullOrEmpty(result.Token)) + + if (result.Type == ServiceResultType.Success) { - return Ok(new { Message = result.ErrorMessage, Email = result.Email }); // Sende Status und Info, dass Mail gesendet + return Ok(new { Message = "Registrierung erfolgreich. Bitte bestätigen Sie Ihre E-Mail-Adresse." }); } - if (!result.IsAuthSuccessful) return BadRequest(new { Message = result.ErrorMessage }); - return Ok(result); + + return BadRequest(new { Message = result.ErrorMessage }); } - [HttpPost("login/customer")] // /api/v1/auth/login/customer (für Kunden-Login) + [HttpPost("login/customer")] [AllowAnonymous] + [ProducesResponseType(typeof(AuthResponseDto), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] public async Task LoginCustomer([FromBody] LoginRequestDto request) { if (!ModelState.IsValid) return BadRequest(ModelState); + var result = await _authService.LoginUserAsync(request); - if (!result.IsAuthSuccessful) return Unauthorized(new { Message = result.ErrorMessage }); - return Ok(result); + + if (result.Type == ServiceResultType.Success) + { + return Ok(result.Value); + } + + return Unauthorized(new { Message = result.ErrorMessage }); } - [HttpPost("login/admin")] // /api/v1/auth/login/admin (für Admin-Dashboard Login) + [HttpPost("login/admin")] [AllowAnonymous] + [ProducesResponseType(typeof(AuthResponseDto), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] public async Task LoginAdmin([FromBody] LoginRequestDto request) { if (!ModelState.IsValid) return BadRequest(ModelState); + var result = await _authService.LoginAdminAsync(request); - if (!result.IsAuthSuccessful) return Unauthorized(new { Message = result.ErrorMessage }); - return Ok(result); + + return result.Type switch + { + ServiceResultType.Success => Ok(result.Value), + ServiceResultType.Forbidden => Forbid(), + _ => Unauthorized(new { Message = result.ErrorMessage }) + }; } - [HttpGet("confirm-email")] // Für Registrierungsbestätigung + [HttpGet("confirm-email")] [AllowAnonymous] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task ConfirmEmail([FromQuery] string userId, [FromQuery] string token) { - var (success, errorMessage) = await _authService.ConfirmEmailAsync(userId, token); - if (!success) return BadRequest(new { Message = errorMessage }); - return Ok(new { Message = "E-Mail-Adresse erfolgreich bestätigt. Sie können sich jetzt anmelden!" }); + var result = await _authService.ConfirmEmailAsync(userId, token); + + if (result.Type == ServiceResultType.Success) + { + return Ok(new { Message = "E-Mail-Adresse erfolgreich bestätigt. Sie können sich jetzt anmelden." }); + } + + return BadRequest(new { Message = result.ErrorMessage }); } [HttpPost("resend-email-confirmation")] [AllowAnonymous] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task ResendEmailConfirmation([FromBody] ResendEmailConfirmationRequestDto request) { - if (string.IsNullOrEmpty(request.Email)) return BadRequest(new { Message = "E-Mail ist erforderlich." }); - var (success, errorMessage) = await _authService.ResendEmailConfirmationAsync(request.Email); - if (!success) return BadRequest(new { Message = errorMessage }); - return Ok(new { Message = errorMessage }); + if (!ModelState.IsValid) return BadRequest(ModelState); + + var result = await _authService.ResendEmailConfirmationAsync(request.Email); + + if (result.Type == ServiceResultType.Success) + { + return Ok(new { Message = "Wenn ein Konto mit dieser E-Mail-Adresse existiert und noch nicht bestätigt wurde, wurde eine neue E-Mail gesendet." }); + } + + return BadRequest(new { Message = result.ErrorMessage }); } - [HttpGet("change-email-confirm")] // Für E-Mail-Änderungsbestätigung + // << NEUER ENDPUNKT FÜR PASSWORT VERGESSEN >> + [HttpPost("forgot-password")] [AllowAnonymous] - public async Task ConfirmEmailChange([FromQuery] string userId, [FromQuery] string newEmail, [FromQuery] string token) + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async Task ForgotPassword([FromBody] ForgotPasswordRequestDto request) { - var (success, errorMessage) = await _customerService.ConfirmEmailChangeAsync(userId, newEmail, token); // << Jetzt korrekt >> - if (!success) return BadRequest(new { Message = errorMessage }); - return Ok(new { Message = "Ihre E-Mail-Adresse wurde erfolgreich geändert und bestätigt!" }); + if (!ModelState.IsValid) return BadRequest(ModelState); + + await _authService.ForgotPasswordAsync(request); + + return Ok(new { Message = "Wenn ein Konto mit dieser E-Mail-Adresse existiert, wurde eine E-Mail zum Zurücksetzen des Passworts gesendet." }); } + // << NEUER ENDPUNKT FÜR PASSWORT ZURÜCKSETZEN >> + [HttpPost("reset-password")] + [AllowAnonymous] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async Task ResetPassword([FromBody] ResetPasswordDto request) + { + if (!ModelState.IsValid) return BadRequest(ModelState); + + var result = await _authService.ResetPasswordAsync(request); + + if (result.Type == ServiceResultType.Success) + { + return Ok(new { Message = "Ihr Passwort wurde erfolgreich zurückgesetzt." }); + } + + return BadRequest(new { Message = result.ErrorMessage }); + } + + // HINWEIS: Der 'change-email-confirm'-Endpunkt gehört logisch eher zum CustomerController, + // da er eine Aktion eines eingeloggten Benutzers ist. } - - } \ No newline at end of file diff --git a/Webshop.Api/Program.cs b/Webshop.Api/Program.cs index e477ebc..b14c10a 100644 --- a/Webshop.Api/Program.cs +++ b/Webshop.Api/Program.cs @@ -316,4 +316,10 @@ app.MapControllers(); app.Run(); -//zuletzt überarbeitet categories und discounds fehlermeldungen und results. next is order \ No newline at end of file +//zuletzt überarbeitet categories und discounds fehlermeldungen und results. next is order + + + + + + diff --git a/Webshop.Api/appsettings.json b/Webshop.Api/appsettings.json index b2aacea..336370f 100644 --- a/Webshop.Api/appsettings.json +++ b/Webshop.Api/appsettings.json @@ -11,12 +11,15 @@ }, "JwtSettings": { "Secret": "MEIN_DEBUG_PASSWORT", - "Issuer": "https://dein-webshop.com", + "Issuer": "https://shopsolution-backend.tzbre.de", "Audience": "webshop-users", "ExpirationMinutes": 120 }, "Resend": { - "ApiToken": "re_Zd6aFcvz_CqSa9Krs7WCHXjngDVLTYhGv" + "ApiToken": "IHRE_RESEND_API_TOKEN_HIER", + "FromEmail": "onboarding@resend.dev" }, - "App:BaseUrl": "https://shopsolution-backend.tzbre.dev" + "App": { + "ClientUrl": "https://shopsolution-backend.tzbre.de" //erstmal backend url oder loak "ClientUrl": "http://localhost:3000" + } } \ No newline at end of file diff --git a/Webshop.Application/DTOs/Auth/ForgotPasswordRequestDto.cs b/Webshop.Application/DTOs/Auth/ForgotPasswordRequestDto.cs new file mode 100644 index 0000000..cc8e546 --- /dev/null +++ b/Webshop.Application/DTOs/Auth/ForgotPasswordRequestDto.cs @@ -0,0 +1,12 @@ +// src/Webshop.Application/DTOs/Auth/ForgotPasswordRequestDto.cs +using System.ComponentModel.DataAnnotations; + +namespace Webshop.Application.DTOs.Auth +{ + public class ForgotPasswordRequestDto + { + [Required] + [EmailAddress] + public string Email { get; set; } = string.Empty; + } +} \ No newline at end of file diff --git a/Webshop.Application/DTOs/Auth/ResetPasswordDto.cs b/Webshop.Application/DTOs/Auth/ResetPasswordDto.cs new file mode 100644 index 0000000..47ab995 --- /dev/null +++ b/Webshop.Application/DTOs/Auth/ResetPasswordDto.cs @@ -0,0 +1,23 @@ +// src/Webshop.Application/DTOs/Auth/ResetPasswordDto.cs +using System.ComponentModel.DataAnnotations; + +namespace Webshop.Application.DTOs.Auth +{ + public class ResetPasswordDto + { + [Required] + [EmailAddress] + public string Email { get; set; } = string.Empty; + + [Required] + public string Token { get; set; } = string.Empty; + + [Required] + [MinLength(6)] + public string NewPassword { get; set; } = string.Empty; + + [Required] + [Compare(nameof(NewPassword), ErrorMessage = "Die Passwörter stimmen nicht überein.")] + public string ConfirmPassword { get; set; } = string.Empty; + } +} \ No newline at end of file diff --git a/Webshop.Application/DTOs/Email/ChangeEmailRequestDto.cs b/Webshop.Application/DTOs/Email/ChangeEmailRequestDto.cs index a4304b9..67055a6 100644 --- a/Webshop.Application/DTOs/Email/ChangeEmailRequestDto.cs +++ b/Webshop.Application/DTOs/Email/ChangeEmailRequestDto.cs @@ -1,4 +1,6 @@ -using System; +//Webshop.Application/DTOs/Email/ChangeEmailRequestDto.cs + +using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; diff --git a/Webshop.Application/DTOs/Email/ResendEmailConfirmationRequestDto.cs b/Webshop.Application/DTOs/Email/ResendEmailConfirmationRequestDto.cs index cac65f0..ca45388 100644 --- a/Webshop.Application/DTOs/Email/ResendEmailConfirmationRequestDto.cs +++ b/Webshop.Application/DTOs/Email/ResendEmailConfirmationRequestDto.cs @@ -1,4 +1,5 @@ -using System; +//Webshop.Application/DTOs/Email/ResendEmailConfirmationRequestDto.cs +using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; diff --git a/Webshop.Application/ServiceResult.cs b/Webshop.Application/ServiceResult.cs index 0883a06..a6d5184 100644 --- a/Webshop.Application/ServiceResult.cs +++ b/Webshop.Application/ServiceResult.cs @@ -7,7 +7,9 @@ namespace Webshop.Application NotFound, InvalidInput, Conflict, - Failure + Failure, + Unauthorized, + Forbidden } public class ServiceResult diff --git a/Webshop.Application/Services/Auth/AuthService.cs b/Webshop.Application/Services/Auth/AuthService.cs index a2e76c0..e44d5d5 100644 --- a/Webshop.Application/Services/Auth/AuthService.cs +++ b/Webshop.Application/Services/Auth/AuthService.cs @@ -1,52 +1,47 @@ -using Microsoft.AspNetCore.Identity; +// src/Webshop.Application/Services/Auth/AuthService.cs +using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Configuration; using Microsoft.IdentityModel.Tokens; +using Resend; +using System; using System.IdentityModel.Tokens.Jwt; +using System.Linq; using System.Security.Claims; using System.Text; -using Webshop.Application.DTOs.Auth; -using Webshop.Domain.Entities; -using Webshop.Infrastructure.Data; -using Webshop.Domain.Identity; -using Resend; +using System.Threading.Tasks; using System.Web; -using System.Net.Mail; +using Webshop.Application.DTOs.Auth; +using Webshop.Domain.Identity; +using Webshop.Infrastructure.Data; +using Webshop.Application; namespace Webshop.Application.Services.Auth { public class AuthService : IAuthService { private readonly UserManager _userManager; - private readonly SignInManager _signInManager; private readonly IConfiguration _configuration; - private readonly RoleManager _roleManager; private readonly IResend _resend; - private readonly ApplicationDbContext _context; // << NEU: Deklaration >> + private readonly ApplicationDbContext _context; public AuthService( UserManager userManager, - SignInManager signInManager, IConfiguration configuration, - RoleManager roleManager, IResend resend, - ApplicationDbContext context) // << NEU: DbContext im Konstruktor injizieren >> + ApplicationDbContext context) { _userManager = userManager; - _signInManager = signInManager; _configuration = configuration; - _roleManager = roleManager; _resend = resend; - _context = context; // << NEU: Initialisierung >> + _context = context; } - - - public async Task RegisterUserAsync(RegisterRequestDto request) + public async Task RegisterUserAsync(RegisterRequestDto request) { - var existingUser = await _userManager.FindByEmailAsync(request.Email); - if (existingUser != null) + var userExists = await _userManager.FindByEmailAsync(request.Email); + if (userExists != null) { - return new AuthResponseDto { IsAuthSuccessful = false, ErrorMessage = "E-Mail ist bereits registriert." }; + return ServiceResult.Fail(ServiceResultType.InvalidInput, "Ein Benutzer mit dieser E-Mail-Adresse existiert bereits."); } var user = new ApplicationUser { Email = request.Email, UserName = request.Email, CreatedDate = DateTimeOffset.UtcNow }; @@ -55,121 +50,41 @@ namespace Webshop.Application.Services.Auth if (!result.Succeeded) { var errors = string.Join(" ", result.Errors.Select(e => e.Description)); - return new AuthResponseDto { IsAuthSuccessful = false, ErrorMessage = errors }; + return ServiceResult.Fail(ServiceResultType.Failure, errors); } - if (!await _roleManager.RoleExistsAsync("Customer")) - { - await _roleManager.CreateAsync(new IdentityRole("Customer")); - } await _userManager.AddToRoleAsync(user, "Customer"); - // << NEU: HIER WIRD DAS CUSTOMER-PROFIL ERSTELLT UND GESPEICHERT >> - var customerProfile = new Webshop.Domain.Entities.Customer + var customerProfile = new Domain.Entities.Customer { - Id = Guid.NewGuid(), - AspNetUserId = user.Id, // Verknüpfung zum ApplicationUser - FirstName = request.FirstName ?? string.Empty, // Vom Request-DTO - LastName = request.LastName ?? string.Empty, // Vom Request-DTO - + AspNetUserId = user.Id, + FirstName = request.FirstName, + LastName = request.LastName }; _context.Customers.Add(customerProfile); - await _context.SaveChangesAsync(); // Speichere das neue Kundenprofil - // << ENDE NEUER TEIL >> + await _context.SaveChangesAsync(); await SendEmailConfirmationEmail(user); - - return new AuthResponseDto - { - IsAuthSuccessful = true, - ErrorMessage = "Registrierung erfolgreich. Bitte bestätigen Sie Ihre E-Mail-Adresse.", - Token = "", - UserId = user.Id, - Email = user.Email, - Roles = (await _userManager.GetRolesAsync(user)).ToList() - }; + return ServiceResult.Ok(); } - public async Task<(bool Success, string ErrorMessage)> ConfirmEmailAsync(string userId, string token) - { - var user = await _userManager.FindByIdAsync(userId); - if (user == null) - { - return (false, "Benutzer nicht gefunden."); - } - - var result = await _userManager.ConfirmEmailAsync(user, HttpUtility.UrlDecode(token)); - if (!result.Succeeded) - { - var errors = string.Join(" ", result.Errors.Select(e => e.Description)); - return (false, $"E-Mail-Bestätigung fehlgeschlagen: {errors}"); - } - - return (true, "E-Mail-Adresse erfolgreich bestätigt."); - } - - public async Task<(bool Success, string ErrorMessage)> ResendEmailConfirmationAsync(string email) - { - var user = await _userManager.FindByEmailAsync(email); - if (user == null) - { - return (false, "Benutzer nicht gefunden oder E-Mail existiert nicht."); - } - if (await _userManager.IsEmailConfirmedAsync(user)) - { - return (false, "E-Mail-Adresse ist bereits bestätigt."); - } - - await SendEmailConfirmationEmail(user); - return (true, "Bestätigungs-E-Mail wurde erneut gesendet. Bitte prüfen Sie Ihr Postfach."); - } - - private async Task SendEmailConfirmationEmail(ApplicationUser user) // << WICHTIG: ApplicationUser als Typ >> - { - var token = await _userManager.GenerateEmailConfirmationTokenAsync(user); - var encodedToken = HttpUtility.UrlEncode(token); - var baseUrl = _configuration["App:BaseUrl"]; // Von appsettings.json oder Umgebungsvariablen - - var confirmationLink = $"{baseUrl}/api/v1/auth/confirm-email?userId={user.Id}&token={encodedToken}"; - - var message = new EmailMessage(); - message.From = "Your Webshop "; // << ANPASSEN: Absender-E-Mail und Domain >> - message.To.Add(user.Email); - message.Subject = "Bestätigen Sie Ihre E-Mail-Adresse für Your Webshop"; - message.HtmlBody = $@" -

Willkommen bei Your Webshop!

-

Bitte klicken Sie auf den folgenden Link, um Ihre E-Mail-Adresse zu bestätigen:

-

{confirmationLink}

-

Vielen Dank!

"; - - await _resend.EmailSendAsync(message); - } - - - public async Task LoginUserAsync(LoginRequestDto request) + public async Task> LoginUserAsync(LoginRequestDto request) { var user = await _userManager.FindByEmailAsync(request.Email); - if (user == null) + if (user == null || !await _userManager.CheckPasswordAsync(user, request.Password)) { - return new AuthResponseDto { IsAuthSuccessful = false, ErrorMessage = "Ungültige Anmeldeinformationen." }; + return ServiceResult.Fail(ServiceResultType.Unauthorized, "Ungültige Anmeldeinformationen."); } - // << NEU: Prüfen, ob E-Mail bestätigt ist >> if (!await _userManager.IsEmailConfirmedAsync(user)) { - return new AuthResponseDto { IsAuthSuccessful = false, ErrorMessage = "E-Mail-Adresse wurde noch nicht bestätigt. Bitte prüfen Sie Ihr Postfach." }; - } - - var signInResult = await _signInManager.CheckPasswordSignInAsync(user, request.Password, false); - if (!signInResult.Succeeded) - { - return new AuthResponseDto { IsAuthSuccessful = false, ErrorMessage = "Ungültige Anmeldeinformationen." }; + return ServiceResult.Fail(ServiceResultType.Unauthorized, "E-Mail-Adresse wurde noch nicht bestätigt."); } var roles = await _userManager.GetRolesAsync(user); - var token = await GenerateJwtToken(user, roles); + var token = GenerateJwtToken(user, roles); - return new AuthResponseDto + var response = new AuthResponseDto { IsAuthSuccessful = true, Token = token, @@ -177,34 +92,117 @@ namespace Webshop.Application.Services.Auth Email = user.Email, Roles = roles.ToList() }; + return ServiceResult.Ok(response); } - public async Task LoginAdminAsync(LoginRequestDto request) + public async Task> LoginAdminAsync(LoginRequestDto request) { - var authResponse = await LoginUserAsync(request); - if (!authResponse.IsAuthSuccessful) + var loginResult = await LoginUserAsync(request); + if (loginResult.Type != ServiceResultType.Success) { - return authResponse; + return loginResult; } var user = await _userManager.FindByEmailAsync(request.Email); if (user == null || !await _userManager.IsInRoleAsync(user, "Admin")) { - return new AuthResponseDto { IsAuthSuccessful = false, ErrorMessage = "Keine Berechtigung." }; + return ServiceResult.Fail(ServiceResultType.Forbidden, "Keine Berechtigung für den Admin-Zugang."); } - return authResponse; + return loginResult; } - private async Task GenerateJwtToken(ApplicationUser user, IList roles) // << WICHTIG: ApplicationUser >> + public async Task ConfirmEmailAsync(string userId, string token) + { + var user = await _userManager.FindByIdAsync(userId); + if (user == null) + { + return ServiceResult.Fail(ServiceResultType.NotFound, "Benutzer nicht gefunden."); + } + + var result = await _userManager.ConfirmEmailAsync(user, HttpUtility.UrlDecode(token)); + return result.Succeeded ? ServiceResult.Ok() : ServiceResult.Fail(ServiceResultType.Failure, "E-Mail-Bestätigung fehlgeschlagen."); + } + + public async Task ResendEmailConfirmationAsync(string email) + { + var user = await _userManager.FindByEmailAsync(email); + if (user == null) + { + // Aus Sicherheitsgründen nicht verraten, ob die E-Mail existiert + 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(); + } + + public async Task ForgotPasswordAsync(ForgotPasswordRequestDto request) + { + var user = await _userManager.FindByEmailAsync(request.Email); + if (user == null || !(await _userManager.IsEmailConfirmedAsync(user))) + { + return ServiceResult.Ok(); + } + + var token = await _userManager.GeneratePasswordResetTokenAsync(user); + var encodedToken = HttpUtility.UrlEncode(token); + var clientUrl = _configuration["App:ClientUrl"]!; + var resetLink = $"{clientUrl}/reset-password?email={HttpUtility.UrlEncode(request.Email)}&token={encodedToken}"; + + // << KORREKTUR: EmailMessage verwenden >> + 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

"; + await _resend.EmailSendAsync(message); + + return ServiceResult.Ok(); + } + + public async Task ResetPasswordAsync(ResetPasswordDto request) + { + var user = await _userManager.FindByEmailAsync(request.Email); + if (user == null) + { + return ServiceResult.Fail(ServiceResultType.InvalidInput, "Fehler beim Zurücksetzen des Passworts."); + } + + var result = await _userManager.ResetPasswordAsync(user, HttpUtility.UrlDecode(request.Token), request.NewPassword); + + return result.Succeeded + ? ServiceResult.Ok() + : ServiceResult.Fail(ServiceResultType.InvalidInput, string.Join(" ", result.Errors.Select(e => e.Description))); + } + + private async Task SendEmailConfirmationEmail(ApplicationUser user) + { + var token = await _userManager.GenerateEmailConfirmationTokenAsync(user); + var encodedToken = HttpUtility.UrlEncode(token); + var clientUrl = _configuration["App:ClientUrl"]!; + var confirmationLink = $"{clientUrl}/confirm-email?userId={user.Id}&token={encodedToken}"; + + // << KORREKTUR: EmailMessage verwenden >> + 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

"; + await _resend.EmailSendAsync(message); + } + + private string GenerateJwtToken(ApplicationUser user, IList roles) { var claims = new List { new Claim(JwtRegisteredClaimNames.Sub, user.Id), new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), new Claim(JwtRegisteredClaimNames.Email, user.Email!) - // Optional: Fügen Sie Custom Claims von ApplicationUser hinzu - // new Claim("created_date", user.CreatedDate.ToString("o")) }; foreach (var role in roles) @@ -227,6 +225,5 @@ namespace Webshop.Application.Services.Auth return new JwtSecurityTokenHandler().WriteToken(token); } - -} + } } \ No newline at end of file diff --git a/Webshop.Application/Services/Auth/IAuthService.cs b/Webshop.Application/Services/Auth/IAuthService.cs index 7e36ef8..3fae1a5 100644 --- a/Webshop.Application/Services/Auth/IAuthService.cs +++ b/Webshop.Application/Services/Auth/IAuthService.cs @@ -1,15 +1,20 @@ // src/Webshop.Application/Services/Auth/IAuthService.cs using System.Threading.Tasks; +using Webshop.Application; using Webshop.Application.DTOs.Auth; namespace Webshop.Application.Services.Auth { public interface IAuthService { - Task RegisterUserAsync(RegisterRequestDto request); - Task LoginUserAsync(LoginRequestDto request); - Task LoginAdminAsync(LoginRequestDto request); - Task<(bool Success, string ErrorMessage)> ConfirmEmailAsync(string userId, string token); - Task<(bool Success, string ErrorMessage)> ResendEmailConfirmationAsync(string email); + Task RegisterUserAsync(RegisterRequestDto request); // << Gibt kein DTO mehr zurück >> + Task> LoginUserAsync(LoginRequestDto request); + Task> LoginAdminAsync(LoginRequestDto request); + Task ConfirmEmailAsync(string userId, string token); + Task ResendEmailConfirmationAsync(string email); + + // << NEUE METHODEN >> + Task ForgotPasswordAsync(ForgotPasswordRequestDto request); + Task ResetPasswordAsync(ResetPasswordDto request); } } \ No newline at end of file