:drunk: No idea what did AI did
This commit is contained in:
80
DysonNetwork.Pass/Features/Auth/Models/Account.cs
Normal file
80
DysonNetwork.Pass/Features/Auth/Models/Account.cs
Normal file
@ -0,0 +1,80 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Text.Json.Serialization;
|
||||
using DysonNetwork.Common.Models;
|
||||
using NodaTime;
|
||||
|
||||
namespace DysonNetwork.Pass.Features.Auth.Models;
|
||||
|
||||
[Index(nameof(Email), IsUnique = true)]
|
||||
public class Account : ModelBase
|
||||
{
|
||||
[Key]
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
public Guid Id { get; set; }
|
||||
|
||||
[Required]
|
||||
[MaxLength(256)]
|
||||
public string Email { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
[MaxLength(256)]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
[MaxLength(32)]
|
||||
public string? Status { get; set; }
|
||||
|
||||
[Required]
|
||||
public Instant CreatedAt { get; set; } = SystemClock.Instance.GetCurrentInstant();
|
||||
|
||||
[Required]
|
||||
public Instant UpdatedAt { get; set; } = SystemClock.Instance.GetCurrentInstant();
|
||||
|
||||
// Navigation properties
|
||||
[JsonIgnore]
|
||||
public virtual ICollection<AuthSession> Sessions { get; set; } = new List<AuthSession>();
|
||||
|
||||
[JsonIgnore]
|
||||
public virtual ICollection<AuthChallenge> Challenges { get; set; } = new List<AuthChallenge>();
|
||||
|
||||
[JsonIgnore]
|
||||
public virtual ICollection<AccountAuthFactor> AuthFactors { get; set; } = new List<AccountAuthFactor>();
|
||||
|
||||
[JsonIgnore]
|
||||
public virtual ICollection<AccountConnection> Connections { get; set; } = new List<AccountConnection>();
|
||||
|
||||
public void UpdateTimestamp()
|
||||
{
|
||||
UpdatedAt = SystemClock.Instance.GetCurrentInstant();
|
||||
}
|
||||
|
||||
public static Account FromCommonModel(DysonNetwork.Common.Models.Account commonAccount)
|
||||
{
|
||||
return new Account
|
||||
{
|
||||
Id = Guid.Parse(commonAccount.Id),
|
||||
Email = commonAccount.Profile?.Email ?? string.Empty,
|
||||
Name = commonAccount.Name,
|
||||
Status = commonAccount.Status,
|
||||
CreatedAt = commonAccount.CreatedAt,
|
||||
UpdatedAt = commonAccount.UpdatedAt
|
||||
};
|
||||
}
|
||||
|
||||
public DysonNetwork.Common.Models.Account ToCommonModel()
|
||||
{
|
||||
return new DysonNetwork.Common.Models.Account
|
||||
{
|
||||
Id = Id.ToString(),
|
||||
Name = Name,
|
||||
Status = Status,
|
||||
CreatedAt = CreatedAt,
|
||||
UpdatedAt = UpdatedAt,
|
||||
Profile = new DysonNetwork.Common.Models.Profile
|
||||
{
|
||||
Email = Email,
|
||||
DisplayName = Name
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
74
DysonNetwork.Pass/Features/Auth/Models/AccountAuthFactor.cs
Normal file
74
DysonNetwork.Pass/Features/Auth/Models/AccountAuthFactor.cs
Normal file
@ -0,0 +1,74 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Text.Json;
|
||||
using DysonNetwork.Common.Models;
|
||||
using NodaTime;
|
||||
|
||||
namespace DysonNetwork.Pass.Features.Auth.Models;
|
||||
|
||||
public class AccountAuthFactor : ModelBase
|
||||
{
|
||||
[Required]
|
||||
public Guid AccountId { get; set; }
|
||||
|
||||
[ForeignKey(nameof(AccountId))]
|
||||
public virtual Account Account { get; set; } = null!;
|
||||
|
||||
[Required]
|
||||
public AuthFactorType FactorType { get; set; }
|
||||
|
||||
[Required]
|
||||
[MaxLength(100)]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
[MaxLength(500)]
|
||||
public string? Description { get; set; }
|
||||
|
||||
[Required]
|
||||
public string Secret { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
public bool IsDefault { get; set; }
|
||||
|
||||
[Required]
|
||||
public bool IsBackup { get; set; }
|
||||
|
||||
public Instant? LastUsedAt { get; set; }
|
||||
|
||||
public Instant? EnabledAt { get; set; }
|
||||
|
||||
public Instant? DisabledAt { get; set; }
|
||||
|
||||
[Column(TypeName = "jsonb")]
|
||||
public JsonDocument? Metadata { get; set; }
|
||||
|
||||
// Navigation property for related AuthSessions
|
||||
public virtual ICollection<AuthSession> Sessions { get; set; } = new List<AuthSession>();
|
||||
|
||||
public void UpdateMetadata(Action<JsonDocument> updateAction)
|
||||
{
|
||||
if (Metadata == null)
|
||||
{
|
||||
Metadata = JsonSerializer.SerializeToDocument(new { });
|
||||
}
|
||||
|
||||
updateAction(Metadata);
|
||||
}
|
||||
|
||||
public void MarkAsUsed()
|
||||
{
|
||||
LastUsedAt = SystemClock.Instance.GetCurrentInstant();
|
||||
}
|
||||
|
||||
public void Enable()
|
||||
{
|
||||
EnabledAt = SystemClock.Instance.GetCurrentInstant();
|
||||
DisabledAt = null;
|
||||
}
|
||||
|
||||
public void Disable()
|
||||
{
|
||||
DisabledAt = SystemClock.Instance.GetCurrentInstant();
|
||||
}
|
||||
}
|
70
DysonNetwork.Pass/Features/Auth/Models/AccountConnection.cs
Normal file
70
DysonNetwork.Pass/Features/Auth/Models/AccountConnection.cs
Normal file
@ -0,0 +1,70 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using DysonNetwork.Pass.Models;
|
||||
using NodaTime;
|
||||
|
||||
namespace DysonNetwork.Pass.Features.Auth.Models;
|
||||
|
||||
public class AccountConnection : ModelBase
|
||||
{
|
||||
[Required]
|
||||
public Guid AccountId { get; set; }
|
||||
|
||||
[ForeignKey(nameof(AccountId))]
|
||||
[JsonIgnore]
|
||||
public virtual Account Account { get; set; } = null!;
|
||||
|
||||
[Required]
|
||||
[MaxLength(50)]
|
||||
public string Provider { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
[MaxLength(256)]
|
||||
public string ProviderId { get; set; } = string.Empty;
|
||||
|
||||
[MaxLength(256)]
|
||||
public string? DisplayName { get; set; }
|
||||
|
||||
[MaxLength(1000)]
|
||||
public string? AccessToken { get; set; }
|
||||
|
||||
[MaxLength(1000)]
|
||||
public string? RefreshToken { get; set; }
|
||||
|
||||
public Instant? ExpiresAt { get; set; }
|
||||
|
||||
[Column(TypeName = "jsonb")]
|
||||
public JsonDocument? ProfileData { get; set; }
|
||||
|
||||
public Instant ConnectedAt { get; set; } = SystemClock.Instance.GetCurrentInstant();
|
||||
public Instant? LastUsedAt { get; set; }
|
||||
|
||||
[Column(TypeName = "jsonb")]
|
||||
public JsonDocument? Metadata { get; set; }
|
||||
|
||||
public bool IsConnected => ExpiresAt == null || ExpiresAt > SystemClock.Instance.GetCurrentInstant();
|
||||
|
||||
public void UpdateTokens(string? accessToken, string? refreshToken, Instant? expiresAt)
|
||||
{
|
||||
AccessToken = accessToken;
|
||||
RefreshToken = refreshToken;
|
||||
ExpiresAt = expiresAt;
|
||||
LastUsedAt = SystemClock.Instance.GetCurrentInstant();
|
||||
}
|
||||
|
||||
public void Disconnect()
|
||||
{
|
||||
AccessToken = null;
|
||||
RefreshToken = null;
|
||||
ExpiresAt = null;
|
||||
ConnectedAt = default; // Set to default value for Instant
|
||||
}
|
||||
|
||||
public void UpdateProfileData(JsonDocument? profileData)
|
||||
{
|
||||
ProfileData = profileData;
|
||||
}
|
||||
}
|
122
DysonNetwork.Pass/Features/Auth/Models/AuthChallenge.cs
Normal file
122
DysonNetwork.Pass/Features/Auth/Models/AuthChallenge.cs
Normal file
@ -0,0 +1,122 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using DysonNetwork.Common.Models.Auth;
|
||||
using DysonNetwork.Pass.Models;
|
||||
using NetTopologySuite.Geometries;
|
||||
using NodaTime;
|
||||
|
||||
namespace DysonNetwork.Pass.Features.Auth.Models;
|
||||
|
||||
public class AuthChallenge : ModelBase
|
||||
{
|
||||
[Required]
|
||||
public Guid AccountId { get; set; }
|
||||
|
||||
[ForeignKey(nameof(AccountId))]
|
||||
[JsonIgnore]
|
||||
public virtual Account Account { get; set; } = null!;
|
||||
|
||||
[Required]
|
||||
[Column(TypeName = "varchar(50)")]
|
||||
public AuthChallengeType Type { get; set; }
|
||||
|
||||
[Required]
|
||||
[Column(TypeName = "varchar(50)")]
|
||||
public AuthChallengePlatform Platform { get; set; }
|
||||
|
||||
public Instant? ExpiredAt { get; set; }
|
||||
|
||||
[Required]
|
||||
public int StepRemain { get; set; } = 1;
|
||||
|
||||
[Required]
|
||||
public int StepTotal { get; set; } = 1;
|
||||
|
||||
[Required]
|
||||
public int FailedAttempts { get; set; } = 0;
|
||||
|
||||
[MaxLength(128)]
|
||||
public string? IpAddress { get; set; }
|
||||
|
||||
[MaxLength(512)]
|
||||
public string? UserAgent { get; set; }
|
||||
|
||||
[MaxLength(256)]
|
||||
public string? DeviceId { get; set; }
|
||||
|
||||
[MaxLength(1024)]
|
||||
public string? Nonce { get; set; }
|
||||
|
||||
[Column(TypeName = "jsonb")]
|
||||
public JsonDocument? BlacklistFactors { get; set; }
|
||||
|
||||
[Column(TypeName = "jsonb")]
|
||||
public JsonDocument? Audiences { get; set; }
|
||||
|
||||
[Column(TypeName = "jsonb")]
|
||||
public JsonDocument? Scopes { get; set; }
|
||||
|
||||
[NotMapped]
|
||||
public Point? Location { get; set; }
|
||||
|
||||
// Navigation property for AuthSession
|
||||
[JsonIgnore]
|
||||
public virtual ICollection<AuthSession> Sessions { get; set; } = new List<AuthSession>();
|
||||
|
||||
public bool IsExpired() => ExpiredAt != null && SystemClock.Instance.GetCurrentInstant() >= ExpiredAt.Value;
|
||||
|
||||
public bool CanAttempt(int maxAttempts = 5) => !IsExpired() && FailedAttempts < maxAttempts;
|
||||
|
||||
public void RecordAttempt()
|
||||
{
|
||||
if (IsExpired())
|
||||
return;
|
||||
|
||||
FailedAttempts++;
|
||||
}
|
||||
|
||||
public void UpdateStep(int step, int totalSteps)
|
||||
{
|
||||
StepRemain = step;
|
||||
StepTotal = totalSteps;
|
||||
}
|
||||
|
||||
public void UpdateExpiration(Instant? expiresAt)
|
||||
{
|
||||
ExpiredAt = expiresAt;
|
||||
}
|
||||
|
||||
public void UpdateBlacklistFactors(IEnumerable<string> factors)
|
||||
{
|
||||
BlacklistFactors = JsonSerializer.SerializeToDocument(factors);
|
||||
}
|
||||
|
||||
public void UpdateAudiences(IEnumerable<string> audiences)
|
||||
{
|
||||
Audiences = JsonSerializer.SerializeToDocument(audiences);
|
||||
}
|
||||
|
||||
public void UpdateScopes(IEnumerable<string> scopes)
|
||||
{
|
||||
Scopes = JsonSerializer.SerializeToDocument(scopes);
|
||||
}
|
||||
|
||||
public void UpdateLocation(double? latitude, double? longitude)
|
||||
{
|
||||
if (latitude.HasValue && longitude.HasValue)
|
||||
{
|
||||
Location = new Point(longitude.Value, latitude.Value) { SRID = 4326 };
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateDeviceInfo(string? ipAddress, string? userAgent, string? deviceId = null)
|
||||
{
|
||||
IpAddress = ipAddress;
|
||||
UserAgent = userAgent;
|
||||
DeviceId = deviceId;
|
||||
}
|
||||
}
|
13
DysonNetwork.Pass/Features/Auth/Models/AuthFactorType.cs
Normal file
13
DysonNetwork.Pass/Features/Auth/Models/AuthFactorType.cs
Normal file
@ -0,0 +1,13 @@
|
||||
namespace DysonNetwork.Pass.Features.Auth.Models;
|
||||
|
||||
public enum AuthFactorType
|
||||
{
|
||||
Password = 0,
|
||||
TOTP = 1,
|
||||
Email = 2,
|
||||
Phone = 3,
|
||||
SecurityKey = 4,
|
||||
RecoveryCode = 5,
|
||||
BackupCode = 6,
|
||||
OIDC = 7
|
||||
}
|
13
DysonNetwork.Pass/Features/Auth/Models/AuthResult.cs
Normal file
13
DysonNetwork.Pass/Features/Auth/Models/AuthResult.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using DysonNetwork.Common.Models;
|
||||
|
||||
namespace DysonNetwork.Pass.Features.Auth.Models;
|
||||
|
||||
public class AuthResult
|
||||
{
|
||||
public bool Success { get; set; }
|
||||
public string? AccessToken { get; set; }
|
||||
public string? RefreshToken { get; set; }
|
||||
public AuthSession? Session { get; set; }
|
||||
public string? Error { get; set; }
|
||||
public IEnumerable<string>? RequiredFactors { get; set; }
|
||||
}
|
87
DysonNetwork.Pass/Features/Auth/Models/AuthSession.cs
Normal file
87
DysonNetwork.Pass/Features/Auth/Models/AuthSession.cs
Normal file
@ -0,0 +1,87 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Text.Json;
|
||||
using DysonNetwork.Common.Models;
|
||||
using NodaTime;
|
||||
using Account = DysonNetwork.Common.Models.Account;
|
||||
|
||||
namespace DysonNetwork.Pass.Features.Auth.Models;
|
||||
|
||||
public class AuthSession : ModelBase
|
||||
{
|
||||
[Required]
|
||||
public Guid AccountId { get; set; }
|
||||
|
||||
[ForeignKey(nameof(AccountId))]
|
||||
public virtual Account Account { get; set; } = null!;
|
||||
|
||||
[Required]
|
||||
[MaxLength(500)]
|
||||
public string Label { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
public Instant LastGrantedAt { get; set; } = SystemClock.Instance.GetCurrentInstant();
|
||||
|
||||
[Required]
|
||||
public Instant ExpiredAt { get; set; }
|
||||
|
||||
[MaxLength(1000)]
|
||||
public string? AccessToken { get; set; }
|
||||
|
||||
[MaxLength(1000)]
|
||||
public string? RefreshToken { get; set; }
|
||||
|
||||
public bool IsRevoked { get; set; }
|
||||
|
||||
public string? IpAddress { get; set; }
|
||||
|
||||
[MaxLength(500)]
|
||||
public string? UserAgent { get; set; }
|
||||
|
||||
[Column(TypeName = "jsonb")]
|
||||
public Dictionary<string, object>? Metadata { get; set; }
|
||||
|
||||
public Guid? ChallengeId { get; set; }
|
||||
|
||||
[ForeignKey(nameof(ChallengeId))]
|
||||
public virtual AuthChallenge? Challenge { get; set; }
|
||||
|
||||
// Helper methods
|
||||
public bool IsExpired() => SystemClock.Instance.GetCurrentInstant() >= ExpiredAt;
|
||||
|
||||
public bool IsActive() => !IsExpired() && !IsRevoked;
|
||||
|
||||
public void UpdateLastActivity()
|
||||
{
|
||||
LastGrantedAt = SystemClock.Instance.GetCurrentInstant();
|
||||
}
|
||||
|
||||
public void SetChallenge(AuthChallenge challenge)
|
||||
{
|
||||
Challenge = challenge;
|
||||
ChallengeId = challenge.Id;
|
||||
}
|
||||
|
||||
public void ClearChallenge()
|
||||
{
|
||||
Challenge = null;
|
||||
ChallengeId = null;
|
||||
}
|
||||
|
||||
public void UpdateTokens(string accessToken, string refreshToken, Duration accessTokenLifetime)
|
||||
{
|
||||
AccessToken = accessToken;
|
||||
RefreshToken = refreshToken;
|
||||
ExpiredAt = SystemClock.Instance.GetCurrentInstant().Plus(accessTokenLifetime);
|
||||
UpdateLastActivity();
|
||||
}
|
||||
|
||||
public void Revoke()
|
||||
{
|
||||
IsRevoked = true;
|
||||
AccessToken = null;
|
||||
RefreshToken = null;
|
||||
ExpiredAt = SystemClock.Instance.GetCurrentInstant();
|
||||
}
|
||||
}
|
9
DysonNetwork.Pass/Features/Auth/Models/AuthTokens.cs
Normal file
9
DysonNetwork.Pass/Features/Auth/Models/AuthTokens.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace DysonNetwork.Pass.Features.Auth.Models;
|
||||
|
||||
public class AuthTokens
|
||||
{
|
||||
public string AccessToken { get; set; } = string.Empty;
|
||||
public string RefreshToken { get; set; } = string.Empty;
|
||||
public int ExpiresIn { get; set; }
|
||||
public string TokenType { get; set; } = "Bearer";
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
namespace DysonNetwork.Pass.Features.Auth.Models;
|
||||
|
||||
public class OidcCallbackData
|
||||
{
|
||||
public string Code { get; set; } = string.Empty;
|
||||
public string State { get; set; } = string.Empty;
|
||||
public string? Error { get; set; }
|
||||
public string? ErrorDescription { get; set; }
|
||||
}
|
70
DysonNetwork.Pass/Features/Auth/Models/OidcUserInfo.cs
Normal file
70
DysonNetwork.Pass/Features/Auth/Models/OidcUserInfo.cs
Normal file
@ -0,0 +1,70 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace DysonNetwork.Pass.Features.Auth.Models;
|
||||
|
||||
public class OidcUserInfo
|
||||
{
|
||||
[JsonPropertyName("sub")]
|
||||
public string Subject { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("name")]
|
||||
public string? Name { get; set; }
|
||||
|
||||
[JsonPropertyName("given_name")]
|
||||
public string? GivenName { get; set; }
|
||||
|
||||
[JsonPropertyName("family_name")]
|
||||
public string? FamilyName { get; set; }
|
||||
|
||||
[JsonPropertyName("middle_name")]
|
||||
public string? MiddleName { get; set; }
|
||||
|
||||
[JsonPropertyName("nickname")]
|
||||
public string? Nickname { get; set; }
|
||||
|
||||
[JsonPropertyName("preferred_username")]
|
||||
public string? PreferredUsername { get; set; }
|
||||
|
||||
[JsonPropertyName("profile")]
|
||||
public string? Profile { get; set; }
|
||||
|
||||
[JsonPropertyName("picture")]
|
||||
public string? Picture { get; set; }
|
||||
|
||||
[JsonPropertyName("website")]
|
||||
public string? Website { get; set; }
|
||||
|
||||
[JsonPropertyName("email")]
|
||||
public string? Email { get; set; }
|
||||
|
||||
[JsonPropertyName("email_verified")]
|
||||
public bool? EmailVerified { get; set; }
|
||||
|
||||
[JsonPropertyName("gender")]
|
||||
public string? Gender { get; set; }
|
||||
|
||||
[JsonPropertyName("birthdate")]
|
||||
public string? Birthdate { get; set; }
|
||||
|
||||
[JsonPropertyName("zoneinfo")]
|
||||
public string? ZoneInfo { get; set; }
|
||||
|
||||
[JsonPropertyName("locale")]
|
||||
public string? Locale { get; set; }
|
||||
|
||||
[JsonPropertyName("phone_number")]
|
||||
public string? PhoneNumber { get; set; }
|
||||
|
||||
[JsonPropertyName("phone_number_verified")]
|
||||
public bool? PhoneNumberVerified { get; set; }
|
||||
|
||||
[JsonPropertyName("address")]
|
||||
public Dictionary<string, string>? Address { get; set; }
|
||||
|
||||
[JsonPropertyName("updated_at")]
|
||||
public long? UpdatedAt { get; set; }
|
||||
|
||||
// Custom claims
|
||||
[JsonExtensionData]
|
||||
public Dictionary<string, object>? AdditionalData { get; set; }
|
||||
}
|
Reference in New Issue
Block a user