stuff
This commit is contained in:
parent
14fd359ea8
commit
a4ef2b4a20
26 changed files with 331 additions and 78 deletions
11
Femto.Modules.Auth/Application/AuthApplication.cs
Normal file
11
Femto.Modules.Auth/Application/AuthApplication.cs
Normal file
|
@ -0,0 +1,11 @@
|
|||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace Femto.Modules.Auth.Application;
|
||||
|
||||
public class AuthApplication(IHost host) : BackgroundService
|
||||
{
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
await host.RunAsync(stoppingToken);
|
||||
}
|
||||
}
|
|
@ -5,7 +5,7 @@ using Microsoft.Extensions.Hosting;
|
|||
|
||||
namespace Femto.Modules.Auth.Application;
|
||||
|
||||
internal class AuthenticationModule(IHost host) : IAuthenticationModule
|
||||
internal class AuthModule(IHost host) : IAuthModule
|
||||
{
|
||||
public async Task<TResponse> PostCommand<TResponse>(ICommand<TResponse> command, CancellationToken cancellationToken = default)
|
||||
{
|
|
@ -4,47 +4,46 @@ using MediatR;
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Femto.Modules.Auth.Application;
|
||||
|
||||
public static class AuthenticationStartup
|
||||
public static class AuthStartup
|
||||
{
|
||||
public static void InitializeAuthenticationModule(this IServiceCollection rootContainer, string connectionString)
|
||||
public static void InitializeAuthenticationModule(
|
||||
this IServiceCollection rootContainer,
|
||||
string connectionString
|
||||
)
|
||||
{
|
||||
var hostBuilder = Host.CreateDefaultBuilder();
|
||||
hostBuilder.ConfigureServices(services => ConfigureServices(services, connectionString));
|
||||
var host = hostBuilder.Build();
|
||||
rootContainer.AddScoped<IAuthenticationModule>(_ => new AuthenticationModule(host));
|
||||
rootContainer.AddScoped<IAuthModule>(_ => new AuthModule(host));
|
||||
rootContainer.AddHostedService(services => new AuthApplication(host));
|
||||
}
|
||||
|
||||
private static void ConfigureServices(IServiceCollection services, string connectionString)
|
||||
{
|
||||
services.AddDbContext<AuthContext>(
|
||||
builder =>
|
||||
{
|
||||
builder.UseNpgsql(connectionString);
|
||||
builder.UseSnakeCaseNamingConvention();
|
||||
});
|
||||
|
||||
services.AddMediatR(c => c.RegisterServicesFromAssembly(typeof(AuthenticationStartup).Assembly));
|
||||
|
||||
services.AddDbContext<AuthContext>(builder =>
|
||||
{
|
||||
builder.UseNpgsql(connectionString);
|
||||
builder.UseSnakeCaseNamingConvention();
|
||||
});
|
||||
|
||||
services.AddMediatR(c => c.RegisterServicesFromAssembly(typeof(AuthStartup).Assembly));
|
||||
|
||||
services.AddDbContext<AuthContext>(builder =>
|
||||
{
|
||||
builder.UseNpgsql();
|
||||
builder.UseSnakeCaseNamingConvention();
|
||||
builder.EnableSensitiveDataLogging();
|
||||
});
|
||||
|
||||
services.AddScoped<DbContext>(s => s.GetRequiredService<AuthContext>());
|
||||
|
||||
|
||||
services.ConfigureDomainServices<AuthContext>();
|
||||
|
||||
services.AddMediatR(c =>
|
||||
{
|
||||
c.RegisterServicesFromAssembly(typeof(AuthenticationStartup).Assembly);
|
||||
c.RegisterServicesFromAssembly(typeof(AuthStartup).Assembly);
|
||||
});
|
||||
|
||||
services.AddTransient(
|
||||
typeof(IPipelineBehavior<,>),
|
||||
typeof(SaveChangesPipelineBehaviour<,>)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -24,7 +24,7 @@ internal class ValidateSessionCommandHandler(AuthContext context)
|
|||
if (user is null)
|
||||
throw new InvalidSessionError();
|
||||
|
||||
var session = user.StartNewSession();
|
||||
var session = user.PossiblyRefreshSession(request.SessionId);
|
||||
|
||||
return new ValidateSessionResult(
|
||||
new Session(session.Id, session.Expires),
|
||||
|
|
|
@ -2,7 +2,7 @@ using Femto.Common.Domain;
|
|||
|
||||
namespace Femto.Modules.Auth.Application;
|
||||
|
||||
public interface IAuthenticationModule
|
||||
public interface IAuthModule
|
||||
{
|
||||
Task<TResponse> PostCommand<TResponse>(ICommand<TResponse> command, CancellationToken cancellationToken = default);
|
||||
}
|
|
@ -6,8 +6,8 @@ namespace Femto.Modules.Auth.Data;
|
|||
|
||||
internal class AuthContext(DbContextOptions<AuthContext> options) : DbContext(options), IOutboxContext
|
||||
{
|
||||
public virtual DbSet<UserIdentity> Users { get; }
|
||||
public virtual DbSet<OutboxEntry> Outbox { get; }
|
||||
public virtual DbSet<UserIdentity> Users { get; set; }
|
||||
public virtual DbSet<OutboxEntry> Outbox { get; set; }
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder builder)
|
||||
{
|
||||
|
|
|
@ -9,7 +9,16 @@ internal class UserIdentityTypeConfiguration : IEntityTypeConfiguration<UserIden
|
|||
public void Configure(EntityTypeBuilder<UserIdentity> builder)
|
||||
{
|
||||
builder.ToTable("user_identity");
|
||||
builder.OwnsOne(u => u.Password).WithOwner().HasForeignKey("user_id");
|
||||
builder.OwnsOne(u => u.Password, pw =>
|
||||
{
|
||||
pw.Property(p => p.Hash)
|
||||
.HasColumnName("password_hash")
|
||||
.IsRequired(false);
|
||||
|
||||
pw.Property(p => p.Salt)
|
||||
.HasColumnName("password_salt")
|
||||
.IsRequired(false);
|
||||
});
|
||||
builder.OwnsMany(u => u.Sessions).WithOwner().HasForeignKey("user_id");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,23 +4,20 @@ using JetBrains.Annotations;
|
|||
|
||||
namespace Femto.Modules.Auth.Models;
|
||||
|
||||
internal class UserPassword
|
||||
internal class Password
|
||||
{
|
||||
private const int Iterations = 3;
|
||||
private const int MemorySize = 67108864;
|
||||
|
||||
public Guid Id { get; private set; }
|
||||
public byte[] Hash { get; private set; }
|
||||
|
||||
private byte[] Hash { get; set; }
|
||||
|
||||
private byte[] Salt { get; set; }
|
||||
public byte[] Salt { get; private set; }
|
||||
|
||||
[UsedImplicitly]
|
||||
private UserPassword() {}
|
||||
private Password() {}
|
||||
|
||||
public UserPassword(string password)
|
||||
public Password(string password)
|
||||
{
|
||||
this.Id = Guid.NewGuid();
|
||||
this.Salt = ComputeSalt();
|
||||
this.Hash = ComputePasswordHash(password, Salt);
|
||||
}
|
|
@ -12,7 +12,7 @@ internal class UserIdentity : Entity
|
|||
|
||||
public string Username { get; private set; }
|
||||
|
||||
public UserPassword Password { get; private set; }
|
||||
public Password? Password { get; private set; }
|
||||
|
||||
public ICollection<UserSession> Sessions { get; private set; } = [];
|
||||
|
||||
|
@ -34,7 +34,7 @@ internal class UserIdentity : Entity
|
|||
|
||||
public void SetPassword(string password)
|
||||
{
|
||||
this.Password = new UserPassword(password);
|
||||
this.Password = new Password(password);
|
||||
}
|
||||
|
||||
public bool HasPassword(string requestPassword)
|
||||
|
@ -47,6 +47,16 @@ internal class UserIdentity : Entity
|
|||
return this.Password.Check(requestPassword);
|
||||
}
|
||||
|
||||
public UserSession PossiblyRefreshSession(string sessionId)
|
||||
{
|
||||
var session = this.Sessions.Single(s => s.Id == sessionId);
|
||||
|
||||
if (session.ExpiresSoon)
|
||||
return this.StartNewSession();
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
public UserSession StartNewSession()
|
||||
{
|
||||
var session = UserSession.Create();
|
||||
|
|
|
@ -2,10 +2,13 @@ namespace Femto.Modules.Auth.Models;
|
|||
|
||||
public class UserSession
|
||||
{
|
||||
private static TimeSpan SessionTimeout = TimeSpan.FromMinutes(30);
|
||||
private static TimeSpan SessionTimeout { get; } = TimeSpan.FromMinutes(30);
|
||||
private static TimeSpan ExpiryBuffer { get; } = TimeSpan.FromMinutes(5);
|
||||
public string Id { get; private set; }
|
||||
public DateTimeOffset Expires { get; private set; }
|
||||
|
||||
public bool ExpiresSoon => Expires < DateTimeOffset.UtcNow + ExpiryBuffer;
|
||||
|
||||
private UserSession() {}
|
||||
|
||||
public static UserSession Create()
|
||||
|
@ -13,7 +16,7 @@ public class UserSession
|
|||
return new()
|
||||
{
|
||||
Id = Convert.ToBase64String(System.Security.Cryptography.RandomNumberGenerator.GetBytes(32)),
|
||||
Expires = DateTimeOffset.Now + SessionTimeout
|
||||
Expires = DateTimeOffset.UtcNow + SessionTimeout
|
||||
};
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue