Skip to content

Commit

Permalink
Merge pull request #471 from Inxton/470-_NEW-FEATURE_Auto_log-out_for…
Browse files Browse the repository at this point in the history
…_users

[NEW-FEATURE] Auto log-out for users
  • Loading branch information
PTKu authored Jan 28, 2025
2 parents 54d6919 + 6ee0abe commit 318eda3
Show file tree
Hide file tree
Showing 23 changed files with 696 additions and 91 deletions.
140 changes: 140 additions & 0 deletions src/Security/Security.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
Microsoft Visual Studio Solution File, Format Version 12.00
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "this", "this.proj", "{838B2E33-ECE5-4D43-83B3-6B4E983A5FD5}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AXOpen.Base.Abstractions", "..\base\src\AXOpen.Base.Abstractions\AXOpen.Base.Abstractions.csproj", "{665D0CFD-82C4-4265-9304-EB14463C5C59}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AXOpen.Data.InMemory", "..\data\src\repositories\InMemory\AXOpen.Data.InMemory.csproj", "{10D92A1A-0B43-464A-BE99-70C47A48118F}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AXOpen.Data.Json", "..\data\src\repositories\Json\AXOpen.Data.Json.csproj", "{5B8F59BA-D635-4ED6-AD9C-30A85BCCDEA2}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AXOpen.Data.MongoDb", "..\data\src\repositories\MongoDb\AXOpen.Data.MongoDb.csproj", "{4F8519F2-4133-4EED-AA2C-F920E16437DF}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AXOpen.Data.RavenDb", "..\data\src\repositories\RavenDb\AXOpen.Data.RavenDb\AXOpen.Data.RavenDb.csproj", "{43311BB6-E215-4C5C-BE8E-E3B645B011F5}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "axopen_blazor_auth_app", "integrations\axopen_blazor_auth_app\axopen_blazor_auth_app.csproj", "{2773528C-64E5-4554-93C4-E02DC7565EDC}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AXOpen.Security.Blazor", "src\AXOpen.Security.Blazor\AXOpen.Security.Blazor.csproj", "{9DB96930-A1F2-4788-A8B7-D1818FD3F760}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AXOpen.Security", "src\AXOpen.Security\AXOpen.Security.csproj", "{9EE55814-E7E8-46D6-8C4B-E1B3AE340346}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AXOpen.SecurityTests_L1", "tests\axopen_security_tests\AXOpen.SecurityTests_L1.csproj", "{2B512EE1-4B9E-44AB-A534-0C9C97FFCB16}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AXOpen.Base.Abstractions", "..\base\src\AXOpen.Base.Abstractions", "{83F31AB1-B15C-4EF6-8E53-A81A53302521}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "..\base\src", "{411A6B73-18F1-48C5-87D4-826EC2D8D647}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "base", "..\base", "{A925B0D9-3B08-47C2-9D5E-D6A524E190E5}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "InMemory", "..\data\src\repositories\InMemory", "{5D0DFD2A-EA1B-40BD-A673-E8A284E2AF29}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Json", "..\data\src\repositories\Json", "{0D7E1332-22B0-4875-85A5-8EA9538167A7}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MongoDb", "..\data\src\repositories\MongoDb", "{F8754EB9-301E-4A3F-807A-0AAE58EFAC37}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AXOpen.Data.RavenDb", "..\data\src\repositories\RavenDb\AXOpen.Data.RavenDb", "{897EA375-B99B-4B86-BF57-0EED905B9458}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "RavenDb", "..\data\src\repositories\RavenDb", "{CEA0B298-02C1-457E-A371-CA25344C7BDC}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "repositories", "..\data\src\repositories", "{B4061626-3263-4217-B0B4-F6B9F0FCD2DD}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "..\data\src", "{0659C482-2A98-4009-94FE-9334B0CA0351}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "data", "..\data", "{B0F5E97B-74A3-45A5-AEAD-3166553D681B}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "axopen_blazor_auth_app", "integrations\axopen_blazor_auth_app", "{E6B1B7C6-58C7-4B90-9112-1AE2DB3BC87C}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "integrations", "integrations", "{36701393-9B31-4C6B-9D41-51431B729B6C}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AXOpen.Security.Blazor", "src\AXOpen.Security.Blazor", "{5C3885F1-84BF-4D06-A355-EF37AF617ECD}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AXOpen.Security", "src\AXOpen.Security", "{27B03F8E-58E1-4004-93B5-9604C4AC6F64}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{103CD471-C355-4E98-8930-91D111FBE3A5}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "axopen_security_tests", "tests\axopen_security_tests", "{4E0482DA-A1AE-4724-BA84-CAF28ADF387A}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{BB0B9DEF-C0E9-4C71-9DA9-0184B4E25A50}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Security", "..\Security", "{BA61E15F-E917-4F5B-A66B-9633788E7062}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{838B2E33-ECE5-4D43-83B3-6B4E983A5FD5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{838B2E33-ECE5-4D43-83B3-6B4E983A5FD5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{838B2E33-ECE5-4D43-83B3-6B4E983A5FD5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{838B2E33-ECE5-4D43-83B3-6B4E983A5FD5}.Release|Any CPU.Build.0 = Release|Any CPU
{665D0CFD-82C4-4265-9304-EB14463C5C59}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{665D0CFD-82C4-4265-9304-EB14463C5C59}.Debug|Any CPU.Build.0 = Debug|Any CPU
{665D0CFD-82C4-4265-9304-EB14463C5C59}.Release|Any CPU.ActiveCfg = Release|Any CPU
{665D0CFD-82C4-4265-9304-EB14463C5C59}.Release|Any CPU.Build.0 = Release|Any CPU
{10D92A1A-0B43-464A-BE99-70C47A48118F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{10D92A1A-0B43-464A-BE99-70C47A48118F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{10D92A1A-0B43-464A-BE99-70C47A48118F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{10D92A1A-0B43-464A-BE99-70C47A48118F}.Release|Any CPU.Build.0 = Release|Any CPU
{5B8F59BA-D635-4ED6-AD9C-30A85BCCDEA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5B8F59BA-D635-4ED6-AD9C-30A85BCCDEA2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5B8F59BA-D635-4ED6-AD9C-30A85BCCDEA2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5B8F59BA-D635-4ED6-AD9C-30A85BCCDEA2}.Release|Any CPU.Build.0 = Release|Any CPU
{4F8519F2-4133-4EED-AA2C-F920E16437DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4F8519F2-4133-4EED-AA2C-F920E16437DF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4F8519F2-4133-4EED-AA2C-F920E16437DF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4F8519F2-4133-4EED-AA2C-F920E16437DF}.Release|Any CPU.Build.0 = Release|Any CPU
{43311BB6-E215-4C5C-BE8E-E3B645B011F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{43311BB6-E215-4C5C-BE8E-E3B645B011F5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{43311BB6-E215-4C5C-BE8E-E3B645B011F5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{43311BB6-E215-4C5C-BE8E-E3B645B011F5}.Release|Any CPU.Build.0 = Release|Any CPU
{2773528C-64E5-4554-93C4-E02DC7565EDC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2773528C-64E5-4554-93C4-E02DC7565EDC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2773528C-64E5-4554-93C4-E02DC7565EDC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2773528C-64E5-4554-93C4-E02DC7565EDC}.Release|Any CPU.Build.0 = Release|Any CPU
{9DB96930-A1F2-4788-A8B7-D1818FD3F760}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9DB96930-A1F2-4788-A8B7-D1818FD3F760}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9DB96930-A1F2-4788-A8B7-D1818FD3F760}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9DB96930-A1F2-4788-A8B7-D1818FD3F760}.Release|Any CPU.Build.0 = Release|Any CPU
{9EE55814-E7E8-46D6-8C4B-E1B3AE340346}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9EE55814-E7E8-46D6-8C4B-E1B3AE340346}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9EE55814-E7E8-46D6-8C4B-E1B3AE340346}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9EE55814-E7E8-46D6-8C4B-E1B3AE340346}.Release|Any CPU.Build.0 = Release|Any CPU
{2B512EE1-4B9E-44AB-A534-0C9C97FFCB16}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2B512EE1-4B9E-44AB-A534-0C9C97FFCB16}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2B512EE1-4B9E-44AB-A534-0C9C97FFCB16}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2B512EE1-4B9E-44AB-A534-0C9C97FFCB16}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{665D0CFD-82C4-4265-9304-EB14463C5C59} = {83F31AB1-B15C-4EF6-8E53-A81A53302521}
{83F31AB1-B15C-4EF6-8E53-A81A53302521} = {411A6B73-18F1-48C5-87D4-826EC2D8D647}
{411A6B73-18F1-48C5-87D4-826EC2D8D647} = {A925B0D9-3B08-47C2-9D5E-D6A524E190E5}
{10D92A1A-0B43-464A-BE99-70C47A48118F} = {5D0DFD2A-EA1B-40BD-A673-E8A284E2AF29}
{5D0DFD2A-EA1B-40BD-A673-E8A284E2AF29} = {B4061626-3263-4217-B0B4-F6B9F0FCD2DD}
{5B8F59BA-D635-4ED6-AD9C-30A85BCCDEA2} = {0D7E1332-22B0-4875-85A5-8EA9538167A7}
{0D7E1332-22B0-4875-85A5-8EA9538167A7} = {B4061626-3263-4217-B0B4-F6B9F0FCD2DD}
{4F8519F2-4133-4EED-AA2C-F920E16437DF} = {F8754EB9-301E-4A3F-807A-0AAE58EFAC37}
{F8754EB9-301E-4A3F-807A-0AAE58EFAC37} = {B4061626-3263-4217-B0B4-F6B9F0FCD2DD}
{43311BB6-E215-4C5C-BE8E-E3B645B011F5} = {897EA375-B99B-4B86-BF57-0EED905B9458}
{897EA375-B99B-4B86-BF57-0EED905B9458} = {CEA0B298-02C1-457E-A371-CA25344C7BDC}
{CEA0B298-02C1-457E-A371-CA25344C7BDC} = {B4061626-3263-4217-B0B4-F6B9F0FCD2DD}
{B4061626-3263-4217-B0B4-F6B9F0FCD2DD} = {0659C482-2A98-4009-94FE-9334B0CA0351}
{0659C482-2A98-4009-94FE-9334B0CA0351} = {B0F5E97B-74A3-45A5-AEAD-3166553D681B}
{2773528C-64E5-4554-93C4-E02DC7565EDC} = {E6B1B7C6-58C7-4B90-9112-1AE2DB3BC87C}
{E6B1B7C6-58C7-4B90-9112-1AE2DB3BC87C} = {36701393-9B31-4C6B-9D41-51431B729B6C}
{36701393-9B31-4C6B-9D41-51431B729B6C} = {BA61E15F-E917-4F5B-A66B-9633788E7062}
{9DB96930-A1F2-4788-A8B7-D1818FD3F760} = {5C3885F1-84BF-4D06-A355-EF37AF617ECD}
{5C3885F1-84BF-4D06-A355-EF37AF617ECD} = {103CD471-C355-4E98-8930-91D111FBE3A5}
{9EE55814-E7E8-46D6-8C4B-E1B3AE340346} = {27B03F8E-58E1-4004-93B5-9604C4AC6F64}
{27B03F8E-58E1-4004-93B5-9604C4AC6F64} = {103CD471-C355-4E98-8930-91D111FBE3A5}
{103CD471-C355-4E98-8930-91D111FBE3A5} = {BA61E15F-E917-4F5B-A66B-9633788E7062}
{2B512EE1-4B9E-44AB-A534-0C9C97FFCB16} = {4E0482DA-A1AE-4724-BA84-CAF28ADF387A}
{4E0482DA-A1AE-4724-BA84-CAF28ADF387A} = {BB0B9DEF-C0E9-4C71-9DA9-0184B4E25A50}
{BB0B9DEF-C0E9-4C71-9DA9-0184B4E25A50} = {BA61E15F-E917-4F5B-A66B-9633788E7062}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {E8038EAD-B369-4907-89AB-2FB3776B456B}
EndGlobalSection
EndGlobal
8 changes: 8 additions & 0 deletions src/Security/slngen.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
dotnet slngen this.proj -o this.sln --folders true --launch false

$currentDirectory = Split-Path -Leaf (Get-Location).Path
$sourceFile = Join-Path (Get-Location).Path "this.sln"
$destinationFile = Join-Path (Get-Location).Path ("$currentDirectory.sln")
if (Test-Path $sourceFile) {
Copy-Item -Path $sourceFile -Destination $destinationFile -Force
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
using System.Security.Claims;
using AxOpen.Security.Entities;
using AXOpen;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Server;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace AxOpen.Security.Services
{
//Server-side AuthenticationStateProvider that revalidates the security stamp for the connected user
// every 30 seconds an interactive circuit is connected.
internal sealed class IdentityRevalidatingAuthenticationStateProvider(
ILoggerFactory loggerFactory,
IServiceScopeFactory scopeFactory,
IUserStore<User> userStore,
IOptions<IdentityOptions> options
)
: RevalidatingServerAuthenticationStateProvider(loggerFactory)
{
protected override TimeSpan RevalidationInterval => TimeSpan.FromSeconds(30);

protected override async Task<bool> ValidateAuthenticationStateAsync(
AuthenticationState authenticationState, CancellationToken cancellationToken)
{
// Get the user manager from a new scope to ensure it fetches fresh data
await using var scope = scopeFactory.CreateAsyncScope();

var userManager = scope.ServiceProvider.GetRequiredService<UserManager<User>>();

ClaimsPrincipal userPrincipal = (authenticationState.User as ClaimsPrincipal);

if (userPrincipal == null) { return false; }

var c = new CancellationTokenSource();

var user = await userStore.FindByNameAsync(userPrincipal.Identity.Name, c.Token);

if (user == null) return false; // user was deleted

if (user.EnableAutoLogOut)
{
var httpContext = scope.ServiceProvider.GetRequiredService<IHttpContextAccessor>().HttpContext;

if (httpContext == null) return false;

var ticket = await httpContext.AuthenticateAsync("Identity.Application");

if (!ticket.Succeeded)
{
AxoApplication.Current.Logger.Warning($"User ticket does not exist for '{user.UserName}' and the user will be logged out!", userPrincipal.Identity);

return false;
}

var start = ticket.Properties.IssuedUtc.Value;

//todo - fix
if (DateTime.UtcNow > (start + TimeSpan.FromMinutes(user.AutoLogOutTimeOutMinutes)))
{
AxoApplication.Current.Logger.Information($"User '{user.UserName}' has reached the logout timeout and will be logged out!", userPrincipal.Identity);
return false;
}
}

return await ValidateSecurityStampAsync(userManager, authenticationState.User, user);
}

/// <summary>
/// check user if user have changed sensitive information (password, role ....)
/// </summary>
/// <param name="userManager"></param>
/// <param name="principal"></param>
/// <param name="user"></param>
/// <returns></returns>
private async Task<bool> ValidateSecurityStampAsync(UserManager<User> userManager, ClaimsPrincipal principal, User user)
{
var principalStamp = principal.FindFirstValue(options.Value.ClaimsIdentity.SecurityStampClaimType);

if (principal.Identity is not { IsAuthenticated: true })
{
return false;
}

// !!! BUG !!! - tcopen repo do not find user => record key Tcopen/Sql? => EntityId / Id
// var user = await userManager.GetUserAsync(principal);

if (user is null)
{
return false;
}
else if (!userManager.SupportsUserSecurityStamp)
{
return true;
}
else
{
var userStamp = await userManager.GetSecurityStampAsync(user);

var equals = principalStamp == userStamp;
if (!equals)
{
AxoApplication.Current.Logger.Warning($"User security stamp was changed for '{user.UserName}', and the user will be logged out!", principal.Identity);
}

return equals;
}
}
}
}
38 changes: 38 additions & 0 deletions src/Security/src/AXOpen.Security/Services/InactivityService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//namespace AxOpen.Security.Services
//{
// public class InactivityService : IDisposable
// {
// private TimeSpan _timeoutPeriod { get; set; }
// private Timer? _timer { get; set; }
// private Action _onTimeout { get; set; }

// public void StartMonitoring(TimeSpan timeoutPeriod)
// {
// _timeoutPeriod = timeoutPeriod;

// _timer = new Timer(OnTimerElapsed, null, _timeoutPeriod, Timeout.InfiniteTimeSpan);
// }

// private void OnTimerElapsed(object state)
// {
// _onTimeout?.Invoke();
// _timer?.Dispose();
// }

// public void SetAction(Action onTimeout)
// {
// _onTimeout = onTimeout;
// }

// public void Reset()
// {
// if (_timer != null)
// _timer.Change(_timeoutPeriod, Timeout.InfiniteTimeSpan);
// }

// public void Dispose()
// {
// _timer?.Dispose();
// }
// }
//}
5 changes: 5 additions & 0 deletions src/Security/this.proj
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<Project Sdk="Microsoft.Build.Traversal/4.1.0">
<ItemGroup>
<ProjectReference Include="**\*.csproj" Exclude="**\.apax\**"/>
</ItemGroup>
</Project>
Loading

0 comments on commit 318eda3

Please sign in to comment.