package model import "time" const ( LoginSMSPurposeLogin = "login" DeviceChallengeStatusPending = "pending" DeviceChallengeStatusAuthorized = "authorized" DeviceChallengeStatusTicketIssued = "ticket_issued" DeviceChallengeStatusConsumed = "consumed" DeviceChallengeStatusExpired = "expired" DeviceChallengeStatusRejected = "rejected" UserDeviceStatusActive = "active" UserDeviceStatusRevoked = "revoked" ) // LoginSMSCode 登录短信验证码。验证码只保存 sha256 哈希,且一次性消费。 type LoginSMSCode struct { ID uint `gorm:"primarykey" json:"id"` UserID uint `gorm:"not null;index" json:"user_id"` Phone string `gorm:"size:32;not null" json:"phone"` Purpose string `gorm:"size:32;not null;default:login" json:"purpose"` CodeHash string `gorm:"size:64;not null" json:"-"` ExpiresAt time.Time `gorm:"not null" json:"expires_at"` ConsumedAt *time.Time `json:"consumed_at,omitempty"` FailedAttemptCount int `gorm:"not null;default:0" json:"failed_attempt_count"` RequestIP string `gorm:"size:64;not null;default:''" json:"request_ip"` CreatedAt time.Time `json:"created_at"` } func (LoginSMSCode) TableName() string { return "login_sms_codes" } // UserDevice DCAuth 设备绑定。每个用户同时只能有一台 active 设备。 type UserDevice struct { ID uint `gorm:"primarykey" json:"id"` UserID uint `gorm:"not null;index" json:"user_id"` DeviceID string `gorm:"size:128;not null;index" json:"device_id"` PublicKey string `gorm:"size:128;not null" json:"public_key"` ClientVersion string `gorm:"size:64;not null;default:''" json:"client_version"` Status string `gorm:"size:32;not null;default:active" json:"status"` BoundAt time.Time `gorm:"not null" json:"bound_at"` LastUsedAt *time.Time `json:"last_used_at,omitempty"` RevokedAt *time.Time `json:"revoked_at,omitempty"` RevokedBy *uint `json:"revoked_by,omitempty"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } func (UserDevice) TableName() string { return "user_devices" } // DeviceChallenge DCAuth 登录 challenge 与一次性 ticket 的状态机。 // pending -> authorized -> ticket_issued -> consumed type DeviceChallenge struct { ID uint `gorm:"primarykey" json:"id"` ChallengeID string `gorm:"size:128;not null;uniqueIndex" json:"challenge_id"` App string `gorm:"size:64;not null" json:"app"` Purpose string `gorm:"size:32;not null;default:login" json:"purpose"` Challenge string `gorm:"size:512;not null" json:"challenge"` PollTokenHash string `gorm:"size:64;not null" json:"-"` Status string `gorm:"size:32;not null;default:pending" json:"status"` UserID *uint `json:"user_id,omitempty"` DeviceID string `gorm:"size:128;not null;default:''" json:"device_id"` PublicKey string `gorm:"size:128;not null;default:''" json:"public_key"` ClientVersion string `gorm:"size:64;not null;default:''" json:"client_version"` DeviceTicketHash string `gorm:"size:64;not null;default:''" json:"-"` ExpiresAt time.Time `gorm:"not null" json:"expires_at"` AuthorizedAt *time.Time `json:"authorized_at,omitempty"` TicketIssuedAt *time.Time `json:"ticket_issued_at,omitempty"` ConsumedAt *time.Time `json:"consumed_at,omitempty"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } func (DeviceChallenge) TableName() string { return "device_challenges" }