Merge branch 'feature/tokens' into 'dev'

Resolve "API tokens"

See merge request leon.hoppe/hopframe!3
This commit was merged in pull request #41.
This commit is contained in:
2024-12-21 16:36:12 +00:00
27 changed files with 214 additions and 72 deletions

View File

@@ -5,7 +5,7 @@
<driver-ref>sqlite.xerial</driver-ref> <driver-ref>sqlite.xerial</driver-ref>
<synchronize>true</synchronize> <synchronize>true</synchronize>
<jdbc-driver>org.sqlite.JDBC</jdbc-driver> <jdbc-driver>org.sqlite.JDBC</jdbc-driver>
<jdbc-url>jdbc:sqlite:$PROJECT_DIR$/test/RestApiTest/bin/Debug/net8.0/test.db</jdbc-url> <jdbc-url>jdbc:sqlite:C:\Users\leon\Documents\Projekte\HopFrame\testing\HopFrame.Testing.Api\bin\Debug\net8.0\test.db</jdbc-url>
<jdbc-additional-properties> <jdbc-additional-properties>
<property name="com.intellij.clouds.kubernetes.db.enabled" value="false" /> <property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />
</jdbc-additional-properties> </jdbc-additional-properties>

14
.idea/.idea.HopFrame/.idea/discord.xml generated Normal file
View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DiscordProjectSettings">
<option name="show" value="PROJECT_FILES" />
<option name="description" value="" />
<option name="applicationTheme" value="default" />
<option name="iconsTheme" value="default" />
<option name="button1Title" value="" />
<option name="button1Url" value="" />
<option name="button2Title" value="" />
<option name="button2Url" value="" />
<option name="customApplicationId" value="" />
</component>
</project>

View File

@@ -1,4 +1,5 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> <wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAuthenticationHandler_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fca451c12d69fe026a0e7e9b1a0ddbf4cf6f6b8316cb2aec7984a7241813f648_003FAuthenticationHandler_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAuthenticationSchemeOptions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003F8525b7a9e58c77f532f1a88d4f2897e3c2baf316b9eb2c391b242a3885fcce6_003FAuthenticationSchemeOptions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAuthenticationSchemeOptions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003F8525b7a9e58c77f532f1a88d4f2897e3c2baf316b9eb2c391b242a3885fcce6_003FAuthenticationSchemeOptions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEditContextDataAnnotationsExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fbc307cd57fb42fc4c7fb9795381958122734d3750f41b6c1735c7d132ecda70_003FEditContextDataAnnotationsExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEditContextDataAnnotationsExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fbc307cd57fb42fc4c7fb9795381958122734d3750f41b6c1735c7d132ecda70_003FEditContextDataAnnotationsExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AExceptionDispatchInfo_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fbd1d5c50194fea68ff3559c160230b0ab50f5acf4ce3061bffd6d62958e2182_003FExceptionDispatchInfo_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AExceptionDispatchInfo_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fbd1d5c50194fea68ff3559c160230b0ab50f5acf4ce3061bffd6d62958e2182_003FExceptionDispatchInfo_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
@@ -67,6 +68,9 @@

View File

@@ -23,8 +23,6 @@ by configuring your configuration to load these.
> `builder.Configuration.AddEnvironmentVariables();` to your startup configuration before you add the > `builder.Configuration.AddEnvironmentVariables();` to your startup configuration before you add the
> custom configurations / HopFrame services. > custom configurations / HopFrame services.
### Example
You can specify `Seconds`, `Minutes`, `Hours` and `Days` for either of the two token types. You can specify `Seconds`, `Minutes`, `Hours` and `Days` for either of the two token types.
These get combined to a single time span. These get combined to a single time span.
@@ -49,3 +47,28 @@ HOPFRAME__AUTHENTICATION__ACCESSTOKEN__MINUTES=30
HOPFRAME__AUTHENTICATION__REFRESHTOKEN__DAYS=10 HOPFRAME__AUTHENTICATION__REFRESHTOKEN__DAYS=10
HOPFRAME__AUTHENTICATION__REFRESHTOKEN__HOURS=5 HOPFRAME__AUTHENTICATION__REFRESHTOKEN__HOURS=5
``` ```
## API tokens
API tokens are useful to use in automation environments that need to access an endpoint or page of your application.
The HopFrame supports this natively and no further configuration is required in order to use them.
### Create an api token
You can create an api token via the `ITokenRepository`:
```csharp
tokens.CreateApiToken(user, DateTime.MaxValue);
```
This creates a new api token that is valid until the provided DateTime has passed. Note that in the database and the token
model the `CreatedAt` property represents the expiration date on an api token. For security reasons the api token by default
has no permissions. This allows you to create tokens that are just permitted to perform a single action. Note that an api token
can **never** have more permissions than the user associated with it.
### Add permissions to an api token
You can add permissions to an api token like you would to a normal user or group:
```csharp
permissions.AddPermission(apiToken, "token.permission");
```

View File

@@ -35,16 +35,18 @@ public class Permission {
public DateTime GrantedAt { get; set; } public DateTime GrantedAt { get; set; }
public virtual User User { get; set; } public virtual User User { get; set; }
public virtual PermissionGroup Group { get; set; } public virtual PermissionGroup Group { get; set; }
public virtual Token Token { get; set; }
} }
``` ```
## Token ## Token
```csharp ```csharp
public class Token { public class Token : IPermissionOwner {
public int Type { get; set; } public int Type { get; set; }
public Guid Content { get; set; } public Guid Content { get; set; }
public DateTime CreatedAt { get; set; } public DateTime CreatedAt { get; set; }
public virtual User Owner { get; set; } public virtual User Owner { get; set; }
public virtual List<Permission> Permissions { get; set; }
} }
``` ```

View File

@@ -71,5 +71,9 @@ public interface ITokenRepository {
Task<Token> CreateToken(int type, User owner); Task<Token> CreateToken(int type, User owner);
Task DeleteUserTokens(User owner); Task DeleteUserTokens(User owner);
Task DeleteToken(Token token);
Task<Token> CreateApiToken(User owner, DateTime expirationDate);
} }
``` ```

View File

@@ -23,18 +23,18 @@ internal class AuthLogic(IUserRepository users, ITokenRepository tokens, ITokenC
var refreshToken = await tokens.CreateToken(Token.RefreshTokenType, user); var refreshToken = await tokens.CreateToken(Token.RefreshTokenType, user);
var accessToken = await tokens.CreateToken(Token.AccessTokenType, user); var accessToken = await tokens.CreateToken(Token.AccessTokenType, user);
accessor.HttpContext?.Response.Cookies.Append(ITokenContext.RefreshTokenType, refreshToken.Content.ToString(), new CookieOptions { accessor.HttpContext?.Response.Cookies.Append(ITokenContext.RefreshTokenType, refreshToken.TokenId.ToString(), new CookieOptions {
MaxAge = options.Value.RefreshTokenTime, MaxAge = options.Value.RefreshTokenTime,
HttpOnly = true, HttpOnly = true,
Secure = true Secure = true
}); });
accessor.HttpContext?.Response.Cookies.Append(ITokenContext.AccessTokenType, accessToken.Content.ToString(), new CookieOptions { accessor.HttpContext?.Response.Cookies.Append(ITokenContext.AccessTokenType, accessToken.TokenId.ToString(), new CookieOptions {
MaxAge = options.Value.AccessTokenTime, MaxAge = options.Value.AccessTokenTime,
HttpOnly = true, HttpOnly = true,
Secure = true Secure = true
}); });
return LogicResult<SingleValueResult<string>>.Ok(accessToken.Content.ToString()); return LogicResult<SingleValueResult<string>>.Ok(accessToken.TokenId.ToString());
} }
public async Task<LogicResult<SingleValueResult<string>>> Register(UserRegister register) { public async Task<LogicResult<SingleValueResult<string>>> Register(UserRegister register) {
@@ -54,18 +54,18 @@ internal class AuthLogic(IUserRepository users, ITokenRepository tokens, ITokenC
var refreshToken = await tokens.CreateToken(Token.RefreshTokenType, user); var refreshToken = await tokens.CreateToken(Token.RefreshTokenType, user);
var accessToken = await tokens.CreateToken(Token.AccessTokenType, user); var accessToken = await tokens.CreateToken(Token.AccessTokenType, user);
accessor.HttpContext?.Response.Cookies.Append(ITokenContext.RefreshTokenType, refreshToken.Content.ToString(), new CookieOptions { accessor.HttpContext?.Response.Cookies.Append(ITokenContext.RefreshTokenType, refreshToken.TokenId.ToString(), new CookieOptions {
MaxAge = options.Value.RefreshTokenTime, MaxAge = options.Value.RefreshTokenTime,
HttpOnly = true, HttpOnly = true,
Secure = true Secure = true
}); });
accessor.HttpContext?.Response.Cookies.Append(ITokenContext.AccessTokenType, accessToken.Content.ToString(), new CookieOptions { accessor.HttpContext?.Response.Cookies.Append(ITokenContext.AccessTokenType, accessToken.TokenId.ToString(), new CookieOptions {
MaxAge = options.Value.AccessTokenTime, MaxAge = options.Value.AccessTokenTime,
HttpOnly = false, HttpOnly = false,
Secure = true Secure = true
}); });
return LogicResult<SingleValueResult<string>>.Ok(accessToken.Content.ToString()); return LogicResult<SingleValueResult<string>>.Ok(accessToken.TokenId.ToString());
} }
public async Task<LogicResult<SingleValueResult<string>>> Authenticate() { public async Task<LogicResult<SingleValueResult<string>>> Authenticate() {
@@ -87,13 +87,13 @@ internal class AuthLogic(IUserRepository users, ITokenRepository tokens, ITokenC
var accessToken = await tokens.CreateToken(Token.AccessTokenType, token.Owner); var accessToken = await tokens.CreateToken(Token.AccessTokenType, token.Owner);
accessor.HttpContext?.Response.Cookies.Append(ITokenContext.AccessTokenType, accessToken.Content.ToString(), new CookieOptions { accessor.HttpContext?.Response.Cookies.Append(ITokenContext.AccessTokenType, accessToken.TokenId.ToString(), new CookieOptions {
MaxAge = options.Value.AccessTokenTime, MaxAge = options.Value.AccessTokenTime,
HttpOnly = false, HttpOnly = false,
Secure = true Secure = true
}); });
return LogicResult<SingleValueResult<string>>.Ok(accessToken.Content.ToString()); return LogicResult<SingleValueResult<string>>.Ok(accessToken.TokenId.ToString());
} }
public async Task<LogicResult> Logout() { public async Task<LogicResult> Logout() {

View File

@@ -30,5 +30,10 @@ public abstract class HopDbContextBase : DbContext {
.HasMany(g => g.Permissions) .HasMany(g => g.Permissions)
.WithOne(p => p.Group) .WithOne(p => p.Group)
.OnDelete(DeleteBehavior.Cascade); .OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<Token>()
.HasMany(t => t.Permissions)
.WithOne(t => t.Token)
.OnDelete(DeleteBehavior.Cascade);
} }
} }

View File

@@ -21,6 +21,9 @@ public class Permission {
[ForeignKey("GroupName"), JsonIgnore] [ForeignKey("GroupName"), JsonIgnore]
public virtual PermissionGroup Group { get; set; } public virtual PermissionGroup Group { get; set; }
[ForeignKey("TokenId"), JsonIgnore]
public virtual Token Token { get; set; }
} }
public interface IPermissionOwner; public interface IPermissionOwner;

View File

@@ -4,24 +4,32 @@ using System.Text.Json.Serialization;
namespace HopFrame.Database.Models; namespace HopFrame.Database.Models;
public class Token { public class Token : IPermissionOwner {
public const int RefreshTokenType = 0; public const int RefreshTokenType = 0;
public const int AccessTokenType = 1; public const int AccessTokenType = 1;
public const int ApiTokenType = 2;
/// <summary> /// <summary>
/// Defines the Type of the stored Token /// Defines the Type of the stored Token
/// 0: Refresh token /// 0: Refresh token
/// 1: Access token /// 1: Access token
/// 2: Api token
/// </summary> /// </summary>
[Required, MinLength(1), MaxLength(1)] [Required, MinLength(1), MaxLength(1)]
public int Type { get; set; } public int Type { get; set; }
[Key, Required, MinLength(36), MaxLength(36)] [Key, Required, MinLength(36), MaxLength(36)]
public Guid Content { get; set; } public Guid TokenId { get; set; }
/// <summary>
/// Defines the creation date of the token
/// In case of an api token it defines the date it becomes invalid
/// </summary>
[Required] [Required]
public DateTime CreatedAt { get; set; } public DateTime CreatedAt { get; set; }
[ForeignKey("UserId"), JsonIgnore] [ForeignKey("UserId"), JsonIgnore]
public virtual User Owner { get; set; } public virtual User Owner { get; set; }
public virtual List<Permission> Permissions { get; set; }
} }

View File

@@ -5,5 +5,7 @@ namespace HopFrame.Database.Repositories;
public interface ITokenRepository { public interface ITokenRepository {
Task<Token> GetToken(string content); Task<Token> GetToken(string content);
Task<Token> CreateToken(int type, User owner); Task<Token> CreateToken(int type, User owner);
Task DeleteUserTokens(User owner); Task DeleteUserTokens(User owner, bool includeApiTokens = false);
Task DeleteToken(Token token);
Task<Token> CreateApiToken(User owner, DateTime expirationDate);
} }

View File

@@ -5,6 +5,10 @@ namespace HopFrame.Database.Repositories.Implementation;
internal sealed class PermissionRepository<TDbContext>(TDbContext context, IGroupRepository groupRepository) : IPermissionRepository where TDbContext : HopDbContextBase { internal sealed class PermissionRepository<TDbContext>(TDbContext context, IGroupRepository groupRepository) : IPermissionRepository where TDbContext : HopDbContextBase {
public async Task<bool> HasPermission(IPermissionOwner owner, params string[] permissions) { public async Task<bool> HasPermission(IPermissionOwner owner, params string[] permissions) {
if (owner is Token { Type: Token.ApiTokenType } token) {
if (!await HasPermission(token.Owner, permissions)) return false;
}
var perms = (await GetFullPermissions(owner)).ToArray(); var perms = (await GetFullPermissions(owner)).ToArray();
foreach (var permission in permissions) { foreach (var permission in permissions) {
@@ -24,6 +28,12 @@ internal sealed class PermissionRepository<TDbContext>(TDbContext context, IGrou
entry.User = user; entry.User = user;
}else if (owner is PermissionGroup group) { }else if (owner is PermissionGroup group) {
entry.Group = group; entry.Group = group;
}else if (owner is Token token) {
if (token.Type != Token.ApiTokenType)
throw new ArgumentException("Only API tokens can have permissions!");
if (!await HasPermission(token.Owner, permission))
throw new ArgumentException("An api token cannot have more permissions than the owner has!");
entry.Token = token;
} }
await context.Permissions.AddAsync(entry); await context.Permissions.AddAsync(entry);
@@ -48,6 +58,13 @@ internal sealed class PermissionRepository<TDbContext>(TDbContext context, IGrou
.Where(p =>p.Group.Name == group.Name) .Where(p =>p.Group.Name == group.Name)
.Where(p => p.PermissionName == permission) .Where(p => p.PermissionName == permission)
.SingleOrDefaultAsync(); .SingleOrDefaultAsync();
}else if (owner is Token token) {
entry = await context.Permissions
.Include(p => p.Token)
.Where(p => p.Token != null)
.Where(p => p.Token.TokenId == token.TokenId)
.Where(p => p.PermissionName == permission)
.SingleOrDefaultAsync();
} }
if (entry is not null) { if (entry is not null) {
@@ -59,6 +76,10 @@ internal sealed class PermissionRepository<TDbContext>(TDbContext context, IGrou
public async Task<IList<string>> GetFullPermissions(IPermissionOwner owner) { public async Task<IList<string>> GetFullPermissions(IPermissionOwner owner) {
var permissions = new List<string>(); var permissions = new List<string>();
if (owner is Token token && token.Type != Token.ApiTokenType) {
owner = token.Owner;
}
if (owner is User user) { if (owner is User user) {
var perms = await context.Permissions var perms = await context.Permissions
.Include(p => p.User) .Include(p => p.User)
@@ -74,6 +95,14 @@ internal sealed class PermissionRepository<TDbContext>(TDbContext context, IGrou
.Where(p =>p.Group.Name == group.Name) .Where(p =>p.Group.Name == group.Name)
.ToListAsync(); .ToListAsync();
permissions.AddRange(perms.Select(p => p.PermissionName));
}else if (owner is Token apiToken) {
var perms = await context.Permissions
.Include(p => p.Token)
.Where(p => p.Token != null)
.Where(p =>p.Token.TokenId == apiToken.TokenId)
.ToListAsync();
permissions.AddRange(perms.Select(p => p.PermissionName)); permissions.AddRange(perms.Select(p => p.PermissionName));
} }

View File

@@ -11,14 +11,14 @@ internal sealed class TokenRepository<TDbContext>(TDbContext context) : ITokenRe
return await context.Tokens return await context.Tokens
.Include(t => t.Owner) .Include(t => t.Owner)
.Where(t => t.Content == guid) .Where(t => t.TokenId == guid)
.SingleOrDefaultAsync(); .SingleOrDefaultAsync();
} }
public async Task<Token> CreateToken(int type, User owner) { public async Task<Token> CreateToken(int type, User owner) {
var token = new Token { var token = new Token {
CreatedAt = DateTime.Now, CreatedAt = DateTime.Now,
Content = Guid.NewGuid(), TokenId = Guid.NewGuid(),
Type = type, Type = type,
Owner = owner Owner = owner
}; };
@@ -29,13 +29,37 @@ internal sealed class TokenRepository<TDbContext>(TDbContext context) : ITokenRe
return token; return token;
} }
public async Task DeleteUserTokens(User owner) { public async Task DeleteUserTokens(User owner, bool includeApiTokens = false) {
var tokens = await context.Tokens var tokens = await context.Tokens
.Include(t => t.Owner) .Include(t => t.Owner)
.Where(t => t.Owner.Id == owner.Id) .Where(t => t.Owner.Id == owner.Id)
.ToListAsync(); .ToListAsync();
if (!includeApiTokens)
tokens = tokens
.Where(t => t.Type != Token.ApiTokenType)
.ToList();
context.Tokens.RemoveRange(tokens); context.Tokens.RemoveRange(tokens);
await context.SaveChangesAsync(); await context.SaveChangesAsync();
} }
public async Task DeleteToken(Token token) {
context.Tokens.Remove(token);
await context.SaveChangesAsync();
}
public async Task<Token> CreateApiToken(User owner, DateTime expirationDate) {
var token = new Token {
CreatedAt = expirationDate,
TokenId = Guid.NewGuid(),
Type = Token.ApiTokenType,
Owner = owner
};
await context.Tokens.AddAsync(token);
await context.SaveChangesAsync();
return token;
}
} }

View File

@@ -1,5 +1,6 @@
using System.Security.Claims; using System.Security.Claims;
using System.Text.Encodings.Web; using System.Text.Encodings.Web;
using HopFrame.Database.Models;
using HopFrame.Database.Repositories; using HopFrame.Database.Repositories;
using HopFrame.Security.Claims; using HopFrame.Security.Claims;
using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication;
@@ -33,7 +34,10 @@ public class HopFrameAuthentication(
var tokenEntry = await tokens.GetToken(accessToken); var tokenEntry = await tokens.GetToken(accessToken);
if (tokenEntry is null) return AuthenticateResult.Fail("The provided Access Token does not exist"); if (tokenEntry is null) return AuthenticateResult.Fail("The provided Access Token does not exist");
if (tokenEntry.CreatedAt + tokenOptions.Value.AccessTokenTime < DateTime.Now) return AuthenticateResult.Fail("The provided Access Token is expired");
if (tokenEntry.Type == Token.ApiTokenType) {
if (tokenEntry.CreatedAt < DateTime.Now) return AuthenticateResult.Fail("The provided API Token is expired");
}else if (tokenEntry.CreatedAt + tokenOptions.Value.AccessTokenTime < DateTime.Now) return AuthenticateResult.Fail("The provided Access Token is expired");
if (tokenEntry.Owner is null) if (tokenEntry.Owner is null)
return AuthenticateResult.Fail("The provided Access Token does not match any user"); return AuthenticateResult.Fail("The provided Access Token does not match any user");
@@ -43,7 +47,7 @@ public class HopFrameAuthentication(
new(HopFrameClaimTypes.UserId, tokenEntry.Owner.Id.ToString()) new(HopFrameClaimTypes.UserId, tokenEntry.Owner.Id.ToString())
}; };
var permissions = await perms.GetFullPermissions(tokenEntry.Owner); var permissions = await perms.GetFullPermissions(tokenEntry);
claims.AddRange(permissions.Select(perm => new Claim(HopFrameClaimTypes.Permission, perm))); claims.AddRange(permissions.Select(perm => new Claim(HopFrameClaimTypes.Permission, perm)));
var principal = new ClaimsPrincipal(); var principal = new ClaimsPrincipal();

View File

@@ -21,4 +21,6 @@ public interface ITokenContext {
/// The access token the user provided /// The access token the user provided
/// </summary> /// </summary>
Token AccessToken { get; } Token AccessToken { get; }
IList<string> ContextualPermissions { get; }
} }

View File

@@ -4,10 +4,12 @@ using Microsoft.AspNetCore.Http;
namespace HopFrame.Security.Claims; namespace HopFrame.Security.Claims;
internal sealed class TokenContextImplementor(IHttpContextAccessor accessor, IUserRepository users, ITokenRepository tokens) : ITokenContext { internal sealed class TokenContextImplementor(IHttpContextAccessor accessor, IUserRepository users, ITokenRepository tokens, IPermissionRepository permissions) : ITokenContext {
public bool IsAuthenticated => !string.IsNullOrEmpty(accessor.HttpContext?.User.GetAccessTokenId()); public bool IsAuthenticated => !string.IsNullOrEmpty(accessor.HttpContext?.User.GetAccessTokenId());
public User User => users.GetUser(Guid.Parse(accessor.HttpContext?.User.GetUserId() ?? Guid.Empty.ToString())).GetAwaiter().GetResult(); public User User => users.GetUser(Guid.Parse(accessor.HttpContext?.User.GetUserId() ?? Guid.Empty.ToString())).GetAwaiter().GetResult();
public Token AccessToken => tokens.GetToken(accessor.HttpContext?.User.GetAccessTokenId()).GetAwaiter().GetResult(); public Token AccessToken => tokens.GetToken(accessor.HttpContext?.User.GetAccessTokenId()).GetAwaiter().GetResult();
public IList<string> ContextualPermissions => permissions.GetFullPermissions(AccessToken).GetAwaiter().GetResult();
} }

View File

@@ -22,11 +22,11 @@ public sealed class AuthMiddleware(IAuthService auth, IPermissionRepository perm
} }
var claims = new List<Claim> { var claims = new List<Claim> {
new(HopFrameClaimTypes.AccessTokenId, token.Content.ToString()), new(HopFrameClaimTypes.AccessTokenId, token.TokenId.ToString()),
new(HopFrameClaimTypes.UserId, token.Owner.Id.ToString()) new(HopFrameClaimTypes.UserId, token.Owner.Id.ToString())
}; };
var permissions = await perms.GetFullPermissions(token.Owner); var permissions = await perms.GetFullPermissions(token);
claims.AddRange(permissions.Select(perm => new Claim(HopFrameClaimTypes.Permission, perm))); claims.AddRange(permissions.Select(perm => new Claim(HopFrameClaimTypes.Permission, perm)));
context.User.AddIdentity(new ClaimsIdentity(claims, HopFrameAuthentication.SchemeName)); context.User.AddIdentity(new ClaimsIdentity(claims, HopFrameAuthentication.SchemeName));

View File

@@ -321,7 +321,7 @@
private async void Save() { private async void Save() {
if (_isEdit && _currentPage.Permissions.Update is not null) { if (_isEdit && _currentPage.Permissions.Update is not null) {
if (!await Permissions.HasPermission(Auth.User, _currentPage.Permissions.Update)) { if (!await Permissions.HasPermission(Auth.AccessToken, _currentPage.Permissions.Update)) {
await Alerts.FireAsync(new SweetAlertOptions { await Alerts.FireAsync(new SweetAlertOptions {
Title = "Unauthorized!", Title = "Unauthorized!",
Text = "You don't have the required permissions to edit an entry!", Text = "You don't have the required permissions to edit an entry!",
@@ -330,7 +330,7 @@
return; return;
} }
}else if (_currentPage.Permissions.Create is not null) { }else if (_currentPage.Permissions.Create is not null) {
if (!await Permissions.HasPermission(Auth.User, _currentPage.Permissions.Create)) { if (!await Permissions.HasPermission(Auth.AccessToken, _currentPage.Permissions.Create)) {
await Alerts.FireAsync(new SweetAlertOptions { await Alerts.FireAsync(new SweetAlertOptions {
Title = "Unauthorized!", Title = "Unauthorized!",
Text = "You don't have the required permissions to add an entry!", Text = "You don't have the required permissions to add an entry!",

View File

@@ -140,8 +140,8 @@
throw new ArgumentException($"AdminPage '{_pageData.Title}' does not specify a model repository!'"); throw new ArgumentException($"AdminPage '{_pageData.Title}' does not specify a model repository!'");
_modelProvider = _pageData.LoadModelProvider(Provider); _modelProvider = _pageData.LoadModelProvider(Provider);
_hasEditPermission = _pageData.Permissions.Update is null || await Permissions.HasPermission(Auth.User, _pageData.Permissions.Update); _hasEditPermission = _pageData.Permissions.Update is null || await Permissions.HasPermission(Auth.AccessToken, _pageData.Permissions.Update);
_hasDeletePermission = _pageData.Permissions.Delete is null || await Permissions.HasPermission(Auth.User, _pageData.Permissions.Delete); _hasDeletePermission = _pageData.Permissions.Delete is null || await Permissions.HasPermission(Auth.AccessToken, _pageData.Permissions.Delete);
await Reload(); await Reload();
} }

View File

@@ -28,12 +28,12 @@ internal class AuthService(
var refreshToken = await tokens.CreateToken(Token.RefreshTokenType, user); var refreshToken = await tokens.CreateToken(Token.RefreshTokenType, user);
var accessToken = await tokens.CreateToken(Token.AccessTokenType, user); var accessToken = await tokens.CreateToken(Token.AccessTokenType, user);
httpAccessor.HttpContext?.Response.Cookies.Append(ITokenContext.RefreshTokenType, refreshToken.Content.ToString(), new CookieOptions { httpAccessor.HttpContext?.Response.Cookies.Append(ITokenContext.RefreshTokenType, refreshToken.TokenId.ToString(), new CookieOptions {
MaxAge = options.Value.RefreshTokenTime, MaxAge = options.Value.RefreshTokenTime,
HttpOnly = true, HttpOnly = true,
Secure = true Secure = true
}); });
httpAccessor.HttpContext?.Response.Cookies.Append(ITokenContext.AccessTokenType, accessToken.Content.ToString(), new CookieOptions { httpAccessor.HttpContext?.Response.Cookies.Append(ITokenContext.AccessTokenType, accessToken.TokenId.ToString(), new CookieOptions {
MaxAge = options.Value.AccessTokenTime, MaxAge = options.Value.AccessTokenTime,
HttpOnly = false, HttpOnly = false,
Secure = true Secure = true
@@ -49,12 +49,12 @@ internal class AuthService(
var refreshToken = await tokens.CreateToken(Token.RefreshTokenType, user); var refreshToken = await tokens.CreateToken(Token.RefreshTokenType, user);
var accessToken = await tokens.CreateToken(Token.AccessTokenType, user); var accessToken = await tokens.CreateToken(Token.AccessTokenType, user);
httpAccessor.HttpContext?.Response.Cookies.Append(ITokenContext.RefreshTokenType, refreshToken.Content.ToString(), new CookieOptions { httpAccessor.HttpContext?.Response.Cookies.Append(ITokenContext.RefreshTokenType, refreshToken.TokenId.ToString(), new CookieOptions {
MaxAge = options.Value.RefreshTokenTime, MaxAge = options.Value.RefreshTokenTime,
HttpOnly = true, HttpOnly = true,
Secure = true Secure = true
}); });
httpAccessor.HttpContext?.Response.Cookies.Append(ITokenContext.AccessTokenType, accessToken.Content.ToString(), new CookieOptions { httpAccessor.HttpContext?.Response.Cookies.Append(ITokenContext.AccessTokenType, accessToken.TokenId.ToString(), new CookieOptions {
MaxAge = options.Value.AccessTokenTime, MaxAge = options.Value.AccessTokenTime,
HttpOnly = false, HttpOnly = false,
Secure = true Secure = true
@@ -83,7 +83,7 @@ internal class AuthService(
var accessToken = await tokens.CreateToken(Token.AccessTokenType, token.Owner); var accessToken = await tokens.CreateToken(Token.AccessTokenType, token.Owner);
httpAccessor.HttpContext?.Response.Cookies.Append(ITokenContext.AccessTokenType, accessToken.Content.ToString(), new CookieOptions { httpAccessor.HttpContext?.Response.Cookies.Append(ITokenContext.AccessTokenType, accessToken.TokenId.ToString(), new CookieOptions {
MaxAge = options.Value.AccessTokenTime, MaxAge = options.Value.AccessTokenTime,
HttpOnly = false, HttpOnly = false,
Secure = true Secure = true

View File

@@ -1,5 +1,7 @@
using HopFrame.Api.Logic; using HopFrame.Api.Logic;
using HopFrame.Api.Models;
using HopFrame.Database.Models; using HopFrame.Database.Models;
using HopFrame.Database.Repositories;
using HopFrame.Security.Authorization; using HopFrame.Security.Authorization;
using HopFrame.Security.Claims; using HopFrame.Security.Claims;
using HopFrame.Testing.Api.Models; using HopFrame.Testing.Api.Models;
@@ -10,11 +12,11 @@ namespace HopFrame.Testing.Api.Controllers;
[ApiController] [ApiController]
[Route("test")] [Route("test")]
public class TestController(ITokenContext userContext, DatabaseContext context) : ControllerBase { public class TestController(ITokenContext userContext, DatabaseContext context, ITokenRepository tokens, IPermissionRepository permissions) : ControllerBase {
[HttpGet("permissions"), Authorized] [HttpGet("permissions"), Authorized]
public ActionResult<IList<Permission>> Permissions() { public ActionResult<IList<string>> Permissions() {
return new ActionResult<IList<Permission>>(userContext.User.Permissions); return new ActionResult<IList<string>>(userContext.ContextualPermissions);
} }
[HttpGet("generate")] [HttpGet("generate")]
@@ -51,4 +53,18 @@ public class TestController(ITokenContext userContext, DatabaseContext context)
return LogicResult<IList<Address>>.Ok(await context.Addresses.Include(e => e.Employee).ToListAsync()); return LogicResult<IList<Address>>.Ok(await context.Addresses.Include(e => e.Employee).ToListAsync());
} }
[HttpGet("token"), Authorized]
public async Task<ActionResult<SingleValueResult<string>>> GetApiToken() {
var token = await tokens.CreateApiToken(userContext.User, DateTime.MaxValue);
await permissions.AddPermission(token, "hopframe.admin");
await permissions.AddPermission(token, "hopframe.admin.users.read");
return LogicResult<SingleValueResult<string>>.Ok(token.TokenId.ToString());
}
[HttpDelete("token/{tokenId}")]
public async Task DeleteToken(string tokenId) {
var token = await tokens.GetToken(tokenId);
await tokens.DeleteToken(token);
}
} }

View File

@@ -18,7 +18,7 @@ builder.Services.AddSwaggerGen(c => {
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme { c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme {
Description = @"JWT Authorization header using the Bearer scheme. \r\n\r\n Description = @"JWT Authorization header using the Bearer scheme. \r\n\r\n
Enter 'Bearer' [space] and then your token in the text input below.", Enter 'Bearer' [space] and then your token in the text input below.",
Name = "Authorization", Name = "Token",
In = ParameterLocation.Header, In = ParameterLocation.Header,
Type = SecuritySchemeType.ApiKey, Type = SecuritySchemeType.ApiKey,
Scheme = "Bearer" Scheme = "Bearer"

View File

@@ -58,13 +58,13 @@ public class AuthLogicTests {
tokens tokens
.Setup(t => t.CreateToken(It.Is<int>(t => t == Token.RefreshTokenType), It.IsAny<User>())) .Setup(t => t.CreateToken(It.Is<int>(t => t == Token.RefreshTokenType), It.IsAny<User>()))
.ReturnsAsync(new Token { .ReturnsAsync(new Token {
Content = _refreshToken, TokenId = _refreshToken,
Type = Token.RefreshTokenType Type = Token.RefreshTokenType
}); });
tokens tokens
.Setup(t => t.CreateToken(It.Is<int>(t => t == Token.AccessTokenType), It.IsAny<User>())) .Setup(t => t.CreateToken(It.Is<int>(t => t == Token.AccessTokenType), It.IsAny<User>()))
.ReturnsAsync(new Token { .ReturnsAsync(new Token {
Content = _accessToken, TokenId = _accessToken,
Type = Token.AccessTokenType Type = Token.AccessTokenType
}); });
tokens tokens
@@ -229,11 +229,11 @@ public class AuthLogicTests {
// Arrange // Arrange
var token = new Token { var token = new Token {
Type = Token.RefreshTokenType, Type = Token.RefreshTokenType,
Content = _refreshToken, TokenId = _refreshToken,
CreatedAt = DateTime.Now, CreatedAt = DateTime.Now,
Owner = CreateDummyUser() Owner = CreateDummyUser()
}; };
var (auth, context) = SetupEnvironment(true, token, token.Content.ToString()); var (auth, context) = SetupEnvironment(true, token, token.TokenId.ToString());
// Act // Act
var result = await auth.Authenticate(); var result = await auth.Authenticate();
@@ -277,11 +277,11 @@ public class AuthLogicTests {
// Arrange // Arrange
var token = new Token { var token = new Token {
Type = Token.AccessTokenType, Type = Token.AccessTokenType,
Content = _refreshToken, TokenId = _refreshToken,
CreatedAt = DateTime.Now, CreatedAt = DateTime.Now,
Owner = CreateDummyUser() Owner = CreateDummyUser()
}; };
var (auth, context) = SetupEnvironment(true, token, token.Content.ToString()); var (auth, context) = SetupEnvironment(true, token, token.TokenId.ToString());
// Act // Act
var result = await auth.Authenticate(); var result = await auth.Authenticate();
@@ -297,11 +297,11 @@ public class AuthLogicTests {
// Arrange // Arrange
var token = new Token { var token = new Token {
Type = Token.RefreshTokenType, Type = Token.RefreshTokenType,
Content = _refreshToken, TokenId = _refreshToken,
CreatedAt = DateTime.MinValue, CreatedAt = DateTime.MinValue,
Owner = CreateDummyUser() Owner = CreateDummyUser()
}; };
var (auth, context) = SetupEnvironment(true, token, token.Content.ToString()); var (auth, context) = SetupEnvironment(true, token, token.TokenId.ToString());
// Act // Act
var result = await auth.Authenticate(); var result = await auth.Authenticate();

View File

@@ -14,7 +14,7 @@ public class TokenRepositoryTests {
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
await context.Tokens.AddAsync(new() { await context.Tokens.AddAsync(new() {
Content = Guid.NewGuid(), TokenId = Guid.NewGuid(),
Owner = CreateTestUser(), Owner = CreateTestUser(),
Type = Token.AccessTokenType Type = Token.AccessTokenType
}); });
@@ -37,7 +37,7 @@ public class TokenRepositoryTests {
var token = context.Tokens.First(); var token = context.Tokens.First();
// Act // Act
var result = await repo.GetToken(token.Content.ToString()); var result = await repo.GetToken(token.TokenId.ToString());
// Assert // Assert
Assert.Equal(token, result); Assert.Equal(token, result);
@@ -64,12 +64,12 @@ public class TokenRepositoryTests {
var user = CreateTestUser(); var user = CreateTestUser();
await context.Tokens.AddRangeAsync(new List<Token> { await context.Tokens.AddRangeAsync(new List<Token> {
new() { new() {
Content = Guid.NewGuid(), TokenId = Guid.NewGuid(),
Owner = user, Owner = user,
Type = Token.AccessTokenType Type = Token.AccessTokenType
}, },
new() { new() {
Content = Guid.NewGuid(), TokenId = Guid.NewGuid(),
Owner = user, Owner = user,
Type = Token.RefreshTokenType Type = Token.RefreshTokenType
} }

View File

@@ -30,7 +30,7 @@ public class AuthenticationTests {
var provideCorrectToken = correctToken is null; var provideCorrectToken = correctToken is null;
correctToken ??= new Token { correctToken ??= new Token {
Content = Guid.NewGuid(), TokenId = Guid.NewGuid(),
CreatedAt = DateTime.Now, CreatedAt = DateTime.Now,
Type = Token.AccessTokenType, Type = Token.AccessTokenType,
Owner = new User { Owner = new User {
@@ -39,17 +39,17 @@ public class AuthenticationTests {
}; };
tokens tokens
.Setup(x => x.GetToken(It.Is<string>(t => t == correctToken.Content.ToString()))) .Setup(x => x.GetToken(It.Is<string>(t => t == correctToken.TokenId.ToString())))
.ReturnsAsync(correctToken); .ReturnsAsync(correctToken);
perms perms
.Setup(x => x.GetFullPermissions(It.IsAny<User>())) .Setup(x => x.GetFullPermissions(It.IsAny<Token>()))
.ReturnsAsync(new List<string>()); .ReturnsAsync(new List<string>());
var auth = new HopFrameAuthentication(options.Object, logger.Object, encoder.Object, clock.Object, tokens.Object, perms.Object, new OptionsWrapper<HopFrameAuthenticationOptions>(new HopFrameAuthenticationOptions())); var auth = new HopFrameAuthentication(options.Object, logger.Object, encoder.Object, clock.Object, tokens.Object, perms.Object, new OptionsWrapper<HopFrameAuthenticationOptions>(new HopFrameAuthenticationOptions()));
var context = new DefaultHttpContext(); var context = new DefaultHttpContext();
if (provideCorrectToken) if (provideCorrectToken)
context.HttpContext.Request.Headers.Append(HopFrameAuthentication.SchemeName, correctToken.Content.ToString()); context.HttpContext.Request.Headers.Append(HopFrameAuthentication.SchemeName, correctToken.TokenId.ToString());
if (providedToken is not null) if (providedToken is not null)
context.HttpContext.Request.Headers.Append(HopFrameAuthentication.SchemeName, providedToken); context.HttpContext.Request.Headers.Append(HopFrameAuthentication.SchemeName, providedToken);
@@ -101,12 +101,12 @@ public class AuthenticationTests {
public async Task Authentication_With_ExpiredToken_Should_Fail() { public async Task Authentication_With_ExpiredToken_Should_Fail() {
// Arrange // Arrange
var token = new Token { var token = new Token {
Content = Guid.NewGuid(), TokenId = Guid.NewGuid(),
CreatedAt = DateTime.MinValue, CreatedAt = DateTime.MinValue,
Type = Token.AccessTokenType, Type = Token.AccessTokenType,
Owner = new User() Owner = new User()
}; };
var auth = await SetupEnvironment(token, token.Content.ToString()); var auth = await SetupEnvironment(token, token.TokenId.ToString());
// Act // Act
var result = await auth.AuthenticateAsync(); var result = await auth.AuthenticateAsync();
@@ -121,12 +121,12 @@ public class AuthenticationTests {
public async Task Authentication_With_UnownedToken_Should_Fail() { public async Task Authentication_With_UnownedToken_Should_Fail() {
// Arrange // Arrange
var token = new Token { var token = new Token {
Content = Guid.NewGuid(), TokenId = Guid.NewGuid(),
CreatedAt = DateTime.Now, CreatedAt = DateTime.Now,
Type = Token.AccessTokenType, Type = Token.AccessTokenType,
Owner = null Owner = null
}; };
var auth = await SetupEnvironment(token, token.Content.ToString()); var auth = await SetupEnvironment(token, token.TokenId.ToString());
// Act // Act
var result = await auth.AuthenticateAsync(); var result = await auth.AuthenticateAsync();

View File

@@ -23,7 +23,7 @@ public class AuthMiddlewareTests {
var perms = new Mock<IPermissionRepository>(); var perms = new Mock<IPermissionRepository>();
perms perms
.Setup(p => p.GetFullPermissions(It.Is<User>(u => newToken.Owner.Id == u.Id))) .Setup(p => p.GetFullPermissions(It.Is<Token>(u => newToken.Owner.Id == u.Owner.Id)))
.ReturnsAsync(CreateDummyUser().Permissions.Select(p => p.PermissionName).ToList); .ReturnsAsync(CreateDummyUser().Permissions.Select(p => p.PermissionName).ToList);
return new AuthMiddleware(auth.Object, perms.Object); return new AuthMiddleware(auth.Object, perms.Object);
@@ -61,7 +61,7 @@ public class AuthMiddlewareTests {
public async Task InvokeAsync_With_InvalidLoginValidToken_Should_Succeed() { public async Task InvokeAsync_With_InvalidLoginValidToken_Should_Succeed() {
// Arrange // Arrange
var token = new Token { var token = new Token {
Content = Guid.NewGuid(), TokenId = Guid.NewGuid(),
CreatedAt = DateTime.Now, CreatedAt = DateTime.Now,
Type = Token.AccessTokenType, Type = Token.AccessTokenType,
Owner = CreateDummyUser() Owner = CreateDummyUser()
@@ -74,7 +74,7 @@ public class AuthMiddlewareTests {
// Assert // Assert
Assert.Equal(token.Owner.Id.ToString(), context.User.FindFirstValue(HopFrameClaimTypes.UserId)); Assert.Equal(token.Owner.Id.ToString(), context.User.FindFirstValue(HopFrameClaimTypes.UserId));
Assert.Equal(token.Content.ToString(), context.User.FindFirstValue(HopFrameClaimTypes.AccessTokenId)); Assert.Equal(token.TokenId.ToString(), context.User.FindFirstValue(HopFrameClaimTypes.AccessTokenId));
Assert.Equal(token.Owner.Permissions.First().PermissionName, context.User.FindFirstValue(HopFrameClaimTypes.Permission)); Assert.Equal(token.Owner.Permissions.First().PermissionName, context.User.FindFirstValue(HopFrameClaimTypes.Permission));
} }

View File

@@ -47,13 +47,13 @@ public class AuthServiceTests {
tokens tokens
.Setup(t => t.CreateToken(It.Is<int>(t => t == Token.RefreshTokenType), It.IsAny<User>())) .Setup(t => t.CreateToken(It.Is<int>(t => t == Token.RefreshTokenType), It.IsAny<User>()))
.ReturnsAsync(new Token { .ReturnsAsync(new Token {
Content = _refreshToken, TokenId = _refreshToken,
Type = Token.RefreshTokenType Type = Token.RefreshTokenType
}); });
tokens tokens
.Setup(t => t.CreateToken(It.Is<int>(t => t == Token.AccessTokenType), It.IsAny<User>())) .Setup(t => t.CreateToken(It.Is<int>(t => t == Token.AccessTokenType), It.IsAny<User>()))
.ReturnsAsync(new Token { .ReturnsAsync(new Token {
Content = _accessToken, TokenId = _accessToken,
Type = Token.AccessTokenType Type = Token.AccessTokenType
}); });
tokens tokens
@@ -171,18 +171,18 @@ public class AuthServiceTests {
// Arrange // Arrange
var token = new Token { var token = new Token {
Type = Token.RefreshTokenType, Type = Token.RefreshTokenType,
Content = _refreshToken, TokenId = _refreshToken,
CreatedAt = DateTime.Now, CreatedAt = DateTime.Now,
Owner = CreateDummyUser() Owner = CreateDummyUser()
}; };
var (service, context) = SetupEnvironment(true, token, token.Content.ToString()); var (service, context) = SetupEnvironment(true, token, token.TokenId.ToString());
// Act // Act
var result = await service.RefreshLogin(); var result = await service.RefreshLogin();
// Assert // Assert
Assert.NotNull(result); Assert.NotNull(result);
Assert.Equal(_accessToken, result.Content); Assert.Equal(_accessToken, result.TokenId);
Assert.Equal(_accessToken.ToString(), context.Response.Headers.FindCookie(ITokenContext.AccessTokenType)); Assert.Equal(_accessToken.ToString(), context.Response.Headers.FindCookie(ITokenContext.AccessTokenType));
} }
@@ -217,11 +217,11 @@ public class AuthServiceTests {
// Arrange // Arrange
var token = new Token { var token = new Token {
Type = Token.AccessTokenType, Type = Token.AccessTokenType,
Content = _refreshToken, TokenId = _refreshToken,
CreatedAt = DateTime.Now, CreatedAt = DateTime.Now,
Owner = CreateDummyUser() Owner = CreateDummyUser()
}; };
var (service, context) = SetupEnvironment(true, token, token.Content.ToString()); var (service, context) = SetupEnvironment(true, token, token.TokenId.ToString());
// Act // Act
var result = await service.RefreshLogin(); var result = await service.RefreshLogin();
@@ -236,11 +236,11 @@ public class AuthServiceTests {
// Arrange // Arrange
var token = new Token { var token = new Token {
Type = Token.RefreshTokenType, Type = Token.RefreshTokenType,
Content = _refreshToken, TokenId = _refreshToken,
CreatedAt = DateTime.MinValue, CreatedAt = DateTime.MinValue,
Owner = CreateDummyUser() Owner = CreateDummyUser()
}; };
var (service, context) = SetupEnvironment(true, token, token.Content.ToString()); var (service, context) = SetupEnvironment(true, token, token.TokenId.ToString());
// Act // Act
var result = await service.RefreshLogin(); var result = await service.RefreshLogin();
@@ -255,7 +255,7 @@ public class AuthServiceTests {
// Arrange // Arrange
var token = new Token { var token = new Token {
Type = Token.AccessTokenType, Type = Token.AccessTokenType,
Content = _accessToken, TokenId = _accessToken,
CreatedAt = DateTime.Now, CreatedAt = DateTime.Now,
Owner = CreateDummyUser() Owner = CreateDummyUser()
}; };
@@ -285,7 +285,7 @@ public class AuthServiceTests {
// Arrange // Arrange
var token = new Token { var token = new Token {
Type = Token.RefreshTokenType, Type = Token.RefreshTokenType,
Content = _accessToken, TokenId = _accessToken,
CreatedAt = DateTime.Now, CreatedAt = DateTime.Now,
Owner = CreateDummyUser() Owner = CreateDummyUser()
}; };
@@ -303,7 +303,7 @@ public class AuthServiceTests {
// Arrange // Arrange
var token = new Token { var token = new Token {
Type = Token.AccessTokenType, Type = Token.AccessTokenType,
Content = _accessToken, TokenId = _accessToken,
CreatedAt = DateTime.MinValue, CreatedAt = DateTime.MinValue,
Owner = CreateDummyUser() Owner = CreateDummyUser()
}; };
@@ -321,7 +321,7 @@ public class AuthServiceTests {
// Arrange // Arrange
var token = new Token { var token = new Token {
Type = Token.AccessTokenType, Type = Token.AccessTokenType,
Content = _accessToken, TokenId = _accessToken,
CreatedAt = DateTime.Now, CreatedAt = DateTime.Now,
Owner = null Owner = null
}; };