Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/ja 117 activation emails #107

Merged
merged 25 commits into from
Jun 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
22086c9
Create SendActivationEmail method
skrawus Jun 6, 2024
0a71588
Create Activation emails
skrawus Jun 6, 2024
c3b9ba2
Make ActivationCode nullable
skrawus Jun 7, 2024
cf19d58
Make ActivateUser method async
skrawus Jun 7, 2024
9c69231
Correct return view and messages in ActivateAccount method
skrawus Jun 7, 2024
ce4d98d
Correct generating URL and change ActivationCode after activation
skrawus Jun 7, 2024
21e66fb
Add IsActive to UserDto and edit LogIn
skrawus Jun 7, 2024
6913c4c
Change Login method so it shows correct messages
skrawus Jun 7, 2024
c76eaa5
Fix IsUserActive method
skrawus Jun 7, 2024
b708ad6
Move sensitive data to userSecrets
skrawus Jun 14, 2024
026eb47
Add ActivationResult model
skrawus Jun 14, 2024
b1a3512
Move link to secrets
skrawus Jun 14, 2024
5d8d35e
Move activationCode generation to Service
skrawus Jun 14, 2024
b02f9e2
Change Activation page view
skrawus Jun 14, 2024
af3d37f
Fix logging in so only active user can log in
skrawus Jun 14, 2024
76dab34
Change repository
skrawus Jun 14, 2024
c88c3a4
Make LogIn method in IUserService not-nullable
skrawus Jun 15, 2024
b919b95
Change Exception in AccountController
skrawus Jun 15, 2024
5642e2f
Delete duplicated class
skrawus Jun 15, 2024
f53ed17
Make IsActive not nullable & update LogIn method
skrawus Jun 15, 2024
dfe0c00
Change the way of saving changes in ActivateUserAsync method
skrawus Jun 16, 2024
0184191
Merge develop into ja-117
skrawus Jun 16, 2024
df071c9
Move ActivateUserAsync method to UserService
skrawus Jun 23, 2024
c51b510
Move data to secrets
skrawus Jun 23, 2024
3ae5360
Merge develop into ja-117
skrawus Jun 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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; }
}
}
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 @@
public class User
{
public int Id { get; set; }
public bool IsActive { get; set; }

[Required]
[MinLength(5)]
Expand Down Expand Up @@ -37,7 +38,7 @@
PasswordHash = passwordHash;
GoogleId = googleId;
}
public User()

Check warning on line 41 in TutorLizard.BusinessLogic/Models/User.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'Name' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 41 in TutorLizard.BusinessLogic/Models/User.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'Email' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.
{
}

Expand All @@ -45,4 +46,5 @@
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);

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
Loading