mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-05-07 14:56:02 -04:00
Add new user sessions endpoint to webapi (#80)
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
namespace AliasVault.Api.Controllers;
|
||||
namespace AliasVault.Api.Controllers.Abstracts;
|
||||
|
||||
using System.Security.Claims;
|
||||
using AliasServerDb;
|
||||
@@ -20,8 +20,14 @@ using Microsoft.AspNetCore.Mvc;
|
||||
[Route("api/v{version:apiVersion}/[controller]")]
|
||||
[ApiController]
|
||||
[Authorize]
|
||||
public class AuthenticatedRequestController(UserManager<AliasVaultUser> userManager) : ControllerBase
|
||||
public abstract class AuthenticatedRequestController(UserManager<AliasVaultUser> userManager) : ControllerBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the userManager instance.
|
||||
/// </summary>
|
||||
/// <returns>UserManager instance.</returns>
|
||||
protected UserManager<AliasVaultUser> GetUserManager() => userManager;
|
||||
|
||||
/// <summary>
|
||||
/// Get the current authenticated user.
|
||||
/// </summary>
|
||||
@@ -5,9 +5,10 @@
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
namespace AliasVault.Api.Controllers;
|
||||
namespace AliasVault.Api.Controllers.Email;
|
||||
|
||||
using AliasServerDb;
|
||||
using AliasVault.Api.Controllers.Abstracts;
|
||||
using AliasVault.Api.Helpers;
|
||||
using AliasVault.Shared.Models.Spamok;
|
||||
using AliasVault.Shared.Models.WebApi;
|
||||
@@ -5,9 +5,10 @@
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
namespace AliasVault.Api.Controllers;
|
||||
namespace AliasVault.Api.Controllers.Email;
|
||||
|
||||
using AliasServerDb;
|
||||
using AliasVault.Api.Controllers.Abstracts;
|
||||
using AliasVault.Api.Helpers;
|
||||
using AliasVault.Shared.Models.Spamok;
|
||||
using Asp.Versioning;
|
||||
@@ -8,6 +8,7 @@
|
||||
namespace AliasVault.Api.Controllers;
|
||||
|
||||
using AliasServerDb;
|
||||
using AliasVault.Api.Controllers.Abstracts;
|
||||
using AliasVault.Shared.Models.WebApi.Favicon;
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="IdentityController.cs" company="lanedirt">
|
||||
// Copyright (c) lanedirt. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
namespace AliasVault.Api.Controllers;
|
||||
|
||||
using AliasGenerators.Identity.Implementations;
|
||||
using AliasServerDb;
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
/// <summary>
|
||||
/// Controller for identity generation.
|
||||
/// </summary>
|
||||
/// <param name="userManager">UserManager instance.</param>
|
||||
[ApiVersion("1")]
|
||||
public class IdentityController(UserManager<AliasVaultUser> userManager) : AuthenticatedRequestController(userManager)
|
||||
{
|
||||
/// <summary>
|
||||
/// Proxies the request to the english identity generator to generate a random identity.
|
||||
/// </summary>
|
||||
/// <returns>Identity model.</returns>
|
||||
[HttpGet("Generate")]
|
||||
public async Task<IActionResult> Generate()
|
||||
{
|
||||
var user = await GetCurrentUserAsync();
|
||||
if (user == null)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
var identityGenerator = new IdentityGeneratorEn();
|
||||
return Ok(await identityGenerator.GenerateRandomIdentityAsync());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="SecurityController.cs" company="lanedirt">
|
||||
// Copyright (c) lanedirt. All rights reserved.
|
||||
// Licensed under the MIT 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.AuthLogging;
|
||||
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("api/v{version:apiVersion}/[controller]")]
|
||||
[ApiController]
|
||||
[ApiVersion("1")]
|
||||
public class SecurityController(IDbContextFactory<AliasServerDbContext> 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,
|
||||
})
|
||||
.OrderBy(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();
|
||||
}
|
||||
}
|
||||
@@ -5,10 +5,11 @@
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
namespace AliasVault.Api.Controllers;
|
||||
namespace AliasVault.Api.Controllers.Security;
|
||||
|
||||
using System.Text.Encodings.Web;
|
||||
using AliasServerDb;
|
||||
using AliasVault.Api.Controllers.Abstracts;
|
||||
using AliasVault.AuthLogging;
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
@@ -19,13 +20,13 @@ using Microsoft.EntityFrameworkCore;
|
||||
/// Auth controller for handling authentication.
|
||||
/// </summary>
|
||||
/// <param name="dbContextFactory">AliasServerDbContext instance.</param>
|
||||
/// <param name="userManager">UserManager instance.</param>
|
||||
/// <param name="urlEncoder">UrlEncoder instance.</param>
|
||||
/// <param name="authLoggingService">AuthLoggingService instance. This is used to log auth attempts to the database.</param>
|
||||
/// <param name="userManager">UserManager instance.</param>
|
||||
[Route("api/v{version:apiVersion}/[controller]")]
|
||||
[ApiController]
|
||||
[ApiVersion("1")]
|
||||
public class TwoFactorAuthController(IDbContextFactory<AliasServerDbContext> dbContextFactory, UserManager<AliasVaultUser> userManager, UrlEncoder urlEncoder, AuthLoggingService authLoggingService) : ControllerBase
|
||||
public class TwoFactorAuthController(IDbContextFactory<AliasServerDbContext> dbContextFactory, UrlEncoder urlEncoder, AuthLoggingService authLoggingService, UserManager<AliasVaultUser> userManager) : AuthenticatedRequestController(userManager)
|
||||
{
|
||||
/// <summary>
|
||||
/// Get two-factor authentication enabled status for a user.
|
||||
@@ -34,13 +35,13 @@ public class TwoFactorAuthController(IDbContextFactory<AliasServerDbContext> dbC
|
||||
[HttpGet("status")]
|
||||
public async Task<IActionResult> Status()
|
||||
{
|
||||
var user = await userManager.GetUserAsync(User);
|
||||
if (user == null)
|
||||
var user = await GetCurrentUserAsync();
|
||||
if (user is null)
|
||||
{
|
||||
return NotFound();
|
||||
return Unauthorized("Not authenticated.");
|
||||
}
|
||||
|
||||
var twoFactorEnabled = await userManager.GetTwoFactorEnabledAsync(user);
|
||||
var twoFactorEnabled = await GetUserManager().GetTwoFactorEnabledAsync(user);
|
||||
return Ok(new { TwoFactorEnabled = twoFactorEnabled });
|
||||
}
|
||||
|
||||
@@ -51,17 +52,17 @@ public class TwoFactorAuthController(IDbContextFactory<AliasServerDbContext> dbC
|
||||
[HttpPost("enable")]
|
||||
public async Task<IActionResult> Enable()
|
||||
{
|
||||
var user = await userManager.GetUserAsync(User);
|
||||
if (user == null)
|
||||
var user = await GetCurrentUserAsync();
|
||||
if (user is null)
|
||||
{
|
||||
return NotFound();
|
||||
return Unauthorized("Not authenticated.");
|
||||
}
|
||||
|
||||
var authenticatorKey = await userManager.GetAuthenticatorKeyAsync(user);
|
||||
var authenticatorKey = await GetUserManager().GetAuthenticatorKeyAsync(user);
|
||||
if (string.IsNullOrEmpty(authenticatorKey))
|
||||
{
|
||||
await userManager.ResetAuthenticatorKeyAsync(user);
|
||||
authenticatorKey = await userManager.GetAuthenticatorKeyAsync(user);
|
||||
await GetUserManager().ResetAuthenticatorKeyAsync(user);
|
||||
authenticatorKey = await GetUserManager().GetAuthenticatorKeyAsync(user);
|
||||
}
|
||||
|
||||
var encodedKey = urlEncoder.Encode(authenticatorKey!);
|
||||
@@ -78,20 +79,20 @@ public class TwoFactorAuthController(IDbContextFactory<AliasServerDbContext> dbC
|
||||
[HttpPost("verify")]
|
||||
public async Task<IActionResult> Verify([FromBody] string code)
|
||||
{
|
||||
var user = await userManager.GetUserAsync(User);
|
||||
if (user == null)
|
||||
var user = await GetCurrentUserAsync();
|
||||
if (user is null)
|
||||
{
|
||||
return NotFound();
|
||||
return Unauthorized("Not authenticated.");
|
||||
}
|
||||
|
||||
var isValid = await userManager.VerifyTwoFactorTokenAsync(user, userManager.Options.Tokens.AuthenticatorTokenProvider, code);
|
||||
var isValid = await GetUserManager().VerifyTwoFactorTokenAsync(user, GetUserManager().Options.Tokens.AuthenticatorTokenProvider, code);
|
||||
|
||||
if (isValid)
|
||||
{
|
||||
await userManager.SetTwoFactorEnabledAsync(user, true);
|
||||
await GetUserManager().SetTwoFactorEnabledAsync(user, true);
|
||||
|
||||
// Generate new recovery codes.
|
||||
var recoveryCodes = await userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10);
|
||||
var recoveryCodes = await GetUserManager().GenerateNewTwoFactorRecoveryCodesAsync(user, 10);
|
||||
|
||||
await authLoggingService.LogAuthEventSuccessAsync(user.UserName!, AuthEventType.TwoFactorAuthEnable);
|
||||
|
||||
@@ -108,16 +109,16 @@ public class TwoFactorAuthController(IDbContextFactory<AliasServerDbContext> dbC
|
||||
[HttpPost("disable")]
|
||||
public async Task<IActionResult> Disable()
|
||||
{
|
||||
var user = await userManager.GetUserAsync(User);
|
||||
if (user == null)
|
||||
var user = await GetCurrentUserAsync();
|
||||
if (user is null)
|
||||
{
|
||||
return NotFound();
|
||||
return Unauthorized("Not authenticated.");
|
||||
}
|
||||
|
||||
await using var context = await dbContextFactory.CreateDbContextAsync();
|
||||
|
||||
// Disable 2FA and remove any existing authenticator key(s) and recovery codes.
|
||||
await userManager.SetTwoFactorEnabledAsync(user, false);
|
||||
await GetUserManager().SetTwoFactorEnabledAsync(user, false);
|
||||
context.UserTokens.RemoveRange(
|
||||
await context.UserTokens.Where(
|
||||
x => x.UserId == user.Id &&
|
||||
@@ -11,9 +11,10 @@
|
||||
* attack surfaces we don't include this file in the production build.
|
||||
*/
|
||||
|
||||
namespace AliasVault.Api.Controllers;
|
||||
namespace AliasVault.Api.Controllers.Tests;
|
||||
|
||||
using AliasServerDb;
|
||||
using AliasVault.Api.Controllers.Abstracts;
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
@@ -9,6 +9,7 @@ namespace AliasVault.Api.Controllers;
|
||||
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using AliasServerDb;
|
||||
using AliasVault.Api.Controllers.Abstracts;
|
||||
using AliasVault.Api.Helpers;
|
||||
using AliasVault.Api.Vault;
|
||||
using AliasVault.Api.Vault.RetentionRules;
|
||||
|
||||
@@ -97,21 +97,6 @@ public sealed class CredentialService(HttpClient httpClient, DbService dbService
|
||||
return domainToUse;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate random identity by calling the IdentityGenerator API.
|
||||
/// </summary>
|
||||
/// <returns>Identity object.</returns>
|
||||
public async Task<Identity> GenerateRandomIdentityAsync()
|
||||
{
|
||||
var identity = await httpClient.GetFromJsonAsync<Identity>("api/v1/Identity/Generate");
|
||||
if (identity is null)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to generate random identity.");
|
||||
}
|
||||
|
||||
return identity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Insert new entry into database.
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="RefreshTokenModel.cs" company="lanedirt">
|
||||
// Copyright (c) lanedirt. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
namespace AliasVault.Shared.Models.WebApi.Security;
|
||||
|
||||
/// <summary>
|
||||
/// RefreshToken (user session) model.
|
||||
/// </summary>
|
||||
public class RefreshTokenModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the unique identifier for the refresh token.
|
||||
/// </summary>
|
||||
public Guid Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the device identifier associated with the refresh token.
|
||||
/// </summary>
|
||||
public string DeviceIdentifier { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the expiration date of the refresh token.
|
||||
/// </summary>
|
||||
public DateTime ExpireDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the creation date of the refresh token.
|
||||
/// </summary>
|
||||
public DateTime CreatedAt { get; set; }
|
||||
}
|
||||
Reference in New Issue
Block a user