From a5f9e67647ec63f9577fc282d7d1a58054251f79 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 15 Jun 2024 21:53:00 +0300 Subject: [PATCH 01/13] feat: add middleware for revocated tokens --- .../Middleware/JwtRevocationMiddleware.cs | 23 +++++++++++++++++++ Endpoint/Program.cs | 1 + 2 files changed, 24 insertions(+) create mode 100644 Endpoint/Middleware/JwtRevocationMiddleware.cs diff --git a/Endpoint/Middleware/JwtRevocationMiddleware.cs b/Endpoint/Middleware/JwtRevocationMiddleware.cs new file mode 100644 index 0000000..97818c7 --- /dev/null +++ b/Endpoint/Middleware/JwtRevocationMiddleware.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.Http; +using Mirea.Api.Security.Common.Interfaces; +using System.Threading.Tasks; + +namespace Mirea.Api.Endpoint.Middleware; + +public class JwtRevocationMiddleware(RequestDelegate next) +{ + public async Task Invoke(HttpContext context, IRevokedToken revokedTokenStore) + { + if (context.Request.Headers.ContainsKey("Authorization")) + { + var token = context.Request.Headers.Authorization.ToString().Replace("Bearer ", ""); + if (await revokedTokenStore.IsTokenRevokedAsync(token)) + { + context.Response.StatusCode = StatusCodes.Status401Unauthorized; + return; + } + } + + await next(context); + } +} \ No newline at end of file diff --git a/Endpoint/Program.cs b/Endpoint/Program.cs index cf016f4..a904e98 100644 --- a/Endpoint/Program.cs +++ b/Endpoint/Program.cs @@ -95,6 +95,7 @@ public class Program app.UseMiddleware(); app.UseMiddleware(); + app.UseMiddleware(); app.UseHttpsRedirection(); -- 2.34.1 From 039d323643ceb6fa736b58a1d0c102e09700926c Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 21 Jun 2024 21:36:11 +0300 Subject: [PATCH 02/13] fix: add refresh expire date --- .../Common/Dto/Responses/AuthTokenResponse.cs | 3 ++- Security/Services/AuthService.cs | 22 ++++++++++--------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/Security/Common/Dto/Responses/AuthTokenResponse.cs b/Security/Common/Dto/Responses/AuthTokenResponse.cs index 0c8a3d4..16aed38 100644 --- a/Security/Common/Dto/Responses/AuthTokenResponse.cs +++ b/Security/Common/Dto/Responses/AuthTokenResponse.cs @@ -5,6 +5,7 @@ namespace Mirea.Api.Security.Common.Dto.Responses; public class AuthTokenResponse { public required string AccessToken { get; set; } + public DateTime AccessExpiresIn { get; set; } public required string RefreshToken { get; set; } - public DateTime ExpiresIn { get; set; } + public DateTime RefreshExpiresIn { get; set; } } \ No newline at end of file diff --git a/Security/Services/AuthService.cs b/Security/Services/AuthService.cs index 5426532..2aa01d7 100644 --- a/Security/Services/AuthService.cs +++ b/Security/Services/AuthService.cs @@ -34,7 +34,7 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I public async Task GenerateAuthTokensAsync(TokenRequest request, string userId, CancellationToken cancellation = default) { var refreshToken = GenerateRefreshToken(); - var accessToken = GenerateAccessToken(userId); + var (token, expireIn) = GenerateAccessToken(userId); var authTokenStruct = new AuthToken { @@ -43,16 +43,17 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I RefreshToken = refreshToken, UserAgent = request.UserAgent, UserId = userId, - AccessToken = accessToken.Token + AccessToken = token }; await SetAuthTokenDataToCache(request.Fingerprint, authTokenStruct, cancellation); return new AuthTokenResponse { - AccessToken = accessToken.Token, - ExpiresIn = accessToken.ExpireIn, - RefreshToken = authTokenStruct.RefreshToken + AccessToken = token, + AccessExpiresIn = expireIn, + RefreshToken = authTokenStruct.RefreshToken, + RefreshExpiresIn = DateTime.UtcNow.Add(Lifetime), }; } @@ -77,17 +78,18 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I throw new SecurityException(request.Fingerprint); } - var accessToken = GenerateAccessToken(authToken.UserId); + var (token, expireIn) = GenerateAccessToken(authToken.UserId); await RevokeAccessToken(authToken.AccessToken); - authToken.AccessToken = accessToken.Token; + authToken.AccessToken = token; await SetAuthTokenDataToCache(request.Fingerprint, authToken, cancellation); return new AuthTokenResponse { - AccessToken = accessToken.Token, - ExpiresIn = accessToken.ExpireIn, - RefreshToken = GenerateRefreshToken() + AccessToken = token, + AccessExpiresIn = expireIn, + RefreshToken = GenerateRefreshToken(), + RefreshExpiresIn = DateTime.UtcNow.Add(Lifetime) }; } -- 2.34.1 From 79151e7da8b5452293a18ad178654bb40fb521c1 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 21 Jun 2024 21:43:40 +0300 Subject: [PATCH 03/13] feat: add bearer auth to swagger --- .../AppConfig/SwaggerConfiguration.cs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Endpoint/Configuration/AppConfig/SwaggerConfiguration.cs b/Endpoint/Configuration/AppConfig/SwaggerConfiguration.cs index c43c730..4c2ff9f 100644 --- a/Endpoint/Configuration/AppConfig/SwaggerConfiguration.cs +++ b/Endpoint/Configuration/AppConfig/SwaggerConfiguration.cs @@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Mvc.ApiExplorer; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; +using Microsoft.OpenApi.Models; using Mirea.Api.Endpoint.Configuration.Swagger; using Swashbuckle.AspNetCore.SwaggerGen; using System; @@ -19,6 +20,29 @@ public static class SwaggerConfiguration options.OperationFilter(); var basePath = AppDomain.CurrentDomain.BaseDirectory; + options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme + { + In = ParameterLocation.Header, + Description = "Keep the JWT token in the field (Bearer token)", + Name = "Authorization", + Type = SecuritySchemeType.ApiKey + }); + + options.AddSecurityRequirement(new OpenApiSecurityRequirement + { + { + new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Type = ReferenceType.SecurityScheme, + Id = "Bearer" + } + }, + [] + } + }); + options.IncludeXmlComments(Path.Combine(basePath, "docs.xml")); options.IncludeXmlComments(Path.Combine(basePath, "ApiDtoDocs.xml")); }); -- 2.34.1 From 1a0d539e7632cf0f297bf0744a1670fc99046cca Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 21 Jun 2024 21:44:34 +0300 Subject: [PATCH 04/13] fix: if cache get bytes then skip serealize --- .../Common/Services/Security/DistributedCacheService.cs | 2 +- Endpoint/Common/Services/Security/MemoryCacheService.cs | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Endpoint/Common/Services/Security/DistributedCacheService.cs b/Endpoint/Common/Services/Security/DistributedCacheService.cs index bf3dc39..1d4a32b 100644 --- a/Endpoint/Common/Services/Security/DistributedCacheService.cs +++ b/Endpoint/Common/Services/Security/DistributedCacheService.cs @@ -17,7 +17,7 @@ public class DistributedCacheService(IDistributedCache cache) : ICacheService SlidingExpiration = slidingExpiration }; - var serializedValue = JsonSerializer.SerializeToUtf8Bytes(value); + var serializedValue = value as byte[] ?? JsonSerializer.SerializeToUtf8Bytes(value); await cache.SetAsync(key, serializedValue, options, cancellationToken); } diff --git a/Endpoint/Common/Services/Security/MemoryCacheService.cs b/Endpoint/Common/Services/Security/MemoryCacheService.cs index a428034..fcb1f09 100644 --- a/Endpoint/Common/Services/Security/MemoryCacheService.cs +++ b/Endpoint/Common/Services/Security/MemoryCacheService.cs @@ -1,6 +1,7 @@ using Microsoft.Extensions.Caching.Memory; using Mirea.Api.Security.Common.Interfaces; using System; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; @@ -16,14 +17,14 @@ public class MemoryCacheService(IMemoryCache cache) : ICacheService SlidingExpiration = slidingExpiration }; - cache.Set(key, value, options); + cache.Set(key, value as byte[] ?? JsonSerializer.SerializeToUtf8Bytes(value), options); return Task.CompletedTask; } public Task GetAsync(string key, CancellationToken cancellationToken = default) { - cache.TryGetValue(key, out T? value); - return Task.FromResult(value); + cache.TryGetValue(key, out byte[]? value); + return Task.FromResult(JsonSerializer.Deserialize(value)); } public Task RemoveAsync(string key, CancellationToken cancellationToken = default) -- 2.34.1 From a36e0694ec8b8bb3e0884641c92b613aa15381d9 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 21 Jun 2024 21:50:42 +0300 Subject: [PATCH 05/13] feat: add token response --- ApiDto/Responses/TokenResponse.cs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 ApiDto/Responses/TokenResponse.cs diff --git a/ApiDto/Responses/TokenResponse.cs b/ApiDto/Responses/TokenResponse.cs new file mode 100644 index 0000000..9761b87 --- /dev/null +++ b/ApiDto/Responses/TokenResponse.cs @@ -0,0 +1,23 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace Mirea.Api.Dto.Responses; + +/// +/// Provides a JWT and RT token. +/// +public class TokenResponse +{ + /// + /// A JWT token for accessing protected resources. + /// + [Required] + public required string AccessToken { get; set; } + + /// + /// The date and time when the JWT token expires. + /// + /// After this date, a new JWT token must be requested. + [Required] + public required DateTime ExpiresIn { get; set; } +} \ No newline at end of file -- 2.34.1 From 55371cb6751b5830b91d70a380fce9b3c22ac29b Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 21 Jun 2024 21:51:06 +0300 Subject: [PATCH 06/13] feat: add request to log in --- ApiDto/Requests/LoginRequest.cs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 ApiDto/Requests/LoginRequest.cs diff --git a/ApiDto/Requests/LoginRequest.cs b/ApiDto/Requests/LoginRequest.cs new file mode 100644 index 0000000..65fd4ec --- /dev/null +++ b/ApiDto/Requests/LoginRequest.cs @@ -0,0 +1,21 @@ +using System.ComponentModel.DataAnnotations; + +namespace Mirea.Api.Dto.Requests; + +/// +/// Request to receive protected content +/// +public class LoginRequest +{ + /// + /// Login or Email to identify the client. + /// + [Required] + public required string Username { get; set; } + + /// + /// The client's password. + /// + [Required] + public required string Password { get; set; } +} \ No newline at end of file -- 2.34.1 From c62ec33130560fab198e985831ec6b026b34719c Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 21 Jun 2024 21:51:23 +0300 Subject: [PATCH 07/13] feat: add user roles --- ApiDto/Common/AuthRoles.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 ApiDto/Common/AuthRoles.cs diff --git a/ApiDto/Common/AuthRoles.cs b/ApiDto/Common/AuthRoles.cs new file mode 100644 index 0000000..3d7529c --- /dev/null +++ b/ApiDto/Common/AuthRoles.cs @@ -0,0 +1,12 @@ +namespace Mirea.Api.Dto.Common; + +/// +/// An enumeration that indicates which role the user belongs to +/// +public enum AuthRoles +{ + /// + /// Administrator + /// + Admin +} \ No newline at end of file -- 2.34.1 From 160c7505f0ee87aa5d7f3710b7c0f0726b99e03d Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 21 Jun 2024 21:52:21 +0300 Subject: [PATCH 08/13] feat: add controller for authentication --- Endpoint/Controllers/V1/AuthController.cs | 140 ++++++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 Endpoint/Controllers/V1/AuthController.cs diff --git a/Endpoint/Controllers/V1/AuthController.cs b/Endpoint/Controllers/V1/AuthController.cs new file mode 100644 index 0000000..86b6fd7 --- /dev/null +++ b/Endpoint/Controllers/V1/AuthController.cs @@ -0,0 +1,140 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.Options; +using Mirea.Api.Dto.Common; +using Mirea.Api.Dto.Requests; +using Mirea.Api.Dto.Responses; +using Mirea.Api.Endpoint.Common.Model; +using Mirea.Api.Security.Common.Dto.Requests; +using Mirea.Api.Security.Services; +using System; +using System.Security; +using System.Threading.Tasks; + +namespace Mirea.Api.Endpoint.Controllers.V1; + +[ApiVersion("1.0")] +public class AuthController(IOptionsSnapshot user, AuthService auth, PasswordHashService passwordService) : BaseController, IActionFilter +{ + private string Fingerprint { get; set; } = string.Empty; + private string Ip { get; set; } = string.Empty; + private string UserAgent { get; set; } = string.Empty; + private string RefreshToken { get; set; } = string.Empty; + + private void SetCookie(string name, string value, DateTimeOffset? expires = null) + { + var cookieOptions = new CookieOptions + { + Expires = expires, + Path = "/api", + Domain = Request.Headers["X-Forwarded-Host"], + Secure = true, + HttpOnly = true + }; + + Response.Cookies.Append(name, value, cookieOptions); + } + + private void SetRefreshToken(string value, DateTimeOffset? expires = null) => + SetCookie("refresh_token", value, expires); + + private void SetFirstToken(string value, DateTimeOffset? expires = null) => + SetCookie("authentication_token", value, expires); + + [ApiExplorerSettings(IgnoreApi = true)] + public void OnActionExecuting(ActionExecutingContext context) + { + Ip = context.HttpContext.Connection.RemoteIpAddress?.ToString()!; + UserAgent = context.HttpContext.Request.Headers.UserAgent.ToString(); + Fingerprint = context.HttpContext.Request.Cookies["user_key"] ?? string.Empty; + RefreshToken = Request.Cookies["refresh_token"] ?? string.Empty; + + if (!string.IsNullOrWhiteSpace(Fingerprint)) return; + + Fingerprint = Guid.NewGuid().ToString().Replace("-", ""); + SetCookie("user_key", Fingerprint); + } + + [ApiExplorerSettings(IgnoreApi = true)] + public void OnActionExecuted(ActionExecutedContext context) { } + + [HttpPost("Login")] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + public async Task> Login([FromBody] LoginRequest request) + { + var userEntity = user.Value; + + if (!userEntity.Username.Equals(request.Username, StringComparison.OrdinalIgnoreCase) && + !userEntity.Email.Equals(request.Username, StringComparison.OrdinalIgnoreCase) || + !passwordService.VerifyPassword(request.Password, userEntity.Salt, userEntity.PasswordHash)) + return Unauthorized("Invalid username/email or password"); + + var token = await auth.GenerateAuthTokensAsync(new TokenRequest + { + Fingerprint = Fingerprint, + Ip = Ip, + UserAgent = UserAgent + }, "1"); + + SetRefreshToken(token.RefreshToken, token.RefreshExpiresIn); + + return Ok(new TokenResponse + { + AccessToken = token.AccessToken, + ExpiresIn = token.AccessExpiresIn + }); + } + + [HttpGet("ReLogin")] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + public async Task> ReLogin() + { + if (string.IsNullOrEmpty(RefreshToken)) + return Unauthorized(); + + try + { + var token = await auth.RefreshTokenAsync( + new TokenRequest + { + Ip = Ip, + UserAgent = UserAgent, + Fingerprint = Fingerprint + }, + RefreshToken + ); + + SetRefreshToken(token.RefreshToken, token.RefreshExpiresIn); + + return Ok(new TokenResponse + { + AccessToken = token.AccessToken, + ExpiresIn = token.AccessExpiresIn + }); + } + catch (SecurityException) + { + return Unauthorized(); + } + } + + [HttpGet("Logout")] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [Authorize] + public async Task Logout() + { + SetRefreshToken("", DateTimeOffset.MinValue); + SetFirstToken("", DateTimeOffset.MinValue); + + await auth.LogoutAsync(Fingerprint); + + return Ok(); + } + + [HttpGet("GetRole")] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [Authorize] + public ActionResult GetRole() => Ok(AuthRoles.Admin); +} -- 2.34.1 From 8d4c482bbd40775e2a7c7f1f3b0e5cc2d8d4af02 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Thu, 27 Jun 2024 23:37:40 +0300 Subject: [PATCH 09/13] fix: set correct cache key --- Security/Services/AuthService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Security/Services/AuthService.cs b/Security/Services/AuthService.cs index 2aa01d7..d932dd1 100644 --- a/Security/Services/AuthService.cs +++ b/Security/Services/AuthService.cs @@ -72,7 +72,7 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I authToken.UserAgent != request.UserAgent && authToken.Ip != request.Ip) { - await cache.RemoveAsync(request.Fingerprint, cancellation); + await cache.RemoveAsync(GetAuthCacheKey(request.Fingerprint), cancellation); await RevokeAccessToken(authToken.AccessToken); throw new SecurityException(request.Fingerprint); -- 2.34.1 From 612efcb91cc660221f1731fbe6b3c9380e47c9fa Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 28 Jun 2024 01:49:45 +0300 Subject: [PATCH 10/13] fix: exception if value is null --- Endpoint/Common/Services/Security/MemoryCacheService.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Endpoint/Common/Services/Security/MemoryCacheService.cs b/Endpoint/Common/Services/Security/MemoryCacheService.cs index fcb1f09..6c5b8f6 100644 --- a/Endpoint/Common/Services/Security/MemoryCacheService.cs +++ b/Endpoint/Common/Services/Security/MemoryCacheService.cs @@ -23,8 +23,11 @@ public class MemoryCacheService(IMemoryCache cache) : ICacheService public Task GetAsync(string key, CancellationToken cancellationToken = default) { - cache.TryGetValue(key, out byte[]? value); - return Task.FromResult(JsonSerializer.Deserialize(value)); + return Task.FromResult( + cache.TryGetValue(key, out byte[]? value) ? + JsonSerializer.Deserialize(value) : + default + ); } public Task RemoveAsync(string key, CancellationToken cancellationToken = default) -- 2.34.1 From f89136669d5fb7c9620ecdaacf78c8ad1c236558 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 28 Jun 2024 22:52:05 +0300 Subject: [PATCH 11/13] fix: change RT in cache after generation --- Security/Services/AuthService.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Security/Services/AuthService.cs b/Security/Services/AuthService.cs index d932dd1..5b293b3 100644 --- a/Security/Services/AuthService.cs +++ b/Security/Services/AuthService.cs @@ -81,14 +81,18 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I var (token, expireIn) = GenerateAccessToken(authToken.UserId); await RevokeAccessToken(authToken.AccessToken); + var newRefreshToken = GenerateRefreshToken(); + authToken.AccessToken = token; + authToken.RefreshToken = newRefreshToken; + await SetAuthTokenDataToCache(request.Fingerprint, authToken, cancellation); return new AuthTokenResponse { AccessToken = token, AccessExpiresIn = expireIn, - RefreshToken = GenerateRefreshToken(), + RefreshToken = newRefreshToken, RefreshExpiresIn = DateTime.UtcNow.Add(Lifetime) }; } -- 2.34.1 From 2c112d00df883a3fc57dd8556b7a54ab595830d6 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 28 Jun 2024 22:52:58 +0300 Subject: [PATCH 12/13] fix: add Admin model to configuration --- Endpoint/Program.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Endpoint/Program.cs b/Endpoint/Program.cs index a904e98..d368177 100644 --- a/Endpoint/Program.cs +++ b/Endpoint/Program.cs @@ -6,6 +6,7 @@ using Mirea.Api.DataAccess.Application; using Mirea.Api.DataAccess.Persistence; using Mirea.Api.DataAccess.Persistence.Common; using Mirea.Api.Endpoint.Common.Interfaces; +using Mirea.Api.Endpoint.Common.Model; using Mirea.Api.Endpoint.Common.Services; using Mirea.Api.Endpoint.Configuration.AppConfig; using Mirea.Api.Endpoint.Configuration.General; @@ -37,6 +38,8 @@ public class Program builder.Configuration.AddConfiguration(EnvironmentConfiguration.GetEnvironment()); builder.Configuration.AddJsonFile(PathBuilder.Combine(GeneralConfig.FilePath), optional: true, reloadOnChange: true); builder.Services.Configure(builder.Configuration); + builder.Configuration.AddJsonFile(PathBuilder.Combine(Admin.PathToSave), optional: true, reloadOnChange: true); + builder.Services.Configure(builder.Configuration); builder.Host.AddCustomSerilog(); AddDatabase(builder.Services, builder.Configuration); -- 2.34.1 From 41b5bb571b7b2c3b98e20a6cda6d0e8fb2cd716f Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 28 Jun 2024 22:55:18 +0300 Subject: [PATCH 13/13] docs: add xml comments --- Endpoint/Controllers/V1/AuthController.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Endpoint/Controllers/V1/AuthController.cs b/Endpoint/Controllers/V1/AuthController.cs index 86b6fd7..dcd1821 100644 --- a/Endpoint/Controllers/V1/AuthController.cs +++ b/Endpoint/Controllers/V1/AuthController.cs @@ -60,6 +60,12 @@ public class AuthController(IOptionsSnapshot user, AuthService auth, Pass [ApiExplorerSettings(IgnoreApi = true)] public void OnActionExecuted(ActionExecutedContext context) { } + /// + /// Handles user authentication by verifying the username/email and password, + /// then generating and returning an authentication token if successful. + /// + /// The login request containing the username/email and password. + /// A TokenResponse containing the access token and its expiry if successful, otherwise an Unauthorized response. [HttpPost("Login")] [ProducesResponseType(StatusCodes.Status401Unauthorized)] public async Task> Login([FromBody] LoginRequest request) @@ -87,6 +93,10 @@ public class AuthController(IOptionsSnapshot user, AuthService auth, Pass }); } + /// + /// Refreshes the authentication token using the existing refresh token. + /// + /// A TokenResponse containing the new access token and its expiry if successful, otherwise an Unauthorized response. [HttpGet("ReLogin")] [ProducesResponseType(StatusCodes.Status401Unauthorized)] public async Task> ReLogin() @@ -120,6 +130,10 @@ public class AuthController(IOptionsSnapshot user, AuthService auth, Pass } } + /// + /// Logs the user out by clearing the refresh token and performing any necessary cleanup. + /// + /// An Ok response if the logout was successful. [HttpGet("Logout")] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [Authorize] @@ -133,6 +147,10 @@ public class AuthController(IOptionsSnapshot user, AuthService auth, Pass return Ok(); } + /// + /// Retrieves the role of the authenticated user. + /// + /// The role of the authenticated user. [HttpGet("GetRole")] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [Authorize] -- 2.34.1