chris cf6ce0d736 Implement token caching and refresh mechanism for auth.
Added distributed caching for access and refresh tokens in both `AuthController` and `AppProxyController`. Introduced logic to handle token expiration and refresh, ensuring continuous authorization through token renewal and session management.
2025-05-31 02:16:10 +02:00

116 lines
3.6 KiB
C#

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Distributed;
using Suspectus.Gandalf.Core.Abstractions.CQRS.Commands;
using Suspectus.Gandalf.Core.Abstractions.DTOs.Internal.Auth;
using Suspectus.Gandalf.Core.Abstractions.Extensions;
using Suspectus.Gandalf.Palantir.Client;
namespace Suspectus.Gandalf.Bridgekeeper.Api.Controllers;
[ApiController]
[Route("api/[controller]")]
public class AuthController : ControllerBase
{
private readonly IPalantirClient _client;
private readonly IDistributedCache _tokenCache;
public AuthController(IPalantirClient client, IDistributedCache tokenCache)
{
_client = client;
_tokenCache = tokenCache;
}
[HttpGet("[action]")]
public async Task<IActionResult> Check()
{
return Ok(true);
}
[HttpPost("[action]")]
public async Task<IActionResult> Login([FromBody] ValidateCredentialsCommand validateCredentialsCommand, [FromQuery] string? clientId, CancellationToken cancellationToken)
{
if (clientId is null)
{
var masterAppInfoResponse = await _client.Internal.App.GetMasterInfo();
if (masterAppInfoResponse.IsFaulted)
{
return BadRequest($"Failed to retrieve master app info.");
}
clientId = masterAppInfoResponse.GetValue().Id;
}
var tokenRequestCommandResponse = await _client.Internal.Auth.Token(new TokenRequestCommand
{
Password = validateCredentialsCommand.Password,
Username = validateCredentialsCommand.UsernameOrEmail,
GrandType = GrandType.Password,
ClientId = clientId
});
if (tokenRequestCommandResponse.IsFaulted)
{
return tokenRequestCommandResponse.Match<IActionResult>(
BadRequest,
e => BadRequest($"{e.Message}\n{e.InnerException?.Message}")
);
}
var tokenRequestResponse = tokenRequestCommandResponse.GetValue();
if (tokenRequestResponse is null)
{
return BadRequest("Invalid username or password.");
}
await _tokenCache.SetStringAsync(
GetCacheKey(tokenRequestResponse.SubjectId, clientId, "access_token"),
tokenRequestResponse.AccessToken,
new DistributedCacheEntryOptions
{
AbsoluteExpiration = tokenRequestResponse.AccessTokenExpiresAt.AddSeconds(-10),
},
cancellationToken
);
await _tokenCache.SetStringAsync(
GetCacheKey(tokenRequestResponse.SubjectId, clientId, "refresh_token"),
tokenRequestResponse.RefreshToken,
new DistributedCacheEntryOptions
{
AbsoluteExpiration = tokenRequestResponse.RefreshTokenExpiresAt.AddSeconds(-10),
},
cancellationToken
);
Response.Cookies.Append("MithrandirSession", tokenRequestResponse.SubjectId, new CookieOptions
{
Secure = true,
HttpOnly = true,
SameSite = SameSiteMode.Lax,
Expires = tokenRequestResponse.RefreshTokenExpiresAt.AddSeconds(-10)
});
return Ok();
}
[HttpGet("[action]")]
public async Task<IActionResult> Logout()
{
Response.Cookies.Delete("MithrandirSession");
return Ok();
}
[HttpPost("[action]")]
public async Task<IActionResult> Register()
{
return Ok(true);
}
private string GetCacheKey(string subjectId, string appId, string tokenType)
{
return $"{subjectId}:{appId}:{tokenType}";
}
}