feat: add user agent verification to session

This commit is contained in:
Oliver Booth 2024-02-25 17:18:43 +00:00
parent caceb0fe4c
commit d38167bb97
Signed by: oliverbooth
GPG Key ID: E60B570D1B7557B5
4 changed files with 27 additions and 3 deletions

View File

@ -17,6 +17,7 @@ internal sealed class SessionConfiguration : IEntityTypeConfiguration<Session>
builder.Property(e => e.Updated).IsRequired(); builder.Property(e => e.Updated).IsRequired();
builder.Property(e => e.LastAccessed).IsRequired(); builder.Property(e => e.LastAccessed).IsRequired();
builder.Property(e => e.Expires).IsRequired(); builder.Property(e => e.Expires).IsRequired();
builder.Property(e => e.UserAgent).HasMaxLength(255).IsRequired();
builder.Property(e => e.UserId); builder.Property(e => e.UserId);
builder.Property(e => e.IpAddress).HasConversion<IPAddressToBytesConverter>().IsRequired(); builder.Property(e => e.IpAddress).HasConversion<IPAddressToBytesConverter>().IsRequired();
builder.Property(e => e.RequiresTotp).IsRequired(); builder.Property(e => e.RequiresTotp).IsRequired();

View File

@ -49,6 +49,12 @@ public interface ISession
/// <value>The update timestamp.</value> /// <value>The update timestamp.</value>
DateTimeOffset Updated { get; } DateTimeOffset Updated { get; }
/// <summary>
/// Gets the user agent string associated with this session.
/// </summary>
/// <value>The user agent string.</value>
string UserAgent { get; }
/// <summary> /// <summary>
/// Gets the user ID associated with the session. /// Gets the user ID associated with the session.
/// </summary> /// </summary>

View File

@ -25,6 +25,9 @@ internal sealed class Session : ISession
/// <inheritdoc /> /// <inheritdoc />
public DateTimeOffset Updated { get; set; } public DateTimeOffset Updated { get; set; }
/// <inheritdoc />
public string UserAgent { get; set; } = string.Empty;
/// <inheritdoc /> /// <inheritdoc />
public Guid UserId { get; set; } public Guid UserId { get; set; }
} }

View File

@ -55,7 +55,8 @@ internal sealed class SessionService : BackgroundService, ISessionService
Updated = now, Updated = now,
LastAccessed = now, LastAccessed = now,
Expires = now + TimeSpan.FromDays(1), Expires = now + TimeSpan.FromDays(1),
RequiresTotp = !string.IsNullOrWhiteSpace(user.Totp) RequiresTotp = !string.IsNullOrWhiteSpace(user.Totp),
UserAgent = request.Headers.UserAgent.ToString()
}; };
EntityEntry<Session> entry = context.Sessions.Add(session); EntityEntry<Session> entry = context.Sessions.Add(session);
context.SaveChanges(); context.SaveChanges();
@ -203,16 +204,29 @@ internal sealed class SessionService : BackgroundService, ISessionService
if (!remoteIpAddress.TryWriteBytes(remoteAddressBytes, out _) || if (!remoteIpAddress.TryWriteBytes(remoteAddressBytes, out _) ||
!session.IpAddress.TryWriteBytes(sessionAddressBytes, out _)) !session.IpAddress.TryWriteBytes(sessionAddressBytes, out _))
{ {
_logger.LogWarning("Failed to write bytes for session {Id}", session.Id);
return false; return false;
} }
if (!remoteAddressBytes.SequenceEqual(sessionAddressBytes)) if (!remoteAddressBytes.SequenceEqual(sessionAddressBytes))
{ {
_logger.LogInformation("Session {Id} has IP mismatch (wanted {Expected}, got {Actual})", session.Id,
session.IpAddress, remoteIpAddress);
return false;
}
var userAgent = request.Headers.UserAgent.ToString();
if (session.UserAgent != userAgent)
{
_logger.LogInformation("Session {Id} has user agent mismatch (wanted {Expected}, got {Actual})", session.Id,
session.UserAgent, userAgent);
return false; return false;
} }
if (!_userService.TryGetUser(session.UserId, out _)) if (!_userService.TryGetUser(session.UserId, out _))
{ {
_logger.LogWarning("User {Id} not found for session {Session} (client {Ip})", session.UserId, session.Id,
remoteIpAddress);
return false; return false;
} }