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..c0c61ed 100644
--- a/ReleaseNotes.xml
+++ b/ReleaseNotes.xml
@@ -8,16 +8,16 @@
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
1.0.1
- ◾ Hard changes in token structure. Now the token format is base64 and contains a json with all user data like username, first name, last name, profile picture url, email address and a list of claims that can be configured from the database for each user independently.
+ ◾ Big changes in token structure. Now the token format is base64 and contains a json with all user data like username, first name, last name, profile picture url, email address and a list of claims that can be configured from the database for each user independently.
◾ The generation and validation mechanism for the token has been rewritten to meet the new token structure.
◾ The complexity of user information has grown a lot. All users have now besides the data from token other information such as statuses, failed login attempts, last login date, password change date and security stamp.
◾ All tokens are persisted in the database and the active ones are reload at a server failure or in case of a restart.
@@ -60,4 +60,12 @@
◾ Added README.md file
+
+ 2.1.0
+
+ ◾ Tuitio refactoring
+ ◾ Added account logout method
+ ◾ Tuitio performance optimizations
+
+
\ No newline at end of file
diff --git a/src/Tuitio.Application/CommandHandlers/AccountLoginHandler.cs b/src/Tuitio.Application/CommandHandlers/AccountLoginHandler.cs
new file mode 100644
index 0000000..4d6c9df
--- /dev/null
+++ b/src/Tuitio.Application/CommandHandlers/AccountLoginHandler.cs
@@ -0,0 +1,44 @@
+// Copyright (c) 2020 Tudor Stanciu
+
+using MediatR;
+using Microsoft.Extensions.Logging;
+using System.Threading;
+using System.Threading.Tasks;
+using Tuitio.Application.Services.Abstractions;
+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($"Login failed for user '{command.UserName}'.");
+ return Envelope.Fail(EnvelopeError.BAD_CREDENTIALS);
+ }
+
+ _logger.LogDebug($"Login succeeded for user '{command.UserName}'.");
+
+ var result = new AccountLoginResult(loginResult.Raw, loginResult.Token.ExpiresIn);
+ 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..f07b672
--- /dev/null
+++ b/src/Tuitio.Application/CommandHandlers/AccountLogoutHandler.cs
@@ -0,0 +1,47 @@
+// Copyright (c) 2020 Tudor Stanciu
+
+using AutoMapper;
+using MediatR;
+using Microsoft.Extensions.Logging;
+using System.Threading;
+using System.Threading.Tasks;
+using Tuitio.Application.Services.Abstractions;
+using Tuitio.PublishedLanguage.Constants;
+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);
+ if (logoutResult == null)
+ {
+ _logger.LogDebug($"Logout failed for token '{command.Token}'.");
+ return Envelope.Fail(EnvelopeError.UNAUTHENTICATED);
+ }
+
+ _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/AuthorizationHandler.cs b/src/Tuitio.Application/CommandHandlers/AuthorizationHandler.cs
new file mode 100644
index 0000000..cdb4999
--- /dev/null
+++ b/src/Tuitio.Application/CommandHandlers/AuthorizationHandler.cs
@@ -0,0 +1,48 @@
+// Copyright (c) 2020 Tudor Stanciu
+
+using AutoMapper;
+using MediatR;
+using Microsoft.Extensions.Logging;
+using System.Threading;
+using System.Threading.Tasks;
+using Tuitio.Application.Services.Abstractions;
+using Tuitio.PublishedLanguage.Constants;
+using Tuitio.PublishedLanguage.Dto;
+
+namespace Tuitio.Application.CommandHandlers
+{
+ public class AuthorizationHandler
+ {
+ 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 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.Fail(EnvelopeError.UNAUTHORIZED);
+ return Task.FromResult(result);
+ }
+
+ _logger.LogDebug($"Authorization succeeded for token '{command.Token}'.");
+ var authorizationResult = _mapper.Map(token);
+ var envelope = Envelope.Success(authorizationResult);
+ return Task.FromResult(envelope);
+ }
+ }
+ }
+}
diff --git a/src/Tuitio.Application/CommandHandlers/AuthorizeTokenHandler.cs b/src/Tuitio.Application/CommandHandlers/AuthorizeTokenHandler.cs
deleted file mode 100644
index 986307f..0000000
--- a/src/Tuitio.Application/CommandHandlers/AuthorizeTokenHandler.cs
+++ /dev/null
@@ -1,42 +0,0 @@
-// 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;
-
-namespace Tuitio.Application.CommandHandlers
-{
- public class AuthorizeTokenHandler : IRequestHandler
- {
- private readonly IUserService _userService;
- private readonly IMapper _mapper;
- private readonly ILogger _logger;
-
- public AuthorizeTokenHandler(IUserService userService, IMapper mapper, ILogger logger)
- {
- _userService = userService;
- _mapper = mapper;
- _logger = logger;
- }
-
- public Task Handle(AuthorizeToken command, CancellationToken cancellationToken)
- {
- var tokenCore = _userService.Authorize(command.Token);
- if (tokenCore == null)
- {
- _logger.LogDebug($"Authorization failed for token '{command.Token}'.");
- return null;
- }
-
- _logger.LogDebug($"Authorization succeeded for token '{command.Token}'.");
- var tokenCoreResult = _mapper.Map(tokenCore);
-
- return Task.FromResult(tokenCoreResult);
- }
- }
-}
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/DependencyInjectionExtensions.cs b/src/Tuitio.Application/DependencyInjectionExtensions.cs
index 7ce0a8b..ea164f2 100644
--- a/src/Tuitio.Application/DependencyInjectionExtensions.cs
+++ b/src/Tuitio.Application/DependencyInjectionExtensions.cs
@@ -1,10 +1,10 @@
// Copyright (c) 2020 Tudor Stanciu
+using Microsoft.Extensions.DependencyInjection;
using Tuitio.Application.Services;
using Tuitio.Application.Services.Abstractions;
using Tuitio.Application.Stores;
using Tuitio.Domain.Abstractions;
-using Microsoft.Extensions.DependencyInjection;
namespace Tuitio.Application
{
diff --git a/src/Tuitio.Application/Mappings/MappingProfile.cs b/src/Tuitio.Application/Mappings/MappingProfile.cs
index 21b6efd..d1dfbf0 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/ITokenService.cs b/src/Tuitio.Application/Services/Abstractions/ITokenService.cs
similarity index 57%
rename from src/Tuitio.Application/Services/ITokenService.cs
rename to src/Tuitio.Application/Services/Abstractions/ITokenService.cs
index 6eec150..c9c775d 100644
--- a/src/Tuitio.Application/Services/ITokenService.cs
+++ b/src/Tuitio.Application/Services/Abstractions/ITokenService.cs
@@ -3,11 +3,12 @@
using Tuitio.Domain.Entities;
using Tuitio.Domain.Models;
-namespace Tuitio.Application.Services
+namespace Tuitio.Application.Services.Abstractions
{
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/Abstractions/IUserService.cs b/src/Tuitio.Application/Services/Abstractions/IUserService.cs
new file mode 100644
index 0000000..88caf1d
--- /dev/null
+++ b/src/Tuitio.Application/Services/Abstractions/IUserService.cs
@@ -0,0 +1,15 @@
+// Copyright (c) 2020 Tudor Stanciu
+
+using System.Threading.Tasks;
+using Tuitio.Domain.Models;
+using Tuitio.Domain.Models.Account;
+
+namespace Tuitio.Application.Services.Abstractions
+{
+ public interface IUserService
+ {
+ 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/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/IUserService.cs b/src/Tuitio.Application/Services/IUserService.cs
deleted file mode 100644
index 7b12c69..0000000
--- a/src/Tuitio.Application/Services/IUserService.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-// Copyright (c) 2020 Tudor Stanciu
-
-using Tuitio.Domain.Models;
-using System.Threading.Tasks;
-
-namespace Tuitio.Application.Services
-{
- public interface IUserService
- {
- Task Authenticate(string userName, string password);
- TokenCore Authorize(string token);
- }
-}
\ No newline at end of file
diff --git a/src/Tuitio.Application/Services/TokenService.cs b/src/Tuitio.Application/Services/TokenService.cs
index 0353639..2897ef8 100644
--- a/src/Tuitio.Application/Services/TokenService.cs
+++ b/src/Tuitio.Application/Services/TokenService.cs
@@ -1,13 +1,15 @@
// 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.Services.Abstractions;
+using Tuitio.Application.Stores;
+using Tuitio.Domain.Abstractions;
+using Tuitio.Domain.Entities;
+using Tuitio.Domain.Models;
namespace Tuitio.Application.Services
{
@@ -15,47 +17,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..df57c5d 100644
--- a/src/Tuitio.Application/Services/UserService.cs
+++ b/src/Tuitio.Application/Services/UserService.cs
@@ -1,61 +1,78 @@
// Copyright (c) 2020 Tudor Stanciu
+using System;
+using System.Threading.Tasks;
using Tuitio.Application.Services.Abstractions;
using Tuitio.Application.Stores;
using Tuitio.Domain.Abstractions;
using Tuitio.Domain.Entities;
using Tuitio.Domain.Models;
+using Tuitio.Domain.Models.Account;
using Tuitio.Domain.Repositories;
-using System;
-using System.Threading.Tasks;
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);
+ if (user == null)
+ return null;
+
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.UpdateUserAfterLogin(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/IdentityRepository.cs
deleted file mode 100644
index 77ec773..0000000
--- a/src/Tuitio.Domain.Data/Repositories/IdentityRepository.cs
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright (c) 2020 Tudor Stanciu
-
-using Tuitio.Domain.Data.DbContexts;
-using Tuitio.Domain.Entities;
-using Tuitio.Domain.Models;
-using Tuitio.Domain.Repositories;
-using Microsoft.EntityFrameworkCore;
-using System;
-using System.Linq;
-using System.Threading.Tasks;
-
-namespace Tuitio.Domain.Data.Repositories
-{
- class IdentityRepository : IIdentityRepository
- {
- private readonly IdentityDbContext _dbContext;
-
- public IdentityRepository(IdentityDbContext dbContext)
- {
- _dbContext = dbContext;
- }
-
- public Task GetUser(string userName, string password)
- {
- return _dbContext.Users
- .Include(z => z.Status)
- .Include(z => z.Claims)
- .FirstOrDefaultAsync(z => z.UserName == userName && z.Password == password);
- }
-
- public async Task UpdateUserAfterAuthentication(AppUser user, Token token)
- {
- var userToken = new UserToken()
- {
- UserId = user.UserId,
- Token = token.Raw,
- ValidFrom = token.ValidFrom,
- ValidUntil = token.ValidUntil
- };
- await _dbContext.AddAsync(userToken);
-
- user.LastLoginDate = DateTime.Now;
- _dbContext.Update(user);
-
- await _dbContext.SaveChangesAsync();
- }
-
- public Task GetActiveTokens()
- {
- var currentDate = DateTime.Now;
- var query = _dbContext.UserTokens
- .Where(z => z.ValidFrom <= currentDate && z.ValidUntil >= currentDate && (!z.Burnt.HasValue || z.Burnt.Value == false));
- return query.ToArrayAsync();
- }
- }
-}
diff --git a/src/Tuitio.Domain.Data/Repositories/UserRepository.cs b/src/Tuitio.Domain.Data/Repositories/UserRepository.cs
new file mode 100644
index 0000000..e11b6f6
--- /dev/null
+++ b/src/Tuitio.Domain.Data/Repositories/UserRepository.cs
@@ -0,0 +1,77 @@
+// Copyright (c) 2020 Tudor Stanciu
+
+using Microsoft.EntityFrameworkCore;
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using Tuitio.Domain.Data.DbContexts;
+using Tuitio.Domain.Entities;
+using Tuitio.Domain.Models;
+using Tuitio.Domain.Repositories;
+
+namespace Tuitio.Domain.Data.Repositories
+{
+ class UserRepository : IUserRepository
+ {
+ private readonly TuitioDbContext _dbContext;
+
+ public UserRepository(TuitioDbContext dbContext)
+ {
+ _dbContext = dbContext;
+ }
+
+ public Task GetUser(string userName, string password)
+ {
+ return _dbContext.Users
+ .Include(z => z.Status)
+ .Include(z => z.Claims)
+ .FirstOrDefaultAsync(z => z.UserName == userName && z.Password == password);
+ }
+
+ public async Task UpdateUserAfterLogin(AppUser user, Token token, string tokenRaw)
+ {
+ var userToken = new UserToken()
+ {
+ TokenId = token.TokenId,
+ UserId = token.UserId,
+ Token = tokenRaw,
+ ValidFrom = token.CreatedAt,
+ ValidUntil = token.CreatedAt.AddMilliseconds(token.ExpiresIn)
+ };
+
+ await _dbContext.AddAsync(userToken);
+ user.LastLoginDate = DateTime.UtcNow;
+
+ await _dbContext.SaveChangesAsync();
+ }
+
+ public async Task GetActiveTokens()
+ {
+ var currentDate = DateTime.UtcNow;
+
+ // remove expired tokens
+ _dbContext.UserTokens.RemoveRange(_dbContext.UserTokens.Where(z => z.ValidUntil < currentDate));
+ await _dbContext.SaveChangesAsync();
+
+ // retrieve active tokens
+ var query = _dbContext.UserTokens
+ .Where(z => z.ValidFrom <= currentDate && z.ValidUntil >= currentDate);
+
+ var tokens = await query.ToArrayAsync();
+ return tokens;
+ }
+
+ 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..731661c
--- /dev/null
+++ b/src/Tuitio.Domain/Models/Account/Records.cs
@@ -0,0 +1,9 @@
+// Copyright (c) 2020 Tudor Stanciu
+
+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 63%
rename from src/Tuitio.Domain/Repositories/IIdentityRepository.cs
rename to src/Tuitio.Domain/Repositories/IUserRepository.cs
index c20b145..158f629 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 UpdateUserAfterLogin(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
deleted file mode 100644
index 2a57aed..0000000
--- a/src/Tuitio.PublishedLanguage/Constants/AuthenticationStatus.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-// Copyright (c) 2020 Tudor Stanciu
-
-namespace Tuitio.PublishedLanguage.Constants
-{
- public struct AuthenticationStatus
- {
- public const string
- SUCCESS = "SUCCESS",
- BAD_CREDENTIALS = "BAD_CREDENTIALS";
- }
-}
diff --git a/src/Tuitio.PublishedLanguage/Constants/EnvelopeError.cs b/src/Tuitio.PublishedLanguage/Constants/EnvelopeError.cs
new file mode 100644
index 0000000..40e7444
--- /dev/null
+++ b/src/Tuitio.PublishedLanguage/Constants/EnvelopeError.cs
@@ -0,0 +1,12 @@
+// Copyright (c) 2020 Tudor Stanciu
+
+namespace Tuitio.PublishedLanguage.Constants
+{
+ public struct EnvelopeError
+ {
+ public const string
+ BAD_CREDENTIALS = "BAD_CREDENTIALS",
+ UNAUTHENTICATED = "UNAUTHENTICATED",
+ UNAUTHORIZED = "UNAUTHORIZED";
+ }
+}
diff --git a/src/Tuitio.PublishedLanguage/Dto/Envelope.cs b/src/Tuitio.PublishedLanguage/Dto/Envelope.cs
new file mode 100644
index 0000000..18ba4dd
--- /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 Error { get; }
+
+ public Envelope(T result, string error)
+ {
+ Result=result;
+ Error=error;
+ }
+
+ public static Envelope Success(T result)
+ {
+ return new Envelope(result, null);
+ }
+
+ public static Envelope Fail(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..21bcbf2
--- /dev/null
+++ b/src/Tuitio.PublishedLanguage/Dto/ResultRecords.cs
@@ -0,0 +1,25 @@
+// Copyright (c) 2020 Tudor Stanciu
+
+using System;
+using System.Collections.Generic;
+
+namespace Tuitio.PublishedLanguage.Dto
+{
+ public record AccountLoginResult(string Token, double ExpiresIn);
+ public record AccountLogoutResult(int UserId, string UserName, DateTime LogoutDate);
+ public class AuthorizationResult
+ {
+ public Guid TokenId { get; init; }
+ public int UserId { get; init; }
+ public string UserName { get; init; }
+ public string FirstName { get; init; }
+ public string LastName { get; init; }
+ public string Email { get; init; }
+ public string ProfilePictureUrl { get; init; }
+ public string SecurityStamp { get; init; }
+ public string LockStamp { get; init; }
+ public DateTime CreatedAt { get; init; }
+ public double ExpiresIn { get; init; }
+ public Dictionary Claims { get; init; }
+ }
+}
diff --git a/src/Tuitio.PublishedLanguage/Dto/Token.cs b/src/Tuitio.PublishedLanguage/Dto/Token.cs
deleted file mode 100644
index af4d362..0000000
--- a/src/Tuitio.PublishedLanguage/Dto/Token.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-// 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; }
- }
-}
diff --git a/src/Tuitio.PublishedLanguage/Dto/TokenCore.cs b/src/Tuitio.PublishedLanguage/Dto/TokenCore.cs
deleted file mode 100644
index ecd5db5..0000000
--- a/src/Tuitio.PublishedLanguage/Dto/TokenCore.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright (c) 2020 Tudor Stanciu
-
-using System.Collections.Generic;
-
-namespace Tuitio.PublishedLanguage.Dto
-{
- 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.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.PublishedLanguage/ReleaseNotes.txt b/src/Tuitio.PublishedLanguage/ReleaseNotes.txt
index efdeed3..7d36bc8 100644
--- a/src/Tuitio.PublishedLanguage/ReleaseNotes.txt
+++ b/src/Tuitio.PublishedLanguage/ReleaseNotes.txt
@@ -1,3 +1,7 @@
-2.0.0 release [2023-01-31 02:17]
+2.1.0 release [2023-03-07 22:17]
+◾ Tuitio refactoring
+◾ Added account logout method
+
+2.0.0 release [2023-01-31 02:17]
◾ Tuitio rebranding
◾ Initial release of Tuitio's published language package
\ No newline at end of file
diff --git a/src/Tuitio.PublishedLanguage/Tuitio.PublishedLanguage.csproj b/src/Tuitio.PublishedLanguage/Tuitio.PublishedLanguage.csproj
index 90b1888..ba0a4e4 100644
--- a/src/Tuitio.PublishedLanguage/Tuitio.PublishedLanguage.csproj
+++ b/src/Tuitio.PublishedLanguage/Tuitio.PublishedLanguage.csproj
@@ -7,7 +7,7 @@
https://lab.code-rove.com/gitea/tudor.stanciu/tuitio
Git
$([System.IO.File]::ReadAllText("$(MSBuildProjectDirectory)/ReleaseNotes.txt"))
- 2.0.0
+ 2.1.0
logo.png
README.md
Toodle HomeLab
diff --git a/src/Tuitio.Wrapper/Constants/ApiRoutes.cs b/src/Tuitio.Wrapper/Constants/ApiRoutes.cs
index aa40734..5501cb0 100644
--- a/src/Tuitio.Wrapper/Constants/ApiRoutes.cs
+++ b/src/Tuitio.Wrapper/Constants/ApiRoutes.cs
@@ -5,7 +5,8 @@ namespace Tuitio.Wrapper.Constants
internal struct ApiRoutes
{
public const string
- Authentication = "identity/authenticate?UserName={0}&Password={1}",
- Authorization = "identity/authorize?Token={0}";
+ AccountLogin = "account/login?UserName={0}&Password={1}",
+ AccountLogout = "account/logout?Token={0}",
+ 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/ReleaseNotes.txt b/src/Tuitio.Wrapper/ReleaseNotes.txt
index c33c26d..899baa6 100644
--- a/src/Tuitio.Wrapper/ReleaseNotes.txt
+++ b/src/Tuitio.Wrapper/ReleaseNotes.txt
@@ -1,3 +1,7 @@
-2.0.0 release [2023-01-31 02:17]
+2.1.0 release [2023-03-07 22:17]
+◾ Tuitio refactoring
+◾ Added account logout method
+
+2.0.0 release [2023-01-31 02:17]
◾ Tuitio rebranding
◾ Initial release of Tuitio's API wrapper
\ No newline at end of file
diff --git a/src/Tuitio.Wrapper/Services/IIdentityService.cs b/src/Tuitio.Wrapper/Services/IIdentityService.cs
deleted file mode 100644
index 241dac9..0000000
--- a/src/Tuitio.Wrapper/Services/IIdentityService.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-// Copyright (c) 2020 Tudor Stanciu
-
-using Tuitio.PublishedLanguage.Dto;
-using System.Threading.Tasks;
-
-namespace Tuitio.Wrapper.Services
-{
- public interface IIdentityService
- {
- Task Authenticate(string userName, string password);
- Task Authorize(string token);
- }
-}
diff --git a/src/Tuitio.Wrapper/Services/ITuitioService.cs b/src/Tuitio.Wrapper/Services/ITuitioService.cs
new file mode 100644
index 0000000..89fcb0f
--- /dev/null
+++ b/src/Tuitio.Wrapper/Services/ITuitioService.cs
@@ -0,0 +1,14 @@
+// Copyright (c) 2020 Tudor Stanciu
+
+using System.Threading.Tasks;
+using Tuitio.PublishedLanguage.Dto;
+
+namespace Tuitio.Wrapper.Services
+{
+ public interface ITuitioService
+ {
+ Task> Login(string userName, string password);
+ Task> Logout(string token);
+ Task> Authorize(string token);
+ }
+}
\ No newline at end of file
diff --git a/src/Tuitio.Wrapper/Services/IdentityService.cs b/src/Tuitio.Wrapper/Services/IdentityService.cs
deleted file mode 100644
index 2d0085f..0000000
--- a/src/Tuitio.Wrapper/Services/IdentityService.cs
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright (c) 2020 Tudor Stanciu
-
-using Tuitio.PublishedLanguage.Dto;
-using Tuitio.Wrapper.Constants;
-using Tuitio.Wrapper.Models;
-using Netmash.Extensions.Http;
-using System;
-using System.Net.Http;
-using System.Net.Http.Headers;
-using System.Threading.Tasks;
-
-namespace Tuitio.Wrapper.Services
-{
- internal class IdentityService : IIdentityService
- {
- private const string _contentType = "application/json";
- private readonly HttpClient _httpClient;
-
- public IdentityService(HttpClient httpClient, ServiceConfiguration configuration)
- {
- httpClient.BaseAddress = new Uri(configuration.BaseAddress);
- httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(_contentType));
-
- _httpClient = httpClient;
- }
-
- public async Task Authenticate(string userName, string password)
- {
- var route = string.Format(ApiRoutes.Authentication, userName, password);
- var result = await _httpClient.ExecutePostRequest(route);
- return result;
- }
-
- public async Task Authorize(string token)
- {
- var route = string.Format(ApiRoutes.Authorization, token);
- var result = await _httpClient.ExecutePostRequest(route);
- return result;
- }
- }
-}
diff --git a/src/Tuitio.Wrapper/Services/TuitioService.cs b/src/Tuitio.Wrapper/Services/TuitioService.cs
new file mode 100644
index 0000000..93d4248
--- /dev/null
+++ b/src/Tuitio.Wrapper/Services/TuitioService.cs
@@ -0,0 +1,48 @@
+// Copyright (c) 2020 Tudor Stanciu
+
+using Netmash.Extensions.Http;
+using System;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Threading.Tasks;
+using Tuitio.PublishedLanguage.Dto;
+using Tuitio.Wrapper.Constants;
+using Tuitio.Wrapper.Models;
+
+namespace Tuitio.Wrapper.Services
+{
+ internal class TuitioService : ITuitioService
+ {
+ private const string _contentType = "application/json";
+ private readonly HttpClient _httpClient;
+
+ public TuitioService(HttpClient httpClient, ServiceConfiguration configuration)
+ {
+ httpClient.BaseAddress = new Uri(configuration.BaseAddress);
+ httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(_contentType));
+
+ _httpClient = httpClient;
+ }
+
+ public async Task> Login(string userName, string password)
+ {
+ var route = string.Format(ApiRoutes.AccountLogin, userName, password);
+ var result = await _httpClient.ExecutePostRequest>(route);
+ return result;
+ }
+
+ public async Task> Logout(string token)
+ {
+ var route = string.Format(ApiRoutes.AccountLogout, token);
+ var result = await _httpClient.ExecutePostRequest>(route);
+ return result;
+ }
+
+ public async Task> Authorize(string token)
+ {
+ var route = string.Format(ApiRoutes.Authorization, token);
+ 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..1c7fb82 100644
--- a/src/Tuitio.Wrapper/Tuitio.Wrapper.csproj
+++ b/src/Tuitio.Wrapper/Tuitio.Wrapper.csproj
@@ -2,12 +2,12 @@
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
$([System.IO.File]::ReadAllText("$(MSBuildProjectDirectory)/ReleaseNotes.txt"))
- 2.0.0
+ 2.1.0
logo.png
README.md
Toodle HomeLab
diff --git a/src/Tuitio/Controllers/AccountController.cs b/src/Tuitio/Controllers/AccountController.cs
new file mode 100644
index 0000000..f5e4cac
--- /dev/null
+++ b/src/Tuitio/Controllers/AccountController.cs
@@ -0,0 +1,37 @@
+// Copyright (c) 2020 Tudor Stanciu
+
+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 command = new AccountLogoutHandler.Command(token);
+ var result = await _mediator.Send(command);
+ return Ok(result);
+ }
+ }
+}
diff --git a/src/Tuitio/Controllers/ConnectController.cs b/src/Tuitio/Controllers/ConnectController.cs
new file mode 100644
index 0000000..e94b151
--- /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 AuthorizationHandler.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/Controllers/SystemController.cs b/src/Tuitio/Controllers/SystemController.cs
index 0016823..a90e5eb 100644
--- a/src/Tuitio/Controllers/SystemController.cs
+++ b/src/Tuitio/Controllers/SystemController.cs
@@ -11,11 +11,8 @@ namespace Tuitio.Api.Controllers
[Route("system")]
public class SystemController : ControllerBase
{
- private readonly IMediator _mediator;
-
public SystemController(IMediator mediator)
{
- _mediator = mediator;
}
[AllowAnonymous]
@@ -24,12 +21,5 @@ namespace Tuitio.Api.Controllers
{
return Ok($"Ping success. System datetime: {DateTime.Now}");
}
-
- /*
- Methods:
- /version
- /burn-token
- /burn-all-tokens
- */
}
}
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<,>));