Resolve "API tokens" #41
14
.idea/.idea.HopFrame/.idea/discord.xml
generated
Normal file
14
.idea/.idea.HopFrame/.idea/discord.xml
generated
Normal 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>
|
||||||
@@ -70,6 +70,7 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</wpf:ResourceDictionary>
|
</wpf:ResourceDictionary>
|
||||||
@@ -6,5 +6,6 @@ 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);
|
||||||
|
Task DeleteToken(Token token);
|
||||||
Task<Token> CreateApiToken(User owner, DateTime expirationDate);
|
Task<Token> CreateApiToken(User owner, DateTime expirationDate);
|
||||||
}
|
}
|
||||||
@@ -70,6 +70,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)
|
||||||
@@ -86,11 +90,11 @@ internal sealed class PermissionRepository<TDbContext>(TDbContext context, IGrou
|
|||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
permissions.AddRange(perms.Select(p => p.PermissionName));
|
permissions.AddRange(perms.Select(p => p.PermissionName));
|
||||||
}else if (owner is Token token) {
|
}else if (owner is Token apiToken) {
|
||||||
var perms = await context.Permissions
|
var perms = await context.Permissions
|
||||||
.Include(p => p.Token)
|
.Include(p => p.Token)
|
||||||
.Where(p => p.Token != null)
|
.Where(p => p.Token != null)
|
||||||
.Where(p =>p.Token.TokenId == token.TokenId)
|
.Where(p =>p.Token.TokenId == apiToken.TokenId)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
permissions.AddRange(perms.Select(p => p.PermissionName));
|
permissions.AddRange(perms.Select(p => p.PermissionName));
|
||||||
|
|||||||
@@ -39,6 +39,11 @@ internal sealed class TokenRepository<TDbContext>(TDbContext context) : ITokenRe
|
|||||||
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) {
|
public async Task<Token> CreateApiToken(User owner, DateTime expirationDate) {
|
||||||
var token = new Token {
|
var token = new Token {
|
||||||
CreatedAt = expirationDate,
|
CreatedAt = expirationDate,
|
||||||
|
|||||||
@@ -47,15 +47,7 @@ public class HopFrameAuthentication(
|
|||||||
new(HopFrameClaimTypes.UserId, tokenEntry.Owner.Id.ToString())
|
new(HopFrameClaimTypes.UserId, tokenEntry.Owner.Id.ToString())
|
||||||
};
|
};
|
||||||
|
|
||||||
IList<string> permissions;
|
var permissions = await perms.GetFullPermissions(tokenEntry);
|
||||||
|
|
||||||
if (tokenEntry.Type == Token.ApiTokenType) {
|
|
||||||
permissions = await perms.GetFullPermissions(tokenEntry);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
permissions = await perms.GetFullPermissions(tokenEntry.Owner);
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
||||||
|
|||||||
@@ -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; }
|
||||||
}
|
}
|
||||||
@@ -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();
|
||||||
}
|
}
|
||||||
@@ -26,7 +26,7 @@ public sealed class AuthMiddleware(IAuthService auth, IPermissionRepository perm
|
|||||||
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));
|
||||||
|
|||||||
@@ -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!",
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user