feat: 安全设置展示有效登录会话,支持剔除其它设备
按设备指纹去重并直出 IP;非当前会话可踢下线,对方需重新输入密码。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -42,6 +42,11 @@ func InitDB(dsn string) error {
|
||||
return fmt.Errorf("公告置顶列迁移失败: %w", err)
|
||||
}
|
||||
|
||||
// refresh_tokens 会话时间列:存量行不能直接 ADD NOT NULL
|
||||
if err := prepareRefreshTokenSessionColumns(db); err != nil {
|
||||
return fmt.Errorf("refresh token 会话列迁移失败: %w", err)
|
||||
}
|
||||
|
||||
if err := db.AutoMigrate(
|
||||
&User{}, &Board{}, &Post{}, &Comment{}, &CommentEditHistory{}, &RefreshToken{}, &Like{}, &Notification{}, &Checkin{},
|
||||
&Announcement{}, &SitePage{}, &SiteSetting{}, &SiteDailyStats{}, &SiteDailyVisitor{}, &Attachment{}, &UserBoard{}, &LoginLog{},
|
||||
@@ -152,6 +157,37 @@ func prepareAnnouncementPinnedColumn(db *gorm.DB) error {
|
||||
return db.Exec(`ALTER TABLE announcements ADD COLUMN pinned boolean NOT NULL DEFAULT false`).Error
|
||||
}
|
||||
|
||||
// prepareRefreshTokenSessionColumns 存量 refresh_tokens 加 logged_in_at / last_used_at:
|
||||
// 先可空加列,用 created_at 回填后再收紧 NOT NULL。
|
||||
func prepareRefreshTokenSessionColumns(db *gorm.DB) error {
|
||||
var tableCount int64
|
||||
if err := db.Raw(`SELECT count(1) FROM information_schema.tables WHERE table_name = 'refresh_tokens'`).
|
||||
Scan(&tableCount).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if tableCount == 0 {
|
||||
return nil
|
||||
}
|
||||
for _, col := range []string{"logged_in_at", "last_used_at"} {
|
||||
if err := db.Exec(fmt.Sprintf(
|
||||
`ALTER TABLE refresh_tokens ADD COLUMN IF NOT EXISTS %s timestamptz`, col,
|
||||
)).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := db.Exec(fmt.Sprintf(
|
||||
`UPDATE refresh_tokens SET %s = created_at WHERE %s IS NULL`, col, col,
|
||||
)).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := db.Exec(fmt.Sprintf(
|
||||
`ALTER TABLE refresh_tokens ALTER COLUMN %s SET NOT NULL`, col,
|
||||
)).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// prepareRefreshTokenMigration 旧版 refresh_tokens 表把明文存在 token 列,
|
||||
// 新版改为 token_hash(SHA-256,NOT NULL+唯一索引)。在 AutoMigrate 之前:
|
||||
// 1. 新增可带默认值的 token_hash 列(避免对存量行加 NOT NULL 列失败)
|
||||
|
||||
@@ -137,8 +137,13 @@ type User struct {
|
||||
type RefreshToken struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
UserID uint `gorm:"index;not null" json:"user_id"`
|
||||
FamilyID uint `gorm:"index;not null;default:0" json:"-"` // 同一登录会话跨轮转保持不变
|
||||
TokenHash string `gorm:"uniqueIndex;size:64;not null" json:"-"`
|
||||
TokenCipher string `gorm:"size:512;default:''" json:"-"`
|
||||
IP string `gorm:"size:45;not null;default:''" json:"-"`
|
||||
UserAgent string `gorm:"size:512;not null;default:''" json:"-"`
|
||||
LoggedInAt time.Time `gorm:"not null" json:"-"` // 该会话首次登录时间,轮转时原样复制
|
||||
LastUsedAt time.Time `gorm:"index;not null" json:"-"`
|
||||
ExpiresAt time.Time `gorm:"index;not null" json:"expires_at"`
|
||||
Revoked bool `gorm:"default:false;index" json:"revoked"`
|
||||
RotatedTo uint `gorm:"index;default:0" json:"-"`
|
||||
|
||||
Reference in New Issue
Block a user