using System.ComponentModel; using System.ComponentModel.DataAnnotations; using Tiku.Application.Auth; using Tiku.Domain.Tenancy; namespace Tiku.Api.Contracts; /// /// 手机号密码登录请求。 /// public sealed class PasswordLoginDto { [Required] public AuthRealm? Realm { get; set; } /// /// 平台控制域名登录时使用的租户代码;自定义域名登录可省略。 /// [StringLength(100)] [Description("平台控制域名登录时使用的租户代码;自定义域名登录可省略。")] public string? TenantCode { get; set; } /// /// 账号标识。tenant 可使用手机号,platform 可使用邮箱或用户名。 /// [StringLength(320)] [Description("账号标识。tenant 可使用手机号,platform 可使用邮箱或用户名。")] public string? Identifier { get; set; } [StringLength(32)] [Description("兼容手机号字段;新客户端应使用 identifier。")] public string? Phone { get; set; } /// /// 用户密码。 /// [Required] [StringLength(128, MinimumLength = 10)] [Description("用户密码。")] public string Password { get; set; } = string.Empty; } /// /// 短信验证码登录请求。 /// public sealed class SmsLoginDto { [Required] public AuthRealm? Realm { get; set; } /// /// 平台控制域名登录时使用的租户代码;自定义域名登录可省略。 /// [StringLength(100)] [Description("平台控制域名登录时使用的租户代码;自定义域名登录可省略。")] public string? TenantCode { get; set; } /// /// 中国大陆手机号。 /// [Required] [StringLength(32)] [Description("中国大陆手机号。")] public string Phone { get; set; } = string.Empty; /// /// 收到的短信验证码。 /// [Required] [StringLength(12, MinimumLength = 4)] [Description("收到的短信验证码。")] public string Code { get; set; } = string.Empty; } public sealed class SendSmsCodeDto { [Required] public AuthRealm? Realm { get; set; } [StringLength(100)] public string? TenantCode { get; set; } [Required] [StringLength(32)] public string Phone { get; set; } = string.Empty; [StringLength(256)] public string? DeviceId { get; set; } } /// /// OAuth code 登录请求。 /// public sealed class OAuthCodeDto { [Required] public AuthRealm? Realm { get; set; } /// /// 平台控制域名登录时使用的租户代码;自定义域名登录可省略。 /// [StringLength(100)] [Description("平台控制域名登录时使用的租户代码;自定义域名登录可省略。")] public string? TenantCode { get; set; } /// /// OAuth 平台返回的一次性授权 code。 /// [Required] [StringLength(512)] [Description("OAuth 平台返回的一次性授权 code。")] public string Code { get; set; } = string.Empty; /// /// 客户端可提供的公开用户资料,不允许包含 token/secret。当前后端先保留模板字段,后续按业务需要逐步使用。 /// [Description("客户端可提供的公开用户资料,不允许包含 token/secret。当前后端先保留模板字段,后续按业务需要逐步使用。")] public Dictionary? Profile { get; set; } /// /// 微信用户资料语言,例如 zh_CN。当前微信小程序登录不会使用该字段。 /// [StringLength(20)] [Description("微信用户资料语言,例如 zh_CN。当前微信小程序登录不会使用该字段。")] public string? Lang { get; set; } } /// /// refresh token 请求。 /// public sealed class RefreshSessionDto { /// /// refresh token。服务端只存储哈希,明文只返回给客户端一次。 /// [Required] [StringLength(2048)] [Description("refresh token。服务端只存储哈希,明文只返回给客户端一次。")] public string RefreshToken { get; set; } = string.Empty; } /// /// 登录成功后的用户、租户成员和令牌信息。 /// public sealed class AuthenticatedUserDto { /// /// 用户 ID。 /// public Guid UserId { get; init; } /// /// 手机号。 /// public string? Phone { get; init; } /// /// 邮箱。 /// public string? Email { get; init; } /// /// 用户显示名称。 /// public string? Name { get; init; } public AuthRealm Realm { get; init; } /// /// 当前登录租户成员摘要。 /// public TenantMembershipSummary? Tenant { get; init; } /// /// access token 和 refresh token。 /// public AuthTokenPair Tokens { get; init; } = default!; public static AuthenticatedUserDto FromApplication(AuthenticatedUser user) { return new AuthenticatedUserDto { UserId = user.UserId, Phone = user.Phone, Email = user.Email, Name = user.Name, Realm = user.Realm, Tenant = user.Tenant, Tokens = user.Tokens }; } } public sealed class AuthenticationResultDto { public AuthenticationStatus Status { get; init; } public AuthenticatedUserDto? User { get; init; } public string? ChallengeToken { get; init; } public DateTimeOffset? ChallengeExpiresAt { get; init; } public static AuthenticationResultDto FromApplication(AuthenticationResult result) => new() { Status = result.Status, User = result.User is null ? null : AuthenticatedUserDto.FromApplication(result.User), ChallengeToken = result.ChallengeToken, ChallengeExpiresAt = result.ChallengeExpiresAt }; } public sealed class MfaChallengeDto { [Required] [StringLength(2048)] public string ChallengeToken { get; set; } = string.Empty; [StringLength(64)] public string? Code { get; set; } } public sealed class MfaConfirmDto { public AuthenticationResultDto Authentication { get; init; } = default!; public IReadOnlyList RecoveryCodes { get; init; } = []; } public sealed class RequiredPasswordChangeDto { [Required] [StringLength(2048)] public string ChallengeToken { get; set; } = string.Empty; [Required] [StringLength(128, MinimumLength = 10)] public string NewPassword { get; set; } = string.Empty; }