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

refactor: identity and authentication #52

Open
wants to merge 48 commits into
base: release-3
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
a3d73a6
refactor: when add user, assigned base role
KotDimos Jun 23, 2022
89ab5ed
fix: bug, when add and remove chat from user
KotDimos Jun 23, 2022
f731ed1
refactor: delete creator in chat
KotDimos Jun 23, 2022
c8786b9
feat: AuthenticateByJwt scenario supported
Jun 23, 2022
7dba6ac
fix: small codefix
Jun 23, 2022
f392bb3
chore: stuff linked with auth
Jun 23, 2022
997b7d7
feat: auth supported in controllers
Jun 23, 2022
9e102fc
feat: auth supported in swagger
Jun 23, 2022
450193e
style: redundant usings removed
Jun 23, 2022
2a80450
fix: api versions updated in swagger
Jun 24, 2022
1597e46
style: CQRS classes renamed with Command/Request suffix
Jun 24, 2022
3efc7a0
fix: auth redeveloped again...
Jun 24, 2022
28ef062
style: codestyle tips in controllers
Jun 24, 2022
7f8535d
feat: error-codes throwing and handling supported
Jun 24, 2022
9a3b578
feat: middlewares registered
Jun 24, 2022
47e3ceb
feat: error-handling middleware replaced with error-handling filter
Jun 24, 2022
9210677
feat: 401 error-code handling supported
Jun 24, 2022
19e6abe
fix: error-handling middleware replaced error-handling filter
Jun 25, 2022
4fcaf80
Merge remote-tracking branch 'origin/release-3' into refactor/identit…
Jun 25, 2022
652191e
refactor!: MessageIdentityUser merged with MessengerUser
Jun 25, 2022
cec0032
fix: tests updated after refactor
Jun 25, 2022
3f138ed
style: tests codestyle
Jun 25, 2022
6e2acc5
test: tests fixed with new mockExtension methods
Jun 25, 2022
c24d833
fix: small bug/logic fixes
Jun 25, 2022
4f64259
chore: CQRS.Tests renamed to Unit.Tests
Jun 25, 2022
705c2ea
style: small codestyle && naming fixes in integration tests
Jun 25, 2022
f02edd6
fix: add personal chat
KotDimos Jun 26, 2022
87e5bdb
feat: add test for add chats
KotDimos Jun 26, 2022
701a0db
fix: MessengerUser removed from ChatUser && ChatUserDto added
Jun 26, 2022
817961a
style: namespaces fixed
Jun 26, 2022
bfbbfc2
test: fixed after removing MessengerUser from ChatUser
Jun 26, 2022
b434b01
Merge remote-tracking branch 'origin/refactor/identity-and-authentica…
Jun 26, 2022
5bf81ba
fix: small fixes in user finding
Jun 26, 2022
f266890
fix: tests fixed
Jun 26, 2022
15b9474
feat: validTo prop added into AuthenticateByJwt endpoint response
Jun 26, 2022
4494245
chore: ApiClients updated
Jun 26, 2022
48dd03f
refactor: change role authorizate
KotDimos Jun 26, 2022
c3269de
Merge remote-tracking branch 'origin/refactor/identity-and-authentica…
Jun 26, 2022
84ce91b
fix: small api rules fix
Jun 26, 2022
90fbca2
chore: ApiClients updated with prettier classes and ctors
Jun 26, 2022
cd0ecd0
chore: small fixes in ApiClients
Jun 26, 2022
65f4033
chore: `nswag` configuration updated
Jul 8, 2022
132afc3
chore: ApiClients updated
Jul 8, 2022
e2020b3
feat: ClientBase class added
Jul 8, 2022
4a64111
fix: unused files removed
Jul 8, 2022
5822be8
fix: wrong response type
Jul 8, 2022
1467862
fix: unused files removed again
Jul 8, 2022
b27375a
fix: OpenApiContracts output renamed
Jul 8, 2022
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
2 changes: 1 addition & 1 deletion Do-Svyazi.User.sln
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Do-Svyazi.User.Web.Controll
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Do-Svyazi.User.Web.ApiClient", "Source\Presentation\Do-Svyazi.User.Web.ApiClient\Do-Svyazi.User.Web.ApiClient.csproj", "{A5520DDE-4790-46C6-BD8B-FD65BD5E32B9}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CQRS.Tests", "Source\Tests\CQRS.Tests\CQRS.Tests.csproj", "{86C8E30B-B631-46E7-A0DD-815D0F447079}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Unit.Tests", "Source\Tests\Unit.Tests\Unit.Tests.csproj", "{86C8E30B-B631-46E7-A0DD-815D0F447079}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Integration.Tests", "Source\Tests\Integration.Tests\Integration.Tests.csproj", "{83F47902-3FCC-4D11-A780-5590930C224C}"
EndProject
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@

namespace Do_Svyazi.User.Application.CQRS.Authenticate.Commands;

public record Register(RegisterModel model)
public record RegisterAdminCommand(RegisterModel model)
: IRequest;
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@

namespace Do_Svyazi.User.Application.CQRS.Authenticate.Commands;

public record RegisterAdmin(RegisterModel model)
: IRequest;
public record RegisterCommand(RegisterModel model)
: IRequest<Guid>;
Original file line number Diff line number Diff line change
Expand Up @@ -2,84 +2,72 @@
using Do_Svyazi.User.Application.CQRS.Handlers;
using Do_Svyazi.User.Domain.Authenticate;
using Do_Svyazi.User.Domain.Exceptions;
using Do_Svyazi.User.Domain.Users;
using MediatR;
using Microsoft.AspNetCore.Identity;

namespace Do_Svyazi.User.Application.CQRS.Authenticate.Handlers;

public class AuthenticateCommandHandler
lipa44 marked this conversation as resolved.
Show resolved Hide resolved
: ICommandHandler<Register, Unit>,
ICommandHandler<RegisterAdmin, Unit>
public class AuthenticateCommandHandler :
ICommandHandler<RegisterCommand, Guid>,
ICommandHandler<RegisterAdminCommand, Unit>
{
private readonly UserManager<MessageIdentityUser> _userManager;
private readonly UserManager<MessengerUser> _userManager;
private readonly RoleManager<MessageIdentityRole> _roleManager;

public AuthenticateCommandHandler(UserManager<MessageIdentityUser> userManager, RoleManager<MessageIdentityRole> roleManager)
public AuthenticateCommandHandler(
UserManager<MessengerUser> userManager,
RoleManager<MessageIdentityRole> roleManager)
{
_userManager = userManager;
_roleManager = roleManager;
}

public async Task<Unit> Handle(Register request, CancellationToken cancellationToken)
public async Task<Guid> Handle(RegisterCommand request, CancellationToken cancellationToken)
{
MessageIdentityUser? userExists = await _userManager.FindByNameAsync(request.model.NickName);

if (userExists != null)
{
throw new Do_Svyazi_User_BusinessLogicException(
"User already exists");
}

MessageIdentityUser user = new ()
{
SecurityStamp = Guid.NewGuid().ToString(),
UserName = request.model.NickName,
Email = request.model.Email,
};

IdentityResult? result = await _userManager.CreateAsync(user, request.model.Password);
if (!result.Succeeded)
{
throw new Do_Svyazi_User_BusinessLogicException(
"User creation failed! Please check user details and try again.");
}
RegisterModel registerModel = request.model;

return Unit.Value;
if (await _userManager.FindByNameAsync(registerModel.UserName) is not null)
throw new Do_Svyazi_User_BusinessLogicException("User already exists");

MessengerUser user = CreateIdentityUser(registerModel);

if (!(await _userManager.CreateAsync(user, registerModel.Password)).Succeeded)
throw new Do_Svyazi_User_BusinessLogicException("User creation failed! Please check user details and try again.");

return user.Id;
}

public async Task<Unit> Handle(RegisterAdmin request, CancellationToken cancellationToken)
public async Task<Unit> Handle(RegisterAdminCommand request, CancellationToken cancellationToken)
{
MessageIdentityUser userExists = await _userManager.FindByNameAsync(request.model.NickName);
if (userExists != null)
{
throw new Do_Svyazi_User_BusinessLogicException(
"User already exists");
}

MessageIdentityUser user = new ()
{
SecurityStamp = Guid.NewGuid().ToString(),
UserName = request.model.NickName,
};
IdentityResult? result = await _userManager.CreateAsync(user, request.model.Password);
if (!result.Succeeded)
{
throw new Do_Svyazi_User_BusinessLogicException(
"User creation failed! Please check user details and try again.");
}
RegisterModel registerModel = request.model;

MessengerUser user = await GetUserByUsernameOrEmail(registerModel);

if (!await _roleManager.RoleExistsAsync(MessageIdentityRole.Admin))
await _roleManager.CreateAsync(new MessageIdentityRole(MessageIdentityRole.Admin));

if (!await _roleManager.RoleExistsAsync(MessageIdentityRole.User))
await _roleManager.CreateAsync(new MessageIdentityRole(MessageIdentityRole.User));

if (await _roleManager.RoleExistsAsync(MessageIdentityRole.Admin))
{
await _userManager.AddToRoleAsync(user, MessageIdentityRole.Admin);
await _roleManager.CreateAsync(new MessageIdentityRole(MessageIdentityRole.User));
}

if (await _roleManager.RoleExistsAsync(MessageIdentityRole.User))
{
if (await _roleManager.RoleExistsAsync(MessageIdentityRole.Admin))
await _userManager.AddToRoleAsync(user, MessageIdentityRole.User);
await _roleManager.CreateAsync(new MessageIdentityRole(MessageIdentityRole.Admin));
}

return Unit.Value;
}

private MessengerUser CreateIdentityUser(RegisterModel userModel) =>
new (userModel.Name, userModel.UserName, userModel.Email, userModel.PhoneNumber);

private async Task<MessengerUser> GetUserByUsernameOrEmail(RegisterModel registerModel)
{
MessengerUser user = await _userManager.FindByNameAsync(registerModel.UserName)
?? await _userManager.FindByEmailAsync(registerModel.Email)
?? throw new Do_Svyazi_User_NotFoundException($"User with userName {registerModel.UserName} or email {registerModel.Email} was not found");

return user;
}
}
Original file line number Diff line number Diff line change
@@ -1,61 +1,107 @@
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using AutoMapper;
using AutoMapper.QueryableExtensions;
using Do_Svyazi.User.Application.CQRS.Authenticate.Queries;
using Do_Svyazi.User.Application.CQRS.Handlers;
using Do_Svyazi.User.Domain.Authenticate;
using Do_Svyazi.User.Domain.Exceptions;
using Do_Svyazi.User.Domain.Users;
using Do_Svyazi.User.Dtos.Users;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;

namespace Do_Svyazi.User.Application.CQRS.Authenticate.Handlers;

public class AuthenticateQueryHandler
: IQueryHandler<Login, JwtSecurityToken>
public class AuthenticateQueryHandler :
IQueryHandler<LoginRequest, JwtSecurityToken>,
IQueryHandler<AuthenticateByJwtRequest, AuthenticateResponse>,
IQueryHandler<GetUsersRequest, IReadOnlyCollection<MessengerUserDto>>
{
private readonly UserManager<MessageIdentityUser> _userManager;
private const string NameType = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name";

private readonly UserManager<MessengerUser> _userManager;
private readonly IConfiguration _configuration;
private readonly IMapper _mapper;

public AuthenticateQueryHandler(UserManager<MessageIdentityUser> userManager, IConfiguration configuration)
public AuthenticateQueryHandler(UserManager<MessengerUser> userManager, IConfiguration configuration, IMapper mapper)
{
_userManager = userManager;
_configuration = configuration;
_mapper = mapper;
}

public async Task<JwtSecurityToken> Handle(Login request, CancellationToken cancellationToken)
public async Task<JwtSecurityToken> Handle(LoginRequest request, CancellationToken cancellationToken)
{
var token = new JwtSecurityToken();
MessageIdentityUser user = await _userManager.FindByNameAsync(request.model.NickName);
if (user != null && await _userManager.CheckPasswordAsync(user, request.model.Password))
LoginModel loginModel = request.model;
MessengerUser user = await GetUserByUsernameOrEmail(loginModel);

if (!await _userManager.CheckPasswordAsync(user, loginModel.Password))
throw new UnauthorizedAccessException("Can't login with this credentials");

IList<string>? userRoles = await _userManager.GetRolesAsync(user);

var authClaims = new List<Claim>
{
IList<string>? userRoles = await _userManager.GetRolesAsync(user);
new Claim(ClaimTypes.Name, user.UserName),
new Claim(JwtRegisteredClaimNames.Jti, $"{user.Id}"),
new Claim(JwtRegisteredClaimNames.Email, $"{user.Email}"),
};

var authClaims = new List<Claim>
{
new Claim(ClaimTypes.Name, user.UserName),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
};
authClaims
.AddRange(userRoles
.Select(userRole => new Claim(ClaimTypes.Role, userRole)));
authClaims
.AddRange(userRoles
.Select(userRole => new Claim(ClaimTypes.Role, userRole)));

token = GetToken(authClaims);
}
return GetToken(authClaims);
}

return token;
public async Task<AuthenticateResponse> Handle(AuthenticateByJwtRequest request, CancellationToken cancellationToken)
{
var token = new JwtSecurityToken(request.jwtToken);
string userNickName = token.Claims.First(x => x.Type == NameType).Value;

var identityUser = await _userManager.FindByNameAsync(userNickName);
return new AuthenticateResponse(identityUser.Id, token.ValidTo);
}

public async Task<IReadOnlyCollection<MessengerUserDto>> Handle(GetUsersRequest request, CancellationToken cancellationToken)
=> await _userManager.Users
.ProjectTo<MessengerUserDto>(_mapper.ConfigurationProvider)
.ToListAsync(cancellationToken: cancellationToken);

private JwtSecurityToken GetToken(List<Claim> authClaims)
{
var authSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["JWT:Secret"]));

var time = DateTime.UtcNow.AddHours(int.Parse(_configuration["JWT:Expires"]));

var token = new JwtSecurityToken(
issuer: _configuration["JWT:ValidIssuer"],
audience: _configuration["JWT:ValidAudience"],
expires: DateTime.Now.AddHours(int.Parse(_configuration["JWT:Expires"])),
notBefore: DateTime.UtcNow,
expires: DateTime.UtcNow.AddHours(int.Parse(_configuration["JWT:Expires"])),
claims: authClaims,
signingCredentials: new SigningCredentials(authSigningKey, SecurityAlgorithms.HmacSha256));

return token;
}

private async Task<MessengerUser> GetUserByUsernameOrEmail(LoginModel loginModel)
{
MessengerUser? user = default;

if (loginModel.UserName is not null)
user = await _userManager.FindByNameAsync(loginModel.UserName);

if (user is null && loginModel.Email is not null)
user = await _userManager.FindByEmailAsync(loginModel.Email);

if (user is null)
throw new Do_Svyazi_User_NotFoundException($"User {loginModel.UserName} to login was not found");

return user;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using MediatR;

namespace Do_Svyazi.User.Application.CQRS.Authenticate.Queries;

public record AuthenticateByJwtRequest(string jwtToken)
: IRequest<AuthenticateResponse>;

public record AuthenticateResponse(Guid userId, DateTime validTo);
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
using Do_Svyazi.User.Dtos.Users;
using MediatR;

namespace Do_Svyazi.User.Application.CQRS.Authenticate.Queries;

public record GetUsersRequest
: IRequest<IReadOnlyCollection<MessengerUserDto>>;
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@

namespace Do_Svyazi.User.Application.CQRS.Authenticate.Queries;

public record Login(LoginModel model)
public record LoginRequest(LoginModel model)
: IRequest<JwtSecurityToken>;
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@

namespace Do_Svyazi.User.Application.CQRS.Chats.Commands;

public record AddSavedMessages(Guid userId, string name, string description)
public record AddChannelCommand(Guid adminId, string name, string description)
: IRequest<Guid>;
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@

namespace Do_Svyazi.User.Application.CQRS.Chats.Commands;

public record AddGroupChat(Guid adminId, string name, string description)
public record AddGroupChatCommand(Guid adminId, string name, string description)
: IRequest<Guid>;

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
using MediatR;

namespace Do_Svyazi.User.Application.CQRS.Chats.Commands;

public record AddPersonalChatCommand(Guid firstUserId, Guid secondUserId, string name, string description)
: IRequest<Guid>;
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@

namespace Do_Svyazi.User.Application.CQRS.Chats.Commands;

public record AddChannel(Guid adminId, string name, string description)
public record AddSavedMessagesCommand(Guid userId, string name, string description)
: IRequest<Guid>;
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@

namespace Do_Svyazi.User.Application.CQRS.Chats.Commands;

public record AddUserToChat(Guid userId, Guid chatId)
public record AddUserToChatCommand(Guid userId, Guid chatId)
: IRequest<Unit>;
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@

namespace Do_Svyazi.User.Application.CQRS.Chats.Commands;

public record DeleteUserFromChat(Guid userId, Guid chatId)
public record DeleteUserFromChatCommand(Guid userId, Guid chatId)
: IRequest;
Loading