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.LastAccessed).IsRequired();
builder.Property(e => e.Expires).IsRequired();
builder.Property(e => e.UserAgent).HasMaxLength(255).IsRequired();
builder.Property(e => e.UserId);
builder.Property(e => e.IpAddress).HasConversion<IPAddressToBytesConverter>().IsRequired();
builder.Property(e => e.RequiresTotp).IsRequired();

View File

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

View File

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

View File

@ -55,7 +55,8 @@ internal sealed class SessionService : BackgroundService, ISessionService
Updated = now,
LastAccessed = now,
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);
context.SaveChanges();
@ -110,7 +111,7 @@ internal sealed class SessionService : BackgroundService, ISessionService
public bool TryGetCurrentUser(HttpRequest request, HttpResponse response, [NotNullWhen(true)] out IUser? user)
{
user = null;
if (!TryGetSession(request, out ISession? session))
{
_logger.LogDebug("Session not found; redirecting");
@ -203,16 +204,29 @@ internal sealed class SessionService : BackgroundService, ISessionService
if (!remoteIpAddress.TryWriteBytes(remoteAddressBytes, out _) ||
!session.IpAddress.TryWriteBytes(sessionAddressBytes, out _))
{
_logger.LogWarning("Failed to write bytes for session {Id}", session.Id);
return false;
}
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;
}
if (!_userService.TryGetUser(session.UserId, out _))
{
_logger.LogWarning("User {Id} not found for session {Session} (client {Ip})", session.UserId, session.Id,
remoteIpAddress);
return false;
}