mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-10-07 02:06:38 +08:00
feat: add secure passkey login for issue 536
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
package model
|
||||
|
||||
// Passkey stores a WebAuthn credential for exactly one panel user.
|
||||
type Passkey struct {
|
||||
ID string `gorm:"primaryKey;type:varchar(512)"`
|
||||
UserID int64 `gorm:"column:user_id;not null;index"`
|
||||
Name string `gorm:"type:varchar(100);not null"`
|
||||
CredentialJSON string `gorm:"column:credential_json;type:text;not null"`
|
||||
CreatedAt int64 `gorm:"column:created_at;not null"`
|
||||
LastUsedAt int64 `gorm:"column:last_used_at;not null;default:0"`
|
||||
}
|
||||
|
||||
func (Passkey) TableName() string { return "passkey" }
|
||||
@@ -288,6 +288,7 @@ func autoMigrateAll(db *gorm.DB) error {
|
||||
|
||||
models := []interface{}{
|
||||
&model.User{},
|
||||
&model.Passkey{},
|
||||
&model.UserQuota{},
|
||||
&model.Forward{},
|
||||
&model.ForwardPort{},
|
||||
|
||||
@@ -156,6 +156,9 @@ func (r *Repository) DeleteUserCascade(userID int64) error {
|
||||
return errors.New("repository not initialized")
|
||||
}
|
||||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Where("user_id = ?", userID).Delete(&model.Passkey{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
forwardIDs := tx.Model(&model.Forward{}).Select("id").Where("user_id = ?", userID)
|
||||
if err := tx.Where("forward_id IN (?)", forwardIDs).Delete(&model.ForwardPort{}).Error; err != nil {
|
||||
return err
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"go-backend/internal/store/model"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func (r *Repository) ListPasskeys(userID int64) ([]model.Passkey, error) {
|
||||
var keys []model.Passkey
|
||||
err := r.db.Where("user_id = ?", userID).Order("created_at desc").Find(&keys).Error
|
||||
return keys, err
|
||||
}
|
||||
|
||||
func (r *Repository) GetPasskey(userID int64, id string) (*model.Passkey, error) {
|
||||
var key model.Passkey
|
||||
err := r.db.Where("user_id = ? AND id = ?", userID, id).Take(&key).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
return &key, err
|
||||
}
|
||||
|
||||
func (r *Repository) CreatePasskey(key *model.Passkey) error {
|
||||
return r.db.Create(key).Error
|
||||
}
|
||||
|
||||
func (r *Repository) DeletePasskey(userID int64, id string) (bool, error) {
|
||||
result := r.db.Where("user_id = ? AND id = ?", userID, id).Delete(&model.Passkey{})
|
||||
return result.RowsAffected == 1, result.Error
|
||||
}
|
||||
|
||||
// Compare-and-swap prevents a concurrent assertion from reusing an old sign counter.
|
||||
func (r *Repository) UpdatePasskeyCredential(userID int64, id, oldJSON, newJSON string, now int64) (bool, error) {
|
||||
result := r.db.Model(&model.Passkey{}).
|
||||
Where("user_id = ? AND id = ? AND credential_json = ?", userID, id, oldJSON).
|
||||
Updates(map[string]interface{}{"credential_json": newJSON, "last_used_at": now})
|
||||
return result.RowsAffected == 1, result.Error
|
||||
}
|
||||
Reference in New Issue
Block a user