Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
陈曦
sub2api
Commits
a161fcc8
Commit
a161fcc8
authored
Jan 26, 2026
by
cyhhao
Browse files
Merge branch 'main' of github.com:Wei-Shaw/sub2api
parents
65e69738
e32c5f53
Changes
119
Hide whitespace changes
Inline
Side-by-side
README.md
View file @
a161fcc8
...
@@ -18,7 +18,7 @@ English | [中文](README_CN.md)
...
@@ -18,7 +18,7 @@ English | [中文](README_CN.md)
## Demo
## Demo
Try Sub2API online:
**https://
v2.pincc.ai
/**
Try Sub2API online:
**https://
demo.sub2api.org
/**
Demo credentials (shared demo environment;
**not**
created automatically for self-hosted installs):
Demo credentials (shared demo environment;
**not**
created automatically for self-hosted installs):
...
...
backend/cmd/server/wire.go
View file @
a161fcc8
...
@@ -70,6 +70,7 @@ func provideCleanup(
...
@@ -70,6 +70,7 @@ func provideCleanup(
schedulerSnapshot
*
service
.
SchedulerSnapshotService
,
schedulerSnapshot
*
service
.
SchedulerSnapshotService
,
tokenRefresh
*
service
.
TokenRefreshService
,
tokenRefresh
*
service
.
TokenRefreshService
,
accountExpiry
*
service
.
AccountExpiryService
,
accountExpiry
*
service
.
AccountExpiryService
,
subscriptionExpiry
*
service
.
SubscriptionExpiryService
,
usageCleanup
*
service
.
UsageCleanupService
,
usageCleanup
*
service
.
UsageCleanupService
,
pricing
*
service
.
PricingService
,
pricing
*
service
.
PricingService
,
emailQueue
*
service
.
EmailQueueService
,
emailQueue
*
service
.
EmailQueueService
,
...
@@ -138,6 +139,10 @@ func provideCleanup(
...
@@ -138,6 +139,10 @@ func provideCleanup(
accountExpiry
.
Stop
()
accountExpiry
.
Stop
()
return
nil
return
nil
}},
}},
{
"SubscriptionExpiryService"
,
func
()
error
{
subscriptionExpiry
.
Stop
()
return
nil
}},
{
"PricingService"
,
func
()
error
{
{
"PricingService"
,
func
()
error
{
pricing
.
Stop
()
pricing
.
Stop
()
return
nil
return
nil
...
...
backend/cmd/server/wire_gen.go
View file @
a161fcc8
...
@@ -63,7 +63,13 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
...
@@ -63,7 +63,13 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
promoService
:=
service
.
NewPromoService
(
promoCodeRepository
,
userRepository
,
billingCacheService
,
client
,
apiKeyAuthCacheInvalidator
)
promoService
:=
service
.
NewPromoService
(
promoCodeRepository
,
userRepository
,
billingCacheService
,
client
,
apiKeyAuthCacheInvalidator
)
authService
:=
service
.
NewAuthService
(
userRepository
,
configConfig
,
settingService
,
emailService
,
turnstileService
,
emailQueueService
,
promoService
)
authService
:=
service
.
NewAuthService
(
userRepository
,
configConfig
,
settingService
,
emailService
,
turnstileService
,
emailQueueService
,
promoService
)
userService
:=
service
.
NewUserService
(
userRepository
,
apiKeyAuthCacheInvalidator
)
userService
:=
service
.
NewUserService
(
userRepository
,
apiKeyAuthCacheInvalidator
)
authHandler
:=
handler
.
NewAuthHandler
(
configConfig
,
authService
,
userService
,
settingService
,
promoService
)
secretEncryptor
,
err
:=
repository
.
NewAESEncryptor
(
configConfig
)
if
err
!=
nil
{
return
nil
,
err
}
totpCache
:=
repository
.
NewTotpCache
(
redisClient
)
totpService
:=
service
.
NewTotpService
(
userRepository
,
secretEncryptor
,
totpCache
,
settingService
,
emailService
,
emailQueueService
)
authHandler
:=
handler
.
NewAuthHandler
(
configConfig
,
authService
,
userService
,
settingService
,
promoService
,
totpService
)
userHandler
:=
handler
.
NewUserHandler
(
userService
)
userHandler
:=
handler
.
NewUserHandler
(
userService
)
apiKeyHandler
:=
handler
.
NewAPIKeyHandler
(
apiKeyService
)
apiKeyHandler
:=
handler
.
NewAPIKeyHandler
(
apiKeyService
)
usageLogRepository
:=
repository
.
NewUsageLogRepository
(
client
,
db
)
usageLogRepository
:=
repository
.
NewUsageLogRepository
(
client
,
db
)
...
@@ -165,7 +171,8 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
...
@@ -165,7 +171,8 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
gatewayHandler
:=
handler
.
NewGatewayHandler
(
gatewayService
,
geminiMessagesCompatService
,
antigravityGatewayService
,
userService
,
concurrencyService
,
billingCacheService
,
configConfig
)
gatewayHandler
:=
handler
.
NewGatewayHandler
(
gatewayService
,
geminiMessagesCompatService
,
antigravityGatewayService
,
userService
,
concurrencyService
,
billingCacheService
,
configConfig
)
openAIGatewayHandler
:=
handler
.
NewOpenAIGatewayHandler
(
openAIGatewayService
,
concurrencyService
,
billingCacheService
,
configConfig
)
openAIGatewayHandler
:=
handler
.
NewOpenAIGatewayHandler
(
openAIGatewayService
,
concurrencyService
,
billingCacheService
,
configConfig
)
handlerSettingHandler
:=
handler
.
ProvideSettingHandler
(
settingService
,
buildInfo
)
handlerSettingHandler
:=
handler
.
ProvideSettingHandler
(
settingService
,
buildInfo
)
handlers
:=
handler
.
ProvideHandlers
(
authHandler
,
userHandler
,
apiKeyHandler
,
usageHandler
,
redeemHandler
,
subscriptionHandler
,
adminHandlers
,
gatewayHandler
,
openAIGatewayHandler
,
handlerSettingHandler
)
totpHandler
:=
handler
.
NewTotpHandler
(
totpService
)
handlers
:=
handler
.
ProvideHandlers
(
authHandler
,
userHandler
,
apiKeyHandler
,
usageHandler
,
redeemHandler
,
subscriptionHandler
,
adminHandlers
,
gatewayHandler
,
openAIGatewayHandler
,
handlerSettingHandler
,
totpHandler
)
jwtAuthMiddleware
:=
middleware
.
NewJWTAuthMiddleware
(
authService
,
userService
)
jwtAuthMiddleware
:=
middleware
.
NewJWTAuthMiddleware
(
authService
,
userService
)
adminAuthMiddleware
:=
middleware
.
NewAdminAuthMiddleware
(
authService
,
userService
,
settingService
)
adminAuthMiddleware
:=
middleware
.
NewAdminAuthMiddleware
(
authService
,
userService
,
settingService
)
apiKeyAuthMiddleware
:=
middleware
.
NewAPIKeyAuthMiddleware
(
apiKeyService
,
subscriptionService
,
configConfig
)
apiKeyAuthMiddleware
:=
middleware
.
NewAPIKeyAuthMiddleware
(
apiKeyService
,
subscriptionService
,
configConfig
)
...
@@ -178,7 +185,8 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
...
@@ -178,7 +185,8 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
opsScheduledReportService
:=
service
.
ProvideOpsScheduledReportService
(
opsService
,
userService
,
emailService
,
redisClient
,
configConfig
)
opsScheduledReportService
:=
service
.
ProvideOpsScheduledReportService
(
opsService
,
userService
,
emailService
,
redisClient
,
configConfig
)
tokenRefreshService
:=
service
.
ProvideTokenRefreshService
(
accountRepository
,
oAuthService
,
openAIOAuthService
,
geminiOAuthService
,
antigravityOAuthService
,
compositeTokenCacheInvalidator
,
configConfig
)
tokenRefreshService
:=
service
.
ProvideTokenRefreshService
(
accountRepository
,
oAuthService
,
openAIOAuthService
,
geminiOAuthService
,
antigravityOAuthService
,
compositeTokenCacheInvalidator
,
configConfig
)
accountExpiryService
:=
service
.
ProvideAccountExpiryService
(
accountRepository
)
accountExpiryService
:=
service
.
ProvideAccountExpiryService
(
accountRepository
)
v
:=
provideCleanup
(
client
,
redisClient
,
opsMetricsCollector
,
opsAggregationService
,
opsAlertEvaluatorService
,
opsCleanupService
,
opsScheduledReportService
,
schedulerSnapshotService
,
tokenRefreshService
,
accountExpiryService
,
usageCleanupService
,
pricingService
,
emailQueueService
,
billingCacheService
,
oAuthService
,
openAIOAuthService
,
geminiOAuthService
,
antigravityOAuthService
)
subscriptionExpiryService
:=
service
.
ProvideSubscriptionExpiryService
(
userSubscriptionRepository
)
v
:=
provideCleanup
(
client
,
redisClient
,
opsMetricsCollector
,
opsAggregationService
,
opsAlertEvaluatorService
,
opsCleanupService
,
opsScheduledReportService
,
schedulerSnapshotService
,
tokenRefreshService
,
accountExpiryService
,
subscriptionExpiryService
,
usageCleanupService
,
pricingService
,
emailQueueService
,
billingCacheService
,
oAuthService
,
openAIOAuthService
,
geminiOAuthService
,
antigravityOAuthService
)
application
:=
&
Application
{
application
:=
&
Application
{
Server
:
httpServer
,
Server
:
httpServer
,
Cleanup
:
v
,
Cleanup
:
v
,
...
@@ -211,6 +219,7 @@ func provideCleanup(
...
@@ -211,6 +219,7 @@ func provideCleanup(
schedulerSnapshot
*
service
.
SchedulerSnapshotService
,
schedulerSnapshot
*
service
.
SchedulerSnapshotService
,
tokenRefresh
*
service
.
TokenRefreshService
,
tokenRefresh
*
service
.
TokenRefreshService
,
accountExpiry
*
service
.
AccountExpiryService
,
accountExpiry
*
service
.
AccountExpiryService
,
subscriptionExpiry
*
service
.
SubscriptionExpiryService
,
usageCleanup
*
service
.
UsageCleanupService
,
usageCleanup
*
service
.
UsageCleanupService
,
pricing
*
service
.
PricingService
,
pricing
*
service
.
PricingService
,
emailQueue
*
service
.
EmailQueueService
,
emailQueue
*
service
.
EmailQueueService
,
...
@@ -278,6 +287,10 @@ func provideCleanup(
...
@@ -278,6 +287,10 @@ func provideCleanup(
accountExpiry
.
Stop
()
accountExpiry
.
Stop
()
return
nil
return
nil
}},
}},
{
"SubscriptionExpiryService"
,
func
()
error
{
subscriptionExpiry
.
Stop
()
return
nil
}},
{
"PricingService"
,
func
()
error
{
{
"PricingService"
,
func
()
error
{
pricing
.
Stop
()
pricing
.
Stop
()
return
nil
return
nil
...
...
backend/ent/migrate/schema.go
View file @
a161fcc8
...
@@ -610,6 +610,9 @@ var (
...
@@ -610,6 +610,9 @@ var (
{
Name
:
"status"
,
Type
:
field
.
TypeString
,
Size
:
20
,
Default
:
"active"
},
{
Name
:
"status"
,
Type
:
field
.
TypeString
,
Size
:
20
,
Default
:
"active"
},
{
Name
:
"username"
,
Type
:
field
.
TypeString
,
Size
:
100
,
Default
:
""
},
{
Name
:
"username"
,
Type
:
field
.
TypeString
,
Size
:
100
,
Default
:
""
},
{
Name
:
"notes"
,
Type
:
field
.
TypeString
,
Default
:
""
,
SchemaType
:
map
[
string
]
string
{
"postgres"
:
"text"
}},
{
Name
:
"notes"
,
Type
:
field
.
TypeString
,
Default
:
""
,
SchemaType
:
map
[
string
]
string
{
"postgres"
:
"text"
}},
{
Name
:
"totp_secret_encrypted"
,
Type
:
field
.
TypeString
,
Nullable
:
true
,
SchemaType
:
map
[
string
]
string
{
"postgres"
:
"text"
}},
{
Name
:
"totp_enabled"
,
Type
:
field
.
TypeBool
,
Default
:
false
},
{
Name
:
"totp_enabled_at"
,
Type
:
field
.
TypeTime
,
Nullable
:
true
},
}
}
// UsersTable holds the schema information for the "users" table.
// UsersTable holds the schema information for the "users" table.
UsersTable
=
&
schema
.
Table
{
UsersTable
=
&
schema
.
Table
{
...
...
backend/ent/mutation.go
View file @
a161fcc8
...
@@ -14360,6 +14360,9 @@ type UserMutation struct {
...
@@ -14360,6 +14360,9 @@ type UserMutation struct {
status *string
status *string
username *string
username *string
notes *string
notes *string
totp_secret_encrypted *string
totp_enabled *bool
totp_enabled_at *time.Time
clearedFields map[string]struct{}
clearedFields map[string]struct{}
api_keys map[int64]struct{}
api_keys map[int64]struct{}
removedapi_keys map[int64]struct{}
removedapi_keys map[int64]struct{}
...
@@ -14937,6 +14940,140 @@ func (m *UserMutation) ResetNotes() {
...
@@ -14937,6 +14940,140 @@ func (m *UserMutation) ResetNotes() {
m.notes = nil
m.notes = nil
}
}
// SetTotpSecretEncrypted sets the "totp_secret_encrypted" field.
func (m *UserMutation) SetTotpSecretEncrypted(s string) {
m.totp_secret_encrypted = &s
}
// TotpSecretEncrypted returns the value of the "totp_secret_encrypted" field in the mutation.
func (m *UserMutation) TotpSecretEncrypted() (r string, exists bool) {
v := m.totp_secret_encrypted
if v == nil {
return
}
return *v, true
}
// OldTotpSecretEncrypted returns the old "totp_secret_encrypted" field's value of the User entity.
// If the User object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
func (m *UserMutation) OldTotpSecretEncrypted(ctx context.Context) (v *string, err error) {
if !m.op.Is(OpUpdateOne) {
return v, errors.New("OldTotpSecretEncrypted is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
return v, errors.New("OldTotpSecretEncrypted requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
return v, fmt.Errorf("querying old value for OldTotpSecretEncrypted: %w", err)
}
return oldValue.TotpSecretEncrypted, nil
}
// ClearTotpSecretEncrypted clears the value of the "totp_secret_encrypted" field.
func (m *UserMutation) ClearTotpSecretEncrypted() {
m.totp_secret_encrypted = nil
m.clearedFields[user.FieldTotpSecretEncrypted] = struct{}{}
}
// TotpSecretEncryptedCleared returns if the "totp_secret_encrypted" field was cleared in this mutation.
func (m *UserMutation) TotpSecretEncryptedCleared() bool {
_, ok := m.clearedFields[user.FieldTotpSecretEncrypted]
return ok
}
// ResetTotpSecretEncrypted resets all changes to the "totp_secret_encrypted" field.
func (m *UserMutation) ResetTotpSecretEncrypted() {
m.totp_secret_encrypted = nil
delete(m.clearedFields, user.FieldTotpSecretEncrypted)
}
// SetTotpEnabled sets the "totp_enabled" field.
func (m *UserMutation) SetTotpEnabled(b bool) {
m.totp_enabled = &b
}
// TotpEnabled returns the value of the "totp_enabled" field in the mutation.
func (m *UserMutation) TotpEnabled() (r bool, exists bool) {
v := m.totp_enabled
if v == nil {
return
}
return *v, true
}
// OldTotpEnabled returns the old "totp_enabled" field's value of the User entity.
// If the User object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
func (m *UserMutation) OldTotpEnabled(ctx context.Context) (v bool, err error) {
if !m.op.Is(OpUpdateOne) {
return v, errors.New("OldTotpEnabled is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
return v, errors.New("OldTotpEnabled requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
return v, fmt.Errorf("querying old value for OldTotpEnabled: %w", err)
}
return oldValue.TotpEnabled, nil
}
// ResetTotpEnabled resets all changes to the "totp_enabled" field.
func (m *UserMutation) ResetTotpEnabled() {
m.totp_enabled = nil
}
// SetTotpEnabledAt sets the "totp_enabled_at" field.
func (m *UserMutation) SetTotpEnabledAt(t time.Time) {
m.totp_enabled_at = &t
}
// TotpEnabledAt returns the value of the "totp_enabled_at" field in the mutation.
func (m *UserMutation) TotpEnabledAt() (r time.Time, exists bool) {
v := m.totp_enabled_at
if v == nil {
return
}
return *v, true
}
// OldTotpEnabledAt returns the old "totp_enabled_at" field's value of the User entity.
// If the User object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
func (m *UserMutation) OldTotpEnabledAt(ctx context.Context) (v *time.Time, err error) {
if !m.op.Is(OpUpdateOne) {
return v, errors.New("OldTotpEnabledAt is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
return v, errors.New("OldTotpEnabledAt requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
return v, fmt.Errorf("querying old value for OldTotpEnabledAt: %w", err)
}
return oldValue.TotpEnabledAt, nil
}
// ClearTotpEnabledAt clears the value of the "totp_enabled_at" field.
func (m *UserMutation) ClearTotpEnabledAt() {
m.totp_enabled_at = nil
m.clearedFields[user.FieldTotpEnabledAt] = struct{}{}
}
// TotpEnabledAtCleared returns if the "totp_enabled_at" field was cleared in this mutation.
func (m *UserMutation) TotpEnabledAtCleared() bool {
_, ok := m.clearedFields[user.FieldTotpEnabledAt]
return ok
}
// ResetTotpEnabledAt resets all changes to the "totp_enabled_at" field.
func (m *UserMutation) ResetTotpEnabledAt() {
m.totp_enabled_at = nil
delete(m.clearedFields, user.FieldTotpEnabledAt)
}
// AddAPIKeyIDs adds the "api_keys" edge to the APIKey entity by ids.
// AddAPIKeyIDs adds the "api_keys" edge to the APIKey entity by ids.
func (m *UserMutation) AddAPIKeyIDs(ids ...int64) {
func (m *UserMutation) AddAPIKeyIDs(ids ...int64) {
if m.api_keys == nil {
if m.api_keys == nil {
...
@@ -15403,7 +15540,7 @@ func (m *UserMutation) Type() string {
...
@@ -15403,7 +15540,7 @@ func (m *UserMutation) Type() string {
// order to get all numeric fields that were incremented/decremented, call
// order to get all numeric fields that were incremented/decremented, call
// AddedFields().
// AddedFields().
func (m *UserMutation) Fields() []string {
func (m *UserMutation) Fields() []string {
fields := make([]string, 0, 1
1
)
fields := make([]string, 0, 1
4
)
if m.created_at != nil {
if m.created_at != nil {
fields = append(fields, user.FieldCreatedAt)
fields = append(fields, user.FieldCreatedAt)
}
}
...
@@ -15437,6 +15574,15 @@ func (m *UserMutation) Fields() []string {
...
@@ -15437,6 +15574,15 @@ func (m *UserMutation) Fields() []string {
if m.notes != nil {
if m.notes != nil {
fields = append(fields, user.FieldNotes)
fields = append(fields, user.FieldNotes)
}
}
if m.totp_secret_encrypted != nil {
fields = append(fields, user.FieldTotpSecretEncrypted)
}
if m.totp_enabled != nil {
fields = append(fields, user.FieldTotpEnabled)
}
if m.totp_enabled_at != nil {
fields = append(fields, user.FieldTotpEnabledAt)
}
return fields
return fields
}
}
...
@@ -15467,6 +15613,12 @@ func (m *UserMutation) Field(name string) (ent.Value, bool) {
...
@@ -15467,6 +15613,12 @@ func (m *UserMutation) Field(name string) (ent.Value, bool) {
return m.Username()
return m.Username()
case user.FieldNotes:
case user.FieldNotes:
return m.Notes()
return m.Notes()
case user.FieldTotpSecretEncrypted:
return m.TotpSecretEncrypted()
case user.FieldTotpEnabled:
return m.TotpEnabled()
case user.FieldTotpEnabledAt:
return m.TotpEnabledAt()
}
}
return nil, false
return nil, false
}
}
...
@@ -15498,6 +15650,12 @@ func (m *UserMutation) OldField(ctx context.Context, name string) (ent.Value, er
...
@@ -15498,6 +15650,12 @@ func (m *UserMutation) OldField(ctx context.Context, name string) (ent.Value, er
return m.OldUsername(ctx)
return m.OldUsername(ctx)
case user.FieldNotes:
case user.FieldNotes:
return m.OldNotes(ctx)
return m.OldNotes(ctx)
case user.FieldTotpSecretEncrypted:
return m.OldTotpSecretEncrypted(ctx)
case user.FieldTotpEnabled:
return m.OldTotpEnabled(ctx)
case user.FieldTotpEnabledAt:
return m.OldTotpEnabledAt(ctx)
}
}
return nil, fmt.Errorf("unknown User field %s", name)
return nil, fmt.Errorf("unknown User field %s", name)
}
}
...
@@ -15584,6 +15742,27 @@ func (m *UserMutation) SetField(name string, value ent.Value) error {
...
@@ -15584,6 +15742,27 @@ func (m *UserMutation) SetField(name string, value ent.Value) error {
}
}
m.SetNotes(v)
m.SetNotes(v)
return nil
return nil
case user.FieldTotpSecretEncrypted:
v, ok := value.(string)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.SetTotpSecretEncrypted(v)
return nil
case user.FieldTotpEnabled:
v, ok := value.(bool)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.SetTotpEnabled(v)
return nil
case user.FieldTotpEnabledAt:
v, ok := value.(time.Time)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.SetTotpEnabledAt(v)
return nil
}
}
return fmt.Errorf("unknown User field %s", name)
return fmt.Errorf("unknown User field %s", name)
}
}
...
@@ -15644,6 +15823,12 @@ func (m *UserMutation) ClearedFields() []string {
...
@@ -15644,6 +15823,12 @@ func (m *UserMutation) ClearedFields() []string {
if m.FieldCleared(user.FieldDeletedAt) {
if m.FieldCleared(user.FieldDeletedAt) {
fields = append(fields, user.FieldDeletedAt)
fields = append(fields, user.FieldDeletedAt)
}
}
if m.FieldCleared(user.FieldTotpSecretEncrypted) {
fields = append(fields, user.FieldTotpSecretEncrypted)
}
if m.FieldCleared(user.FieldTotpEnabledAt) {
fields = append(fields, user.FieldTotpEnabledAt)
}
return fields
return fields
}
}
...
@@ -15661,6 +15846,12 @@ func (m *UserMutation) ClearField(name string) error {
...
@@ -15661,6 +15846,12 @@ func (m *UserMutation) ClearField(name string) error {
case user.FieldDeletedAt:
case user.FieldDeletedAt:
m.ClearDeletedAt()
m.ClearDeletedAt()
return nil
return nil
case user.FieldTotpSecretEncrypted:
m.ClearTotpSecretEncrypted()
return nil
case user.FieldTotpEnabledAt:
m.ClearTotpEnabledAt()
return nil
}
}
return fmt.Errorf("unknown User nullable field %s", name)
return fmt.Errorf("unknown User nullable field %s", name)
}
}
...
@@ -15702,6 +15893,15 @@ func (m *UserMutation) ResetField(name string) error {
...
@@ -15702,6 +15893,15 @@ func (m *UserMutation) ResetField(name string) error {
case user.FieldNotes:
case user.FieldNotes:
m.ResetNotes()
m.ResetNotes()
return nil
return nil
case user.FieldTotpSecretEncrypted:
m.ResetTotpSecretEncrypted()
return nil
case user.FieldTotpEnabled:
m.ResetTotpEnabled()
return nil
case user.FieldTotpEnabledAt:
m.ResetTotpEnabledAt()
return nil
}
}
return fmt.Errorf("unknown User field %s", name)
return fmt.Errorf("unknown User field %s", name)
}
}
...
...
backend/ent/runtime/runtime.go
View file @
a161fcc8
...
@@ -736,6 +736,10 @@ func init() {
...
@@ -736,6 +736,10 @@ func init() {
userDescNotes
:=
userFields
[
7
]
.
Descriptor
()
userDescNotes
:=
userFields
[
7
]
.
Descriptor
()
// user.DefaultNotes holds the default value on creation for the notes field.
// user.DefaultNotes holds the default value on creation for the notes field.
user
.
DefaultNotes
=
userDescNotes
.
Default
.
(
string
)
user
.
DefaultNotes
=
userDescNotes
.
Default
.
(
string
)
// userDescTotpEnabled is the schema descriptor for totp_enabled field.
userDescTotpEnabled
:=
userFields
[
9
]
.
Descriptor
()
// user.DefaultTotpEnabled holds the default value on creation for the totp_enabled field.
user
.
DefaultTotpEnabled
=
userDescTotpEnabled
.
Default
.
(
bool
)
userallowedgroupFields
:=
schema
.
UserAllowedGroup
{}
.
Fields
()
userallowedgroupFields
:=
schema
.
UserAllowedGroup
{}
.
Fields
()
_
=
userallowedgroupFields
_
=
userallowedgroupFields
// userallowedgroupDescCreatedAt is the schema descriptor for created_at field.
// userallowedgroupDescCreatedAt is the schema descriptor for created_at field.
...
...
backend/ent/schema/user.go
View file @
a161fcc8
...
@@ -61,6 +61,17 @@ func (User) Fields() []ent.Field {
...
@@ -61,6 +61,17 @@ func (User) Fields() []ent.Field {
field
.
String
(
"notes"
)
.
field
.
String
(
"notes"
)
.
SchemaType
(
map
[
string
]
string
{
dialect
.
Postgres
:
"text"
})
.
SchemaType
(
map
[
string
]
string
{
dialect
.
Postgres
:
"text"
})
.
Default
(
""
),
Default
(
""
),
// TOTP 双因素认证字段
field
.
String
(
"totp_secret_encrypted"
)
.
SchemaType
(
map
[
string
]
string
{
dialect
.
Postgres
:
"text"
})
.
Optional
()
.
Nillable
(),
field
.
Bool
(
"totp_enabled"
)
.
Default
(
false
),
field
.
Time
(
"totp_enabled_at"
)
.
Optional
()
.
Nillable
(),
}
}
}
}
...
...
backend/ent/user.go
View file @
a161fcc8
...
@@ -39,6 +39,12 @@ type User struct {
...
@@ -39,6 +39,12 @@ type User struct {
Username
string
`json:"username,omitempty"`
Username
string
`json:"username,omitempty"`
// Notes holds the value of the "notes" field.
// Notes holds the value of the "notes" field.
Notes
string
`json:"notes,omitempty"`
Notes
string
`json:"notes,omitempty"`
// TotpSecretEncrypted holds the value of the "totp_secret_encrypted" field.
TotpSecretEncrypted
*
string
`json:"totp_secret_encrypted,omitempty"`
// TotpEnabled holds the value of the "totp_enabled" field.
TotpEnabled
bool
`json:"totp_enabled,omitempty"`
// TotpEnabledAt holds the value of the "totp_enabled_at" field.
TotpEnabledAt
*
time
.
Time
`json:"totp_enabled_at,omitempty"`
// Edges holds the relations/edges for other nodes in the graph.
// Edges holds the relations/edges for other nodes in the graph.
// The values are being populated by the UserQuery when eager-loading is set.
// The values are being populated by the UserQuery when eager-loading is set.
Edges
UserEdges
`json:"edges"`
Edges
UserEdges
`json:"edges"`
...
@@ -156,13 +162,15 @@ func (*User) scanValues(columns []string) ([]any, error) {
...
@@ -156,13 +162,15 @@ func (*User) scanValues(columns []string) ([]any, error) {
values
:=
make
([]
any
,
len
(
columns
))
values
:=
make
([]
any
,
len
(
columns
))
for
i
:=
range
columns
{
for
i
:=
range
columns
{
switch
columns
[
i
]
{
switch
columns
[
i
]
{
case
user
.
FieldTotpEnabled
:
values
[
i
]
=
new
(
sql
.
NullBool
)
case
user
.
FieldBalance
:
case
user
.
FieldBalance
:
values
[
i
]
=
new
(
sql
.
NullFloat64
)
values
[
i
]
=
new
(
sql
.
NullFloat64
)
case
user
.
FieldID
,
user
.
FieldConcurrency
:
case
user
.
FieldID
,
user
.
FieldConcurrency
:
values
[
i
]
=
new
(
sql
.
NullInt64
)
values
[
i
]
=
new
(
sql
.
NullInt64
)
case
user
.
FieldEmail
,
user
.
FieldPasswordHash
,
user
.
FieldRole
,
user
.
FieldStatus
,
user
.
FieldUsername
,
user
.
FieldNotes
:
case
user
.
FieldEmail
,
user
.
FieldPasswordHash
,
user
.
FieldRole
,
user
.
FieldStatus
,
user
.
FieldUsername
,
user
.
FieldNotes
,
user
.
FieldTotpSecretEncrypted
:
values
[
i
]
=
new
(
sql
.
NullString
)
values
[
i
]
=
new
(
sql
.
NullString
)
case
user
.
FieldCreatedAt
,
user
.
FieldUpdatedAt
,
user
.
FieldDeletedAt
:
case
user
.
FieldCreatedAt
,
user
.
FieldUpdatedAt
,
user
.
FieldDeletedAt
,
user
.
FieldTotpEnabledAt
:
values
[
i
]
=
new
(
sql
.
NullTime
)
values
[
i
]
=
new
(
sql
.
NullTime
)
default
:
default
:
values
[
i
]
=
new
(
sql
.
UnknownType
)
values
[
i
]
=
new
(
sql
.
UnknownType
)
...
@@ -252,6 +260,26 @@ func (_m *User) assignValues(columns []string, values []any) error {
...
@@ -252,6 +260,26 @@ func (_m *User) assignValues(columns []string, values []any) error {
}
else
if
value
.
Valid
{
}
else
if
value
.
Valid
{
_m
.
Notes
=
value
.
String
_m
.
Notes
=
value
.
String
}
}
case
user
.
FieldTotpSecretEncrypted
:
if
value
,
ok
:=
values
[
i
]
.
(
*
sql
.
NullString
);
!
ok
{
return
fmt
.
Errorf
(
"unexpected type %T for field totp_secret_encrypted"
,
values
[
i
])
}
else
if
value
.
Valid
{
_m
.
TotpSecretEncrypted
=
new
(
string
)
*
_m
.
TotpSecretEncrypted
=
value
.
String
}
case
user
.
FieldTotpEnabled
:
if
value
,
ok
:=
values
[
i
]
.
(
*
sql
.
NullBool
);
!
ok
{
return
fmt
.
Errorf
(
"unexpected type %T for field totp_enabled"
,
values
[
i
])
}
else
if
value
.
Valid
{
_m
.
TotpEnabled
=
value
.
Bool
}
case
user
.
FieldTotpEnabledAt
:
if
value
,
ok
:=
values
[
i
]
.
(
*
sql
.
NullTime
);
!
ok
{
return
fmt
.
Errorf
(
"unexpected type %T for field totp_enabled_at"
,
values
[
i
])
}
else
if
value
.
Valid
{
_m
.
TotpEnabledAt
=
new
(
time
.
Time
)
*
_m
.
TotpEnabledAt
=
value
.
Time
}
default
:
default
:
_m
.
selectValues
.
Set
(
columns
[
i
],
values
[
i
])
_m
.
selectValues
.
Set
(
columns
[
i
],
values
[
i
])
}
}
...
@@ -367,6 +395,19 @@ func (_m *User) String() string {
...
@@ -367,6 +395,19 @@ func (_m *User) String() string {
builder
.
WriteString
(
", "
)
builder
.
WriteString
(
", "
)
builder
.
WriteString
(
"notes="
)
builder
.
WriteString
(
"notes="
)
builder
.
WriteString
(
_m
.
Notes
)
builder
.
WriteString
(
_m
.
Notes
)
builder
.
WriteString
(
", "
)
if
v
:=
_m
.
TotpSecretEncrypted
;
v
!=
nil
{
builder
.
WriteString
(
"totp_secret_encrypted="
)
builder
.
WriteString
(
*
v
)
}
builder
.
WriteString
(
", "
)
builder
.
WriteString
(
"totp_enabled="
)
builder
.
WriteString
(
fmt
.
Sprintf
(
"%v"
,
_m
.
TotpEnabled
))
builder
.
WriteString
(
", "
)
if
v
:=
_m
.
TotpEnabledAt
;
v
!=
nil
{
builder
.
WriteString
(
"totp_enabled_at="
)
builder
.
WriteString
(
v
.
Format
(
time
.
ANSIC
))
}
builder
.
WriteByte
(
')'
)
builder
.
WriteByte
(
')'
)
return
builder
.
String
()
return
builder
.
String
()
}
}
...
...
backend/ent/user/user.go
View file @
a161fcc8
...
@@ -37,6 +37,12 @@ const (
...
@@ -37,6 +37,12 @@ const (
FieldUsername
=
"username"
FieldUsername
=
"username"
// FieldNotes holds the string denoting the notes field in the database.
// FieldNotes holds the string denoting the notes field in the database.
FieldNotes
=
"notes"
FieldNotes
=
"notes"
// FieldTotpSecretEncrypted holds the string denoting the totp_secret_encrypted field in the database.
FieldTotpSecretEncrypted
=
"totp_secret_encrypted"
// FieldTotpEnabled holds the string denoting the totp_enabled field in the database.
FieldTotpEnabled
=
"totp_enabled"
// FieldTotpEnabledAt holds the string denoting the totp_enabled_at field in the database.
FieldTotpEnabledAt
=
"totp_enabled_at"
// EdgeAPIKeys holds the string denoting the api_keys edge name in mutations.
// EdgeAPIKeys holds the string denoting the api_keys edge name in mutations.
EdgeAPIKeys
=
"api_keys"
EdgeAPIKeys
=
"api_keys"
// EdgeRedeemCodes holds the string denoting the redeem_codes edge name in mutations.
// EdgeRedeemCodes holds the string denoting the redeem_codes edge name in mutations.
...
@@ -134,6 +140,9 @@ var Columns = []string{
...
@@ -134,6 +140,9 @@ var Columns = []string{
FieldStatus
,
FieldStatus
,
FieldUsername
,
FieldUsername
,
FieldNotes
,
FieldNotes
,
FieldTotpSecretEncrypted
,
FieldTotpEnabled
,
FieldTotpEnabledAt
,
}
}
var
(
var
(
...
@@ -188,6 +197,8 @@ var (
...
@@ -188,6 +197,8 @@ var (
UsernameValidator
func
(
string
)
error
UsernameValidator
func
(
string
)
error
// DefaultNotes holds the default value on creation for the "notes" field.
// DefaultNotes holds the default value on creation for the "notes" field.
DefaultNotes
string
DefaultNotes
string
// DefaultTotpEnabled holds the default value on creation for the "totp_enabled" field.
DefaultTotpEnabled
bool
)
)
// OrderOption defines the ordering options for the User queries.
// OrderOption defines the ordering options for the User queries.
...
@@ -253,6 +264,21 @@ func ByNotes(opts ...sql.OrderTermOption) OrderOption {
...
@@ -253,6 +264,21 @@ func ByNotes(opts ...sql.OrderTermOption) OrderOption {
return
sql
.
OrderByField
(
FieldNotes
,
opts
...
)
.
ToFunc
()
return
sql
.
OrderByField
(
FieldNotes
,
opts
...
)
.
ToFunc
()
}
}
// ByTotpSecretEncrypted orders the results by the totp_secret_encrypted field.
func
ByTotpSecretEncrypted
(
opts
...
sql
.
OrderTermOption
)
OrderOption
{
return
sql
.
OrderByField
(
FieldTotpSecretEncrypted
,
opts
...
)
.
ToFunc
()
}
// ByTotpEnabled orders the results by the totp_enabled field.
func
ByTotpEnabled
(
opts
...
sql
.
OrderTermOption
)
OrderOption
{
return
sql
.
OrderByField
(
FieldTotpEnabled
,
opts
...
)
.
ToFunc
()
}
// ByTotpEnabledAt orders the results by the totp_enabled_at field.
func
ByTotpEnabledAt
(
opts
...
sql
.
OrderTermOption
)
OrderOption
{
return
sql
.
OrderByField
(
FieldTotpEnabledAt
,
opts
...
)
.
ToFunc
()
}
// ByAPIKeysCount orders the results by api_keys count.
// ByAPIKeysCount orders the results by api_keys count.
func
ByAPIKeysCount
(
opts
...
sql
.
OrderTermOption
)
OrderOption
{
func
ByAPIKeysCount
(
opts
...
sql
.
OrderTermOption
)
OrderOption
{
return
func
(
s
*
sql
.
Selector
)
{
return
func
(
s
*
sql
.
Selector
)
{
...
...
backend/ent/user/where.go
View file @
a161fcc8
...
@@ -110,6 +110,21 @@ func Notes(v string) predicate.User {
...
@@ -110,6 +110,21 @@ func Notes(v string) predicate.User {
return
predicate
.
User
(
sql
.
FieldEQ
(
FieldNotes
,
v
))
return
predicate
.
User
(
sql
.
FieldEQ
(
FieldNotes
,
v
))
}
}
// TotpSecretEncrypted applies equality check predicate on the "totp_secret_encrypted" field. It's identical to TotpSecretEncryptedEQ.
func
TotpSecretEncrypted
(
v
string
)
predicate
.
User
{
return
predicate
.
User
(
sql
.
FieldEQ
(
FieldTotpSecretEncrypted
,
v
))
}
// TotpEnabled applies equality check predicate on the "totp_enabled" field. It's identical to TotpEnabledEQ.
func
TotpEnabled
(
v
bool
)
predicate
.
User
{
return
predicate
.
User
(
sql
.
FieldEQ
(
FieldTotpEnabled
,
v
))
}
// TotpEnabledAt applies equality check predicate on the "totp_enabled_at" field. It's identical to TotpEnabledAtEQ.
func
TotpEnabledAt
(
v
time
.
Time
)
predicate
.
User
{
return
predicate
.
User
(
sql
.
FieldEQ
(
FieldTotpEnabledAt
,
v
))
}
// CreatedAtEQ applies the EQ predicate on the "created_at" field.
// CreatedAtEQ applies the EQ predicate on the "created_at" field.
func
CreatedAtEQ
(
v
time
.
Time
)
predicate
.
User
{
func
CreatedAtEQ
(
v
time
.
Time
)
predicate
.
User
{
return
predicate
.
User
(
sql
.
FieldEQ
(
FieldCreatedAt
,
v
))
return
predicate
.
User
(
sql
.
FieldEQ
(
FieldCreatedAt
,
v
))
...
@@ -710,6 +725,141 @@ func NotesContainsFold(v string) predicate.User {
...
@@ -710,6 +725,141 @@ func NotesContainsFold(v string) predicate.User {
return
predicate
.
User
(
sql
.
FieldContainsFold
(
FieldNotes
,
v
))
return
predicate
.
User
(
sql
.
FieldContainsFold
(
FieldNotes
,
v
))
}
}
// TotpSecretEncryptedEQ applies the EQ predicate on the "totp_secret_encrypted" field.
func
TotpSecretEncryptedEQ
(
v
string
)
predicate
.
User
{
return
predicate
.
User
(
sql
.
FieldEQ
(
FieldTotpSecretEncrypted
,
v
))
}
// TotpSecretEncryptedNEQ applies the NEQ predicate on the "totp_secret_encrypted" field.
func
TotpSecretEncryptedNEQ
(
v
string
)
predicate
.
User
{
return
predicate
.
User
(
sql
.
FieldNEQ
(
FieldTotpSecretEncrypted
,
v
))
}
// TotpSecretEncryptedIn applies the In predicate on the "totp_secret_encrypted" field.
func
TotpSecretEncryptedIn
(
vs
...
string
)
predicate
.
User
{
return
predicate
.
User
(
sql
.
FieldIn
(
FieldTotpSecretEncrypted
,
vs
...
))
}
// TotpSecretEncryptedNotIn applies the NotIn predicate on the "totp_secret_encrypted" field.
func
TotpSecretEncryptedNotIn
(
vs
...
string
)
predicate
.
User
{
return
predicate
.
User
(
sql
.
FieldNotIn
(
FieldTotpSecretEncrypted
,
vs
...
))
}
// TotpSecretEncryptedGT applies the GT predicate on the "totp_secret_encrypted" field.
func
TotpSecretEncryptedGT
(
v
string
)
predicate
.
User
{
return
predicate
.
User
(
sql
.
FieldGT
(
FieldTotpSecretEncrypted
,
v
))
}
// TotpSecretEncryptedGTE applies the GTE predicate on the "totp_secret_encrypted" field.
func
TotpSecretEncryptedGTE
(
v
string
)
predicate
.
User
{
return
predicate
.
User
(
sql
.
FieldGTE
(
FieldTotpSecretEncrypted
,
v
))
}
// TotpSecretEncryptedLT applies the LT predicate on the "totp_secret_encrypted" field.
func
TotpSecretEncryptedLT
(
v
string
)
predicate
.
User
{
return
predicate
.
User
(
sql
.
FieldLT
(
FieldTotpSecretEncrypted
,
v
))
}
// TotpSecretEncryptedLTE applies the LTE predicate on the "totp_secret_encrypted" field.
func
TotpSecretEncryptedLTE
(
v
string
)
predicate
.
User
{
return
predicate
.
User
(
sql
.
FieldLTE
(
FieldTotpSecretEncrypted
,
v
))
}
// TotpSecretEncryptedContains applies the Contains predicate on the "totp_secret_encrypted" field.
func
TotpSecretEncryptedContains
(
v
string
)
predicate
.
User
{
return
predicate
.
User
(
sql
.
FieldContains
(
FieldTotpSecretEncrypted
,
v
))
}
// TotpSecretEncryptedHasPrefix applies the HasPrefix predicate on the "totp_secret_encrypted" field.
func
TotpSecretEncryptedHasPrefix
(
v
string
)
predicate
.
User
{
return
predicate
.
User
(
sql
.
FieldHasPrefix
(
FieldTotpSecretEncrypted
,
v
))
}
// TotpSecretEncryptedHasSuffix applies the HasSuffix predicate on the "totp_secret_encrypted" field.
func
TotpSecretEncryptedHasSuffix
(
v
string
)
predicate
.
User
{
return
predicate
.
User
(
sql
.
FieldHasSuffix
(
FieldTotpSecretEncrypted
,
v
))
}
// TotpSecretEncryptedIsNil applies the IsNil predicate on the "totp_secret_encrypted" field.
func
TotpSecretEncryptedIsNil
()
predicate
.
User
{
return
predicate
.
User
(
sql
.
FieldIsNull
(
FieldTotpSecretEncrypted
))
}
// TotpSecretEncryptedNotNil applies the NotNil predicate on the "totp_secret_encrypted" field.
func
TotpSecretEncryptedNotNil
()
predicate
.
User
{
return
predicate
.
User
(
sql
.
FieldNotNull
(
FieldTotpSecretEncrypted
))
}
// TotpSecretEncryptedEqualFold applies the EqualFold predicate on the "totp_secret_encrypted" field.
func
TotpSecretEncryptedEqualFold
(
v
string
)
predicate
.
User
{
return
predicate
.
User
(
sql
.
FieldEqualFold
(
FieldTotpSecretEncrypted
,
v
))
}
// TotpSecretEncryptedContainsFold applies the ContainsFold predicate on the "totp_secret_encrypted" field.
func
TotpSecretEncryptedContainsFold
(
v
string
)
predicate
.
User
{
return
predicate
.
User
(
sql
.
FieldContainsFold
(
FieldTotpSecretEncrypted
,
v
))
}
// TotpEnabledEQ applies the EQ predicate on the "totp_enabled" field.
func
TotpEnabledEQ
(
v
bool
)
predicate
.
User
{
return
predicate
.
User
(
sql
.
FieldEQ
(
FieldTotpEnabled
,
v
))
}
// TotpEnabledNEQ applies the NEQ predicate on the "totp_enabled" field.
func
TotpEnabledNEQ
(
v
bool
)
predicate
.
User
{
return
predicate
.
User
(
sql
.
FieldNEQ
(
FieldTotpEnabled
,
v
))
}
// TotpEnabledAtEQ applies the EQ predicate on the "totp_enabled_at" field.
func
TotpEnabledAtEQ
(
v
time
.
Time
)
predicate
.
User
{
return
predicate
.
User
(
sql
.
FieldEQ
(
FieldTotpEnabledAt
,
v
))
}
// TotpEnabledAtNEQ applies the NEQ predicate on the "totp_enabled_at" field.
func
TotpEnabledAtNEQ
(
v
time
.
Time
)
predicate
.
User
{
return
predicate
.
User
(
sql
.
FieldNEQ
(
FieldTotpEnabledAt
,
v
))
}
// TotpEnabledAtIn applies the In predicate on the "totp_enabled_at" field.
func
TotpEnabledAtIn
(
vs
...
time
.
Time
)
predicate
.
User
{
return
predicate
.
User
(
sql
.
FieldIn
(
FieldTotpEnabledAt
,
vs
...
))
}
// TotpEnabledAtNotIn applies the NotIn predicate on the "totp_enabled_at" field.
func
TotpEnabledAtNotIn
(
vs
...
time
.
Time
)
predicate
.
User
{
return
predicate
.
User
(
sql
.
FieldNotIn
(
FieldTotpEnabledAt
,
vs
...
))
}
// TotpEnabledAtGT applies the GT predicate on the "totp_enabled_at" field.
func
TotpEnabledAtGT
(
v
time
.
Time
)
predicate
.
User
{
return
predicate
.
User
(
sql
.
FieldGT
(
FieldTotpEnabledAt
,
v
))
}
// TotpEnabledAtGTE applies the GTE predicate on the "totp_enabled_at" field.
func
TotpEnabledAtGTE
(
v
time
.
Time
)
predicate
.
User
{
return
predicate
.
User
(
sql
.
FieldGTE
(
FieldTotpEnabledAt
,
v
))
}
// TotpEnabledAtLT applies the LT predicate on the "totp_enabled_at" field.
func
TotpEnabledAtLT
(
v
time
.
Time
)
predicate
.
User
{
return
predicate
.
User
(
sql
.
FieldLT
(
FieldTotpEnabledAt
,
v
))
}
// TotpEnabledAtLTE applies the LTE predicate on the "totp_enabled_at" field.
func
TotpEnabledAtLTE
(
v
time
.
Time
)
predicate
.
User
{
return
predicate
.
User
(
sql
.
FieldLTE
(
FieldTotpEnabledAt
,
v
))
}
// TotpEnabledAtIsNil applies the IsNil predicate on the "totp_enabled_at" field.
func
TotpEnabledAtIsNil
()
predicate
.
User
{
return
predicate
.
User
(
sql
.
FieldIsNull
(
FieldTotpEnabledAt
))
}
// TotpEnabledAtNotNil applies the NotNil predicate on the "totp_enabled_at" field.
func
TotpEnabledAtNotNil
()
predicate
.
User
{
return
predicate
.
User
(
sql
.
FieldNotNull
(
FieldTotpEnabledAt
))
}
// HasAPIKeys applies the HasEdge predicate on the "api_keys" edge.
// HasAPIKeys applies the HasEdge predicate on the "api_keys" edge.
func
HasAPIKeys
()
predicate
.
User
{
func
HasAPIKeys
()
predicate
.
User
{
return
predicate
.
User
(
func
(
s
*
sql
.
Selector
)
{
return
predicate
.
User
(
func
(
s
*
sql
.
Selector
)
{
...
...
backend/ent/user_create.go
View file @
a161fcc8
...
@@ -167,6 +167,48 @@ func (_c *UserCreate) SetNillableNotes(v *string) *UserCreate {
...
@@ -167,6 +167,48 @@ func (_c *UserCreate) SetNillableNotes(v *string) *UserCreate {
return
_c
return
_c
}
}
// SetTotpSecretEncrypted sets the "totp_secret_encrypted" field.
func
(
_c
*
UserCreate
)
SetTotpSecretEncrypted
(
v
string
)
*
UserCreate
{
_c
.
mutation
.
SetTotpSecretEncrypted
(
v
)
return
_c
}
// SetNillableTotpSecretEncrypted sets the "totp_secret_encrypted" field if the given value is not nil.
func
(
_c
*
UserCreate
)
SetNillableTotpSecretEncrypted
(
v
*
string
)
*
UserCreate
{
if
v
!=
nil
{
_c
.
SetTotpSecretEncrypted
(
*
v
)
}
return
_c
}
// SetTotpEnabled sets the "totp_enabled" field.
func
(
_c
*
UserCreate
)
SetTotpEnabled
(
v
bool
)
*
UserCreate
{
_c
.
mutation
.
SetTotpEnabled
(
v
)
return
_c
}
// SetNillableTotpEnabled sets the "totp_enabled" field if the given value is not nil.
func
(
_c
*
UserCreate
)
SetNillableTotpEnabled
(
v
*
bool
)
*
UserCreate
{
if
v
!=
nil
{
_c
.
SetTotpEnabled
(
*
v
)
}
return
_c
}
// SetTotpEnabledAt sets the "totp_enabled_at" field.
func
(
_c
*
UserCreate
)
SetTotpEnabledAt
(
v
time
.
Time
)
*
UserCreate
{
_c
.
mutation
.
SetTotpEnabledAt
(
v
)
return
_c
}
// SetNillableTotpEnabledAt sets the "totp_enabled_at" field if the given value is not nil.
func
(
_c
*
UserCreate
)
SetNillableTotpEnabledAt
(
v
*
time
.
Time
)
*
UserCreate
{
if
v
!=
nil
{
_c
.
SetTotpEnabledAt
(
*
v
)
}
return
_c
}
// AddAPIKeyIDs adds the "api_keys" edge to the APIKey entity by IDs.
// AddAPIKeyIDs adds the "api_keys" edge to the APIKey entity by IDs.
func
(
_c
*
UserCreate
)
AddAPIKeyIDs
(
ids
...
int64
)
*
UserCreate
{
func
(
_c
*
UserCreate
)
AddAPIKeyIDs
(
ids
...
int64
)
*
UserCreate
{
_c
.
mutation
.
AddAPIKeyIDs
(
ids
...
)
_c
.
mutation
.
AddAPIKeyIDs
(
ids
...
)
...
@@ -362,6 +404,10 @@ func (_c *UserCreate) defaults() error {
...
@@ -362,6 +404,10 @@ func (_c *UserCreate) defaults() error {
v
:=
user
.
DefaultNotes
v
:=
user
.
DefaultNotes
_c
.
mutation
.
SetNotes
(
v
)
_c
.
mutation
.
SetNotes
(
v
)
}
}
if
_
,
ok
:=
_c
.
mutation
.
TotpEnabled
();
!
ok
{
v
:=
user
.
DefaultTotpEnabled
_c
.
mutation
.
SetTotpEnabled
(
v
)
}
return
nil
return
nil
}
}
...
@@ -422,6 +468,9 @@ func (_c *UserCreate) check() error {
...
@@ -422,6 +468,9 @@ func (_c *UserCreate) check() error {
if
_
,
ok
:=
_c
.
mutation
.
Notes
();
!
ok
{
if
_
,
ok
:=
_c
.
mutation
.
Notes
();
!
ok
{
return
&
ValidationError
{
Name
:
"notes"
,
err
:
errors
.
New
(
`ent: missing required field "User.notes"`
)}
return
&
ValidationError
{
Name
:
"notes"
,
err
:
errors
.
New
(
`ent: missing required field "User.notes"`
)}
}
}
if
_
,
ok
:=
_c
.
mutation
.
TotpEnabled
();
!
ok
{
return
&
ValidationError
{
Name
:
"totp_enabled"
,
err
:
errors
.
New
(
`ent: missing required field "User.totp_enabled"`
)}
}
return
nil
return
nil
}
}
...
@@ -493,6 +542,18 @@ func (_c *UserCreate) createSpec() (*User, *sqlgraph.CreateSpec) {
...
@@ -493,6 +542,18 @@ func (_c *UserCreate) createSpec() (*User, *sqlgraph.CreateSpec) {
_spec
.
SetField
(
user
.
FieldNotes
,
field
.
TypeString
,
value
)
_spec
.
SetField
(
user
.
FieldNotes
,
field
.
TypeString
,
value
)
_node
.
Notes
=
value
_node
.
Notes
=
value
}
}
if
value
,
ok
:=
_c
.
mutation
.
TotpSecretEncrypted
();
ok
{
_spec
.
SetField
(
user
.
FieldTotpSecretEncrypted
,
field
.
TypeString
,
value
)
_node
.
TotpSecretEncrypted
=
&
value
}
if
value
,
ok
:=
_c
.
mutation
.
TotpEnabled
();
ok
{
_spec
.
SetField
(
user
.
FieldTotpEnabled
,
field
.
TypeBool
,
value
)
_node
.
TotpEnabled
=
value
}
if
value
,
ok
:=
_c
.
mutation
.
TotpEnabledAt
();
ok
{
_spec
.
SetField
(
user
.
FieldTotpEnabledAt
,
field
.
TypeTime
,
value
)
_node
.
TotpEnabledAt
=
&
value
}
if
nodes
:=
_c
.
mutation
.
APIKeysIDs
();
len
(
nodes
)
>
0
{
if
nodes
:=
_c
.
mutation
.
APIKeysIDs
();
len
(
nodes
)
>
0
{
edge
:=
&
sqlgraph
.
EdgeSpec
{
edge
:=
&
sqlgraph
.
EdgeSpec
{
Rel
:
sqlgraph
.
O2M
,
Rel
:
sqlgraph
.
O2M
,
...
@@ -815,6 +876,54 @@ func (u *UserUpsert) UpdateNotes() *UserUpsert {
...
@@ -815,6 +876,54 @@ func (u *UserUpsert) UpdateNotes() *UserUpsert {
return
u
return
u
}
}
// SetTotpSecretEncrypted sets the "totp_secret_encrypted" field.
func
(
u
*
UserUpsert
)
SetTotpSecretEncrypted
(
v
string
)
*
UserUpsert
{
u
.
Set
(
user
.
FieldTotpSecretEncrypted
,
v
)
return
u
}
// UpdateTotpSecretEncrypted sets the "totp_secret_encrypted" field to the value that was provided on create.
func
(
u
*
UserUpsert
)
UpdateTotpSecretEncrypted
()
*
UserUpsert
{
u
.
SetExcluded
(
user
.
FieldTotpSecretEncrypted
)
return
u
}
// ClearTotpSecretEncrypted clears the value of the "totp_secret_encrypted" field.
func
(
u
*
UserUpsert
)
ClearTotpSecretEncrypted
()
*
UserUpsert
{
u
.
SetNull
(
user
.
FieldTotpSecretEncrypted
)
return
u
}
// SetTotpEnabled sets the "totp_enabled" field.
func
(
u
*
UserUpsert
)
SetTotpEnabled
(
v
bool
)
*
UserUpsert
{
u
.
Set
(
user
.
FieldTotpEnabled
,
v
)
return
u
}
// UpdateTotpEnabled sets the "totp_enabled" field to the value that was provided on create.
func
(
u
*
UserUpsert
)
UpdateTotpEnabled
()
*
UserUpsert
{
u
.
SetExcluded
(
user
.
FieldTotpEnabled
)
return
u
}
// SetTotpEnabledAt sets the "totp_enabled_at" field.
func
(
u
*
UserUpsert
)
SetTotpEnabledAt
(
v
time
.
Time
)
*
UserUpsert
{
u
.
Set
(
user
.
FieldTotpEnabledAt
,
v
)
return
u
}
// UpdateTotpEnabledAt sets the "totp_enabled_at" field to the value that was provided on create.
func
(
u
*
UserUpsert
)
UpdateTotpEnabledAt
()
*
UserUpsert
{
u
.
SetExcluded
(
user
.
FieldTotpEnabledAt
)
return
u
}
// ClearTotpEnabledAt clears the value of the "totp_enabled_at" field.
func
(
u
*
UserUpsert
)
ClearTotpEnabledAt
()
*
UserUpsert
{
u
.
SetNull
(
user
.
FieldTotpEnabledAt
)
return
u
}
// UpdateNewValues updates the mutable fields using the new values that were set on create.
// UpdateNewValues updates the mutable fields using the new values that were set on create.
// Using this option is equivalent to using:
// Using this option is equivalent to using:
//
//
...
@@ -1021,6 +1130,62 @@ func (u *UserUpsertOne) UpdateNotes() *UserUpsertOne {
...
@@ -1021,6 +1130,62 @@ func (u *UserUpsertOne) UpdateNotes() *UserUpsertOne {
})
})
}
}
// SetTotpSecretEncrypted sets the "totp_secret_encrypted" field.
func
(
u
*
UserUpsertOne
)
SetTotpSecretEncrypted
(
v
string
)
*
UserUpsertOne
{
return
u
.
Update
(
func
(
s
*
UserUpsert
)
{
s
.
SetTotpSecretEncrypted
(
v
)
})
}
// UpdateTotpSecretEncrypted sets the "totp_secret_encrypted" field to the value that was provided on create.
func
(
u
*
UserUpsertOne
)
UpdateTotpSecretEncrypted
()
*
UserUpsertOne
{
return
u
.
Update
(
func
(
s
*
UserUpsert
)
{
s
.
UpdateTotpSecretEncrypted
()
})
}
// ClearTotpSecretEncrypted clears the value of the "totp_secret_encrypted" field.
func
(
u
*
UserUpsertOne
)
ClearTotpSecretEncrypted
()
*
UserUpsertOne
{
return
u
.
Update
(
func
(
s
*
UserUpsert
)
{
s
.
ClearTotpSecretEncrypted
()
})
}
// SetTotpEnabled sets the "totp_enabled" field.
func
(
u
*
UserUpsertOne
)
SetTotpEnabled
(
v
bool
)
*
UserUpsertOne
{
return
u
.
Update
(
func
(
s
*
UserUpsert
)
{
s
.
SetTotpEnabled
(
v
)
})
}
// UpdateTotpEnabled sets the "totp_enabled" field to the value that was provided on create.
func
(
u
*
UserUpsertOne
)
UpdateTotpEnabled
()
*
UserUpsertOne
{
return
u
.
Update
(
func
(
s
*
UserUpsert
)
{
s
.
UpdateTotpEnabled
()
})
}
// SetTotpEnabledAt sets the "totp_enabled_at" field.
func
(
u
*
UserUpsertOne
)
SetTotpEnabledAt
(
v
time
.
Time
)
*
UserUpsertOne
{
return
u
.
Update
(
func
(
s
*
UserUpsert
)
{
s
.
SetTotpEnabledAt
(
v
)
})
}
// UpdateTotpEnabledAt sets the "totp_enabled_at" field to the value that was provided on create.
func
(
u
*
UserUpsertOne
)
UpdateTotpEnabledAt
()
*
UserUpsertOne
{
return
u
.
Update
(
func
(
s
*
UserUpsert
)
{
s
.
UpdateTotpEnabledAt
()
})
}
// ClearTotpEnabledAt clears the value of the "totp_enabled_at" field.
func
(
u
*
UserUpsertOne
)
ClearTotpEnabledAt
()
*
UserUpsertOne
{
return
u
.
Update
(
func
(
s
*
UserUpsert
)
{
s
.
ClearTotpEnabledAt
()
})
}
// Exec executes the query.
// Exec executes the query.
func
(
u
*
UserUpsertOne
)
Exec
(
ctx
context
.
Context
)
error
{
func
(
u
*
UserUpsertOne
)
Exec
(
ctx
context
.
Context
)
error
{
if
len
(
u
.
create
.
conflict
)
==
0
{
if
len
(
u
.
create
.
conflict
)
==
0
{
...
@@ -1393,6 +1558,62 @@ func (u *UserUpsertBulk) UpdateNotes() *UserUpsertBulk {
...
@@ -1393,6 +1558,62 @@ func (u *UserUpsertBulk) UpdateNotes() *UserUpsertBulk {
})
})
}
}
// SetTotpSecretEncrypted sets the "totp_secret_encrypted" field.
func
(
u
*
UserUpsertBulk
)
SetTotpSecretEncrypted
(
v
string
)
*
UserUpsertBulk
{
return
u
.
Update
(
func
(
s
*
UserUpsert
)
{
s
.
SetTotpSecretEncrypted
(
v
)
})
}
// UpdateTotpSecretEncrypted sets the "totp_secret_encrypted" field to the value that was provided on create.
func
(
u
*
UserUpsertBulk
)
UpdateTotpSecretEncrypted
()
*
UserUpsertBulk
{
return
u
.
Update
(
func
(
s
*
UserUpsert
)
{
s
.
UpdateTotpSecretEncrypted
()
})
}
// ClearTotpSecretEncrypted clears the value of the "totp_secret_encrypted" field.
func
(
u
*
UserUpsertBulk
)
ClearTotpSecretEncrypted
()
*
UserUpsertBulk
{
return
u
.
Update
(
func
(
s
*
UserUpsert
)
{
s
.
ClearTotpSecretEncrypted
()
})
}
// SetTotpEnabled sets the "totp_enabled" field.
func
(
u
*
UserUpsertBulk
)
SetTotpEnabled
(
v
bool
)
*
UserUpsertBulk
{
return
u
.
Update
(
func
(
s
*
UserUpsert
)
{
s
.
SetTotpEnabled
(
v
)
})
}
// UpdateTotpEnabled sets the "totp_enabled" field to the value that was provided on create.
func
(
u
*
UserUpsertBulk
)
UpdateTotpEnabled
()
*
UserUpsertBulk
{
return
u
.
Update
(
func
(
s
*
UserUpsert
)
{
s
.
UpdateTotpEnabled
()
})
}
// SetTotpEnabledAt sets the "totp_enabled_at" field.
func
(
u
*
UserUpsertBulk
)
SetTotpEnabledAt
(
v
time
.
Time
)
*
UserUpsertBulk
{
return
u
.
Update
(
func
(
s
*
UserUpsert
)
{
s
.
SetTotpEnabledAt
(
v
)
})
}
// UpdateTotpEnabledAt sets the "totp_enabled_at" field to the value that was provided on create.
func
(
u
*
UserUpsertBulk
)
UpdateTotpEnabledAt
()
*
UserUpsertBulk
{
return
u
.
Update
(
func
(
s
*
UserUpsert
)
{
s
.
UpdateTotpEnabledAt
()
})
}
// ClearTotpEnabledAt clears the value of the "totp_enabled_at" field.
func
(
u
*
UserUpsertBulk
)
ClearTotpEnabledAt
()
*
UserUpsertBulk
{
return
u
.
Update
(
func
(
s
*
UserUpsert
)
{
s
.
ClearTotpEnabledAt
()
})
}
// Exec executes the query.
// Exec executes the query.
func
(
u
*
UserUpsertBulk
)
Exec
(
ctx
context
.
Context
)
error
{
func
(
u
*
UserUpsertBulk
)
Exec
(
ctx
context
.
Context
)
error
{
if
u
.
create
.
err
!=
nil
{
if
u
.
create
.
err
!=
nil
{
...
...
backend/ent/user_update.go
View file @
a161fcc8
...
@@ -187,6 +187,60 @@ func (_u *UserUpdate) SetNillableNotes(v *string) *UserUpdate {
...
@@ -187,6 +187,60 @@ func (_u *UserUpdate) SetNillableNotes(v *string) *UserUpdate {
return
_u
return
_u
}
}
// SetTotpSecretEncrypted sets the "totp_secret_encrypted" field.
func
(
_u
*
UserUpdate
)
SetTotpSecretEncrypted
(
v
string
)
*
UserUpdate
{
_u
.
mutation
.
SetTotpSecretEncrypted
(
v
)
return
_u
}
// SetNillableTotpSecretEncrypted sets the "totp_secret_encrypted" field if the given value is not nil.
func
(
_u
*
UserUpdate
)
SetNillableTotpSecretEncrypted
(
v
*
string
)
*
UserUpdate
{
if
v
!=
nil
{
_u
.
SetTotpSecretEncrypted
(
*
v
)
}
return
_u
}
// ClearTotpSecretEncrypted clears the value of the "totp_secret_encrypted" field.
func
(
_u
*
UserUpdate
)
ClearTotpSecretEncrypted
()
*
UserUpdate
{
_u
.
mutation
.
ClearTotpSecretEncrypted
()
return
_u
}
// SetTotpEnabled sets the "totp_enabled" field.
func
(
_u
*
UserUpdate
)
SetTotpEnabled
(
v
bool
)
*
UserUpdate
{
_u
.
mutation
.
SetTotpEnabled
(
v
)
return
_u
}
// SetNillableTotpEnabled sets the "totp_enabled" field if the given value is not nil.
func
(
_u
*
UserUpdate
)
SetNillableTotpEnabled
(
v
*
bool
)
*
UserUpdate
{
if
v
!=
nil
{
_u
.
SetTotpEnabled
(
*
v
)
}
return
_u
}
// SetTotpEnabledAt sets the "totp_enabled_at" field.
func
(
_u
*
UserUpdate
)
SetTotpEnabledAt
(
v
time
.
Time
)
*
UserUpdate
{
_u
.
mutation
.
SetTotpEnabledAt
(
v
)
return
_u
}
// SetNillableTotpEnabledAt sets the "totp_enabled_at" field if the given value is not nil.
func
(
_u
*
UserUpdate
)
SetNillableTotpEnabledAt
(
v
*
time
.
Time
)
*
UserUpdate
{
if
v
!=
nil
{
_u
.
SetTotpEnabledAt
(
*
v
)
}
return
_u
}
// ClearTotpEnabledAt clears the value of the "totp_enabled_at" field.
func
(
_u
*
UserUpdate
)
ClearTotpEnabledAt
()
*
UserUpdate
{
_u
.
mutation
.
ClearTotpEnabledAt
()
return
_u
}
// AddAPIKeyIDs adds the "api_keys" edge to the APIKey entity by IDs.
// AddAPIKeyIDs adds the "api_keys" edge to the APIKey entity by IDs.
func
(
_u
*
UserUpdate
)
AddAPIKeyIDs
(
ids
...
int64
)
*
UserUpdate
{
func
(
_u
*
UserUpdate
)
AddAPIKeyIDs
(
ids
...
int64
)
*
UserUpdate
{
_u
.
mutation
.
AddAPIKeyIDs
(
ids
...
)
_u
.
mutation
.
AddAPIKeyIDs
(
ids
...
)
...
@@ -603,6 +657,21 @@ func (_u *UserUpdate) sqlSave(ctx context.Context) (_node int, err error) {
...
@@ -603,6 +657,21 @@ func (_u *UserUpdate) sqlSave(ctx context.Context) (_node int, err error) {
if
value
,
ok
:=
_u
.
mutation
.
Notes
();
ok
{
if
value
,
ok
:=
_u
.
mutation
.
Notes
();
ok
{
_spec
.
SetField
(
user
.
FieldNotes
,
field
.
TypeString
,
value
)
_spec
.
SetField
(
user
.
FieldNotes
,
field
.
TypeString
,
value
)
}
}
if
value
,
ok
:=
_u
.
mutation
.
TotpSecretEncrypted
();
ok
{
_spec
.
SetField
(
user
.
FieldTotpSecretEncrypted
,
field
.
TypeString
,
value
)
}
if
_u
.
mutation
.
TotpSecretEncryptedCleared
()
{
_spec
.
ClearField
(
user
.
FieldTotpSecretEncrypted
,
field
.
TypeString
)
}
if
value
,
ok
:=
_u
.
mutation
.
TotpEnabled
();
ok
{
_spec
.
SetField
(
user
.
FieldTotpEnabled
,
field
.
TypeBool
,
value
)
}
if
value
,
ok
:=
_u
.
mutation
.
TotpEnabledAt
();
ok
{
_spec
.
SetField
(
user
.
FieldTotpEnabledAt
,
field
.
TypeTime
,
value
)
}
if
_u
.
mutation
.
TotpEnabledAtCleared
()
{
_spec
.
ClearField
(
user
.
FieldTotpEnabledAt
,
field
.
TypeTime
)
}
if
_u
.
mutation
.
APIKeysCleared
()
{
if
_u
.
mutation
.
APIKeysCleared
()
{
edge
:=
&
sqlgraph
.
EdgeSpec
{
edge
:=
&
sqlgraph
.
EdgeSpec
{
Rel
:
sqlgraph
.
O2M
,
Rel
:
sqlgraph
.
O2M
,
...
@@ -1147,6 +1216,60 @@ func (_u *UserUpdateOne) SetNillableNotes(v *string) *UserUpdateOne {
...
@@ -1147,6 +1216,60 @@ func (_u *UserUpdateOne) SetNillableNotes(v *string) *UserUpdateOne {
return
_u
return
_u
}
}
// SetTotpSecretEncrypted sets the "totp_secret_encrypted" field.
func
(
_u
*
UserUpdateOne
)
SetTotpSecretEncrypted
(
v
string
)
*
UserUpdateOne
{
_u
.
mutation
.
SetTotpSecretEncrypted
(
v
)
return
_u
}
// SetNillableTotpSecretEncrypted sets the "totp_secret_encrypted" field if the given value is not nil.
func
(
_u
*
UserUpdateOne
)
SetNillableTotpSecretEncrypted
(
v
*
string
)
*
UserUpdateOne
{
if
v
!=
nil
{
_u
.
SetTotpSecretEncrypted
(
*
v
)
}
return
_u
}
// ClearTotpSecretEncrypted clears the value of the "totp_secret_encrypted" field.
func
(
_u
*
UserUpdateOne
)
ClearTotpSecretEncrypted
()
*
UserUpdateOne
{
_u
.
mutation
.
ClearTotpSecretEncrypted
()
return
_u
}
// SetTotpEnabled sets the "totp_enabled" field.
func
(
_u
*
UserUpdateOne
)
SetTotpEnabled
(
v
bool
)
*
UserUpdateOne
{
_u
.
mutation
.
SetTotpEnabled
(
v
)
return
_u
}
// SetNillableTotpEnabled sets the "totp_enabled" field if the given value is not nil.
func
(
_u
*
UserUpdateOne
)
SetNillableTotpEnabled
(
v
*
bool
)
*
UserUpdateOne
{
if
v
!=
nil
{
_u
.
SetTotpEnabled
(
*
v
)
}
return
_u
}
// SetTotpEnabledAt sets the "totp_enabled_at" field.
func
(
_u
*
UserUpdateOne
)
SetTotpEnabledAt
(
v
time
.
Time
)
*
UserUpdateOne
{
_u
.
mutation
.
SetTotpEnabledAt
(
v
)
return
_u
}
// SetNillableTotpEnabledAt sets the "totp_enabled_at" field if the given value is not nil.
func
(
_u
*
UserUpdateOne
)
SetNillableTotpEnabledAt
(
v
*
time
.
Time
)
*
UserUpdateOne
{
if
v
!=
nil
{
_u
.
SetTotpEnabledAt
(
*
v
)
}
return
_u
}
// ClearTotpEnabledAt clears the value of the "totp_enabled_at" field.
func
(
_u
*
UserUpdateOne
)
ClearTotpEnabledAt
()
*
UserUpdateOne
{
_u
.
mutation
.
ClearTotpEnabledAt
()
return
_u
}
// AddAPIKeyIDs adds the "api_keys" edge to the APIKey entity by IDs.
// AddAPIKeyIDs adds the "api_keys" edge to the APIKey entity by IDs.
func
(
_u
*
UserUpdateOne
)
AddAPIKeyIDs
(
ids
...
int64
)
*
UserUpdateOne
{
func
(
_u
*
UserUpdateOne
)
AddAPIKeyIDs
(
ids
...
int64
)
*
UserUpdateOne
{
_u
.
mutation
.
AddAPIKeyIDs
(
ids
...
)
_u
.
mutation
.
AddAPIKeyIDs
(
ids
...
)
...
@@ -1593,6 +1716,21 @@ func (_u *UserUpdateOne) sqlSave(ctx context.Context) (_node *User, err error) {
...
@@ -1593,6 +1716,21 @@ func (_u *UserUpdateOne) sqlSave(ctx context.Context) (_node *User, err error) {
if
value
,
ok
:=
_u
.
mutation
.
Notes
();
ok
{
if
value
,
ok
:=
_u
.
mutation
.
Notes
();
ok
{
_spec
.
SetField
(
user
.
FieldNotes
,
field
.
TypeString
,
value
)
_spec
.
SetField
(
user
.
FieldNotes
,
field
.
TypeString
,
value
)
}
}
if
value
,
ok
:=
_u
.
mutation
.
TotpSecretEncrypted
();
ok
{
_spec
.
SetField
(
user
.
FieldTotpSecretEncrypted
,
field
.
TypeString
,
value
)
}
if
_u
.
mutation
.
TotpSecretEncryptedCleared
()
{
_spec
.
ClearField
(
user
.
FieldTotpSecretEncrypted
,
field
.
TypeString
)
}
if
value
,
ok
:=
_u
.
mutation
.
TotpEnabled
();
ok
{
_spec
.
SetField
(
user
.
FieldTotpEnabled
,
field
.
TypeBool
,
value
)
}
if
value
,
ok
:=
_u
.
mutation
.
TotpEnabledAt
();
ok
{
_spec
.
SetField
(
user
.
FieldTotpEnabledAt
,
field
.
TypeTime
,
value
)
}
if
_u
.
mutation
.
TotpEnabledAtCleared
()
{
_spec
.
ClearField
(
user
.
FieldTotpEnabledAt
,
field
.
TypeTime
)
}
if
_u
.
mutation
.
APIKeysCleared
()
{
if
_u
.
mutation
.
APIKeysCleared
()
{
edge
:=
&
sqlgraph
.
EdgeSpec
{
edge
:=
&
sqlgraph
.
EdgeSpec
{
Rel
:
sqlgraph
.
O2M
,
Rel
:
sqlgraph
.
O2M
,
...
...
backend/go.mod
View file @
a161fcc8
...
@@ -37,6 +37,7 @@ require (
...
@@ -37,6 +37,7 @@ require (
github.com/andybalholm/brotli v1.2.0 // indirect
github.com/andybalholm/brotli v1.2.0 // indirect
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
github.com/bmatcuk/doublestar v1.3.4 // indirect
github.com/bmatcuk/doublestar v1.3.4 // indirect
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
github.com/bytedance/sonic v1.9.1 // indirect
github.com/bytedance/sonic v1.9.1 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
...
@@ -106,6 +107,7 @@ require (
...
@@ -106,6 +107,7 @@ require (
github.com/pkg/errors v0.9.1 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/pquerna/otp v1.5.0 // indirect
github.com/quic-go/qpack v0.6.0 // indirect
github.com/quic-go/qpack v0.6.0 // indirect
github.com/quic-go/quic-go v0.57.1 // indirect
github.com/quic-go/quic-go v0.57.1 // indirect
github.com/refraction-networking/utls v1.8.1 // indirect
github.com/refraction-networking/utls v1.8.1 // indirect
...
...
backend/go.sum
View file @
a161fcc8
...
@@ -20,6 +20,8 @@ github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew
...
@@ -20,6 +20,8 @@ github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew
github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
github.com/bmatcuk/doublestar v1.3.4 h1:gPypJ5xD31uhX6Tf54sDPUOBXTqKH4c9aPY66CyQrS0=
github.com/bmatcuk/doublestar v1.3.4 h1:gPypJ5xD31uhX6Tf54sDPUOBXTqKH4c9aPY66CyQrS0=
github.com/bmatcuk/doublestar v1.3.4/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE=
github.com/bmatcuk/doublestar v1.3.4/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
...
@@ -217,6 +219,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
...
@@ -217,6 +219,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs=
github.com/pquerna/otp v1.5.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
...
...
backend/internal/config/config.go
View file @
a161fcc8
...
@@ -47,6 +47,7 @@ type Config struct {
...
@@ -47,6 +47,7 @@ type Config struct {
Redis
RedisConfig
`mapstructure:"redis"`
Redis
RedisConfig
`mapstructure:"redis"`
Ops
OpsConfig
`mapstructure:"ops"`
Ops
OpsConfig
`mapstructure:"ops"`
JWT
JWTConfig
`mapstructure:"jwt"`
JWT
JWTConfig
`mapstructure:"jwt"`
Totp
TotpConfig
`mapstructure:"totp"`
LinuxDo
LinuxDoConnectConfig
`mapstructure:"linuxdo_connect"`
LinuxDo
LinuxDoConnectConfig
`mapstructure:"linuxdo_connect"`
Default
DefaultConfig
`mapstructure:"default"`
Default
DefaultConfig
`mapstructure:"default"`
RateLimit
RateLimitConfig
`mapstructure:"rate_limit"`
RateLimit
RateLimitConfig
`mapstructure:"rate_limit"`
...
@@ -466,6 +467,16 @@ type JWTConfig struct {
...
@@ -466,6 +467,16 @@ type JWTConfig struct {
ExpireHour
int
`mapstructure:"expire_hour"`
ExpireHour
int
`mapstructure:"expire_hour"`
}
}
// TotpConfig TOTP 双因素认证配置
type
TotpConfig
struct
{
// EncryptionKey 用于加密 TOTP 密钥的 AES-256 密钥(32 字节 hex 编码)
// 如果为空,将自动生成一个随机密钥(仅适用于开发环境)
EncryptionKey
string
`mapstructure:"encryption_key"`
// EncryptionKeyConfigured 标记加密密钥是否为手动配置(非自动生成)
// 只有手动配置了密钥才允许在管理后台启用 TOTP 功能
EncryptionKeyConfigured
bool
`mapstructure:"-"`
}
type
TurnstileConfig
struct
{
type
TurnstileConfig
struct
{
Required
bool
`mapstructure:"required"`
Required
bool
`mapstructure:"required"`
}
}
...
@@ -626,6 +637,20 @@ func Load() (*Config, error) {
...
@@ -626,6 +637,20 @@ func Load() (*Config, error) {
log
.
Println
(
"Warning: JWT secret auto-generated. Consider setting a fixed secret for production."
)
log
.
Println
(
"Warning: JWT secret auto-generated. Consider setting a fixed secret for production."
)
}
}
// Auto-generate TOTP encryption key if not set (32 bytes = 64 hex chars for AES-256)
cfg
.
Totp
.
EncryptionKey
=
strings
.
TrimSpace
(
cfg
.
Totp
.
EncryptionKey
)
if
cfg
.
Totp
.
EncryptionKey
==
""
{
key
,
err
:=
generateJWTSecret
(
32
)
// Reuse the same random generation function
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"generate totp encryption key error: %w"
,
err
)
}
cfg
.
Totp
.
EncryptionKey
=
key
cfg
.
Totp
.
EncryptionKeyConfigured
=
false
log
.
Println
(
"Warning: TOTP encryption key auto-generated. Consider setting a fixed key for production."
)
}
else
{
cfg
.
Totp
.
EncryptionKeyConfigured
=
true
}
if
err
:=
cfg
.
Validate
();
err
!=
nil
{
if
err
:=
cfg
.
Validate
();
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"validate config error: %w"
,
err
)
return
nil
,
fmt
.
Errorf
(
"validate config error: %w"
,
err
)
}
}
...
@@ -756,6 +781,9 @@ func setDefaults() {
...
@@ -756,6 +781,9 @@ func setDefaults() {
viper
.
SetDefault
(
"jwt.secret"
,
""
)
viper
.
SetDefault
(
"jwt.secret"
,
""
)
viper
.
SetDefault
(
"jwt.expire_hour"
,
24
)
viper
.
SetDefault
(
"jwt.expire_hour"
,
24
)
// TOTP
viper
.
SetDefault
(
"totp.encryption_key"
,
""
)
// Default
// Default
// Admin credentials are created via the setup flow (web wizard / CLI / AUTO_SETUP).
// Admin credentials are created via the setup flow (web wizard / CLI / AUTO_SETUP).
// Do not ship fixed defaults here to avoid insecure "known credentials" in production.
// Do not ship fixed defaults here to avoid insecure "known credentials" in production.
...
...
backend/internal/handler/admin/account_handler.go
View file @
a161fcc8
...
@@ -547,9 +547,18 @@ func (h *AccountHandler) Refresh(c *gin.Context) {
...
@@ -547,9 +547,18 @@ func (h *AccountHandler) Refresh(c *gin.Context) {
}
}
}
}
// 如果 project_id 获取失败,先更新凭证,再标记账户为 error
// 特殊处理 project_id:如果新值为空但旧值非空,保留旧值
// 这确保了即使 LoadCodeAssist 失败,project_id 也不会丢失
if
newProjectID
,
_
:=
newCredentials
[
"project_id"
]
.
(
string
);
newProjectID
==
""
{
if
oldProjectID
:=
strings
.
TrimSpace
(
account
.
GetCredential
(
"project_id"
));
oldProjectID
!=
""
{
newCredentials
[
"project_id"
]
=
oldProjectID
}
}
// 如果 project_id 获取失败,更新凭证但不标记为 error
// LoadCodeAssist 失败可能是临时网络问题,给它机会在下次自动刷新时重试
if
tokenInfo
.
ProjectIDMissing
{
if
tokenInfo
.
ProjectIDMissing
{
// 先更新凭证
// 先更新凭证
(token 本身刷新成功了)
_
,
updateErr
:=
h
.
adminService
.
UpdateAccount
(
c
.
Request
.
Context
(),
accountID
,
&
service
.
UpdateAccountInput
{
_
,
updateErr
:=
h
.
adminService
.
UpdateAccount
(
c
.
Request
.
Context
(),
accountID
,
&
service
.
UpdateAccountInput
{
Credentials
:
newCredentials
,
Credentials
:
newCredentials
,
})
})
...
@@ -557,14 +566,10 @@ func (h *AccountHandler) Refresh(c *gin.Context) {
...
@@ -557,14 +566,10 @@ func (h *AccountHandler) Refresh(c *gin.Context) {
response
.
InternalError
(
c
,
"Failed to update credentials: "
+
updateErr
.
Error
())
response
.
InternalError
(
c
,
"Failed to update credentials: "
+
updateErr
.
Error
())
return
return
}
}
// 标记账户为 error
// 不标记为 error,只返回警告信息
if
setErr
:=
h
.
adminService
.
SetAccountError
(
c
.
Request
.
Context
(),
accountID
,
"missing_project_id: 账户缺少project id,可能无法使用Antigravity"
);
setErr
!=
nil
{
response
.
InternalError
(
c
,
"Failed to set account error: "
+
setErr
.
Error
())
return
}
response
.
Success
(
c
,
gin
.
H
{
response
.
Success
(
c
,
gin
.
H
{
"message"
:
"Token refreshed but project_id
is missing, account marked as error
"
,
"message"
:
"Token refreshed
successfully,
but project_id
could not be retrieved (will retry automatically)
"
,
"warning"
:
"missing_project_id"
,
"warning"
:
"missing_project_id
_temporary
"
,
})
})
return
return
}
}
...
@@ -668,6 +673,15 @@ func (h *AccountHandler) ClearError(c *gin.Context) {
...
@@ -668,6 +673,15 @@ func (h *AccountHandler) ClearError(c *gin.Context) {
return
return
}
}
// 清除错误后,同时清除 token 缓存,确保下次请求会获取最新的 token(触发刷新或从 DB 读取)
// 这解决了管理员重置账号状态后,旧的失效 token 仍在缓存中导致立即再次 401 的问题
if
h
.
tokenCacheInvalidator
!=
nil
&&
account
.
IsOAuth
()
{
if
invalidateErr
:=
h
.
tokenCacheInvalidator
.
InvalidateToken
(
c
.
Request
.
Context
(),
account
);
invalidateErr
!=
nil
{
// 缓存失效失败只记录日志,不影响主流程
_
=
c
.
Error
(
invalidateErr
)
}
}
response
.
Success
(
c
,
dto
.
AccountFromService
(
account
))
response
.
Success
(
c
,
dto
.
AccountFromService
(
account
))
}
}
...
...
backend/internal/handler/admin/setting_handler.go
View file @
a161fcc8
...
@@ -48,6 +48,9 @@ func (h *SettingHandler) GetSettings(c *gin.Context) {
...
@@ -48,6 +48,9 @@ func (h *SettingHandler) GetSettings(c *gin.Context) {
RegistrationEnabled
:
settings
.
RegistrationEnabled
,
RegistrationEnabled
:
settings
.
RegistrationEnabled
,
EmailVerifyEnabled
:
settings
.
EmailVerifyEnabled
,
EmailVerifyEnabled
:
settings
.
EmailVerifyEnabled
,
PromoCodeEnabled
:
settings
.
PromoCodeEnabled
,
PromoCodeEnabled
:
settings
.
PromoCodeEnabled
,
PasswordResetEnabled
:
settings
.
PasswordResetEnabled
,
TotpEnabled
:
settings
.
TotpEnabled
,
TotpEncryptionKeyConfigured
:
h
.
settingService
.
IsTotpEncryptionKeyConfigured
(),
SMTPHost
:
settings
.
SMTPHost
,
SMTPHost
:
settings
.
SMTPHost
,
SMTPPort
:
settings
.
SMTPPort
,
SMTPPort
:
settings
.
SMTPPort
,
SMTPUsername
:
settings
.
SMTPUsername
,
SMTPUsername
:
settings
.
SMTPUsername
,
...
@@ -89,9 +92,11 @@ func (h *SettingHandler) GetSettings(c *gin.Context) {
...
@@ -89,9 +92,11 @@ func (h *SettingHandler) GetSettings(c *gin.Context) {
// UpdateSettingsRequest 更新设置请求
// UpdateSettingsRequest 更新设置请求
type
UpdateSettingsRequest
struct
{
type
UpdateSettingsRequest
struct
{
// 注册设置
// 注册设置
RegistrationEnabled
bool
`json:"registration_enabled"`
RegistrationEnabled
bool
`json:"registration_enabled"`
EmailVerifyEnabled
bool
`json:"email_verify_enabled"`
EmailVerifyEnabled
bool
`json:"email_verify_enabled"`
PromoCodeEnabled
bool
`json:"promo_code_enabled"`
PromoCodeEnabled
bool
`json:"promo_code_enabled"`
PasswordResetEnabled
bool
`json:"password_reset_enabled"`
TotpEnabled
bool
`json:"totp_enabled"`
// TOTP 双因素认证
// 邮件服务设置
// 邮件服务设置
SMTPHost
string
`json:"smtp_host"`
SMTPHost
string
`json:"smtp_host"`
...
@@ -198,6 +203,16 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
...
@@ -198,6 +203,16 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
}
}
}
}
// TOTP 双因素认证参数验证
// 只有手动配置了加密密钥才允许启用 TOTP 功能
if
req
.
TotpEnabled
&&
!
previousSettings
.
TotpEnabled
{
// 尝试启用 TOTP,检查加密密钥是否已手动配置
if
!
h
.
settingService
.
IsTotpEncryptionKeyConfigured
()
{
response
.
BadRequest
(
c
,
"Cannot enable TOTP: TOTP_ENCRYPTION_KEY environment variable must be configured first. Generate a key with 'openssl rand -hex 32' and set it in your environment."
)
return
}
}
// LinuxDo Connect 参数验证
// LinuxDo Connect 参数验证
if
req
.
LinuxDoConnectEnabled
{
if
req
.
LinuxDoConnectEnabled
{
req
.
LinuxDoConnectClientID
=
strings
.
TrimSpace
(
req
.
LinuxDoConnectClientID
)
req
.
LinuxDoConnectClientID
=
strings
.
TrimSpace
(
req
.
LinuxDoConnectClientID
)
...
@@ -243,6 +258,8 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
...
@@ -243,6 +258,8 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
RegistrationEnabled
:
req
.
RegistrationEnabled
,
RegistrationEnabled
:
req
.
RegistrationEnabled
,
EmailVerifyEnabled
:
req
.
EmailVerifyEnabled
,
EmailVerifyEnabled
:
req
.
EmailVerifyEnabled
,
PromoCodeEnabled
:
req
.
PromoCodeEnabled
,
PromoCodeEnabled
:
req
.
PromoCodeEnabled
,
PasswordResetEnabled
:
req
.
PasswordResetEnabled
,
TotpEnabled
:
req
.
TotpEnabled
,
SMTPHost
:
req
.
SMTPHost
,
SMTPHost
:
req
.
SMTPHost
,
SMTPPort
:
req
.
SMTPPort
,
SMTPPort
:
req
.
SMTPPort
,
SMTPUsername
:
req
.
SMTPUsername
,
SMTPUsername
:
req
.
SMTPUsername
,
...
@@ -318,6 +335,9 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
...
@@ -318,6 +335,9 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
RegistrationEnabled
:
updatedSettings
.
RegistrationEnabled
,
RegistrationEnabled
:
updatedSettings
.
RegistrationEnabled
,
EmailVerifyEnabled
:
updatedSettings
.
EmailVerifyEnabled
,
EmailVerifyEnabled
:
updatedSettings
.
EmailVerifyEnabled
,
PromoCodeEnabled
:
updatedSettings
.
PromoCodeEnabled
,
PromoCodeEnabled
:
updatedSettings
.
PromoCodeEnabled
,
PasswordResetEnabled
:
updatedSettings
.
PasswordResetEnabled
,
TotpEnabled
:
updatedSettings
.
TotpEnabled
,
TotpEncryptionKeyConfigured
:
h
.
settingService
.
IsTotpEncryptionKeyConfigured
(),
SMTPHost
:
updatedSettings
.
SMTPHost
,
SMTPHost
:
updatedSettings
.
SMTPHost
,
SMTPPort
:
updatedSettings
.
SMTPPort
,
SMTPPort
:
updatedSettings
.
SMTPPort
,
SMTPUsername
:
updatedSettings
.
SMTPUsername
,
SMTPUsername
:
updatedSettings
.
SMTPUsername
,
...
@@ -384,6 +404,12 @@ func diffSettings(before *service.SystemSettings, after *service.SystemSettings,
...
@@ -384,6 +404,12 @@ func diffSettings(before *service.SystemSettings, after *service.SystemSettings,
if
before
.
EmailVerifyEnabled
!=
after
.
EmailVerifyEnabled
{
if
before
.
EmailVerifyEnabled
!=
after
.
EmailVerifyEnabled
{
changed
=
append
(
changed
,
"email_verify_enabled"
)
changed
=
append
(
changed
,
"email_verify_enabled"
)
}
}
if
before
.
PasswordResetEnabled
!=
after
.
PasswordResetEnabled
{
changed
=
append
(
changed
,
"password_reset_enabled"
)
}
if
before
.
TotpEnabled
!=
after
.
TotpEnabled
{
changed
=
append
(
changed
,
"totp_enabled"
)
}
if
before
.
SMTPHost
!=
after
.
SMTPHost
{
if
before
.
SMTPHost
!=
after
.
SMTPHost
{
changed
=
append
(
changed
,
"smtp_host"
)
changed
=
append
(
changed
,
"smtp_host"
)
}
}
...
...
backend/internal/handler/admin/subscription_handler.go
View file @
a161fcc8
...
@@ -77,7 +77,11 @@ func (h *SubscriptionHandler) List(c *gin.Context) {
...
@@ -77,7 +77,11 @@ func (h *SubscriptionHandler) List(c *gin.Context) {
}
}
status
:=
c
.
Query
(
"status"
)
status
:=
c
.
Query
(
"status"
)
subscriptions
,
pagination
,
err
:=
h
.
subscriptionService
.
List
(
c
.
Request
.
Context
(),
page
,
pageSize
,
userID
,
groupID
,
status
)
// Parse sorting parameters
sortBy
:=
c
.
DefaultQuery
(
"sort_by"
,
"created_at"
)
sortOrder
:=
c
.
DefaultQuery
(
"sort_order"
,
"desc"
)
subscriptions
,
pagination
,
err
:=
h
.
subscriptionService
.
List
(
c
.
Request
.
Context
(),
page
,
pageSize
,
userID
,
groupID
,
status
,
sortBy
,
sortOrder
)
if
err
!=
nil
{
if
err
!=
nil
{
response
.
ErrorFrom
(
c
,
err
)
response
.
ErrorFrom
(
c
,
err
)
return
return
...
...
backend/internal/handler/auth_handler.go
View file @
a161fcc8
package
handler
package
handler
import
(
import
(
"log/slog"
"github.com/Wei-Shaw/sub2api/internal/config"
"github.com/Wei-Shaw/sub2api/internal/config"
"github.com/Wei-Shaw/sub2api/internal/handler/dto"
"github.com/Wei-Shaw/sub2api/internal/handler/dto"
"github.com/Wei-Shaw/sub2api/internal/pkg/ip"
"github.com/Wei-Shaw/sub2api/internal/pkg/ip"
...
@@ -18,16 +20,18 @@ type AuthHandler struct {
...
@@ -18,16 +20,18 @@ type AuthHandler struct {
userService
*
service
.
UserService
userService
*
service
.
UserService
settingSvc
*
service
.
SettingService
settingSvc
*
service
.
SettingService
promoService
*
service
.
PromoService
promoService
*
service
.
PromoService
totpService
*
service
.
TotpService
}
}
// NewAuthHandler creates a new AuthHandler
// NewAuthHandler creates a new AuthHandler
func
NewAuthHandler
(
cfg
*
config
.
Config
,
authService
*
service
.
AuthService
,
userService
*
service
.
UserService
,
settingService
*
service
.
SettingService
,
promoService
*
service
.
PromoService
)
*
AuthHandler
{
func
NewAuthHandler
(
cfg
*
config
.
Config
,
authService
*
service
.
AuthService
,
userService
*
service
.
UserService
,
settingService
*
service
.
SettingService
,
promoService
*
service
.
PromoService
,
totpService
*
service
.
TotpService
)
*
AuthHandler
{
return
&
AuthHandler
{
return
&
AuthHandler
{
cfg
:
cfg
,
cfg
:
cfg
,
authService
:
authService
,
authService
:
authService
,
userService
:
userService
,
userService
:
userService
,
settingSvc
:
settingService
,
settingSvc
:
settingService
,
promoService
:
promoService
,
promoService
:
promoService
,
totpService
:
totpService
,
}
}
}
}
...
@@ -144,6 +148,100 @@ func (h *AuthHandler) Login(c *gin.Context) {
...
@@ -144,6 +148,100 @@ func (h *AuthHandler) Login(c *gin.Context) {
return
return
}
}
// Check if TOTP 2FA is enabled for this user
if
h
.
totpService
!=
nil
&&
h
.
settingSvc
.
IsTotpEnabled
(
c
.
Request
.
Context
())
&&
user
.
TotpEnabled
{
// Create a temporary login session for 2FA
tempToken
,
err
:=
h
.
totpService
.
CreateLoginSession
(
c
.
Request
.
Context
(),
user
.
ID
,
user
.
Email
)
if
err
!=
nil
{
response
.
InternalError
(
c
,
"Failed to create 2FA session"
)
return
}
response
.
Success
(
c
,
TotpLoginResponse
{
Requires2FA
:
true
,
TempToken
:
tempToken
,
UserEmailMasked
:
service
.
MaskEmail
(
user
.
Email
),
})
return
}
response
.
Success
(
c
,
AuthResponse
{
AccessToken
:
token
,
TokenType
:
"Bearer"
,
User
:
dto
.
UserFromService
(
user
),
})
}
// TotpLoginResponse represents the response when 2FA is required
type
TotpLoginResponse
struct
{
Requires2FA
bool
`json:"requires_2fa"`
TempToken
string
`json:"temp_token,omitempty"`
UserEmailMasked
string
`json:"user_email_masked,omitempty"`
}
// Login2FARequest represents the 2FA login request
type
Login2FARequest
struct
{
TempToken
string
`json:"temp_token" binding:"required"`
TotpCode
string
`json:"totp_code" binding:"required,len=6"`
}
// Login2FA completes the login with 2FA verification
// POST /api/v1/auth/login/2fa
func
(
h
*
AuthHandler
)
Login2FA
(
c
*
gin
.
Context
)
{
var
req
Login2FARequest
if
err
:=
c
.
ShouldBindJSON
(
&
req
);
err
!=
nil
{
response
.
BadRequest
(
c
,
"Invalid request: "
+
err
.
Error
())
return
}
slog
.
Debug
(
"login_2fa_request"
,
"temp_token_len"
,
len
(
req
.
TempToken
),
"totp_code_len"
,
len
(
req
.
TotpCode
))
// Get the login session
session
,
err
:=
h
.
totpService
.
GetLoginSession
(
c
.
Request
.
Context
(),
req
.
TempToken
)
if
err
!=
nil
||
session
==
nil
{
tokenPrefix
:=
""
if
len
(
req
.
TempToken
)
>=
8
{
tokenPrefix
=
req
.
TempToken
[
:
8
]
}
slog
.
Debug
(
"login_2fa_session_invalid"
,
"temp_token_prefix"
,
tokenPrefix
,
"error"
,
err
)
response
.
BadRequest
(
c
,
"Invalid or expired 2FA session"
)
return
}
slog
.
Debug
(
"login_2fa_session_found"
,
"user_id"
,
session
.
UserID
,
"email"
,
session
.
Email
)
// Verify the TOTP code
if
err
:=
h
.
totpService
.
VerifyCode
(
c
.
Request
.
Context
(),
session
.
UserID
,
req
.
TotpCode
);
err
!=
nil
{
slog
.
Debug
(
"login_2fa_verify_failed"
,
"user_id"
,
session
.
UserID
,
"error"
,
err
)
response
.
ErrorFrom
(
c
,
err
)
return
}
// Delete the login session
_
=
h
.
totpService
.
DeleteLoginSession
(
c
.
Request
.
Context
(),
req
.
TempToken
)
// Get the user
user
,
err
:=
h
.
userService
.
GetByID
(
c
.
Request
.
Context
(),
session
.
UserID
)
if
err
!=
nil
{
response
.
ErrorFrom
(
c
,
err
)
return
}
// Generate the JWT token
token
,
err
:=
h
.
authService
.
GenerateToken
(
user
)
if
err
!=
nil
{
response
.
InternalError
(
c
,
"Failed to generate token"
)
return
}
response
.
Success
(
c
,
AuthResponse
{
response
.
Success
(
c
,
AuthResponse
{
AccessToken
:
token
,
AccessToken
:
token
,
TokenType
:
"Bearer"
,
TokenType
:
"Bearer"
,
...
@@ -247,3 +345,85 @@ func (h *AuthHandler) ValidatePromoCode(c *gin.Context) {
...
@@ -247,3 +345,85 @@ func (h *AuthHandler) ValidatePromoCode(c *gin.Context) {
BonusAmount
:
promoCode
.
BonusAmount
,
BonusAmount
:
promoCode
.
BonusAmount
,
})
})
}
}
// ForgotPasswordRequest 忘记密码请求
type
ForgotPasswordRequest
struct
{
Email
string
`json:"email" binding:"required,email"`
TurnstileToken
string
`json:"turnstile_token"`
}
// ForgotPasswordResponse 忘记密码响应
type
ForgotPasswordResponse
struct
{
Message
string
`json:"message"`
}
// ForgotPassword 请求密码重置
// POST /api/v1/auth/forgot-password
func
(
h
*
AuthHandler
)
ForgotPassword
(
c
*
gin
.
Context
)
{
var
req
ForgotPasswordRequest
if
err
:=
c
.
ShouldBindJSON
(
&
req
);
err
!=
nil
{
response
.
BadRequest
(
c
,
"Invalid request: "
+
err
.
Error
())
return
}
// Turnstile 验证
if
err
:=
h
.
authService
.
VerifyTurnstile
(
c
.
Request
.
Context
(),
req
.
TurnstileToken
,
ip
.
GetClientIP
(
c
));
err
!=
nil
{
response
.
ErrorFrom
(
c
,
err
)
return
}
// Build frontend base URL from request
scheme
:=
"https"
if
c
.
Request
.
TLS
==
nil
{
// Check X-Forwarded-Proto header (common in reverse proxy setups)
if
proto
:=
c
.
GetHeader
(
"X-Forwarded-Proto"
);
proto
!=
""
{
scheme
=
proto
}
else
{
scheme
=
"http"
}
}
frontendBaseURL
:=
scheme
+
"://"
+
c
.
Request
.
Host
// Request password reset (async)
// Note: This returns success even if email doesn't exist (to prevent enumeration)
if
err
:=
h
.
authService
.
RequestPasswordResetAsync
(
c
.
Request
.
Context
(),
req
.
Email
,
frontendBaseURL
);
err
!=
nil
{
response
.
ErrorFrom
(
c
,
err
)
return
}
response
.
Success
(
c
,
ForgotPasswordResponse
{
Message
:
"If your email is registered, you will receive a password reset link shortly."
,
})
}
// ResetPasswordRequest 重置密码请求
type
ResetPasswordRequest
struct
{
Email
string
`json:"email" binding:"required,email"`
Token
string
`json:"token" binding:"required"`
NewPassword
string
`json:"new_password" binding:"required,min=6"`
}
// ResetPasswordResponse 重置密码响应
type
ResetPasswordResponse
struct
{
Message
string
`json:"message"`
}
// ResetPassword 重置密码
// POST /api/v1/auth/reset-password
func
(
h
*
AuthHandler
)
ResetPassword
(
c
*
gin
.
Context
)
{
var
req
ResetPasswordRequest
if
err
:=
c
.
ShouldBindJSON
(
&
req
);
err
!=
nil
{
response
.
BadRequest
(
c
,
"Invalid request: "
+
err
.
Error
())
return
}
// Reset password
if
err
:=
h
.
authService
.
ResetPassword
(
c
.
Request
.
Context
(),
req
.
Email
,
req
.
Token
,
req
.
NewPassword
);
err
!=
nil
{
response
.
ErrorFrom
(
c
,
err
)
return
}
response
.
Success
(
c
,
ResetPasswordResponse
{
Message
:
"Your password has been reset successfully. You can now log in with your new password."
,
})
}
backend/internal/handler/dto/settings.go
View file @
a161fcc8
...
@@ -2,9 +2,12 @@ package dto
...
@@ -2,9 +2,12 @@ package dto
// SystemSettings represents the admin settings API response payload.
// SystemSettings represents the admin settings API response payload.
type
SystemSettings
struct
{
type
SystemSettings
struct
{
RegistrationEnabled
bool
`json:"registration_enabled"`
RegistrationEnabled
bool
`json:"registration_enabled"`
EmailVerifyEnabled
bool
`json:"email_verify_enabled"`
EmailVerifyEnabled
bool
`json:"email_verify_enabled"`
PromoCodeEnabled
bool
`json:"promo_code_enabled"`
PromoCodeEnabled
bool
`json:"promo_code_enabled"`
PasswordResetEnabled
bool
`json:"password_reset_enabled"`
TotpEnabled
bool
`json:"totp_enabled"`
// TOTP 双因素认证
TotpEncryptionKeyConfigured
bool
`json:"totp_encryption_key_configured"`
// TOTP 加密密钥是否已配置
SMTPHost
string
`json:"smtp_host"`
SMTPHost
string
`json:"smtp_host"`
SMTPPort
int
`json:"smtp_port"`
SMTPPort
int
`json:"smtp_port"`
...
@@ -54,21 +57,23 @@ type SystemSettings struct {
...
@@ -54,21 +57,23 @@ type SystemSettings struct {
}
}
type
PublicSettings
struct
{
type
PublicSettings
struct
{
RegistrationEnabled
bool
`json:"registration_enabled"`
RegistrationEnabled
bool
`json:"registration_enabled"`
EmailVerifyEnabled
bool
`json:"email_verify_enabled"`
EmailVerifyEnabled
bool
`json:"email_verify_enabled"`
PromoCodeEnabled
bool
`json:"promo_code_enabled"`
PromoCodeEnabled
bool
`json:"promo_code_enabled"`
TurnstileEnabled
bool
`json:"turnstile_enabled"`
PasswordResetEnabled
bool
`json:"password_reset_enabled"`
TurnstileSiteKey
string
`json:"turnstile_site_key"`
TotpEnabled
bool
`json:"totp_enabled"`
// TOTP 双因素认证
SiteName
string
`json:"site_name"`
TurnstileEnabled
bool
`json:"turnstile_enabled"`
SiteLogo
string
`json:"site_logo"`
TurnstileSiteKey
string
`json:"turnstile_site_key"`
SiteSubtitle
string
`json:"site_subtitle"`
SiteName
string
`json:"site_name"`
APIBaseURL
string
`json:"api_base_url"`
SiteLogo
string
`json:"site_logo"`
ContactInfo
string
`json:"contact_info"`
SiteSubtitle
string
`json:"site_subtitle"`
DocURL
string
`json:"doc_url"`
APIBaseURL
string
`json:"api_base_url"`
HomeContent
string
`json:"home_content"`
ContactInfo
string
`json:"contact_info"`
HideCcsImportButton
bool
`json:"hide_ccs_import_button"`
DocURL
string
`json:"doc_url"`
LinuxDoOAuthEnabled
bool
`json:"linuxdo_oauth_enabled"`
HomeContent
string
`json:"home_content"`
Version
string
`json:"version"`
HideCcsImportButton
bool
`json:"hide_ccs_import_button"`
LinuxDoOAuthEnabled
bool
`json:"linuxdo_oauth_enabled"`
Version
string
`json:"version"`
}
}
// StreamTimeoutSettings 流超时处理配置 DTO
// StreamTimeoutSettings 流超时处理配置 DTO
...
...
Prev
1
2
3
4
5
6
Next
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment