From e5854ef76b989d0fa61fe4b54455b7849b65d184 Mon Sep 17 00:00:00 2001 From: Tudor Stanciu Date: Tue, 7 Mar 2023 19:44:55 +0200 Subject: [PATCH] Tuitio refactoring and account logout implementation --- README.md | 7 +-- ReleaseNotes.xml | 4 +- .../CommandHandlers/AccountLoginHandler.cs | 46 ++++++++++++++++ .../CommandHandlers/AccountLogoutHandler.cs | 40 ++++++++++++++ .../AuthenticateUserHandler.cs | 44 ---------------- .../CommandHandlers/AuthorizeTokenHandler.cs | 44 +++++++++------- .../Commands/AuthenticateUser.cs | 13 ----- .../Commands/AuthorizeToken.cs | 12 ----- .../Mappings/MappingProfile.cs | 7 +-- .../Services/BehaviorService.cs | 25 ++++----- .../Services/ITokenService.cs | 3 +- .../Services/IUserService.cs | 6 ++- .../Services/TokenService.cs | 52 +++++++++---------- .../Services/UserService.cs | 41 ++++++++++----- src/Tuitio.Application/Stores/ITokenStore.cs | 6 ++- src/Tuitio.Application/Stores/TokenStore.cs | 48 +++++++---------- ...dentityDbContext.cs => TuitioDbContext.cs} | 4 +- .../DependencyInjectionExtensions.cs | 4 +- .../UserTokenConfiguration.cs | 3 +- ...dentityRepository.cs => UserRepository.cs} | 43 ++++++++++----- .../Scripts/1.0.1/02.UserToken table.sql | 5 +- src/Tuitio.Domain/Entities/UserToken.cs | 3 +- src/Tuitio.Domain/Models/Account/Records.cs | 7 +++ src/Tuitio.Domain/Models/Token.cs | 16 ++++-- src/Tuitio.Domain/Models/TokenCore.cs | 19 ------- ...entityRepository.cs => IUserRepository.cs} | 6 ++- .../Constants/AuthenticationStatus.cs | 6 +-- src/Tuitio.PublishedLanguage/Dto/Envelope.cs | 26 ++++++++++ .../Dto/ResultRecords.cs | 10 ++++ src/Tuitio.PublishedLanguage/Dto/Token.cs | 12 ++--- .../Events/AuthenticateUserResult.cs | 12 ----- src/Tuitio.Wrapper/Constants/ApiRoutes.cs | 4 +- .../DependencyInjectionExtension.cs | 2 +- src/Tuitio.Wrapper/README.md | 6 +-- ...{IIdentityService.cs => ITuitioService.cs} | 4 +- .../{IdentityService.cs => TuitioService.cs} | 8 +-- src/Tuitio.Wrapper/Tuitio.Wrapper.csproj | 2 +- src/Tuitio/Controllers/AccountController.cs | 38 ++++++++++++++ src/Tuitio/Controllers/ConnectController.cs | 29 +++++++++++ src/Tuitio/Controllers/IdentityController.cs | 43 --------------- src/Tuitio/Extensions/StartupExtensions.cs | 2 +- 41 files changed, 403 insertions(+), 309 deletions(-) create mode 100644 src/Tuitio.Application/CommandHandlers/AccountLoginHandler.cs create mode 100644 src/Tuitio.Application/CommandHandlers/AccountLogoutHandler.cs delete mode 100644 src/Tuitio.Application/CommandHandlers/AuthenticateUserHandler.cs delete mode 100644 src/Tuitio.Application/Commands/AuthenticateUser.cs delete mode 100644 src/Tuitio.Application/Commands/AuthorizeToken.cs rename src/Tuitio.Domain.Data/DbContexts/{IdentityDbContext.cs => TuitioDbContext.cs} (88%) rename src/Tuitio.Domain.Data/Repositories/{IdentityRepository.cs => UserRepository.cs} (53%) create mode 100644 src/Tuitio.Domain/Models/Account/Records.cs delete mode 100644 src/Tuitio.Domain/Models/TokenCore.cs rename src/Tuitio.Domain/Repositories/{IIdentityRepository.cs => IUserRepository.cs} (75%) create mode 100644 src/Tuitio.PublishedLanguage/Dto/Envelope.cs create mode 100644 src/Tuitio.PublishedLanguage/Dto/ResultRecords.cs delete mode 100644 src/Tuitio.PublishedLanguage/Events/AuthenticateUserResult.cs rename src/Tuitio.Wrapper/Services/{IIdentityService.cs => ITuitioService.cs} (69%) rename src/Tuitio.Wrapper/Services/{IdentityService.cs => TuitioService.cs} (82%) create mode 100644 src/Tuitio/Controllers/AccountController.cs create mode 100644 src/Tuitio/Controllers/ConnectController.cs delete mode 100644 src/Tuitio/Controllers/IdentityController.cs diff --git a/README.md b/README.md index 70354ec..49a845f 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,10 @@ # Tuitio Tuitio is a simple identity server implementation focused strictly on the needs of my home lab. -At the moment it has a simple API consisting of only two methods: -* ```/identity/authenticate``` - handles user authentication using credentials and generates an access token. -* ```/identity/authorize``` - manages the authorization process for a token, including verification of its existence, validity, and authenticity. +At the moment it has a simple API consisting of only three methods: +* ```/account/login``` - handles user authentication using credentials and generates an access token. +* ```/account/logout``` - handles user logout. +* ```/connect/authorize``` - manages the authorization process for a token, including verification of its existence, validity, and authenticity. ***Tuitio*** is a latin word that encompasses meanings such as supervision, safeguarding, defense, guard duty, and protection. diff --git a/ReleaseNotes.xml b/ReleaseNotes.xml index 48b7fb7..4fc73d6 100644 --- a/ReleaseNotes.xml +++ b/ReleaseNotes.xml @@ -8,9 +8,9 @@ A client/consumer can do only two things: - Authentication: An user name and a password are required in the request body. The request type is POST. The output is an object with the following structure: { token: { raw: "***", validFrom: "", validUntil: "" }, status: "SUCCESS" } - Authorization: The request type is also POST and and its scope is to authorize a token. The input is just the token in string format: { token: "***" } - For .NET consumers there are two nuget packages developed to facilitate the integration with this identity server: + For .NET consumers there are two nuget packages developed to facilitate the integration with this Tuitio server: - Tuitio.PublishedLanguage: It contains constants and classes for data transfer objects. - - Tuitio.Wrapper: It compose and executes all the REST requests to the identity server and offers to a consumer a simple interface with all methods. This interface can be injected with dependency injection at consumer startup with UseIdentityServices method. The only input is the server base address. + - Tuitio.Wrapper: It compose and executes all the REST requests to the Tuitio server and offers to a consumer a simple interface with all methods. This interface can be injected with dependency injection at consumer startup with UseTuitioServices method. The only input is the server base address. - The source of this nugets is public, but on my personal server: https://lab.code-rove.com/public-nuget-server/nuget diff --git a/src/Tuitio.Application/CommandHandlers/AccountLoginHandler.cs b/src/Tuitio.Application/CommandHandlers/AccountLoginHandler.cs new file mode 100644 index 0000000..64fffde --- /dev/null +++ b/src/Tuitio.Application/CommandHandlers/AccountLoginHandler.cs @@ -0,0 +1,46 @@ +// Copyright (c) 2020 Tudor Stanciu + +using MediatR; +using Microsoft.Extensions.Logging; +using System.Threading; +using System.Threading.Tasks; +using Tuitio.Application.Services; +using Tuitio.PublishedLanguage.Constants; +using Tuitio.PublishedLanguage.Dto; + +namespace Tuitio.Application.CommandHandlers +{ + public class AccountLoginHandler + { + public record Command(string UserName, string Password) : IRequest>; + + public class CommandHandler : IRequestHandler> + { + private readonly IUserService _userService; + private readonly ILogger _logger; + + public CommandHandler(IUserService userService, ILogger logger) + { + _userService = userService; + _logger = logger; + } + + public async Task> Handle(Command command, CancellationToken cancellationToken) + { + var loginResult = await _userService.Login(command.UserName, command.Password); + if (loginResult == null) + { + _logger.LogDebug($"Authentication failed for user '{command.UserName}'."); + return Envelope.Error(EnvelopeStatus.BAD_CREDENTIALS); + } + + _logger.LogDebug($"Authentication succeeded for user '{command.UserName}'."); + + var token = new Token(loginResult.Raw, loginResult.Token.ExpiresIn); + + var result = new AccountLoginResult(token); + return Envelope.Success(result); + } + } + } +} diff --git a/src/Tuitio.Application/CommandHandlers/AccountLogoutHandler.cs b/src/Tuitio.Application/CommandHandlers/AccountLogoutHandler.cs new file mode 100644 index 0000000..f3c389c --- /dev/null +++ b/src/Tuitio.Application/CommandHandlers/AccountLogoutHandler.cs @@ -0,0 +1,40 @@ +// Copyright (c) 2020 Tudor Stanciu + +using AutoMapper; +using MediatR; +using Microsoft.Extensions.Logging; +using System.Threading; +using System.Threading.Tasks; +using Tuitio.Application.Services; +using Tuitio.PublishedLanguage.Dto; + +namespace Tuitio.Application.CommandHandlers +{ + public class AccountLogoutHandler + { + public record Command(string Token) : IRequest>; + + public class CommandHandler : IRequestHandler> + { + private readonly IUserService _userService; + private readonly IMapper _mapper; + private readonly ILogger _logger; + + public CommandHandler(IUserService userService, IMapper mapper, ILogger logger) + { + _userService = userService; + _mapper = mapper; + _logger = logger; + } + + public async Task> Handle(Command command, CancellationToken cancellationToken) + { + var logoutResult = await _userService.Logout(command.Token); + _logger.LogDebug($"Logout succeeded for user '{logoutResult.UserName}'."); + + var result = _mapper.Map(logoutResult); + return Envelope.Success(result); + } + } + } +} diff --git a/src/Tuitio.Application/CommandHandlers/AuthenticateUserHandler.cs b/src/Tuitio.Application/CommandHandlers/AuthenticateUserHandler.cs deleted file mode 100644 index 61e4b60..0000000 --- a/src/Tuitio.Application/CommandHandlers/AuthenticateUserHandler.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) 2020 Tudor Stanciu - -using AutoMapper; -using Tuitio.Application.Commands; -using Tuitio.Application.Services; -using Tuitio.PublishedLanguage.Constants; -using Tuitio.PublishedLanguage.Dto; -using Tuitio.PublishedLanguage.Events; -using MediatR; -using Microsoft.Extensions.Logging; -using System.Threading; -using System.Threading.Tasks; - -namespace Tuitio.Application.CommandHandlers -{ - public class AuthenticateUserHandler : IRequestHandler - { - private readonly IUserService _userService; - private readonly IMapper _mapper; - private readonly ILogger _logger; - - public AuthenticateUserHandler(IUserService userService, IMapper mapper, ILogger logger) - { - _userService = userService; - _mapper = mapper; - _logger = logger; - } - - public async Task Handle(AuthenticateUser command, CancellationToken cancellationToken) - { - var internalToken = await _userService.Authenticate(command.UserName, command.Password); - if (internalToken == null) - { - _logger.LogDebug($"Authentication failed for user '{command.UserName}'."); - return new AuthenticateUserResult() { Status = AuthenticationStatus.BAD_CREDENTIALS }; - } - - _logger.LogDebug($"Authentication succeeded for user '{command.UserName}'."); - var token = _mapper.Map(internalToken); - - return new AuthenticateUserResult() { Token = token, Status = AuthenticationStatus.SUCCESS }; - } - } -} diff --git a/src/Tuitio.Application/CommandHandlers/AuthorizeTokenHandler.cs b/src/Tuitio.Application/CommandHandlers/AuthorizeTokenHandler.cs index 986307f..f798dfc 100644 --- a/src/Tuitio.Application/CommandHandlers/AuthorizeTokenHandler.cs +++ b/src/Tuitio.Application/CommandHandlers/AuthorizeTokenHandler.cs @@ -1,42 +1,48 @@ // Copyright (c) 2020 Tudor Stanciu using AutoMapper; -using Tuitio.Application.Commands; using Tuitio.Application.Services; using Tuitio.PublishedLanguage.Dto; using MediatR; using Microsoft.Extensions.Logging; using System.Threading; using System.Threading.Tasks; +using Tuitio.PublishedLanguage.Constants; namespace Tuitio.Application.CommandHandlers { - public class AuthorizeTokenHandler : IRequestHandler + public class AuthorizeTokenHandler { - private readonly IUserService _userService; - private readonly IMapper _mapper; - private readonly ILogger _logger; + public record Command(string Token) : IRequest>; - public AuthorizeTokenHandler(IUserService userService, IMapper mapper, ILogger logger) + public class CommandHandler : IRequestHandler> { - _userService = userService; - _mapper = mapper; - _logger = logger; - } + private readonly IUserService _userService; + private readonly IMapper _mapper; + private readonly ILogger _logger; - public Task Handle(AuthorizeToken command, CancellationToken cancellationToken) - { - var tokenCore = _userService.Authorize(command.Token); - if (tokenCore == null) + public CommandHandler(IUserService userService, IMapper mapper, ILogger logger) { - _logger.LogDebug($"Authorization failed for token '{command.Token}'."); - return null; + _userService = userService; + _mapper = mapper; + _logger = logger; } - _logger.LogDebug($"Authorization succeeded for token '{command.Token}'."); - var tokenCoreResult = _mapper.Map(tokenCore); + public Task> Handle(Command command, CancellationToken cancellationToken) + { + var token = _userService.Authorize(command.Token); + if (token == null) + { + _logger.LogDebug($"Authorization failed for token '{command.Token}'."); + var result = Envelope.Error(EnvelopeStatus.UNAUTHORIZED); + return Task.FromResult(result); + } - return Task.FromResult(tokenCoreResult); + _logger.LogDebug($"Authorization succeeded for token '{command.Token}'."); + var authorizationResult = new TokenAuthorizationResult(_mapper.Map(token)); + var envelope = Envelope.Success(authorizationResult); + return Task.FromResult(envelope); + } } } } diff --git a/src/Tuitio.Application/Commands/AuthenticateUser.cs b/src/Tuitio.Application/Commands/AuthenticateUser.cs deleted file mode 100644 index 7345a7f..0000000 --- a/src/Tuitio.Application/Commands/AuthenticateUser.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) 2020 Tudor Stanciu - -using MediatR; -using Tuitio.PublishedLanguage.Events; - -namespace Tuitio.Application.Commands -{ - public class AuthenticateUser : IRequest - { - public string UserName { get; set; } - public string Password { get; set; } - } -} diff --git a/src/Tuitio.Application/Commands/AuthorizeToken.cs b/src/Tuitio.Application/Commands/AuthorizeToken.cs deleted file mode 100644 index 7cc0c32..0000000 --- a/src/Tuitio.Application/Commands/AuthorizeToken.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) 2020 Tudor Stanciu - -using MediatR; -using Tuitio.PublishedLanguage.Dto; - -namespace Tuitio.Application.Commands -{ - public class AuthorizeToken : IRequest - { - public string Token { get; set; } - } -} diff --git a/src/Tuitio.Application/Mappings/MappingProfile.cs b/src/Tuitio.Application/Mappings/MappingProfile.cs index 21b6efd..d77d479 100644 --- a/src/Tuitio.Application/Mappings/MappingProfile.cs +++ b/src/Tuitio.Application/Mappings/MappingProfile.cs @@ -12,10 +12,11 @@ namespace Tuitio.Application.Mappings { public MappingProfile() { - CreateMap(); - CreateMap(); - CreateMap() + CreateMap(); + CreateMap() .ForMember(z => z.Claims, src => src.MapFrom(z => ComposeClaims(z.Claims))); + + CreateMap(); } private Dictionary ComposeClaims(ICollection claims) diff --git a/src/Tuitio.Application/Services/BehaviorService.cs b/src/Tuitio.Application/Services/BehaviorService.cs index c4e5bee..74616f1 100644 --- a/src/Tuitio.Application/Services/BehaviorService.cs +++ b/src/Tuitio.Application/Services/BehaviorService.cs @@ -1,14 +1,13 @@ // Copyright (c) 2020 Tudor Stanciu -using Tuitio.Application.Services.Abstractions; -using Tuitio.Application.Stores; -using Tuitio.Domain.Entities; -using Tuitio.Domain.Models; -using Tuitio.Domain.Repositories; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using System; using System.Threading.Tasks; +using Tuitio.Application.Services.Abstractions; +using Tuitio.Application.Stores; +using Tuitio.Domain.Entities; +using Tuitio.Domain.Repositories; namespace Tuitio.Application.Services { @@ -16,13 +15,15 @@ namespace Tuitio.Application.Services { private readonly IServiceProvider _serviceProvider; private readonly ILogger _logger; + private readonly ITokenService _tokenService; private readonly ITokenStore _securityStore; - public BehaviorService(IServiceProvider serviceProvider, ILogger logger, ITokenStore securityStore) + public BehaviorService(IServiceProvider serviceProvider, ILogger logger, ITokenService tokenService, ITokenStore securityStore) { - _serviceProvider = serviceProvider; - _logger = logger; - _securityStore = securityStore; + _serviceProvider=serviceProvider; + _logger=logger; + _tokenService=tokenService; + _securityStore=securityStore; } public void FillTokenStore() @@ -33,7 +34,7 @@ namespace Tuitio.Application.Services var activeTokens = Array.Empty(); using (var scope = _serviceProvider.CreateScope()) { - var _repository = scope.ServiceProvider.GetRequiredService(); + var _repository = scope.ServiceProvider.GetRequiredService(); activeTokens = await _repository.GetActiveTokens(); } @@ -43,8 +44,8 @@ namespace Tuitio.Application.Services _logger.LogInformation($"BehaviorService: {activeTokens.Length} active tokens were found in database."); foreach (var token in activeTokens) { - var storeToken = new Token() { Raw = token.Token, ValidFrom = token.ValidFrom, ValidUntil = token.ValidUntil }; - _securityStore.SetToken(storeToken, token.UserId); + var storeToken = _tokenService.ExtractToken(token.Token); + _securityStore.Set(token.Token, storeToken); } } } diff --git a/src/Tuitio.Application/Services/ITokenService.cs b/src/Tuitio.Application/Services/ITokenService.cs index 6eec150..ff1f77c 100644 --- a/src/Tuitio.Application/Services/ITokenService.cs +++ b/src/Tuitio.Application/Services/ITokenService.cs @@ -8,6 +8,7 @@ namespace Tuitio.Application.Services internal interface ITokenService { Token GenerateToken(AppUser user); - TokenCore ExtractTokenCore(string tokenRaw); + string GenerateTokenRaw(Token token); + Token ExtractToken(string tokenRaw); } } \ No newline at end of file diff --git a/src/Tuitio.Application/Services/IUserService.cs b/src/Tuitio.Application/Services/IUserService.cs index 7b12c69..d4df8c7 100644 --- a/src/Tuitio.Application/Services/IUserService.cs +++ b/src/Tuitio.Application/Services/IUserService.cs @@ -2,12 +2,14 @@ using Tuitio.Domain.Models; using System.Threading.Tasks; +using Tuitio.Domain.Models.Account; namespace Tuitio.Application.Services { public interface IUserService { - Task Authenticate(string userName, string password); - TokenCore Authorize(string token); + Task Login(string userName, string password); + Task Logout(string tokenRaw); + Token Authorize(string tokenRaw); } } \ No newline at end of file diff --git a/src/Tuitio.Application/Services/TokenService.cs b/src/Tuitio.Application/Services/TokenService.cs index 0353639..7dd100d 100644 --- a/src/Tuitio.Application/Services/TokenService.cs +++ b/src/Tuitio.Application/Services/TokenService.cs @@ -1,13 +1,14 @@ // Copyright (c) 2020 Tudor Stanciu using AutoMapper; -using Tuitio.Domain.Abstractions; -using Tuitio.Domain.Entities; -using Tuitio.Domain.Models; using Newtonsoft.Json; using System; using System.Text; using System.Text.RegularExpressions; +using Tuitio.Application.Stores; +using Tuitio.Domain.Abstractions; +using Tuitio.Domain.Entities; +using Tuitio.Domain.Models; namespace Tuitio.Application.Services { @@ -15,47 +16,46 @@ namespace Tuitio.Application.Services { private readonly IMapper _mapper; private readonly IConfigProvider _configProvider; + private readonly ITokenStore _tokenStore; - public TokenService(IMapper mapper, IConfigProvider configProvider) + public TokenService(IMapper mapper, IConfigProvider configProvider, ITokenStore tokenStore) { - _mapper = mapper; - _configProvider = configProvider; + _mapper=mapper; + _configProvider=configProvider; + _tokenStore=tokenStore; } public Token GenerateToken(AppUser user) { - var tokenRaw = GenerateTokenRaw(user); - var currentDate = DateTime.Now; - var token = new Token() { Raw = tokenRaw, ValidFrom = currentDate, ValidUntil = currentDate.AddMinutes(_configProvider.Token.ValidityInMinutes) }; + var currentDate = DateTime.UtcNow; + var validUntil = currentDate.AddMinutes(_configProvider.Token.ValidityInMinutes); + + var token = _mapper.Map(user); + token.TokenId = Guid.NewGuid(); + token.LockStamp = Regex.Replace(Convert.ToBase64String(Guid.NewGuid().ToByteArray()), "[/+=]", ""); + token.CreatedAt = currentDate; + token.ExpiresIn = (validUntil - currentDate).TotalMilliseconds; return token; } - private string GenerateTokenRaw(AppUser user) + public string GenerateTokenRaw(Token token) { - var tokenCore = GenerateTokenCore(user); - var tokenCoreString = JsonConvert.SerializeObject(tokenCore); - var tokenCoreBytes = Encoding.UTF8.GetBytes(tokenCoreString); - var tokenRaw = Convert.ToBase64String(tokenCoreBytes); + var tokenString = JsonConvert.SerializeObject(token); + var tokenBytes = Encoding.UTF8.GetBytes(tokenString); + var tokenRaw = Convert.ToBase64String(tokenBytes); return tokenRaw; } - private TokenCore GenerateTokenCore(AppUser user) - { - var tokenCore = _mapper.Map(user); - tokenCore.LockStamp = Regex.Replace(Convert.ToBase64String(Guid.NewGuid().ToByteArray()), "[/+=]", ""); - return tokenCore; - } - - public TokenCore ExtractTokenCore(string tokenRaw) + public Token ExtractToken(string tokenRaw) { var valid = ValidateTokenRaw(tokenRaw); if (!valid) return null; - var tokenCoreBytes = Convert.FromBase64String(tokenRaw); - var tokenCoreString = Encoding.UTF8.GetString(tokenCoreBytes); - var tokenCore = JsonConvert.DeserializeObject(tokenCoreString); - return tokenCore; + var tokenBytes = Convert.FromBase64String(tokenRaw); + var tokenString = Encoding.UTF8.GetString(tokenBytes); + var token = JsonConvert.DeserializeObject(tokenString); + return token; } private bool ValidateTokenRaw(string tokenRaw) diff --git a/src/Tuitio.Application/Services/UserService.cs b/src/Tuitio.Application/Services/UserService.cs index 24cafe3..e519fa2 100644 --- a/src/Tuitio.Application/Services/UserService.cs +++ b/src/Tuitio.Application/Services/UserService.cs @@ -8,54 +8,69 @@ using Tuitio.Domain.Models; using Tuitio.Domain.Repositories; using System; using System.Threading.Tasks; +using Tuitio.Domain.Models.Account; namespace Tuitio.Application.Services { internal class UserService : IUserService { private readonly ITokenStore _securityStore; - private readonly IIdentityRepository _identityRepository; + private readonly IUserRepository _userRepository; private readonly ITokenService _tokenService; private readonly IConfigProvider _configProvider; private readonly IHashingService _hashingService; - public UserService(ITokenStore securityStore, IIdentityRepository identityRepository, ITokenService tokenService, IConfigProvider configProvider, IHashingService hashingService) + public UserService(ITokenStore securityStore, IUserRepository userRepository, ITokenService tokenService, IConfigProvider configProvider, IHashingService hashingService) { _securityStore=securityStore; - _identityRepository=identityRepository; + _userRepository=userRepository; _tokenService=tokenService; _configProvider=configProvider; _hashingService=hashingService; } - public async Task Authenticate(string userName, string password) + public async Task Login(string userName, string password) { var passwordHash = _hashingService.HashSha256(password); - var user = await _identityRepository.GetUser(userName, passwordHash); + var user = await _userRepository.GetUser(userName, passwordHash); var valid = ValidateUser(user); if (!valid) return null; var token = _tokenService.GenerateToken(user); - _securityStore.SetToken(token, user.UserId); - await _identityRepository.UpdateUserAfterAuthentication(user, token); + var raw = _tokenService.GenerateTokenRaw(token); + _securityStore.Set(raw, token); - return token; + + await _userRepository.UpdateUserAfterAuthentication(user, token, raw); + + var result = new LoginResult(token, raw); + return result; } - public TokenCore Authorize(string token) + public async Task Logout(string tokenRaw) { - var tokenCore = _securityStore.ValidateAndGetTokenCore(token); - if (tokenCore == null) + var token = _securityStore.Get(tokenRaw); + if (token == null) return null; - return tokenCore; + await _userRepository.RemoveToken(token.TokenId); + _securityStore.Remove(tokenRaw); + + var result = new LogoutResult(token.UserId, token.UserName, DateTime.UtcNow); + return result; + } + + public Token Authorize(string tokenRaw) + { + var token = _securityStore.Get(tokenRaw); + return token; } private bool ValidateUser(AppUser user) { if (user == null) - return false; + throw new ArgumentNullException(nameof(user)); if (user.FailedLoginAttempts.HasValue && user.FailedLoginAttempts.Value > _configProvider.Restrictions.MaxFailedLoginAttempts) return false; diff --git a/src/Tuitio.Application/Stores/ITokenStore.cs b/src/Tuitio.Application/Stores/ITokenStore.cs index 0c4e1b9..b4026f8 100644 --- a/src/Tuitio.Application/Stores/ITokenStore.cs +++ b/src/Tuitio.Application/Stores/ITokenStore.cs @@ -1,12 +1,14 @@ // Copyright (c) 2020 Tudor Stanciu +using System; using Tuitio.Domain.Models; namespace Tuitio.Application.Stores { internal interface ITokenStore { - void SetToken(Token token, int userId); - TokenCore ValidateAndGetTokenCore(string token); + Token Get(string key); + bool Set(string key, Token token); + void Remove(string key); } } diff --git a/src/Tuitio.Application/Stores/TokenStore.cs b/src/Tuitio.Application/Stores/TokenStore.cs index c427197..3f44440 100644 --- a/src/Tuitio.Application/Stores/TokenStore.cs +++ b/src/Tuitio.Application/Stores/TokenStore.cs @@ -1,49 +1,39 @@ // Copyright (c) 2020 Tudor Stanciu -using Tuitio.Application.Services; -using Tuitio.Domain.Models; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Linq; +using Tuitio.Domain.Models; namespace Tuitio.Application.Stores { internal class TokenStore : ITokenStore { - private readonly ITokenService _tokenService; - private ConcurrentDictionary> Tokens { get; } + private ConcurrentDictionary Tokens { get; } - public TokenStore(ITokenService tokenService) + public TokenStore() { - _tokenService = tokenService; - Tokens = new ConcurrentDictionary>(); + Tokens = new ConcurrentDictionary(); } - public void SetToken(Token token, int userId) + public Token Get(string key) { - var registered = Tokens.TryGetValue(userId, out List list); - - if (registered) - list.Add(token); - else - Tokens.TryAdd(userId, new List() { token }); - } - - public TokenCore ValidateAndGetTokenCore(string token) - { - var tokenCore = _tokenService.ExtractTokenCore(token); - if (tokenCore == null) - return null; - - var registered = Tokens.TryGetValue(tokenCore.UserId, out List list); + var registered = Tokens.ContainsKey(key); if (!registered) return null; - var valid = list.FirstOrDefault(z => z.Raw == token); - if (valid == null) - return null; - - return tokenCore; + return Tokens[key]; } + + public bool Set(string key, Token token) + { + var registered = Tokens.ContainsKey(key); + if (registered) + return false; + + Tokens.TryAdd(key, token); + return true; + } + + public void Remove(string key) => Tokens.Remove(key, out _); } } diff --git a/src/Tuitio.Domain.Data/DbContexts/IdentityDbContext.cs b/src/Tuitio.Domain.Data/DbContexts/TuitioDbContext.cs similarity index 88% rename from src/Tuitio.Domain.Data/DbContexts/IdentityDbContext.cs rename to src/Tuitio.Domain.Data/DbContexts/TuitioDbContext.cs index cf61a1a..0b5580d 100644 --- a/src/Tuitio.Domain.Data/DbContexts/IdentityDbContext.cs +++ b/src/Tuitio.Domain.Data/DbContexts/TuitioDbContext.cs @@ -6,12 +6,12 @@ using Microsoft.EntityFrameworkCore; namespace Tuitio.Domain.Data.DbContexts { - public class IdentityDbContext : DbContext + public class TuitioDbContext : DbContext { public DbSet Users { get; set; } public DbSet UserTokens { get; set; } - public IdentityDbContext(DbContextOptions options) + public TuitioDbContext(DbContextOptions options) : base(options) { base.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.TrackAll; diff --git a/src/Tuitio.Domain.Data/DependencyInjectionExtensions.cs b/src/Tuitio.Domain.Data/DependencyInjectionExtensions.cs index 47a41c2..36e3dd6 100644 --- a/src/Tuitio.Domain.Data/DependencyInjectionExtensions.cs +++ b/src/Tuitio.Domain.Data/DependencyInjectionExtensions.cs @@ -13,10 +13,10 @@ namespace Tuitio.Domain.Data { public static void AddDataAccess(this IServiceCollection services) { - services.AddScoped(); + services.AddScoped(); services - .AddDbContextPool( + .AddDbContextPool( (serviceProvider, options) => { var configuration = serviceProvider.GetService(); diff --git a/src/Tuitio.Domain.Data/EntityTypeConfiguration/UserTokenConfiguration.cs b/src/Tuitio.Domain.Data/EntityTypeConfiguration/UserTokenConfiguration.cs index 4c39237..5676689 100644 --- a/src/Tuitio.Domain.Data/EntityTypeConfiguration/UserTokenConfiguration.cs +++ b/src/Tuitio.Domain.Data/EntityTypeConfiguration/UserTokenConfiguration.cs @@ -1,8 +1,8 @@ // Copyright (c) 2020 Tudor Stanciu -using Tuitio.Domain.Entities; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Tuitio.Domain.Entities; namespace Tuitio.Domain.Data.EntityTypeConfiguration { @@ -11,7 +11,6 @@ namespace Tuitio.Domain.Data.EntityTypeConfiguration public void Configure(EntityTypeBuilder builder) { builder.ToTable("UserToken").HasKey(z => z.TokenId); - builder.Property(z => z.TokenId).ValueGeneratedOnAdd(); } } } diff --git a/src/Tuitio.Domain.Data/Repositories/IdentityRepository.cs b/src/Tuitio.Domain.Data/Repositories/UserRepository.cs similarity index 53% rename from src/Tuitio.Domain.Data/Repositories/IdentityRepository.cs rename to src/Tuitio.Domain.Data/Repositories/UserRepository.cs index 77ec773..f01d665 100644 --- a/src/Tuitio.Domain.Data/Repositories/IdentityRepository.cs +++ b/src/Tuitio.Domain.Data/Repositories/UserRepository.cs @@ -11,11 +11,11 @@ using System.Threading.Tasks; namespace Tuitio.Domain.Data.Repositories { - class IdentityRepository : IIdentityRepository + class UserRepository : IUserRepository { - private readonly IdentityDbContext _dbContext; + private readonly TuitioDbContext _dbContext; - public IdentityRepository(IdentityDbContext dbContext) + public UserRepository(TuitioDbContext dbContext) { _dbContext = dbContext; } @@ -28,29 +28,48 @@ namespace Tuitio.Domain.Data.Repositories .FirstOrDefaultAsync(z => z.UserName == userName && z.Password == password); } - public async Task UpdateUserAfterAuthentication(AppUser user, Token token) + public async Task UpdateUserAfterAuthentication(AppUser user, Token token, string tokenRaw) { var userToken = new UserToken() { - UserId = user.UserId, - Token = token.Raw, - ValidFrom = token.ValidFrom, - ValidUntil = token.ValidUntil + TokenId = token.TokenId, + UserId = token.UserId, + Token = tokenRaw, + ValidFrom = token.CreatedAt, + ValidUntil = token.CreatedAt.AddMilliseconds(token.ExpiresIn) }; + await _dbContext.AddAsync(userToken); - user.LastLoginDate = DateTime.Now; - _dbContext.Update(user); + user.LastLoginDate = DateTime.UtcNow; + + //asta nu trebuie + //_dbContext.Update(user); await _dbContext.SaveChangesAsync(); } public Task GetActiveTokens() { - var currentDate = DateTime.Now; + var currentDate = DateTime.UtcNow; var query = _dbContext.UserTokens - .Where(z => z.ValidFrom <= currentDate && z.ValidUntil >= currentDate && (!z.Burnt.HasValue || z.Burnt.Value == false)); + .Where(z => z.ValidFrom <= currentDate && z.ValidUntil >= currentDate); + + // read all tokens, keep the valid ones and remove the expired ones. return query.ToArrayAsync(); } + + public Task RemoveToken(Guid tokenId) + { + var token = new UserToken() + { + TokenId = tokenId + }; + + _dbContext.UserTokens.Attach(token); + _dbContext.UserTokens.Remove(token); + + return _dbContext.SaveChangesAsync(); + } } } diff --git a/src/Tuitio.Domain.Data/Scripts/1.0.1/02.UserToken table.sql b/src/Tuitio.Domain.Data/Scripts/1.0.1/02.UserToken table.sql index 22b953c..396e93a 100644 --- a/src/Tuitio.Domain.Data/Scripts/1.0.1/02.UserToken table.sql +++ b/src/Tuitio.Domain.Data/Scripts/1.0.1/02.UserToken table.sql @@ -2,11 +2,10 @@ if not exists (select top 1 1 from sys.objects where name = 'UserToken' and type begin create table UserToken ( - TokenId int identity(1, 1) constraint PK_Token primary key, + TokenId uniqueidentifier constraint PK_Token primary key, UserId int not null constraint FK_Token_AppUser foreign key references AppUser(UserId), Token varchar(1000) not null, ValidFrom datetime not null, - ValidUntil datetime not null, - Burnt bit + ValidUntil datetime not null ) end \ No newline at end of file diff --git a/src/Tuitio.Domain/Entities/UserToken.cs b/src/Tuitio.Domain/Entities/UserToken.cs index e2f59a4..9744ee5 100644 --- a/src/Tuitio.Domain/Entities/UserToken.cs +++ b/src/Tuitio.Domain/Entities/UserToken.cs @@ -6,11 +6,10 @@ namespace Tuitio.Domain.Entities { public class UserToken { - public int TokenId { get; set; } + public Guid TokenId { get; set; } public int UserId { get; set; } public string Token { get; set; } public DateTime ValidFrom { get; set; } public DateTime ValidUntil { get; set; } - public bool? Burnt { get; set; } } } diff --git a/src/Tuitio.Domain/Models/Account/Records.cs b/src/Tuitio.Domain/Models/Account/Records.cs new file mode 100644 index 0000000..359d06b --- /dev/null +++ b/src/Tuitio.Domain/Models/Account/Records.cs @@ -0,0 +1,7 @@ +using System; + +namespace Tuitio.Domain.Models.Account +{ + public record LoginResult(Token Token, string Raw); + public record LogoutResult(int UserId, string UserName, DateTime LogoutDate); +} diff --git a/src/Tuitio.Domain/Models/Token.cs b/src/Tuitio.Domain/Models/Token.cs index 1bf56a2..9877562 100644 --- a/src/Tuitio.Domain/Models/Token.cs +++ b/src/Tuitio.Domain/Models/Token.cs @@ -1,13 +1,23 @@ // Copyright (c) 2020 Tudor Stanciu using System; +using System.Collections.Generic; namespace Tuitio.Domain.Models { public class Token { - public string Raw { get; set; } - public DateTime ValidFrom { get; set; } - public DateTime ValidUntil { get; set; } + public Guid TokenId { get; set; } + public int UserId { get; set; } + public string UserName { get; set; } + public string FirstName { get; set; } + public string LastName { get; set; } + public string Email { get; set; } + public string ProfilePictureUrl { get; set; } + public string SecurityStamp { get; set; } + public string LockStamp { get; set; } + public DateTime CreatedAt { get; set; } + public double ExpiresIn { get; set; } + public Dictionary Claims { get; set; } } } diff --git a/src/Tuitio.Domain/Models/TokenCore.cs b/src/Tuitio.Domain/Models/TokenCore.cs deleted file mode 100644 index f8259c3..0000000 --- a/src/Tuitio.Domain/Models/TokenCore.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) 2020 Tudor Stanciu - -using System.Collections.Generic; - -namespace Tuitio.Domain.Models -{ - public class TokenCore - { - public int UserId { get; set; } - public string UserName { get; set; } - public string FirstName { get; set; } - public string LastName { get; set; } - public string Email { get; set; } - public string ProfilePictureUrl { get; set; } - public string SecurityStamp { get; set; } - public string LockStamp { get; set; } - public Dictionary Claims { get; set; } - } -} diff --git a/src/Tuitio.Domain/Repositories/IIdentityRepository.cs b/src/Tuitio.Domain/Repositories/IUserRepository.cs similarity index 75% rename from src/Tuitio.Domain/Repositories/IIdentityRepository.cs rename to src/Tuitio.Domain/Repositories/IUserRepository.cs index c20b145..4bc41f1 100644 --- a/src/Tuitio.Domain/Repositories/IIdentityRepository.cs +++ b/src/Tuitio.Domain/Repositories/IUserRepository.cs @@ -3,13 +3,15 @@ using Tuitio.Domain.Entities; using Tuitio.Domain.Models; using System.Threading.Tasks; +using System; namespace Tuitio.Domain.Repositories { - public interface IIdentityRepository + public interface IUserRepository { Task GetUser(string userName, string password); - Task UpdateUserAfterAuthentication(AppUser user, Token token); + Task UpdateUserAfterAuthentication(AppUser user, Token token, string tokenRaw); Task GetActiveTokens(); + Task RemoveToken(Guid tokenId); } } diff --git a/src/Tuitio.PublishedLanguage/Constants/AuthenticationStatus.cs b/src/Tuitio.PublishedLanguage/Constants/AuthenticationStatus.cs index 2a57aed..2e0cb16 100644 --- a/src/Tuitio.PublishedLanguage/Constants/AuthenticationStatus.cs +++ b/src/Tuitio.PublishedLanguage/Constants/AuthenticationStatus.cs @@ -2,10 +2,10 @@ namespace Tuitio.PublishedLanguage.Constants { - public struct AuthenticationStatus + public struct EnvelopeStatus { public const string - SUCCESS = "SUCCESS", - BAD_CREDENTIALS = "BAD_CREDENTIALS"; + BAD_CREDENTIALS = "BAD_CREDENTIALS", + UNAUTHORIZED = "UNAUTHORIZED"; } } diff --git a/src/Tuitio.PublishedLanguage/Dto/Envelope.cs b/src/Tuitio.PublishedLanguage/Dto/Envelope.cs new file mode 100644 index 0000000..2738288 --- /dev/null +++ b/src/Tuitio.PublishedLanguage/Dto/Envelope.cs @@ -0,0 +1,26 @@ +// Copyright (c) 2020 Tudor Stanciu + +namespace Tuitio.PublishedLanguage.Dto +{ + public class Envelope where T : class + { + public T Result { get; } + public string Status { get; } + + public Envelope(T result, string status) + { + Result=result; + Status=status; + } + + public static Envelope Success(T result) + { + return new Envelope(result, null); + } + + public static Envelope Error(string message) + { + return new Envelope(null, message); + } + } +} diff --git a/src/Tuitio.PublishedLanguage/Dto/ResultRecords.cs b/src/Tuitio.PublishedLanguage/Dto/ResultRecords.cs new file mode 100644 index 0000000..de1e2b4 --- /dev/null +++ b/src/Tuitio.PublishedLanguage/Dto/ResultRecords.cs @@ -0,0 +1,10 @@ +// Copyright (c) 2020 Tudor Stanciu + +using System; + +namespace Tuitio.PublishedLanguage.Dto +{ + public record AccountLoginResult(Token Token); + public record AccountLogoutResult(int UserId, string UserName, DateTime LogoutDate); + public record TokenAuthorizationResult(TokenCore TokenCore); +} diff --git a/src/Tuitio.PublishedLanguage/Dto/Token.cs b/src/Tuitio.PublishedLanguage/Dto/Token.cs index af4d362..22a773b 100644 --- a/src/Tuitio.PublishedLanguage/Dto/Token.cs +++ b/src/Tuitio.PublishedLanguage/Dto/Token.cs @@ -1,13 +1,7 @@ // Copyright (c) 2020 Tudor Stanciu -using System; - namespace Tuitio.PublishedLanguage.Dto { - public class Token - { - public string Raw { get; set; } - public DateTime ValidFrom { get; set; } - public DateTime ValidUntil { get; set; } - } -} + // move this in result + public record Token(string Raw, double ExpiresIn); +} \ No newline at end of file diff --git a/src/Tuitio.PublishedLanguage/Events/AuthenticateUserResult.cs b/src/Tuitio.PublishedLanguage/Events/AuthenticateUserResult.cs deleted file mode 100644 index f73cb3c..0000000 --- a/src/Tuitio.PublishedLanguage/Events/AuthenticateUserResult.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) 2020 Tudor Stanciu - -using Tuitio.PublishedLanguage.Dto; - -namespace Tuitio.PublishedLanguage.Events -{ - public class AuthenticateUserResult - { - public Token Token { get; set; } - public string Status { get; set; } - } -} \ No newline at end of file diff --git a/src/Tuitio.Wrapper/Constants/ApiRoutes.cs b/src/Tuitio.Wrapper/Constants/ApiRoutes.cs index aa40734..c04f345 100644 --- a/src/Tuitio.Wrapper/Constants/ApiRoutes.cs +++ b/src/Tuitio.Wrapper/Constants/ApiRoutes.cs @@ -5,7 +5,7 @@ namespace Tuitio.Wrapper.Constants internal struct ApiRoutes { public const string - Authentication = "identity/authenticate?UserName={0}&Password={1}", - Authorization = "identity/authorize?Token={0}"; + Authentication = "account/login?UserName={0}&Password={1}", + Authorization = "connect/authorize?Token={0}"; } } diff --git a/src/Tuitio.Wrapper/DependencyInjectionExtension.cs b/src/Tuitio.Wrapper/DependencyInjectionExtension.cs index 49c7757..af56c0f 100644 --- a/src/Tuitio.Wrapper/DependencyInjectionExtension.cs +++ b/src/Tuitio.Wrapper/DependencyInjectionExtension.cs @@ -11,7 +11,7 @@ namespace Tuitio.Wrapper public static void UseTuitioServices(this IServiceCollection services, string baseAddress) { services.AddSingleton(new ServiceConfiguration(baseAddress)); - services.AddHttpClient(); + services.AddHttpClient(); } } } diff --git a/src/Tuitio.Wrapper/README.md b/src/Tuitio.Wrapper/README.md index e373a2c..6177855 100644 --- a/src/Tuitio.Wrapper/README.md +++ b/src/Tuitio.Wrapper/README.md @@ -2,9 +2,9 @@ [Tuitio](https://lab.code-rove.com/gitea/tudor.stanciu/tuitio) is a simple identity server implementation focused strictly on the needs of my home lab. -***Tuitio.Wrapper*** is a NuGet package that facilitates integration with a Tuitio instance in a .NET environment by registering a service called IIdentityService in the application's service collection. -It contains two methods, "Authenticate" and "Authorize", which are responsible for calling the appropriate methods from the API controller, ```/identity/authenticate``` or ```/identity/authorize```. These methods provide a simple and convenient way for developers to handle authentication and authorization when communicating with Tuitio's API. -Once the package is installed, all the developer has to do is call the ```UseTuitioServices``` method at application startup. After this step, IIdentityService can be injected into any class in the application. +***Tuitio.Wrapper*** is a NuGet package that facilitates integration with a Tuitio instance in a .NET environment by registering a service called ITuitioService in the application's service collection. +It contains three methods, "Login", "Logout" and "Authorize", which are responsible for calling the appropriate methods from the API controller, ```/account/login```, ```/account/logout``` or ```/connect/authorize```. These methods provide a simple and convenient way for developers to handle authentication and authorization when communicating with Tuitio's API. +Once the package is installed, all the developer has to do is call the ```UseTuitioServices``` method at application startup. After this step, ITuitioService can be injected into any class in the application. ## Package repository diff --git a/src/Tuitio.Wrapper/Services/IIdentityService.cs b/src/Tuitio.Wrapper/Services/ITuitioService.cs similarity index 69% rename from src/Tuitio.Wrapper/Services/IIdentityService.cs rename to src/Tuitio.Wrapper/Services/ITuitioService.cs index 241dac9..6b5dd6d 100644 --- a/src/Tuitio.Wrapper/Services/IIdentityService.cs +++ b/src/Tuitio.Wrapper/Services/ITuitioService.cs @@ -5,9 +5,9 @@ using System.Threading.Tasks; namespace Tuitio.Wrapper.Services { - public interface IIdentityService + public interface ITuitioService { Task Authenticate(string userName, string password); - Task Authorize(string token); + Task Authorize(string token); } } diff --git a/src/Tuitio.Wrapper/Services/IdentityService.cs b/src/Tuitio.Wrapper/Services/TuitioService.cs similarity index 82% rename from src/Tuitio.Wrapper/Services/IdentityService.cs rename to src/Tuitio.Wrapper/Services/TuitioService.cs index 2d0085f..2999095 100644 --- a/src/Tuitio.Wrapper/Services/IdentityService.cs +++ b/src/Tuitio.Wrapper/Services/TuitioService.cs @@ -11,12 +11,12 @@ using System.Threading.Tasks; namespace Tuitio.Wrapper.Services { - internal class IdentityService : IIdentityService + internal class TuitioService : ITuitioService { private const string _contentType = "application/json"; private readonly HttpClient _httpClient; - public IdentityService(HttpClient httpClient, ServiceConfiguration configuration) + public TuitioService(HttpClient httpClient, ServiceConfiguration configuration) { httpClient.BaseAddress = new Uri(configuration.BaseAddress); httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(_contentType)); @@ -31,10 +31,10 @@ namespace Tuitio.Wrapper.Services return result; } - public async Task Authorize(string token) + public async Task Authorize(string token) { var route = string.Format(ApiRoutes.Authorization, token); - var result = await _httpClient.ExecutePostRequest(route); + var result = await _httpClient.ExecutePostRequest(route); return result; } } diff --git a/src/Tuitio.Wrapper/Tuitio.Wrapper.csproj b/src/Tuitio.Wrapper/Tuitio.Wrapper.csproj index 1b2ea4f..bde115e 100644 --- a/src/Tuitio.Wrapper/Tuitio.Wrapper.csproj +++ b/src/Tuitio.Wrapper/Tuitio.Wrapper.csproj @@ -2,7 +2,7 @@ net6.0 - Tuitio.Wrapper facilitates integration with a Tuitio instance in a .NET environment by registering a service called IIdentityService in the application’s service collection. It contains two methods that provide a simple and convenient way for developers to handle authentication and authorization when communicating with Tuitio’s API. + Tuitio.Wrapper facilitates integration with a Tuitio instance in a .NET environment by registering a service called ITuitioService in the application’s service collection. It contains three methods that provide a simple and convenient way for developers to handle authentication and authorization when communicating with Tuitio’s API. https://lab.code-rove.com/gitea/tudor.stanciu/tuitio https://lab.code-rove.com/gitea/tudor.stanciu/tuitio Git diff --git a/src/Tuitio/Controllers/AccountController.cs b/src/Tuitio/Controllers/AccountController.cs new file mode 100644 index 0000000..4c18f06 --- /dev/null +++ b/src/Tuitio/Controllers/AccountController.cs @@ -0,0 +1,38 @@ +using MediatR; +using Microsoft.AspNetCore.Mvc; +using System.Threading.Tasks; +using Tuitio.Application.CommandHandlers; + +namespace Tuitio.Controllers +{ + [ApiController] + [Route("account")] + public class AccountController : ControllerBase + { + private readonly IMediator _mediator; + + public AccountController(IMediator mediator) + { + _mediator = mediator; + } + + [HttpPost("login")] + public async Task Login([FromQuery] string userName, string password) + { + var command = new AccountLoginHandler.Command(userName, password); + var result = await _mediator.Send(command); + return Ok(result); + } + + [HttpPost("logout")] + public async Task Logout([FromQuery] string token) + { + var result = await _mediator.Send(token); + + if (result != null) + return Ok(result); + else + return BadRequest(); + } + } +} diff --git a/src/Tuitio/Controllers/ConnectController.cs b/src/Tuitio/Controllers/ConnectController.cs new file mode 100644 index 0000000..e7bd156 --- /dev/null +++ b/src/Tuitio/Controllers/ConnectController.cs @@ -0,0 +1,29 @@ +// Copyright (c) 2020 Tudor Stanciu + +using MediatR; +using Microsoft.AspNetCore.Mvc; +using System.Threading.Tasks; +using Tuitio.Application.CommandHandlers; + +namespace Tuitio.Api.Controllers +{ + [ApiController] + [Route("connect")] + public class ConnectController : ControllerBase + { + private readonly IMediator _mediator; + + public ConnectController(IMediator mediator) + { + _mediator = mediator; + } + + [HttpPost("authorize")] + public async Task AuthorizeToken([FromQuery] string token) + { + var command = new AuthorizeTokenHandler.Command(token); + var result = await _mediator.Send(command); + return Ok(result); + } + } +} diff --git a/src/Tuitio/Controllers/IdentityController.cs b/src/Tuitio/Controllers/IdentityController.cs deleted file mode 100644 index b3f4d2d..0000000 --- a/src/Tuitio/Controllers/IdentityController.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) 2020 Tudor Stanciu - -using Tuitio.Application.Commands; -using MediatR; -using Microsoft.AspNetCore.Mvc; -using System.Threading.Tasks; - -namespace Tuitio.Api.Controllers -{ - [ApiController] - [Route("identity")] - public class IdentityController : ControllerBase - { - private readonly IMediator _mediator; - - public IdentityController(IMediator mediator) - { - _mediator = mediator; - } - - [HttpPost("authenticate")] - public async Task AuthenticateUser([FromQuery] AuthenticateUser authenticateUser) - { - var result = await _mediator.Send(authenticateUser); - - if (result != null) - return Ok(result); - else - return BadRequest(); - } - - [HttpPost("authorize")] - public async Task AuthorizeToken([FromQuery] AuthorizeToken authorizeToken) - { - var result = await _mediator.Send(authorizeToken); - - if (result != null) - return Ok(result); - else - return BadRequest(); - } - } -} diff --git a/src/Tuitio/Extensions/StartupExtensions.cs b/src/Tuitio/Extensions/StartupExtensions.cs index 439d339..9d14d30 100644 --- a/src/Tuitio/Extensions/StartupExtensions.cs +++ b/src/Tuitio/Extensions/StartupExtensions.cs @@ -22,7 +22,7 @@ namespace Tuitio.Extensions services.AddControllers(); // MediatR - services.AddMediatR(typeof(Application.Commands.AuthenticateUser).Assembly); + services.AddMediatR(typeof(Application.CommandHandlers.AccountLoginHandler).Assembly); services.AddScoped(typeof(IPipelineBehavior<,>), typeof(RequestPreProcessorBehavior<,>)); services.AddScoped(typeof(IPipelineBehavior<,>), typeof(RequestPostProcessorBehavior<,>));