diff --git a/API/Controller/Account/Authenticated/Logout.cs b/API/Controller/Account/Authenticated/Logout.cs new file mode 100644 index 00000000..a330362b --- /dev/null +++ b/API/Controller/Account/Authenticated/Logout.cs @@ -0,0 +1,55 @@ +using Microsoft.AspNetCore.Mvc; +using OpenShock.Common.Authentication.Attributes; +using OpenShock.Common.Authentication.Services; +using OpenShock.Common.Problems; + +namespace OpenShock.API.Controller.Account.Authenticated; + +public sealed partial class AuthenticatedAccountController +{ + [HttpDelete("logout")] + [UserSessionOnly] + [ProducesSlimSuccess] + public async Task Logout( + [FromServices] IUserReferenceService userReferenceService, + [FromServices] ApiConfig apiConfig) + { + var x = userReferenceService.AuthReference; + + if (x == null) throw new Exception("This should not be reachable due to AuthenticatedSession requirement"); + if (!x.Value.IsT0) throw new Exception("This should not be reachable due to the [UserSessionOnly] attribute"); + + var session = x.Value.AsT0; + + await _sessionService.DeleteSession(session); + + var cookieDomainToUse = apiConfig.Frontend.CookieDomain.Split(',').FirstOrDefault(domain => Request.Headers.Host.ToString().EndsWith(domain, StringComparison.OrdinalIgnoreCase)); + if (cookieDomainToUse != null) + { + HttpContext.Response.Cookies.Append("openShockSession", string.Empty, new CookieOptions + { + Expires = DateTimeOffset.FromUnixTimeSeconds(0), + Secure = true, + HttpOnly = true, + SameSite = SameSiteMode.Strict, + Domain = "." + cookieDomainToUse + }); + } + else // Fallback to all domains + { + foreach (var stringValue in apiConfig.Frontend.CookieDomain.Split(',')) + { + HttpContext.Response.Cookies.Append("openShockSession", string.Empty, new CookieOptions + { + Expires = DateTimeOffset.FromUnixTimeSeconds(0), + Secure = true, + HttpOnly = true, + SameSite = SameSiteMode.Strict, + Domain = "." + stringValue + }); + } + } + + return RespondSlimSuccess(); + } +} \ No newline at end of file diff --git a/API/Controller/Account/Authenticated/_ApiController.cs b/API/Controller/Account/Authenticated/_ApiController.cs index c4c1258e..59986d12 100644 --- a/API/Controller/Account/Authenticated/_ApiController.cs +++ b/API/Controller/Account/Authenticated/_ApiController.cs @@ -1,6 +1,7 @@ using Asp.Versioning; using Microsoft.AspNetCore.Mvc; using OpenShock.API.Services.Account; +using OpenShock.API.Services.Session; using OpenShock.Common.Authentication.Attributes; using OpenShock.Common.Authentication.ControllerBase; using OpenShock.Common.OpenShockDb; @@ -21,12 +22,19 @@ public sealed partial class AuthenticatedAccountController : AuthenticatedSessio private readonly IRedisConnectionProvider _redis; private readonly ILogger _logger; private readonly IAccountService _accountService; + private readonly ISessionService _sessionService; - public AuthenticatedAccountController(OpenShockContext db, IRedisConnectionProvider redis, ILogger logger, IAccountService accountService) + public AuthenticatedAccountController( + OpenShockContext db, + IRedisConnectionProvider redis, + ILogger logger, + IAccountService accountService, + ISessionService sessionService) { _db = db; _redis = redis; _logger = logger; _accountService = accountService; + _sessionService = sessionService; } } \ No newline at end of file diff --git a/API/Services/Session/ISessionService.cs b/API/Services/Session/ISessionService.cs index 2b015b88..f287a6c7 100644 --- a/API/Services/Session/ISessionService.cs +++ b/API/Services/Session/ISessionService.cs @@ -9,7 +9,8 @@ public interface ISessionService { public Task> ListSessions(Guid userId); - public Task GetSession(Guid userId); + public Task GetSession(Guid sessionId); + public Task DeleteSession(Guid sessionId); public Task DeleteSession(LoginSession loginSession); } \ No newline at end of file diff --git a/API/Services/Session/SessionService.cs b/API/Services/Session/SessionService.cs index 0284fe79..bb06b7bf 100644 --- a/API/Services/Session/SessionService.cs +++ b/API/Services/Session/SessionService.cs @@ -47,6 +47,15 @@ public async Task> ListSessions(Guid userId) .FirstOrDefaultAsync(); } + public async Task DeleteSession(Guid sessionId) + { + var session = await GetSession(sessionId); + if (session == null) return false; + + await _loginSessions.DeleteAsync(session); + return true; + } + public async Task DeleteSession(LoginSession loginSession) { await _loginSessions.DeleteAsync(loginSession);