♻️ Refactored the authorized device (now client)
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using DysonNetwork.Pass.Auth;
|
||||
using DysonNetwork.Pass.Permission;
|
||||
using DysonNetwork.Pass.Wallet;
|
||||
using DysonNetwork.Shared.Data;
|
||||
@@ -9,7 +10,6 @@ using Microsoft.EntityFrameworkCore;
|
||||
using NodaTime;
|
||||
using AuthService = DysonNetwork.Pass.Auth.AuthService;
|
||||
using AuthSession = DysonNetwork.Pass.Auth.AuthSession;
|
||||
using ChallengePlatform = DysonNetwork.Pass.Auth.ChallengePlatform;
|
||||
|
||||
namespace DysonNetwork.Pass.Account;
|
||||
|
||||
@@ -437,25 +437,16 @@ public class AccountCurrentController(
|
||||
}
|
||||
}
|
||||
|
||||
public class AuthorizedDevice
|
||||
{
|
||||
public string? Label { get; set; }
|
||||
public string UserAgent { get; set; } = null!;
|
||||
public string DeviceId { get; set; } = null!;
|
||||
public ChallengePlatform Platform { get; set; }
|
||||
public List<AuthSession> Sessions { get; set; } = [];
|
||||
}
|
||||
|
||||
[HttpGet("devices")]
|
||||
[Authorize]
|
||||
public async Task<ActionResult<List<AuthorizedDevice>>> GetDevices()
|
||||
public async Task<ActionResult<List<AuthClient>>> GetDevices()
|
||||
{
|
||||
if (HttpContext.Items["CurrentUser"] is not Account currentUser ||
|
||||
HttpContext.Items["CurrentSession"] is not AuthSession currentSession) return Unauthorized();
|
||||
|
||||
Response.Headers.Append("X-Auth-Session", currentSession.Id.ToString());
|
||||
|
||||
var devices = await db.AuthDevices
|
||||
var devices = await db.AuthClients
|
||||
.Where(device => device.AccountId == currentUser.Id)
|
||||
.ToListAsync();
|
||||
|
||||
@@ -525,14 +516,14 @@ public class AccountCurrentController(
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPatch("sessions/{id:guid}/label")]
|
||||
public async Task<ActionResult<AuthSession>> UpdateSessionLabel(Guid id, [FromBody] string label)
|
||||
[HttpPatch("devices/{id}/label")]
|
||||
public async Task<ActionResult<AuthSession>> UpdateDeviceLabel(string id, [FromBody] string label)
|
||||
{
|
||||
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
|
||||
|
||||
try
|
||||
{
|
||||
await accounts.UpdateSessionLabel(currentUser, id, label);
|
||||
await accounts.UpdateDeviceName(currentUser, id, label);
|
||||
return NoContent();
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -541,15 +532,18 @@ public class AccountCurrentController(
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPatch("sessions/current/label")]
|
||||
public async Task<ActionResult<AuthSession>> UpdateCurrentSessionLabel([FromBody] string label)
|
||||
[HttpPatch("devices/current/label")]
|
||||
public async Task<ActionResult<AuthSession>> UpdateCurrentDeviceLabel([FromBody] string label)
|
||||
{
|
||||
if (HttpContext.Items["CurrentUser"] is not Account currentUser ||
|
||||
HttpContext.Items["CurrentSession"] is not AuthSession currentSession) return Unauthorized();
|
||||
|
||||
var device = await db.AuthClients.FirstOrDefaultAsync(d => d.Id == currentSession.Challenge.ClientId);
|
||||
if (device is null) return NotFound();
|
||||
|
||||
try
|
||||
{
|
||||
await accounts.UpdateSessionLabel(currentUser, currentSession.Id, label);
|
||||
await accounts.UpdateDeviceName(currentUser, device.DeviceId, label);
|
||||
return NoContent();
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -637,7 +631,7 @@ public class AccountCurrentController(
|
||||
return BadRequest(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[HttpPost("contacts/{id:guid}/public")]
|
||||
[Authorize]
|
||||
public async Task<ActionResult<AccountContact>> SetPublicContact(Guid id)
|
||||
@@ -659,7 +653,7 @@ public class AccountCurrentController(
|
||||
return BadRequest(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[HttpDelete("contacts/{id:guid}/public")]
|
||||
[Authorize]
|
||||
public async Task<ActionResult<AccountContact>> UnsetPublicContact(Guid id)
|
||||
@@ -733,4 +727,4 @@ public class AccountCurrentController(
|
||||
return BadRequest(ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -11,7 +11,6 @@ using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using NodaTime;
|
||||
using OtpNet;
|
||||
using AuthSession = DysonNetwork.Pass.Auth.AuthSession;
|
||||
|
||||
namespace DysonNetwork.Pass.Account;
|
||||
|
||||
@@ -458,37 +457,28 @@ public class AccountService(
|
||||
|
||||
public async Task<bool> IsDeviceActive(Guid id)
|
||||
{
|
||||
return await db.AuthChallenges.AnyAsync(d => d.DeviceId == id);
|
||||
return await db.AuthSessions
|
||||
.Include(s => s.Challenge)
|
||||
.AnyAsync(s => s.Challenge.ClientId == id);
|
||||
}
|
||||
|
||||
public async Task<AuthSession> UpdateSessionLabel(Account account, Guid sessionId, string label)
|
||||
public async Task<AuthClient> UpdateDeviceName(Account account, string deviceId, string label)
|
||||
{
|
||||
var session = await db.AuthSessions
|
||||
.Include(s => s.Challenge)
|
||||
.Where(s => s.Id == sessionId && s.AccountId == account.Id)
|
||||
.FirstOrDefaultAsync();
|
||||
if (session is null) throw new InvalidOperationException("Session was not found.");
|
||||
var device = await db.AuthClients.FirstOrDefaultAsync(d => d.DeviceId == deviceId && d.AccountId == account.Id);
|
||||
if (device is null) throw new InvalidOperationException("Device was not found.");
|
||||
|
||||
await db.AuthSessions
|
||||
.Include(s => s.Challenge)
|
||||
.Where(s => s.Challenge.DeviceId == session.Challenge.DeviceId)
|
||||
.ExecuteUpdateAsync(p => p.SetProperty(s => s.Label, label));
|
||||
device.DeviceLabel = label;
|
||||
db.Update(device);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
var sessions = await db.AuthSessions
|
||||
.Include(s => s.Challenge)
|
||||
.Where(s => s.AccountId == session.Id && s.Challenge.DeviceId == session.Challenge.DeviceId)
|
||||
.ToListAsync();
|
||||
foreach (var item in sessions)
|
||||
await cache.RemoveAsync($"{DysonTokenAuthHandler.AuthCachePrefix}{item.Id}");
|
||||
|
||||
return session;
|
||||
return device;
|
||||
}
|
||||
|
||||
public async Task DeleteSession(Account account, Guid sessionId)
|
||||
{
|
||||
var session = await db.AuthSessions
|
||||
.Include(s => s.Challenge)
|
||||
.ThenInclude(s => s.Device)
|
||||
.ThenInclude(s => s.Client)
|
||||
.Where(s => s.Id == sessionId && s.AccountId == account.Id)
|
||||
.FirstOrDefaultAsync();
|
||||
if (session is null) throw new InvalidOperationException("Session was not found.");
|
||||
@@ -498,10 +488,13 @@ public class AccountService(
|
||||
.Where(s => s.AccountId == session.Id && s.Challenge.DeviceId == session.Challenge.DeviceId)
|
||||
.ToListAsync();
|
||||
|
||||
if (!await IsDeviceActive(session.Challenge.DeviceId))
|
||||
await pusher.UnsubscribePushNotificationsAsync(new UnsubscribePushNotificationsRequest()
|
||||
{ DeviceId = session.Challenge.Device.DeviceId }
|
||||
);
|
||||
if (session.Challenge.ClientId.HasValue)
|
||||
{
|
||||
if (!await IsDeviceActive(session.Challenge.ClientId.Value))
|
||||
await pusher.UnsubscribePushNotificationsAsync(new UnsubscribePushNotificationsRequest()
|
||||
{ DeviceId = session.Challenge.Client!.DeviceId }
|
||||
);
|
||||
}
|
||||
|
||||
// The current session should be included in the sessions' list
|
||||
await db.AuthSessions
|
||||
@@ -679,4 +672,4 @@ public class AccountService(
|
||||
await db.BulkInsertAsync(newProfiles);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -37,15 +37,15 @@ public class AppDatabase(
|
||||
|
||||
public DbSet<AuthSession> AuthSessions { get; set; }
|
||||
public DbSet<AuthChallenge> AuthChallenges { get; set; }
|
||||
public DbSet<AuthDevice> AuthDevices { get; set; }
|
||||
|
||||
public DbSet<AuthClient> AuthClients { get; set; }
|
||||
|
||||
public DbSet<Wallet.Wallet> Wallets { get; set; }
|
||||
public DbSet<WalletPocket> WalletPockets { get; set; }
|
||||
public DbSet<Order> PaymentOrders { get; set; }
|
||||
public DbSet<Transaction> PaymentTransactions { get; set; }
|
||||
public DbSet<Subscription> WalletSubscriptions { get; set; }
|
||||
public DbSet<Coupon> WalletCoupons { get; set; }
|
||||
|
||||
|
||||
public DbSet<Punishment> Punishments { get; set; }
|
||||
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
@@ -89,7 +89,7 @@ public class AppDatabase(
|
||||
}
|
||||
});
|
||||
|
||||
optionsBuilder.UseSeeding((context, _) => {});
|
||||
optionsBuilder.UseSeeding((context, _) => { });
|
||||
|
||||
base.OnConfiguring(optionsBuilder);
|
||||
}
|
||||
@@ -270,4 +270,4 @@ public static class OptionalQueryExtensions
|
||||
{
|
||||
return condition ? transform(source) : source;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -68,7 +68,7 @@ public class AuthController(
|
||||
IpAddress = ipAddress,
|
||||
UserAgent = userAgent,
|
||||
Location = geo.GetPointFromIp(ipAddress),
|
||||
DeviceId = device.Id,
|
||||
ClientId = device.Id,
|
||||
AccountId = account.Id
|
||||
}.Normalize();
|
||||
|
||||
|
@@ -1,17 +0,0 @@
|
||||
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!;
|
||||
}
|
@@ -100,17 +100,17 @@ public class AuthService(
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
public async Task<AuthDevice> GetOrCreateDeviceAsync(Guid accountId, string deviceId)
|
||||
|
||||
public async Task<AuthClient> GetOrCreateDeviceAsync(Guid accountId, string deviceId)
|
||||
{
|
||||
var device = await db.AuthDevices.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;
|
||||
device = new AuthDevice
|
||||
device = new AuthClient
|
||||
{
|
||||
DeviceId = deviceId,
|
||||
AccountId = accountId
|
||||
};
|
||||
db.AuthDevices.Add(device);
|
||||
db.AuthClients.Add(device);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
return device;
|
||||
@@ -203,43 +203,43 @@ public class AuthService(
|
||||
// Check if the session is already in sudo mode (cached)
|
||||
var sudoModeKey = $"accounts:{session.Id}:sudo";
|
||||
var (found, _) = await cache.GetAsyncWithStatus<bool>(sudoModeKey);
|
||||
|
||||
|
||||
if (found)
|
||||
{
|
||||
// Session is already in sudo mode
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Check if the user has a pin code
|
||||
var hasPinCode = await db.AccountAuthFactors
|
||||
.Where(f => f.AccountId == session.AccountId)
|
||||
.Where(f => f.EnabledAt != null)
|
||||
.Where(f => f.Type == AccountAuthFactorType.PinCode)
|
||||
.AnyAsync();
|
||||
|
||||
|
||||
if (!hasPinCode)
|
||||
{
|
||||
// User doesn't have a pin code, no validation needed
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// If pin code is not provided, we can't validate
|
||||
if (string.IsNullOrEmpty(pinCode))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
// Validate the pin code
|
||||
var isValid = await ValidatePinCode(session.AccountId, pinCode);
|
||||
|
||||
|
||||
if (isValid)
|
||||
{
|
||||
// Set session in sudo mode for 5 minutes
|
||||
await cache.SetAsync(sudoModeKey, true, TimeSpan.FromMinutes(5));
|
||||
}
|
||||
|
||||
|
||||
return isValid;
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
@@ -316,4 +316,4 @@ public class AuthService(
|
||||
|
||||
return Convert.FromBase64String(padded);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -2,6 +2,7 @@ using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Text.Json.Serialization;
|
||||
using DysonNetwork.Shared.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NodaTime;
|
||||
using NodaTime.Serialization.Protobuf;
|
||||
using Point = NetTopologySuite.Geometries.Point;
|
||||
@@ -68,12 +69,13 @@ public class AuthChallenge : ModelBase
|
||||
[MaxLength(128)] public string? IpAddress { get; set; }
|
||||
[MaxLength(512)] public string? UserAgent { get; set; }
|
||||
[MaxLength(1024)] public string? Nonce { get; set; }
|
||||
[MaxLength(1024)] public string? DeviceId { get; set; } = string.Empty;
|
||||
public Point? Location { get; set; }
|
||||
|
||||
public Guid AccountId { get; set; }
|
||||
[JsonIgnore] public Account.Account Account { get; set; } = null!;
|
||||
public Guid DeviceId { get; set; }
|
||||
public AuthDevice Device { get; set; } = null!;
|
||||
public Guid? ClientId { get; set; }
|
||||
public AuthClient? Client { get; set; } = null!;
|
||||
|
||||
public AuthChallenge Normalize()
|
||||
{
|
||||
@@ -95,8 +97,20 @@ public class AuthChallenge : ModelBase
|
||||
Scopes = { Scopes },
|
||||
IpAddress = IpAddress,
|
||||
UserAgent = UserAgent,
|
||||
DeviceId = DeviceId.ToString(),
|
||||
DeviceId = Client.DeviceId.ToString(),
|
||||
Nonce = Nonce,
|
||||
AccountId = AccountId.ToString()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Index(nameof(DeviceId), IsUnique = true)]
|
||||
public class AuthClient : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
[MaxLength(1024)] public string DeviceName { get; set; } = string.Empty;
|
||||
[MaxLength(1024)] public string? DeviceLabel { get; set; }
|
||||
[MaxLength(1024)] public string DeviceId { get; set; } = string.Empty;
|
||||
|
||||
public Guid AccountId { get; set; }
|
||||
[JsonIgnore] public Account.Account Account { get; set; } = null!;
|
||||
}
|
@@ -227,7 +227,7 @@ public abstract class OidcService(
|
||||
Audiences = [ProviderName],
|
||||
Scopes = ["*"],
|
||||
AccountId = account.Id,
|
||||
DeviceId = device.Id,
|
||||
ClientId = device.Id,
|
||||
IpAddress = request.Connection.RemoteIpAddress?.ToString() ?? null,
|
||||
UserAgent = request.Request.Headers.UserAgent,
|
||||
};
|
||||
|
1830
DysonNetwork.Pass/Migrations/20250813072436_AddAuthorizeDevice.Designer.cs
generated
Normal file
1830
DysonNetwork.Pass/Migrations/20250813072436_AddAuthorizeDevice.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,110 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using NodaTime;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DysonNetwork.Pass.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddAuthorizeDevice : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "device_id",
|
||||
table: "auth_challenges",
|
||||
type: "character varying(1024)",
|
||||
maxLength: 1024,
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(256)",
|
||||
oldMaxLength: 256,
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<Guid>(
|
||||
name: "client_id",
|
||||
table: "auth_challenges",
|
||||
type: "uuid",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "auth_clients",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
device_name = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||
device_label = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
|
||||
device_id = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||
account_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_auth_clients", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_auth_clients_accounts_account_id",
|
||||
column: x => x.account_id,
|
||||
principalTable: "accounts",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_auth_challenges_client_id",
|
||||
table: "auth_challenges",
|
||||
column: "client_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_auth_clients_account_id",
|
||||
table: "auth_clients",
|
||||
column: "account_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_auth_clients_device_id",
|
||||
table: "auth_clients",
|
||||
column: "device_id",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "fk_auth_challenges_auth_clients_client_id",
|
||||
table: "auth_challenges",
|
||||
column: "client_id",
|
||||
principalTable: "auth_clients",
|
||||
principalColumn: "id");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "fk_auth_challenges_auth_clients_client_id",
|
||||
table: "auth_challenges");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "auth_clients");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "ix_auth_challenges_client_id",
|
||||
table: "auth_challenges");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "client_id",
|
||||
table: "auth_challenges");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "device_id",
|
||||
table: "auth_challenges",
|
||||
type: "character varying(256)",
|
||||
maxLength: 256,
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(1024)",
|
||||
oldMaxLength: 1024,
|
||||
oldNullable: true);
|
||||
}
|
||||
}
|
||||
}
|
@@ -435,7 +435,7 @@ namespace DysonNetwork.Pass.Migrations
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("last_seen_at");
|
||||
|
||||
b.Property<Dictionary<string, string>>("Links")
|
||||
b.Property<List<ProfileLink>>("Links")
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("links");
|
||||
|
||||
@@ -817,6 +817,10 @@ namespace DysonNetwork.Pass.Migrations
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("blacklist_factors");
|
||||
|
||||
b.Property<Guid?>("ClientId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("client_id");
|
||||
|
||||
b.Property<Instant>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
@@ -826,8 +830,8 @@ namespace DysonNetwork.Pass.Migrations
|
||||
.HasColumnName("deleted_at");
|
||||
|
||||
b.Property<string>("DeviceId")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)")
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasColumnName("device_id");
|
||||
|
||||
b.Property<Instant?>("ExpiredAt")
|
||||
@@ -888,9 +892,65 @@ namespace DysonNetwork.Pass.Migrations
|
||||
b.HasIndex("AccountId")
|
||||
.HasDatabaseName("ix_auth_challenges_account_id");
|
||||
|
||||
b.HasIndex("ClientId")
|
||||
.HasDatabaseName("ix_auth_challenges_client_id");
|
||||
|
||||
b.ToTable("auth_challenges", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Pass.Auth.AuthClient", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<Guid>("AccountId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("account_id");
|
||||
|
||||
b.Property<Instant>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
b.Property<Instant?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("deleted_at");
|
||||
|
||||
b.Property<string>("DeviceId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasColumnName("device_id");
|
||||
|
||||
b.Property<string>("DeviceLabel")
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasColumnName("device_label");
|
||||
|
||||
b.Property<string>("DeviceName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasColumnName("device_name");
|
||||
|
||||
b.Property<Instant>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated_at");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_auth_clients");
|
||||
|
||||
b.HasIndex("AccountId")
|
||||
.HasDatabaseName("ix_auth_clients_account_id");
|
||||
|
||||
b.HasIndex("DeviceId")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("ix_auth_clients_device_id");
|
||||
|
||||
b.ToTable("auth_clients", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Pass.Auth.AuthSession", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
@@ -1586,6 +1646,25 @@ namespace DysonNetwork.Pass.Migrations
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_auth_challenges_accounts_account_id");
|
||||
|
||||
b.HasOne("DysonNetwork.Pass.Auth.AuthClient", "Client")
|
||||
.WithMany()
|
||||
.HasForeignKey("ClientId")
|
||||
.HasConstraintName("fk_auth_challenges_auth_clients_client_id");
|
||||
|
||||
b.Navigation("Account");
|
||||
|
||||
b.Navigation("Client");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Pass.Auth.AuthClient", b =>
|
||||
{
|
||||
b.HasOne("DysonNetwork.Pass.Account.Account", "Account")
|
||||
.WithMany()
|
||||
.HasForeignKey("AccountId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_auth_clients_accounts_account_id");
|
||||
|
||||
b.Navigation("Account");
|
||||
});
|
||||
|
||||
|
Reference in New Issue
Block a user