♻️ Refactored authorize device system (wip) (skip ci)
This commit is contained in:
@@ -455,27 +455,11 @@ public class AccountCurrentController(
|
|||||||
|
|
||||||
Response.Headers.Append("X-Auth-Session", currentSession.Id.ToString());
|
Response.Headers.Append("X-Auth-Session", currentSession.Id.ToString());
|
||||||
|
|
||||||
// Group sessions by the related DeviceId, then create an AuthorizedDevice for each group.
|
var devices = await db.AuthDevices
|
||||||
var deviceGroups = await db.AuthSessions
|
.Where(device => device.AccountId == currentUser.Id)
|
||||||
.Where(s => s.Account.Id == currentUser.Id)
|
|
||||||
.Include(s => s.Challenge)
|
|
||||||
.GroupBy(s => s.Challenge.DeviceId!)
|
|
||||||
.Select(g => new AuthorizedDevice
|
|
||||||
{
|
|
||||||
DeviceId = g.Key!,
|
|
||||||
UserAgent = g.First(x => x.Challenge.UserAgent != null).Challenge.UserAgent!,
|
|
||||||
Platform = g.First().Challenge.Platform!,
|
|
||||||
Label = g.Where(x => !string.IsNullOrWhiteSpace(x.Label)).Select(x => x.Label).FirstOrDefault(),
|
|
||||||
Sessions = g
|
|
||||||
.OrderByDescending(x => x.LastGrantedAt)
|
|
||||||
.ToList()
|
|
||||||
})
|
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
deviceGroups = deviceGroups
|
|
||||||
.OrderByDescending(s => s.Sessions.First().LastGrantedAt)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
return Ok(deviceGroups);
|
return Ok(devices);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("sessions")]
|
[HttpGet("sessions")]
|
||||||
|
@@ -456,6 +456,11 @@ public class AccountService(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<bool> IsDeviceActive(Guid id)
|
||||||
|
{
|
||||||
|
return await db.AuthChallenges.AnyAsync(d => d.DeviceId == id);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<AuthSession> UpdateSessionLabel(Account account, Guid sessionId, string label)
|
public async Task<AuthSession> UpdateSessionLabel(Account account, Guid sessionId, string label)
|
||||||
{
|
{
|
||||||
var session = await db.AuthSessions
|
var session = await db.AuthSessions
|
||||||
@@ -483,6 +488,7 @@ public class AccountService(
|
|||||||
{
|
{
|
||||||
var session = await db.AuthSessions
|
var session = await db.AuthSessions
|
||||||
.Include(s => s.Challenge)
|
.Include(s => s.Challenge)
|
||||||
|
.ThenInclude(s => s.Device)
|
||||||
.Where(s => s.Id == sessionId && s.AccountId == account.Id)
|
.Where(s => s.Id == sessionId && s.AccountId == account.Id)
|
||||||
.FirstOrDefaultAsync();
|
.FirstOrDefaultAsync();
|
||||||
if (session is null) throw new InvalidOperationException("Session was not found.");
|
if (session is null) throw new InvalidOperationException("Session was not found.");
|
||||||
@@ -492,11 +498,10 @@ public class AccountService(
|
|||||||
.Where(s => s.AccountId == session.Id && s.Challenge.DeviceId == session.Challenge.DeviceId)
|
.Where(s => s.AccountId == session.Id && s.Challenge.DeviceId == session.Challenge.DeviceId)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
if (session.Challenge.DeviceId is not null)
|
if (!await IsDeviceActive(session.Challenge.DeviceId))
|
||||||
await pusher.UnsubscribePushNotificationsAsync(new UnsubscribePushNotificationsRequest()
|
await pusher.UnsubscribePushNotificationsAsync(new UnsubscribePushNotificationsRequest()
|
||||||
{
|
{ DeviceId = session.Challenge.Device.DeviceId }
|
||||||
DeviceId = session.Challenge.DeviceId
|
);
|
||||||
});
|
|
||||||
|
|
||||||
// The current session should be included in the sessions' list
|
// The current session should be included in the sessions' list
|
||||||
await db.AuthSessions
|
await db.AuthSessions
|
||||||
|
@@ -37,6 +37,7 @@ public class AppDatabase(
|
|||||||
|
|
||||||
public DbSet<AuthSession> AuthSessions { get; set; }
|
public DbSet<AuthSession> AuthSessions { get; set; }
|
||||||
public DbSet<AuthChallenge> AuthChallenges { get; set; }
|
public DbSet<AuthChallenge> AuthChallenges { get; set; }
|
||||||
|
public DbSet<AuthDevice> AuthDevices { get; set; }
|
||||||
|
|
||||||
public DbSet<Wallet.Wallet> Wallets { get; set; }
|
public DbSet<Wallet.Wallet> Wallets { get; set; }
|
||||||
public DbSet<WalletPocket> WalletPockets { get; set; }
|
public DbSet<WalletPocket> WalletPockets { get; set; }
|
||||||
|
@@ -57,6 +57,7 @@ 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 challenge = new AuthChallenge
|
var challenge = new AuthChallenge
|
||||||
{
|
{
|
||||||
ExpiredAt = Instant.FromDateTimeUtc(DateTime.UtcNow.AddHours(1)),
|
ExpiredAt = Instant.FromDateTimeUtc(DateTime.UtcNow.AddHours(1)),
|
||||||
@@ -67,7 +68,7 @@ public class AuthController(
|
|||||||
IpAddress = ipAddress,
|
IpAddress = ipAddress,
|
||||||
UserAgent = userAgent,
|
UserAgent = userAgent,
|
||||||
Location = geo.GetPointFromIp(ipAddress),
|
Location = geo.GetPointFromIp(ipAddress),
|
||||||
DeviceId = request.DeviceId,
|
DeviceId = device.Id,
|
||||||
AccountId = account.Id
|
AccountId = account.Id
|
||||||
}.Normalize();
|
}.Normalize();
|
||||||
|
|
||||||
|
17
DysonNetwork.Pass/Auth/AuthDevice.cs
Normal file
17
DysonNetwork.Pass/Auth/AuthDevice.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using DysonNetwork.Shared.Data;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace DysonNetwork.Pass.Auth;
|
||||||
|
|
||||||
|
[Index(nameof(DeviceId), IsUnique = true)]
|
||||||
|
public class AuthDevice : ModelBase
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; } = Guid.NewGuid();
|
||||||
|
[MaxLength(1024)] public string DeviceName { get; set; } = string.Empty;
|
||||||
|
[MaxLength(1024)] public string DeviceId { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public Guid AccountId { get; set; }
|
||||||
|
[JsonIgnore] public Account.Account Account { get; set; } = null!;
|
||||||
|
}
|
@@ -101,6 +101,21 @@ public class AuthService(
|
|||||||
return session;
|
return session;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<AuthDevice> GetOrCreateDeviceAsync(Guid accountId, string deviceId)
|
||||||
|
{
|
||||||
|
var device = await db.AuthDevices.FirstOrDefaultAsync(d => d.DeviceId == deviceId && d.AccountId == accountId);
|
||||||
|
if (device is not null) return device;
|
||||||
|
device = new AuthDevice
|
||||||
|
{
|
||||||
|
DeviceId = deviceId,
|
||||||
|
AccountId = accountId
|
||||||
|
};
|
||||||
|
db.AuthDevices.Add(device);
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
|
return device;
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<bool> ValidateCaptcha(string token)
|
public async Task<bool> ValidateCaptcha(string token)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(token)) return false;
|
if (string.IsNullOrWhiteSpace(token)) return false;
|
||||||
|
@@ -217,6 +217,7 @@ 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 challenge = new AuthChallenge
|
var challenge = new AuthChallenge
|
||||||
{
|
{
|
||||||
ExpiredAt = now.Plus(Duration.FromHours(1)),
|
ExpiredAt = now.Plus(Duration.FromHours(1)),
|
||||||
@@ -226,7 +227,7 @@ public abstract class OidcService(
|
|||||||
Audiences = [ProviderName],
|
Audiences = [ProviderName],
|
||||||
Scopes = ["*"],
|
Scopes = ["*"],
|
||||||
AccountId = account.Id,
|
AccountId = account.Id,
|
||||||
DeviceId = deviceId,
|
DeviceId = device.Id,
|
||||||
IpAddress = request.Connection.RemoteIpAddress?.ToString() ?? null,
|
IpAddress = request.Connection.RemoteIpAddress?.ToString() ?? null,
|
||||||
UserAgent = request.Request.Headers.UserAgent,
|
UserAgent = request.Request.Headers.UserAgent,
|
||||||
};
|
};
|
||||||
|
@@ -67,12 +67,13 @@ public class AuthChallenge : ModelBase
|
|||||||
[Column(TypeName = "jsonb")] public List<string> Scopes { get; set; } = new();
|
[Column(TypeName = "jsonb")] public List<string> Scopes { get; set; } = new();
|
||||||
[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; }
|
||||||
[MaxLength(256)] public string? DeviceId { get; set; }
|
|
||||||
[MaxLength(1024)] public string? Nonce { get; set; }
|
[MaxLength(1024)] public string? Nonce { get; set; }
|
||||||
public Point? Location { get; set; }
|
public Point? Location { get; set; }
|
||||||
|
|
||||||
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 Guid DeviceId { get; set; }
|
||||||
|
public AuthDevice Device { get; set; } = null!;
|
||||||
|
|
||||||
public AuthChallenge Normalize()
|
public AuthChallenge Normalize()
|
||||||
{
|
{
|
||||||
@@ -94,7 +95,7 @@ public class AuthChallenge : ModelBase
|
|||||||
Scopes = { Scopes },
|
Scopes = { Scopes },
|
||||||
IpAddress = IpAddress,
|
IpAddress = IpAddress,
|
||||||
UserAgent = UserAgent,
|
UserAgent = UserAgent,
|
||||||
DeviceId = DeviceId,
|
DeviceId = DeviceId.ToString(),
|
||||||
Nonce = Nonce,
|
Nonce = Nonce,
|
||||||
AccountId = AccountId.ToString()
|
AccountId = AccountId.ToString()
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user