Files
aliasvault/apps/server/AliasVault.Api/Controllers/Security/SecurityController.cs
2025-05-13 16:47:16 +02:00

124 lines
4.4 KiB
C#

//-----------------------------------------------------------------------
// <copyright file="SecurityController.cs" company="lanedirt">
// Copyright (c) lanedirt. All rights reserved.
// Licensed under the AGPLv3 license. See LICENSE.md file in the project root for full license information.
// </copyright>
//-----------------------------------------------------------------------
namespace AliasVault.Api.Controllers.Security;
using AliasServerDb;
using AliasVault.Api.Controllers.Abstracts;
using AliasVault.Shared.Models.Enums;
using AliasVault.Shared.Models.WebApi.Security;
using Asp.Versioning;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
/// <summary>
/// Security controller for handling security related actions such as showing auth logs or revoking sessions for user.
/// </summary>
/// <param name="dbContextFactory">AliasServerDbContext instance.</param>
/// <param name="userManager">UserManager instance.</param>
[Route("v{version:apiVersion}/[controller]")]
[ApiController]
[ApiVersion("1")]
public class SecurityController(IAliasServerDbContextFactory dbContextFactory, UserManager<AliasVaultUser> userManager) : AuthenticatedRequestController(userManager)
{
/// <summary>
/// Returns list of active sessions (refresh tokens) for the current user.
/// </summary>
/// <returns>Task with list of active refresh tokens.</returns>
[HttpGet("sessions")]
public async Task<IActionResult> Sessions()
{
var user = await GetCurrentUserAsync();
if (user is null)
{
return Unauthorized("Not authenticated.");
}
await using var context = await dbContextFactory.CreateDbContextAsync();
var refreshTokenList = await context.AliasVaultUserRefreshTokens.Where(x => x.UserId == user.Id).Select(x => new RefreshTokenModel()
{
Id = x.Id,
DeviceIdentifier = x.DeviceIdentifier,
ExpireDate = x.ExpireDate,
CreatedAt = x.CreatedAt,
})
.Where(x => x.ExpireDate > DateTime.UtcNow)
.OrderByDescending(x => x.CreatedAt)
.ToListAsync();
return Ok(refreshTokenList);
}
/// <summary>
/// Revokes a specific user session (refresh token).
/// </summary>
/// <param name="id">The ID of the refresh token to revoke.</param>
/// <returns>Http200 if success.</returns>
[HttpDelete("sessions/{id}")]
public async Task<IActionResult> RevokeSession(Guid id)
{
var user = await GetCurrentUserAsync();
if (user is null)
{
return Unauthorized("Not authenticated.");
}
await using var context = await dbContextFactory.CreateDbContextAsync();
var refreshToken = await context.AliasVaultUserRefreshTokens
.FirstOrDefaultAsync(x => x.Id == id && x.UserId == user.Id);
if (refreshToken == null)
{
return NotFound("Session not found or does not belong to the current user.");
}
context.AliasVaultUserRefreshTokens.Remove(refreshToken);
await context.SaveChangesAsync();
return Ok();
}
/// <summary>
/// Returns the last 50 authentication logs for the current user.
/// </summary>
/// <returns>Task with a list of the last 50 authentication logs.</returns>
[HttpGet("authlogs")]
public async Task<IActionResult> GetUserAuthLogs()
{
var user = await GetCurrentUserAsync();
if (user is null)
{
return Unauthorized("Not authenticated.");
}
await using var context = await dbContextFactory.CreateDbContextAsync();
var authLogs = await context.AuthLogs
.Where(x => x.Username == user.UserName)
.Where(x => x.EventType != AuthEventType.TokenRefresh)
.OrderByDescending(x => x.Timestamp)
.Take(50)
.Select(x => new AuthLogModel
{
Id = x.Id,
Timestamp = x.Timestamp,
EventType = x.EventType,
Username = x.Username,
IpAddress = x.IpAddress ?? string.Empty,
UserAgent = x.UserAgent ?? string.Empty,
Client = x.Client ?? string.Empty,
IsSuccess = x.IsSuccess,
})
.ToListAsync();
return Ok(authLogs);
}
}