Proper trace for auth session

This commit is contained in:
2025-12-04 00:38:44 +08:00
parent 2020d625aa
commit ad3c104c5c
5 changed files with 2947 additions and 11 deletions

View File

@@ -3,6 +3,7 @@ using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using DysonNetwork.Shared.Cache; using DysonNetwork.Shared.Cache;
using DysonNetwork.Shared.Data; using DysonNetwork.Shared.Data;
using DysonNetwork.Shared.GeoIp;
using DysonNetwork.Shared.Models; using DysonNetwork.Shared.Models;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using NodaTime; using NodaTime;
@@ -14,7 +15,8 @@ public class AuthService(
IConfiguration config, IConfiguration config,
IHttpClientFactory httpClientFactory, IHttpClientFactory httpClientFactory,
IHttpContextAccessor httpContextAccessor, IHttpContextAccessor httpContextAccessor,
ICacheService cache ICacheService cache,
GeoIpService geo
) )
{ {
private HttpContext HttpContext => httpContextAccessor.HttpContext!; private HttpContext HttpContext => httpContextAccessor.HttpContext!;
@@ -49,7 +51,9 @@ public class AuthService(
.ToListAsync(); .ToListAsync();
var recentChallengeIds = var recentChallengeIds =
recentSessions.Where(s => s.ChallengeId != null).Select(s => s.ChallengeId.Value).ToList(); recentSessions
.Where(s => s.ChallengeId != null)
.Select(s => s.ChallengeId!.Value).ToList();
var recentChallenges = await db.AuthChallenges.Where(c => recentChallengeIds.Contains(c.Id)).ToListAsync(); var recentChallenges = await db.AuthChallenges.Where(c => recentChallengeIds.Contains(c.Id)).ToListAsync();
var ipAddress = request.HttpContext.Connection.RemoteIpAddress?.ToString(); var ipAddress = request.HttpContext.Connection.RemoteIpAddress?.ToString();
@@ -184,16 +188,23 @@ public class AuthService(
return totalRequiredSteps; return totalRequiredSteps;
} }
public async Task<SnAuthSession> CreateSessionForOidcAsync(SnAccount account, Instant time, public async Task<SnAuthSession> CreateSessionForOidcAsync(
Guid? customAppId = null, SnAuthSession? parentSession = null) SnAccount account,
Instant time,
Guid? customAppId = null,
SnAuthSession? parentSession = null
)
{ {
var ipAddr = HttpContext.Connection.RemoteIpAddress?.ToString();
var geoLocation = ipAddr is not null ? geo.GetPointFromIp(ipAddr) : null;
var session = new SnAuthSession var session = new SnAuthSession
{ {
AccountId = account.Id, AccountId = account.Id,
CreatedAt = time, CreatedAt = time,
LastGrantedAt = time, LastGrantedAt = time,
IpAddress = HttpContext.Connection.RemoteIpAddress?.ToString(), IpAddress = ipAddr,
UserAgent = HttpContext.Request.Headers.UserAgent, UserAgent = HttpContext.Request.Headers.UserAgent,
Location = geoLocation,
AppId = customAppId, AppId = customAppId,
ParentSessionId = parentSession?.Id, ParentSessionId = parentSession?.Id,
Type = customAppId is not null ? SessionType.OAuth : SessionType.Oidc, Type = customAppId is not null ? SessionType.OAuth : SessionType.Oidc,
@@ -212,7 +223,8 @@ public class AuthService(
ClientPlatform platform = ClientPlatform.Unidentified ClientPlatform platform = ClientPlatform.Unidentified
) )
{ {
var device = await db.AuthClients.FirstOrDefaultAsync(d => d.DeviceId == deviceId && d.AccountId == accountId); var device = await db.AuthClients
.FirstOrDefaultAsync(d => d.DeviceId == deviceId && d.AccountId == accountId);
if (device is not null) return device; if (device is not null) return device;
device = new SnAuthClient device = new SnAuthClient
{ {
@@ -333,12 +345,8 @@ public class AuthService(
/// <param name="sessionsToRevoke">A HashSet to store the IDs of all sessions to be revoked.</param> /// <param name="sessionsToRevoke">A HashSet to store the IDs of all sessions to be revoked.</param>
private async Task CollectSessionsToRevoke(Guid currentSessionId, HashSet<Guid> sessionsToRevoke) private async Task CollectSessionsToRevoke(Guid currentSessionId, HashSet<Guid> sessionsToRevoke)
{ {
if (sessionsToRevoke.Contains(currentSessionId)) if (!sessionsToRevoke.Add(currentSessionId))
{
return; // Already processed this session return; // Already processed this session
}
sessionsToRevoke.Add(currentSessionId);
// Find direct children // Find direct children
var childSessions = await db.AuthSessions var childSessions = await db.AuthSessions
@@ -425,6 +433,7 @@ public class AuthService(
AccountId = challenge.AccountId, AccountId = challenge.AccountId,
IpAddress = challenge.IpAddress, IpAddress = challenge.IpAddress,
UserAgent = challenge.UserAgent, UserAgent = challenge.UserAgent,
Location = challenge.Location,
Scopes = challenge.Scopes, Scopes = challenge.Scopes,
Audiences = challenge.Audiences, Audiences = challenge.Audiences,
ChallengeId = challenge.Id, ChallengeId = challenge.Id,
@@ -679,9 +688,16 @@ public class AuthService(
{ {
var device = await GetOrCreateDeviceAsync(parentSession.AccountId, deviceId, deviceName, platform); var device = await GetOrCreateDeviceAsync(parentSession.AccountId, deviceId, deviceName, platform);
var ipAddress = HttpContext.Connection.RemoteIpAddress?.ToString();
var userAgent = HttpContext.Request.Headers.UserAgent.ToString();
var geoLocation = ipAddress is not null ? geo.GetPointFromIp(ipAddress) : null;
var now = SystemClock.Instance.GetCurrentInstant(); var now = SystemClock.Instance.GetCurrentInstant();
var session = new SnAuthSession var session = new SnAuthSession
{ {
IpAddress = ipAddress,
UserAgent = userAgent,
Location = geoLocation,
AccountId = parentSession.AccountId, AccountId = parentSession.AccountId,
CreatedAt = now, CreatedAt = now,
LastGrantedAt = now, LastGrantedAt = now,

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,29 @@
using DysonNetwork.Shared.GeoIp;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace DysonNetwork.Pass.Migrations
{
/// <inheritdoc />
public partial class AddLocationToSession : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<GeoPoint>(
name: "location",
table: "auth_sessions",
type: "jsonb",
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "location",
table: "auth_sessions");
}
}
}

View File

@@ -1053,6 +1053,10 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("timestamp with time zone") .HasColumnType("timestamp with time zone")
.HasColumnName("last_granted_at"); .HasColumnName("last_granted_at");
b.Property<GeoPoint>("Location")
.HasColumnType("jsonb")
.HasColumnName("location");
b.Property<Guid?>("ParentSessionId") b.Property<Guid?>("ParentSessionId")
.HasColumnType("uuid") .HasColumnType("uuid")
.HasColumnName("parent_session_id"); .HasColumnName("parent_session_id");

View File

@@ -26,6 +26,7 @@ public class SnAuthSession : ModelBase
[Column(TypeName = "jsonb")] public List<string> Scopes { get; set; } = []; [Column(TypeName = "jsonb")] public List<string> Scopes { get; set; } = [];
[MaxLength(128)] public string? IpAddress { get; set; } [MaxLength(128)] public string? IpAddress { get; set; }
[MaxLength(512)] public string? UserAgent { get; set; } [MaxLength(512)] public string? UserAgent { get; set; }
[Column(TypeName = "jsonb")] public GeoPoint? Location { get; set; }
public Guid AccountId { get; set; } public Guid AccountId { get; set; }
[JsonIgnore] public SnAccount Account { get; set; } = null!; [JsonIgnore] public SnAccount Account { get; set; } = null!;