Skip to content

Commit

Permalink
refactor(auth): use generic security entities
Browse files Browse the repository at this point in the history
  • Loading branch information
ahmet-cetinkaya committed Feb 10, 2024
1 parent db0a8b5 commit 6214dfb
Show file tree
Hide file tree
Showing 82 changed files with 406 additions and 393 deletions.
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2022 Ahmet Çetinkaya
Copyright (c) 2022 Kodlama.io

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
3 changes: 3 additions & 0 deletions NArchitecture.sln
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StarterProject.Application.
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8A52191F-DCC6-4487-AFFF-4B6BB86231FC}"
ProjectSection(SolutionItems) = preProject
.csharpierrc = .csharpierrc
.editorconfig = .editorconfig
LICENSE = LICENSE
README.md = README.md
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Core.Persistence.DependencyInjection", "src\corePackages\Core.Persistence.DependencyInjection\Core.Persistence.DependencyInjection.csproj", "{E1F0BD02-5781-4309-B469-DFDA36C2462E}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,17 +55,17 @@ IAuthenticatorService authenticatorService

public async Task Handle(EnableEmailAuthenticatorCommand request, CancellationToken cancellationToken)
{
User? user = await _userService.GetAsync(predicate: u => u.Id == request.UserId, cancellationToken: cancellationToken);
User<int, int>? user = await _userService.GetAsync(predicate: u => u.Id == request.UserId, cancellationToken: cancellationToken);
await _authBusinessRules.UserShouldBeExistsWhenSelected(user);
await _authBusinessRules.UserShouldNotBeHaveAuthenticator(user!);

user!.AuthenticatorType = AuthenticatorType.Email;
await _userService.UpdateAsync(user);

EmailAuthenticator emailAuthenticator = await _authenticatorService.CreateEmailAuthenticator(user);
EmailAuthenticator addedEmailAuthenticator = await _emailAuthenticatorRepository.AddAsync(emailAuthenticator);
EmailAuthenticator<int, int> emailAuthenticator = await _authenticatorService.CreateEmailAuthenticator(user);
EmailAuthenticator<int, int> addedEmailAuthenticator = await _emailAuthenticatorRepository.AddAsync(emailAuthenticator);

var toEmailList = new List<MailboxAddress> { new(name: $"{user.FirstName} {user.LastName}", user.Email) };
var toEmailList = new List<MailboxAddress> { new(name: user.Email, user.Email) };

_mailService.SendMail(
new Mail
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,20 +39,20 @@ public async Task<EnabledOtpAuthenticatorResponse> Handle(
CancellationToken cancellationToken
)
{
User? user = await _userService.GetAsync(predicate: u => u.Id == request.UserId, cancellationToken: cancellationToken);
User<int, int>? user = await _userService.GetAsync(predicate: u => u.Id == request.UserId, cancellationToken: cancellationToken);
await _authBusinessRules.UserShouldBeExistsWhenSelected(user);
await _authBusinessRules.UserShouldNotBeHaveAuthenticator(user!);

OtpAuthenticator? doesExistOtpAuthenticator = await _otpAuthenticatorRepository.GetAsync(
OtpAuthenticator<int, int>? doesExistOtpAuthenticator = await _otpAuthenticatorRepository.GetAsync(
predicate: o => o.UserId == request.UserId,
cancellationToken: cancellationToken
);
await _authBusinessRules.OtpAuthenticatorThatVerifiedShouldNotBeExists(doesExistOtpAuthenticator);
if (doesExistOtpAuthenticator is not null)
await _otpAuthenticatorRepository.DeleteAsync(doesExistOtpAuthenticator);

OtpAuthenticator newOtpAuthenticator = await _authenticatorService.CreateOtpAuthenticator(user!);
OtpAuthenticator addedOtpAuthenticator = await _otpAuthenticatorRepository.AddAsync(newOtpAuthenticator);
OtpAuthenticator<int, int> newOtpAuthenticator = await _authenticatorService.CreateOtpAuthenticator(user!);
OtpAuthenticator<int, int> addedOtpAuthenticator = await _otpAuthenticatorRepository.AddAsync(newOtpAuthenticator);

EnabledOtpAuthenticatorResponse enabledOtpAuthenticatorDto =
new() { SecretKey = await _authenticatorService.ConvertSecretKeyToString(addedOtpAuthenticator.SecretKey) };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace Application.Features.Auth.Commands.Login;
public class LoggedResponse : IResponse
{
public AccessToken? AccessToken { get; set; }
public Core.Security.Entities.RefreshToken? RefreshToken { get; set; }
public Core.Security.Entities.RefreshToken<int, int>? RefreshToken { get; set; }
public AuthenticatorType? RequiredAuthenticatorType { get; set; }

public LoggedHttpResponse ToHttpResponse() =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ IAuthenticatorService authenticatorService

public async Task<LoggedResponse> Handle(LoginCommand request, CancellationToken cancellationToken)
{
User? user = await _userService.GetAsync(
User<int, int>? user = await _userService.GetAsync(
predicate: u => u.Email == request.UserForLoginDto.Email,
cancellationToken: cancellationToken
);
Expand All @@ -72,8 +72,8 @@ public async Task<LoggedResponse> Handle(LoginCommand request, CancellationToken

AccessToken createdAccessToken = await _authService.CreateAccessToken(user);

Core.Security.Entities.RefreshToken createdRefreshToken = await _authService.CreateRefreshToken(user, request.IpAddress);
Core.Security.Entities.RefreshToken addedRefreshToken = await _authService.AddRefreshToken(createdRefreshToken);
Core.Security.Entities.RefreshToken<int, int> createdRefreshToken = await _authService.CreateRefreshToken(user, request.IpAddress);
Core.Security.Entities.RefreshToken<int, int> addedRefreshToken = await _authService.AddRefreshToken(createdRefreshToken);
await _authService.DeleteOldRefreshTokens(user.Id);

loggedResponse.AccessToken = createdAccessToken;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,26 +39,26 @@ public RefreshTokenCommandHandler(IAuthService authService, IUserService userSer

public async Task<RefreshedTokensResponse> Handle(RefreshTokenCommand request, CancellationToken cancellationToken)
{
Core.Security.Entities.RefreshToken? refreshToken = await _authService.GetRefreshTokenByToken(request.RefreshToken);
Core.Security.Entities.RefreshToken<int, int>? refreshToken = await _authService.GetRefreshTokenByToken(request.RefreshToken);
await _authBusinessRules.RefreshTokenShouldBeExists(refreshToken);

if (refreshToken!.Revoked != null)
if (refreshToken!.RevokedDate != null)
await _authService.RevokeDescendantRefreshTokens(
refreshToken,
request.IpAddress,
reason: $"Attempted reuse of revoked ancestor token: {refreshToken.Token}"
);
await _authBusinessRules.RefreshTokenShouldBeActive(refreshToken);

User? user = await _userService.GetAsync(predicate: u => u.Id == refreshToken.UserId, cancellationToken: cancellationToken);
User<int, int>? user = await _userService.GetAsync(predicate: u => u.Id == refreshToken.UserId, cancellationToken: cancellationToken);
await _authBusinessRules.UserShouldBeExistsWhenSelected(user);

Core.Security.Entities.RefreshToken newRefreshToken = await _authService.RotateRefreshToken(
Core.Security.Entities.RefreshToken<int, int> newRefreshToken = await _authService.RotateRefreshToken(
user: user!,
refreshToken,
request.IpAddress
);
Core.Security.Entities.RefreshToken addedRefreshToken = await _authService.AddRefreshToken(newRefreshToken);
Core.Security.Entities.RefreshToken<int, int> addedRefreshToken = await _authService.AddRefreshToken(newRefreshToken);
await _authService.DeleteOldRefreshTokens(refreshToken.UserId);

AccessToken createdAccessToken = await _authService.CreateAccessToken(user!);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ namespace Application.Features.Auth.Commands.RefreshToken;
public class RefreshedTokensResponse : IResponse
{
public AccessToken AccessToken { get; set; }
public Core.Security.Entities.RefreshToken RefreshToken { get; set; }
public Core.Security.Entities.RefreshToken<int, int> RefreshToken { get; set; }

public RefreshedTokensResponse()
{
AccessToken = null!;
RefreshToken = null!;
}

public RefreshedTokensResponse(AccessToken accessToken, Core.Security.Entities.RefreshToken refreshToken)
public RefreshedTokensResponse(AccessToken accessToken, Core.Security.Entities.RefreshToken<int, int> refreshToken)
{
AccessToken = accessToken;
RefreshToken = refreshToken;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,22 +48,19 @@ public async Task<RegisteredResponse> Handle(RegisterCommand request, Cancellati
passwordHash: out byte[] passwordHash,
passwordSalt: out byte[] passwordSalt
);
User newUser =
User<int, int> newUser =
new()
{
Email = request.UserForRegisterDto.Email,
FirstName = request.UserForRegisterDto.FirstName,
LastName = request.UserForRegisterDto.LastName,
PasswordHash = passwordHash,
PasswordSalt = passwordSalt,
Status = true
};
User createdUser = await _userRepository.AddAsync(newUser);
User<int, int> createdUser = await _userRepository.AddAsync(newUser);

AccessToken createdAccessToken = await _authService.CreateAccessToken(createdUser);

Core.Security.Entities.RefreshToken createdRefreshToken = await _authService.CreateRefreshToken(createdUser, request.IpAddress);
Core.Security.Entities.RefreshToken addedRefreshToken = await _authService.AddRefreshToken(createdRefreshToken);
Core.Security.Entities.RefreshToken<int, int> createdRefreshToken = await _authService.CreateRefreshToken(createdUser, request.IpAddress);
Core.Security.Entities.RefreshToken<int, int> addedRefreshToken = await _authService.AddRefreshToken(createdRefreshToken);

RegisteredResponse registeredResponse = new() { AccessToken = createdAccessToken, RefreshToken = addedRefreshToken };
return registeredResponse;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,26 @@
using FluentValidation;
using System.Text.RegularExpressions;

namespace Application.Features.Auth.Commands.Register;

public class RegisterCommandValidator : AbstractValidator<RegisterCommand>
{
public RegisterCommandValidator()
{
RuleFor(c => c.UserForRegisterDto.FirstName).NotEmpty().MinimumLength(2);
RuleFor(c => c.UserForRegisterDto.LastName).NotEmpty().MinimumLength(2);
RuleFor(c => c.UserForRegisterDto.Email).NotEmpty().EmailAddress();
RuleFor(c => c.UserForRegisterDto.Password).NotEmpty().MinimumLength(4);
RuleFor(c => c.UserForRegisterDto.Password)
.NotEmpty()
.MinimumLength(6)
.Must(StrongPassword)
.WithMessage(
"Password must contain at least one uppercase letter, one lowercase letter, one number and one special character."
);
}

private bool StrongPassword(string value)
{
Regex strongPasswordRegex = new("^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,}$", RegexOptions.Compiled);

return strongPasswordRegex.IsMatch(value);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ namespace Application.Features.Auth.Commands.Register;
public class RegisteredResponse : IResponse
{
public AccessToken AccessToken { get; set; }
public Core.Security.Entities.RefreshToken RefreshToken { get; set; }
public Core.Security.Entities.RefreshToken<int, int> RefreshToken { get; set; }

public RegisteredResponse()
{
AccessToken = null!;
RefreshToken = null!;
}

public RegisteredResponse(AccessToken accessToken, Core.Security.Entities.RefreshToken refreshToken)
public RegisteredResponse(AccessToken accessToken, Core.Security.Entities.RefreshToken<int, int> refreshToken)
{
AccessToken = accessToken;
RefreshToken = refreshToken;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public RevokeTokenCommandHandler(IAuthService authService, AuthBusinessRules aut

public async Task<RevokedTokenResponse> Handle(RevokeTokenCommand request, CancellationToken cancellationToken)
{
Core.Security.Entities.RefreshToken? refreshToken = await _authService.GetRefreshTokenByToken(request.Token);
Core.Security.Entities.RefreshToken<int, int>? refreshToken = await _authService.GetRefreshTokenByToken(request.Token);
await _authBusinessRules.RefreshTokenShouldBeExists(refreshToken);
await _authBusinessRules.RefreshTokenShouldBeActive(refreshToken!);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ AuthBusinessRules authBusinessRules

public async Task Handle(VerifyEmailAuthenticatorCommand request, CancellationToken cancellationToken)
{
EmailAuthenticator? emailAuthenticator = await _emailAuthenticatorRepository.GetAsync(
EmailAuthenticator<int, int>? emailAuthenticator = await _emailAuthenticatorRepository.GetAsync(
predicate: e => e.ActivationKey == request.ActivationKey,
cancellationToken: cancellationToken
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,13 @@ IAuthenticatorService authenticatorService

public async Task Handle(VerifyOtpAuthenticatorCommand request, CancellationToken cancellationToken)
{
OtpAuthenticator? otpAuthenticator = await _otpAuthenticatorRepository.GetAsync(
OtpAuthenticator<int, int>? otpAuthenticator = await _otpAuthenticatorRepository.GetAsync(
predicate: e => e.UserId == request.UserId,
cancellationToken: cancellationToken
);
await _authBusinessRules.OtpAuthenticatorShouldBeExists(otpAuthenticator);

User? user = await _userService.GetAsync(predicate: u => u.Id == request.UserId, cancellationToken: cancellationToken);
User<int, int>? user = await _userService.GetAsync(predicate: u => u.Id == request.UserId, cancellationToken: cancellationToken);
await _authBusinessRules.UserShouldBeExistsWhenSelected(user);

otpAuthenticator!.IsVerified = true;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
namespace Application.Features.Auth.Constants;
using Core.Security.Attributes;

namespace Application.Features.Auth.Constants;

[OperationClaimConstants]
public static class AuthOperationClaims
{
public const string Admin = "Auth.Admin";
public const string Write = "Auth.Write";
public const string Read = "Auth.Read";
public const string RevokeToken = "Auth.RevokeToken";
private const string _section = "Auth";

public const string Admin = $"{_section}.Admin";

public const string Write = $"{_section}.Write";
public const string Read = $"{_section}.Read";

public const string RevokeToken = $"{_section}.RevokeToken";
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ public class MappingProfiles : Profile
{
public MappingProfiles()
{
CreateMap<RefreshToken, RevokedTokenResponse>().ReverseMap();
CreateMap<RefreshToken<int, int>, RevokedTokenResponse>().ReverseMap();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
OtpAuthenticatorDontExists: "OTP authenticator don't exists."
AlreadyVerifiedOtpAuthenticatorIsExists: "Already verified OTP authenticator is exists."
EmailActivationKeyDontExists: "Email Activation Key don't exists."
UserDontExists: "User don't exists."
UserHaveAlreadyAAuthenticator: "User have already a authenticator."
UserDontExists: "User<int, int> don't exists."
UserHaveAlreadyAAuthenticator: "User<int, int> have already a authenticator."
RefreshDontExists: "Refresh don't exists."
InvalidRefreshToken: "Invalid refresh token."
UserMailAlreadyExists: "User mail already exists."
UserMailAlreadyExists: "User<int, int> mail already exists."
PasswordDontMatch: "Password don't match."
Original file line number Diff line number Diff line change
Expand Up @@ -26,51 +26,51 @@ private async Task throwBusinessException(string messageKey)
throw new BusinessException(message);
}

public async Task EmailAuthenticatorShouldBeExists(EmailAuthenticator? emailAuthenticator)
public async Task EmailAuthenticatorShouldBeExists(EmailAuthenticator<int, int>? emailAuthenticator)
{
if (emailAuthenticator is null)
await throwBusinessException(AuthMessages.EmailAuthenticatorDontExists);
}

public async Task OtpAuthenticatorShouldBeExists(OtpAuthenticator? otpAuthenticator)
public async Task OtpAuthenticatorShouldBeExists(OtpAuthenticator<int, int>? otpAuthenticator)
{
if (otpAuthenticator is null)
await throwBusinessException(AuthMessages.OtpAuthenticatorDontExists);
}

public async Task OtpAuthenticatorThatVerifiedShouldNotBeExists(OtpAuthenticator? otpAuthenticator)
public async Task OtpAuthenticatorThatVerifiedShouldNotBeExists(OtpAuthenticator<int, int>? otpAuthenticator)
{
if (otpAuthenticator is not null && otpAuthenticator.IsVerified)
await throwBusinessException(AuthMessages.AlreadyVerifiedOtpAuthenticatorIsExists);
}

public async Task EmailAuthenticatorActivationKeyShouldBeExists(EmailAuthenticator emailAuthenticator)
public async Task EmailAuthenticatorActivationKeyShouldBeExists(EmailAuthenticator<int, int> emailAuthenticator)
{
if (emailAuthenticator.ActivationKey is null)
await throwBusinessException(AuthMessages.EmailActivationKeyDontExists);
}

public async Task UserShouldBeExistsWhenSelected(User? user)
public async Task UserShouldBeExistsWhenSelected(User<int, int>? user)
{
if (user == null)
await throwBusinessException(AuthMessages.UserDontExists);
}

public async Task UserShouldNotBeHaveAuthenticator(User user)
public async Task UserShouldNotBeHaveAuthenticator(User<int, int> user)
{
if (user.AuthenticatorType != AuthenticatorType.None)
await throwBusinessException(AuthMessages.UserHaveAlreadyAAuthenticator);
}

public async Task RefreshTokenShouldBeExists(RefreshToken? refreshToken)
public async Task RefreshTokenShouldBeExists(RefreshToken<int, int>? refreshToken)
{
if (refreshToken == null)
await throwBusinessException(AuthMessages.RefreshDontExists);
}

public async Task RefreshTokenShouldBeActive(RefreshToken refreshToken)
public async Task RefreshTokenShouldBeActive(RefreshToken<int, int> refreshToken)
{
if (refreshToken.Revoked != null && DateTime.UtcNow >= refreshToken.Expires)
if (refreshToken.RevokedDate != null && DateTime.UtcNow >= refreshToken.ExpiresDate)
await throwBusinessException(AuthMessages.InvalidRefreshToken);
}

Expand All @@ -83,7 +83,7 @@ public async Task UserEmailShouldBeNotExists(string email)

public async Task UserPasswordShouldBeMatch(int id, string password)
{
User? user = await _userRepository.GetAsync(predicate: u => u.Id == id, enableTracking: false);
User<int, int>? user = await _userRepository.GetAsync(predicate: u => u.Id == id, enableTracking: false);
await UserShouldBeExistsWhenSelected(user);
if (!HashingHelper.VerifyPasswordHash(password, user!.PasswordHash, user.PasswordSalt))
await throwBusinessException(AuthMessages.PasswordDontMatch);
Expand Down
Loading

0 comments on commit 6214dfb

Please sign in to comment.