forked from QuantumNous/new-api
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathexternal_identity_claim.go
More file actions
103 lines (91 loc) · 3.77 KB
/
Copy pathexternal_identity_claim.go
File metadata and controls
103 lines (91 loc) · 3.77 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
package model
import (
"errors"
"fmt"
"strings"
"time"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
const ExternalIdentityProviderTelegram = "telegram"
var ErrExternalIdentityAlreadyClaimed = errors.New("external identity is already claimed")
// ExternalIdentityClaim is the durable ownership record for an identity issued
// by an external provider. The two unique indexes make both the provider
// subject and the user's provider slot single-owner without relying on a
// check-then-update sequence.
type ExternalIdentityClaim struct {
Id int64 `json:"id" gorm:"primaryKey"`
Provider string `json:"provider" gorm:"type:varchar(32);not null;uniqueIndex:idx_external_identity_subject,priority:1;uniqueIndex:idx_external_identity_user,priority:1"`
Subject string `json:"subject" gorm:"type:varchar(128);not null;uniqueIndex:idx_external_identity_subject,priority:2"`
UserId int `json:"user_id" gorm:"not null;index;uniqueIndex:idx_external_identity_user,priority:2"`
CreatedAt time.Time `json:"created_at"`
}
func (ExternalIdentityClaim) TableName() string {
return "external_identity_claims"
}
// ClaimExternalIdentityWithTx atomically claims a provider subject for one
// user. Repeating the exact mapping is idempotent; every competing subject or
// user is rejected. Ownership is read back instead of trusting RowsAffected,
// whose duplicate-key semantics differ between supported databases.
func ClaimExternalIdentityWithTx(tx *gorm.DB, provider, subject string, userId int) error {
provider = strings.TrimSpace(provider)
subject = strings.TrimSpace(subject)
if tx == nil || provider == "" || subject == "" || userId == 0 {
return errors.New("external identity claim is invalid")
}
claim := ExternalIdentityClaim{Provider: provider, Subject: subject, UserId: userId}
result := tx.Clauses(clause.OnConflict{DoNothing: true}).Create(&claim)
if result.Error != nil {
return result.Error
}
var subjectOwner ExternalIdentityClaim
if err := tx.Where("provider = ? AND subject = ?", provider, subject).First(&subjectOwner).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return ErrExternalIdentityAlreadyClaimed
}
return err
}
if subjectOwner.UserId != userId {
return ErrExternalIdentityAlreadyClaimed
}
var userClaim ExternalIdentityClaim
if err := tx.Where("provider = ? AND user_id = ?", provider, userId).First(&userClaim).Error; err != nil {
return err
}
if userClaim.Subject != subject {
return ErrExternalIdentityAlreadyClaimed
}
return nil
}
func ReleaseExternalIdentityWithTx(tx *gorm.DB, provider string, userId int) error {
provider = strings.TrimSpace(provider)
if tx == nil || provider == "" || userId == 0 {
return errors.New("external identity release is invalid")
}
return tx.Where("provider = ? AND user_id = ?", provider, userId).
Delete(&ExternalIdentityClaim{}).Error
}
func releaseAllExternalIdentitiesWithTx(tx *gorm.DB, userId int) error {
if tx == nil || userId == 0 {
return errors.New("external identity release is invalid")
}
return tx.Where("user_id = ?", userId).Delete(&ExternalIdentityClaim{}).Error
}
// InitializeExternalIdentityClaims imports legacy Telegram bindings after the
// claim table is migrated. Existing duplicate ownership fails migration rather
// than preserving an ambiguous login identity.
func InitializeExternalIdentityClaims() error {
var users []User
if err := DB.Unscoped().Select("id", "telegram_id").
Where("telegram_id <> ?", "").Find(&users).Error; err != nil {
return err
}
return DB.Transaction(func(tx *gorm.DB) error {
for _, user := range users {
if err := ClaimExternalIdentityWithTx(tx, ExternalIdentityProviderTelegram, user.TelegramId, user.Id); err != nil {
return fmt.Errorf("backfill Telegram identity for user %d: %w", user.Id, err)
}
}
return nil
})
}