Compare commits
	
		
			3 Commits
		
	
	
		
			f8d8e485f1
			...
			1778ab112d
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 1778ab112d | |||
| 5f70d53c94 | |||
| 4b66e97bda | 
| @@ -439,7 +439,7 @@ public class AccountCurrentController( | |||||||
|  |  | ||||||
|     [HttpGet("devices")] |     [HttpGet("devices")] | ||||||
|     [Authorize] |     [Authorize] | ||||||
|     public async Task<ActionResult<List<AuthClient>>> GetDevices() |     public async Task<ActionResult<List<AuthClientWithChallenge>>> GetDevices() | ||||||
|     { |     { | ||||||
|         if (HttpContext.Items["CurrentUser"] is not Account currentUser || |         if (HttpContext.Items["CurrentUser"] is not Account currentUser || | ||||||
|             HttpContext.Items["CurrentSession"] is not AuthSession currentSession) return Unauthorized(); |             HttpContext.Items["CurrentSession"] is not AuthSession currentSession) return Unauthorized(); | ||||||
| @@ -450,7 +450,18 @@ public class AccountCurrentController( | |||||||
|             .Where(device => device.AccountId == currentUser.Id) |             .Where(device => device.AccountId == currentUser.Id) | ||||||
|             .ToListAsync(); |             .ToListAsync(); | ||||||
|  |  | ||||||
|         return Ok(devices); |         var challengeDevices = devices.Select(AuthClientWithChallenge.FromClient).ToList(); | ||||||
|  |         var deviceIds = challengeDevices.Select(x => x.Id).ToList(); | ||||||
|  |  | ||||||
|  |         var authChallenges = await db.AuthChallenges | ||||||
|  |             .Where(c => c.ClientId != null && deviceIds.Contains(c.ClientId.Value)) | ||||||
|  |             .GroupBy(c => c.ClientId) | ||||||
|  |             .ToDictionaryAsync(c => c.Key!.Value, c => c.ToList()); | ||||||
|  |         foreach (var challengeDevice in challengeDevices) | ||||||
|  |             if (authChallenges.TryGetValue(challengeDevice.Id, out var challenge)) | ||||||
|  |                 challengeDevice.Challenges = challenge; | ||||||
|  |  | ||||||
|  |         return Ok(challengeDevices); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     [HttpGet("sessions")] |     [HttpGet("sessions")] | ||||||
|   | |||||||
| @@ -75,6 +75,7 @@ public class DysonTokenAuthHandler( | |||||||
|                 session = await database.AuthSessions |                 session = await database.AuthSessions | ||||||
|                     .Where(e => e.Id == sessionId) |                     .Where(e => e.Id == sessionId) | ||||||
|                     .Include(e => e.Challenge) |                     .Include(e => e.Challenge) | ||||||
|  |                     .ThenInclude(e => e.Client) | ||||||
|                     .Include(e => e.Account) |                     .Include(e => e.Account) | ||||||
|                     .ThenInclude(e => e.Profile) |                     .ThenInclude(e => e.Profile) | ||||||
|                     .FirstOrDefaultAsync(); |                     .FirstOrDefaultAsync(); | ||||||
|   | |||||||
| @@ -23,9 +23,10 @@ public class AuthController( | |||||||
|      |      | ||||||
|     public class ChallengeRequest |     public class ChallengeRequest | ||||||
|     { |     { | ||||||
|         [Required] public ChallengePlatform Platform { get; set; } |         [Required] public ClientPlatform Platform { get; set; } | ||||||
|         [Required] [MaxLength(256)] public string Account { get; set; } = null!; |         [Required] [MaxLength(256)] public string Account { get; set; } = null!; | ||||||
|         [Required] [MaxLength(512)] public string DeviceId { get; set; } = null!; |         [Required] [MaxLength(512)] public string DeviceId { get; set; } = null!; | ||||||
|  |         [MaxLength(1024)] public string? DeviceName { get; set; } | ||||||
|         public List<string> Audiences { get; set; } = new(); |         public List<string> Audiences { get; set; } = new(); | ||||||
|         public List<string> Scopes { get; set; } = new(); |         public List<string> Scopes { get; set; } = new(); | ||||||
|     } |     } | ||||||
| @@ -57,12 +58,11 @@ public class AuthController( | |||||||
|             .FirstOrDefaultAsync(); |             .FirstOrDefaultAsync(); | ||||||
|         if (existingChallenge is not null) return existingChallenge; |         if (existingChallenge is not null) return existingChallenge; | ||||||
|  |  | ||||||
|         var device = await auth.GetOrCreateDeviceAsync(account.Id, request.DeviceId); |         var device = await auth.GetOrCreateDeviceAsync(account.Id, request.DeviceId, request.DeviceName, request.Platform); | ||||||
|         var challenge = new AuthChallenge |         var challenge = new AuthChallenge | ||||||
|         { |         { | ||||||
|             ExpiredAt = Instant.FromDateTimeUtc(DateTime.UtcNow.AddHours(1)), |             ExpiredAt = Instant.FromDateTimeUtc(DateTime.UtcNow.AddHours(1)), | ||||||
|             StepTotal = await auth.DetectChallengeRisk(Request, account), |             StepTotal = await auth.DetectChallengeRisk(Request, account), | ||||||
|             Platform = request.Platform, |  | ||||||
|             Audiences = request.Audiences, |             Audiences = request.Audiences, | ||||||
|             Scopes = request.Scopes, |             Scopes = request.Scopes, | ||||||
|             IpAddress = ipAddress, |             IpAddress = ipAddress, | ||||||
|   | |||||||
| @@ -73,7 +73,8 @@ public class AuthService( | |||||||
|         return totalRequiredSteps; |         return totalRequiredSteps; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public async Task<AuthSession> CreateSessionForOidcAsync(Account.Account account, Instant time, Guid? customAppId = null) |     public async Task<AuthSession> CreateSessionForOidcAsync(Account.Account account, Instant time, | ||||||
|  |         Guid? customAppId = null) | ||||||
|     { |     { | ||||||
|         var challenge = new AuthChallenge |         var challenge = new AuthChallenge | ||||||
|         { |         { | ||||||
| @@ -101,15 +102,22 @@ public class AuthService( | |||||||
|         return session; |         return session; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public async Task<AuthClient> GetOrCreateDeviceAsync(Guid accountId, string deviceId) |     public async Task<AuthClient> GetOrCreateDeviceAsync( | ||||||
|  |         Guid accountId, | ||||||
|  |         string deviceId, | ||||||
|  |         string? deviceName = null, | ||||||
|  |         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 AuthClient |         device = new AuthClient | ||||||
|         { |         { | ||||||
|  |             Platform = platform, | ||||||
|             DeviceId = deviceId, |             DeviceId = deviceId, | ||||||
|             AccountId = accountId |             AccountId = accountId | ||||||
|         }; |         }; | ||||||
|  |         if (deviceName is not null) device.DeviceName = deviceName; | ||||||
|         db.AuthClients.Add(device); |         db.AuthClients.Add(device); | ||||||
|         await db.SaveChangesAsync(); |         await db.SaveChangesAsync(); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -30,6 +30,7 @@ public class AuthServiceGrpc( | |||||||
|         session = await db.AuthSessions |         session = await db.AuthSessions | ||||||
|             .AsNoTracking() |             .AsNoTracking() | ||||||
|             .Include(e => e.Challenge) |             .Include(e => e.Challenge) | ||||||
|  |             .ThenInclude(e => e.Client) | ||||||
|             .Include(e => e.Account) |             .Include(e => e.Account) | ||||||
|             .ThenInclude(e => e.Profile) |             .ThenInclude(e => e.Profile) | ||||||
|             .FirstOrDefaultAsync(s => s.Id == sessionId); |             .FirstOrDefaultAsync(s => s.Id == sessionId); | ||||||
|   | |||||||
| @@ -43,7 +43,7 @@ public enum ChallengeType | |||||||
|     Oidc // Trying to connect other platforms |     Oidc // Trying to connect other platforms | ||||||
| } | } | ||||||
|  |  | ||||||
| public enum ChallengePlatform | public enum ClientPlatform | ||||||
| { | { | ||||||
|     Unidentified, |     Unidentified, | ||||||
|     Web, |     Web, | ||||||
| @@ -61,7 +61,6 @@ public class AuthChallenge : ModelBase | |||||||
|     public int StepRemain { get; set; } |     public int StepRemain { get; set; } | ||||||
|     public int StepTotal { get; set; } |     public int StepTotal { get; set; } | ||||||
|     public int FailedAttempts { get; set; } |     public int FailedAttempts { get; set; } | ||||||
|     public ChallengePlatform Platform { get; set; } = ChallengePlatform.Unidentified; |  | ||||||
|     public ChallengeType Type { get; set; } = ChallengeType.Login; |     public ChallengeType Type { get; set; } = ChallengeType.Login; | ||||||
|     [Column(TypeName = "jsonb")] public List<Guid> BlacklistFactors { get; set; } = new(); |     [Column(TypeName = "jsonb")] public List<Guid> BlacklistFactors { get; set; } = new(); | ||||||
|     [Column(TypeName = "jsonb")] public List<string> Audiences { get; set; } = new(); |     [Column(TypeName = "jsonb")] public List<string> Audiences { get; set; } = new(); | ||||||
| @@ -90,14 +89,13 @@ public class AuthChallenge : ModelBase | |||||||
|         StepRemain = StepRemain, |         StepRemain = StepRemain, | ||||||
|         StepTotal = StepTotal, |         StepTotal = StepTotal, | ||||||
|         FailedAttempts = FailedAttempts, |         FailedAttempts = FailedAttempts, | ||||||
|         Platform = (Shared.Proto.ChallengePlatform)Platform, |  | ||||||
|         Type = (Shared.Proto.ChallengeType)Type, |         Type = (Shared.Proto.ChallengeType)Type, | ||||||
|         BlacklistFactors = { BlacklistFactors.Select(x => x.ToString()) }, |         BlacklistFactors = { BlacklistFactors.Select(x => x.ToString()) }, | ||||||
|         Audiences = { Audiences }, |         Audiences = { Audiences }, | ||||||
|         Scopes = { Scopes }, |         Scopes = { Scopes }, | ||||||
|         IpAddress = IpAddress, |         IpAddress = IpAddress, | ||||||
|         UserAgent = UserAgent, |         UserAgent = UserAgent, | ||||||
|         DeviceId = Client.DeviceId.ToString(), |         DeviceId = Client!.DeviceId, | ||||||
|         Nonce = Nonce, |         Nonce = Nonce, | ||||||
|         AccountId = AccountId.ToString() |         AccountId = AccountId.ToString() | ||||||
|     }; |     }; | ||||||
| @@ -107,6 +105,7 @@ public class AuthChallenge : ModelBase | |||||||
| public class AuthClient : ModelBase | public class AuthClient : ModelBase | ||||||
| { | { | ||||||
|     public Guid Id { get; set; } = Guid.NewGuid(); |     public Guid Id { get; set; } = Guid.NewGuid(); | ||||||
|  |     public ClientPlatform Platform { get; set; } = ClientPlatform.Unidentified; | ||||||
|     [MaxLength(1024)] public string DeviceName { get; set; } = string.Empty; |     [MaxLength(1024)] public string DeviceName { get; set; } = string.Empty; | ||||||
|     [MaxLength(1024)] public string? DeviceLabel { get; set; } |     [MaxLength(1024)] public string? DeviceLabel { get; set; } | ||||||
|     [MaxLength(1024)] public string DeviceId { get; set; } = string.Empty; |     [MaxLength(1024)] public string DeviceId { get; set; } = string.Empty; | ||||||
| @@ -114,3 +113,21 @@ public class AuthClient : ModelBase | |||||||
|     public Guid AccountId { get; set; } |     public Guid AccountId { get; set; } | ||||||
|     [JsonIgnore] public Account.Account Account { get; set; } = null!; |     [JsonIgnore] public Account.Account Account { get; set; } = null!; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | public class AuthClientWithChallenge : AuthClient | ||||||
|  | { | ||||||
|  |     public List<AuthChallenge> Challenges { get; set; } = []; | ||||||
|  |      | ||||||
|  |     public static AuthClientWithChallenge FromClient(AuthClient client) | ||||||
|  |     { | ||||||
|  |         return new AuthClientWithChallenge | ||||||
|  |         { | ||||||
|  |             Id = client.Id, | ||||||
|  |             Platform = client.Platform, | ||||||
|  |             DeviceName = client.DeviceName, | ||||||
|  |             DeviceLabel = client.DeviceLabel, | ||||||
|  |             DeviceId = client.DeviceId, | ||||||
|  |             AccountId = client.AccountId, | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  | } | ||||||
|   | |||||||
| @@ -14,6 +14,7 @@ public class AppleMobileConnectRequest | |||||||
|  |  | ||||||
| public class AppleMobileSignInRequest : AppleMobileConnectRequest | public class AppleMobileSignInRequest : AppleMobileConnectRequest | ||||||
| { | { | ||||||
|     [Required] |     [Required] [MaxLength(512)] | ||||||
|     public required string DeviceId { get; set; } |     public required string DeviceId { get; set; } | ||||||
|  |     [MaxLength(1024)] public string? DeviceName { get; set; } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -96,7 +96,8 @@ public class OidcController( | |||||||
|                 userInfo, |                 userInfo, | ||||||
|                 account, |                 account, | ||||||
|                 HttpContext, |                 HttpContext, | ||||||
|                 request.DeviceId |                 request.DeviceId, | ||||||
|  |                 request.DeviceName | ||||||
|             ); |             ); | ||||||
|  |  | ||||||
|             return Ok(challenge); |             return Ok(challenge); | ||||||
|   | |||||||
| @@ -191,7 +191,8 @@ public abstract class OidcService( | |||||||
|         OidcUserInfo userInfo, |         OidcUserInfo userInfo, | ||||||
|         Account.Account account, |         Account.Account account, | ||||||
|         HttpContext request, |         HttpContext request, | ||||||
|         string deviceId |         string deviceId, | ||||||
|  |         string? deviceName = null | ||||||
|     ) |     ) | ||||||
|     { |     { | ||||||
|         // Create or update the account connection |         // Create or update the account connection | ||||||
| @@ -217,13 +218,12 @@ public abstract class OidcService( | |||||||
|  |  | ||||||
|         // Create a challenge that's already completed |         // Create a challenge that's already completed | ||||||
|         var now = SystemClock.Instance.GetCurrentInstant(); |         var now = SystemClock.Instance.GetCurrentInstant(); | ||||||
|         var device = await auth.GetOrCreateDeviceAsync(account.Id, deviceId); |         var device = await auth.GetOrCreateDeviceAsync(account.Id, deviceId, deviceName, ClientPlatform.Ios); | ||||||
|         var challenge = new AuthChallenge |         var challenge = new AuthChallenge | ||||||
|         { |         { | ||||||
|             ExpiredAt = now.Plus(Duration.FromHours(1)), |             ExpiredAt = now.Plus(Duration.FromHours(1)), | ||||||
|             StepTotal = await auth.DetectChallengeRisk(request.Request, account), |             StepTotal = await auth.DetectChallengeRisk(request.Request, account), | ||||||
|             Type = ChallengeType.Oidc, |             Type = ChallengeType.Oidc, | ||||||
|             Platform = ChallengePlatform.Unidentified, |  | ||||||
|             Audiences = [ProviderName], |             Audiences = [ProviderName], | ||||||
|             Scopes = ["*"], |             Scopes = ["*"], | ||||||
|             AccountId = account.Id, |             AccountId = account.Id, | ||||||
|   | |||||||
							
								
								
									
										1830
									
								
								DysonNetwork.Pass/Migrations/20250813121421_AddAuthDevicePlatform.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										1830
									
								
								DysonNetwork.Pass/Migrations/20250813121421_AddAuthDevicePlatform.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -0,0 +1,40 @@ | |||||||
|  | using Microsoft.EntityFrameworkCore.Migrations; | ||||||
|  |  | ||||||
|  | #nullable disable | ||||||
|  |  | ||||||
|  | namespace DysonNetwork.Pass.Migrations | ||||||
|  | { | ||||||
|  |     /// <inheritdoc /> | ||||||
|  |     public partial class AddAuthDevicePlatform : Migration | ||||||
|  |     { | ||||||
|  |         /// <inheritdoc /> | ||||||
|  |         protected override void Up(MigrationBuilder migrationBuilder) | ||||||
|  |         { | ||||||
|  |             migrationBuilder.DropColumn( | ||||||
|  |                 name: "platform", | ||||||
|  |                 table: "auth_challenges"); | ||||||
|  |  | ||||||
|  |             migrationBuilder.AddColumn<int>( | ||||||
|  |                 name: "platform", | ||||||
|  |                 table: "auth_clients", | ||||||
|  |                 type: "integer", | ||||||
|  |                 nullable: false, | ||||||
|  |                 defaultValue: 0); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <inheritdoc /> | ||||||
|  |         protected override void Down(MigrationBuilder migrationBuilder) | ||||||
|  |         { | ||||||
|  |             migrationBuilder.DropColumn( | ||||||
|  |                 name: "platform", | ||||||
|  |                 table: "auth_clients"); | ||||||
|  |  | ||||||
|  |             migrationBuilder.AddColumn<int>( | ||||||
|  |                 name: "platform", | ||||||
|  |                 table: "auth_challenges", | ||||||
|  |                 type: "integer", | ||||||
|  |                 nullable: false, | ||||||
|  |                 defaultValue: 0); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -856,10 +856,6 @@ namespace DysonNetwork.Pass.Migrations | |||||||
|                         .HasColumnType("character varying(1024)") |                         .HasColumnType("character varying(1024)") | ||||||
|                         .HasColumnName("nonce"); |                         .HasColumnName("nonce"); | ||||||
|  |  | ||||||
|                     b.Property<int>("Platform") |  | ||||||
|                         .HasColumnType("integer") |  | ||||||
|                         .HasColumnName("platform"); |  | ||||||
|  |  | ||||||
|                     b.Property<List<string>>("Scopes") |                     b.Property<List<string>>("Scopes") | ||||||
|                         .IsRequired() |                         .IsRequired() | ||||||
|                         .HasColumnType("jsonb") |                         .HasColumnType("jsonb") | ||||||
| @@ -934,6 +930,10 @@ namespace DysonNetwork.Pass.Migrations | |||||||
|                         .HasColumnType("character varying(1024)") |                         .HasColumnType("character varying(1024)") | ||||||
|                         .HasColumnName("device_name"); |                         .HasColumnName("device_name"); | ||||||
|  |  | ||||||
|  |                     b.Property<int>("Platform") | ||||||
|  |                         .HasColumnType("integer") | ||||||
|  |                         .HasColumnName("platform"); | ||||||
|  |  | ||||||
|                     b.Property<Instant>("UpdatedAt") |                     b.Property<Instant>("UpdatedAt") | ||||||
|                         .HasColumnType("timestamp with time zone") |                         .HasColumnType("timestamp with time zone") | ||||||
|                         .HasColumnName("updated_at"); |                         .HasColumnName("updated_at"); | ||||||
|   | |||||||
| @@ -45,7 +45,7 @@ public class WebSocketController(WebSocketService ws, ILogger<WebSocketContext> | |||||||
|                     Type = "error.dupe", |                     Type = "error.dupe", | ||||||
|                     ErrorMessage = "Too many connections from the same device and account." |                     ErrorMessage = "Too many connections from the same device and account." | ||||||
|                 }.ToBytes()), |                 }.ToBytes()), | ||||||
|                 WebSocketMessageType.Close, |                 WebSocketMessageType.Binary, | ||||||
|                 true, |                 true, | ||||||
|                 CancellationToken.None |                 CancellationToken.None | ||||||
|             ); |             ); | ||||||
|   | |||||||
| @@ -81,7 +81,7 @@ public class NotificationController( | |||||||
|  |  | ||||||
|         var result = |         var result = | ||||||
|             await nty.SubscribeDevice( |             await nty.SubscribeDevice( | ||||||
|                 currentSession.Challenge.DeviceId!, |                 currentSession.Challenge.DeviceId, | ||||||
|                 request.DeviceToken, |                 request.DeviceToken, | ||||||
|                 request.Provider, |                 request.Provider, | ||||||
|                 currentUser |                 currentUser | ||||||
|   | |||||||
| @@ -30,7 +30,6 @@ message AuthChallenge { | |||||||
|   int32 step_remain = 3; |   int32 step_remain = 3; | ||||||
|   int32 step_total = 4; |   int32 step_total = 4; | ||||||
|   int32 failed_attempts = 5; |   int32 failed_attempts = 5; | ||||||
|   ChallengePlatform platform = 6; |  | ||||||
|   ChallengeType type = 7; |   ChallengeType type = 7; | ||||||
|   repeated string blacklist_factors = 8; |   repeated string blacklist_factors = 8; | ||||||
|   repeated string audiences = 9; |   repeated string audiences = 9; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user