Compare commits
No commits in common. "cd7fe934fd72702e59e1f875559d5cab79b7b1be" and "8b7e4b1e6489cd44728602abcde442d07dc505f3" have entirely different histories.
cd7fe934fd
...
8b7e4b1e64
|
@ -1,7 +1,7 @@
|
|||
<Project>
|
||||
<Import Project="dependencies.props" />
|
||||
<PropertyGroup>
|
||||
<Version>2.4.4</Version>
|
||||
<Version>2.4.3</Version>
|
||||
<Authors>Tudor Stanciu</Authors>
|
||||
<Company>STA</Company>
|
||||
<PackageTags>Tuitio</PackageTags>
|
||||
|
|
|
@ -19,16 +19,16 @@
|
|||
<Content>
|
||||
◾ 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.
|
||||
◾ 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.
|
||||
</Content>
|
||||
</Note>
|
||||
<Note>
|
||||
<Version>1.1.0</Version>
|
||||
<Content>
|
||||
◾ Upgrade all projects to .NET 5
|
||||
◾ Upgrade packages MicrosoftExtensions, AutoMapper, EntityFramework, Netmash
|
||||
</Content>
|
||||
<Version>1.1.0</Version>
|
||||
<Content>
|
||||
◾ Upgrade all projects to .NET 5
|
||||
◾ Upgrade packages MicrosoftExtensions, AutoMapper, EntityFramework, Netmash
|
||||
</Content>
|
||||
</Note>
|
||||
<Note>
|
||||
<Version>1.1.1</Version>
|
||||
|
@ -83,7 +83,7 @@
|
|||
◾ The "user-info" method returns the data of the authenticated user.
|
||||
◾ Added http context accessor and authentication handler
|
||||
◾ Added user contact options
|
||||
◾ Published new versions of Tuitio's nuget packages
|
||||
◾ Published new versions of Tuitio's nuget packages
|
||||
</Content>
|
||||
</Note>
|
||||
<Note>
|
||||
|
@ -109,9 +109,9 @@
|
|||
<Content>
|
||||
Added user roles and groups in authorization result
|
||||
◾ The authorization result will contain the user role and group codes. They are very useful for an application because after the token is authorized, the application can directly validate its internal permissions based on roles or groups, without calling another method to obtain this information.
|
||||
◾ In addition to these changes, some refactorings were also made.
|
||||
◾ The token "expires in" information measuring unit was changed from milliseconds to seconds.
|
||||
◾ New versions of nuget packages have been released.
|
||||
◾ In addition to these changes, some refactorings were also made.
|
||||
◾ The token "expires in" information measuring unit was changed from milliseconds to seconds.
|
||||
◾ New versions of nuget packages have been released.
|
||||
</Content>
|
||||
</Note>
|
||||
<Note>
|
||||
|
@ -120,16 +120,7 @@
|
|||
<Content>
|
||||
Added IDs for user roles and groups in authorization result
|
||||
◾ Based on the development from the previous version, the authorization result has been extended and will contain both IDs and codes for the groups and roles of the authenticated user.
|
||||
◾ New versions of nuget packages have been released.
|
||||
</Content>
|
||||
</Note>
|
||||
<Note>
|
||||
<Version>2.4.4</Version>
|
||||
<Date>2023-05-03 19:02</Date>
|
||||
<Content>
|
||||
Added better token persistence in the database
|
||||
◾ All tokens are persisted in the database after a login operation to recover them in case of a system crash or restart.
|
||||
◾ Expired tokens are still deleted periodically.
|
||||
◾ New versions of nuget packages have been released.
|
||||
</Content>
|
||||
</Note>
|
||||
</ReleaseNotes>
|
|
@ -36,7 +36,7 @@ namespace Tuitio.Application.CommandHandlers
|
|||
|
||||
_logger.LogDebug($"Login succeeded for user '{command.UserName}'.");
|
||||
|
||||
var result = new AccountLoginResult(loginResult.Raw, loginResult.TokenExtension.Token.ExpiresIn);
|
||||
var result = new AccountLoginResult(loginResult.Raw, loginResult.Token.ExpiresIn);
|
||||
return Envelope<AccountLoginResult>.Success(result);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,21 +11,7 @@ namespace Tuitio.Application.Mappings
|
|||
public MappingProfile()
|
||||
{
|
||||
CreateMap<models.RecordIdentifier, dto.RecordIdentifier>();
|
||||
|
||||
CreateMap<models.TokenExtension, dto.AuthorizationResult>()
|
||||
.ForMember(z => z.TokenId, src => src.MapFrom(z => z.Token.TokenId))
|
||||
.ForMember(z => z.UserId, src => src.MapFrom(z => z.Token.UserId))
|
||||
.ForMember(z => z.UserName, src => src.MapFrom(z => z.Token.UserName))
|
||||
.ForMember(z => z.FirstName, src => src.MapFrom(z => z.Token.FirstName))
|
||||
.ForMember(z => z.LastName, src => src.MapFrom(z => z.Token.LastName))
|
||||
.ForMember(z => z.Email, src => src.MapFrom(z => z.Token.Email))
|
||||
.ForMember(z => z.SecurityStamp, src => src.MapFrom(z => z.Token.SecurityStamp))
|
||||
.ForMember(z => z.LockStamp, src => src.MapFrom(z => z.Token.LockStamp))
|
||||
.ForMember(z => z.CreatedAt, src => src.MapFrom(z => z.Token.CreatedAt))
|
||||
.ForMember(z => z.ExpiresIn, src => src.MapFrom(z => z.Token.ExpiresIn))
|
||||
.ForMember(z => z.Claims, src => src.MapFrom(z => z.Token.Claims))
|
||||
;
|
||||
|
||||
CreateMap<models.Token, dto.AuthorizationResult>();
|
||||
CreateMap<models.Account.LogoutResult, dto.AccountLogoutResult>();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,6 @@ namespace Tuitio.Application.Services.Abstractions
|
|||
{
|
||||
internal interface ITokenService
|
||||
{
|
||||
TokenExtension GenerateToken(AppUser user);
|
||||
Token GenerateToken(AppUser user);
|
||||
}
|
||||
}
|
|
@ -10,6 +10,6 @@ namespace Tuitio.Application.Services.Abstractions
|
|||
{
|
||||
Task<LoginResult> Login(string userName, string password);
|
||||
Task<LogoutResult> Logout(string tokenRaw);
|
||||
TokenExtension Authorize(string tokenRaw);
|
||||
Token Authorize(string tokenRaw);
|
||||
}
|
||||
}
|
|
@ -43,7 +43,7 @@ namespace Tuitio.Application.Services
|
|||
_logger.LogInformation($"BehaviorService: {activeTokens.Length} active tokens were found in database.");
|
||||
foreach (var token in activeTokens)
|
||||
{
|
||||
var storeToken = TokenExtension.Deserialize(token.TokenExtension);
|
||||
var storeToken = Token.Import(token.Token);
|
||||
_securityStore.Set(token.Token, storeToken);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,14 +18,15 @@ namespace Tuitio.Application.Services
|
|||
_configProvider=configProvider;
|
||||
}
|
||||
|
||||
public TokenExtension GenerateToken(AppUser user)
|
||||
public Token GenerateToken(AppUser user)
|
||||
{
|
||||
var token = new Token(_configProvider.Token.ValidityInMinutes);
|
||||
var claims = user.Claims?.ToDictionary();
|
||||
var userRoles = user.GetUserRoles().AsRecordIdentifiers();
|
||||
var userGroups = user.UserGroups?.Select(z => z.UserGroup).AsRecordIdentifiers();
|
||||
var token = Token.Initialize(_configProvider.Token.ValidityInMinutes, user.UserId, user.UserName, user.FirstName, user.LastName, user.Email, user.SecurityStamp, claims);
|
||||
var extension = token.Extend(userRoles, userGroups);
|
||||
return extension;
|
||||
|
||||
token.SetUserData(user.UserId, user.UserName, user.FirstName, user.LastName, user.Email, user.SecurityStamp, claims, userRoles, userGroups);
|
||||
return token;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,30 +42,30 @@ namespace Tuitio.Application.Services
|
|||
if (!valid)
|
||||
return null;
|
||||
|
||||
var tokenExtension = _tokenService.GenerateToken(user);
|
||||
var raw = tokenExtension.Token.Export();
|
||||
_securityStore.Set(raw, tokenExtension);
|
||||
var token = _tokenService.GenerateToken(user);
|
||||
var raw = token.Export();
|
||||
_securityStore.Set(raw, token);
|
||||
|
||||
await _userRepository.UpdateUserAfterLogin(user, tokenExtension, raw);
|
||||
await _userRepository.UpdateUserAfterLogin(user, token, raw);
|
||||
|
||||
var result = new LoginResult(tokenExtension, raw);
|
||||
var result = new LoginResult(token, raw);
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<LogoutResult> Logout(string tokenRaw)
|
||||
{
|
||||
var tokenExtension = _securityStore.Get(tokenRaw);
|
||||
if (tokenExtension == null)
|
||||
var token = _securityStore.Get(tokenRaw);
|
||||
if (token == null)
|
||||
return null;
|
||||
|
||||
await _userRepository.RemoveToken(tokenExtension.Token.TokenId);
|
||||
await _userRepository.RemoveToken(token.TokenId);
|
||||
_securityStore.Remove(tokenRaw);
|
||||
|
||||
var result = new LogoutResult(tokenExtension.Token.UserId, tokenExtension.Token.UserName, DateTime.UtcNow);
|
||||
var result = new LogoutResult(token.UserId, token.UserName, DateTime.UtcNow);
|
||||
return result;
|
||||
}
|
||||
|
||||
public TokenExtension Authorize(string tokenRaw)
|
||||
public Token Authorize(string tokenRaw)
|
||||
{
|
||||
var token = _securityStore.Get(tokenRaw);
|
||||
return token;
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
// Copyright (c) 2020 Tudor Stanciu
|
||||
|
||||
using System;
|
||||
using Tuitio.Domain.Models;
|
||||
|
||||
namespace Tuitio.Application.Stores
|
||||
{
|
||||
internal interface ITokenStore
|
||||
{
|
||||
TokenExtension Get(string key);
|
||||
bool Set(string key, TokenExtension token);
|
||||
Token Get(string key);
|
||||
bool Set(string key, Token token);
|
||||
void Remove(string key);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,14 +8,14 @@ namespace Tuitio.Application.Stores
|
|||
{
|
||||
internal class TokenStore : ITokenStore
|
||||
{
|
||||
private ConcurrentDictionary<string, TokenExtension> Tokens { get; }
|
||||
private ConcurrentDictionary<string, Token> Tokens { get; }
|
||||
|
||||
public TokenStore()
|
||||
{
|
||||
Tokens = new ConcurrentDictionary<string, TokenExtension>();
|
||||
Tokens = new ConcurrentDictionary<string, Token>();
|
||||
}
|
||||
|
||||
public TokenExtension Get(string key)
|
||||
public Token Get(string key)
|
||||
{
|
||||
var registered = Tokens.ContainsKey(key);
|
||||
if (!registered)
|
||||
|
@ -24,7 +24,7 @@ namespace Tuitio.Application.Stores
|
|||
return Tokens[key];
|
||||
}
|
||||
|
||||
public bool Set(string key, TokenExtension token)
|
||||
public bool Set(string key, Token token)
|
||||
{
|
||||
var registered = Tokens.ContainsKey(key);
|
||||
if (registered)
|
||||
|
|
|
@ -41,16 +41,13 @@ namespace Tuitio.Domain.Data.Repositories
|
|||
.FirstOrDefaultAsync(z => z.UserId == userId);
|
||||
}
|
||||
|
||||
public async Task UpdateUserAfterLogin(AppUser user, TokenExtension tokenExtension, string tokenRaw)
|
||||
public async Task UpdateUserAfterLogin(AppUser user, Token token, string tokenRaw)
|
||||
{
|
||||
var token = tokenExtension.Token;
|
||||
var extension = tokenExtension.Serialize();
|
||||
var userToken = new UserToken()
|
||||
{
|
||||
TokenId = token.TokenId,
|
||||
UserId = token.UserId,
|
||||
Token = tokenRaw,
|
||||
TokenExtension = extension,
|
||||
ValidFrom = token.CreatedAt,
|
||||
ValidUntil = token.CreatedAt.AddSeconds(token.ExpiresIn)
|
||||
};
|
||||
|
|
|
@ -5,7 +5,6 @@ begin
|
|||
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,
|
||||
TokenExtension varchar(2000) not null,
|
||||
ValidFrom datetime not null,
|
||||
ValidUntil datetime not null
|
||||
)
|
||||
|
|
|
@ -9,7 +9,6 @@ namespace Tuitio.Domain.Entities
|
|||
public Guid TokenId { get; set; }
|
||||
public int UserId { get; set; }
|
||||
public string Token { get; set; }
|
||||
public string TokenExtension { get; set; }
|
||||
public DateTime ValidFrom { get; set; }
|
||||
public DateTime ValidUntil { get; set; }
|
||||
}
|
||||
|
|
|
@ -4,6 +4,6 @@ using System;
|
|||
|
||||
namespace Tuitio.Domain.Models.Account
|
||||
{
|
||||
public record LoginResult(TokenExtension TokenExtension, string Raw);
|
||||
public record LoginResult(Token Token, string Raw);
|
||||
public record LogoutResult(int UserId, string UserName, DateTime LogoutDate);
|
||||
}
|
||||
|
|
|
@ -23,10 +23,16 @@ namespace Tuitio.Domain.Models
|
|||
public long ExpiresIn { get; set; }
|
||||
public Dictionary<string, string> Claims { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public IEnumerable<RecordIdentifier> UserRoles { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public IEnumerable<RecordIdentifier> UserGroups { get; set; }
|
||||
|
||||
[Obsolete("This constructor is only used for deserialization and should not be used for any other purpose.")]
|
||||
public Token() { }
|
||||
|
||||
private Token(int validityInMinutes)
|
||||
public Token(int validityInMinutes)
|
||||
{
|
||||
TokenId = Guid.NewGuid();
|
||||
CreatedAt = DateTime.UtcNow;
|
||||
|
@ -34,20 +40,17 @@ namespace Tuitio.Domain.Models
|
|||
ExpiresIn = validityInMinutes * 60; // seconds
|
||||
}
|
||||
|
||||
public static Token Initialize(int validityInMinutes, int userId, string userName, string firstName, string lastName, string email, string securityStamp, Dictionary<string, string> claims)
|
||||
public void SetUserData(int userId, string userName, string firstName, string lastName, string email, string securityStamp, Dictionary<string, string> claims, IEnumerable<RecordIdentifier> userRoles, IEnumerable<RecordIdentifier> userGroups)
|
||||
{
|
||||
var token = new Token(validityInMinutes)
|
||||
{
|
||||
UserId = userId,
|
||||
UserName = userName,
|
||||
FirstName = firstName,
|
||||
LastName = lastName,
|
||||
Email = email,
|
||||
SecurityStamp = securityStamp,
|
||||
Claims = claims
|
||||
};
|
||||
|
||||
return token;
|
||||
UserId = userId;
|
||||
UserName = userName;
|
||||
FirstName = firstName;
|
||||
LastName = lastName;
|
||||
Email = email;
|
||||
SecurityStamp = securityStamp;
|
||||
Claims = claims;
|
||||
UserRoles = userRoles;
|
||||
UserGroups = userGroups;
|
||||
}
|
||||
|
||||
public string Export()
|
||||
|
@ -70,9 +73,6 @@ namespace Tuitio.Domain.Models
|
|||
return token;
|
||||
}
|
||||
|
||||
public TokenExtension Extend(IEnumerable<RecordIdentifier> userRoles, IEnumerable<RecordIdentifier> userGroups)
|
||||
=> TokenExtension.Create(this, userRoles, userGroups);
|
||||
|
||||
private static bool ValidateTokenRaw(string tokenRaw)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(tokenRaw))
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
// Copyright (c) 2020 Tudor Stanciu
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Tuitio.Domain.Models
|
||||
{
|
||||
public class TokenExtension
|
||||
{
|
||||
public Token Token { get; set; }
|
||||
public IEnumerable<RecordIdentifier> UserRoles { get; set; }
|
||||
public IEnumerable<RecordIdentifier> UserGroups { get; set; }
|
||||
|
||||
[Obsolete("This constructor is only used for deserialization and should not be used for any other purpose.")]
|
||||
public TokenExtension() { }
|
||||
|
||||
private TokenExtension(Token token, IEnumerable<RecordIdentifier> userRoles, IEnumerable<RecordIdentifier> userGroups)
|
||||
{
|
||||
Token=token;
|
||||
UserRoles=userRoles;
|
||||
UserGroups=userGroups;
|
||||
}
|
||||
|
||||
public static TokenExtension Create(Token token, IEnumerable<RecordIdentifier> userRoles, IEnumerable<RecordIdentifier> userGroups)
|
||||
=> new(token, userRoles, userGroups);
|
||||
|
||||
public string Serialize()
|
||||
{
|
||||
var text = JsonConvert.SerializeObject(this);
|
||||
return text;
|
||||
}
|
||||
|
||||
public static TokenExtension Deserialize(string text)
|
||||
{
|
||||
var token = JsonConvert.DeserializeObject<TokenExtension>(text);
|
||||
return token;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,7 +11,7 @@ namespace Tuitio.Domain.Repositories
|
|||
{
|
||||
Task<AppUser> GetUser(string userName, string password);
|
||||
Task<AppUser> GetFullUser(int userId);
|
||||
Task UpdateUserAfterLogin(AppUser user, TokenExtension tokenExtension, string tokenRaw);
|
||||
Task UpdateUserAfterLogin(AppUser user, Token token, string tokenRaw);
|
||||
Task<UserToken[]> GetActiveTokens();
|
||||
Task RemoveToken(Guid tokenId);
|
||||
}
|
||||
|
|
|
@ -59,26 +59,26 @@ namespace Tuitio.Authentication
|
|||
return null;
|
||||
}
|
||||
|
||||
private AuthenticationTicket GetAuthenticationTicket(TokenExtension result)
|
||||
private AuthenticationTicket GetAuthenticationTicket(Token result)
|
||||
{
|
||||
var claimCollection = new Dictionary<string, string>()
|
||||
{
|
||||
{ ClaimTypes.NameIdentifier, result.Token.UserId.ToString() },
|
||||
{ ClaimTypes.Name, result.Token.UserName },
|
||||
{ ClaimTypes.NameIdentifier, result.UserId.ToString() },
|
||||
{ ClaimTypes.Name, result.UserName },
|
||||
};
|
||||
|
||||
if (result.Token.FirstName != null)
|
||||
claimCollection.Add(ClaimTypes.GivenName, result.Token.FirstName);
|
||||
if (result.FirstName != null)
|
||||
claimCollection.Add(ClaimTypes.GivenName, result.FirstName);
|
||||
|
||||
if (result.Token.LastName != null)
|
||||
claimCollection.Add(ClaimTypes.Surname, result.Token.FirstName);
|
||||
if (result.LastName != null)
|
||||
claimCollection.Add(ClaimTypes.Surname, result.FirstName);
|
||||
|
||||
if (result.Token.Email != null)
|
||||
claimCollection.Add(ClaimTypes.Email, result.Token.Email);
|
||||
if (result.Email != null)
|
||||
claimCollection.Add(ClaimTypes.Email, result.Email);
|
||||
|
||||
if (result.Token.Claims != null && result.Token.Claims.Any())
|
||||
if (result.Claims != null && result.Claims.Any())
|
||||
{
|
||||
foreach (var claim in result.Token.Claims)
|
||||
foreach (var claim in result.Claims)
|
||||
{
|
||||
if (claimCollection.ContainsKey(claim.Key))
|
||||
{
|
||||
|
|
|
@ -34,7 +34,8 @@ RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
|
|||
|
||||
ARG APP_VERSION=0.0.0.0
|
||||
ENV APP_VERSION=${APP_VERSION}
|
||||
ARG APP_DATE="-"
|
||||
ENV APP_DATE=${APP_DATE}
|
||||
|
||||
#Workaround to lower the TLS level in container for old sql server version
|
||||
RUN sed -i 's/TLSv1.2/TLSv1.0/g' /etc/ssl/openssl.cnf
|
||||
|
||||
ENTRYPOINT ["dotnet", "Tuitio.dll"]
|
|
@ -39,7 +39,7 @@ namespace Tuitio.Api
|
|||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Fatal(ex, "Tuitio API host terminated unexpectedly.");
|
||||
Log.Fatal(ex, "Tuitio API host terminated unexpectedly");
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
|
@ -68,7 +68,7 @@ namespace Tuitio.Application.Tests
|
|||
|
||||
// Act
|
||||
var token = _tokenService.GenerateToken(_userMock);
|
||||
var raw = token.Token.Export();
|
||||
var raw = token.Export();
|
||||
var extracted = Token.Import(raw);
|
||||
|
||||
// Assert
|
||||
|
|
|
@ -9,11 +9,11 @@ namespace Tuitio.Application.Tests
|
|||
{
|
||||
public class TokenStoreTests
|
||||
{
|
||||
private TokenExtension GetMockedToken()
|
||||
private Token GetMockedToken()
|
||||
{
|
||||
var token = Token.Initialize(1, 0, "test.tuitio", "tuitio", "user", "user.tuitio@lab.com", Guid.NewGuid().ToString(), null);
|
||||
var extension = token.Extend(null, null);
|
||||
return extension;
|
||||
var token = new Token(1);
|
||||
token.SetUserData(0, "test.tuitio", "tuitio", "user", "user.tuitio@lab.com", Guid.NewGuid().ToString(), null, null, null);
|
||||
return token;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
@ -68,13 +68,13 @@ namespace Tuitio.Application.Tests
|
|||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.NotNull(result.TokenExtension.Token);
|
||||
Assert.NotNull(result.Token);
|
||||
Assert.NotEmpty(result.Raw);
|
||||
Assert.Equal(userName, result.TokenExtension.Token.UserName);
|
||||
Assert.True(result.TokenExtension.Token.TokenId != Guid.Empty, "Token id cannot be an empty guid.");
|
||||
Assert.NotEmpty(result.TokenExtension.Token.LockStamp);
|
||||
Assert.True(result.TokenExtension.Token.ExpiresIn > 0, "Token expiration must be a positive number.");
|
||||
Assert.True((DateTime.UtcNow - result.TokenExtension.Token.CreatedAt).TotalMinutes <= 1, "Token creation date must be within the last minute.");
|
||||
Assert.Equal(userName, result.Token.UserName);
|
||||
Assert.True(result.Token.TokenId != Guid.Empty, "Token id cannot be an empty guid.");
|
||||
Assert.NotEmpty(result.Token.LockStamp);
|
||||
Assert.True(result.Token.ExpiresIn > 0, "Token expiration must be a positive number.");
|
||||
Assert.True((DateTime.UtcNow - result.Token.CreatedAt).TotalMinutes <= 1, "Token creation date must be within the last minute.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -105,11 +105,11 @@ namespace Tuitio.Application.Tests
|
|||
var loginResult = await _userService.Login(userName, password);
|
||||
|
||||
// Act
|
||||
var userTokenFromDb = await dbContext.UserTokens.FirstOrDefaultAsync(z => z.TokenId == loginResult.TokenExtension.Token.TokenId);
|
||||
var userTokenFromDb = await dbContext.UserTokens.FirstOrDefaultAsync(z => z.TokenId == loginResult.Token.TokenId);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(userTokenFromDb);
|
||||
Assert.True(loginResult.TokenExtension.Token.TokenId != Guid.Empty, "Token id cannot be an empty guid.");
|
||||
Assert.True(loginResult.Token.TokenId != Guid.Empty, "Token id cannot be an empty guid.");
|
||||
Assert.True((DateTime.UtcNow - userTokenFromDb.ValidFrom).TotalMinutes <= 1, "Token valid from date must be within the last minute.");
|
||||
Assert.True(userTokenFromDb.ValidUntil > DateTime.UtcNow, "Token valid until date must be greater than the current date.");
|
||||
}
|
||||
|
@ -141,11 +141,11 @@ namespace Tuitio.Application.Tests
|
|||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(userName, result.Token.UserName);
|
||||
Assert.NotNull(result.Token.SecurityStamp);
|
||||
Assert.NotNull(result.Token.LockStamp);
|
||||
Assert.True(result.Token.TokenId != Guid.Empty, "Token id cannot be an empty guid.");
|
||||
Assert.True(result.Token.ExpiresIn > 0, "Token expiration must be a positive number.");
|
||||
Assert.Equal(userName, result.UserName);
|
||||
Assert.NotNull(result.SecurityStamp);
|
||||
Assert.NotNull(result.LockStamp);
|
||||
Assert.True(result.TokenId != Guid.Empty, "Token id cannot be an empty guid.");
|
||||
Assert.True(result.ExpiresIn > 0, "Token expiration must be a positive number.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
Loading…
Reference in New Issue