diff --git a/API/Controller/Account/Authenticated/_ApiController.cs b/API/Controller/Account/Authenticated/_ApiController.cs index aa547a7f..80e53e29 100644 --- a/API/Controller/Account/Authenticated/_ApiController.cs +++ b/API/Controller/Account/Authenticated/_ApiController.cs @@ -1,11 +1,9 @@ using Asp.Versioning; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using OpenShock.API.Services.Account; -using OpenShock.Common.Authentication.Attributes; +using OpenShock.Common.Authentication; using OpenShock.Common.Authentication.ControllerBase; -using OpenShock.Common.OpenShockDb; -using OpenShock.Common.Services.Session; -using Redis.OM.Contracts; namespace OpenShock.API.Controller.Account.Authenticated; @@ -13,28 +11,20 @@ namespace OpenShock.API.Controller.Account.Authenticated; /// User account management /// [ApiController] -[UserSessionOnly] [ApiVersion("1")] [Route("/{version:apiVersion}/account")] +[Authorize(AuthenticationSchemes = OpenShockAuthSchemas.UserSessionCookie)] public sealed partial class AuthenticatedAccountController : AuthenticatedSessionControllerBase { - private readonly OpenShockContext _db; - private readonly IRedisConnectionProvider _redis; - private readonly ILogger _logger; private readonly IAccountService _accountService; - private readonly ISessionService _sessionService; + private readonly ILogger _logger; public AuthenticatedAccountController( - OpenShockContext db, - IRedisConnectionProvider redis, - ILogger logger, IAccountService accountService, - ISessionService sessionService) + ILogger logger + ) { - _db = db; - _redis = redis; - _logger = logger; _accountService = accountService; - _sessionService = sessionService; + _logger = logger; } } \ No newline at end of file diff --git a/API/Controller/Account/Logout.cs b/API/Controller/Account/Logout.cs index 80cf5b57..bf998036 100644 --- a/API/Controller/Account/Logout.cs +++ b/API/Controller/Account/Logout.cs @@ -16,9 +16,9 @@ public async Task Logout( [FromServices] ApiConfig apiConfig) { // Remove session if valid - if (HttpContext.TryGetSessionKey(out var sessionKey)) + if (HttpContext.TryGetUserSessionCookie(out var sessionCookie)) { - await sessionService.DeleteSessionById(sessionKey); + await sessionService.DeleteSessionById(sessionCookie); } // Make sure cookie is removed, no matter if authenticated or not diff --git a/API/Controller/Account/_ApiController.cs b/API/Controller/Account/_ApiController.cs index 47952a8c..3b7f4a99 100644 --- a/API/Controller/Account/_ApiController.cs +++ b/API/Controller/Account/_ApiController.cs @@ -12,22 +12,17 @@ namespace OpenShock.API.Controller.Account; /// User account management /// [ApiController] -[AllowAnonymous] [ApiVersion("1")] [ApiVersion("2")] [Route("/{version:apiVersion}/account")] public sealed partial class AccountController : OpenShockControllerBase { - private readonly OpenShockContext _db; - private readonly IRedisConnectionProvider _redis; - private readonly ILogger _logger; private readonly IAccountService _accountService; + private readonly ILogger _logger; - public AccountController(OpenShockContext db, IRedisConnectionProvider redis, ILogger logger, IAccountService accountService) + public AccountController(IAccountService accountService, ILogger logger) { - _db = db; - _redis = redis; - _logger = logger; _accountService = accountService; + _logger = logger; } } \ No newline at end of file diff --git a/API/Controller/Admin/_ApiController.cs b/API/Controller/Admin/_ApiController.cs index 1dc4ce8b..ac6c33fe 100644 --- a/API/Controller/Admin/_ApiController.cs +++ b/API/Controller/Admin/_ApiController.cs @@ -1,16 +1,15 @@ -using Microsoft.AspNetCore.Mvc; -using OpenShock.Common.Authentication.Attributes; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using OpenShock.Common.Authentication; using OpenShock.Common.Authentication.ControllerBase; -using OpenShock.Common.Models; using OpenShock.Common.OpenShockDb; using Redis.OM.Contracts; namespace OpenShock.API.Controller.Admin; [ApiController] -[Rank(RankType.Admin)] -[UserSessionOnly] [Route("/{version:apiVersion}/admin")] +[Authorize(AuthenticationSchemes = OpenShockAuthSchemas.UserSessionCookie, Policy = OpenShockAuthPolicies.AdminAccess)] public sealed partial class AdminController : AuthenticatedSessionControllerBase { private readonly OpenShockContext _db; diff --git a/API/Controller/Device/_ApiController.cs b/API/Controller/Device/_ApiController.cs index a0634d9c..ab3baa1f 100644 --- a/API/Controller/Device/_ApiController.cs +++ b/API/Controller/Device/_ApiController.cs @@ -1,4 +1,6 @@ -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using OpenShock.Common.Authentication; using OpenShock.Common.Authentication.ControllerBase; using OpenShock.Common.OpenShockDb; using Redis.OM.Contracts; @@ -10,7 +12,8 @@ namespace OpenShock.API.Controller.Device; /// [ApiController] [Route("/{version:apiVersion}/device")] -public sealed partial class DeviceController : AuthenticatedDeviceControllerBase +[Authorize(AuthenticationSchemes = OpenShockAuthSchemas.HubToken)] +public sealed partial class DeviceController : AuthenticatedHubControllerBase { private readonly OpenShockContext _db; private readonly IRedisConnectionProvider _redis; diff --git a/API/Controller/Devices/DeviceOtaController.cs b/API/Controller/Devices/DeviceOtaController.cs index 09a48af3..1d2c983c 100644 --- a/API/Controller/Devices/DeviceOtaController.cs +++ b/API/Controller/Devices/DeviceOtaController.cs @@ -1,8 +1,10 @@ using System.Net; using System.Net.Mime; using Asp.Versioning; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; +using OpenShock.Common.Authentication; using OpenShock.Common.Authentication.Attributes; using OpenShock.Common.Errors; using OpenShock.Common.Models; @@ -22,10 +24,10 @@ public sealed partial class DevicesController /// OK /// Could not find device or you do not have access to it [HttpGet("{deviceId}/ota")] - [UserSessionOnly] + [MapToApiVersion("1")] + [Authorize(Policy = OpenShockAuthPolicies.UserAccess)] [ProducesResponseType>>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // DeviceNotFound - [MapToApiVersion("1")] public async Task GetOtaUpdateHistory([FromRoute] Guid deviceId, [FromServices] IOtaService otaService) { // Check if user owns device or has a share diff --git a/API/Controller/Devices/_ApiController.cs b/API/Controller/Devices/_ApiController.cs index b002b88b..75137a4a 100644 --- a/API/Controller/Devices/_ApiController.cs +++ b/API/Controller/Devices/_ApiController.cs @@ -1,5 +1,7 @@ using Asp.Versioning; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using OpenShock.Common.Authentication; using OpenShock.Common.Authentication.ControllerBase; using OpenShock.Common.OpenShockDb; using Redis.OM.Contracts; @@ -10,9 +12,10 @@ namespace OpenShock.API.Controller.Devices; /// Device management /// [ApiController] -[Route("/{version:apiVersion}/devices")] [ApiVersion("1")] [ApiVersion("2")] +[Route("/{version:apiVersion}/devices")] +[Authorize(AuthenticationSchemes = OpenShockAuthSchemas.UserSessionApiTokenCombo)] public sealed partial class DevicesController : AuthenticatedSessionControllerBase { private readonly OpenShockContext _db; diff --git a/API/Controller/Public/_ApiController.cs b/API/Controller/Public/_ApiController.cs index d7c11986..67d312b8 100644 --- a/API/Controller/Public/_ApiController.cs +++ b/API/Controller/Public/_ApiController.cs @@ -8,7 +8,6 @@ namespace OpenShock.API.Controller.Public; [ApiController] [Route("/{version:apiVersion}/public")] -[AllowAnonymous] public sealed partial class PublicController : OpenShockControllerBase { private readonly OpenShockContext _db; diff --git a/API/Controller/Sessions/SessionSelf.cs b/API/Controller/Sessions/SessionSelf.cs index e79d57be..1e621f81 100644 --- a/API/Controller/Sessions/SessionSelf.cs +++ b/API/Controller/Sessions/SessionSelf.cs @@ -1,6 +1,8 @@ using System.Net.Mime; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using OpenShock.API.Models.Response; +using OpenShock.Common.Authentication; using OpenShock.Common.Authentication.Attributes; using OpenShock.Common.Authentication.Services; using OpenShock.Common.Problems; @@ -16,7 +18,6 @@ public sealed partial class SessionsController /// /// [HttpGet("self")] - [UserSessionOnly] [ProducesResponseType(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] public LoginSessionResponse GetSelfSession([FromServices] IUserReferenceService userReferenceService) { diff --git a/API/Controller/Sessions/_ApiController.cs b/API/Controller/Sessions/_ApiController.cs index af766dad..1d8cf3d0 100644 --- a/API/Controller/Sessions/_ApiController.cs +++ b/API/Controller/Sessions/_ApiController.cs @@ -1,5 +1,7 @@ using Asp.Versioning; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using OpenShock.Common.Authentication; using OpenShock.Common.Authentication.Attributes; using OpenShock.Common.Authentication.ControllerBase; using OpenShock.Common.Services.Session; @@ -10,9 +12,9 @@ namespace OpenShock.API.Controller.Sessions; /// Session management /// [ApiController] -[UserSessionOnly] [ApiVersion("1")] [Route("/{version:apiVersion}/sessions")] +[Authorize(AuthenticationSchemes = OpenShockAuthSchemas.UserSessionCookie)] public sealed partial class SessionsController : AuthenticatedSessionControllerBase { private readonly ISessionService _sessionService; diff --git a/API/Controller/Shares/Links/_ApiController.cs b/API/Controller/Shares/Links/_ApiController.cs index a4865dd3..60ebed9f 100644 --- a/API/Controller/Shares/Links/_ApiController.cs +++ b/API/Controller/Shares/Links/_ApiController.cs @@ -1,4 +1,6 @@ -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using OpenShock.Common.Authentication; using OpenShock.Common.Authentication.ControllerBase; using OpenShock.Common.OpenShockDb; @@ -9,6 +11,7 @@ namespace OpenShock.API.Controller.Shares.Links; /// [ApiController] [Route("/{version:apiVersion}/shares/links")] +[Authorize(AuthenticationSchemes = OpenShockAuthSchemas.UserSessionApiTokenCombo)] public sealed partial class ShareLinksController : AuthenticatedSessionControllerBase { private readonly OpenShockContext _db; diff --git a/API/Controller/Shares/_ApiController.cs b/API/Controller/Shares/_ApiController.cs index 192e7aa0..5b515222 100644 --- a/API/Controller/Shares/_ApiController.cs +++ b/API/Controller/Shares/_ApiController.cs @@ -1,5 +1,7 @@ using Asp.Versioning; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using OpenShock.Common.Authentication; using OpenShock.Common.Authentication.ControllerBase; using OpenShock.Common.OpenShockDb; @@ -9,9 +11,10 @@ namespace OpenShock.API.Controller.Shares; /// Shocker share management /// [ApiController] -[Route("/{version:apiVersion}/shares")] [ApiVersion("1")] [ApiVersion("2")] +[Route("/{version:apiVersion}/shares")] +[Authorize(AuthenticationSchemes = OpenShockAuthSchemas.UserSessionApiTokenCombo)] public sealed partial class SharesController : AuthenticatedSessionControllerBase { private readonly OpenShockContext _db; diff --git a/API/Controller/Shockers/_ApiController.cs b/API/Controller/Shockers/_ApiController.cs index 9ee4129c..c07746a8 100644 --- a/API/Controller/Shockers/_ApiController.cs +++ b/API/Controller/Shockers/_ApiController.cs @@ -1,5 +1,7 @@ using Asp.Versioning; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using OpenShock.Common.Authentication; using OpenShock.Common.Authentication.ControllerBase; using OpenShock.Common.OpenShockDb; @@ -12,6 +14,7 @@ namespace OpenShock.API.Controller.Shockers; [ApiVersion("1")] [ApiVersion("2")] [Route("/{version:apiVersion}/shockers")] +[Authorize(AuthenticationSchemes = OpenShockAuthSchemas.UserSessionApiTokenCombo)] public sealed partial class ShockerController : AuthenticatedSessionControllerBase { private readonly OpenShockContext _db; diff --git a/API/Controller/Tokens/TokenController.cs b/API/Controller/Tokens/TokenController.cs index ace7625c..df433de5 100644 --- a/API/Controller/Tokens/TokenController.cs +++ b/API/Controller/Tokens/TokenController.cs @@ -1,11 +1,13 @@ using System.ComponentModel.DataAnnotations; using System.Net; using System.Net.Mime; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using OpenShock.API.Models.Response; using OpenShock.API.Utils; using OpenShock.Common; +using OpenShock.Common.Authentication; using OpenShock.Common.Authentication.Attributes; using OpenShock.Common.Constants; using OpenShock.Common.Errors; @@ -23,7 +25,7 @@ public sealed partial class TokensController /// /// All tokens for the current user [HttpGet] - [UserSessionOnly] + [Authorize(Policy = OpenShockAuthPolicies.UserAccess)] [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] public async Task> ListTokens() { @@ -50,7 +52,7 @@ public async Task> ListTokens() /// The token /// The token does not exist or you do not have access to it. [HttpGet("{tokenId}")] - [UserSessionOnly] + [Authorize(Policy = OpenShockAuthPolicies.UserAccess)] [ProducesResponseType(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // ApiTokenNotFound public async Task GetTokenById([FromRoute] Guid tokenId) @@ -79,7 +81,7 @@ public async Task GetTokenById([FromRoute] Guid tokenId) /// Successfully deleted token /// The token does not exist or you do not have access to it. [HttpDelete("{tokenId}")] - [UserSessionOnly] + [Authorize(Policy = OpenShockAuthPolicies.UserAccess)] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // ApiTokenNotFound public async Task DeleteToken([FromRoute] Guid tokenId) @@ -103,7 +105,7 @@ public async Task DeleteToken([FromRoute] Guid tokenId) /// /// The created token [HttpPost] - [UserSessionOnly] + [Authorize(Policy = OpenShockAuthPolicies.UserAccess)] [ProducesResponseType(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] public async Task CreateToken([FromBody] CreateTokenRequest body) { @@ -137,7 +139,7 @@ public async Task CreateToken([FromBody] CreateTokenReques /// The edited token /// The token does not exist or you do not have access to it. [HttpPatch("{tokenId}")] - [UserSessionOnly] + [Authorize(Policy = OpenShockAuthPolicies.UserAccess)] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // ApiTokenNotFound public async Task EditToken([FromRoute] Guid tokenId, [FromBody] EditTokenRequest body) diff --git a/API/Controller/Tokens/TokenSelfController.cs b/API/Controller/Tokens/TokenSelfController.cs index b0072cd8..587e3d8f 100644 --- a/API/Controller/Tokens/TokenSelfController.cs +++ b/API/Controller/Tokens/TokenSelfController.cs @@ -1,6 +1,8 @@ using System.Net.Mime; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using OpenShock.API.Models.Response; +using OpenShock.Common.Authentication; using OpenShock.Common.Authentication.Attributes; using OpenShock.Common.Authentication.Services; using OpenShock.Common.OpenShockDb; @@ -17,7 +19,7 @@ public sealed partial class TokensController /// /// [HttpGet("self")] - [TokenOnly] + [Authorize(Policy = OpenShockAuthPolicies.TokenSessionOnly)] [ProducesResponseType(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] public TokenResponse GetSelfToken([FromServices] IUserReferenceService userReferenceService) { diff --git a/API/Controller/Tokens/_ApiController.cs b/API/Controller/Tokens/_ApiController.cs index 16d36701..c7b33cfe 100644 --- a/API/Controller/Tokens/_ApiController.cs +++ b/API/Controller/Tokens/_ApiController.cs @@ -1,4 +1,6 @@ -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using OpenShock.Common.Authentication; using OpenShock.Common.Authentication.ControllerBase; using OpenShock.Common.OpenShockDb; using Redis.OM.Contracts; @@ -7,6 +9,7 @@ namespace OpenShock.API.Controller.Tokens; [ApiController] [Route("/{version:apiVersion}/tokens")] +[Authorize(AuthenticationSchemes = OpenShockAuthSchemas.UserSessionApiTokenCombo)] public sealed partial class TokensController : AuthenticatedSessionControllerBase { private readonly OpenShockContext _db; diff --git a/API/Controller/Users/_ApiController.cs b/API/Controller/Users/_ApiController.cs index e0e179fd..c96aadfa 100644 --- a/API/Controller/Users/_ApiController.cs +++ b/API/Controller/Users/_ApiController.cs @@ -1,4 +1,6 @@ -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using OpenShock.Common.Authentication; using OpenShock.Common.Authentication.ControllerBase; using OpenShock.Common.OpenShockDb; using Redis.OM.Contracts; @@ -7,6 +9,7 @@ namespace OpenShock.API.Controller.Users; [ApiController] [Route("/{version:apiVersion}/users")] +[Authorize(AuthenticationSchemes = OpenShockAuthSchemas.UserSessionApiTokenCombo)] public sealed partial class UsersController : AuthenticatedSessionControllerBase { private readonly OpenShockContext _db; diff --git a/API/Controller/Version/_ApiController.cs b/API/Controller/Version/_ApiController.cs index 8210d3dc..f2305e68 100644 --- a/API/Controller/Version/_ApiController.cs +++ b/API/Controller/Version/_ApiController.cs @@ -15,7 +15,6 @@ namespace OpenShock.API.Controller.Version; /// Version stuff /// [ApiController] -[AllowAnonymous] [Route("/{version:apiVersion}")] public sealed partial class VersionController : OpenShockControllerBase { diff --git a/API/Program.cs b/API/Program.cs index b24c5bc4..bdb71b66 100644 --- a/API/Program.cs +++ b/API/Program.cs @@ -15,6 +15,7 @@ using OpenShock.Common.Services.LCGNodeProvisioner; using OpenShock.Common.Services.Ota; using OpenShock.Common.Services.Turnstile; +using OpenShock.Common.Swagger; using Scalar.AspNetCore; using Serilog; diff --git a/Common/AttributeFilter.cs b/Common/AttributeFilter.cs deleted file mode 100644 index 2a39b4cc..00000000 --- a/Common/AttributeFilter.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Microsoft.OpenApi.Models; -using OpenShock.Common.DataAnnotations.Interfaces; -using Swashbuckle.AspNetCore.SwaggerGen; - -namespace OpenShock.Common; - -public sealed class AttributeFilter : ISchemaFilter, IParameterFilter, IOperationFilter -{ - public void Apply(OpenApiParameter parameter, ParameterFilterContext context) - { - foreach (var attribute in context.ParameterInfo?.GetCustomAttributes(true).OfType() ?? - Enumerable.Empty()) - attribute.Apply(parameter); - foreach (var attribute in context.PropertyInfo?.GetCustomAttributes(true).OfType() ?? - Enumerable.Empty()) - attribute.Apply(parameter); - } - - public void Apply(OpenApiSchema schema, SchemaFilterContext context) - { - foreach (var attribute in context.MemberInfo?.GetCustomAttributes(true).OfType() ?? - Enumerable.Empty()) attribute.Apply(schema); - } - - public void Apply(OpenApiOperation operation, OperationFilterContext context) - { - foreach (var attribute in context.MethodInfo?.GetCustomAttributes(true).OfType() ?? - Enumerable.Empty()) - attribute.Apply(operation); - } -} \ No newline at end of file diff --git a/Common/Authentication/Attributes/RankAttribute.cs b/Common/Authentication/Attributes/RankAttribute.cs deleted file mode 100644 index 46a62c71..00000000 --- a/Common/Authentication/Attributes/RankAttribute.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System.Net; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Filters; -using OpenShock.Common.Authentication.Services; -using OpenShock.Common.Models; - -namespace OpenShock.Common.Authentication.Attributes; - -[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] -public sealed class RankAttribute : Attribute, IAuthorizationFilter -{ - private readonly RankType _requiredRank; - - public RankAttribute(RankType requiredRank) - { - _requiredRank = requiredRank; - } - - public void OnAuthorization(AuthorizationFilterContext context) - { - var user = context.HttpContext.RequestServices.GetService>()?.CurrentClient; - if (user == null) - { - context.Result = new JsonResult(new BaseResponse - { - Message = "Error while authorizing request", - }); - context.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError; - return; - } - if(user.DbUser.Rank.IsAllowed(_requiredRank)) return; - - context.Result = new JsonResult(new BaseResponse - { - Message = $"Required rank not met. Required rank is {_requiredRank} but you only have {user.DbUser.Rank}" - }); - context.HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; - } -} \ No newline at end of file diff --git a/Common/Authentication/Attributes/TokenOnlyAttribute.cs b/Common/Authentication/Attributes/TokenOnlyAttribute.cs deleted file mode 100644 index 97b4d033..00000000 --- a/Common/Authentication/Attributes/TokenOnlyAttribute.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Microsoft.AspNetCore.Mvc.Filters; -using OpenShock.Common.Authentication.Services; -using OpenShock.Common.Errors; -using OpenShock.Common.OpenShockDb; - -namespace OpenShock.Common.Authentication.Attributes; - -[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] -public sealed class TokenOnlyAttribute : Attribute, IAuthorizationFilter -{ - public void OnAuthorization(AuthorizationFilterContext context) - { - var userReferenceService = context.HttpContext.RequestServices.GetRequiredService(); - - if (userReferenceService.AuthReference == null) - { - var error = AuthorizationError.UnknownError; - context.Result = error.ToObjectResult(context.HttpContext); - return; - } - - if (!userReferenceService.AuthReference.Value.IsT1) context.Result = AuthorizationError.TokenOnly.ToObjectResult(context.HttpContext); - } -} \ No newline at end of file diff --git a/Common/Authentication/Attributes/UserSessionOnlyAttribute.cs b/Common/Authentication/Attributes/UserSessionOnlyAttribute.cs deleted file mode 100644 index 822ed3ef..00000000 --- a/Common/Authentication/Attributes/UserSessionOnlyAttribute.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Microsoft.AspNetCore.Mvc.Filters; -using OpenShock.Common.Authentication.Services; -using OpenShock.Common.Errors; -using OpenShock.Common.OpenShockDb; - -namespace OpenShock.Common.Authentication.Attributes; - -[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] -public sealed class UserSessionOnlyAttribute : Attribute, IAuthorizationFilter -{ - public void OnAuthorization(AuthorizationFilterContext context) - { - var userReferenceService = context.HttpContext.RequestServices.GetRequiredService(); - - if (userReferenceService.AuthReference == null) - { - var error = AuthorizationError.UnknownError; - context.Result = error.ToObjectResult(context.HttpContext); - return; - } - - if (!userReferenceService.AuthReference.Value.IsT0) context.Result = AuthorizationError.UserSessionOnly.ToObjectResult(context.HttpContext); - } -} \ No newline at end of file diff --git a/Common/Authentication/LinkUser.cs b/Common/Authentication/AuthenticatedUser.cs similarity index 95% rename from Common/Authentication/LinkUser.cs rename to Common/Authentication/AuthenticatedUser.cs index 365af977..f6df4041 100644 --- a/Common/Authentication/LinkUser.cs +++ b/Common/Authentication/AuthenticatedUser.cs @@ -4,7 +4,7 @@ namespace OpenShock.Common.Authentication; -public sealed class LinkUser +public sealed class AuthenticatedUser { public required User DbUser { get; set; } diff --git a/Common/Authentication/AuthenticationHandlers/ApiTokenAuthentication.cs b/Common/Authentication/AuthenticationHandlers/ApiTokenAuthentication.cs new file mode 100644 index 00000000..22d8c992 --- /dev/null +++ b/Common/Authentication/AuthenticationHandlers/ApiTokenAuthentication.cs @@ -0,0 +1,83 @@ +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Http.Json; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Options; +using OpenShock.Common.Authentication.Services; +using OpenShock.Common.Errors; +using OpenShock.Common.Models; +using OpenShock.Common.OpenShockDb; +using OpenShock.Common.Problems; +using OpenShock.Common.Services.BatchUpdate; +using OpenShock.Common.Utils; +using System.Net.Mime; +using System.Security.Claims; +using System.Text.Encodings.Web; +using System.Text.Json; + +namespace OpenShock.Common.Authentication.AuthenticationHandlers; + +public sealed class ApiTokenAuthentication : AuthenticationHandler +{ + private readonly IClientAuthService _authService; + private readonly IUserReferenceService _userReferenceService; + private readonly IBatchUpdateService _batchUpdateService; + private readonly OpenShockContext _db; + private readonly JsonSerializerOptions _serializerOptions; + + public ApiTokenAuthentication( + IOptionsMonitor options, + ILoggerFactory logger, + UrlEncoder encoder, + IClientAuthService clientAuth, + IUserReferenceService userReferenceService, + OpenShockContext db, + IOptions jsonOptions, IBatchUpdateService batchUpdateService) + : base(options, logger, encoder) + { + _authService = clientAuth; + _userReferenceService = userReferenceService; + _db = db; + _serializerOptions = jsonOptions.Value.SerializerOptions; + _batchUpdateService = batchUpdateService; + } + + protected override async Task HandleAuthenticateAsync() + { + if (!Context.TryGetApiTokenFromHeader(out var token)) + { + return AuthenticateResult.Fail(AuthResultError.HeaderMissingOrInvalid.Title!); + } + + string tokenHash = HashingUtils.HashSha256(token); + + var tokenDto = await _db.ApiTokens.Include(x => x.User).FirstOrDefaultAsync(x => x.TokenHash == tokenHash && + (x.ValidUntil == null || x.ValidUntil >= DateTime.UtcNow)); + if (tokenDto == null) return AuthenticateResult.Fail(AuthResultError.TokenInvalid.Title!); + + _batchUpdateService.UpdateTokenLastUsed(tokenDto.Id); + _authService.CurrentClient = new AuthenticatedUser + { + DbUser = tokenDto.User + }; + _userReferenceService.AuthReference = tokenDto; + + List claims = [ + new(ClaimTypes.AuthenticationMethod, OpenShockAuthSchemas.ApiToken), + new(ClaimTypes.NameIdentifier, tokenDto.User.Id.ToString()), + new(OpenShockAuthClaims.ApiTokenId, tokenDto.Id.ToString()) + ]; + + foreach (var perm in tokenDto.Permissions) + { + claims.Add(new(OpenShockAuthClaims.ApiTokenPermission, perm.ToString())); + } + + var ident = new ClaimsIdentity(claims, nameof(ApiTokenAuthentication)); + + Context.User = new ClaimsPrincipal(ident); + + var ticket = new AuthenticationTicket(new ClaimsPrincipal(ident), Scheme.Name); + + return AuthenticateResult.Success(ticket); + } +} \ No newline at end of file diff --git a/Common/Authentication/Handlers/DeviceAuthentication.cs b/Common/Authentication/AuthenticationHandlers/HubAuthentication.cs similarity index 88% rename from Common/Authentication/Handlers/DeviceAuthentication.cs rename to Common/Authentication/AuthenticationHandlers/HubAuthentication.cs index d519d77b..886d3ede 100644 --- a/Common/Authentication/Handlers/DeviceAuthentication.cs +++ b/Common/Authentication/AuthenticationHandlers/HubAuthentication.cs @@ -12,12 +12,12 @@ using OpenShock.Common.Problems; using OpenShock.Common.Utils; -namespace OpenShock.Common.Authentication.Handlers; +namespace OpenShock.Common.Authentication.AuthenticationHandlers; /// /// Device / Box / The Thing / ESP32 authentication with DeviceToken header /// -public sealed class DeviceAuthentication : AuthenticationHandler +public sealed class HubAuthentication : AuthenticationHandler { private readonly IClientAuthService _authService; private readonly OpenShockContext _db; @@ -26,7 +26,7 @@ public sealed class DeviceAuthentication : AuthenticationHandler options, ILoggerFactory logger, UrlEncoder encoder, @@ -44,7 +44,7 @@ protected override async Task HandleAuthenticateAsync() { if (!Context.TryGetDeviceTokenFromHeader(out string? sessionKey)) { - return Fail(AuthResultError.CookieOrHeaderMissingOrInvalid); + return Fail(AuthResultError.HeaderMissingOrInvalid); } var device = await _db.Devices.Where(x => x.Token == sessionKey).FirstOrDefaultAsync(); @@ -57,7 +57,7 @@ protected override async Task HandleAuthenticateAsync() { new Claim("id", _authService.CurrentClient.Id.ToString()), }; - var ident = new ClaimsIdentity(claims, nameof(LoginSessionAuthentication)); + var ident = new ClaimsIdentity(claims, nameof(HubAuthentication)); var ticket = new AuthenticationTicket(new ClaimsPrincipal(ident), Scheme.Name); return AuthenticateResult.Success(ticket); diff --git a/Common/Authentication/AuthenticationHandlers/UserSessionAuthentication.cs b/Common/Authentication/AuthenticationHandlers/UserSessionAuthentication.cs new file mode 100644 index 00000000..5523dcdb --- /dev/null +++ b/Common/Authentication/AuthenticationHandlers/UserSessionAuthentication.cs @@ -0,0 +1,93 @@ +using System.Net.Mime; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Http.Json; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Options; +using OpenShock.Common.Authentication.Services; +using OpenShock.Common.Constants; +using OpenShock.Common.Errors; +using OpenShock.Common.OpenShockDb; +using OpenShock.Common.Problems; +using OpenShock.Common.Services.BatchUpdate; +using OpenShock.Common.Services.Session; +using OpenShock.Common.Utils; +using System.Security.Claims; +using System.Text.Encodings.Web; +using System.Text.Json; + +namespace OpenShock.Common.Authentication.AuthenticationHandlers; + +public sealed class UserSessionAuthentication : AuthenticationHandler +{ + private readonly IClientAuthService _authService; + private readonly IUserReferenceService _userReferenceService; + private readonly IBatchUpdateService _batchUpdateService; + private readonly OpenShockContext _db; + private readonly ISessionService _sessionService; + private readonly JsonSerializerOptions _serializerOptions; + + public UserSessionAuthentication( + IOptionsMonitor options, + ILoggerFactory logger, + UrlEncoder encoder, + IClientAuthService clientAuth, + IUserReferenceService userReferenceService, + OpenShockContext db, + ISessionService sessionService, + IOptions jsonOptions, IBatchUpdateService batchUpdateService) + : base(options, logger, encoder) + { + _authService = clientAuth; + _userReferenceService = userReferenceService; + _db = db; + _sessionService = sessionService; + _serializerOptions = jsonOptions.Value.SerializerOptions; + _batchUpdateService = batchUpdateService; + } + + protected override async Task HandleAuthenticateAsync() + { + if (!Context.TryGetUserSessionCookie(out var sessionKey)) + { + return AuthenticateResult.Fail(AuthResultError.CookieMissingOrInvalid.Type!); + } + + var session = await _sessionService.GetSessionById(sessionKey); + if (session == null) return AuthenticateResult.Fail(AuthResultError.SessionInvalid.Type!); + + if (session.Expires!.Value < DateTime.UtcNow.Subtract(Duration.LoginSessionExpansionAfter)) + { +#pragma warning disable CS4014 + LucTask.Run(async () => +#pragma warning restore CS4014 + { + session.Expires = DateTime.UtcNow.Add(Duration.LoginSessionLifetime); + await _sessionService.UpdateSession(session, Duration.LoginSessionLifetime); + }); + } + + _batchUpdateService.UpdateSessionLastUsed(sessionKey, DateTime.UtcNow); + + var retrievedUser = await _db.Users.FirstAsync(user => user.Id == session.UserId); + + _userReferenceService.AuthReference = session; + _authService.CurrentClient = new AuthenticatedUser + { + DbUser = retrievedUser + }; + + List claims = [ + new(ClaimTypes.AuthenticationMethod, OpenShockAuthSchemas.UserSessionCookie), + new(ClaimTypes.NameIdentifier, retrievedUser.Id.ToString()), + new(ClaimTypes.Role, retrievedUser.Rank.ToString()) + ]; + + var ident = new ClaimsIdentity(claims, nameof(UserSessionAuthentication)); + + Context.User = new ClaimsPrincipal(ident); + + var ticket = new AuthenticationTicket(new ClaimsPrincipal(ident), Scheme.Name); + + return AuthenticateResult.Success(ticket); + } +} \ No newline at end of file diff --git a/Common/Authentication/AuthorizationHandlers/ApiTokenPermissionHandler.cs b/Common/Authentication/AuthorizationHandlers/ApiTokenPermissionHandler.cs new file mode 100644 index 00000000..449f52cd --- /dev/null +++ b/Common/Authentication/AuthorizationHandlers/ApiTokenPermissionHandler.cs @@ -0,0 +1,28 @@ +using Microsoft.AspNetCore.Authorization; +using OpenShock.Common.Authentication.Requirements; +using OpenShock.Common.Models; + +namespace OpenShock.Common.Authentication.AuthorizationHandlers; + +public class ApiTokenPermissionHandler : AuthorizationHandler +{ + protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ApiTokenPermissionRequirement requirement) + { + var perms = context + .User + .Identities + .SelectMany(ident => ident.Claims.Where(claim => claim.Type == OpenShockAuthClaims.ApiTokenPermission)) + .Select(claim => Enum.Parse(claim.Value)); + + if (!requirement.RequiredPermission.IsAllowed(perms)) + { + context.Fail(new AuthorizationFailureReason(this, $"You do not have the required permission to access this endpoint. Missing permission: {requirement.RequiredPermission}")); + + return Task.CompletedTask; + } + + context.Succeed(requirement); + + return Task.CompletedTask; + } +} diff --git a/Common/Authentication/ControllerBase/AuthenticatedDeviceControllerBase.cs b/Common/Authentication/ControllerBase/AuthenticatedHubControllerBase.cs similarity index 80% rename from Common/Authentication/ControllerBase/AuthenticatedDeviceControllerBase.cs rename to Common/Authentication/ControllerBase/AuthenticatedHubControllerBase.cs index b2ae8e16..3909c4a2 100644 --- a/Common/Authentication/ControllerBase/AuthenticatedDeviceControllerBase.cs +++ b/Common/Authentication/ControllerBase/AuthenticatedHubControllerBase.cs @@ -6,8 +6,8 @@ namespace OpenShock.Common.Authentication.ControllerBase; -[Authorize(AuthenticationSchemes = OpenShockAuthSchemas.DeviceToken)] -public class AuthenticatedDeviceControllerBase : OpenShockControllerBase, IActionFilter +[Authorize(AuthenticationSchemes = OpenShockAuthSchemas.HubToken)] +public class AuthenticatedHubControllerBase : OpenShockControllerBase, IActionFilter { public Device CurrentDevice = null!; diff --git a/Common/Authentication/ControllerBase/AuthenticatedSessionControllerBase.cs b/Common/Authentication/ControllerBase/AuthenticatedSessionControllerBase.cs index 5f820df0..2c28d2e6 100644 --- a/Common/Authentication/ControllerBase/AuthenticatedSessionControllerBase.cs +++ b/Common/Authentication/ControllerBase/AuthenticatedSessionControllerBase.cs @@ -1,37 +1,34 @@ -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using OpenShock.Common.Authentication.Services; using OpenShock.Common.Models; -using OpenShock.Common.OpenShockDb; namespace OpenShock.Common.Authentication.ControllerBase; -[Authorize(AuthenticationSchemes = OpenShockAuthSchemas.SessionTokenCombo)] public class AuthenticatedSessionControllerBase : OpenShockControllerBase, IActionFilter { - public LinkUser CurrentUser = null!; + public AuthenticatedUser CurrentUser = null!; [NonAction] public void OnActionExecuting(ActionExecutingContext context) { - CurrentUser = ControllerContext.HttpContext.RequestServices.GetRequiredService>().CurrentClient; + CurrentUser = ControllerContext.HttpContext.RequestServices.GetRequiredService>().CurrentClient; } [NonAction] public void OnActionExecuted(ActionExecutedContext context) { } - + [NonAction] public bool IsAllowed(PermissionType requiredType) { var userReferenceService = HttpContext.RequestServices.GetRequiredService(); - - if(userReferenceService.AuthReference == null) throw new Exception("UserReferenceService.AuthReference is null, this should not happen"); - + + if (userReferenceService.AuthReference == null) throw new Exception("UserReferenceService.AuthReference is null, this should not happen"); + if (userReferenceService.AuthReference.Value.IsT0) return true; // We are in a session - + return requiredType.IsAllowed(userReferenceService.AuthReference.Value.AsT1.Permissions); } } \ No newline at end of file diff --git a/Common/Authentication/Handlers/LoginSessionAuthentication.cs b/Common/Authentication/Handlers/LoginSessionAuthentication.cs deleted file mode 100644 index 55c3037e..00000000 --- a/Common/Authentication/Handlers/LoginSessionAuthentication.cs +++ /dev/null @@ -1,149 +0,0 @@ -using System.Net.Mime; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Http.Json; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Options; -using OpenShock.Common.Authentication.Services; -using OpenShock.Common.Constants; -using OpenShock.Common.Errors; -using OpenShock.Common.Models; -using OpenShock.Common.OpenShockDb; -using OpenShock.Common.Problems; -using OpenShock.Common.Redis; -using OpenShock.Common.Services.BatchUpdate; -using OpenShock.Common.Services.Session; -using OpenShock.Common.Utils; -using System.Security.Claims; -using System.Text.Encodings.Web; -using System.Text.Json; - -namespace OpenShock.Common.Authentication.Handlers; - -public sealed class LoginSessionAuthentication : AuthenticationHandler -{ - private readonly IClientAuthService _authService; - private readonly IUserReferenceService _userReferenceService; - private readonly IBatchUpdateService _batchUpdateService; - private readonly OpenShockContext _db; - private readonly ISessionService _sessionService; - private readonly JsonSerializerOptions _serializerOptions; - private OpenShockProblem? _authResultError = null; - - public LoginSessionAuthentication( - IOptionsMonitor options, - ILoggerFactory logger, - UrlEncoder encoder, - IClientAuthService clientAuth, - IUserReferenceService userReferenceService, - OpenShockContext db, - ISessionService sessionService, - IOptions jsonOptions, IBatchUpdateService batchUpdateService) - : base(options, logger, encoder) - { - _authService = clientAuth; - _userReferenceService = userReferenceService; - _db = db; - _sessionService = sessionService; - _serializerOptions = jsonOptions.Value.SerializerOptions; - _batchUpdateService = batchUpdateService; - } - - protected override Task HandleAuthenticateAsync() - { - if (Context.TryGetSessionKey(out var sessionKey)) - { - return SessionAuth(sessionKey); - } - - if (Context.TryGetAuthTokenFromHeader(out var token)) - { - return TokenAuth(token); - } - - return Task.FromResult(Fail(AuthResultError.CookieOrHeaderMissingOrInvalid)); - } - - private async Task TokenAuth(string token) - { - string tokenHash = HashingUtils.HashSha256(token); - - var tokenDto = await _db.ApiTokens.Include(x => x.User).FirstOrDefaultAsync(x => x.TokenHash == tokenHash && - (x.ValidUntil == null || x.ValidUntil >= DateTime.UtcNow)); - if (tokenDto == null) return Fail(AuthResultError.TokenInvalid); - - _batchUpdateService.UpdateTokenLastUsed(tokenDto.Id); - _authService.CurrentClient = new LinkUser - { - DbUser = tokenDto.User - }; - _userReferenceService.AuthReference = tokenDto; - - Context.Items["User"] = _authService.CurrentClient.DbUser.Id; - - var claims = new List - { - new(ClaimTypes.NameIdentifier, _authService.CurrentClient.DbUser.Id.ToString()), - new(ControlLogAdditionalItem.ApiTokenId, tokenDto.Id.ToString()) - }; - - var ident = new ClaimsIdentity(claims, nameof(LoginSessionAuthentication)); - var ticket = new AuthenticationTicket(new ClaimsPrincipal(ident), Scheme.Name); - - return AuthenticateResult.Success(ticket); - } - - private async Task SessionAuth(string sessionKey) - { - var session = await _sessionService.GetSessionById(sessionKey); - if (session == null) return Fail(AuthResultError.SessionInvalid); - - if (session.Expires!.Value < DateTime.UtcNow.Subtract(Duration.LoginSessionExpansionAfter)) - { -#pragma warning disable CS4014 - LucTask.Run(async () => -#pragma warning restore CS4014 - { - session.Expires = DateTime.UtcNow.Add(Duration.LoginSessionLifetime); - await _sessionService.UpdateSession(session, Duration.LoginSessionLifetime); - }); - } - - _batchUpdateService.UpdateSessionLastUsed(sessionKey, DateTime.UtcNow); - - var retrievedUser = await _db.Users.FirstAsync(user => user.Id == session.UserId); - - _userReferenceService.AuthReference = session; - _authService.CurrentClient = new LinkUser - { - DbUser = retrievedUser - }; - - Context.Items["User"] = _authService.CurrentClient.DbUser.Id; - - var claims = new List - { - new(ClaimTypes.NameIdentifier, _authService.CurrentClient.DbUser.Id.ToString()) - }; - - var ident = new ClaimsIdentity(claims, nameof(LoginSessionAuthentication)); - var ticket = new AuthenticationTicket(new ClaimsPrincipal(ident), Scheme.Name); - - return AuthenticateResult.Success(ticket); - } - - - private AuthenticateResult Fail(OpenShockProblem reason) - { - _authResultError = reason; - return AuthenticateResult.Fail(reason.Type!); - } - - /// - protected override Task HandleChallengeAsync(AuthenticationProperties properties) - { - _authResultError ??= AuthResultError.UnknownError; - Response.StatusCode = _authResultError.Status!.Value; - _authResultError.AddContext(Context); - return Context.Response.WriteAsJsonAsync(_authResultError, _serializerOptions, contentType: MediaTypeNames.Application.ProblemJson); - } -} \ No newline at end of file diff --git a/Common/Authentication/OpenShockAuthClaims.cs b/Common/Authentication/OpenShockAuthClaims.cs new file mode 100644 index 00000000..da7691e7 --- /dev/null +++ b/Common/Authentication/OpenShockAuthClaims.cs @@ -0,0 +1,7 @@ +namespace OpenShock.Common.Authentication; + +public class OpenShockAuthClaims +{ + public const string ApiTokenId = "apiTokenId"; + public const string ApiTokenPermission = "ApiTokenPermission"; +} diff --git a/Common/Authentication/OpenShockAuthPolicies.cs b/Common/Authentication/OpenShockAuthPolicies.cs new file mode 100644 index 00000000..eddc6a03 --- /dev/null +++ b/Common/Authentication/OpenShockAuthPolicies.cs @@ -0,0 +1,12 @@ +namespace OpenShock.Common.Authentication; + +public static class OpenShockAuthPolicies +{ + public const string UserAccess = "UserAccess"; + public const string SupportAccess = "SupportAccess"; + public const string StaffAccess = "StaffAccess"; + public const string AdminAccess = "AdminAccess"; + public const string SystemAccess = "SystemAccess"; + + public const string TokenSessionOnly = "ApiTokenOnly"; +} diff --git a/Common/Authentication/OpenShockAuthSchemas.cs b/Common/Authentication/OpenShockAuthSchemas.cs index 8328f9c6..51ce9db1 100644 --- a/Common/Authentication/OpenShockAuthSchemas.cs +++ b/Common/Authentication/OpenShockAuthSchemas.cs @@ -4,9 +4,9 @@ namespace OpenShock.Common.Authentication; public static class OpenShockAuthSchemas { - // TODO: What is this for? - public const string SessionTokenCombo = "session-token-combo"; + public const string UserSessionCookie = "UserSessionCookie"; + public const string ApiToken = "ApiToken"; + public const string HubToken = "HubToken"; - /// TODO: Replace this with ? - public const string DeviceToken = "device-token"; + public const string UserSessionApiTokenCombo = $"{UserSessionCookie},{ApiToken}"; } \ No newline at end of file diff --git a/Common/Authentication/Requirements/ApiTokenPermissionRequirement.cs b/Common/Authentication/Requirements/ApiTokenPermissionRequirement.cs new file mode 100644 index 00000000..275d565f --- /dev/null +++ b/Common/Authentication/Requirements/ApiTokenPermissionRequirement.cs @@ -0,0 +1,14 @@ +using Microsoft.AspNetCore.Authorization; +using OpenShock.Common.Models; + +namespace OpenShock.Common.Authentication.Requirements; + +public class ApiTokenPermissionRequirement : IAuthorizationRequirement +{ + public ApiTokenPermissionRequirement(PermissionType requiredPermission) + { + RequiredPermission = requiredPermission; + } + + public PermissionType RequiredPermission { get; init; } +} diff --git a/Common/Constants/AuthConstants.cs b/Common/Constants/AuthConstants.cs index 0215b079..653c7a6b 100644 --- a/Common/Constants/AuthConstants.cs +++ b/Common/Constants/AuthConstants.cs @@ -2,8 +2,8 @@ public static class AuthConstants { - public const string SessionCookieName = "openShockSession"; + public const string UserSessionCookieName = "openShockSession"; public const string SessionHeaderName = "OpenShockSession"; - public const string AuthTokenHeaderName = "OpenShockToken"; - public const string DeviceAuthTokenHeaderName = "DeviceToken"; + public const string ApiTokenHeaderName = "OpenShockToken"; + public const string HubTokenHeaderName = "DeviceToken"; } diff --git a/Common/Errors/AuthResultError.cs b/Common/Errors/AuthResultError.cs index 1be2dfa5..1c5d0fc5 100644 --- a/Common/Errors/AuthResultError.cs +++ b/Common/Errors/AuthResultError.cs @@ -6,7 +6,8 @@ namespace OpenShock.Common.Errors; public static class AuthResultError { public static OpenShockProblem UnknownError => new("Authentication.UnknownError", "An unknown error occurred.", HttpStatusCode.InternalServerError); - public static OpenShockProblem CookieOrHeaderMissingOrInvalid => new("Authentication.HeaderMissingOrInvalid", "Missing a required authentication cookie or header or it is invalid.", HttpStatusCode.Unauthorized); + public static OpenShockProblem CookieMissingOrInvalid => new("Authentication.CookieMissingOrInvalid", "Missing or invalid authentication cookie.", HttpStatusCode.Unauthorized); + public static OpenShockProblem HeaderMissingOrInvalid => new("Authentication.HeaderMissingOrInvalid", "Missing or invalid authentication header.", HttpStatusCode.Unauthorized); public static OpenShockProblem SessionInvalid => new("Authentication.SessionInvalid", "The session is invalid", HttpStatusCode.Unauthorized); public static OpenShockProblem TokenInvalid => new("Authentication.TokenInvalid", "The token is invalid", HttpStatusCode.Unauthorized); diff --git a/Common/Hubs/ShareLinkHub.cs b/Common/Hubs/ShareLinkHub.cs index 577c646a..0b975fea 100644 --- a/Common/Hubs/ShareLinkHub.cs +++ b/Common/Hubs/ShareLinkHub.cs @@ -35,8 +35,6 @@ public ShareLinkHub(OpenShockContext db, IHubContext userHub, public override async Task OnConnectedAsync() { - _tokenPermissions = _userReferenceService.AuthReference is not { IsT1: true } ? null : _userReferenceService.AuthReference.Value.AsT1.Permissions; - var httpContext = Context.GetHttpContext(); if (httpContext?.GetRouteValue("Id") is not string param || !Guid.TryParse(param, out var id)) { @@ -44,6 +42,21 @@ public override async Task OnConnectedAsync() Context.Abort(); return; } + + GenericIni? user = null; + + if (httpContext.TryGetUserSessionCookie(out var sessionCookie)) + { + user = await SessionAuth(sessionCookie); + if (user == null) + { + _logger.LogWarning("Connection tried authentication with invalid user session cookie, terminating connection..."); + Context.Abort(); + return; + } + } + + _tokenPermissions = _userReferenceService.AuthReference is not { IsT1: true } ? null : _userReferenceService.AuthReference.Value.AsT1.Permissions; var exists = await _db.ShockerSharesLinks.AnyAsync(x => x.Id == id && (x.ExpiresOn == null || x.ExpiresOn > DateTime.UtcNow)); if (!exists) @@ -52,13 +65,6 @@ public override async Task OnConnectedAsync() Context.Abort(); return; } - - GenericIni? user = null; - - if (httpContext.TryGetSessionKey(out var sessionKey)) - { - user = await SessionAuth(sessionKey); - } // TODO: Add token auth @@ -68,6 +74,7 @@ public override async Task OnConnectedAsync() { _logger.LogWarning("customName was not set nor was the user authenticated, terminating connection..."); Context.Abort(); + return; } var additionalItems = new Dictionary diff --git a/Common/Hubs/UserHub.cs b/Common/Hubs/UserHub.cs index ebec4364..4c982ab4 100644 --- a/Common/Hubs/UserHub.cs +++ b/Common/Hubs/UserHub.cs @@ -16,7 +16,7 @@ namespace OpenShock.Common.Hubs; -[Authorize(AuthenticationSchemes = OpenShockAuthSchemas.SessionTokenCombo)] +[Authorize(AuthenticationSchemes = OpenShockAuthSchemas.UserSessionApiTokenCombo)] public sealed class UserHub : Hub { private readonly ILogger _logger; @@ -78,8 +78,8 @@ public async Task ControlV2(IEnumerable sh if (!_tokenPermissions.IsAllowedAllowOnNull(PermissionType.Shockers_Use)) return; var additionalItems = new Dictionary(); - var apiTokenId = Context.User?.FindFirst(ControlLogAdditionalItem.ApiTokenId); - if (apiTokenId != null) additionalItems[ControlLogAdditionalItem.ApiTokenId] = apiTokenId.Value; + var apiTokenId = Context.User?.FindFirst(OpenShockAuthClaims.ApiTokenId); + if (apiTokenId != null) additionalItems[OpenShockAuthClaims.ApiTokenId] = apiTokenId.Value; var sender = await _db.Users.Where(x => x.Id == UserId).Select(x => new ControlLogSender { diff --git a/Common/IQueryableExtensions.cs b/Common/IQueryableExtensions.cs index aae525ce..1e57c2f7 100644 --- a/Common/IQueryableExtensions.cs +++ b/Common/IQueryableExtensions.cs @@ -48,7 +48,7 @@ public static IQueryable WhereIsUserOrRank(this IQueryable WhereIsUserOrRank(this IQueryable source, Expression> navigationSelector, LinkUser user, RankType rank) + public static IQueryable WhereIsUserOrRank(this IQueryable source, Expression> navigationSelector, AuthenticatedUser user, RankType rank) { return WhereIsUserOrRank(source, navigationSelector, user.DbUser, rank); } @@ -58,7 +58,7 @@ public static IQueryable WhereIsUserOrAdmin(this IQueryable WhereIsUserOrAdmin(this IQueryable source, Expression> navigationSelector, LinkUser user) + public static IQueryable WhereIsUserOrAdmin(this IQueryable source, Expression> navigationSelector, AuthenticatedUser user) { return WhereIsUserOrRank(source, navigationSelector, user.DbUser, RankType.Admin); } diff --git a/Common/Models/ControlLogAdditionalItem.cs b/Common/Models/ControlLogAdditionalItem.cs index 90f8f947..abe50c9a 100644 --- a/Common/Models/ControlLogAdditionalItem.cs +++ b/Common/Models/ControlLogAdditionalItem.cs @@ -1,7 +1,9 @@ -namespace OpenShock.Common.Models; +using OpenShock.Common.Authentication; + +namespace OpenShock.Common.Models; public static class ControlLogAdditionalItem { - public const string ApiTokenId = "apiTokenId"; + public const string ApiTokenId = OpenShockAuthClaims.ApiTokenId; public const string ShareLinkId = "shareLinkId"; } \ No newline at end of file diff --git a/Common/OpenShockServiceHelper.cs b/Common/OpenShockServiceHelper.cs index cb641be3..1fb2d4a8 100644 --- a/Common/OpenShockServiceHelper.cs +++ b/Common/OpenShockServiceHelper.cs @@ -1,12 +1,15 @@ -using System.Text.Json; +using System.Security.Claims; +using System.Text.Json; using System.Text.Json.Serialization; using Asp.Versioning; using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection.Extensions; using OpenShock.Common.Authentication; -using OpenShock.Common.Authentication.Handlers; +using OpenShock.Common.Authentication.AuthenticationHandlers; +using OpenShock.Common.Authentication.Requirements; using OpenShock.Common.Authentication.Services; using OpenShock.Common.Config; using OpenShock.Common.ExceptionHandle; @@ -26,6 +29,24 @@ namespace OpenShock.Common; public static class OpenShockServiceHelper { + // TODO: this is temporary while we still rely on enums for user ranks + static bool HandleRankCheck(AuthorizationHandlerContext context, RankType requiredRank) + { + var ranks = context.User.Identities.SelectMany(ident => ident.Claims.Where(claim => claim.Type == ident.RoleClaimType)).Select(claim => Enum.Parse(claim.Value)).ToList(); + + if (!ranks.Any()) + { + return false; + } + + if (ranks.Max() < requiredRank) + { + return false; + } + + return true; + } + /// /// Register all OpenShock services for PRODUCTION use /// @@ -39,18 +60,30 @@ public static ServicesResult AddOpenShockServices(this IServiceCollection servic // <---- ASP.NET ----> services.AddExceptionHandler(); - services.AddScoped, ClientAuthService>(); + services.AddScoped, ClientAuthService>(); services.AddScoped, ClientAuthService>(); services.AddScoped(); - - new AuthenticationBuilder(services) - .AddScheme( - OpenShockAuthSchemas.SessionTokenCombo, _ => { }) - .AddScheme( - OpenShockAuthSchemas.DeviceToken, _ => { }); - + services.AddAuthenticationCore(); - services.AddAuthorization(); + new AuthenticationBuilder(services) + .AddScheme( + OpenShockAuthSchemas.UserSessionCookie, _ => { }) + .AddScheme( + OpenShockAuthSchemas.ApiToken, _ => { }) + .AddScheme( + OpenShockAuthSchemas.HubToken, _ => { }); + + services.AddAuthorization(options => + { + options.AddPolicy(OpenShockAuthPolicies.SystemAccess, policy => policy.RequireRole(RankType.System.ToString())); + options.AddPolicy(OpenShockAuthPolicies.AdminAccess, policy => policy.RequireAssertion(context => HandleRankCheck(context, RankType.Admin))); + options.AddPolicy(OpenShockAuthPolicies.StaffAccess, policy => policy.RequireAssertion(context => HandleRankCheck(context, RankType.Staff))); + options.AddPolicy(OpenShockAuthPolicies.SupportAccess, policy => policy.RequireAssertion(context => HandleRankCheck(context, RankType.Support))); + options.AddPolicy(OpenShockAuthPolicies.UserAccess, policy => policy.RequireAssertion(context => HandleRankCheck(context, RankType.User))); + + options.AddPolicy(OpenShockAuthPolicies.TokenSessionOnly, policy => policy.RequireClaim(ClaimTypes.AuthenticationMethod, OpenShockAuthSchemas.ApiToken)); + // TODO: Add token permission policies + }); services.Configure(options => { diff --git a/Common/Services/Session/SessionService.cs b/Common/Services/Session/SessionService.cs index 9d70f508..5ca5cc78 100644 --- a/Common/Services/Session/SessionService.cs +++ b/Common/Services/Session/SessionService.cs @@ -1,4 +1,4 @@ -using OpenShock.Common.Authentication.Handlers; +using OpenShock.Common.Authentication.AuthenticationHandlers; using OpenShock.Common.Constants; using OpenShock.Common.Redis; using Redis.OM; diff --git a/Common/Swagger/AttributeFilter.cs b/Common/Swagger/AttributeFilter.cs new file mode 100644 index 00000000..6e606476 --- /dev/null +++ b/Common/Swagger/AttributeFilter.cs @@ -0,0 +1,119 @@ +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authorization; +using Microsoft.OpenApi.Models; +using OpenShock.Common.Authentication; +using OpenShock.Common.DataAnnotations.Interfaces; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace OpenShock.Common.Swagger; + +public sealed class AttributeFilter : ISchemaFilter, IParameterFilter, IOperationFilter +{ + public void Apply(OpenApiParameter parameter, ParameterFilterContext context) + { + // Apply OpenShock Parameter Attributes + foreach (var attribute in context.ParameterInfo?.GetCustomAttributes(true).OfType() ?? []) + { + attribute.Apply(parameter); + } + + // Apply OpenShock Parameter Attributes + foreach (var attribute in context.PropertyInfo?.GetCustomAttributes(true).OfType() ?? []) + { + attribute.Apply(parameter); + } + } + + public void Apply(OpenApiSchema schema, SchemaFilterContext context) + { + // Apply OpenShock Parameter Attributes + foreach (var attribute in context.MemberInfo?.GetCustomAttributes(true).OfType() ?? []) + { + attribute.Apply(schema); + } + } + + public void Apply(OpenApiOperation operation, OperationFilterContext context) + { + // Apply OpenShock Parameter Attributes + foreach (var attribute in context.MethodInfo?.GetCustomAttributes(true).OfType() ?? []) + { + attribute.Apply(operation); + } + + // Get Authorize attribute + var attributes = context.MethodInfo?.DeclaringType?.GetCustomAttributes(true) + .Union(context.MethodInfo.GetCustomAttributes(true)) + .OfType() + .ToList() ?? []; + + if (attributes.Count != 0) + { + if (attributes.Count(attr => !string.IsNullOrEmpty(attr.AuthenticationSchemes)) > 1) throw new Exception("Dunno what to apply to this method (multiple authentication attributes with schemes set)"); + + var scheme = attributes.Select(attr => attr.AuthenticationSchemes).SingleOrDefault(scheme => !string.IsNullOrEmpty(scheme)); + var roles = attributes.Select(attr => attr.Roles).Where(roles => !string.IsNullOrEmpty(roles)).SelectMany(roles => roles!.Split(',')).Select(role => role.Trim()).ToList(); + var policies = attributes.Select(attr => attr.Policy).Where(policies => !string.IsNullOrEmpty(policies)).SelectMany(policies => policies!.Split(',')).Select(policy => policy.Trim()).ToList(); + + // Add what should be show inside the security section + List securityInfos = []; + if (!string.IsNullOrEmpty(scheme)) securityInfos.Add($"{nameof(AuthorizeAttribute.AuthenticationSchemes)}:{scheme}"); + if (roles.Count > 0) securityInfos.Add($"{nameof(AuthorizeAttribute.Roles)}:{string.Join(',', roles)}"); + if (policies.Count > 0) securityInfos.Add($"{nameof(AuthorizeAttribute.Policy)}:{string.Join(',', policies)}"); + + List securityRequirements = []; + foreach (var authenticationScheme in scheme?.Split(',').Select(s => s.Trim()) ?? []) + { + securityRequirements.AddRange(authenticationScheme switch + { + OpenShockAuthSchemas.UserSessionCookie => [ + new OpenApiSecurityRequirement {{ + new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Id = OpenShockAuthSchemas.UserSessionCookie, + Type = ReferenceType.SecurityScheme, + } + }, + securityInfos + }} + ], + OpenShockAuthSchemas.ApiToken => [ + new OpenApiSecurityRequirement {{ + new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Id = OpenShockAuthSchemas.ApiToken, + Type = ReferenceType.SecurityScheme, + } + }, + securityInfos + }} + ], + OpenShockAuthSchemas.HubToken => [ + new OpenApiSecurityRequirement {{ + new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Id = OpenShockAuthSchemas.HubToken, + Type = ReferenceType.SecurityScheme + } + }, + securityInfos + }} + ], + _ => [], + }); + } + + operation.Security = securityRequirements; + } + else + { + operation.Security.Clear(); + } + } +} \ No newline at end of file diff --git a/Common/Extensions/SwaggerGenExtensions.cs b/Common/Swagger/SwaggerGenExtensions.cs similarity index 62% rename from Common/Extensions/SwaggerGenExtensions.cs rename to Common/Swagger/SwaggerGenExtensions.cs index ab572c0c..d8e45ef4 100644 --- a/Common/Extensions/SwaggerGenExtensions.cs +++ b/Common/Swagger/SwaggerGenExtensions.cs @@ -9,19 +9,21 @@ using Microsoft.AspNetCore.Mvc; using System.Reflection; using System.Linq; +using OpenShock.Common.Extensions; +using OpenShock.Common.Authentication; -namespace OpenShock.Common.Extensions; +namespace OpenShock.Common.Swagger; public static class SwaggerGenExtensions { public static IServiceCollection AddSwaggerExt(this IServiceCollection services) where TProgram : class { var assembly = typeof(TProgram).Assembly; - + string assemblyName = assembly .GetName() .Name ?? throw new NullReferenceException("Assembly name"); - + var versions = assembly.GetAllControllerEndpointAttributes() .SelectMany(type => type.Versions) .Select(v => v.ToString()) @@ -35,40 +37,58 @@ public static IServiceCollection AddSwaggerExt(this IServiceCollection } return services - .AddSwaggerGen(options => { + .AddSwaggerGen(options => + { options.CustomOperationIds(e => $"{e.ActionDescriptor.RouteValues["controller"]}_{e.ActionDescriptor.AttributeRouteInfo?.Name ?? e.ActionDescriptor.RouteValues["action"]}"); options.SchemaFilter(); options.ParameterFilter(); options.OperationFilter(); options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, assemblyName + ".xml"), true); - options.AddSecurityDefinition(AuthConstants.AuthTokenHeaderName, new OpenApiSecurityScheme + options.AddSecurityDefinition(OpenShockAuthSchemas.UserSessionCookie, new OpenApiSecurityScheme { - Name = AuthConstants.AuthTokenHeaderName, + Name = AuthConstants.UserSessionCookieName, + Description = "Enter user session cookie", + In = ParameterLocation.Cookie, Type = SecuritySchemeType.ApiKey, - Scheme = "ApiKeyAuth", + Scheme = OpenShockAuthSchemas.UserSessionCookie, + Reference = new OpenApiReference + { + Id = OpenShockAuthSchemas.UserSessionCookie, + Type = ReferenceType.SecurityScheme, + } + }); + options.AddSecurityDefinition(OpenShockAuthSchemas.ApiToken, new OpenApiSecurityScheme + { + Name = AuthConstants.ApiTokenHeaderName, + Description = "Enter API Token", In = ParameterLocation.Header, - Description = "API Token Authorization header." + Type = SecuritySchemeType.ApiKey, + Scheme = OpenShockAuthSchemas.ApiToken, + Reference = new OpenApiReference + { + Id = OpenShockAuthSchemas.ApiToken, + Type = ReferenceType.SecurityScheme, + } }); - options.AddSecurityRequirement(new OpenApiSecurityRequirement + options.AddSecurityDefinition(OpenShockAuthSchemas.HubToken, new OpenApiSecurityScheme + { + Name = AuthConstants.HubTokenHeaderName, + Description = "Enter hub token", + In = ParameterLocation.Header, + Type = SecuritySchemeType.ApiKey, + Scheme = OpenShockAuthSchemas.HubToken, + Reference = new OpenApiReference { - { - new OpenApiSecurityScheme - { - Reference = new OpenApiReference - { - Type = ReferenceType.SecurityScheme, - Id = AuthConstants.AuthTokenHeaderName - } - }, - Array.Empty() - } - }); + Id = OpenShockAuthSchemas.HubToken, + Type = ReferenceType.SecurityScheme, + } + }); options.AddServer(new OpenApiServer { Url = "https://api.openshock.app" }); options.AddServer(new OpenApiServer { Url = "https://staging-api.openshock.app" }); - #if DEBUG +#if DEBUG options.AddServer(new OpenApiServer { Url = "https://localhost" }); - #endif +#endif foreach (var version in versions) { options.SwaggerDoc("v" + version, new OpenApiInfo { Title = "OpenShock", Version = version }); diff --git a/Common/Utils/AuthUtils.cs b/Common/Utils/AuthUtils.cs index b7e74320..6a662a7f 100644 --- a/Common/Utils/AuthUtils.cs +++ b/Common/Utils/AuthUtils.cs @@ -6,18 +6,18 @@ namespace OpenShock.Common.Utils; public static class AuthUtils { private static readonly string[] TokenHeaderNames = [ - AuthConstants.AuthTokenHeaderName, + AuthConstants.ApiTokenHeaderName, "Open-Shock-Token", "ShockLinkToken" ]; private static readonly string[] DeviceTokenHeaderNames = [ - AuthConstants.DeviceAuthTokenHeaderName, + AuthConstants.HubTokenHeaderName, "Device-Token" ]; public static void SetSessionKeyCookie(this HttpContext context, string sessionKey, string domain) { - context.Response.Cookies.Append(AuthConstants.SessionCookieName, sessionKey, new CookieOptions + context.Response.Cookies.Append(AuthConstants.UserSessionCookieName, sessionKey, new CookieOptions { Expires = new DateTimeOffset(DateTime.UtcNow.Add(Duration.LoginSessionLifetime)), Secure = true, @@ -29,7 +29,7 @@ public static void SetSessionKeyCookie(this HttpContext context, string sessionK public static void RemoveSessionKeyCookie(this HttpContext context, string domain) { - context.Response.Cookies.Append(AuthConstants.SessionCookieName, string.Empty, new CookieOptions + context.Response.Cookies.Append(AuthConstants.UserSessionCookieName, string.Empty, new CookieOptions { Expires = DateTime.Now.AddDays(-1), Secure = true, @@ -39,43 +39,19 @@ public static void RemoveSessionKeyCookie(this HttpContext context, string domai }); } - public static bool TryGetSessionKeyFromCookie(this HttpContext context, [NotNullWhen(true)] out string? sessionKey) + public static bool TryGetUserSessionCookie(this HttpContext context, [NotNullWhen(true)] out string? sessionCookie) { - if (context.Request.Cookies.TryGetValue(AuthConstants.SessionCookieName, out sessionKey) && !string.IsNullOrEmpty(sessionKey)) + if (context.Request.Cookies.TryGetValue(AuthConstants.UserSessionCookieName, out sessionCookie) && !string.IsNullOrEmpty(sessionCookie)) { return true; } - sessionKey = null; + sessionCookie = null; return false; } - public static bool TryGetSessionAuthFromHeader(this HttpContext context, [NotNullWhen(true)] out string? sessionKey) - { - if (context.Request.Headers.TryGetValue(AuthConstants.SessionHeaderName, out var value) && !string.IsNullOrEmpty(value)) - { - sessionKey = value!; - - return true; - } - - sessionKey = null; - - return false; - } - - public static bool TryGetSessionKey(this HttpContext context, [NotNullWhen(true)] out string? sessionKey) - { - if (TryGetSessionKeyFromCookie(context, out sessionKey)) return true; - if (TryGetSessionAuthFromHeader(context, out sessionKey)) return true; - - sessionKey = null; - - return false; - } - - public static bool TryGetAuthTokenFromHeader(this HttpContext context, [NotNullWhen(true)] out string? token) + public static bool TryGetApiTokenFromHeader(this HttpContext context, [NotNullWhen(true)] out string? token) { foreach (string header in TokenHeaderNames) { diff --git a/Cron/DashboardAdminAuth.cs b/Cron/DashboardAdminAuth.cs index 6fa03c7b..48fb1a4e 100644 --- a/Cron/DashboardAdminAuth.cs +++ b/Cron/DashboardAdminAuth.cs @@ -19,9 +19,9 @@ public async Task AuthorizeAsync(DashboardContext context) var userSessions = redis.RedisCollection(false); var db = httpContext.RequestServices.GetRequiredService(); - if (httpContext.TryGetSessionKeyFromCookie(out var sessionKeyCookie)) + if (httpContext.TryGetUserSessionCookie(out var userSessionCookie)) { - if (await SessionAuthAdmin(sessionKeyCookie, userSessions, db)) + if (await SessionAuthAdmin(userSessionCookie, userSessions, db)) { return true; } diff --git a/LiveControlGateway/Controllers/HubControllerBase.cs b/LiveControlGateway/Controllers/HubControllerBase.cs index 8c64e751..988873cb 100644 --- a/LiveControlGateway/Controllers/HubControllerBase.cs +++ b/LiveControlGateway/Controllers/HubControllerBase.cs @@ -1,5 +1,6 @@ using System.Net.WebSockets; using FlatSharp; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.SignalR; @@ -7,6 +8,7 @@ using OneOf; using OneOf.Types; using OpenShock.Common; +using OpenShock.Common.Authentication; using OpenShock.Common.Authentication.Services; using OpenShock.Common.Constants; using OpenShock.Common.Errors; diff --git a/LiveControlGateway/Controllers/HubV1Controller.cs b/LiveControlGateway/Controllers/HubV1Controller.cs index e157e72e..ada6f041 100644 --- a/LiveControlGateway/Controllers/HubV1Controller.cs +++ b/LiveControlGateway/Controllers/HubV1Controller.cs @@ -19,9 +19,9 @@ namespace OpenShock.LiveControlGateway.Controllers; /// Communication with the hubs aka ESP-32 microcontrollers /// [ApiController] -[Authorize(AuthenticationSchemes = OpenShockAuthSchemas.DeviceToken)] [ApiVersion("1")] [Route("/{version:apiVersion}/ws/device")] +[Authorize(AuthenticationSchemes = OpenShockAuthSchemas.HubToken)] public sealed class HubV1Controller : HubControllerBase { private readonly IHubContext _userHubContext; diff --git a/LiveControlGateway/Controllers/HubV2Controller.cs b/LiveControlGateway/Controllers/HubV2Controller.cs index 7c8e9285..f603c7b4 100644 --- a/LiveControlGateway/Controllers/HubV2Controller.cs +++ b/LiveControlGateway/Controllers/HubV2Controller.cs @@ -21,9 +21,9 @@ namespace OpenShock.LiveControlGateway.Controllers; /// Communication with the hubs aka ESP-32 microcontrollers /// [ApiController] -[Authorize(AuthenticationSchemes = OpenShockAuthSchemas.DeviceToken)] [ApiVersion("2")] [Route("/{version:apiVersion}/ws/hub")] +[Authorize(AuthenticationSchemes = OpenShockAuthSchemas.HubToken)] public sealed class HubV2Controller : HubControllerBase { private readonly IHubContext _userHubContext; diff --git a/LiveControlGateway/Controllers/LiveControlController.cs b/LiveControlGateway/Controllers/LiveControlController.cs index a29a11e4..f89d6700 100644 --- a/LiveControlGateway/Controllers/LiveControlController.cs +++ b/LiveControlGateway/Controllers/LiveControlController.cs @@ -33,7 +33,7 @@ namespace OpenShock.LiveControlGateway.Controllers; [ApiController] [Route("/{version:apiVersion}/ws/live/{hubId:guid}")] [TokenPermission(PermissionType.Shockers_Use)] -[Authorize(AuthenticationSchemes = OpenShockAuthSchemas.SessionTokenCombo)] +[Authorize(AuthenticationSchemes = OpenShockAuthSchemas.UserSessionApiTokenCombo)] public sealed class LiveControlController : WebsocketBaseController>, IActionFilter { private readonly OpenShockContext _db; @@ -51,7 +51,7 @@ public sealed class LiveControlController : WebsocketBaseController _sharedShockers = new(); @@ -187,7 +187,7 @@ public async Task UpdatePermissions(OpenShockContext db) [NonAction] public void OnActionExecuting(ActionExecutingContext context) { - _currentUser = ControllerContext.HttpContext.RequestServices.GetRequiredService>() + _currentUser = ControllerContext.HttpContext.RequestServices.GetRequiredService>() .CurrentClient; } diff --git a/LiveControlGateway/Program.cs b/LiveControlGateway/Program.cs index 1a6b0313..7138537a 100644 --- a/LiveControlGateway/Program.cs +++ b/LiveControlGateway/Program.cs @@ -3,6 +3,7 @@ using OpenShock.Common.JsonSerialization; using OpenShock.Common.Services.Device; using OpenShock.Common.Services.Ota; +using OpenShock.Common.Swagger; using OpenShock.LiveControlGateway; using OpenShock.LiveControlGateway.LifetimeManager; using OpenShock.LiveControlGateway.PubSub;