Skip to content

Commit

Permalink
Merge pull request #107 from infoshareacademy/feature/ja-117-activati…
Browse files Browse the repository at this point in the history
…on-emails

Feature/ja 117 activation emails
  • Loading branch information
Zjyslav committed Jun 26, 2024
2 parents 3c2139d + 3ae5360 commit dce3b36
Show file tree
Hide file tree
Showing 16 changed files with 686 additions and 56 deletions.
5 changes: 5 additions & 0 deletions TutorLizard.BusinessLogic/Data/JaszczurContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
.Property(user => user.Id)
.ValueGeneratedOnAdd();

modelBuilder.Entity<User>()
.Property(user => user.IsActive)
.HasDefaultValue(false)
.IsRequired();

modelBuilder.Entity<User>()
.Property(user => user.UserType)
.HasConversion<byte>();
Expand Down
6 changes: 4 additions & 2 deletions TutorLizard.BusinessLogic/Interfaces/Services/IUserService.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
using TutorLizard.BusinessLogic.Enums;
using TutorLizard.BusinessLogic.Models;
using TutorLizard.BusinessLogic.Models.DTOs;

namespace TutorLizard.BusinessLogic.Interfaces.Services;

public interface IUserService
{
public Task<UserDto?> LogIn(string username, string password);
public Task<bool> RegisterUser(string userName, UserType type, string email, string password);
public Task<LogInResult> LogIn(string username, string password);
public Task<bool> RegisterUser(string userName, UserType type, string email, string password, string activationCode);
Task<ActivationResult> ActivateUserAsync(string activationCode);
public Task<bool> RegisterUserWithGoogle(string username, string email, string googleId);
public Task<bool> IsTheGoogleUserRegistered(string googleId);
public Task<UserDto?> LogInWithGoogle(string username, string googleId);
Expand Down
14 changes: 14 additions & 0 deletions TutorLizard.BusinessLogic/Models/ActivationResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TutorLizard.BusinessLogic.Models
{
public class ActivationResult
{
public bool IsActivated { get; set; }
public string ActivationCode { get; set; }

Check warning on line 12 in TutorLizard.BusinessLogic/Models/ActivationResult.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'ActivationCode' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.
}
}
16 changes: 16 additions & 0 deletions TutorLizard.BusinessLogic/Models/DTOs/LogInResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace TutorLizard.BusinessLogic.Models.DTOs
{
public class LogInResult
{
public LogInResultCode ResultCode { get; set; }
public UserDto? User { get; set; }
}

public enum LogInResultCode
{
Success,
UserNotFound,
InactiveAccount,
InvalidPassword
}
}
1 change: 1 addition & 0 deletions TutorLizard.BusinessLogic/Models/DTOs/UserDto.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ namespace TutorLizard.BusinessLogic.Models.DTOs;
public class UserDto
{
public int Id { get; set; }
public bool? IsActive { get; set; }

[Required(ErrorMessage = "Podaj nazwę użytkownika.")]
[Display(Name = "Nazwa użytkownika")]
Expand Down
2 changes: 2 additions & 0 deletions TutorLizard.BusinessLogic/Models/User.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ namespace TutorLizard.BusinessLogic.Models;
public class User
{
public int Id { get; set; }
public bool IsActive { get; set; }

[Required]
[MinLength(5)]
Expand Down Expand Up @@ -45,4 +46,5 @@ public User()
public ICollection<Ad> Ads { get; set; } = new List<Ad>();
public ICollection<AdRequest> AdRequests { get; set; } = new List<AdRequest>();
public ICollection<ScheduleItemRequest> ScheduleItemRequests { get; set; } = new List<ScheduleItemRequest>();
public string? ActivationCode { get; set; }
}
70 changes: 60 additions & 10 deletions TutorLizard.BusinessLogic/Services/UserService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,25 +18,43 @@ public UserService(IDbRepository<User> userRepository)
{
_userRepository = userRepository;
}
public async Task<UserDto?> LogIn(string username, string password)

public async Task<LogInResult> LogIn(string username, string password)
{
var user = await _userRepository.GetAll()
.FirstOrDefaultAsync(user => user.Name == username);

if (user == null)
{
return null;
return new LogInResult
{
ResultCode = LogInResultCode.UserNotFound
};
}

if (!user.IsActive)
{
return new LogInResult
{
ResultCode = LogInResultCode.InactiveAccount
};
}

var result = _passwordHasher
.VerifyHashedPassword(user,
user.PasswordHash,
password);
var result = _passwordHasher.VerifyHashedPassword(user, user.PasswordHash, password);

Check warning on line 43 in TutorLizard.BusinessLogic/Services/UserService.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 'hashedPassword' in 'PasswordVerificationResult PasswordHasher<User>.VerifyHashedPassword(User user, string hashedPassword, string providedPassword)'.

if (result == PasswordVerificationResult.Success)
return user.ToDto();
{
return new LogInResult
{
ResultCode = LogInResultCode.Success,
User = user.ToDto()
};
}

return null;
return new LogInResult
{
ResultCode = LogInResultCode.InvalidPassword
};
}

public async Task<UserDto?> LogInWithGoogle(string username, string googleId)
Expand All @@ -56,7 +74,7 @@ public UserService(IDbRepository<User> userRepository)
}


public async Task<bool> RegisterUser(string userName, UserType type, string email, string password)
public async Task<bool> RegisterUser(string userName, UserType type, string email, string password, string activationCode)
{
if (await _userRepository.GetAll().AnyAsync(user => user.Name == userName))
return false;
Expand All @@ -66,7 +84,9 @@ public async Task<bool> RegisterUser(string userName, UserType type, string emai
Name = userName,
UserType = type,
Email = email,
PasswordHash = password
PasswordHash = password,
ActivationCode = activationCode,
IsActive = false
};

user.PasswordHash = _passwordHasher.HashPassword(user, password);
Expand Down Expand Up @@ -101,4 +121,34 @@ public async Task<bool> IsTheGoogleUserRegistered(string googleId)

return false;
}

public async Task<ActivationResult> ActivateUserAsync(string activationCode)
{

var user = await _userRepository.GetAll()
.FirstOrDefaultAsync(u => u.ActivationCode == activationCode && u.IsActive == false);

if (user != null)
{
user.IsActive = true;
user.ActivationCode = "ACTIVATED";

await _userRepository.Update(user.Id, u =>
{
u.IsActive = user.IsActive;
u.ActivationCode = user.ActivationCode;
});

return new ActivationResult
{
IsActivated = true,
ActivationCode = activationCode
};
}
return new ActivationResult
{
IsActivated = false,
ActivationCode = activationCode
};
}
}
85 changes: 74 additions & 11 deletions TutorLizard.Web/Controllers/AccountController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,31 @@
using Microsoft.AspNetCore.Mvc;
using System.Security.Authentication;
using System.Security.Claims;
using System.ComponentModel;
using System.Net;
using System.Net.Mail;
using TutorLizard.BusinessLogic.Enums;
using TutorLizard.BusinessLogic.Interfaces.Services;
using TutorLizard.BusinessLogic.Models.DTOs;
using TutorLizard.Web.Interfaces.Services;
using TutorLizard.Web.Models;


namespace TutorLizard.Web.Controllers;

public class AccountController : Controller
{
private readonly IUserAuthenticationService _userAuthenticationService;
private readonly IUiMessagesService _uiMessagesService;
private readonly IUserService _userService;

public AccountController(IUserAuthenticationService userAuthenticationService,
IUiMessagesService uiMessagesService)
IUiMessagesService uiMessagesService,
IUserService userService)
{
_userAuthenticationService = userAuthenticationService;
_uiMessagesService = uiMessagesService;
_userService = userService;
}

public IActionResult Index()
Expand Down Expand Up @@ -111,24 +119,48 @@ public async Task<IActionResult> Login(LoginModel model)

try
{
if (ModelState.IsValid && await _userAuthenticationService.LogInAsync(model.UserName, model.Password))
if (ModelState.IsValid)
{
_uiMessagesService.ShowSuccessMessage("Jesteś zalogowany/a.");
if (string.IsNullOrEmpty(returnUrl))
var logInResult = await _userAuthenticationService.LogInAsync(model.UserName, model.Password);

switch (logInResult.ResultCode)
{
return RedirectToAction("Index", "Home");
case BusinessLogic.Models.DTOs.LogInResultCode.Success:
_uiMessagesService.ShowSuccessMessage("Jesteś zalogowany/a.");
if (string.IsNullOrEmpty(returnUrl))
{
return RedirectToAction("Index", "Home");
}
return Redirect(returnUrl);

case BusinessLogic.Models.DTOs.LogInResultCode.UserNotFound:
case BusinessLogic.Models.DTOs.LogInResultCode.InvalidPassword:
_uiMessagesService.ShowFailureMessage("Logowanie nieudane. Nieprawidłowa nazwa użytkownika lub hasło.");
return RedirectToAction(nameof(Login), new { returnUrl = returnUrl });

case BusinessLogic.Models.DTOs.LogInResultCode.InactiveAccount:
_uiMessagesService.ShowFailureMessage("Logowanie nieudane. Konto nie jest aktywne.");
return LocalRedirect("/Home/Index");

default:
throw new InvalidEnumArgumentException(argumentName: nameof(BusinessLogic.Models.DTOs.LogInResult.ResultCode),
invalidValue: (int)logInResult.ResultCode,
enumClass: typeof(BusinessLogic.Models.DTOs.LogInResult));
}
return Redirect(returnUrl);
}
else
{
_uiMessagesService.ShowFailureMessage("Logowanie nieudane. Proszę wypełnić poprawnie formularz.");
return RedirectToAction(nameof(Login), new { returnUrl = returnUrl });
}
}
catch
{
_uiMessagesService.ShowFailureMessage("Logowanie nieudane.");
return LocalRedirect("/Home/Index");
}
_uiMessagesService.ShowFailureMessage("Logowanie nieudane.");
return RedirectToAction(nameof(Login), new { returnUrl = returnUrl });
}

[Authorize]
public async Task<IActionResult> Logout()
{
Expand All @@ -140,16 +172,30 @@ public IActionResult Register()
{
return View();
}

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Register(RegisterUserModel model)
{
try
{
if (ModelState.IsValid
&& await _userAuthenticationService.RegisterUser(model.UserName, UserType.Regular, model.Email, model.Password))
if (!ModelState.IsValid)
{
_uiMessagesService.ShowSuccessMessage("Użytkownik zarejestrowany.");
var errors = ModelState.Values.SelectMany(v => v.Errors);
foreach (var error in errors)
{
Console.WriteLine(error.ErrorMessage);
}
return View(model);
}

var (registrationResult, activationCode) = await _userAuthenticationService.RegisterUser(
model.UserName, UserType.Regular, model.Email, model.Password);

if (registrationResult)
{
_userAuthenticationService.SendActivationEmail(model.Email, activationCode);
_uiMessagesService.ShowSuccessMessage("Wysłano mail aktywacyjny.");
return LocalRedirect("/Home/Index");
}
}
Expand All @@ -162,6 +208,23 @@ public async Task<IActionResult> Register(RegisterUserModel model)
return LocalRedirect("/Home/Index");
}

[HttpGet]
public async Task<IActionResult> ActivateAccount(string activationCode)
{
var result = await _userService.ActivateUserAsync(activationCode);

if (result.IsActivated)
{
_uiMessagesService.ShowSuccessMessage("Atywacja udana.");
return View("ActivateAccount");
}
else
{
_uiMessagesService.ShowFailureMessage("Aktywacja konta nie powiodła się.");
return LocalRedirect("/Home/Index");
}
}

public IActionResult AccessDenied()
{
return View();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ public interface IUserAuthenticationService

{
int? GetLoggedInUserId();
public Task<bool> LogInAsync(string username, string password);
Task<Models.DTOs.LogInResult> LogInAsync(string username, string password);
public Task LogOutAsync();
public Task<bool> RegisterUser(string username, UserType type, string email, string password);
public Task<bool> IsGoogleUserRegistered(string googleid);
public Task<bool> RegisterUserWithGoogle(string username, string email, string googleId);
public Task<bool> LogInWithGoogleAsync(string username, string googleId);
Task<(bool, string)> RegisterUser(string username, UserType type, string email, string password);
void SendActivationEmail(string email, string activationCode);
}
Loading

0 comments on commit dce3b36

Please sign in to comment.