:drunk: No idea what did AI did

This commit is contained in:
2025-07-06 19:46:59 +08:00
parent 14b79f16f4
commit 3391c08c04
40 changed files with 2484 additions and 112 deletions

View 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
}
};
}
}

View 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();
}
}

View 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;
}
}

View 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;
}
}

View 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
}

View 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; }
}

View 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();
}
}

View 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";
}

View File

@ -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; }
}

View 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; }
}