Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
32 changes: 30 additions & 2 deletions DM.sln
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "github-actions", "github-ac
.github\workflows\dotnet.yml = .github\workflows\dotnet.yml
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DM.Services.Forum.Storage", "src\DM.Services.Forum.Storage\DM.Services.Forum.Storage.csproj", "{101BB826-5855-45C6-BA33-D73E530D4D84}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DM.Services.Forum.Storage.Dependencies", "src\DM.Services.Forum.Storage.Dependencies\DM.Services.Forum.Storage.Dependencies.csproj", "{90B89BED-DBBA-41C7-B91D-12D2CE905FD5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DM.Services.Community.Storage", "src\DM.Services.Community.Storage\DM.Services.Community.Storage.csproj", "{6C2B575F-2E45-49DE-9F89-F554752767E4}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DM.Services.Community.Storage.Dependencies", "src\DM.Services.Community.Storage.Dependencies\DM.Services.Community.Storage.Dependencies.csproj", "{D3D67774-BF7D-4BB3-A877-B706C8D87E73}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -206,11 +214,25 @@ Global
{550DC966-8960-4F98-BA6B-BEDBA73BB431}.Debug|Any CPU.Build.0 = Debug|Any CPU
{550DC966-8960-4F98-BA6B-BEDBA73BB431}.Release|Any CPU.ActiveCfg = Release|Any CPU
{550DC966-8960-4F98-BA6B-BEDBA73BB431}.Release|Any CPU.Build.0 = Release|Any CPU
{101BB826-5855-45C6-BA33-D73E530D4D84}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{101BB826-5855-45C6-BA33-D73E530D4D84}.Debug|Any CPU.Build.0 = Debug|Any CPU
{101BB826-5855-45C6-BA33-D73E530D4D84}.Release|Any CPU.ActiveCfg = Release|Any CPU
{101BB826-5855-45C6-BA33-D73E530D4D84}.Release|Any CPU.Build.0 = Release|Any CPU
{90B89BED-DBBA-41C7-B91D-12D2CE905FD5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{90B89BED-DBBA-41C7-B91D-12D2CE905FD5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{90B89BED-DBBA-41C7-B91D-12D2CE905FD5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{90B89BED-DBBA-41C7-B91D-12D2CE905FD5}.Release|Any CPU.Build.0 = Release|Any CPU
{6C2B575F-2E45-49DE-9F89-F554752767E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6C2B575F-2E45-49DE-9F89-F554752767E4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6C2B575F-2E45-49DE-9F89-F554752767E4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6C2B575F-2E45-49DE-9F89-F554752767E4}.Release|Any CPU.Build.0 = Release|Any CPU
{D3D67774-BF7D-4BB3-A877-B706C8D87E73}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D3D67774-BF7D-4BB3-A877-B706C8D87E73}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D3D67774-BF7D-4BB3-A877-B706C8D87E73}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D3D67774-BF7D-4BB3-A877-B706C8D87E73}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{37EE71A5-6F98-42E7-B92F-3E2B65C67850} = {EA05C4AE-2D7E-4F1F-B112-64FB021BB3B9}
{28709B36-C22C-4EB9-906F-19CDCC769338} = {37EE71A5-6F98-42E7-B92F-3E2B65C67850}
{6DD9DA97-AAFF-46D4-9313-367998B98CF9} = {37EE71A5-6F98-42E7-B92F-3E2B65C67850}
{755C13DD-F19B-450A-BC2D-5C195545DCE8} = {EA05C4AE-2D7E-4F1F-B112-64FB021BB3B9}
{2672341A-1DFD-4FDF-8D36-BFF07177C7DB} = {EA05C4AE-2D7E-4F1F-B112-64FB021BB3B9}
{74044CFF-FF4E-4604-AB39-018D19914357} = {EA05C4AE-2D7E-4F1F-B112-64FB021BB3B9}
Expand Down Expand Up @@ -246,5 +268,11 @@ Global
{456E0B6D-8774-443B-B395-D8B6F16EF71C} = {DB521FBE-9B99-4349-9736-6C3E6E418964}
{550DC966-8960-4F98-BA6B-BEDBA73BB431} = {37EE71A5-6F98-42E7-B92F-3E2B65C67850}
{EAA9F9E3-2684-4C5C-BC70-403F8CE1C4A1} = {60E4E464-934E-42F0-8137-3F2BA8E3063B}
{6DD9DA97-AAFF-46D4-9313-367998B98CF9} = {EA05C4AE-2D7E-4F1F-B112-64FB021BB3B9}
{28709B36-C22C-4EB9-906F-19CDCC769338} = {EA05C4AE-2D7E-4F1F-B112-64FB021BB3B9}
{101BB826-5855-45C6-BA33-D73E530D4D84} = {74044CFF-FF4E-4604-AB39-018D19914357}
{90B89BED-DBBA-41C7-B91D-12D2CE905FD5} = {74044CFF-FF4E-4604-AB39-018D19914357}
{6C2B575F-2E45-49DE-9F89-F554752767E4} = {755C13DD-F19B-450A-BC2D-5C195545DCE8}
{D3D67774-BF7D-4BB3-A877-B706C8D87E73} = {755C13DD-F19B-450A-BC2D-5C195545DCE8}
EndGlobalSection
EndGlobal
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

<ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1>$(AssemblyName).DependencyInjection</_Parameter1>
<_Parameter1>$(AssemblyName).Dependencies</_Parameter1>
</AssemblyAttribute>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1>$(AssemblyName).Tests</_Parameter1>
Expand Down
16 changes: 3 additions & 13 deletions src/DM.Services.Authentication/Factories/SessionFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,10 @@
namespace DM.Services.Authentication.Factories;

/// <inheritdoc />
internal class SessionFactory : ISessionFactory
internal class SessionFactory(
IGuidFactory guidFactory,
IDateTimeProvider dateTimeProvider) : ISessionFactory
{
private readonly IGuidFactory guidFactory;
private readonly IDateTimeProvider dateTimeProvider;

/// <inheritdoc />
public SessionFactory(
IGuidFactory guidFactory,
IDateTimeProvider dateTimeProvider)
{
this.guidFactory = guidFactory;
this.dateTimeProvider = dateTimeProvider;
}

/// <inheritdoc />
public Session Create(bool persistent, bool invisible)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using DM.Services.Authentication.Dto;
using DM.Services.Authentication.Factories;
Expand All @@ -16,42 +17,23 @@
namespace DM.Services.Authentication.Implementation;

/// <inheritdoc />
internal class AuthenticationService : IAuthenticationService
internal class AuthenticationService(
ISecurityManager securityManager,
ISymmetricCryptoService cryptoService,
IAuthenticationRepository repository,
ISessionFactory sessionFactory,
IDateTimeProvider dateTimeProvider,
IIdentityProvider identityProvider,
IUpdateBuilderFactory updateBuilderFactory) : IAuthenticationService
{
private readonly ISecurityManager securityManager;
private readonly ISymmetricCryptoService cryptoService;
private readonly IAuthenticationRepository repository;
private readonly ISessionFactory sessionFactory;
private readonly IDateTimeProvider dateTimeProvider;
private readonly IIdentityProvider identityProvider;
private readonly IUpdateBuilderFactory updateBuilderFactory;

private const string UserIdKey = "userId";
private const string SessionIdKey = "sessionId";

/// <inheritdoc />
public AuthenticationService(
ISecurityManager securityManager,
ISymmetricCryptoService cryptoService,
IAuthenticationRepository repository,
ISessionFactory sessionFactory,
IDateTimeProvider dateTimeProvider,
IIdentityProvider identityProvider,
IUpdateBuilderFactory updateBuilderFactory)
{
this.securityManager = securityManager;
this.cryptoService = cryptoService;
this.repository = repository;
this.sessionFactory = sessionFactory;
this.dateTimeProvider = dateTimeProvider;
this.identityProvider = identityProvider;
this.updateBuilderFactory = updateBuilderFactory;
}

/// <inheritdoc />
public async Task<IIdentity> Authenticate(string login, string password, bool persistent)
public async Task<IIdentity> Authenticate(
string login, string password, bool persistent, CancellationToken cancellationToken)
{
var (userFound, user) = await repository.TryFindUser(login);
var (userFound, user) = await repository.TryFindUser(login, cancellationToken);
switch (userFound)
{
case false:
Expand All @@ -68,20 +50,20 @@ public async Task<IIdentity> Authenticate(string login, string password, bool pe

default:
var session = sessionFactory.Create(persistent, false);
var settings = await repository.FindUserSettings(user.UserId);
return await CreateAuthenticationResult(user, session, settings);
var settings = await repository.FindUserSettings(user.UserId, cancellationToken);
return await CreateAuthenticationResult(user, session, settings, cancellationToken);
}
}

/// <inheritdoc />
public async Task<IIdentity> Authenticate(string authToken)
public async Task<IIdentity> Authenticate(string authToken, CancellationToken cancellationToken)
{
Guid userId;
Guid sessionId;

try
{
var decryptedString = await cryptoService.Decrypt(authToken);
var decryptedString = await cryptoService.Decrypt(authToken, cancellationToken);
var authData = JsonSerializer.Deserialize<Dictionary<string, Guid>>(decryptedString);
userId = authData[UserIdKey];
sessionId = authData[SessionIdKey];
Expand All @@ -91,9 +73,9 @@ public async Task<IIdentity> Authenticate(string authToken)
return Identity.Fail(AuthenticationError.ForgedToken);
}

var fetchUser = repository.FindUser(userId);
var fetchSession = repository.FindUserSession(sessionId);
var fetchSettings = repository.FindUserSettings(userId);
var fetchUser = repository.FindUser(userId, cancellationToken);
var fetchSession = repository.FindUserSession(sessionId, cancellationToken);
var fetchSettings = repository.FindUserSettings(userId, cancellationToken);

await Task.WhenAll(fetchUser, fetchSession, fetchSettings);

Expand All @@ -109,15 +91,16 @@ public async Task<IIdentity> Authenticate(string authToken)
if (!session.Persistent &&
session.ExpirationDate < dateTimeProvider.Now)
{
await repository.RemoveSession(userId, sessionId);
await repository.RemoveSession(userId, sessionId, cancellationToken);
return Identity.Fail(AuthenticationError.SessionExpired);
}

var sessionRefreshDelta = TimeSpan.FromMinutes(20);
if (!session.Persistent &&
session.ExpirationDate < dateTimeProvider.Now + sessionRefreshDelta)
{
await repository.RefreshSession(userId, sessionId, session.ExpirationDate + sessionRefreshDelta);
await repository.RefreshSession(
userId, sessionId, session.ExpirationDate + sessionRefreshDelta, cancellationToken);
}

if (!session.Invisible && (
Expand All @@ -126,47 +109,47 @@ public async Task<IIdentity> Authenticate(string authToken)
{
var userUpdate = updateBuilderFactory.Create<User>(user.UserId)
.Field(u => u.LastVisitDate, dateTimeProvider.Now);
await repository.UpdateActivity(userUpdate);
await repository.UpdateActivity(userUpdate, cancellationToken);
}

return Identity.Success(user, session, settings, authToken);
}

/// <inheritdoc />
public async Task<IIdentity> Authenticate(Guid userId)
public async Task<IIdentity> Authenticate(Guid userId, CancellationToken cancellationToken)
{
var user = await repository.FindUser(userId);
var user = await repository.FindUser(userId, cancellationToken);
var session = sessionFactory.Create(false, true);
var settings = await repository.FindUserSettings(userId);
return await CreateAuthenticationResult(user, session, settings);
var settings = await repository.FindUserSettings(userId, cancellationToken);
return await CreateAuthenticationResult(user, session, settings, cancellationToken);
}

/// <inheritdoc />
public async Task<IIdentity> Logout()
public async Task<IIdentity> Logout(CancellationToken cancellationToken)
{
var identity = identityProvider.Current;
await repository.RemoveSession(identity.User.UserId, identity.Session.Id);
await repository.RemoveSession(identity.User.UserId, identity.Session.Id, cancellationToken);
return Identity.Guest();
}

/// <inheritdoc />
public async Task<IIdentity> LogoutElsewhere()
public async Task<IIdentity> LogoutElsewhere(CancellationToken cancellationToken)
{
var identity = identityProvider.Current;
await repository.RemoveSessionsExcept(identity.User.UserId, identity.Session.Id);
await repository.RemoveSessionsExcept(identity.User.UserId, identity.Session.Id, cancellationToken);
return identity;
}

private async Task<IIdentity> CreateAuthenticationResult(
AuthenticatedUser user, DbSession session, UserSettings settings)
AuthenticatedUser user, DbSession session, UserSettings settings, CancellationToken cancellationToken)
{
var newSession = await repository.AddSession(user.UserId, session);
var newSession = await repository.AddSession(user.UserId, session, cancellationToken);
var authData = new Dictionary<string, Guid>
{
[UserIdKey] = user.UserId,
[SessionIdKey] = session.Id
};
var token = await cryptoService.Encrypt(JsonSerializer.Serialize(authData));
var token = await cryptoService.Encrypt(JsonSerializer.Serialize(authData), cancellationToken);
return Identity.Success(user, newSession, settings, token);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using DM.Services.Authentication.Dto;

Expand All @@ -15,32 +16,35 @@ public interface IAuthenticationService
/// <param name="login">User login</param>
/// <param name="password">User password</param>
/// <param name="persistent">Persistence flag</param>
/// <param name="cancellationToken"></param>
/// <returns>Authentication identity</returns>
Task<IIdentity> Authenticate(string login, string password, bool persistent);
Task<IIdentity> Authenticate(string login, string password, bool persistent, CancellationToken cancellationToken);

/// <summary>
/// Authenticate via token credentials
/// </summary>
/// <param name="authToken">Authentication token</param>
/// <param name="cancellationToken"></param>
/// <returns>Authentication identity</returns>
Task<IIdentity> Authenticate(string authToken);
Task<IIdentity> Authenticate(string authToken, CancellationToken cancellationToken);

/// <summary>
/// Authenticate unconditionally
/// </summary>
/// <param name="userId">User identifier</param>
/// <param name="cancellationToken"></param>
/// <returns>Authentication identity</returns>
Task<IIdentity> Authenticate(Guid userId);
Task<IIdentity> Authenticate(Guid userId, CancellationToken cancellationToken);

/// <summary>
/// Logout as a current user
/// </summary>
/// <returns>Guest identity</returns>
Task<IIdentity> Logout();
Task<IIdentity> Logout(CancellationToken cancellationToken);

/// <summary>
/// Logout from all devices except this
/// </summary>
/// <returns>Newly created authentication identity</returns>
Task<IIdentity> LogoutElsewhere();
Task<IIdentity> LogoutElsewhere(CancellationToken cancellationToken);
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Threading;
using System.Threading.Tasks;

namespace DM.Services.Authentication.Implementation.Security;
Expand All @@ -11,13 +12,15 @@ public interface ISymmetricCryptoService
/// Encrypts value for storage
/// </summary>
/// <param name="valueToEncrypt">Given value</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<string> Encrypt(string valueToEncrypt);
Task<string> Encrypt(string valueToEncrypt, CancellationToken cancellationToken);

/// <summary>
/// Decrypts stored value
/// </summary>
/// <param name="valueToDecrypt">Stored value</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<string> Decrypt(string valueToDecrypt);
Task<string> Decrypt(string valueToDecrypt, CancellationToken cancellationToken);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,10 @@
namespace DM.Services.Authentication.Implementation.Security;

/// <inheritdoc />
internal class SecurityManager : ISecurityManager
internal class SecurityManager(
ISaltFactory saltFactory,
IHashProvider hashProvider) : ISecurityManager
{
private readonly ISaltFactory saltFactory;
private readonly IHashProvider hashProvider;

/// <inheritdoc />
public SecurityManager(
ISaltFactory saltFactory,
IHashProvider hashProvider)
{
this.saltFactory = saltFactory;
this.hashProvider = hashProvider;
}

/// <inheritdoc />
public (string Hash, string Salt) GeneratePassword(string password)
{
Expand Down
Loading