diff --git a/backend/cmd/server/wire_gen.go b/backend/cmd/server/wire_gen.go index 2c1ac5b0..300cda00 100644 --- a/backend/cmd/server/wire_gen.go +++ b/backend/cmd/server/wire_gen.go @@ -132,14 +132,17 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) { antigravityQuotaFetcher := service.NewAntigravityQuotaFetcher(proxyRepository) usageCache := service.NewUsageCache() identityCache := repository.NewIdentityCache(redisClient) - accountUsageService := service.NewAccountUsageService(accountRepository, usageLogRepository, claudeUsageFetcher, geminiQuotaService, antigravityQuotaFetcher, usageCache, identityCache) geminiTokenProvider := service.ProvideGeminiTokenProvider(accountRepository, geminiTokenCache, geminiOAuthService, oauthRefreshAPI) gatewayCache := repository.NewGatewayCache(redisClient) schedulerOutboxRepository := repository.NewSchedulerOutboxRepository(db) schedulerSnapshotService := service.ProvideSchedulerSnapshotService(schedulerCache, schedulerOutboxRepository, accountRepository, groupRepository, configConfig) antigravityTokenProvider := service.ProvideAntigravityTokenProvider(accountRepository, geminiTokenCache, antigravityOAuthService, oauthRefreshAPI, tempUnschedCache) antigravityGatewayService := service.NewAntigravityGatewayService(accountRepository, gatewayCache, schedulerSnapshotService, antigravityTokenProvider, rateLimitService, httpUpstream, settingService) - accountTestService := service.NewAccountTestService(accountRepository, geminiTokenProvider, antigravityGatewayService, httpUpstream, configConfig) + tlsFingerprintProfileRepository := repository.NewTLSFingerprintProfileRepository(client) + tlsFingerprintProfileCache := repository.NewTLSFingerprintProfileCache(redisClient) + tlsFingerprintProfileService := service.NewTLSFingerprintProfileService(tlsFingerprintProfileRepository, tlsFingerprintProfileCache) + accountUsageService := service.NewAccountUsageService(accountRepository, usageLogRepository, claudeUsageFetcher, geminiQuotaService, antigravityQuotaFetcher, usageCache, identityCache, tlsFingerprintProfileService) + accountTestService := service.NewAccountTestService(accountRepository, geminiTokenProvider, antigravityGatewayService, httpUpstream, configConfig, tlsFingerprintProfileService) crsSyncService := service.NewCRSSyncService(accountRepository, proxyRepository, oAuthService, openAIOAuthService, geminiOAuthService, configConfig) sessionLimitCache := repository.ProvideSessionLimitCache(redisClient, configConfig) rpmCache := repository.NewRPMCache(redisClient) @@ -171,7 +174,7 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) { deferredService := service.ProvideDeferredService(accountRepository, timingWheelService) claudeTokenProvider := service.ProvideClaudeTokenProvider(accountRepository, geminiTokenCache, oAuthService, oauthRefreshAPI) digestSessionStore := service.NewDigestSessionStore() - gatewayService := service.NewGatewayService(accountRepository, groupRepository, usageLogRepository, usageBillingRepository, userRepository, userSubscriptionRepository, userGroupRateRepository, gatewayCache, configConfig, schedulerSnapshotService, concurrencyService, billingService, rateLimitService, billingCacheService, identityService, httpUpstream, deferredService, claudeTokenProvider, sessionLimitCache, rpmCache, digestSessionStore, settingService) + gatewayService := service.NewGatewayService(accountRepository, groupRepository, usageLogRepository, usageBillingRepository, userRepository, userSubscriptionRepository, userGroupRateRepository, gatewayCache, configConfig, schedulerSnapshotService, concurrencyService, billingService, rateLimitService, billingCacheService, identityService, httpUpstream, deferredService, claudeTokenProvider, sessionLimitCache, rpmCache, digestSessionStore, settingService, tlsFingerprintProfileService) openAITokenProvider := service.ProvideOpenAITokenProvider(accountRepository, geminiTokenCache, openAIOAuthService, oauthRefreshAPI) openAIGatewayService := service.NewOpenAIGatewayService(accountRepository, usageLogRepository, usageBillingRepository, userRepository, userSubscriptionRepository, userGroupRateRepository, gatewayCache, configConfig, schedulerSnapshotService, concurrencyService, billingService, rateLimitService, billingCacheService, httpUpstream, deferredService, openAITokenProvider) geminiMessagesCompatService := service.NewGeminiMessagesCompatService(accountRepository, groupRepository, gatewayCache, schedulerSnapshotService, geminiTokenProvider, rateLimitService, httpUpstream, antigravityGatewayService, configConfig) @@ -203,12 +206,13 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) { errorPassthroughCache := repository.NewErrorPassthroughCache(redisClient) errorPassthroughService := service.NewErrorPassthroughService(errorPassthroughRepository, errorPassthroughCache) errorPassthroughHandler := admin.NewErrorPassthroughHandler(errorPassthroughService) + tlsFingerprintProfileHandler := admin.NewTLSFingerprintProfileHandler(tlsFingerprintProfileService) adminAPIKeyHandler := admin.NewAdminAPIKeyHandler(adminService) scheduledTestPlanRepository := repository.NewScheduledTestPlanRepository(db) scheduledTestResultRepository := repository.NewScheduledTestResultRepository(db) scheduledTestService := service.ProvideScheduledTestService(scheduledTestPlanRepository, scheduledTestResultRepository) scheduledTestHandler := admin.NewScheduledTestHandler(scheduledTestService) - adminHandlers := handler.ProvideAdminHandlers(dashboardHandler, adminUserHandler, groupHandler, accountHandler, adminAnnouncementHandler, dataManagementHandler, backupHandler, oAuthHandler, openAIOAuthHandler, geminiOAuthHandler, antigravityOAuthHandler, proxyHandler, adminRedeemHandler, promoHandler, settingHandler, opsHandler, systemHandler, adminSubscriptionHandler, adminUsageHandler, userAttributeHandler, errorPassthroughHandler, adminAPIKeyHandler, scheduledTestHandler) + adminHandlers := handler.ProvideAdminHandlers(dashboardHandler, adminUserHandler, groupHandler, accountHandler, adminAnnouncementHandler, dataManagementHandler, backupHandler, oAuthHandler, openAIOAuthHandler, geminiOAuthHandler, antigravityOAuthHandler, proxyHandler, adminRedeemHandler, promoHandler, settingHandler, opsHandler, systemHandler, adminSubscriptionHandler, adminUsageHandler, userAttributeHandler, errorPassthroughHandler, tlsFingerprintProfileHandler, adminAPIKeyHandler, scheduledTestHandler) usageRecordWorkerPool := service.NewUsageRecordWorkerPool(configConfig) userMsgQueueCache := repository.NewUserMsgQueueCache(redisClient) userMessageQueueService := service.ProvideUserMessageQueueService(userMsgQueueCache, rpmCache, configConfig) diff --git a/backend/ent/client.go b/backend/ent/client.go index 7ebbaa32..4129d6c5 100644 --- a/backend/ent/client.go +++ b/backend/ent/client.go @@ -29,6 +29,7 @@ import ( "github.com/Wei-Shaw/sub2api/ent/redeemcode" "github.com/Wei-Shaw/sub2api/ent/securitysecret" "github.com/Wei-Shaw/sub2api/ent/setting" + "github.com/Wei-Shaw/sub2api/ent/tlsfingerprintprofile" "github.com/Wei-Shaw/sub2api/ent/usagecleanuptask" "github.com/Wei-Shaw/sub2api/ent/usagelog" "github.com/Wei-Shaw/sub2api/ent/user" @@ -73,6 +74,8 @@ type Client struct { SecuritySecret *SecuritySecretClient // Setting is the client for interacting with the Setting builders. Setting *SettingClient + // TLSFingerprintProfile is the client for interacting with the TLSFingerprintProfile builders. + TLSFingerprintProfile *TLSFingerprintProfileClient // UsageCleanupTask is the client for interacting with the UsageCleanupTask builders. UsageCleanupTask *UsageCleanupTaskClient // UsageLog is the client for interacting with the UsageLog builders. @@ -112,6 +115,7 @@ func (c *Client) init() { c.RedeemCode = NewRedeemCodeClient(c.config) c.SecuritySecret = NewSecuritySecretClient(c.config) c.Setting = NewSettingClient(c.config) + c.TLSFingerprintProfile = NewTLSFingerprintProfileClient(c.config) c.UsageCleanupTask = NewUsageCleanupTaskClient(c.config) c.UsageLog = NewUsageLogClient(c.config) c.User = NewUserClient(c.config) @@ -225,6 +229,7 @@ func (c *Client) Tx(ctx context.Context) (*Tx, error) { RedeemCode: NewRedeemCodeClient(cfg), SecuritySecret: NewSecuritySecretClient(cfg), Setting: NewSettingClient(cfg), + TLSFingerprintProfile: NewTLSFingerprintProfileClient(cfg), UsageCleanupTask: NewUsageCleanupTaskClient(cfg), UsageLog: NewUsageLogClient(cfg), User: NewUserClient(cfg), @@ -265,6 +270,7 @@ func (c *Client) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) RedeemCode: NewRedeemCodeClient(cfg), SecuritySecret: NewSecuritySecretClient(cfg), Setting: NewSettingClient(cfg), + TLSFingerprintProfile: NewTLSFingerprintProfileClient(cfg), UsageCleanupTask: NewUsageCleanupTaskClient(cfg), UsageLog: NewUsageLogClient(cfg), User: NewUserClient(cfg), @@ -304,8 +310,9 @@ func (c *Client) Use(hooks ...Hook) { c.APIKey, c.Account, c.AccountGroup, c.Announcement, c.AnnouncementRead, c.ErrorPassthroughRule, c.Group, c.IdempotencyRecord, c.PromoCode, c.PromoCodeUsage, c.Proxy, c.RedeemCode, c.SecuritySecret, c.Setting, - c.UsageCleanupTask, c.UsageLog, c.User, c.UserAllowedGroup, - c.UserAttributeDefinition, c.UserAttributeValue, c.UserSubscription, + c.TLSFingerprintProfile, c.UsageCleanupTask, c.UsageLog, c.User, + c.UserAllowedGroup, c.UserAttributeDefinition, c.UserAttributeValue, + c.UserSubscription, } { n.Use(hooks...) } @@ -318,8 +325,9 @@ func (c *Client) Intercept(interceptors ...Interceptor) { c.APIKey, c.Account, c.AccountGroup, c.Announcement, c.AnnouncementRead, c.ErrorPassthroughRule, c.Group, c.IdempotencyRecord, c.PromoCode, c.PromoCodeUsage, c.Proxy, c.RedeemCode, c.SecuritySecret, c.Setting, - c.UsageCleanupTask, c.UsageLog, c.User, c.UserAllowedGroup, - c.UserAttributeDefinition, c.UserAttributeValue, c.UserSubscription, + c.TLSFingerprintProfile, c.UsageCleanupTask, c.UsageLog, c.User, + c.UserAllowedGroup, c.UserAttributeDefinition, c.UserAttributeValue, + c.UserSubscription, } { n.Intercept(interceptors...) } @@ -356,6 +364,8 @@ func (c *Client) Mutate(ctx context.Context, m Mutation) (Value, error) { return c.SecuritySecret.mutate(ctx, m) case *SettingMutation: return c.Setting.mutate(ctx, m) + case *TLSFingerprintProfileMutation: + return c.TLSFingerprintProfile.mutate(ctx, m) case *UsageCleanupTaskMutation: return c.UsageCleanupTask.mutate(ctx, m) case *UsageLogMutation: @@ -2612,6 +2622,139 @@ func (c *SettingClient) mutate(ctx context.Context, m *SettingMutation) (Value, } } +// TLSFingerprintProfileClient is a client for the TLSFingerprintProfile schema. +type TLSFingerprintProfileClient struct { + config +} + +// NewTLSFingerprintProfileClient returns a client for the TLSFingerprintProfile from the given config. +func NewTLSFingerprintProfileClient(c config) *TLSFingerprintProfileClient { + return &TLSFingerprintProfileClient{config: c} +} + +// Use adds a list of mutation hooks to the hooks stack. +// A call to `Use(f, g, h)` equals to `tlsfingerprintprofile.Hooks(f(g(h())))`. +func (c *TLSFingerprintProfileClient) Use(hooks ...Hook) { + c.hooks.TLSFingerprintProfile = append(c.hooks.TLSFingerprintProfile, hooks...) +} + +// Intercept adds a list of query interceptors to the interceptors stack. +// A call to `Intercept(f, g, h)` equals to `tlsfingerprintprofile.Intercept(f(g(h())))`. +func (c *TLSFingerprintProfileClient) Intercept(interceptors ...Interceptor) { + c.inters.TLSFingerprintProfile = append(c.inters.TLSFingerprintProfile, interceptors...) +} + +// Create returns a builder for creating a TLSFingerprintProfile entity. +func (c *TLSFingerprintProfileClient) Create() *TLSFingerprintProfileCreate { + mutation := newTLSFingerprintProfileMutation(c.config, OpCreate) + return &TLSFingerprintProfileCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// CreateBulk returns a builder for creating a bulk of TLSFingerprintProfile entities. +func (c *TLSFingerprintProfileClient) CreateBulk(builders ...*TLSFingerprintProfileCreate) *TLSFingerprintProfileCreateBulk { + return &TLSFingerprintProfileCreateBulk{config: c.config, builders: builders} +} + +// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates +// a builder and applies setFunc on it. +func (c *TLSFingerprintProfileClient) MapCreateBulk(slice any, setFunc func(*TLSFingerprintProfileCreate, int)) *TLSFingerprintProfileCreateBulk { + rv := reflect.ValueOf(slice) + if rv.Kind() != reflect.Slice { + return &TLSFingerprintProfileCreateBulk{err: fmt.Errorf("calling to TLSFingerprintProfileClient.MapCreateBulk with wrong type %T, need slice", slice)} + } + builders := make([]*TLSFingerprintProfileCreate, rv.Len()) + for i := 0; i < rv.Len(); i++ { + builders[i] = c.Create() + setFunc(builders[i], i) + } + return &TLSFingerprintProfileCreateBulk{config: c.config, builders: builders} +} + +// Update returns an update builder for TLSFingerprintProfile. +func (c *TLSFingerprintProfileClient) Update() *TLSFingerprintProfileUpdate { + mutation := newTLSFingerprintProfileMutation(c.config, OpUpdate) + return &TLSFingerprintProfileUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOne returns an update builder for the given entity. +func (c *TLSFingerprintProfileClient) UpdateOne(_m *TLSFingerprintProfile) *TLSFingerprintProfileUpdateOne { + mutation := newTLSFingerprintProfileMutation(c.config, OpUpdateOne, withTLSFingerprintProfile(_m)) + return &TLSFingerprintProfileUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOneID returns an update builder for the given id. +func (c *TLSFingerprintProfileClient) UpdateOneID(id int64) *TLSFingerprintProfileUpdateOne { + mutation := newTLSFingerprintProfileMutation(c.config, OpUpdateOne, withTLSFingerprintProfileID(id)) + return &TLSFingerprintProfileUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// Delete returns a delete builder for TLSFingerprintProfile. +func (c *TLSFingerprintProfileClient) Delete() *TLSFingerprintProfileDelete { + mutation := newTLSFingerprintProfileMutation(c.config, OpDelete) + return &TLSFingerprintProfileDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// DeleteOne returns a builder for deleting the given entity. +func (c *TLSFingerprintProfileClient) DeleteOne(_m *TLSFingerprintProfile) *TLSFingerprintProfileDeleteOne { + return c.DeleteOneID(_m.ID) +} + +// DeleteOneID returns a builder for deleting the given entity by its id. +func (c *TLSFingerprintProfileClient) DeleteOneID(id int64) *TLSFingerprintProfileDeleteOne { + builder := c.Delete().Where(tlsfingerprintprofile.ID(id)) + builder.mutation.id = &id + builder.mutation.op = OpDeleteOne + return &TLSFingerprintProfileDeleteOne{builder} +} + +// Query returns a query builder for TLSFingerprintProfile. +func (c *TLSFingerprintProfileClient) Query() *TLSFingerprintProfileQuery { + return &TLSFingerprintProfileQuery{ + config: c.config, + ctx: &QueryContext{Type: TypeTLSFingerprintProfile}, + inters: c.Interceptors(), + } +} + +// Get returns a TLSFingerprintProfile entity by its id. +func (c *TLSFingerprintProfileClient) Get(ctx context.Context, id int64) (*TLSFingerprintProfile, error) { + return c.Query().Where(tlsfingerprintprofile.ID(id)).Only(ctx) +} + +// GetX is like Get, but panics if an error occurs. +func (c *TLSFingerprintProfileClient) GetX(ctx context.Context, id int64) *TLSFingerprintProfile { + obj, err := c.Get(ctx, id) + if err != nil { + panic(err) + } + return obj +} + +// Hooks returns the client hooks. +func (c *TLSFingerprintProfileClient) Hooks() []Hook { + return c.hooks.TLSFingerprintProfile +} + +// Interceptors returns the client interceptors. +func (c *TLSFingerprintProfileClient) Interceptors() []Interceptor { + return c.inters.TLSFingerprintProfile +} + +func (c *TLSFingerprintProfileClient) mutate(ctx context.Context, m *TLSFingerprintProfileMutation) (Value, error) { + switch m.Op() { + case OpCreate: + return (&TLSFingerprintProfileCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdate: + return (&TLSFingerprintProfileUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdateOne: + return (&TLSFingerprintProfileUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpDelete, OpDeleteOne: + return (&TLSFingerprintProfileDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx) + default: + return nil, fmt.Errorf("ent: unknown TLSFingerprintProfile mutation op: %q", m.Op()) + } +} + // UsageCleanupTaskClient is a client for the UsageCleanupTask schema. type UsageCleanupTaskClient struct { config @@ -3889,16 +4032,16 @@ type ( hooks struct { APIKey, Account, AccountGroup, Announcement, AnnouncementRead, ErrorPassthroughRule, Group, IdempotencyRecord, PromoCode, PromoCodeUsage, - Proxy, RedeemCode, SecuritySecret, Setting, UsageCleanupTask, UsageLog, User, - UserAllowedGroup, UserAttributeDefinition, UserAttributeValue, - UserSubscription []ent.Hook + Proxy, RedeemCode, SecuritySecret, Setting, TLSFingerprintProfile, + UsageCleanupTask, UsageLog, User, UserAllowedGroup, UserAttributeDefinition, + UserAttributeValue, UserSubscription []ent.Hook } inters struct { APIKey, Account, AccountGroup, Announcement, AnnouncementRead, ErrorPassthroughRule, Group, IdempotencyRecord, PromoCode, PromoCodeUsage, - Proxy, RedeemCode, SecuritySecret, Setting, UsageCleanupTask, UsageLog, User, - UserAllowedGroup, UserAttributeDefinition, UserAttributeValue, - UserSubscription []ent.Interceptor + Proxy, RedeemCode, SecuritySecret, Setting, TLSFingerprintProfile, + UsageCleanupTask, UsageLog, User, UserAllowedGroup, UserAttributeDefinition, + UserAttributeValue, UserSubscription []ent.Interceptor } ) diff --git a/backend/ent/ent.go b/backend/ent/ent.go index 5197e4d8..bdeaed8a 100644 --- a/backend/ent/ent.go +++ b/backend/ent/ent.go @@ -26,6 +26,7 @@ import ( "github.com/Wei-Shaw/sub2api/ent/redeemcode" "github.com/Wei-Shaw/sub2api/ent/securitysecret" "github.com/Wei-Shaw/sub2api/ent/setting" + "github.com/Wei-Shaw/sub2api/ent/tlsfingerprintprofile" "github.com/Wei-Shaw/sub2api/ent/usagecleanuptask" "github.com/Wei-Shaw/sub2api/ent/usagelog" "github.com/Wei-Shaw/sub2api/ent/user" @@ -107,6 +108,7 @@ func checkColumn(t, c string) error { redeemcode.Table: redeemcode.ValidColumn, securitysecret.Table: securitysecret.ValidColumn, setting.Table: setting.ValidColumn, + tlsfingerprintprofile.Table: tlsfingerprintprofile.ValidColumn, usagecleanuptask.Table: usagecleanuptask.ValidColumn, usagelog.Table: usagelog.ValidColumn, user.Table: user.ValidColumn, diff --git a/backend/ent/hook/hook.go b/backend/ent/hook/hook.go index 49d7f3c5..f6f7b4e9 100644 --- a/backend/ent/hook/hook.go +++ b/backend/ent/hook/hook.go @@ -177,6 +177,18 @@ func (f SettingFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, err return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.SettingMutation", m) } +// The TLSFingerprintProfileFunc type is an adapter to allow the use of ordinary +// function as TLSFingerprintProfile mutator. +type TLSFingerprintProfileFunc func(context.Context, *ent.TLSFingerprintProfileMutation) (ent.Value, error) + +// Mutate calls f(ctx, m). +func (f TLSFingerprintProfileFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) { + if mv, ok := m.(*ent.TLSFingerprintProfileMutation); ok { + return f(ctx, mv) + } + return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.TLSFingerprintProfileMutation", m) +} + // The UsageCleanupTaskFunc type is an adapter to allow the use of ordinary // function as UsageCleanupTask mutator. type UsageCleanupTaskFunc func(context.Context, *ent.UsageCleanupTaskMutation) (ent.Value, error) diff --git a/backend/ent/intercept/intercept.go b/backend/ent/intercept/intercept.go index e7746402..13169ca7 100644 --- a/backend/ent/intercept/intercept.go +++ b/backend/ent/intercept/intercept.go @@ -23,6 +23,7 @@ import ( "github.com/Wei-Shaw/sub2api/ent/redeemcode" "github.com/Wei-Shaw/sub2api/ent/securitysecret" "github.com/Wei-Shaw/sub2api/ent/setting" + "github.com/Wei-Shaw/sub2api/ent/tlsfingerprintprofile" "github.com/Wei-Shaw/sub2api/ent/usagecleanuptask" "github.com/Wei-Shaw/sub2api/ent/usagelog" "github.com/Wei-Shaw/sub2api/ent/user" @@ -466,6 +467,33 @@ func (f TraverseSetting) Traverse(ctx context.Context, q ent.Query) error { return fmt.Errorf("unexpected query type %T. expect *ent.SettingQuery", q) } +// The TLSFingerprintProfileFunc type is an adapter to allow the use of ordinary function as a Querier. +type TLSFingerprintProfileFunc func(context.Context, *ent.TLSFingerprintProfileQuery) (ent.Value, error) + +// Query calls f(ctx, q). +func (f TLSFingerprintProfileFunc) Query(ctx context.Context, q ent.Query) (ent.Value, error) { + if q, ok := q.(*ent.TLSFingerprintProfileQuery); ok { + return f(ctx, q) + } + return nil, fmt.Errorf("unexpected query type %T. expect *ent.TLSFingerprintProfileQuery", q) +} + +// The TraverseTLSFingerprintProfile type is an adapter to allow the use of ordinary function as Traverser. +type TraverseTLSFingerprintProfile func(context.Context, *ent.TLSFingerprintProfileQuery) error + +// Intercept is a dummy implementation of Intercept that returns the next Querier in the pipeline. +func (f TraverseTLSFingerprintProfile) Intercept(next ent.Querier) ent.Querier { + return next +} + +// Traverse calls f(ctx, q). +func (f TraverseTLSFingerprintProfile) Traverse(ctx context.Context, q ent.Query) error { + if q, ok := q.(*ent.TLSFingerprintProfileQuery); ok { + return f(ctx, q) + } + return fmt.Errorf("unexpected query type %T. expect *ent.TLSFingerprintProfileQuery", q) +} + // The UsageCleanupTaskFunc type is an adapter to allow the use of ordinary function as a Querier. type UsageCleanupTaskFunc func(context.Context, *ent.UsageCleanupTaskQuery) (ent.Value, error) @@ -686,6 +714,8 @@ func NewQuery(q ent.Query) (Query, error) { return &query[*ent.SecuritySecretQuery, predicate.SecuritySecret, securitysecret.OrderOption]{typ: ent.TypeSecuritySecret, tq: q}, nil case *ent.SettingQuery: return &query[*ent.SettingQuery, predicate.Setting, setting.OrderOption]{typ: ent.TypeSetting, tq: q}, nil + case *ent.TLSFingerprintProfileQuery: + return &query[*ent.TLSFingerprintProfileQuery, predicate.TLSFingerprintProfile, tlsfingerprintprofile.OrderOption]{typ: ent.TypeTLSFingerprintProfile, tq: q}, nil case *ent.UsageCleanupTaskQuery: return &query[*ent.UsageCleanupTaskQuery, predicate.UsageCleanupTask, usagecleanuptask.OrderOption]{typ: ent.TypeUsageCleanupTask, tq: q}, nil case *ent.UsageLogQuery: diff --git a/backend/ent/migrate/schema.go b/backend/ent/migrate/schema.go index c4f3af5e..c472d7e0 100644 --- a/backend/ent/migrate/schema.go +++ b/backend/ent/migrate/schema.go @@ -673,6 +673,30 @@ var ( Columns: SettingsColumns, PrimaryKey: []*schema.Column{SettingsColumns[0]}, } + // TLSFingerprintProfilesColumns holds the columns for the "tls_fingerprint_profiles" table. + TLSFingerprintProfilesColumns = []*schema.Column{ + {Name: "id", Type: field.TypeInt64, Increment: true}, + {Name: "created_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}}, + {Name: "updated_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}}, + {Name: "name", Type: field.TypeString, Unique: true, Size: 100}, + {Name: "description", Type: field.TypeString, Nullable: true, Size: 2147483647}, + {Name: "enable_grease", Type: field.TypeBool, Default: false}, + {Name: "cipher_suites", Type: field.TypeJSON, Nullable: true, SchemaType: map[string]string{"postgres": "jsonb"}}, + {Name: "curves", Type: field.TypeJSON, Nullable: true, SchemaType: map[string]string{"postgres": "jsonb"}}, + {Name: "point_formats", Type: field.TypeJSON, Nullable: true, SchemaType: map[string]string{"postgres": "jsonb"}}, + {Name: "signature_algorithms", Type: field.TypeJSON, Nullable: true, SchemaType: map[string]string{"postgres": "jsonb"}}, + {Name: "alpn_protocols", Type: field.TypeJSON, Nullable: true, SchemaType: map[string]string{"postgres": "jsonb"}}, + {Name: "supported_versions", Type: field.TypeJSON, Nullable: true, SchemaType: map[string]string{"postgres": "jsonb"}}, + {Name: "key_share_groups", Type: field.TypeJSON, Nullable: true, SchemaType: map[string]string{"postgres": "jsonb"}}, + {Name: "psk_modes", Type: field.TypeJSON, Nullable: true, SchemaType: map[string]string{"postgres": "jsonb"}}, + {Name: "extensions", Type: field.TypeJSON, Nullable: true, SchemaType: map[string]string{"postgres": "jsonb"}}, + } + // TLSFingerprintProfilesTable holds the schema information for the "tls_fingerprint_profiles" table. + TLSFingerprintProfilesTable = &schema.Table{ + Name: "tls_fingerprint_profiles", + Columns: TLSFingerprintProfilesColumns, + PrimaryKey: []*schema.Column{TLSFingerprintProfilesColumns[0]}, + } // UsageCleanupTasksColumns holds the columns for the "usage_cleanup_tasks" table. UsageCleanupTasksColumns = []*schema.Column{ {Name: "id", Type: field.TypeInt64, Increment: true}, @@ -1111,6 +1135,7 @@ var ( RedeemCodesTable, SecuritySecretsTable, SettingsTable, + TLSFingerprintProfilesTable, UsageCleanupTasksTable, UsageLogsTable, UsersTable, @@ -1175,6 +1200,9 @@ func init() { SettingsTable.Annotation = &entsql.Annotation{ Table: "settings", } + TLSFingerprintProfilesTable.Annotation = &entsql.Annotation{ + Table: "tls_fingerprint_profiles", + } UsageCleanupTasksTable.Annotation = &entsql.Annotation{ Table: "usage_cleanup_tasks", } diff --git a/backend/ent/mutation.go b/backend/ent/mutation.go index 10f7afe4..42c63c2e 100644 --- a/backend/ent/mutation.go +++ b/backend/ent/mutation.go @@ -27,6 +27,7 @@ import ( "github.com/Wei-Shaw/sub2api/ent/redeemcode" "github.com/Wei-Shaw/sub2api/ent/securitysecret" "github.com/Wei-Shaw/sub2api/ent/setting" + "github.com/Wei-Shaw/sub2api/ent/tlsfingerprintprofile" "github.com/Wei-Shaw/sub2api/ent/usagecleanuptask" "github.com/Wei-Shaw/sub2api/ent/usagelog" "github.com/Wei-Shaw/sub2api/ent/user" @@ -60,6 +61,7 @@ const ( TypeRedeemCode = "RedeemCode" TypeSecuritySecret = "SecuritySecret" TypeSetting = "Setting" + TypeTLSFingerprintProfile = "TLSFingerprintProfile" TypeUsageCleanupTask = "UsageCleanupTask" TypeUsageLog = "UsageLog" TypeUser = "User" @@ -17148,6 +17150,1380 @@ func (m *SettingMutation) ResetEdge(name string) error { return fmt.Errorf("unknown Setting edge %s", name) } +// TLSFingerprintProfileMutation represents an operation that mutates the TLSFingerprintProfile nodes in the graph. +type TLSFingerprintProfileMutation struct { + config + op Op + typ string + id *int64 + created_at *time.Time + updated_at *time.Time + name *string + description *string + enable_grease *bool + cipher_suites *[]uint16 + appendcipher_suites []uint16 + curves *[]uint16 + appendcurves []uint16 + point_formats *[]uint16 + appendpoint_formats []uint16 + signature_algorithms *[]uint16 + appendsignature_algorithms []uint16 + alpn_protocols *[]string + appendalpn_protocols []string + supported_versions *[]uint16 + appendsupported_versions []uint16 + key_share_groups *[]uint16 + appendkey_share_groups []uint16 + psk_modes *[]uint16 + appendpsk_modes []uint16 + extensions *[]uint16 + appendextensions []uint16 + clearedFields map[string]struct{} + done bool + oldValue func(context.Context) (*TLSFingerprintProfile, error) + predicates []predicate.TLSFingerprintProfile +} + +var _ ent.Mutation = (*TLSFingerprintProfileMutation)(nil) + +// tlsfingerprintprofileOption allows management of the mutation configuration using functional options. +type tlsfingerprintprofileOption func(*TLSFingerprintProfileMutation) + +// newTLSFingerprintProfileMutation creates new mutation for the TLSFingerprintProfile entity. +func newTLSFingerprintProfileMutation(c config, op Op, opts ...tlsfingerprintprofileOption) *TLSFingerprintProfileMutation { + m := &TLSFingerprintProfileMutation{ + config: c, + op: op, + typ: TypeTLSFingerprintProfile, + clearedFields: make(map[string]struct{}), + } + for _, opt := range opts { + opt(m) + } + return m +} + +// withTLSFingerprintProfileID sets the ID field of the mutation. +func withTLSFingerprintProfileID(id int64) tlsfingerprintprofileOption { + return func(m *TLSFingerprintProfileMutation) { + var ( + err error + once sync.Once + value *TLSFingerprintProfile + ) + m.oldValue = func(ctx context.Context) (*TLSFingerprintProfile, error) { + once.Do(func() { + if m.done { + err = errors.New("querying old values post mutation is not allowed") + } else { + value, err = m.Client().TLSFingerprintProfile.Get(ctx, id) + } + }) + return value, err + } + m.id = &id + } +} + +// withTLSFingerprintProfile sets the old TLSFingerprintProfile of the mutation. +func withTLSFingerprintProfile(node *TLSFingerprintProfile) tlsfingerprintprofileOption { + return func(m *TLSFingerprintProfileMutation) { + m.oldValue = func(context.Context) (*TLSFingerprintProfile, error) { + return node, nil + } + m.id = &node.ID + } +} + +// Client returns a new `ent.Client` from the mutation. If the mutation was +// executed in a transaction (ent.Tx), a transactional client is returned. +func (m TLSFingerprintProfileMutation) Client() *Client { + client := &Client{config: m.config} + client.init() + return client +} + +// Tx returns an `ent.Tx` for mutations that were executed in transactions; +// it returns an error otherwise. +func (m TLSFingerprintProfileMutation) Tx() (*Tx, error) { + if _, ok := m.driver.(*txDriver); !ok { + return nil, errors.New("ent: mutation is not running in a transaction") + } + tx := &Tx{config: m.config} + tx.init() + return tx, nil +} + +// ID returns the ID value in the mutation. Note that the ID is only available +// if it was provided to the builder or after it was returned from the database. +func (m *TLSFingerprintProfileMutation) ID() (id int64, exists bool) { + if m.id == nil { + return + } + return *m.id, true +} + +// IDs queries the database and returns the entity ids that match the mutation's predicate. +// That means, if the mutation is applied within a transaction with an isolation level such +// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated +// or updated by the mutation. +func (m *TLSFingerprintProfileMutation) IDs(ctx context.Context) ([]int64, error) { + switch { + case m.op.Is(OpUpdateOne | OpDeleteOne): + id, exists := m.ID() + if exists { + return []int64{id}, nil + } + fallthrough + case m.op.Is(OpUpdate | OpDelete): + return m.Client().TLSFingerprintProfile.Query().Where(m.predicates...).IDs(ctx) + default: + return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) + } +} + +// SetCreatedAt sets the "created_at" field. +func (m *TLSFingerprintProfileMutation) SetCreatedAt(t time.Time) { + m.created_at = &t +} + +// CreatedAt returns the value of the "created_at" field in the mutation. +func (m *TLSFingerprintProfileMutation) CreatedAt() (r time.Time, exists bool) { + v := m.created_at + if v == nil { + return + } + return *v, true +} + +// OldCreatedAt returns the old "created_at" field's value of the TLSFingerprintProfile entity. +// If the TLSFingerprintProfile 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 *TLSFingerprintProfileMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldCreatedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldCreatedAt: %w", err) + } + return oldValue.CreatedAt, nil +} + +// ResetCreatedAt resets all changes to the "created_at" field. +func (m *TLSFingerprintProfileMutation) ResetCreatedAt() { + m.created_at = nil +} + +// SetUpdatedAt sets the "updated_at" field. +func (m *TLSFingerprintProfileMutation) SetUpdatedAt(t time.Time) { + m.updated_at = &t +} + +// UpdatedAt returns the value of the "updated_at" field in the mutation. +func (m *TLSFingerprintProfileMutation) UpdatedAt() (r time.Time, exists bool) { + v := m.updated_at + if v == nil { + return + } + return *v, true +} + +// OldUpdatedAt returns the old "updated_at" field's value of the TLSFingerprintProfile entity. +// If the TLSFingerprintProfile 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 *TLSFingerprintProfileMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldUpdatedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldUpdatedAt: %w", err) + } + return oldValue.UpdatedAt, nil +} + +// ResetUpdatedAt resets all changes to the "updated_at" field. +func (m *TLSFingerprintProfileMutation) ResetUpdatedAt() { + m.updated_at = nil +} + +// SetName sets the "name" field. +func (m *TLSFingerprintProfileMutation) SetName(s string) { + m.name = &s +} + +// Name returns the value of the "name" field in the mutation. +func (m *TLSFingerprintProfileMutation) Name() (r string, exists bool) { + v := m.name + if v == nil { + return + } + return *v, true +} + +// OldName returns the old "name" field's value of the TLSFingerprintProfile entity. +// If the TLSFingerprintProfile 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 *TLSFingerprintProfileMutation) OldName(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldName is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldName requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldName: %w", err) + } + return oldValue.Name, nil +} + +// ResetName resets all changes to the "name" field. +func (m *TLSFingerprintProfileMutation) ResetName() { + m.name = nil +} + +// SetDescription sets the "description" field. +func (m *TLSFingerprintProfileMutation) SetDescription(s string) { + m.description = &s +} + +// Description returns the value of the "description" field in the mutation. +func (m *TLSFingerprintProfileMutation) Description() (r string, exists bool) { + v := m.description + if v == nil { + return + } + return *v, true +} + +// OldDescription returns the old "description" field's value of the TLSFingerprintProfile entity. +// If the TLSFingerprintProfile 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 *TLSFingerprintProfileMutation) OldDescription(ctx context.Context) (v *string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldDescription is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldDescription requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldDescription: %w", err) + } + return oldValue.Description, nil +} + +// ClearDescription clears the value of the "description" field. +func (m *TLSFingerprintProfileMutation) ClearDescription() { + m.description = nil + m.clearedFields[tlsfingerprintprofile.FieldDescription] = struct{}{} +} + +// DescriptionCleared returns if the "description" field was cleared in this mutation. +func (m *TLSFingerprintProfileMutation) DescriptionCleared() bool { + _, ok := m.clearedFields[tlsfingerprintprofile.FieldDescription] + return ok +} + +// ResetDescription resets all changes to the "description" field. +func (m *TLSFingerprintProfileMutation) ResetDescription() { + m.description = nil + delete(m.clearedFields, tlsfingerprintprofile.FieldDescription) +} + +// SetEnableGrease sets the "enable_grease" field. +func (m *TLSFingerprintProfileMutation) SetEnableGrease(b bool) { + m.enable_grease = &b +} + +// EnableGrease returns the value of the "enable_grease" field in the mutation. +func (m *TLSFingerprintProfileMutation) EnableGrease() (r bool, exists bool) { + v := m.enable_grease + if v == nil { + return + } + return *v, true +} + +// OldEnableGrease returns the old "enable_grease" field's value of the TLSFingerprintProfile entity. +// If the TLSFingerprintProfile 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 *TLSFingerprintProfileMutation) OldEnableGrease(ctx context.Context) (v bool, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldEnableGrease is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldEnableGrease requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldEnableGrease: %w", err) + } + return oldValue.EnableGrease, nil +} + +// ResetEnableGrease resets all changes to the "enable_grease" field. +func (m *TLSFingerprintProfileMutation) ResetEnableGrease() { + m.enable_grease = nil +} + +// SetCipherSuites sets the "cipher_suites" field. +func (m *TLSFingerprintProfileMutation) SetCipherSuites(u []uint16) { + m.cipher_suites = &u + m.appendcipher_suites = nil +} + +// CipherSuites returns the value of the "cipher_suites" field in the mutation. +func (m *TLSFingerprintProfileMutation) CipherSuites() (r []uint16, exists bool) { + v := m.cipher_suites + if v == nil { + return + } + return *v, true +} + +// OldCipherSuites returns the old "cipher_suites" field's value of the TLSFingerprintProfile entity. +// If the TLSFingerprintProfile 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 *TLSFingerprintProfileMutation) OldCipherSuites(ctx context.Context) (v []uint16, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldCipherSuites is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldCipherSuites requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldCipherSuites: %w", err) + } + return oldValue.CipherSuites, nil +} + +// AppendCipherSuites adds u to the "cipher_suites" field. +func (m *TLSFingerprintProfileMutation) AppendCipherSuites(u []uint16) { + m.appendcipher_suites = append(m.appendcipher_suites, u...) +} + +// AppendedCipherSuites returns the list of values that were appended to the "cipher_suites" field in this mutation. +func (m *TLSFingerprintProfileMutation) AppendedCipherSuites() ([]uint16, bool) { + if len(m.appendcipher_suites) == 0 { + return nil, false + } + return m.appendcipher_suites, true +} + +// ClearCipherSuites clears the value of the "cipher_suites" field. +func (m *TLSFingerprintProfileMutation) ClearCipherSuites() { + m.cipher_suites = nil + m.appendcipher_suites = nil + m.clearedFields[tlsfingerprintprofile.FieldCipherSuites] = struct{}{} +} + +// CipherSuitesCleared returns if the "cipher_suites" field was cleared in this mutation. +func (m *TLSFingerprintProfileMutation) CipherSuitesCleared() bool { + _, ok := m.clearedFields[tlsfingerprintprofile.FieldCipherSuites] + return ok +} + +// ResetCipherSuites resets all changes to the "cipher_suites" field. +func (m *TLSFingerprintProfileMutation) ResetCipherSuites() { + m.cipher_suites = nil + m.appendcipher_suites = nil + delete(m.clearedFields, tlsfingerprintprofile.FieldCipherSuites) +} + +// SetCurves sets the "curves" field. +func (m *TLSFingerprintProfileMutation) SetCurves(u []uint16) { + m.curves = &u + m.appendcurves = nil +} + +// Curves returns the value of the "curves" field in the mutation. +func (m *TLSFingerprintProfileMutation) Curves() (r []uint16, exists bool) { + v := m.curves + if v == nil { + return + } + return *v, true +} + +// OldCurves returns the old "curves" field's value of the TLSFingerprintProfile entity. +// If the TLSFingerprintProfile 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 *TLSFingerprintProfileMutation) OldCurves(ctx context.Context) (v []uint16, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldCurves is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldCurves requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldCurves: %w", err) + } + return oldValue.Curves, nil +} + +// AppendCurves adds u to the "curves" field. +func (m *TLSFingerprintProfileMutation) AppendCurves(u []uint16) { + m.appendcurves = append(m.appendcurves, u...) +} + +// AppendedCurves returns the list of values that were appended to the "curves" field in this mutation. +func (m *TLSFingerprintProfileMutation) AppendedCurves() ([]uint16, bool) { + if len(m.appendcurves) == 0 { + return nil, false + } + return m.appendcurves, true +} + +// ClearCurves clears the value of the "curves" field. +func (m *TLSFingerprintProfileMutation) ClearCurves() { + m.curves = nil + m.appendcurves = nil + m.clearedFields[tlsfingerprintprofile.FieldCurves] = struct{}{} +} + +// CurvesCleared returns if the "curves" field was cleared in this mutation. +func (m *TLSFingerprintProfileMutation) CurvesCleared() bool { + _, ok := m.clearedFields[tlsfingerprintprofile.FieldCurves] + return ok +} + +// ResetCurves resets all changes to the "curves" field. +func (m *TLSFingerprintProfileMutation) ResetCurves() { + m.curves = nil + m.appendcurves = nil + delete(m.clearedFields, tlsfingerprintprofile.FieldCurves) +} + +// SetPointFormats sets the "point_formats" field. +func (m *TLSFingerprintProfileMutation) SetPointFormats(u []uint16) { + m.point_formats = &u + m.appendpoint_formats = nil +} + +// PointFormats returns the value of the "point_formats" field in the mutation. +func (m *TLSFingerprintProfileMutation) PointFormats() (r []uint16, exists bool) { + v := m.point_formats + if v == nil { + return + } + return *v, true +} + +// OldPointFormats returns the old "point_formats" field's value of the TLSFingerprintProfile entity. +// If the TLSFingerprintProfile 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 *TLSFingerprintProfileMutation) OldPointFormats(ctx context.Context) (v []uint16, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldPointFormats is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldPointFormats requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldPointFormats: %w", err) + } + return oldValue.PointFormats, nil +} + +// AppendPointFormats adds u to the "point_formats" field. +func (m *TLSFingerprintProfileMutation) AppendPointFormats(u []uint16) { + m.appendpoint_formats = append(m.appendpoint_formats, u...) +} + +// AppendedPointFormats returns the list of values that were appended to the "point_formats" field in this mutation. +func (m *TLSFingerprintProfileMutation) AppendedPointFormats() ([]uint16, bool) { + if len(m.appendpoint_formats) == 0 { + return nil, false + } + return m.appendpoint_formats, true +} + +// ClearPointFormats clears the value of the "point_formats" field. +func (m *TLSFingerprintProfileMutation) ClearPointFormats() { + m.point_formats = nil + m.appendpoint_formats = nil + m.clearedFields[tlsfingerprintprofile.FieldPointFormats] = struct{}{} +} + +// PointFormatsCleared returns if the "point_formats" field was cleared in this mutation. +func (m *TLSFingerprintProfileMutation) PointFormatsCleared() bool { + _, ok := m.clearedFields[tlsfingerprintprofile.FieldPointFormats] + return ok +} + +// ResetPointFormats resets all changes to the "point_formats" field. +func (m *TLSFingerprintProfileMutation) ResetPointFormats() { + m.point_formats = nil + m.appendpoint_formats = nil + delete(m.clearedFields, tlsfingerprintprofile.FieldPointFormats) +} + +// SetSignatureAlgorithms sets the "signature_algorithms" field. +func (m *TLSFingerprintProfileMutation) SetSignatureAlgorithms(u []uint16) { + m.signature_algorithms = &u + m.appendsignature_algorithms = nil +} + +// SignatureAlgorithms returns the value of the "signature_algorithms" field in the mutation. +func (m *TLSFingerprintProfileMutation) SignatureAlgorithms() (r []uint16, exists bool) { + v := m.signature_algorithms + if v == nil { + return + } + return *v, true +} + +// OldSignatureAlgorithms returns the old "signature_algorithms" field's value of the TLSFingerprintProfile entity. +// If the TLSFingerprintProfile 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 *TLSFingerprintProfileMutation) OldSignatureAlgorithms(ctx context.Context) (v []uint16, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldSignatureAlgorithms is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldSignatureAlgorithms requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldSignatureAlgorithms: %w", err) + } + return oldValue.SignatureAlgorithms, nil +} + +// AppendSignatureAlgorithms adds u to the "signature_algorithms" field. +func (m *TLSFingerprintProfileMutation) AppendSignatureAlgorithms(u []uint16) { + m.appendsignature_algorithms = append(m.appendsignature_algorithms, u...) +} + +// AppendedSignatureAlgorithms returns the list of values that were appended to the "signature_algorithms" field in this mutation. +func (m *TLSFingerprintProfileMutation) AppendedSignatureAlgorithms() ([]uint16, bool) { + if len(m.appendsignature_algorithms) == 0 { + return nil, false + } + return m.appendsignature_algorithms, true +} + +// ClearSignatureAlgorithms clears the value of the "signature_algorithms" field. +func (m *TLSFingerprintProfileMutation) ClearSignatureAlgorithms() { + m.signature_algorithms = nil + m.appendsignature_algorithms = nil + m.clearedFields[tlsfingerprintprofile.FieldSignatureAlgorithms] = struct{}{} +} + +// SignatureAlgorithmsCleared returns if the "signature_algorithms" field was cleared in this mutation. +func (m *TLSFingerprintProfileMutation) SignatureAlgorithmsCleared() bool { + _, ok := m.clearedFields[tlsfingerprintprofile.FieldSignatureAlgorithms] + return ok +} + +// ResetSignatureAlgorithms resets all changes to the "signature_algorithms" field. +func (m *TLSFingerprintProfileMutation) ResetSignatureAlgorithms() { + m.signature_algorithms = nil + m.appendsignature_algorithms = nil + delete(m.clearedFields, tlsfingerprintprofile.FieldSignatureAlgorithms) +} + +// SetAlpnProtocols sets the "alpn_protocols" field. +func (m *TLSFingerprintProfileMutation) SetAlpnProtocols(s []string) { + m.alpn_protocols = &s + m.appendalpn_protocols = nil +} + +// AlpnProtocols returns the value of the "alpn_protocols" field in the mutation. +func (m *TLSFingerprintProfileMutation) AlpnProtocols() (r []string, exists bool) { + v := m.alpn_protocols + if v == nil { + return + } + return *v, true +} + +// OldAlpnProtocols returns the old "alpn_protocols" field's value of the TLSFingerprintProfile entity. +// If the TLSFingerprintProfile 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 *TLSFingerprintProfileMutation) OldAlpnProtocols(ctx context.Context) (v []string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldAlpnProtocols is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldAlpnProtocols requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldAlpnProtocols: %w", err) + } + return oldValue.AlpnProtocols, nil +} + +// AppendAlpnProtocols adds s to the "alpn_protocols" field. +func (m *TLSFingerprintProfileMutation) AppendAlpnProtocols(s []string) { + m.appendalpn_protocols = append(m.appendalpn_protocols, s...) +} + +// AppendedAlpnProtocols returns the list of values that were appended to the "alpn_protocols" field in this mutation. +func (m *TLSFingerprintProfileMutation) AppendedAlpnProtocols() ([]string, bool) { + if len(m.appendalpn_protocols) == 0 { + return nil, false + } + return m.appendalpn_protocols, true +} + +// ClearAlpnProtocols clears the value of the "alpn_protocols" field. +func (m *TLSFingerprintProfileMutation) ClearAlpnProtocols() { + m.alpn_protocols = nil + m.appendalpn_protocols = nil + m.clearedFields[tlsfingerprintprofile.FieldAlpnProtocols] = struct{}{} +} + +// AlpnProtocolsCleared returns if the "alpn_protocols" field was cleared in this mutation. +func (m *TLSFingerprintProfileMutation) AlpnProtocolsCleared() bool { + _, ok := m.clearedFields[tlsfingerprintprofile.FieldAlpnProtocols] + return ok +} + +// ResetAlpnProtocols resets all changes to the "alpn_protocols" field. +func (m *TLSFingerprintProfileMutation) ResetAlpnProtocols() { + m.alpn_protocols = nil + m.appendalpn_protocols = nil + delete(m.clearedFields, tlsfingerprintprofile.FieldAlpnProtocols) +} + +// SetSupportedVersions sets the "supported_versions" field. +func (m *TLSFingerprintProfileMutation) SetSupportedVersions(u []uint16) { + m.supported_versions = &u + m.appendsupported_versions = nil +} + +// SupportedVersions returns the value of the "supported_versions" field in the mutation. +func (m *TLSFingerprintProfileMutation) SupportedVersions() (r []uint16, exists bool) { + v := m.supported_versions + if v == nil { + return + } + return *v, true +} + +// OldSupportedVersions returns the old "supported_versions" field's value of the TLSFingerprintProfile entity. +// If the TLSFingerprintProfile 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 *TLSFingerprintProfileMutation) OldSupportedVersions(ctx context.Context) (v []uint16, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldSupportedVersions is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldSupportedVersions requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldSupportedVersions: %w", err) + } + return oldValue.SupportedVersions, nil +} + +// AppendSupportedVersions adds u to the "supported_versions" field. +func (m *TLSFingerprintProfileMutation) AppendSupportedVersions(u []uint16) { + m.appendsupported_versions = append(m.appendsupported_versions, u...) +} + +// AppendedSupportedVersions returns the list of values that were appended to the "supported_versions" field in this mutation. +func (m *TLSFingerprintProfileMutation) AppendedSupportedVersions() ([]uint16, bool) { + if len(m.appendsupported_versions) == 0 { + return nil, false + } + return m.appendsupported_versions, true +} + +// ClearSupportedVersions clears the value of the "supported_versions" field. +func (m *TLSFingerprintProfileMutation) ClearSupportedVersions() { + m.supported_versions = nil + m.appendsupported_versions = nil + m.clearedFields[tlsfingerprintprofile.FieldSupportedVersions] = struct{}{} +} + +// SupportedVersionsCleared returns if the "supported_versions" field was cleared in this mutation. +func (m *TLSFingerprintProfileMutation) SupportedVersionsCleared() bool { + _, ok := m.clearedFields[tlsfingerprintprofile.FieldSupportedVersions] + return ok +} + +// ResetSupportedVersions resets all changes to the "supported_versions" field. +func (m *TLSFingerprintProfileMutation) ResetSupportedVersions() { + m.supported_versions = nil + m.appendsupported_versions = nil + delete(m.clearedFields, tlsfingerprintprofile.FieldSupportedVersions) +} + +// SetKeyShareGroups sets the "key_share_groups" field. +func (m *TLSFingerprintProfileMutation) SetKeyShareGroups(u []uint16) { + m.key_share_groups = &u + m.appendkey_share_groups = nil +} + +// KeyShareGroups returns the value of the "key_share_groups" field in the mutation. +func (m *TLSFingerprintProfileMutation) KeyShareGroups() (r []uint16, exists bool) { + v := m.key_share_groups + if v == nil { + return + } + return *v, true +} + +// OldKeyShareGroups returns the old "key_share_groups" field's value of the TLSFingerprintProfile entity. +// If the TLSFingerprintProfile 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 *TLSFingerprintProfileMutation) OldKeyShareGroups(ctx context.Context) (v []uint16, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldKeyShareGroups is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldKeyShareGroups requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldKeyShareGroups: %w", err) + } + return oldValue.KeyShareGroups, nil +} + +// AppendKeyShareGroups adds u to the "key_share_groups" field. +func (m *TLSFingerprintProfileMutation) AppendKeyShareGroups(u []uint16) { + m.appendkey_share_groups = append(m.appendkey_share_groups, u...) +} + +// AppendedKeyShareGroups returns the list of values that were appended to the "key_share_groups" field in this mutation. +func (m *TLSFingerprintProfileMutation) AppendedKeyShareGroups() ([]uint16, bool) { + if len(m.appendkey_share_groups) == 0 { + return nil, false + } + return m.appendkey_share_groups, true +} + +// ClearKeyShareGroups clears the value of the "key_share_groups" field. +func (m *TLSFingerprintProfileMutation) ClearKeyShareGroups() { + m.key_share_groups = nil + m.appendkey_share_groups = nil + m.clearedFields[tlsfingerprintprofile.FieldKeyShareGroups] = struct{}{} +} + +// KeyShareGroupsCleared returns if the "key_share_groups" field was cleared in this mutation. +func (m *TLSFingerprintProfileMutation) KeyShareGroupsCleared() bool { + _, ok := m.clearedFields[tlsfingerprintprofile.FieldKeyShareGroups] + return ok +} + +// ResetKeyShareGroups resets all changes to the "key_share_groups" field. +func (m *TLSFingerprintProfileMutation) ResetKeyShareGroups() { + m.key_share_groups = nil + m.appendkey_share_groups = nil + delete(m.clearedFields, tlsfingerprintprofile.FieldKeyShareGroups) +} + +// SetPskModes sets the "psk_modes" field. +func (m *TLSFingerprintProfileMutation) SetPskModes(u []uint16) { + m.psk_modes = &u + m.appendpsk_modes = nil +} + +// PskModes returns the value of the "psk_modes" field in the mutation. +func (m *TLSFingerprintProfileMutation) PskModes() (r []uint16, exists bool) { + v := m.psk_modes + if v == nil { + return + } + return *v, true +} + +// OldPskModes returns the old "psk_modes" field's value of the TLSFingerprintProfile entity. +// If the TLSFingerprintProfile 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 *TLSFingerprintProfileMutation) OldPskModes(ctx context.Context) (v []uint16, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldPskModes is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldPskModes requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldPskModes: %w", err) + } + return oldValue.PskModes, nil +} + +// AppendPskModes adds u to the "psk_modes" field. +func (m *TLSFingerprintProfileMutation) AppendPskModes(u []uint16) { + m.appendpsk_modes = append(m.appendpsk_modes, u...) +} + +// AppendedPskModes returns the list of values that were appended to the "psk_modes" field in this mutation. +func (m *TLSFingerprintProfileMutation) AppendedPskModes() ([]uint16, bool) { + if len(m.appendpsk_modes) == 0 { + return nil, false + } + return m.appendpsk_modes, true +} + +// ClearPskModes clears the value of the "psk_modes" field. +func (m *TLSFingerprintProfileMutation) ClearPskModes() { + m.psk_modes = nil + m.appendpsk_modes = nil + m.clearedFields[tlsfingerprintprofile.FieldPskModes] = struct{}{} +} + +// PskModesCleared returns if the "psk_modes" field was cleared in this mutation. +func (m *TLSFingerprintProfileMutation) PskModesCleared() bool { + _, ok := m.clearedFields[tlsfingerprintprofile.FieldPskModes] + return ok +} + +// ResetPskModes resets all changes to the "psk_modes" field. +func (m *TLSFingerprintProfileMutation) ResetPskModes() { + m.psk_modes = nil + m.appendpsk_modes = nil + delete(m.clearedFields, tlsfingerprintprofile.FieldPskModes) +} + +// SetExtensions sets the "extensions" field. +func (m *TLSFingerprintProfileMutation) SetExtensions(u []uint16) { + m.extensions = &u + m.appendextensions = nil +} + +// Extensions returns the value of the "extensions" field in the mutation. +func (m *TLSFingerprintProfileMutation) Extensions() (r []uint16, exists bool) { + v := m.extensions + if v == nil { + return + } + return *v, true +} + +// OldExtensions returns the old "extensions" field's value of the TLSFingerprintProfile entity. +// If the TLSFingerprintProfile 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 *TLSFingerprintProfileMutation) OldExtensions(ctx context.Context) (v []uint16, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldExtensions is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldExtensions requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldExtensions: %w", err) + } + return oldValue.Extensions, nil +} + +// AppendExtensions adds u to the "extensions" field. +func (m *TLSFingerprintProfileMutation) AppendExtensions(u []uint16) { + m.appendextensions = append(m.appendextensions, u...) +} + +// AppendedExtensions returns the list of values that were appended to the "extensions" field in this mutation. +func (m *TLSFingerprintProfileMutation) AppendedExtensions() ([]uint16, bool) { + if len(m.appendextensions) == 0 { + return nil, false + } + return m.appendextensions, true +} + +// ClearExtensions clears the value of the "extensions" field. +func (m *TLSFingerprintProfileMutation) ClearExtensions() { + m.extensions = nil + m.appendextensions = nil + m.clearedFields[tlsfingerprintprofile.FieldExtensions] = struct{}{} +} + +// ExtensionsCleared returns if the "extensions" field was cleared in this mutation. +func (m *TLSFingerprintProfileMutation) ExtensionsCleared() bool { + _, ok := m.clearedFields[tlsfingerprintprofile.FieldExtensions] + return ok +} + +// ResetExtensions resets all changes to the "extensions" field. +func (m *TLSFingerprintProfileMutation) ResetExtensions() { + m.extensions = nil + m.appendextensions = nil + delete(m.clearedFields, tlsfingerprintprofile.FieldExtensions) +} + +// Where appends a list predicates to the TLSFingerprintProfileMutation builder. +func (m *TLSFingerprintProfileMutation) Where(ps ...predicate.TLSFingerprintProfile) { + m.predicates = append(m.predicates, ps...) +} + +// WhereP appends storage-level predicates to the TLSFingerprintProfileMutation builder. Using this method, +// users can use type-assertion to append predicates that do not depend on any generated package. +func (m *TLSFingerprintProfileMutation) WhereP(ps ...func(*sql.Selector)) { + p := make([]predicate.TLSFingerprintProfile, len(ps)) + for i := range ps { + p[i] = ps[i] + } + m.Where(p...) +} + +// Op returns the operation name. +func (m *TLSFingerprintProfileMutation) Op() Op { + return m.op +} + +// SetOp allows setting the mutation operation. +func (m *TLSFingerprintProfileMutation) SetOp(op Op) { + m.op = op +} + +// Type returns the node type of this mutation (TLSFingerprintProfile). +func (m *TLSFingerprintProfileMutation) Type() string { + return m.typ +} + +// Fields returns all fields that were changed during this mutation. Note that in +// order to get all numeric fields that were incremented/decremented, call +// AddedFields(). +func (m *TLSFingerprintProfileMutation) Fields() []string { + fields := make([]string, 0, 14) + if m.created_at != nil { + fields = append(fields, tlsfingerprintprofile.FieldCreatedAt) + } + if m.updated_at != nil { + fields = append(fields, tlsfingerprintprofile.FieldUpdatedAt) + } + if m.name != nil { + fields = append(fields, tlsfingerprintprofile.FieldName) + } + if m.description != nil { + fields = append(fields, tlsfingerprintprofile.FieldDescription) + } + if m.enable_grease != nil { + fields = append(fields, tlsfingerprintprofile.FieldEnableGrease) + } + if m.cipher_suites != nil { + fields = append(fields, tlsfingerprintprofile.FieldCipherSuites) + } + if m.curves != nil { + fields = append(fields, tlsfingerprintprofile.FieldCurves) + } + if m.point_formats != nil { + fields = append(fields, tlsfingerprintprofile.FieldPointFormats) + } + if m.signature_algorithms != nil { + fields = append(fields, tlsfingerprintprofile.FieldSignatureAlgorithms) + } + if m.alpn_protocols != nil { + fields = append(fields, tlsfingerprintprofile.FieldAlpnProtocols) + } + if m.supported_versions != nil { + fields = append(fields, tlsfingerprintprofile.FieldSupportedVersions) + } + if m.key_share_groups != nil { + fields = append(fields, tlsfingerprintprofile.FieldKeyShareGroups) + } + if m.psk_modes != nil { + fields = append(fields, tlsfingerprintprofile.FieldPskModes) + } + if m.extensions != nil { + fields = append(fields, tlsfingerprintprofile.FieldExtensions) + } + return fields +} + +// Field returns the value of a field with the given name. The second boolean +// return value indicates that this field was not set, or was not defined in the +// schema. +func (m *TLSFingerprintProfileMutation) Field(name string) (ent.Value, bool) { + switch name { + case tlsfingerprintprofile.FieldCreatedAt: + return m.CreatedAt() + case tlsfingerprintprofile.FieldUpdatedAt: + return m.UpdatedAt() + case tlsfingerprintprofile.FieldName: + return m.Name() + case tlsfingerprintprofile.FieldDescription: + return m.Description() + case tlsfingerprintprofile.FieldEnableGrease: + return m.EnableGrease() + case tlsfingerprintprofile.FieldCipherSuites: + return m.CipherSuites() + case tlsfingerprintprofile.FieldCurves: + return m.Curves() + case tlsfingerprintprofile.FieldPointFormats: + return m.PointFormats() + case tlsfingerprintprofile.FieldSignatureAlgorithms: + return m.SignatureAlgorithms() + case tlsfingerprintprofile.FieldAlpnProtocols: + return m.AlpnProtocols() + case tlsfingerprintprofile.FieldSupportedVersions: + return m.SupportedVersions() + case tlsfingerprintprofile.FieldKeyShareGroups: + return m.KeyShareGroups() + case tlsfingerprintprofile.FieldPskModes: + return m.PskModes() + case tlsfingerprintprofile.FieldExtensions: + return m.Extensions() + } + return nil, false +} + +// OldField returns the old value of the field from the database. An error is +// returned if the mutation operation is not UpdateOne, or the query to the +// database failed. +func (m *TLSFingerprintProfileMutation) OldField(ctx context.Context, name string) (ent.Value, error) { + switch name { + case tlsfingerprintprofile.FieldCreatedAt: + return m.OldCreatedAt(ctx) + case tlsfingerprintprofile.FieldUpdatedAt: + return m.OldUpdatedAt(ctx) + case tlsfingerprintprofile.FieldName: + return m.OldName(ctx) + case tlsfingerprintprofile.FieldDescription: + return m.OldDescription(ctx) + case tlsfingerprintprofile.FieldEnableGrease: + return m.OldEnableGrease(ctx) + case tlsfingerprintprofile.FieldCipherSuites: + return m.OldCipherSuites(ctx) + case tlsfingerprintprofile.FieldCurves: + return m.OldCurves(ctx) + case tlsfingerprintprofile.FieldPointFormats: + return m.OldPointFormats(ctx) + case tlsfingerprintprofile.FieldSignatureAlgorithms: + return m.OldSignatureAlgorithms(ctx) + case tlsfingerprintprofile.FieldAlpnProtocols: + return m.OldAlpnProtocols(ctx) + case tlsfingerprintprofile.FieldSupportedVersions: + return m.OldSupportedVersions(ctx) + case tlsfingerprintprofile.FieldKeyShareGroups: + return m.OldKeyShareGroups(ctx) + case tlsfingerprintprofile.FieldPskModes: + return m.OldPskModes(ctx) + case tlsfingerprintprofile.FieldExtensions: + return m.OldExtensions(ctx) + } + return nil, fmt.Errorf("unknown TLSFingerprintProfile field %s", name) +} + +// SetField sets the value of a field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *TLSFingerprintProfileMutation) SetField(name string, value ent.Value) error { + switch name { + case tlsfingerprintprofile.FieldCreatedAt: + v, ok := value.(time.Time) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetCreatedAt(v) + return nil + case tlsfingerprintprofile.FieldUpdatedAt: + v, ok := value.(time.Time) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetUpdatedAt(v) + return nil + case tlsfingerprintprofile.FieldName: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetName(v) + return nil + case tlsfingerprintprofile.FieldDescription: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetDescription(v) + return nil + case tlsfingerprintprofile.FieldEnableGrease: + v, ok := value.(bool) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetEnableGrease(v) + return nil + case tlsfingerprintprofile.FieldCipherSuites: + v, ok := value.([]uint16) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetCipherSuites(v) + return nil + case tlsfingerprintprofile.FieldCurves: + v, ok := value.([]uint16) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetCurves(v) + return nil + case tlsfingerprintprofile.FieldPointFormats: + v, ok := value.([]uint16) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetPointFormats(v) + return nil + case tlsfingerprintprofile.FieldSignatureAlgorithms: + v, ok := value.([]uint16) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetSignatureAlgorithms(v) + return nil + case tlsfingerprintprofile.FieldAlpnProtocols: + v, ok := value.([]string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetAlpnProtocols(v) + return nil + case tlsfingerprintprofile.FieldSupportedVersions: + v, ok := value.([]uint16) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetSupportedVersions(v) + return nil + case tlsfingerprintprofile.FieldKeyShareGroups: + v, ok := value.([]uint16) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetKeyShareGroups(v) + return nil + case tlsfingerprintprofile.FieldPskModes: + v, ok := value.([]uint16) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetPskModes(v) + return nil + case tlsfingerprintprofile.FieldExtensions: + v, ok := value.([]uint16) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetExtensions(v) + return nil + } + return fmt.Errorf("unknown TLSFingerprintProfile field %s", name) +} + +// AddedFields returns all numeric fields that were incremented/decremented during +// this mutation. +func (m *TLSFingerprintProfileMutation) AddedFields() []string { + return nil +} + +// AddedField returns the numeric value that was incremented/decremented on a field +// with the given name. The second boolean return value indicates that this field +// was not set, or was not defined in the schema. +func (m *TLSFingerprintProfileMutation) AddedField(name string) (ent.Value, bool) { + return nil, false +} + +// AddField adds the value to the field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *TLSFingerprintProfileMutation) AddField(name string, value ent.Value) error { + switch name { + } + return fmt.Errorf("unknown TLSFingerprintProfile numeric field %s", name) +} + +// ClearedFields returns all nullable fields that were cleared during this +// mutation. +func (m *TLSFingerprintProfileMutation) ClearedFields() []string { + var fields []string + if m.FieldCleared(tlsfingerprintprofile.FieldDescription) { + fields = append(fields, tlsfingerprintprofile.FieldDescription) + } + if m.FieldCleared(tlsfingerprintprofile.FieldCipherSuites) { + fields = append(fields, tlsfingerprintprofile.FieldCipherSuites) + } + if m.FieldCleared(tlsfingerprintprofile.FieldCurves) { + fields = append(fields, tlsfingerprintprofile.FieldCurves) + } + if m.FieldCleared(tlsfingerprintprofile.FieldPointFormats) { + fields = append(fields, tlsfingerprintprofile.FieldPointFormats) + } + if m.FieldCleared(tlsfingerprintprofile.FieldSignatureAlgorithms) { + fields = append(fields, tlsfingerprintprofile.FieldSignatureAlgorithms) + } + if m.FieldCleared(tlsfingerprintprofile.FieldAlpnProtocols) { + fields = append(fields, tlsfingerprintprofile.FieldAlpnProtocols) + } + if m.FieldCleared(tlsfingerprintprofile.FieldSupportedVersions) { + fields = append(fields, tlsfingerprintprofile.FieldSupportedVersions) + } + if m.FieldCleared(tlsfingerprintprofile.FieldKeyShareGroups) { + fields = append(fields, tlsfingerprintprofile.FieldKeyShareGroups) + } + if m.FieldCleared(tlsfingerprintprofile.FieldPskModes) { + fields = append(fields, tlsfingerprintprofile.FieldPskModes) + } + if m.FieldCleared(tlsfingerprintprofile.FieldExtensions) { + fields = append(fields, tlsfingerprintprofile.FieldExtensions) + } + return fields +} + +// FieldCleared returns a boolean indicating if a field with the given name was +// cleared in this mutation. +func (m *TLSFingerprintProfileMutation) FieldCleared(name string) bool { + _, ok := m.clearedFields[name] + return ok +} + +// ClearField clears the value of the field with the given name. It returns an +// error if the field is not defined in the schema. +func (m *TLSFingerprintProfileMutation) ClearField(name string) error { + switch name { + case tlsfingerprintprofile.FieldDescription: + m.ClearDescription() + return nil + case tlsfingerprintprofile.FieldCipherSuites: + m.ClearCipherSuites() + return nil + case tlsfingerprintprofile.FieldCurves: + m.ClearCurves() + return nil + case tlsfingerprintprofile.FieldPointFormats: + m.ClearPointFormats() + return nil + case tlsfingerprintprofile.FieldSignatureAlgorithms: + m.ClearSignatureAlgorithms() + return nil + case tlsfingerprintprofile.FieldAlpnProtocols: + m.ClearAlpnProtocols() + return nil + case tlsfingerprintprofile.FieldSupportedVersions: + m.ClearSupportedVersions() + return nil + case tlsfingerprintprofile.FieldKeyShareGroups: + m.ClearKeyShareGroups() + return nil + case tlsfingerprintprofile.FieldPskModes: + m.ClearPskModes() + return nil + case tlsfingerprintprofile.FieldExtensions: + m.ClearExtensions() + return nil + } + return fmt.Errorf("unknown TLSFingerprintProfile nullable field %s", name) +} + +// ResetField resets all changes in the mutation for the field with the given name. +// It returns an error if the field is not defined in the schema. +func (m *TLSFingerprintProfileMutation) ResetField(name string) error { + switch name { + case tlsfingerprintprofile.FieldCreatedAt: + m.ResetCreatedAt() + return nil + case tlsfingerprintprofile.FieldUpdatedAt: + m.ResetUpdatedAt() + return nil + case tlsfingerprintprofile.FieldName: + m.ResetName() + return nil + case tlsfingerprintprofile.FieldDescription: + m.ResetDescription() + return nil + case tlsfingerprintprofile.FieldEnableGrease: + m.ResetEnableGrease() + return nil + case tlsfingerprintprofile.FieldCipherSuites: + m.ResetCipherSuites() + return nil + case tlsfingerprintprofile.FieldCurves: + m.ResetCurves() + return nil + case tlsfingerprintprofile.FieldPointFormats: + m.ResetPointFormats() + return nil + case tlsfingerprintprofile.FieldSignatureAlgorithms: + m.ResetSignatureAlgorithms() + return nil + case tlsfingerprintprofile.FieldAlpnProtocols: + m.ResetAlpnProtocols() + return nil + case tlsfingerprintprofile.FieldSupportedVersions: + m.ResetSupportedVersions() + return nil + case tlsfingerprintprofile.FieldKeyShareGroups: + m.ResetKeyShareGroups() + return nil + case tlsfingerprintprofile.FieldPskModes: + m.ResetPskModes() + return nil + case tlsfingerprintprofile.FieldExtensions: + m.ResetExtensions() + return nil + } + return fmt.Errorf("unknown TLSFingerprintProfile field %s", name) +} + +// AddedEdges returns all edge names that were set/added in this mutation. +func (m *TLSFingerprintProfileMutation) AddedEdges() []string { + edges := make([]string, 0, 0) + return edges +} + +// AddedIDs returns all IDs (to other nodes) that were added for the given edge +// name in this mutation. +func (m *TLSFingerprintProfileMutation) AddedIDs(name string) []ent.Value { + return nil +} + +// RemovedEdges returns all edge names that were removed in this mutation. +func (m *TLSFingerprintProfileMutation) RemovedEdges() []string { + edges := make([]string, 0, 0) + return edges +} + +// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with +// the given name in this mutation. +func (m *TLSFingerprintProfileMutation) RemovedIDs(name string) []ent.Value { + return nil +} + +// ClearedEdges returns all edge names that were cleared in this mutation. +func (m *TLSFingerprintProfileMutation) ClearedEdges() []string { + edges := make([]string, 0, 0) + return edges +} + +// EdgeCleared returns a boolean which indicates if the edge with the given name +// was cleared in this mutation. +func (m *TLSFingerprintProfileMutation) EdgeCleared(name string) bool { + return false +} + +// ClearEdge clears the value of the edge with the given name. It returns an error +// if that edge is not defined in the schema. +func (m *TLSFingerprintProfileMutation) ClearEdge(name string) error { + return fmt.Errorf("unknown TLSFingerprintProfile unique edge %s", name) +} + +// ResetEdge resets all changes to the edge with the given name in this mutation. +// It returns an error if the edge is not defined in the schema. +func (m *TLSFingerprintProfileMutation) ResetEdge(name string) error { + return fmt.Errorf("unknown TLSFingerprintProfile edge %s", name) +} + // UsageCleanupTaskMutation represents an operation that mutates the UsageCleanupTask nodes in the graph. type UsageCleanupTaskMutation struct { config diff --git a/backend/ent/predicate/predicate.go b/backend/ent/predicate/predicate.go index 89d933fc..a652ab3f 100644 --- a/backend/ent/predicate/predicate.go +++ b/backend/ent/predicate/predicate.go @@ -48,6 +48,9 @@ type SecuritySecret func(*sql.Selector) // Setting is the predicate function for setting builders. type Setting func(*sql.Selector) +// TLSFingerprintProfile is the predicate function for tlsfingerprintprofile builders. +type TLSFingerprintProfile func(*sql.Selector) + // UsageCleanupTask is the predicate function for usagecleanuptask builders. type UsageCleanupTask func(*sql.Selector) diff --git a/backend/ent/runtime/runtime.go b/backend/ent/runtime/runtime.go index 19c58d76..ca95f13f 100644 --- a/backend/ent/runtime/runtime.go +++ b/backend/ent/runtime/runtime.go @@ -20,6 +20,7 @@ import ( "github.com/Wei-Shaw/sub2api/ent/schema" "github.com/Wei-Shaw/sub2api/ent/securitysecret" "github.com/Wei-Shaw/sub2api/ent/setting" + "github.com/Wei-Shaw/sub2api/ent/tlsfingerprintprofile" "github.com/Wei-Shaw/sub2api/ent/usagecleanuptask" "github.com/Wei-Shaw/sub2api/ent/usagelog" "github.com/Wei-Shaw/sub2api/ent/user" @@ -746,6 +747,43 @@ func init() { setting.DefaultUpdatedAt = settingDescUpdatedAt.Default.(func() time.Time) // setting.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. setting.UpdateDefaultUpdatedAt = settingDescUpdatedAt.UpdateDefault.(func() time.Time) + tlsfingerprintprofileMixin := schema.TLSFingerprintProfile{}.Mixin() + tlsfingerprintprofileMixinFields0 := tlsfingerprintprofileMixin[0].Fields() + _ = tlsfingerprintprofileMixinFields0 + tlsfingerprintprofileFields := schema.TLSFingerprintProfile{}.Fields() + _ = tlsfingerprintprofileFields + // tlsfingerprintprofileDescCreatedAt is the schema descriptor for created_at field. + tlsfingerprintprofileDescCreatedAt := tlsfingerprintprofileMixinFields0[0].Descriptor() + // tlsfingerprintprofile.DefaultCreatedAt holds the default value on creation for the created_at field. + tlsfingerprintprofile.DefaultCreatedAt = tlsfingerprintprofileDescCreatedAt.Default.(func() time.Time) + // tlsfingerprintprofileDescUpdatedAt is the schema descriptor for updated_at field. + tlsfingerprintprofileDescUpdatedAt := tlsfingerprintprofileMixinFields0[1].Descriptor() + // tlsfingerprintprofile.DefaultUpdatedAt holds the default value on creation for the updated_at field. + tlsfingerprintprofile.DefaultUpdatedAt = tlsfingerprintprofileDescUpdatedAt.Default.(func() time.Time) + // tlsfingerprintprofile.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. + tlsfingerprintprofile.UpdateDefaultUpdatedAt = tlsfingerprintprofileDescUpdatedAt.UpdateDefault.(func() time.Time) + // tlsfingerprintprofileDescName is the schema descriptor for name field. + tlsfingerprintprofileDescName := tlsfingerprintprofileFields[0].Descriptor() + // tlsfingerprintprofile.NameValidator is a validator for the "name" field. It is called by the builders before save. + tlsfingerprintprofile.NameValidator = func() func(string) error { + validators := tlsfingerprintprofileDescName.Validators + fns := [...]func(string) error{ + validators[0].(func(string) error), + validators[1].(func(string) error), + } + return func(name string) error { + for _, fn := range fns { + if err := fn(name); err != nil { + return err + } + } + return nil + } + }() + // tlsfingerprintprofileDescEnableGrease is the schema descriptor for enable_grease field. + tlsfingerprintprofileDescEnableGrease := tlsfingerprintprofileFields[2].Descriptor() + // tlsfingerprintprofile.DefaultEnableGrease holds the default value on creation for the enable_grease field. + tlsfingerprintprofile.DefaultEnableGrease = tlsfingerprintprofileDescEnableGrease.Default.(bool) usagecleanuptaskMixin := schema.UsageCleanupTask{}.Mixin() usagecleanuptaskMixinFields0 := usagecleanuptaskMixin[0].Fields() _ = usagecleanuptaskMixinFields0 diff --git a/backend/ent/schema/tls_fingerprint_profile.go b/backend/ent/schema/tls_fingerprint_profile.go new file mode 100644 index 00000000..86856d05 --- /dev/null +++ b/backend/ent/schema/tls_fingerprint_profile.go @@ -0,0 +1,100 @@ +// Package schema 定义 Ent ORM 的数据库 schema。 +package schema + +import ( + "github.com/Wei-Shaw/sub2api/ent/schema/mixins" + + "entgo.io/ent" + "entgo.io/ent/dialect" + "entgo.io/ent/dialect/entsql" + "entgo.io/ent/schema" + "entgo.io/ent/schema/field" +) + +// TLSFingerprintProfile 定义 TLS 指纹配置模板的 schema。 +// +// TLS 指纹模板用于模拟特定客户端(如 Claude Code / Node.js)的 TLS 握手特征。 +// 每个模板包含完整的 ClientHello 参数:加密套件、曲线、扩展等。 +// 通过 Account.Extra.tls_fingerprint_profile_id 绑定到具体账号。 +type TLSFingerprintProfile struct { + ent.Schema +} + +// Annotations 返回 schema 的注解配置。 +func (TLSFingerprintProfile) Annotations() []schema.Annotation { + return []schema.Annotation{ + entsql.Annotation{Table: "tls_fingerprint_profiles"}, + } +} + +// Mixin 返回该 schema 使用的混入组件。 +func (TLSFingerprintProfile) Mixin() []ent.Mixin { + return []ent.Mixin{ + mixins.TimeMixin{}, + } +} + +// Fields 定义 TLS 指纹模板实体的所有字段。 +func (TLSFingerprintProfile) Fields() []ent.Field { + return []ent.Field{ + // name: 模板名称,唯一标识 + field.String("name"). + MaxLen(100). + NotEmpty(). + Unique(), + + // description: 模板描述 + field.Text("description"). + Optional(). + Nillable(), + + // enable_grease: 是否启用 GREASE 扩展(Chrome 使用,Node.js 不使用) + field.Bool("enable_grease"). + Default(false), + + // cipher_suites: TLS 加密套件列表(顺序敏感,影响 JA3) + field.JSON("cipher_suites", []uint16{}). + Optional(). + SchemaType(map[string]string{dialect.Postgres: "jsonb"}), + + // curves: 椭圆曲线/支持的组列表 + field.JSON("curves", []uint16{}). + Optional(). + SchemaType(map[string]string{dialect.Postgres: "jsonb"}), + + // point_formats: EC 点格式列表 + field.JSON("point_formats", []uint16{}). + Optional(). + SchemaType(map[string]string{dialect.Postgres: "jsonb"}), + + // signature_algorithms: 签名算法列表 + field.JSON("signature_algorithms", []uint16{}). + Optional(). + SchemaType(map[string]string{dialect.Postgres: "jsonb"}), + + // alpn_protocols: ALPN 协议列表(如 ["http/1.1"]) + field.JSON("alpn_protocols", []string{}). + Optional(). + SchemaType(map[string]string{dialect.Postgres: "jsonb"}), + + // supported_versions: 支持的 TLS 版本列表(如 [0x0304, 0x0303]) + field.JSON("supported_versions", []uint16{}). + Optional(). + SchemaType(map[string]string{dialect.Postgres: "jsonb"}), + + // key_share_groups: Key Share 中发送的曲线组(如 [29] 即 X25519) + field.JSON("key_share_groups", []uint16{}). + Optional(). + SchemaType(map[string]string{dialect.Postgres: "jsonb"}), + + // psk_modes: PSK 密钥交换模式(如 [1] 即 psk_dhe_ke) + field.JSON("psk_modes", []uint16{}). + Optional(). + SchemaType(map[string]string{dialect.Postgres: "jsonb"}), + + // extensions: TLS 扩展类型 ID 列表,按发送顺序排列 + field.JSON("extensions", []uint16{}). + Optional(). + SchemaType(map[string]string{dialect.Postgres: "jsonb"}), + } +} diff --git a/backend/ent/tlsfingerprintprofile.go b/backend/ent/tlsfingerprintprofile.go new file mode 100644 index 00000000..c9455609 --- /dev/null +++ b/backend/ent/tlsfingerprintprofile.go @@ -0,0 +1,275 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "encoding/json" + "fmt" + "strings" + "time" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "github.com/Wei-Shaw/sub2api/ent/tlsfingerprintprofile" +) + +// TLSFingerprintProfile is the model entity for the TLSFingerprintProfile schema. +type TLSFingerprintProfile struct { + config `json:"-"` + // ID of the ent. + ID int64 `json:"id,omitempty"` + // CreatedAt holds the value of the "created_at" field. + CreatedAt time.Time `json:"created_at,omitempty"` + // UpdatedAt holds the value of the "updated_at" field. + UpdatedAt time.Time `json:"updated_at,omitempty"` + // Name holds the value of the "name" field. + Name string `json:"name,omitempty"` + // Description holds the value of the "description" field. + Description *string `json:"description,omitempty"` + // EnableGrease holds the value of the "enable_grease" field. + EnableGrease bool `json:"enable_grease,omitempty"` + // CipherSuites holds the value of the "cipher_suites" field. + CipherSuites []uint16 `json:"cipher_suites,omitempty"` + // Curves holds the value of the "curves" field. + Curves []uint16 `json:"curves,omitempty"` + // PointFormats holds the value of the "point_formats" field. + PointFormats []uint16 `json:"point_formats,omitempty"` + // SignatureAlgorithms holds the value of the "signature_algorithms" field. + SignatureAlgorithms []uint16 `json:"signature_algorithms,omitempty"` + // AlpnProtocols holds the value of the "alpn_protocols" field. + AlpnProtocols []string `json:"alpn_protocols,omitempty"` + // SupportedVersions holds the value of the "supported_versions" field. + SupportedVersions []uint16 `json:"supported_versions,omitempty"` + // KeyShareGroups holds the value of the "key_share_groups" field. + KeyShareGroups []uint16 `json:"key_share_groups,omitempty"` + // PskModes holds the value of the "psk_modes" field. + PskModes []uint16 `json:"psk_modes,omitempty"` + // Extensions holds the value of the "extensions" field. + Extensions []uint16 `json:"extensions,omitempty"` + selectValues sql.SelectValues +} + +// scanValues returns the types for scanning values from sql.Rows. +func (*TLSFingerprintProfile) scanValues(columns []string) ([]any, error) { + values := make([]any, len(columns)) + for i := range columns { + switch columns[i] { + case tlsfingerprintprofile.FieldCipherSuites, tlsfingerprintprofile.FieldCurves, tlsfingerprintprofile.FieldPointFormats, tlsfingerprintprofile.FieldSignatureAlgorithms, tlsfingerprintprofile.FieldAlpnProtocols, tlsfingerprintprofile.FieldSupportedVersions, tlsfingerprintprofile.FieldKeyShareGroups, tlsfingerprintprofile.FieldPskModes, tlsfingerprintprofile.FieldExtensions: + values[i] = new([]byte) + case tlsfingerprintprofile.FieldEnableGrease: + values[i] = new(sql.NullBool) + case tlsfingerprintprofile.FieldID: + values[i] = new(sql.NullInt64) + case tlsfingerprintprofile.FieldName, tlsfingerprintprofile.FieldDescription: + values[i] = new(sql.NullString) + case tlsfingerprintprofile.FieldCreatedAt, tlsfingerprintprofile.FieldUpdatedAt: + values[i] = new(sql.NullTime) + default: + values[i] = new(sql.UnknownType) + } + } + return values, nil +} + +// assignValues assigns the values that were returned from sql.Rows (after scanning) +// to the TLSFingerprintProfile fields. +func (_m *TLSFingerprintProfile) assignValues(columns []string, values []any) error { + if m, n := len(values), len(columns); m < n { + return fmt.Errorf("mismatch number of scan values: %d != %d", m, n) + } + for i := range columns { + switch columns[i] { + case tlsfingerprintprofile.FieldID: + value, ok := values[i].(*sql.NullInt64) + if !ok { + return fmt.Errorf("unexpected type %T for field id", value) + } + _m.ID = int64(value.Int64) + case tlsfingerprintprofile.FieldCreatedAt: + if value, ok := values[i].(*sql.NullTime); !ok { + return fmt.Errorf("unexpected type %T for field created_at", values[i]) + } else if value.Valid { + _m.CreatedAt = value.Time + } + case tlsfingerprintprofile.FieldUpdatedAt: + if value, ok := values[i].(*sql.NullTime); !ok { + return fmt.Errorf("unexpected type %T for field updated_at", values[i]) + } else if value.Valid { + _m.UpdatedAt = value.Time + } + case tlsfingerprintprofile.FieldName: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field name", values[i]) + } else if value.Valid { + _m.Name = value.String + } + case tlsfingerprintprofile.FieldDescription: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field description", values[i]) + } else if value.Valid { + _m.Description = new(string) + *_m.Description = value.String + } + case tlsfingerprintprofile.FieldEnableGrease: + if value, ok := values[i].(*sql.NullBool); !ok { + return fmt.Errorf("unexpected type %T for field enable_grease", values[i]) + } else if value.Valid { + _m.EnableGrease = value.Bool + } + case tlsfingerprintprofile.FieldCipherSuites: + if value, ok := values[i].(*[]byte); !ok { + return fmt.Errorf("unexpected type %T for field cipher_suites", values[i]) + } else if value != nil && len(*value) > 0 { + if err := json.Unmarshal(*value, &_m.CipherSuites); err != nil { + return fmt.Errorf("unmarshal field cipher_suites: %w", err) + } + } + case tlsfingerprintprofile.FieldCurves: + if value, ok := values[i].(*[]byte); !ok { + return fmt.Errorf("unexpected type %T for field curves", values[i]) + } else if value != nil && len(*value) > 0 { + if err := json.Unmarshal(*value, &_m.Curves); err != nil { + return fmt.Errorf("unmarshal field curves: %w", err) + } + } + case tlsfingerprintprofile.FieldPointFormats: + if value, ok := values[i].(*[]byte); !ok { + return fmt.Errorf("unexpected type %T for field point_formats", values[i]) + } else if value != nil && len(*value) > 0 { + if err := json.Unmarshal(*value, &_m.PointFormats); err != nil { + return fmt.Errorf("unmarshal field point_formats: %w", err) + } + } + case tlsfingerprintprofile.FieldSignatureAlgorithms: + if value, ok := values[i].(*[]byte); !ok { + return fmt.Errorf("unexpected type %T for field signature_algorithms", values[i]) + } else if value != nil && len(*value) > 0 { + if err := json.Unmarshal(*value, &_m.SignatureAlgorithms); err != nil { + return fmt.Errorf("unmarshal field signature_algorithms: %w", err) + } + } + case tlsfingerprintprofile.FieldAlpnProtocols: + if value, ok := values[i].(*[]byte); !ok { + return fmt.Errorf("unexpected type %T for field alpn_protocols", values[i]) + } else if value != nil && len(*value) > 0 { + if err := json.Unmarshal(*value, &_m.AlpnProtocols); err != nil { + return fmt.Errorf("unmarshal field alpn_protocols: %w", err) + } + } + case tlsfingerprintprofile.FieldSupportedVersions: + if value, ok := values[i].(*[]byte); !ok { + return fmt.Errorf("unexpected type %T for field supported_versions", values[i]) + } else if value != nil && len(*value) > 0 { + if err := json.Unmarshal(*value, &_m.SupportedVersions); err != nil { + return fmt.Errorf("unmarshal field supported_versions: %w", err) + } + } + case tlsfingerprintprofile.FieldKeyShareGroups: + if value, ok := values[i].(*[]byte); !ok { + return fmt.Errorf("unexpected type %T for field key_share_groups", values[i]) + } else if value != nil && len(*value) > 0 { + if err := json.Unmarshal(*value, &_m.KeyShareGroups); err != nil { + return fmt.Errorf("unmarshal field key_share_groups: %w", err) + } + } + case tlsfingerprintprofile.FieldPskModes: + if value, ok := values[i].(*[]byte); !ok { + return fmt.Errorf("unexpected type %T for field psk_modes", values[i]) + } else if value != nil && len(*value) > 0 { + if err := json.Unmarshal(*value, &_m.PskModes); err != nil { + return fmt.Errorf("unmarshal field psk_modes: %w", err) + } + } + case tlsfingerprintprofile.FieldExtensions: + if value, ok := values[i].(*[]byte); !ok { + return fmt.Errorf("unexpected type %T for field extensions", values[i]) + } else if value != nil && len(*value) > 0 { + if err := json.Unmarshal(*value, &_m.Extensions); err != nil { + return fmt.Errorf("unmarshal field extensions: %w", err) + } + } + default: + _m.selectValues.Set(columns[i], values[i]) + } + } + return nil +} + +// Value returns the ent.Value that was dynamically selected and assigned to the TLSFingerprintProfile. +// This includes values selected through modifiers, order, etc. +func (_m *TLSFingerprintProfile) Value(name string) (ent.Value, error) { + return _m.selectValues.Get(name) +} + +// Update returns a builder for updating this TLSFingerprintProfile. +// Note that you need to call TLSFingerprintProfile.Unwrap() before calling this method if this TLSFingerprintProfile +// was returned from a transaction, and the transaction was committed or rolled back. +func (_m *TLSFingerprintProfile) Update() *TLSFingerprintProfileUpdateOne { + return NewTLSFingerprintProfileClient(_m.config).UpdateOne(_m) +} + +// Unwrap unwraps the TLSFingerprintProfile entity that was returned from a transaction after it was closed, +// so that all future queries will be executed through the driver which created the transaction. +func (_m *TLSFingerprintProfile) Unwrap() *TLSFingerprintProfile { + _tx, ok := _m.config.driver.(*txDriver) + if !ok { + panic("ent: TLSFingerprintProfile is not a transactional entity") + } + _m.config.driver = _tx.drv + return _m +} + +// String implements the fmt.Stringer. +func (_m *TLSFingerprintProfile) String() string { + var builder strings.Builder + builder.WriteString("TLSFingerprintProfile(") + builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID)) + builder.WriteString("created_at=") + builder.WriteString(_m.CreatedAt.Format(time.ANSIC)) + builder.WriteString(", ") + builder.WriteString("updated_at=") + builder.WriteString(_m.UpdatedAt.Format(time.ANSIC)) + builder.WriteString(", ") + builder.WriteString("name=") + builder.WriteString(_m.Name) + builder.WriteString(", ") + if v := _m.Description; v != nil { + builder.WriteString("description=") + builder.WriteString(*v) + } + builder.WriteString(", ") + builder.WriteString("enable_grease=") + builder.WriteString(fmt.Sprintf("%v", _m.EnableGrease)) + builder.WriteString(", ") + builder.WriteString("cipher_suites=") + builder.WriteString(fmt.Sprintf("%v", _m.CipherSuites)) + builder.WriteString(", ") + builder.WriteString("curves=") + builder.WriteString(fmt.Sprintf("%v", _m.Curves)) + builder.WriteString(", ") + builder.WriteString("point_formats=") + builder.WriteString(fmt.Sprintf("%v", _m.PointFormats)) + builder.WriteString(", ") + builder.WriteString("signature_algorithms=") + builder.WriteString(fmt.Sprintf("%v", _m.SignatureAlgorithms)) + builder.WriteString(", ") + builder.WriteString("alpn_protocols=") + builder.WriteString(fmt.Sprintf("%v", _m.AlpnProtocols)) + builder.WriteString(", ") + builder.WriteString("supported_versions=") + builder.WriteString(fmt.Sprintf("%v", _m.SupportedVersions)) + builder.WriteString(", ") + builder.WriteString("key_share_groups=") + builder.WriteString(fmt.Sprintf("%v", _m.KeyShareGroups)) + builder.WriteString(", ") + builder.WriteString("psk_modes=") + builder.WriteString(fmt.Sprintf("%v", _m.PskModes)) + builder.WriteString(", ") + builder.WriteString("extensions=") + builder.WriteString(fmt.Sprintf("%v", _m.Extensions)) + builder.WriteByte(')') + return builder.String() +} + +// TLSFingerprintProfiles is a parsable slice of TLSFingerprintProfile. +type TLSFingerprintProfiles []*TLSFingerprintProfile diff --git a/backend/ent/tlsfingerprintprofile/tlsfingerprintprofile.go b/backend/ent/tlsfingerprintprofile/tlsfingerprintprofile.go new file mode 100644 index 00000000..49426d36 --- /dev/null +++ b/backend/ent/tlsfingerprintprofile/tlsfingerprintprofile.go @@ -0,0 +1,121 @@ +// Code generated by ent, DO NOT EDIT. + +package tlsfingerprintprofile + +import ( + "time" + + "entgo.io/ent/dialect/sql" +) + +const ( + // Label holds the string label denoting the tlsfingerprintprofile type in the database. + Label = "tls_fingerprint_profile" + // FieldID holds the string denoting the id field in the database. + FieldID = "id" + // FieldCreatedAt holds the string denoting the created_at field in the database. + FieldCreatedAt = "created_at" + // FieldUpdatedAt holds the string denoting the updated_at field in the database. + FieldUpdatedAt = "updated_at" + // FieldName holds the string denoting the name field in the database. + FieldName = "name" + // FieldDescription holds the string denoting the description field in the database. + FieldDescription = "description" + // FieldEnableGrease holds the string denoting the enable_grease field in the database. + FieldEnableGrease = "enable_grease" + // FieldCipherSuites holds the string denoting the cipher_suites field in the database. + FieldCipherSuites = "cipher_suites" + // FieldCurves holds the string denoting the curves field in the database. + FieldCurves = "curves" + // FieldPointFormats holds the string denoting the point_formats field in the database. + FieldPointFormats = "point_formats" + // FieldSignatureAlgorithms holds the string denoting the signature_algorithms field in the database. + FieldSignatureAlgorithms = "signature_algorithms" + // FieldAlpnProtocols holds the string denoting the alpn_protocols field in the database. + FieldAlpnProtocols = "alpn_protocols" + // FieldSupportedVersions holds the string denoting the supported_versions field in the database. + FieldSupportedVersions = "supported_versions" + // FieldKeyShareGroups holds the string denoting the key_share_groups field in the database. + FieldKeyShareGroups = "key_share_groups" + // FieldPskModes holds the string denoting the psk_modes field in the database. + FieldPskModes = "psk_modes" + // FieldExtensions holds the string denoting the extensions field in the database. + FieldExtensions = "extensions" + // Table holds the table name of the tlsfingerprintprofile in the database. + Table = "tls_fingerprint_profiles" +) + +// Columns holds all SQL columns for tlsfingerprintprofile fields. +var Columns = []string{ + FieldID, + FieldCreatedAt, + FieldUpdatedAt, + FieldName, + FieldDescription, + FieldEnableGrease, + FieldCipherSuites, + FieldCurves, + FieldPointFormats, + FieldSignatureAlgorithms, + FieldAlpnProtocols, + FieldSupportedVersions, + FieldKeyShareGroups, + FieldPskModes, + FieldExtensions, +} + +// ValidColumn reports if the column name is valid (part of the table columns). +func ValidColumn(column string) bool { + for i := range Columns { + if column == Columns[i] { + return true + } + } + return false +} + +var ( + // DefaultCreatedAt holds the default value on creation for the "created_at" field. + DefaultCreatedAt func() time.Time + // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. + DefaultUpdatedAt func() time.Time + // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. + UpdateDefaultUpdatedAt func() time.Time + // NameValidator is a validator for the "name" field. It is called by the builders before save. + NameValidator func(string) error + // DefaultEnableGrease holds the default value on creation for the "enable_grease" field. + DefaultEnableGrease bool +) + +// OrderOption defines the ordering options for the TLSFingerprintProfile queries. +type OrderOption func(*sql.Selector) + +// ByID orders the results by the id field. +func ByID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldID, opts...).ToFunc() +} + +// ByCreatedAt orders the results by the created_at field. +func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldCreatedAt, opts...).ToFunc() +} + +// ByUpdatedAt orders the results by the updated_at field. +func ByUpdatedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldUpdatedAt, opts...).ToFunc() +} + +// ByName orders the results by the name field. +func ByName(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldName, opts...).ToFunc() +} + +// ByDescription orders the results by the description field. +func ByDescription(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldDescription, opts...).ToFunc() +} + +// ByEnableGrease orders the results by the enable_grease field. +func ByEnableGrease(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldEnableGrease, opts...).ToFunc() +} diff --git a/backend/ent/tlsfingerprintprofile/where.go b/backend/ent/tlsfingerprintprofile/where.go new file mode 100644 index 00000000..f7d1ba27 --- /dev/null +++ b/backend/ent/tlsfingerprintprofile/where.go @@ -0,0 +1,415 @@ +// Code generated by ent, DO NOT EDIT. + +package tlsfingerprintprofile + +import ( + "time" + + "entgo.io/ent/dialect/sql" + "github.com/Wei-Shaw/sub2api/ent/predicate" +) + +// ID filters vertices based on their ID field. +func ID(id int64) predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldEQ(FieldID, id)) +} + +// IDEQ applies the EQ predicate on the ID field. +func IDEQ(id int64) predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldEQ(FieldID, id)) +} + +// IDNEQ applies the NEQ predicate on the ID field. +func IDNEQ(id int64) predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldNEQ(FieldID, id)) +} + +// IDIn applies the In predicate on the ID field. +func IDIn(ids ...int64) predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldIn(FieldID, ids...)) +} + +// IDNotIn applies the NotIn predicate on the ID field. +func IDNotIn(ids ...int64) predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldNotIn(FieldID, ids...)) +} + +// IDGT applies the GT predicate on the ID field. +func IDGT(id int64) predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldGT(FieldID, id)) +} + +// IDGTE applies the GTE predicate on the ID field. +func IDGTE(id int64) predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldGTE(FieldID, id)) +} + +// IDLT applies the LT predicate on the ID field. +func IDLT(id int64) predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldLT(FieldID, id)) +} + +// IDLTE applies the LTE predicate on the ID field. +func IDLTE(id int64) predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldLTE(FieldID, id)) +} + +// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. +func CreatedAt(v time.Time) predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldEQ(FieldCreatedAt, v)) +} + +// UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ. +func UpdatedAt(v time.Time) predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// Name applies equality check predicate on the "name" field. It's identical to NameEQ. +func Name(v string) predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldEQ(FieldName, v)) +} + +// Description applies equality check predicate on the "description" field. It's identical to DescriptionEQ. +func Description(v string) predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldEQ(FieldDescription, v)) +} + +// EnableGrease applies equality check predicate on the "enable_grease" field. It's identical to EnableGreaseEQ. +func EnableGrease(v bool) predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldEQ(FieldEnableGrease, v)) +} + +// CreatedAtEQ applies the EQ predicate on the "created_at" field. +func CreatedAtEQ(v time.Time) predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldEQ(FieldCreatedAt, v)) +} + +// CreatedAtNEQ applies the NEQ predicate on the "created_at" field. +func CreatedAtNEQ(v time.Time) predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldNEQ(FieldCreatedAt, v)) +} + +// CreatedAtIn applies the In predicate on the "created_at" field. +func CreatedAtIn(vs ...time.Time) predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldIn(FieldCreatedAt, vs...)) +} + +// CreatedAtNotIn applies the NotIn predicate on the "created_at" field. +func CreatedAtNotIn(vs ...time.Time) predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldNotIn(FieldCreatedAt, vs...)) +} + +// CreatedAtGT applies the GT predicate on the "created_at" field. +func CreatedAtGT(v time.Time) predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldGT(FieldCreatedAt, v)) +} + +// CreatedAtGTE applies the GTE predicate on the "created_at" field. +func CreatedAtGTE(v time.Time) predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldGTE(FieldCreatedAt, v)) +} + +// CreatedAtLT applies the LT predicate on the "created_at" field. +func CreatedAtLT(v time.Time) predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldLT(FieldCreatedAt, v)) +} + +// CreatedAtLTE applies the LTE predicate on the "created_at" field. +func CreatedAtLTE(v time.Time) predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldLTE(FieldCreatedAt, v)) +} + +// UpdatedAtEQ applies the EQ predicate on the "updated_at" field. +func UpdatedAtEQ(v time.Time) predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field. +func UpdatedAtNEQ(v time.Time) predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldNEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtIn applies the In predicate on the "updated_at" field. +func UpdatedAtIn(vs ...time.Time) predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field. +func UpdatedAtNotIn(vs ...time.Time) predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldNotIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtGT applies the GT predicate on the "updated_at" field. +func UpdatedAtGT(v time.Time) predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldGT(FieldUpdatedAt, v)) +} + +// UpdatedAtGTE applies the GTE predicate on the "updated_at" field. +func UpdatedAtGTE(v time.Time) predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldGTE(FieldUpdatedAt, v)) +} + +// UpdatedAtLT applies the LT predicate on the "updated_at" field. +func UpdatedAtLT(v time.Time) predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldLT(FieldUpdatedAt, v)) +} + +// UpdatedAtLTE applies the LTE predicate on the "updated_at" field. +func UpdatedAtLTE(v time.Time) predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldLTE(FieldUpdatedAt, v)) +} + +// NameEQ applies the EQ predicate on the "name" field. +func NameEQ(v string) predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldEQ(FieldName, v)) +} + +// NameNEQ applies the NEQ predicate on the "name" field. +func NameNEQ(v string) predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldNEQ(FieldName, v)) +} + +// NameIn applies the In predicate on the "name" field. +func NameIn(vs ...string) predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldIn(FieldName, vs...)) +} + +// NameNotIn applies the NotIn predicate on the "name" field. +func NameNotIn(vs ...string) predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldNotIn(FieldName, vs...)) +} + +// NameGT applies the GT predicate on the "name" field. +func NameGT(v string) predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldGT(FieldName, v)) +} + +// NameGTE applies the GTE predicate on the "name" field. +func NameGTE(v string) predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldGTE(FieldName, v)) +} + +// NameLT applies the LT predicate on the "name" field. +func NameLT(v string) predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldLT(FieldName, v)) +} + +// NameLTE applies the LTE predicate on the "name" field. +func NameLTE(v string) predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldLTE(FieldName, v)) +} + +// NameContains applies the Contains predicate on the "name" field. +func NameContains(v string) predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldContains(FieldName, v)) +} + +// NameHasPrefix applies the HasPrefix predicate on the "name" field. +func NameHasPrefix(v string) predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldHasPrefix(FieldName, v)) +} + +// NameHasSuffix applies the HasSuffix predicate on the "name" field. +func NameHasSuffix(v string) predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldHasSuffix(FieldName, v)) +} + +// NameEqualFold applies the EqualFold predicate on the "name" field. +func NameEqualFold(v string) predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldEqualFold(FieldName, v)) +} + +// NameContainsFold applies the ContainsFold predicate on the "name" field. +func NameContainsFold(v string) predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldContainsFold(FieldName, v)) +} + +// DescriptionEQ applies the EQ predicate on the "description" field. +func DescriptionEQ(v string) predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldEQ(FieldDescription, v)) +} + +// DescriptionNEQ applies the NEQ predicate on the "description" field. +func DescriptionNEQ(v string) predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldNEQ(FieldDescription, v)) +} + +// DescriptionIn applies the In predicate on the "description" field. +func DescriptionIn(vs ...string) predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldIn(FieldDescription, vs...)) +} + +// DescriptionNotIn applies the NotIn predicate on the "description" field. +func DescriptionNotIn(vs ...string) predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldNotIn(FieldDescription, vs...)) +} + +// DescriptionGT applies the GT predicate on the "description" field. +func DescriptionGT(v string) predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldGT(FieldDescription, v)) +} + +// DescriptionGTE applies the GTE predicate on the "description" field. +func DescriptionGTE(v string) predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldGTE(FieldDescription, v)) +} + +// DescriptionLT applies the LT predicate on the "description" field. +func DescriptionLT(v string) predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldLT(FieldDescription, v)) +} + +// DescriptionLTE applies the LTE predicate on the "description" field. +func DescriptionLTE(v string) predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldLTE(FieldDescription, v)) +} + +// DescriptionContains applies the Contains predicate on the "description" field. +func DescriptionContains(v string) predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldContains(FieldDescription, v)) +} + +// DescriptionHasPrefix applies the HasPrefix predicate on the "description" field. +func DescriptionHasPrefix(v string) predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldHasPrefix(FieldDescription, v)) +} + +// DescriptionHasSuffix applies the HasSuffix predicate on the "description" field. +func DescriptionHasSuffix(v string) predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldHasSuffix(FieldDescription, v)) +} + +// DescriptionIsNil applies the IsNil predicate on the "description" field. +func DescriptionIsNil() predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldIsNull(FieldDescription)) +} + +// DescriptionNotNil applies the NotNil predicate on the "description" field. +func DescriptionNotNil() predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldNotNull(FieldDescription)) +} + +// DescriptionEqualFold applies the EqualFold predicate on the "description" field. +func DescriptionEqualFold(v string) predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldEqualFold(FieldDescription, v)) +} + +// DescriptionContainsFold applies the ContainsFold predicate on the "description" field. +func DescriptionContainsFold(v string) predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldContainsFold(FieldDescription, v)) +} + +// EnableGreaseEQ applies the EQ predicate on the "enable_grease" field. +func EnableGreaseEQ(v bool) predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldEQ(FieldEnableGrease, v)) +} + +// EnableGreaseNEQ applies the NEQ predicate on the "enable_grease" field. +func EnableGreaseNEQ(v bool) predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldNEQ(FieldEnableGrease, v)) +} + +// CipherSuitesIsNil applies the IsNil predicate on the "cipher_suites" field. +func CipherSuitesIsNil() predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldIsNull(FieldCipherSuites)) +} + +// CipherSuitesNotNil applies the NotNil predicate on the "cipher_suites" field. +func CipherSuitesNotNil() predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldNotNull(FieldCipherSuites)) +} + +// CurvesIsNil applies the IsNil predicate on the "curves" field. +func CurvesIsNil() predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldIsNull(FieldCurves)) +} + +// CurvesNotNil applies the NotNil predicate on the "curves" field. +func CurvesNotNil() predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldNotNull(FieldCurves)) +} + +// PointFormatsIsNil applies the IsNil predicate on the "point_formats" field. +func PointFormatsIsNil() predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldIsNull(FieldPointFormats)) +} + +// PointFormatsNotNil applies the NotNil predicate on the "point_formats" field. +func PointFormatsNotNil() predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldNotNull(FieldPointFormats)) +} + +// SignatureAlgorithmsIsNil applies the IsNil predicate on the "signature_algorithms" field. +func SignatureAlgorithmsIsNil() predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldIsNull(FieldSignatureAlgorithms)) +} + +// SignatureAlgorithmsNotNil applies the NotNil predicate on the "signature_algorithms" field. +func SignatureAlgorithmsNotNil() predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldNotNull(FieldSignatureAlgorithms)) +} + +// AlpnProtocolsIsNil applies the IsNil predicate on the "alpn_protocols" field. +func AlpnProtocolsIsNil() predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldIsNull(FieldAlpnProtocols)) +} + +// AlpnProtocolsNotNil applies the NotNil predicate on the "alpn_protocols" field. +func AlpnProtocolsNotNil() predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldNotNull(FieldAlpnProtocols)) +} + +// SupportedVersionsIsNil applies the IsNil predicate on the "supported_versions" field. +func SupportedVersionsIsNil() predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldIsNull(FieldSupportedVersions)) +} + +// SupportedVersionsNotNil applies the NotNil predicate on the "supported_versions" field. +func SupportedVersionsNotNil() predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldNotNull(FieldSupportedVersions)) +} + +// KeyShareGroupsIsNil applies the IsNil predicate on the "key_share_groups" field. +func KeyShareGroupsIsNil() predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldIsNull(FieldKeyShareGroups)) +} + +// KeyShareGroupsNotNil applies the NotNil predicate on the "key_share_groups" field. +func KeyShareGroupsNotNil() predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldNotNull(FieldKeyShareGroups)) +} + +// PskModesIsNil applies the IsNil predicate on the "psk_modes" field. +func PskModesIsNil() predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldIsNull(FieldPskModes)) +} + +// PskModesNotNil applies the NotNil predicate on the "psk_modes" field. +func PskModesNotNil() predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldNotNull(FieldPskModes)) +} + +// ExtensionsIsNil applies the IsNil predicate on the "extensions" field. +func ExtensionsIsNil() predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldIsNull(FieldExtensions)) +} + +// ExtensionsNotNil applies the NotNil predicate on the "extensions" field. +func ExtensionsNotNil() predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.FieldNotNull(FieldExtensions)) +} + +// And groups predicates with the AND operator between them. +func And(predicates ...predicate.TLSFingerprintProfile) predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.AndPredicates(predicates...)) +} + +// Or groups predicates with the OR operator between them. +func Or(predicates ...predicate.TLSFingerprintProfile) predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.OrPredicates(predicates...)) +} + +// Not applies the not operator on the given predicate. +func Not(p predicate.TLSFingerprintProfile) predicate.TLSFingerprintProfile { + return predicate.TLSFingerprintProfile(sql.NotPredicates(p)) +} diff --git a/backend/ent/tlsfingerprintprofile_create.go b/backend/ent/tlsfingerprintprofile_create.go new file mode 100644 index 00000000..70a5e6be --- /dev/null +++ b/backend/ent/tlsfingerprintprofile_create.go @@ -0,0 +1,1341 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + "time" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/Wei-Shaw/sub2api/ent/tlsfingerprintprofile" +) + +// TLSFingerprintProfileCreate is the builder for creating a TLSFingerprintProfile entity. +type TLSFingerprintProfileCreate struct { + config + mutation *TLSFingerprintProfileMutation + hooks []Hook + conflict []sql.ConflictOption +} + +// SetCreatedAt sets the "created_at" field. +func (_c *TLSFingerprintProfileCreate) SetCreatedAt(v time.Time) *TLSFingerprintProfileCreate { + _c.mutation.SetCreatedAt(v) + return _c +} + +// SetNillableCreatedAt sets the "created_at" field if the given value is not nil. +func (_c *TLSFingerprintProfileCreate) SetNillableCreatedAt(v *time.Time) *TLSFingerprintProfileCreate { + if v != nil { + _c.SetCreatedAt(*v) + } + return _c +} + +// SetUpdatedAt sets the "updated_at" field. +func (_c *TLSFingerprintProfileCreate) SetUpdatedAt(v time.Time) *TLSFingerprintProfileCreate { + _c.mutation.SetUpdatedAt(v) + return _c +} + +// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. +func (_c *TLSFingerprintProfileCreate) SetNillableUpdatedAt(v *time.Time) *TLSFingerprintProfileCreate { + if v != nil { + _c.SetUpdatedAt(*v) + } + return _c +} + +// SetName sets the "name" field. +func (_c *TLSFingerprintProfileCreate) SetName(v string) *TLSFingerprintProfileCreate { + _c.mutation.SetName(v) + return _c +} + +// SetDescription sets the "description" field. +func (_c *TLSFingerprintProfileCreate) SetDescription(v string) *TLSFingerprintProfileCreate { + _c.mutation.SetDescription(v) + return _c +} + +// SetNillableDescription sets the "description" field if the given value is not nil. +func (_c *TLSFingerprintProfileCreate) SetNillableDescription(v *string) *TLSFingerprintProfileCreate { + if v != nil { + _c.SetDescription(*v) + } + return _c +} + +// SetEnableGrease sets the "enable_grease" field. +func (_c *TLSFingerprintProfileCreate) SetEnableGrease(v bool) *TLSFingerprintProfileCreate { + _c.mutation.SetEnableGrease(v) + return _c +} + +// SetNillableEnableGrease sets the "enable_grease" field if the given value is not nil. +func (_c *TLSFingerprintProfileCreate) SetNillableEnableGrease(v *bool) *TLSFingerprintProfileCreate { + if v != nil { + _c.SetEnableGrease(*v) + } + return _c +} + +// SetCipherSuites sets the "cipher_suites" field. +func (_c *TLSFingerprintProfileCreate) SetCipherSuites(v []uint16) *TLSFingerprintProfileCreate { + _c.mutation.SetCipherSuites(v) + return _c +} + +// SetCurves sets the "curves" field. +func (_c *TLSFingerprintProfileCreate) SetCurves(v []uint16) *TLSFingerprintProfileCreate { + _c.mutation.SetCurves(v) + return _c +} + +// SetPointFormats sets the "point_formats" field. +func (_c *TLSFingerprintProfileCreate) SetPointFormats(v []uint16) *TLSFingerprintProfileCreate { + _c.mutation.SetPointFormats(v) + return _c +} + +// SetSignatureAlgorithms sets the "signature_algorithms" field. +func (_c *TLSFingerprintProfileCreate) SetSignatureAlgorithms(v []uint16) *TLSFingerprintProfileCreate { + _c.mutation.SetSignatureAlgorithms(v) + return _c +} + +// SetAlpnProtocols sets the "alpn_protocols" field. +func (_c *TLSFingerprintProfileCreate) SetAlpnProtocols(v []string) *TLSFingerprintProfileCreate { + _c.mutation.SetAlpnProtocols(v) + return _c +} + +// SetSupportedVersions sets the "supported_versions" field. +func (_c *TLSFingerprintProfileCreate) SetSupportedVersions(v []uint16) *TLSFingerprintProfileCreate { + _c.mutation.SetSupportedVersions(v) + return _c +} + +// SetKeyShareGroups sets the "key_share_groups" field. +func (_c *TLSFingerprintProfileCreate) SetKeyShareGroups(v []uint16) *TLSFingerprintProfileCreate { + _c.mutation.SetKeyShareGroups(v) + return _c +} + +// SetPskModes sets the "psk_modes" field. +func (_c *TLSFingerprintProfileCreate) SetPskModes(v []uint16) *TLSFingerprintProfileCreate { + _c.mutation.SetPskModes(v) + return _c +} + +// SetExtensions sets the "extensions" field. +func (_c *TLSFingerprintProfileCreate) SetExtensions(v []uint16) *TLSFingerprintProfileCreate { + _c.mutation.SetExtensions(v) + return _c +} + +// Mutation returns the TLSFingerprintProfileMutation object of the builder. +func (_c *TLSFingerprintProfileCreate) Mutation() *TLSFingerprintProfileMutation { + return _c.mutation +} + +// Save creates the TLSFingerprintProfile in the database. +func (_c *TLSFingerprintProfileCreate) Save(ctx context.Context) (*TLSFingerprintProfile, error) { + _c.defaults() + return withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks) +} + +// SaveX calls Save and panics if Save returns an error. +func (_c *TLSFingerprintProfileCreate) SaveX(ctx context.Context) *TLSFingerprintProfile { + v, err := _c.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (_c *TLSFingerprintProfileCreate) Exec(ctx context.Context) error { + _, err := _c.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_c *TLSFingerprintProfileCreate) ExecX(ctx context.Context) { + if err := _c.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (_c *TLSFingerprintProfileCreate) defaults() { + if _, ok := _c.mutation.CreatedAt(); !ok { + v := tlsfingerprintprofile.DefaultCreatedAt() + _c.mutation.SetCreatedAt(v) + } + if _, ok := _c.mutation.UpdatedAt(); !ok { + v := tlsfingerprintprofile.DefaultUpdatedAt() + _c.mutation.SetUpdatedAt(v) + } + if _, ok := _c.mutation.EnableGrease(); !ok { + v := tlsfingerprintprofile.DefaultEnableGrease + _c.mutation.SetEnableGrease(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (_c *TLSFingerprintProfileCreate) check() error { + if _, ok := _c.mutation.CreatedAt(); !ok { + return &ValidationError{Name: "created_at", err: errors.New(`ent: missing required field "TLSFingerprintProfile.created_at"`)} + } + if _, ok := _c.mutation.UpdatedAt(); !ok { + return &ValidationError{Name: "updated_at", err: errors.New(`ent: missing required field "TLSFingerprintProfile.updated_at"`)} + } + if _, ok := _c.mutation.Name(); !ok { + return &ValidationError{Name: "name", err: errors.New(`ent: missing required field "TLSFingerprintProfile.name"`)} + } + if v, ok := _c.mutation.Name(); ok { + if err := tlsfingerprintprofile.NameValidator(v); err != nil { + return &ValidationError{Name: "name", err: fmt.Errorf(`ent: validator failed for field "TLSFingerprintProfile.name": %w`, err)} + } + } + if _, ok := _c.mutation.EnableGrease(); !ok { + return &ValidationError{Name: "enable_grease", err: errors.New(`ent: missing required field "TLSFingerprintProfile.enable_grease"`)} + } + return nil +} + +func (_c *TLSFingerprintProfileCreate) sqlSave(ctx context.Context) (*TLSFingerprintProfile, error) { + if err := _c.check(); err != nil { + return nil, err + } + _node, _spec := _c.createSpec() + if err := sqlgraph.CreateNode(ctx, _c.driver, _spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + id := _spec.ID.Value.(int64) + _node.ID = int64(id) + _c.mutation.id = &_node.ID + _c.mutation.done = true + return _node, nil +} + +func (_c *TLSFingerprintProfileCreate) createSpec() (*TLSFingerprintProfile, *sqlgraph.CreateSpec) { + var ( + _node = &TLSFingerprintProfile{config: _c.config} + _spec = sqlgraph.NewCreateSpec(tlsfingerprintprofile.Table, sqlgraph.NewFieldSpec(tlsfingerprintprofile.FieldID, field.TypeInt64)) + ) + _spec.OnConflict = _c.conflict + if value, ok := _c.mutation.CreatedAt(); ok { + _spec.SetField(tlsfingerprintprofile.FieldCreatedAt, field.TypeTime, value) + _node.CreatedAt = value + } + if value, ok := _c.mutation.UpdatedAt(); ok { + _spec.SetField(tlsfingerprintprofile.FieldUpdatedAt, field.TypeTime, value) + _node.UpdatedAt = value + } + if value, ok := _c.mutation.Name(); ok { + _spec.SetField(tlsfingerprintprofile.FieldName, field.TypeString, value) + _node.Name = value + } + if value, ok := _c.mutation.Description(); ok { + _spec.SetField(tlsfingerprintprofile.FieldDescription, field.TypeString, value) + _node.Description = &value + } + if value, ok := _c.mutation.EnableGrease(); ok { + _spec.SetField(tlsfingerprintprofile.FieldEnableGrease, field.TypeBool, value) + _node.EnableGrease = value + } + if value, ok := _c.mutation.CipherSuites(); ok { + _spec.SetField(tlsfingerprintprofile.FieldCipherSuites, field.TypeJSON, value) + _node.CipherSuites = value + } + if value, ok := _c.mutation.Curves(); ok { + _spec.SetField(tlsfingerprintprofile.FieldCurves, field.TypeJSON, value) + _node.Curves = value + } + if value, ok := _c.mutation.PointFormats(); ok { + _spec.SetField(tlsfingerprintprofile.FieldPointFormats, field.TypeJSON, value) + _node.PointFormats = value + } + if value, ok := _c.mutation.SignatureAlgorithms(); ok { + _spec.SetField(tlsfingerprintprofile.FieldSignatureAlgorithms, field.TypeJSON, value) + _node.SignatureAlgorithms = value + } + if value, ok := _c.mutation.AlpnProtocols(); ok { + _spec.SetField(tlsfingerprintprofile.FieldAlpnProtocols, field.TypeJSON, value) + _node.AlpnProtocols = value + } + if value, ok := _c.mutation.SupportedVersions(); ok { + _spec.SetField(tlsfingerprintprofile.FieldSupportedVersions, field.TypeJSON, value) + _node.SupportedVersions = value + } + if value, ok := _c.mutation.KeyShareGroups(); ok { + _spec.SetField(tlsfingerprintprofile.FieldKeyShareGroups, field.TypeJSON, value) + _node.KeyShareGroups = value + } + if value, ok := _c.mutation.PskModes(); ok { + _spec.SetField(tlsfingerprintprofile.FieldPskModes, field.TypeJSON, value) + _node.PskModes = value + } + if value, ok := _c.mutation.Extensions(); ok { + _spec.SetField(tlsfingerprintprofile.FieldExtensions, field.TypeJSON, value) + _node.Extensions = value + } + return _node, _spec +} + +// OnConflict allows configuring the `ON CONFLICT` / `ON DUPLICATE KEY` clause +// of the `INSERT` statement. For example: +// +// client.TLSFingerprintProfile.Create(). +// SetCreatedAt(v). +// OnConflict( +// // Update the row with the new values +// // the was proposed for insertion. +// sql.ResolveWithNewValues(), +// ). +// // Override some of the fields with custom +// // update values. +// Update(func(u *ent.TLSFingerprintProfileUpsert) { +// SetCreatedAt(v+v). +// }). +// Exec(ctx) +func (_c *TLSFingerprintProfileCreate) OnConflict(opts ...sql.ConflictOption) *TLSFingerprintProfileUpsertOne { + _c.conflict = opts + return &TLSFingerprintProfileUpsertOne{ + create: _c, + } +} + +// OnConflictColumns calls `OnConflict` and configures the columns +// as conflict target. Using this option is equivalent to using: +// +// client.TLSFingerprintProfile.Create(). +// OnConflict(sql.ConflictColumns(columns...)). +// Exec(ctx) +func (_c *TLSFingerprintProfileCreate) OnConflictColumns(columns ...string) *TLSFingerprintProfileUpsertOne { + _c.conflict = append(_c.conflict, sql.ConflictColumns(columns...)) + return &TLSFingerprintProfileUpsertOne{ + create: _c, + } +} + +type ( + // TLSFingerprintProfileUpsertOne is the builder for "upsert"-ing + // one TLSFingerprintProfile node. + TLSFingerprintProfileUpsertOne struct { + create *TLSFingerprintProfileCreate + } + + // TLSFingerprintProfileUpsert is the "OnConflict" setter. + TLSFingerprintProfileUpsert struct { + *sql.UpdateSet + } +) + +// SetUpdatedAt sets the "updated_at" field. +func (u *TLSFingerprintProfileUpsert) SetUpdatedAt(v time.Time) *TLSFingerprintProfileUpsert { + u.Set(tlsfingerprintprofile.FieldUpdatedAt, v) + return u +} + +// UpdateUpdatedAt sets the "updated_at" field to the value that was provided on create. +func (u *TLSFingerprintProfileUpsert) UpdateUpdatedAt() *TLSFingerprintProfileUpsert { + u.SetExcluded(tlsfingerprintprofile.FieldUpdatedAt) + return u +} + +// SetName sets the "name" field. +func (u *TLSFingerprintProfileUpsert) SetName(v string) *TLSFingerprintProfileUpsert { + u.Set(tlsfingerprintprofile.FieldName, v) + return u +} + +// UpdateName sets the "name" field to the value that was provided on create. +func (u *TLSFingerprintProfileUpsert) UpdateName() *TLSFingerprintProfileUpsert { + u.SetExcluded(tlsfingerprintprofile.FieldName) + return u +} + +// SetDescription sets the "description" field. +func (u *TLSFingerprintProfileUpsert) SetDescription(v string) *TLSFingerprintProfileUpsert { + u.Set(tlsfingerprintprofile.FieldDescription, v) + return u +} + +// UpdateDescription sets the "description" field to the value that was provided on create. +func (u *TLSFingerprintProfileUpsert) UpdateDescription() *TLSFingerprintProfileUpsert { + u.SetExcluded(tlsfingerprintprofile.FieldDescription) + return u +} + +// ClearDescription clears the value of the "description" field. +func (u *TLSFingerprintProfileUpsert) ClearDescription() *TLSFingerprintProfileUpsert { + u.SetNull(tlsfingerprintprofile.FieldDescription) + return u +} + +// SetEnableGrease sets the "enable_grease" field. +func (u *TLSFingerprintProfileUpsert) SetEnableGrease(v bool) *TLSFingerprintProfileUpsert { + u.Set(tlsfingerprintprofile.FieldEnableGrease, v) + return u +} + +// UpdateEnableGrease sets the "enable_grease" field to the value that was provided on create. +func (u *TLSFingerprintProfileUpsert) UpdateEnableGrease() *TLSFingerprintProfileUpsert { + u.SetExcluded(tlsfingerprintprofile.FieldEnableGrease) + return u +} + +// SetCipherSuites sets the "cipher_suites" field. +func (u *TLSFingerprintProfileUpsert) SetCipherSuites(v []uint16) *TLSFingerprintProfileUpsert { + u.Set(tlsfingerprintprofile.FieldCipherSuites, v) + return u +} + +// UpdateCipherSuites sets the "cipher_suites" field to the value that was provided on create. +func (u *TLSFingerprintProfileUpsert) UpdateCipherSuites() *TLSFingerprintProfileUpsert { + u.SetExcluded(tlsfingerprintprofile.FieldCipherSuites) + return u +} + +// ClearCipherSuites clears the value of the "cipher_suites" field. +func (u *TLSFingerprintProfileUpsert) ClearCipherSuites() *TLSFingerprintProfileUpsert { + u.SetNull(tlsfingerprintprofile.FieldCipherSuites) + return u +} + +// SetCurves sets the "curves" field. +func (u *TLSFingerprintProfileUpsert) SetCurves(v []uint16) *TLSFingerprintProfileUpsert { + u.Set(tlsfingerprintprofile.FieldCurves, v) + return u +} + +// UpdateCurves sets the "curves" field to the value that was provided on create. +func (u *TLSFingerprintProfileUpsert) UpdateCurves() *TLSFingerprintProfileUpsert { + u.SetExcluded(tlsfingerprintprofile.FieldCurves) + return u +} + +// ClearCurves clears the value of the "curves" field. +func (u *TLSFingerprintProfileUpsert) ClearCurves() *TLSFingerprintProfileUpsert { + u.SetNull(tlsfingerprintprofile.FieldCurves) + return u +} + +// SetPointFormats sets the "point_formats" field. +func (u *TLSFingerprintProfileUpsert) SetPointFormats(v []uint16) *TLSFingerprintProfileUpsert { + u.Set(tlsfingerprintprofile.FieldPointFormats, v) + return u +} + +// UpdatePointFormats sets the "point_formats" field to the value that was provided on create. +func (u *TLSFingerprintProfileUpsert) UpdatePointFormats() *TLSFingerprintProfileUpsert { + u.SetExcluded(tlsfingerprintprofile.FieldPointFormats) + return u +} + +// ClearPointFormats clears the value of the "point_formats" field. +func (u *TLSFingerprintProfileUpsert) ClearPointFormats() *TLSFingerprintProfileUpsert { + u.SetNull(tlsfingerprintprofile.FieldPointFormats) + return u +} + +// SetSignatureAlgorithms sets the "signature_algorithms" field. +func (u *TLSFingerprintProfileUpsert) SetSignatureAlgorithms(v []uint16) *TLSFingerprintProfileUpsert { + u.Set(tlsfingerprintprofile.FieldSignatureAlgorithms, v) + return u +} + +// UpdateSignatureAlgorithms sets the "signature_algorithms" field to the value that was provided on create. +func (u *TLSFingerprintProfileUpsert) UpdateSignatureAlgorithms() *TLSFingerprintProfileUpsert { + u.SetExcluded(tlsfingerprintprofile.FieldSignatureAlgorithms) + return u +} + +// ClearSignatureAlgorithms clears the value of the "signature_algorithms" field. +func (u *TLSFingerprintProfileUpsert) ClearSignatureAlgorithms() *TLSFingerprintProfileUpsert { + u.SetNull(tlsfingerprintprofile.FieldSignatureAlgorithms) + return u +} + +// SetAlpnProtocols sets the "alpn_protocols" field. +func (u *TLSFingerprintProfileUpsert) SetAlpnProtocols(v []string) *TLSFingerprintProfileUpsert { + u.Set(tlsfingerprintprofile.FieldAlpnProtocols, v) + return u +} + +// UpdateAlpnProtocols sets the "alpn_protocols" field to the value that was provided on create. +func (u *TLSFingerprintProfileUpsert) UpdateAlpnProtocols() *TLSFingerprintProfileUpsert { + u.SetExcluded(tlsfingerprintprofile.FieldAlpnProtocols) + return u +} + +// ClearAlpnProtocols clears the value of the "alpn_protocols" field. +func (u *TLSFingerprintProfileUpsert) ClearAlpnProtocols() *TLSFingerprintProfileUpsert { + u.SetNull(tlsfingerprintprofile.FieldAlpnProtocols) + return u +} + +// SetSupportedVersions sets the "supported_versions" field. +func (u *TLSFingerprintProfileUpsert) SetSupportedVersions(v []uint16) *TLSFingerprintProfileUpsert { + u.Set(tlsfingerprintprofile.FieldSupportedVersions, v) + return u +} + +// UpdateSupportedVersions sets the "supported_versions" field to the value that was provided on create. +func (u *TLSFingerprintProfileUpsert) UpdateSupportedVersions() *TLSFingerprintProfileUpsert { + u.SetExcluded(tlsfingerprintprofile.FieldSupportedVersions) + return u +} + +// ClearSupportedVersions clears the value of the "supported_versions" field. +func (u *TLSFingerprintProfileUpsert) ClearSupportedVersions() *TLSFingerprintProfileUpsert { + u.SetNull(tlsfingerprintprofile.FieldSupportedVersions) + return u +} + +// SetKeyShareGroups sets the "key_share_groups" field. +func (u *TLSFingerprintProfileUpsert) SetKeyShareGroups(v []uint16) *TLSFingerprintProfileUpsert { + u.Set(tlsfingerprintprofile.FieldKeyShareGroups, v) + return u +} + +// UpdateKeyShareGroups sets the "key_share_groups" field to the value that was provided on create. +func (u *TLSFingerprintProfileUpsert) UpdateKeyShareGroups() *TLSFingerprintProfileUpsert { + u.SetExcluded(tlsfingerprintprofile.FieldKeyShareGroups) + return u +} + +// ClearKeyShareGroups clears the value of the "key_share_groups" field. +func (u *TLSFingerprintProfileUpsert) ClearKeyShareGroups() *TLSFingerprintProfileUpsert { + u.SetNull(tlsfingerprintprofile.FieldKeyShareGroups) + return u +} + +// SetPskModes sets the "psk_modes" field. +func (u *TLSFingerprintProfileUpsert) SetPskModes(v []uint16) *TLSFingerprintProfileUpsert { + u.Set(tlsfingerprintprofile.FieldPskModes, v) + return u +} + +// UpdatePskModes sets the "psk_modes" field to the value that was provided on create. +func (u *TLSFingerprintProfileUpsert) UpdatePskModes() *TLSFingerprintProfileUpsert { + u.SetExcluded(tlsfingerprintprofile.FieldPskModes) + return u +} + +// ClearPskModes clears the value of the "psk_modes" field. +func (u *TLSFingerprintProfileUpsert) ClearPskModes() *TLSFingerprintProfileUpsert { + u.SetNull(tlsfingerprintprofile.FieldPskModes) + return u +} + +// SetExtensions sets the "extensions" field. +func (u *TLSFingerprintProfileUpsert) SetExtensions(v []uint16) *TLSFingerprintProfileUpsert { + u.Set(tlsfingerprintprofile.FieldExtensions, v) + return u +} + +// UpdateExtensions sets the "extensions" field to the value that was provided on create. +func (u *TLSFingerprintProfileUpsert) UpdateExtensions() *TLSFingerprintProfileUpsert { + u.SetExcluded(tlsfingerprintprofile.FieldExtensions) + return u +} + +// ClearExtensions clears the value of the "extensions" field. +func (u *TLSFingerprintProfileUpsert) ClearExtensions() *TLSFingerprintProfileUpsert { + u.SetNull(tlsfingerprintprofile.FieldExtensions) + return u +} + +// UpdateNewValues updates the mutable fields using the new values that were set on create. +// Using this option is equivalent to using: +// +// client.TLSFingerprintProfile.Create(). +// OnConflict( +// sql.ResolveWithNewValues(), +// ). +// Exec(ctx) +func (u *TLSFingerprintProfileUpsertOne) UpdateNewValues() *TLSFingerprintProfileUpsertOne { + u.create.conflict = append(u.create.conflict, sql.ResolveWithNewValues()) + u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(s *sql.UpdateSet) { + if _, exists := u.create.mutation.CreatedAt(); exists { + s.SetIgnore(tlsfingerprintprofile.FieldCreatedAt) + } + })) + return u +} + +// Ignore sets each column to itself in case of conflict. +// Using this option is equivalent to using: +// +// client.TLSFingerprintProfile.Create(). +// OnConflict(sql.ResolveWithIgnore()). +// Exec(ctx) +func (u *TLSFingerprintProfileUpsertOne) Ignore() *TLSFingerprintProfileUpsertOne { + u.create.conflict = append(u.create.conflict, sql.ResolveWithIgnore()) + return u +} + +// DoNothing configures the conflict_action to `DO NOTHING`. +// Supported only by SQLite and PostgreSQL. +func (u *TLSFingerprintProfileUpsertOne) DoNothing() *TLSFingerprintProfileUpsertOne { + u.create.conflict = append(u.create.conflict, sql.DoNothing()) + return u +} + +// Update allows overriding fields `UPDATE` values. See the TLSFingerprintProfileCreate.OnConflict +// documentation for more info. +func (u *TLSFingerprintProfileUpsertOne) Update(set func(*TLSFingerprintProfileUpsert)) *TLSFingerprintProfileUpsertOne { + u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) { + set(&TLSFingerprintProfileUpsert{UpdateSet: update}) + })) + return u +} + +// SetUpdatedAt sets the "updated_at" field. +func (u *TLSFingerprintProfileUpsertOne) SetUpdatedAt(v time.Time) *TLSFingerprintProfileUpsertOne { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.SetUpdatedAt(v) + }) +} + +// UpdateUpdatedAt sets the "updated_at" field to the value that was provided on create. +func (u *TLSFingerprintProfileUpsertOne) UpdateUpdatedAt() *TLSFingerprintProfileUpsertOne { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.UpdateUpdatedAt() + }) +} + +// SetName sets the "name" field. +func (u *TLSFingerprintProfileUpsertOne) SetName(v string) *TLSFingerprintProfileUpsertOne { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.SetName(v) + }) +} + +// UpdateName sets the "name" field to the value that was provided on create. +func (u *TLSFingerprintProfileUpsertOne) UpdateName() *TLSFingerprintProfileUpsertOne { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.UpdateName() + }) +} + +// SetDescription sets the "description" field. +func (u *TLSFingerprintProfileUpsertOne) SetDescription(v string) *TLSFingerprintProfileUpsertOne { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.SetDescription(v) + }) +} + +// UpdateDescription sets the "description" field to the value that was provided on create. +func (u *TLSFingerprintProfileUpsertOne) UpdateDescription() *TLSFingerprintProfileUpsertOne { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.UpdateDescription() + }) +} + +// ClearDescription clears the value of the "description" field. +func (u *TLSFingerprintProfileUpsertOne) ClearDescription() *TLSFingerprintProfileUpsertOne { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.ClearDescription() + }) +} + +// SetEnableGrease sets the "enable_grease" field. +func (u *TLSFingerprintProfileUpsertOne) SetEnableGrease(v bool) *TLSFingerprintProfileUpsertOne { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.SetEnableGrease(v) + }) +} + +// UpdateEnableGrease sets the "enable_grease" field to the value that was provided on create. +func (u *TLSFingerprintProfileUpsertOne) UpdateEnableGrease() *TLSFingerprintProfileUpsertOne { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.UpdateEnableGrease() + }) +} + +// SetCipherSuites sets the "cipher_suites" field. +func (u *TLSFingerprintProfileUpsertOne) SetCipherSuites(v []uint16) *TLSFingerprintProfileUpsertOne { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.SetCipherSuites(v) + }) +} + +// UpdateCipherSuites sets the "cipher_suites" field to the value that was provided on create. +func (u *TLSFingerprintProfileUpsertOne) UpdateCipherSuites() *TLSFingerprintProfileUpsertOne { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.UpdateCipherSuites() + }) +} + +// ClearCipherSuites clears the value of the "cipher_suites" field. +func (u *TLSFingerprintProfileUpsertOne) ClearCipherSuites() *TLSFingerprintProfileUpsertOne { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.ClearCipherSuites() + }) +} + +// SetCurves sets the "curves" field. +func (u *TLSFingerprintProfileUpsertOne) SetCurves(v []uint16) *TLSFingerprintProfileUpsertOne { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.SetCurves(v) + }) +} + +// UpdateCurves sets the "curves" field to the value that was provided on create. +func (u *TLSFingerprintProfileUpsertOne) UpdateCurves() *TLSFingerprintProfileUpsertOne { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.UpdateCurves() + }) +} + +// ClearCurves clears the value of the "curves" field. +func (u *TLSFingerprintProfileUpsertOne) ClearCurves() *TLSFingerprintProfileUpsertOne { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.ClearCurves() + }) +} + +// SetPointFormats sets the "point_formats" field. +func (u *TLSFingerprintProfileUpsertOne) SetPointFormats(v []uint16) *TLSFingerprintProfileUpsertOne { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.SetPointFormats(v) + }) +} + +// UpdatePointFormats sets the "point_formats" field to the value that was provided on create. +func (u *TLSFingerprintProfileUpsertOne) UpdatePointFormats() *TLSFingerprintProfileUpsertOne { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.UpdatePointFormats() + }) +} + +// ClearPointFormats clears the value of the "point_formats" field. +func (u *TLSFingerprintProfileUpsertOne) ClearPointFormats() *TLSFingerprintProfileUpsertOne { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.ClearPointFormats() + }) +} + +// SetSignatureAlgorithms sets the "signature_algorithms" field. +func (u *TLSFingerprintProfileUpsertOne) SetSignatureAlgorithms(v []uint16) *TLSFingerprintProfileUpsertOne { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.SetSignatureAlgorithms(v) + }) +} + +// UpdateSignatureAlgorithms sets the "signature_algorithms" field to the value that was provided on create. +func (u *TLSFingerprintProfileUpsertOne) UpdateSignatureAlgorithms() *TLSFingerprintProfileUpsertOne { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.UpdateSignatureAlgorithms() + }) +} + +// ClearSignatureAlgorithms clears the value of the "signature_algorithms" field. +func (u *TLSFingerprintProfileUpsertOne) ClearSignatureAlgorithms() *TLSFingerprintProfileUpsertOne { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.ClearSignatureAlgorithms() + }) +} + +// SetAlpnProtocols sets the "alpn_protocols" field. +func (u *TLSFingerprintProfileUpsertOne) SetAlpnProtocols(v []string) *TLSFingerprintProfileUpsertOne { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.SetAlpnProtocols(v) + }) +} + +// UpdateAlpnProtocols sets the "alpn_protocols" field to the value that was provided on create. +func (u *TLSFingerprintProfileUpsertOne) UpdateAlpnProtocols() *TLSFingerprintProfileUpsertOne { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.UpdateAlpnProtocols() + }) +} + +// ClearAlpnProtocols clears the value of the "alpn_protocols" field. +func (u *TLSFingerprintProfileUpsertOne) ClearAlpnProtocols() *TLSFingerprintProfileUpsertOne { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.ClearAlpnProtocols() + }) +} + +// SetSupportedVersions sets the "supported_versions" field. +func (u *TLSFingerprintProfileUpsertOne) SetSupportedVersions(v []uint16) *TLSFingerprintProfileUpsertOne { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.SetSupportedVersions(v) + }) +} + +// UpdateSupportedVersions sets the "supported_versions" field to the value that was provided on create. +func (u *TLSFingerprintProfileUpsertOne) UpdateSupportedVersions() *TLSFingerprintProfileUpsertOne { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.UpdateSupportedVersions() + }) +} + +// ClearSupportedVersions clears the value of the "supported_versions" field. +func (u *TLSFingerprintProfileUpsertOne) ClearSupportedVersions() *TLSFingerprintProfileUpsertOne { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.ClearSupportedVersions() + }) +} + +// SetKeyShareGroups sets the "key_share_groups" field. +func (u *TLSFingerprintProfileUpsertOne) SetKeyShareGroups(v []uint16) *TLSFingerprintProfileUpsertOne { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.SetKeyShareGroups(v) + }) +} + +// UpdateKeyShareGroups sets the "key_share_groups" field to the value that was provided on create. +func (u *TLSFingerprintProfileUpsertOne) UpdateKeyShareGroups() *TLSFingerprintProfileUpsertOne { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.UpdateKeyShareGroups() + }) +} + +// ClearKeyShareGroups clears the value of the "key_share_groups" field. +func (u *TLSFingerprintProfileUpsertOne) ClearKeyShareGroups() *TLSFingerprintProfileUpsertOne { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.ClearKeyShareGroups() + }) +} + +// SetPskModes sets the "psk_modes" field. +func (u *TLSFingerprintProfileUpsertOne) SetPskModes(v []uint16) *TLSFingerprintProfileUpsertOne { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.SetPskModes(v) + }) +} + +// UpdatePskModes sets the "psk_modes" field to the value that was provided on create. +func (u *TLSFingerprintProfileUpsertOne) UpdatePskModes() *TLSFingerprintProfileUpsertOne { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.UpdatePskModes() + }) +} + +// ClearPskModes clears the value of the "psk_modes" field. +func (u *TLSFingerprintProfileUpsertOne) ClearPskModes() *TLSFingerprintProfileUpsertOne { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.ClearPskModes() + }) +} + +// SetExtensions sets the "extensions" field. +func (u *TLSFingerprintProfileUpsertOne) SetExtensions(v []uint16) *TLSFingerprintProfileUpsertOne { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.SetExtensions(v) + }) +} + +// UpdateExtensions sets the "extensions" field to the value that was provided on create. +func (u *TLSFingerprintProfileUpsertOne) UpdateExtensions() *TLSFingerprintProfileUpsertOne { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.UpdateExtensions() + }) +} + +// ClearExtensions clears the value of the "extensions" field. +func (u *TLSFingerprintProfileUpsertOne) ClearExtensions() *TLSFingerprintProfileUpsertOne { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.ClearExtensions() + }) +} + +// Exec executes the query. +func (u *TLSFingerprintProfileUpsertOne) Exec(ctx context.Context) error { + if len(u.create.conflict) == 0 { + return errors.New("ent: missing options for TLSFingerprintProfileCreate.OnConflict") + } + return u.create.Exec(ctx) +} + +// ExecX is like Exec, but panics if an error occurs. +func (u *TLSFingerprintProfileUpsertOne) ExecX(ctx context.Context) { + if err := u.create.Exec(ctx); err != nil { + panic(err) + } +} + +// Exec executes the UPSERT query and returns the inserted/updated ID. +func (u *TLSFingerprintProfileUpsertOne) ID(ctx context.Context) (id int64, err error) { + node, err := u.create.Save(ctx) + if err != nil { + return id, err + } + return node.ID, nil +} + +// IDX is like ID, but panics if an error occurs. +func (u *TLSFingerprintProfileUpsertOne) IDX(ctx context.Context) int64 { + id, err := u.ID(ctx) + if err != nil { + panic(err) + } + return id +} + +// TLSFingerprintProfileCreateBulk is the builder for creating many TLSFingerprintProfile entities in bulk. +type TLSFingerprintProfileCreateBulk struct { + config + err error + builders []*TLSFingerprintProfileCreate + conflict []sql.ConflictOption +} + +// Save creates the TLSFingerprintProfile entities in the database. +func (_c *TLSFingerprintProfileCreateBulk) Save(ctx context.Context) ([]*TLSFingerprintProfile, error) { + if _c.err != nil { + return nil, _c.err + } + specs := make([]*sqlgraph.CreateSpec, len(_c.builders)) + nodes := make([]*TLSFingerprintProfile, len(_c.builders)) + mutators := make([]Mutator, len(_c.builders)) + for i := range _c.builders { + func(i int, root context.Context) { + builder := _c.builders[i] + builder.defaults() + var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { + mutation, ok := m.(*TLSFingerprintProfileMutation) + if !ok { + return nil, fmt.Errorf("unexpected mutation type %T", m) + } + if err := builder.check(); err != nil { + return nil, err + } + builder.mutation = mutation + var err error + nodes[i], specs[i] = builder.createSpec() + if i < len(mutators)-1 { + _, err = mutators[i+1].Mutate(root, _c.builders[i+1].mutation) + } else { + spec := &sqlgraph.BatchCreateSpec{Nodes: specs} + spec.OnConflict = _c.conflict + // Invoke the actual operation on the latest mutation in the chain. + if err = sqlgraph.BatchCreate(ctx, _c.driver, spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + } + } + if err != nil { + return nil, err + } + mutation.id = &nodes[i].ID + if specs[i].ID.Value != nil { + id := specs[i].ID.Value.(int64) + nodes[i].ID = int64(id) + } + mutation.done = true + return nodes[i], nil + }) + for i := len(builder.hooks) - 1; i >= 0; i-- { + mut = builder.hooks[i](mut) + } + mutators[i] = mut + }(i, ctx) + } + if len(mutators) > 0 { + if _, err := mutators[0].Mutate(ctx, _c.builders[0].mutation); err != nil { + return nil, err + } + } + return nodes, nil +} + +// SaveX is like Save, but panics if an error occurs. +func (_c *TLSFingerprintProfileCreateBulk) SaveX(ctx context.Context) []*TLSFingerprintProfile { + v, err := _c.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (_c *TLSFingerprintProfileCreateBulk) Exec(ctx context.Context) error { + _, err := _c.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_c *TLSFingerprintProfileCreateBulk) ExecX(ctx context.Context) { + if err := _c.Exec(ctx); err != nil { + panic(err) + } +} + +// OnConflict allows configuring the `ON CONFLICT` / `ON DUPLICATE KEY` clause +// of the `INSERT` statement. For example: +// +// client.TLSFingerprintProfile.CreateBulk(builders...). +// OnConflict( +// // Update the row with the new values +// // the was proposed for insertion. +// sql.ResolveWithNewValues(), +// ). +// // Override some of the fields with custom +// // update values. +// Update(func(u *ent.TLSFingerprintProfileUpsert) { +// SetCreatedAt(v+v). +// }). +// Exec(ctx) +func (_c *TLSFingerprintProfileCreateBulk) OnConflict(opts ...sql.ConflictOption) *TLSFingerprintProfileUpsertBulk { + _c.conflict = opts + return &TLSFingerprintProfileUpsertBulk{ + create: _c, + } +} + +// OnConflictColumns calls `OnConflict` and configures the columns +// as conflict target. Using this option is equivalent to using: +// +// client.TLSFingerprintProfile.Create(). +// OnConflict(sql.ConflictColumns(columns...)). +// Exec(ctx) +func (_c *TLSFingerprintProfileCreateBulk) OnConflictColumns(columns ...string) *TLSFingerprintProfileUpsertBulk { + _c.conflict = append(_c.conflict, sql.ConflictColumns(columns...)) + return &TLSFingerprintProfileUpsertBulk{ + create: _c, + } +} + +// TLSFingerprintProfileUpsertBulk is the builder for "upsert"-ing +// a bulk of TLSFingerprintProfile nodes. +type TLSFingerprintProfileUpsertBulk struct { + create *TLSFingerprintProfileCreateBulk +} + +// UpdateNewValues updates the mutable fields using the new values that +// were set on create. Using this option is equivalent to using: +// +// client.TLSFingerprintProfile.Create(). +// OnConflict( +// sql.ResolveWithNewValues(), +// ). +// Exec(ctx) +func (u *TLSFingerprintProfileUpsertBulk) UpdateNewValues() *TLSFingerprintProfileUpsertBulk { + u.create.conflict = append(u.create.conflict, sql.ResolveWithNewValues()) + u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(s *sql.UpdateSet) { + for _, b := range u.create.builders { + if _, exists := b.mutation.CreatedAt(); exists { + s.SetIgnore(tlsfingerprintprofile.FieldCreatedAt) + } + } + })) + return u +} + +// Ignore sets each column to itself in case of conflict. +// Using this option is equivalent to using: +// +// client.TLSFingerprintProfile.Create(). +// OnConflict(sql.ResolveWithIgnore()). +// Exec(ctx) +func (u *TLSFingerprintProfileUpsertBulk) Ignore() *TLSFingerprintProfileUpsertBulk { + u.create.conflict = append(u.create.conflict, sql.ResolveWithIgnore()) + return u +} + +// DoNothing configures the conflict_action to `DO NOTHING`. +// Supported only by SQLite and PostgreSQL. +func (u *TLSFingerprintProfileUpsertBulk) DoNothing() *TLSFingerprintProfileUpsertBulk { + u.create.conflict = append(u.create.conflict, sql.DoNothing()) + return u +} + +// Update allows overriding fields `UPDATE` values. See the TLSFingerprintProfileCreateBulk.OnConflict +// documentation for more info. +func (u *TLSFingerprintProfileUpsertBulk) Update(set func(*TLSFingerprintProfileUpsert)) *TLSFingerprintProfileUpsertBulk { + u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) { + set(&TLSFingerprintProfileUpsert{UpdateSet: update}) + })) + return u +} + +// SetUpdatedAt sets the "updated_at" field. +func (u *TLSFingerprintProfileUpsertBulk) SetUpdatedAt(v time.Time) *TLSFingerprintProfileUpsertBulk { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.SetUpdatedAt(v) + }) +} + +// UpdateUpdatedAt sets the "updated_at" field to the value that was provided on create. +func (u *TLSFingerprintProfileUpsertBulk) UpdateUpdatedAt() *TLSFingerprintProfileUpsertBulk { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.UpdateUpdatedAt() + }) +} + +// SetName sets the "name" field. +func (u *TLSFingerprintProfileUpsertBulk) SetName(v string) *TLSFingerprintProfileUpsertBulk { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.SetName(v) + }) +} + +// UpdateName sets the "name" field to the value that was provided on create. +func (u *TLSFingerprintProfileUpsertBulk) UpdateName() *TLSFingerprintProfileUpsertBulk { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.UpdateName() + }) +} + +// SetDescription sets the "description" field. +func (u *TLSFingerprintProfileUpsertBulk) SetDescription(v string) *TLSFingerprintProfileUpsertBulk { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.SetDescription(v) + }) +} + +// UpdateDescription sets the "description" field to the value that was provided on create. +func (u *TLSFingerprintProfileUpsertBulk) UpdateDescription() *TLSFingerprintProfileUpsertBulk { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.UpdateDescription() + }) +} + +// ClearDescription clears the value of the "description" field. +func (u *TLSFingerprintProfileUpsertBulk) ClearDescription() *TLSFingerprintProfileUpsertBulk { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.ClearDescription() + }) +} + +// SetEnableGrease sets the "enable_grease" field. +func (u *TLSFingerprintProfileUpsertBulk) SetEnableGrease(v bool) *TLSFingerprintProfileUpsertBulk { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.SetEnableGrease(v) + }) +} + +// UpdateEnableGrease sets the "enable_grease" field to the value that was provided on create. +func (u *TLSFingerprintProfileUpsertBulk) UpdateEnableGrease() *TLSFingerprintProfileUpsertBulk { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.UpdateEnableGrease() + }) +} + +// SetCipherSuites sets the "cipher_suites" field. +func (u *TLSFingerprintProfileUpsertBulk) SetCipherSuites(v []uint16) *TLSFingerprintProfileUpsertBulk { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.SetCipherSuites(v) + }) +} + +// UpdateCipherSuites sets the "cipher_suites" field to the value that was provided on create. +func (u *TLSFingerprintProfileUpsertBulk) UpdateCipherSuites() *TLSFingerprintProfileUpsertBulk { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.UpdateCipherSuites() + }) +} + +// ClearCipherSuites clears the value of the "cipher_suites" field. +func (u *TLSFingerprintProfileUpsertBulk) ClearCipherSuites() *TLSFingerprintProfileUpsertBulk { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.ClearCipherSuites() + }) +} + +// SetCurves sets the "curves" field. +func (u *TLSFingerprintProfileUpsertBulk) SetCurves(v []uint16) *TLSFingerprintProfileUpsertBulk { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.SetCurves(v) + }) +} + +// UpdateCurves sets the "curves" field to the value that was provided on create. +func (u *TLSFingerprintProfileUpsertBulk) UpdateCurves() *TLSFingerprintProfileUpsertBulk { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.UpdateCurves() + }) +} + +// ClearCurves clears the value of the "curves" field. +func (u *TLSFingerprintProfileUpsertBulk) ClearCurves() *TLSFingerprintProfileUpsertBulk { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.ClearCurves() + }) +} + +// SetPointFormats sets the "point_formats" field. +func (u *TLSFingerprintProfileUpsertBulk) SetPointFormats(v []uint16) *TLSFingerprintProfileUpsertBulk { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.SetPointFormats(v) + }) +} + +// UpdatePointFormats sets the "point_formats" field to the value that was provided on create. +func (u *TLSFingerprintProfileUpsertBulk) UpdatePointFormats() *TLSFingerprintProfileUpsertBulk { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.UpdatePointFormats() + }) +} + +// ClearPointFormats clears the value of the "point_formats" field. +func (u *TLSFingerprintProfileUpsertBulk) ClearPointFormats() *TLSFingerprintProfileUpsertBulk { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.ClearPointFormats() + }) +} + +// SetSignatureAlgorithms sets the "signature_algorithms" field. +func (u *TLSFingerprintProfileUpsertBulk) SetSignatureAlgorithms(v []uint16) *TLSFingerprintProfileUpsertBulk { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.SetSignatureAlgorithms(v) + }) +} + +// UpdateSignatureAlgorithms sets the "signature_algorithms" field to the value that was provided on create. +func (u *TLSFingerprintProfileUpsertBulk) UpdateSignatureAlgorithms() *TLSFingerprintProfileUpsertBulk { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.UpdateSignatureAlgorithms() + }) +} + +// ClearSignatureAlgorithms clears the value of the "signature_algorithms" field. +func (u *TLSFingerprintProfileUpsertBulk) ClearSignatureAlgorithms() *TLSFingerprintProfileUpsertBulk { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.ClearSignatureAlgorithms() + }) +} + +// SetAlpnProtocols sets the "alpn_protocols" field. +func (u *TLSFingerprintProfileUpsertBulk) SetAlpnProtocols(v []string) *TLSFingerprintProfileUpsertBulk { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.SetAlpnProtocols(v) + }) +} + +// UpdateAlpnProtocols sets the "alpn_protocols" field to the value that was provided on create. +func (u *TLSFingerprintProfileUpsertBulk) UpdateAlpnProtocols() *TLSFingerprintProfileUpsertBulk { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.UpdateAlpnProtocols() + }) +} + +// ClearAlpnProtocols clears the value of the "alpn_protocols" field. +func (u *TLSFingerprintProfileUpsertBulk) ClearAlpnProtocols() *TLSFingerprintProfileUpsertBulk { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.ClearAlpnProtocols() + }) +} + +// SetSupportedVersions sets the "supported_versions" field. +func (u *TLSFingerprintProfileUpsertBulk) SetSupportedVersions(v []uint16) *TLSFingerprintProfileUpsertBulk { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.SetSupportedVersions(v) + }) +} + +// UpdateSupportedVersions sets the "supported_versions" field to the value that was provided on create. +func (u *TLSFingerprintProfileUpsertBulk) UpdateSupportedVersions() *TLSFingerprintProfileUpsertBulk { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.UpdateSupportedVersions() + }) +} + +// ClearSupportedVersions clears the value of the "supported_versions" field. +func (u *TLSFingerprintProfileUpsertBulk) ClearSupportedVersions() *TLSFingerprintProfileUpsertBulk { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.ClearSupportedVersions() + }) +} + +// SetKeyShareGroups sets the "key_share_groups" field. +func (u *TLSFingerprintProfileUpsertBulk) SetKeyShareGroups(v []uint16) *TLSFingerprintProfileUpsertBulk { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.SetKeyShareGroups(v) + }) +} + +// UpdateKeyShareGroups sets the "key_share_groups" field to the value that was provided on create. +func (u *TLSFingerprintProfileUpsertBulk) UpdateKeyShareGroups() *TLSFingerprintProfileUpsertBulk { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.UpdateKeyShareGroups() + }) +} + +// ClearKeyShareGroups clears the value of the "key_share_groups" field. +func (u *TLSFingerprintProfileUpsertBulk) ClearKeyShareGroups() *TLSFingerprintProfileUpsertBulk { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.ClearKeyShareGroups() + }) +} + +// SetPskModes sets the "psk_modes" field. +func (u *TLSFingerprintProfileUpsertBulk) SetPskModes(v []uint16) *TLSFingerprintProfileUpsertBulk { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.SetPskModes(v) + }) +} + +// UpdatePskModes sets the "psk_modes" field to the value that was provided on create. +func (u *TLSFingerprintProfileUpsertBulk) UpdatePskModes() *TLSFingerprintProfileUpsertBulk { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.UpdatePskModes() + }) +} + +// ClearPskModes clears the value of the "psk_modes" field. +func (u *TLSFingerprintProfileUpsertBulk) ClearPskModes() *TLSFingerprintProfileUpsertBulk { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.ClearPskModes() + }) +} + +// SetExtensions sets the "extensions" field. +func (u *TLSFingerprintProfileUpsertBulk) SetExtensions(v []uint16) *TLSFingerprintProfileUpsertBulk { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.SetExtensions(v) + }) +} + +// UpdateExtensions sets the "extensions" field to the value that was provided on create. +func (u *TLSFingerprintProfileUpsertBulk) UpdateExtensions() *TLSFingerprintProfileUpsertBulk { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.UpdateExtensions() + }) +} + +// ClearExtensions clears the value of the "extensions" field. +func (u *TLSFingerprintProfileUpsertBulk) ClearExtensions() *TLSFingerprintProfileUpsertBulk { + return u.Update(func(s *TLSFingerprintProfileUpsert) { + s.ClearExtensions() + }) +} + +// Exec executes the query. +func (u *TLSFingerprintProfileUpsertBulk) Exec(ctx context.Context) error { + if u.create.err != nil { + return u.create.err + } + for i, b := range u.create.builders { + if len(b.conflict) != 0 { + return fmt.Errorf("ent: OnConflict was set for builder %d. Set it on the TLSFingerprintProfileCreateBulk instead", i) + } + } + if len(u.create.conflict) == 0 { + return errors.New("ent: missing options for TLSFingerprintProfileCreateBulk.OnConflict") + } + return u.create.Exec(ctx) +} + +// ExecX is like Exec, but panics if an error occurs. +func (u *TLSFingerprintProfileUpsertBulk) ExecX(ctx context.Context) { + if err := u.create.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/backend/ent/tlsfingerprintprofile_delete.go b/backend/ent/tlsfingerprintprofile_delete.go new file mode 100644 index 00000000..2f6dea2e --- /dev/null +++ b/backend/ent/tlsfingerprintprofile_delete.go @@ -0,0 +1,88 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/Wei-Shaw/sub2api/ent/predicate" + "github.com/Wei-Shaw/sub2api/ent/tlsfingerprintprofile" +) + +// TLSFingerprintProfileDelete is the builder for deleting a TLSFingerprintProfile entity. +type TLSFingerprintProfileDelete struct { + config + hooks []Hook + mutation *TLSFingerprintProfileMutation +} + +// Where appends a list predicates to the TLSFingerprintProfileDelete builder. +func (_d *TLSFingerprintProfileDelete) Where(ps ...predicate.TLSFingerprintProfile) *TLSFingerprintProfileDelete { + _d.mutation.Where(ps...) + return _d +} + +// Exec executes the deletion query and returns how many vertices were deleted. +func (_d *TLSFingerprintProfileDelete) Exec(ctx context.Context) (int, error) { + return withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks) +} + +// ExecX is like Exec, but panics if an error occurs. +func (_d *TLSFingerprintProfileDelete) ExecX(ctx context.Context) int { + n, err := _d.Exec(ctx) + if err != nil { + panic(err) + } + return n +} + +func (_d *TLSFingerprintProfileDelete) sqlExec(ctx context.Context) (int, error) { + _spec := sqlgraph.NewDeleteSpec(tlsfingerprintprofile.Table, sqlgraph.NewFieldSpec(tlsfingerprintprofile.FieldID, field.TypeInt64)) + if ps := _d.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + affected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec) + if err != nil && sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + _d.mutation.done = true + return affected, err +} + +// TLSFingerprintProfileDeleteOne is the builder for deleting a single TLSFingerprintProfile entity. +type TLSFingerprintProfileDeleteOne struct { + _d *TLSFingerprintProfileDelete +} + +// Where appends a list predicates to the TLSFingerprintProfileDelete builder. +func (_d *TLSFingerprintProfileDeleteOne) Where(ps ...predicate.TLSFingerprintProfile) *TLSFingerprintProfileDeleteOne { + _d._d.mutation.Where(ps...) + return _d +} + +// Exec executes the deletion query. +func (_d *TLSFingerprintProfileDeleteOne) Exec(ctx context.Context) error { + n, err := _d._d.Exec(ctx) + switch { + case err != nil: + return err + case n == 0: + return &NotFoundError{tlsfingerprintprofile.Label} + default: + return nil + } +} + +// ExecX is like Exec, but panics if an error occurs. +func (_d *TLSFingerprintProfileDeleteOne) ExecX(ctx context.Context) { + if err := _d.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/backend/ent/tlsfingerprintprofile_query.go b/backend/ent/tlsfingerprintprofile_query.go new file mode 100644 index 00000000..d1ef4f1d --- /dev/null +++ b/backend/ent/tlsfingerprintprofile_query.go @@ -0,0 +1,564 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "fmt" + "math" + + "entgo.io/ent" + "entgo.io/ent/dialect" + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/Wei-Shaw/sub2api/ent/predicate" + "github.com/Wei-Shaw/sub2api/ent/tlsfingerprintprofile" +) + +// TLSFingerprintProfileQuery is the builder for querying TLSFingerprintProfile entities. +type TLSFingerprintProfileQuery struct { + config + ctx *QueryContext + order []tlsfingerprintprofile.OrderOption + inters []Interceptor + predicates []predicate.TLSFingerprintProfile + modifiers []func(*sql.Selector) + // intermediate query (i.e. traversal path). + sql *sql.Selector + path func(context.Context) (*sql.Selector, error) +} + +// Where adds a new predicate for the TLSFingerprintProfileQuery builder. +func (_q *TLSFingerprintProfileQuery) Where(ps ...predicate.TLSFingerprintProfile) *TLSFingerprintProfileQuery { + _q.predicates = append(_q.predicates, ps...) + return _q +} + +// Limit the number of records to be returned by this query. +func (_q *TLSFingerprintProfileQuery) Limit(limit int) *TLSFingerprintProfileQuery { + _q.ctx.Limit = &limit + return _q +} + +// Offset to start from. +func (_q *TLSFingerprintProfileQuery) Offset(offset int) *TLSFingerprintProfileQuery { + _q.ctx.Offset = &offset + return _q +} + +// Unique configures the query builder to filter duplicate records on query. +// By default, unique is set to true, and can be disabled using this method. +func (_q *TLSFingerprintProfileQuery) Unique(unique bool) *TLSFingerprintProfileQuery { + _q.ctx.Unique = &unique + return _q +} + +// Order specifies how the records should be ordered. +func (_q *TLSFingerprintProfileQuery) Order(o ...tlsfingerprintprofile.OrderOption) *TLSFingerprintProfileQuery { + _q.order = append(_q.order, o...) + return _q +} + +// First returns the first TLSFingerprintProfile entity from the query. +// Returns a *NotFoundError when no TLSFingerprintProfile was found. +func (_q *TLSFingerprintProfileQuery) First(ctx context.Context) (*TLSFingerprintProfile, error) { + nodes, err := _q.Limit(1).All(setContextOp(ctx, _q.ctx, ent.OpQueryFirst)) + if err != nil { + return nil, err + } + if len(nodes) == 0 { + return nil, &NotFoundError{tlsfingerprintprofile.Label} + } + return nodes[0], nil +} + +// FirstX is like First, but panics if an error occurs. +func (_q *TLSFingerprintProfileQuery) FirstX(ctx context.Context) *TLSFingerprintProfile { + node, err := _q.First(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return node +} + +// FirstID returns the first TLSFingerprintProfile ID from the query. +// Returns a *NotFoundError when no TLSFingerprintProfile ID was found. +func (_q *TLSFingerprintProfileQuery) FirstID(ctx context.Context) (id int64, err error) { + var ids []int64 + if ids, err = _q.Limit(1).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryFirstID)); err != nil { + return + } + if len(ids) == 0 { + err = &NotFoundError{tlsfingerprintprofile.Label} + return + } + return ids[0], nil +} + +// FirstIDX is like FirstID, but panics if an error occurs. +func (_q *TLSFingerprintProfileQuery) FirstIDX(ctx context.Context) int64 { + id, err := _q.FirstID(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return id +} + +// Only returns a single TLSFingerprintProfile entity found by the query, ensuring it only returns one. +// Returns a *NotSingularError when more than one TLSFingerprintProfile entity is found. +// Returns a *NotFoundError when no TLSFingerprintProfile entities are found. +func (_q *TLSFingerprintProfileQuery) Only(ctx context.Context) (*TLSFingerprintProfile, error) { + nodes, err := _q.Limit(2).All(setContextOp(ctx, _q.ctx, ent.OpQueryOnly)) + if err != nil { + return nil, err + } + switch len(nodes) { + case 1: + return nodes[0], nil + case 0: + return nil, &NotFoundError{tlsfingerprintprofile.Label} + default: + return nil, &NotSingularError{tlsfingerprintprofile.Label} + } +} + +// OnlyX is like Only, but panics if an error occurs. +func (_q *TLSFingerprintProfileQuery) OnlyX(ctx context.Context) *TLSFingerprintProfile { + node, err := _q.Only(ctx) + if err != nil { + panic(err) + } + return node +} + +// OnlyID is like Only, but returns the only TLSFingerprintProfile ID in the query. +// Returns a *NotSingularError when more than one TLSFingerprintProfile ID is found. +// Returns a *NotFoundError when no entities are found. +func (_q *TLSFingerprintProfileQuery) OnlyID(ctx context.Context) (id int64, err error) { + var ids []int64 + if ids, err = _q.Limit(2).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryOnlyID)); err != nil { + return + } + switch len(ids) { + case 1: + id = ids[0] + case 0: + err = &NotFoundError{tlsfingerprintprofile.Label} + default: + err = &NotSingularError{tlsfingerprintprofile.Label} + } + return +} + +// OnlyIDX is like OnlyID, but panics if an error occurs. +func (_q *TLSFingerprintProfileQuery) OnlyIDX(ctx context.Context) int64 { + id, err := _q.OnlyID(ctx) + if err != nil { + panic(err) + } + return id +} + +// All executes the query and returns a list of TLSFingerprintProfiles. +func (_q *TLSFingerprintProfileQuery) All(ctx context.Context) ([]*TLSFingerprintProfile, error) { + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll) + if err := _q.prepareQuery(ctx); err != nil { + return nil, err + } + qr := querierAll[[]*TLSFingerprintProfile, *TLSFingerprintProfileQuery]() + return withInterceptors[[]*TLSFingerprintProfile](ctx, _q, qr, _q.inters) +} + +// AllX is like All, but panics if an error occurs. +func (_q *TLSFingerprintProfileQuery) AllX(ctx context.Context) []*TLSFingerprintProfile { + nodes, err := _q.All(ctx) + if err != nil { + panic(err) + } + return nodes +} + +// IDs executes the query and returns a list of TLSFingerprintProfile IDs. +func (_q *TLSFingerprintProfileQuery) IDs(ctx context.Context) (ids []int64, err error) { + if _q.ctx.Unique == nil && _q.path != nil { + _q.Unique(true) + } + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryIDs) + if err = _q.Select(tlsfingerprintprofile.FieldID).Scan(ctx, &ids); err != nil { + return nil, err + } + return ids, nil +} + +// IDsX is like IDs, but panics if an error occurs. +func (_q *TLSFingerprintProfileQuery) IDsX(ctx context.Context) []int64 { + ids, err := _q.IDs(ctx) + if err != nil { + panic(err) + } + return ids +} + +// Count returns the count of the given query. +func (_q *TLSFingerprintProfileQuery) Count(ctx context.Context) (int, error) { + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryCount) + if err := _q.prepareQuery(ctx); err != nil { + return 0, err + } + return withInterceptors[int](ctx, _q, querierCount[*TLSFingerprintProfileQuery](), _q.inters) +} + +// CountX is like Count, but panics if an error occurs. +func (_q *TLSFingerprintProfileQuery) CountX(ctx context.Context) int { + count, err := _q.Count(ctx) + if err != nil { + panic(err) + } + return count +} + +// Exist returns true if the query has elements in the graph. +func (_q *TLSFingerprintProfileQuery) Exist(ctx context.Context) (bool, error) { + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryExist) + switch _, err := _q.FirstID(ctx); { + case IsNotFound(err): + return false, nil + case err != nil: + return false, fmt.Errorf("ent: check existence: %w", err) + default: + return true, nil + } +} + +// ExistX is like Exist, but panics if an error occurs. +func (_q *TLSFingerprintProfileQuery) ExistX(ctx context.Context) bool { + exist, err := _q.Exist(ctx) + if err != nil { + panic(err) + } + return exist +} + +// Clone returns a duplicate of the TLSFingerprintProfileQuery builder, including all associated steps. It can be +// used to prepare common query builders and use them differently after the clone is made. +func (_q *TLSFingerprintProfileQuery) Clone() *TLSFingerprintProfileQuery { + if _q == nil { + return nil + } + return &TLSFingerprintProfileQuery{ + config: _q.config, + ctx: _q.ctx.Clone(), + order: append([]tlsfingerprintprofile.OrderOption{}, _q.order...), + inters: append([]Interceptor{}, _q.inters...), + predicates: append([]predicate.TLSFingerprintProfile{}, _q.predicates...), + // clone intermediate query. + sql: _q.sql.Clone(), + path: _q.path, + } +} + +// GroupBy is used to group vertices by one or more fields/columns. +// It is often used with aggregate functions, like: count, max, mean, min, sum. +// +// Example: +// +// var v []struct { +// CreatedAt time.Time `json:"created_at,omitempty"` +// Count int `json:"count,omitempty"` +// } +// +// client.TLSFingerprintProfile.Query(). +// GroupBy(tlsfingerprintprofile.FieldCreatedAt). +// Aggregate(ent.Count()). +// Scan(ctx, &v) +func (_q *TLSFingerprintProfileQuery) GroupBy(field string, fields ...string) *TLSFingerprintProfileGroupBy { + _q.ctx.Fields = append([]string{field}, fields...) + grbuild := &TLSFingerprintProfileGroupBy{build: _q} + grbuild.flds = &_q.ctx.Fields + grbuild.label = tlsfingerprintprofile.Label + grbuild.scan = grbuild.Scan + return grbuild +} + +// Select allows the selection one or more fields/columns for the given query, +// instead of selecting all fields in the entity. +// +// Example: +// +// var v []struct { +// CreatedAt time.Time `json:"created_at,omitempty"` +// } +// +// client.TLSFingerprintProfile.Query(). +// Select(tlsfingerprintprofile.FieldCreatedAt). +// Scan(ctx, &v) +func (_q *TLSFingerprintProfileQuery) Select(fields ...string) *TLSFingerprintProfileSelect { + _q.ctx.Fields = append(_q.ctx.Fields, fields...) + sbuild := &TLSFingerprintProfileSelect{TLSFingerprintProfileQuery: _q} + sbuild.label = tlsfingerprintprofile.Label + sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan + return sbuild +} + +// Aggregate returns a TLSFingerprintProfileSelect configured with the given aggregations. +func (_q *TLSFingerprintProfileQuery) Aggregate(fns ...AggregateFunc) *TLSFingerprintProfileSelect { + return _q.Select().Aggregate(fns...) +} + +func (_q *TLSFingerprintProfileQuery) prepareQuery(ctx context.Context) error { + for _, inter := range _q.inters { + if inter == nil { + return fmt.Errorf("ent: uninitialized interceptor (forgotten import ent/runtime?)") + } + if trv, ok := inter.(Traverser); ok { + if err := trv.Traverse(ctx, _q); err != nil { + return err + } + } + } + for _, f := range _q.ctx.Fields { + if !tlsfingerprintprofile.ValidColumn(f) { + return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} + } + } + if _q.path != nil { + prev, err := _q.path(ctx) + if err != nil { + return err + } + _q.sql = prev + } + return nil +} + +func (_q *TLSFingerprintProfileQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*TLSFingerprintProfile, error) { + var ( + nodes = []*TLSFingerprintProfile{} + _spec = _q.querySpec() + ) + _spec.ScanValues = func(columns []string) ([]any, error) { + return (*TLSFingerprintProfile).scanValues(nil, columns) + } + _spec.Assign = func(columns []string, values []any) error { + node := &TLSFingerprintProfile{config: _q.config} + nodes = append(nodes, node) + return node.assignValues(columns, values) + } + if len(_q.modifiers) > 0 { + _spec.Modifiers = _q.modifiers + } + for i := range hooks { + hooks[i](ctx, _spec) + } + if err := sqlgraph.QueryNodes(ctx, _q.driver, _spec); err != nil { + return nil, err + } + if len(nodes) == 0 { + return nodes, nil + } + return nodes, nil +} + +func (_q *TLSFingerprintProfileQuery) sqlCount(ctx context.Context) (int, error) { + _spec := _q.querySpec() + if len(_q.modifiers) > 0 { + _spec.Modifiers = _q.modifiers + } + _spec.Node.Columns = _q.ctx.Fields + if len(_q.ctx.Fields) > 0 { + _spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique + } + return sqlgraph.CountNodes(ctx, _q.driver, _spec) +} + +func (_q *TLSFingerprintProfileQuery) querySpec() *sqlgraph.QuerySpec { + _spec := sqlgraph.NewQuerySpec(tlsfingerprintprofile.Table, tlsfingerprintprofile.Columns, sqlgraph.NewFieldSpec(tlsfingerprintprofile.FieldID, field.TypeInt64)) + _spec.From = _q.sql + if unique := _q.ctx.Unique; unique != nil { + _spec.Unique = *unique + } else if _q.path != nil { + _spec.Unique = true + } + if fields := _q.ctx.Fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, tlsfingerprintprofile.FieldID) + for i := range fields { + if fields[i] != tlsfingerprintprofile.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, fields[i]) + } + } + } + if ps := _q.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if limit := _q.ctx.Limit; limit != nil { + _spec.Limit = *limit + } + if offset := _q.ctx.Offset; offset != nil { + _spec.Offset = *offset + } + if ps := _q.order; len(ps) > 0 { + _spec.Order = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + return _spec +} + +func (_q *TLSFingerprintProfileQuery) sqlQuery(ctx context.Context) *sql.Selector { + builder := sql.Dialect(_q.driver.Dialect()) + t1 := builder.Table(tlsfingerprintprofile.Table) + columns := _q.ctx.Fields + if len(columns) == 0 { + columns = tlsfingerprintprofile.Columns + } + selector := builder.Select(t1.Columns(columns...)...).From(t1) + if _q.sql != nil { + selector = _q.sql + selector.Select(selector.Columns(columns...)...) + } + if _q.ctx.Unique != nil && *_q.ctx.Unique { + selector.Distinct() + } + for _, m := range _q.modifiers { + m(selector) + } + for _, p := range _q.predicates { + p(selector) + } + for _, p := range _q.order { + p(selector) + } + if offset := _q.ctx.Offset; offset != nil { + // limit is mandatory for offset clause. We start + // with default value, and override it below if needed. + selector.Offset(*offset).Limit(math.MaxInt32) + } + if limit := _q.ctx.Limit; limit != nil { + selector.Limit(*limit) + } + return selector +} + +// ForUpdate locks the selected rows against concurrent updates, and prevent them from being +// updated, deleted or "selected ... for update" by other sessions, until the transaction is +// either committed or rolled-back. +func (_q *TLSFingerprintProfileQuery) ForUpdate(opts ...sql.LockOption) *TLSFingerprintProfileQuery { + if _q.driver.Dialect() == dialect.Postgres { + _q.Unique(false) + } + _q.modifiers = append(_q.modifiers, func(s *sql.Selector) { + s.ForUpdate(opts...) + }) + return _q +} + +// ForShare behaves similarly to ForUpdate, except that it acquires a shared mode lock +// on any rows that are read. Other sessions can read the rows, but cannot modify them +// until your transaction commits. +func (_q *TLSFingerprintProfileQuery) ForShare(opts ...sql.LockOption) *TLSFingerprintProfileQuery { + if _q.driver.Dialect() == dialect.Postgres { + _q.Unique(false) + } + _q.modifiers = append(_q.modifiers, func(s *sql.Selector) { + s.ForShare(opts...) + }) + return _q +} + +// TLSFingerprintProfileGroupBy is the group-by builder for TLSFingerprintProfile entities. +type TLSFingerprintProfileGroupBy struct { + selector + build *TLSFingerprintProfileQuery +} + +// Aggregate adds the given aggregation functions to the group-by query. +func (_g *TLSFingerprintProfileGroupBy) Aggregate(fns ...AggregateFunc) *TLSFingerprintProfileGroupBy { + _g.fns = append(_g.fns, fns...) + return _g +} + +// Scan applies the selector query and scans the result into the given value. +func (_g *TLSFingerprintProfileGroupBy) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, _g.build.ctx, ent.OpQueryGroupBy) + if err := _g.build.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*TLSFingerprintProfileQuery, *TLSFingerprintProfileGroupBy](ctx, _g.build, _g, _g.build.inters, v) +} + +func (_g *TLSFingerprintProfileGroupBy) sqlScan(ctx context.Context, root *TLSFingerprintProfileQuery, v any) error { + selector := root.sqlQuery(ctx).Select() + aggregation := make([]string, 0, len(_g.fns)) + for _, fn := range _g.fns { + aggregation = append(aggregation, fn(selector)) + } + if len(selector.SelectedColumns()) == 0 { + columns := make([]string, 0, len(*_g.flds)+len(_g.fns)) + for _, f := range *_g.flds { + columns = append(columns, selector.C(f)) + } + columns = append(columns, aggregation...) + selector.Select(columns...) + } + selector.GroupBy(selector.Columns(*_g.flds...)...) + if err := selector.Err(); err != nil { + return err + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := _g.build.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} + +// TLSFingerprintProfileSelect is the builder for selecting fields of TLSFingerprintProfile entities. +type TLSFingerprintProfileSelect struct { + *TLSFingerprintProfileQuery + selector +} + +// Aggregate adds the given aggregation functions to the selector query. +func (_s *TLSFingerprintProfileSelect) Aggregate(fns ...AggregateFunc) *TLSFingerprintProfileSelect { + _s.fns = append(_s.fns, fns...) + return _s +} + +// Scan applies the selector query and scans the result into the given value. +func (_s *TLSFingerprintProfileSelect) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, _s.ctx, ent.OpQuerySelect) + if err := _s.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*TLSFingerprintProfileQuery, *TLSFingerprintProfileSelect](ctx, _s.TLSFingerprintProfileQuery, _s, _s.inters, v) +} + +func (_s *TLSFingerprintProfileSelect) sqlScan(ctx context.Context, root *TLSFingerprintProfileQuery, v any) error { + selector := root.sqlQuery(ctx) + aggregation := make([]string, 0, len(_s.fns)) + for _, fn := range _s.fns { + aggregation = append(aggregation, fn(selector)) + } + switch n := len(*_s.selector.flds); { + case n == 0 && len(aggregation) > 0: + selector.Select(aggregation...) + case n != 0 && len(aggregation) > 0: + selector.AppendSelect(aggregation...) + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := _s.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} diff --git a/backend/ent/tlsfingerprintprofile_update.go b/backend/ent/tlsfingerprintprofile_update.go new file mode 100644 index 00000000..3b12508c --- /dev/null +++ b/backend/ent/tlsfingerprintprofile_update.go @@ -0,0 +1,881 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + "time" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/dialect/sql/sqljson" + "entgo.io/ent/schema/field" + "github.com/Wei-Shaw/sub2api/ent/predicate" + "github.com/Wei-Shaw/sub2api/ent/tlsfingerprintprofile" +) + +// TLSFingerprintProfileUpdate is the builder for updating TLSFingerprintProfile entities. +type TLSFingerprintProfileUpdate struct { + config + hooks []Hook + mutation *TLSFingerprintProfileMutation +} + +// Where appends a list predicates to the TLSFingerprintProfileUpdate builder. +func (_u *TLSFingerprintProfileUpdate) Where(ps ...predicate.TLSFingerprintProfile) *TLSFingerprintProfileUpdate { + _u.mutation.Where(ps...) + return _u +} + +// SetUpdatedAt sets the "updated_at" field. +func (_u *TLSFingerprintProfileUpdate) SetUpdatedAt(v time.Time) *TLSFingerprintProfileUpdate { + _u.mutation.SetUpdatedAt(v) + return _u +} + +// SetName sets the "name" field. +func (_u *TLSFingerprintProfileUpdate) SetName(v string) *TLSFingerprintProfileUpdate { + _u.mutation.SetName(v) + return _u +} + +// SetNillableName sets the "name" field if the given value is not nil. +func (_u *TLSFingerprintProfileUpdate) SetNillableName(v *string) *TLSFingerprintProfileUpdate { + if v != nil { + _u.SetName(*v) + } + return _u +} + +// SetDescription sets the "description" field. +func (_u *TLSFingerprintProfileUpdate) SetDescription(v string) *TLSFingerprintProfileUpdate { + _u.mutation.SetDescription(v) + return _u +} + +// SetNillableDescription sets the "description" field if the given value is not nil. +func (_u *TLSFingerprintProfileUpdate) SetNillableDescription(v *string) *TLSFingerprintProfileUpdate { + if v != nil { + _u.SetDescription(*v) + } + return _u +} + +// ClearDescription clears the value of the "description" field. +func (_u *TLSFingerprintProfileUpdate) ClearDescription() *TLSFingerprintProfileUpdate { + _u.mutation.ClearDescription() + return _u +} + +// SetEnableGrease sets the "enable_grease" field. +func (_u *TLSFingerprintProfileUpdate) SetEnableGrease(v bool) *TLSFingerprintProfileUpdate { + _u.mutation.SetEnableGrease(v) + return _u +} + +// SetNillableEnableGrease sets the "enable_grease" field if the given value is not nil. +func (_u *TLSFingerprintProfileUpdate) SetNillableEnableGrease(v *bool) *TLSFingerprintProfileUpdate { + if v != nil { + _u.SetEnableGrease(*v) + } + return _u +} + +// SetCipherSuites sets the "cipher_suites" field. +func (_u *TLSFingerprintProfileUpdate) SetCipherSuites(v []uint16) *TLSFingerprintProfileUpdate { + _u.mutation.SetCipherSuites(v) + return _u +} + +// AppendCipherSuites appends value to the "cipher_suites" field. +func (_u *TLSFingerprintProfileUpdate) AppendCipherSuites(v []uint16) *TLSFingerprintProfileUpdate { + _u.mutation.AppendCipherSuites(v) + return _u +} + +// ClearCipherSuites clears the value of the "cipher_suites" field. +func (_u *TLSFingerprintProfileUpdate) ClearCipherSuites() *TLSFingerprintProfileUpdate { + _u.mutation.ClearCipherSuites() + return _u +} + +// SetCurves sets the "curves" field. +func (_u *TLSFingerprintProfileUpdate) SetCurves(v []uint16) *TLSFingerprintProfileUpdate { + _u.mutation.SetCurves(v) + return _u +} + +// AppendCurves appends value to the "curves" field. +func (_u *TLSFingerprintProfileUpdate) AppendCurves(v []uint16) *TLSFingerprintProfileUpdate { + _u.mutation.AppendCurves(v) + return _u +} + +// ClearCurves clears the value of the "curves" field. +func (_u *TLSFingerprintProfileUpdate) ClearCurves() *TLSFingerprintProfileUpdate { + _u.mutation.ClearCurves() + return _u +} + +// SetPointFormats sets the "point_formats" field. +func (_u *TLSFingerprintProfileUpdate) SetPointFormats(v []uint16) *TLSFingerprintProfileUpdate { + _u.mutation.SetPointFormats(v) + return _u +} + +// AppendPointFormats appends value to the "point_formats" field. +func (_u *TLSFingerprintProfileUpdate) AppendPointFormats(v []uint16) *TLSFingerprintProfileUpdate { + _u.mutation.AppendPointFormats(v) + return _u +} + +// ClearPointFormats clears the value of the "point_formats" field. +func (_u *TLSFingerprintProfileUpdate) ClearPointFormats() *TLSFingerprintProfileUpdate { + _u.mutation.ClearPointFormats() + return _u +} + +// SetSignatureAlgorithms sets the "signature_algorithms" field. +func (_u *TLSFingerprintProfileUpdate) SetSignatureAlgorithms(v []uint16) *TLSFingerprintProfileUpdate { + _u.mutation.SetSignatureAlgorithms(v) + return _u +} + +// AppendSignatureAlgorithms appends value to the "signature_algorithms" field. +func (_u *TLSFingerprintProfileUpdate) AppendSignatureAlgorithms(v []uint16) *TLSFingerprintProfileUpdate { + _u.mutation.AppendSignatureAlgorithms(v) + return _u +} + +// ClearSignatureAlgorithms clears the value of the "signature_algorithms" field. +func (_u *TLSFingerprintProfileUpdate) ClearSignatureAlgorithms() *TLSFingerprintProfileUpdate { + _u.mutation.ClearSignatureAlgorithms() + return _u +} + +// SetAlpnProtocols sets the "alpn_protocols" field. +func (_u *TLSFingerprintProfileUpdate) SetAlpnProtocols(v []string) *TLSFingerprintProfileUpdate { + _u.mutation.SetAlpnProtocols(v) + return _u +} + +// AppendAlpnProtocols appends value to the "alpn_protocols" field. +func (_u *TLSFingerprintProfileUpdate) AppendAlpnProtocols(v []string) *TLSFingerprintProfileUpdate { + _u.mutation.AppendAlpnProtocols(v) + return _u +} + +// ClearAlpnProtocols clears the value of the "alpn_protocols" field. +func (_u *TLSFingerprintProfileUpdate) ClearAlpnProtocols() *TLSFingerprintProfileUpdate { + _u.mutation.ClearAlpnProtocols() + return _u +} + +// SetSupportedVersions sets the "supported_versions" field. +func (_u *TLSFingerprintProfileUpdate) SetSupportedVersions(v []uint16) *TLSFingerprintProfileUpdate { + _u.mutation.SetSupportedVersions(v) + return _u +} + +// AppendSupportedVersions appends value to the "supported_versions" field. +func (_u *TLSFingerprintProfileUpdate) AppendSupportedVersions(v []uint16) *TLSFingerprintProfileUpdate { + _u.mutation.AppendSupportedVersions(v) + return _u +} + +// ClearSupportedVersions clears the value of the "supported_versions" field. +func (_u *TLSFingerprintProfileUpdate) ClearSupportedVersions() *TLSFingerprintProfileUpdate { + _u.mutation.ClearSupportedVersions() + return _u +} + +// SetKeyShareGroups sets the "key_share_groups" field. +func (_u *TLSFingerprintProfileUpdate) SetKeyShareGroups(v []uint16) *TLSFingerprintProfileUpdate { + _u.mutation.SetKeyShareGroups(v) + return _u +} + +// AppendKeyShareGroups appends value to the "key_share_groups" field. +func (_u *TLSFingerprintProfileUpdate) AppendKeyShareGroups(v []uint16) *TLSFingerprintProfileUpdate { + _u.mutation.AppendKeyShareGroups(v) + return _u +} + +// ClearKeyShareGroups clears the value of the "key_share_groups" field. +func (_u *TLSFingerprintProfileUpdate) ClearKeyShareGroups() *TLSFingerprintProfileUpdate { + _u.mutation.ClearKeyShareGroups() + return _u +} + +// SetPskModes sets the "psk_modes" field. +func (_u *TLSFingerprintProfileUpdate) SetPskModes(v []uint16) *TLSFingerprintProfileUpdate { + _u.mutation.SetPskModes(v) + return _u +} + +// AppendPskModes appends value to the "psk_modes" field. +func (_u *TLSFingerprintProfileUpdate) AppendPskModes(v []uint16) *TLSFingerprintProfileUpdate { + _u.mutation.AppendPskModes(v) + return _u +} + +// ClearPskModes clears the value of the "psk_modes" field. +func (_u *TLSFingerprintProfileUpdate) ClearPskModes() *TLSFingerprintProfileUpdate { + _u.mutation.ClearPskModes() + return _u +} + +// SetExtensions sets the "extensions" field. +func (_u *TLSFingerprintProfileUpdate) SetExtensions(v []uint16) *TLSFingerprintProfileUpdate { + _u.mutation.SetExtensions(v) + return _u +} + +// AppendExtensions appends value to the "extensions" field. +func (_u *TLSFingerprintProfileUpdate) AppendExtensions(v []uint16) *TLSFingerprintProfileUpdate { + _u.mutation.AppendExtensions(v) + return _u +} + +// ClearExtensions clears the value of the "extensions" field. +func (_u *TLSFingerprintProfileUpdate) ClearExtensions() *TLSFingerprintProfileUpdate { + _u.mutation.ClearExtensions() + return _u +} + +// Mutation returns the TLSFingerprintProfileMutation object of the builder. +func (_u *TLSFingerprintProfileUpdate) Mutation() *TLSFingerprintProfileMutation { + return _u.mutation +} + +// Save executes the query and returns the number of nodes affected by the update operation. +func (_u *TLSFingerprintProfileUpdate) Save(ctx context.Context) (int, error) { + _u.defaults() + return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (_u *TLSFingerprintProfileUpdate) SaveX(ctx context.Context) int { + affected, err := _u.Save(ctx) + if err != nil { + panic(err) + } + return affected +} + +// Exec executes the query. +func (_u *TLSFingerprintProfileUpdate) Exec(ctx context.Context) error { + _, err := _u.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_u *TLSFingerprintProfileUpdate) ExecX(ctx context.Context) { + if err := _u.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (_u *TLSFingerprintProfileUpdate) defaults() { + if _, ok := _u.mutation.UpdatedAt(); !ok { + v := tlsfingerprintprofile.UpdateDefaultUpdatedAt() + _u.mutation.SetUpdatedAt(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (_u *TLSFingerprintProfileUpdate) check() error { + if v, ok := _u.mutation.Name(); ok { + if err := tlsfingerprintprofile.NameValidator(v); err != nil { + return &ValidationError{Name: "name", err: fmt.Errorf(`ent: validator failed for field "TLSFingerprintProfile.name": %w`, err)} + } + } + return nil +} + +func (_u *TLSFingerprintProfileUpdate) sqlSave(ctx context.Context) (_node int, err error) { + if err := _u.check(); err != nil { + return _node, err + } + _spec := sqlgraph.NewUpdateSpec(tlsfingerprintprofile.Table, tlsfingerprintprofile.Columns, sqlgraph.NewFieldSpec(tlsfingerprintprofile.FieldID, field.TypeInt64)) + if ps := _u.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := _u.mutation.UpdatedAt(); ok { + _spec.SetField(tlsfingerprintprofile.FieldUpdatedAt, field.TypeTime, value) + } + if value, ok := _u.mutation.Name(); ok { + _spec.SetField(tlsfingerprintprofile.FieldName, field.TypeString, value) + } + if value, ok := _u.mutation.Description(); ok { + _spec.SetField(tlsfingerprintprofile.FieldDescription, field.TypeString, value) + } + if _u.mutation.DescriptionCleared() { + _spec.ClearField(tlsfingerprintprofile.FieldDescription, field.TypeString) + } + if value, ok := _u.mutation.EnableGrease(); ok { + _spec.SetField(tlsfingerprintprofile.FieldEnableGrease, field.TypeBool, value) + } + if value, ok := _u.mutation.CipherSuites(); ok { + _spec.SetField(tlsfingerprintprofile.FieldCipherSuites, field.TypeJSON, value) + } + if value, ok := _u.mutation.AppendedCipherSuites(); ok { + _spec.AddModifier(func(u *sql.UpdateBuilder) { + sqljson.Append(u, tlsfingerprintprofile.FieldCipherSuites, value) + }) + } + if _u.mutation.CipherSuitesCleared() { + _spec.ClearField(tlsfingerprintprofile.FieldCipherSuites, field.TypeJSON) + } + if value, ok := _u.mutation.Curves(); ok { + _spec.SetField(tlsfingerprintprofile.FieldCurves, field.TypeJSON, value) + } + if value, ok := _u.mutation.AppendedCurves(); ok { + _spec.AddModifier(func(u *sql.UpdateBuilder) { + sqljson.Append(u, tlsfingerprintprofile.FieldCurves, value) + }) + } + if _u.mutation.CurvesCleared() { + _spec.ClearField(tlsfingerprintprofile.FieldCurves, field.TypeJSON) + } + if value, ok := _u.mutation.PointFormats(); ok { + _spec.SetField(tlsfingerprintprofile.FieldPointFormats, field.TypeJSON, value) + } + if value, ok := _u.mutation.AppendedPointFormats(); ok { + _spec.AddModifier(func(u *sql.UpdateBuilder) { + sqljson.Append(u, tlsfingerprintprofile.FieldPointFormats, value) + }) + } + if _u.mutation.PointFormatsCleared() { + _spec.ClearField(tlsfingerprintprofile.FieldPointFormats, field.TypeJSON) + } + if value, ok := _u.mutation.SignatureAlgorithms(); ok { + _spec.SetField(tlsfingerprintprofile.FieldSignatureAlgorithms, field.TypeJSON, value) + } + if value, ok := _u.mutation.AppendedSignatureAlgorithms(); ok { + _spec.AddModifier(func(u *sql.UpdateBuilder) { + sqljson.Append(u, tlsfingerprintprofile.FieldSignatureAlgorithms, value) + }) + } + if _u.mutation.SignatureAlgorithmsCleared() { + _spec.ClearField(tlsfingerprintprofile.FieldSignatureAlgorithms, field.TypeJSON) + } + if value, ok := _u.mutation.AlpnProtocols(); ok { + _spec.SetField(tlsfingerprintprofile.FieldAlpnProtocols, field.TypeJSON, value) + } + if value, ok := _u.mutation.AppendedAlpnProtocols(); ok { + _spec.AddModifier(func(u *sql.UpdateBuilder) { + sqljson.Append(u, tlsfingerprintprofile.FieldAlpnProtocols, value) + }) + } + if _u.mutation.AlpnProtocolsCleared() { + _spec.ClearField(tlsfingerprintprofile.FieldAlpnProtocols, field.TypeJSON) + } + if value, ok := _u.mutation.SupportedVersions(); ok { + _spec.SetField(tlsfingerprintprofile.FieldSupportedVersions, field.TypeJSON, value) + } + if value, ok := _u.mutation.AppendedSupportedVersions(); ok { + _spec.AddModifier(func(u *sql.UpdateBuilder) { + sqljson.Append(u, tlsfingerprintprofile.FieldSupportedVersions, value) + }) + } + if _u.mutation.SupportedVersionsCleared() { + _spec.ClearField(tlsfingerprintprofile.FieldSupportedVersions, field.TypeJSON) + } + if value, ok := _u.mutation.KeyShareGroups(); ok { + _spec.SetField(tlsfingerprintprofile.FieldKeyShareGroups, field.TypeJSON, value) + } + if value, ok := _u.mutation.AppendedKeyShareGroups(); ok { + _spec.AddModifier(func(u *sql.UpdateBuilder) { + sqljson.Append(u, tlsfingerprintprofile.FieldKeyShareGroups, value) + }) + } + if _u.mutation.KeyShareGroupsCleared() { + _spec.ClearField(tlsfingerprintprofile.FieldKeyShareGroups, field.TypeJSON) + } + if value, ok := _u.mutation.PskModes(); ok { + _spec.SetField(tlsfingerprintprofile.FieldPskModes, field.TypeJSON, value) + } + if value, ok := _u.mutation.AppendedPskModes(); ok { + _spec.AddModifier(func(u *sql.UpdateBuilder) { + sqljson.Append(u, tlsfingerprintprofile.FieldPskModes, value) + }) + } + if _u.mutation.PskModesCleared() { + _spec.ClearField(tlsfingerprintprofile.FieldPskModes, field.TypeJSON) + } + if value, ok := _u.mutation.Extensions(); ok { + _spec.SetField(tlsfingerprintprofile.FieldExtensions, field.TypeJSON, value) + } + if value, ok := _u.mutation.AppendedExtensions(); ok { + _spec.AddModifier(func(u *sql.UpdateBuilder) { + sqljson.Append(u, tlsfingerprintprofile.FieldExtensions, value) + }) + } + if _u.mutation.ExtensionsCleared() { + _spec.ClearField(tlsfingerprintprofile.FieldExtensions, field.TypeJSON) + } + if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{tlsfingerprintprofile.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return 0, err + } + _u.mutation.done = true + return _node, nil +} + +// TLSFingerprintProfileUpdateOne is the builder for updating a single TLSFingerprintProfile entity. +type TLSFingerprintProfileUpdateOne struct { + config + fields []string + hooks []Hook + mutation *TLSFingerprintProfileMutation +} + +// SetUpdatedAt sets the "updated_at" field. +func (_u *TLSFingerprintProfileUpdateOne) SetUpdatedAt(v time.Time) *TLSFingerprintProfileUpdateOne { + _u.mutation.SetUpdatedAt(v) + return _u +} + +// SetName sets the "name" field. +func (_u *TLSFingerprintProfileUpdateOne) SetName(v string) *TLSFingerprintProfileUpdateOne { + _u.mutation.SetName(v) + return _u +} + +// SetNillableName sets the "name" field if the given value is not nil. +func (_u *TLSFingerprintProfileUpdateOne) SetNillableName(v *string) *TLSFingerprintProfileUpdateOne { + if v != nil { + _u.SetName(*v) + } + return _u +} + +// SetDescription sets the "description" field. +func (_u *TLSFingerprintProfileUpdateOne) SetDescription(v string) *TLSFingerprintProfileUpdateOne { + _u.mutation.SetDescription(v) + return _u +} + +// SetNillableDescription sets the "description" field if the given value is not nil. +func (_u *TLSFingerprintProfileUpdateOne) SetNillableDescription(v *string) *TLSFingerprintProfileUpdateOne { + if v != nil { + _u.SetDescription(*v) + } + return _u +} + +// ClearDescription clears the value of the "description" field. +func (_u *TLSFingerprintProfileUpdateOne) ClearDescription() *TLSFingerprintProfileUpdateOne { + _u.mutation.ClearDescription() + return _u +} + +// SetEnableGrease sets the "enable_grease" field. +func (_u *TLSFingerprintProfileUpdateOne) SetEnableGrease(v bool) *TLSFingerprintProfileUpdateOne { + _u.mutation.SetEnableGrease(v) + return _u +} + +// SetNillableEnableGrease sets the "enable_grease" field if the given value is not nil. +func (_u *TLSFingerprintProfileUpdateOne) SetNillableEnableGrease(v *bool) *TLSFingerprintProfileUpdateOne { + if v != nil { + _u.SetEnableGrease(*v) + } + return _u +} + +// SetCipherSuites sets the "cipher_suites" field. +func (_u *TLSFingerprintProfileUpdateOne) SetCipherSuites(v []uint16) *TLSFingerprintProfileUpdateOne { + _u.mutation.SetCipherSuites(v) + return _u +} + +// AppendCipherSuites appends value to the "cipher_suites" field. +func (_u *TLSFingerprintProfileUpdateOne) AppendCipherSuites(v []uint16) *TLSFingerprintProfileUpdateOne { + _u.mutation.AppendCipherSuites(v) + return _u +} + +// ClearCipherSuites clears the value of the "cipher_suites" field. +func (_u *TLSFingerprintProfileUpdateOne) ClearCipherSuites() *TLSFingerprintProfileUpdateOne { + _u.mutation.ClearCipherSuites() + return _u +} + +// SetCurves sets the "curves" field. +func (_u *TLSFingerprintProfileUpdateOne) SetCurves(v []uint16) *TLSFingerprintProfileUpdateOne { + _u.mutation.SetCurves(v) + return _u +} + +// AppendCurves appends value to the "curves" field. +func (_u *TLSFingerprintProfileUpdateOne) AppendCurves(v []uint16) *TLSFingerprintProfileUpdateOne { + _u.mutation.AppendCurves(v) + return _u +} + +// ClearCurves clears the value of the "curves" field. +func (_u *TLSFingerprintProfileUpdateOne) ClearCurves() *TLSFingerprintProfileUpdateOne { + _u.mutation.ClearCurves() + return _u +} + +// SetPointFormats sets the "point_formats" field. +func (_u *TLSFingerprintProfileUpdateOne) SetPointFormats(v []uint16) *TLSFingerprintProfileUpdateOne { + _u.mutation.SetPointFormats(v) + return _u +} + +// AppendPointFormats appends value to the "point_formats" field. +func (_u *TLSFingerprintProfileUpdateOne) AppendPointFormats(v []uint16) *TLSFingerprintProfileUpdateOne { + _u.mutation.AppendPointFormats(v) + return _u +} + +// ClearPointFormats clears the value of the "point_formats" field. +func (_u *TLSFingerprintProfileUpdateOne) ClearPointFormats() *TLSFingerprintProfileUpdateOne { + _u.mutation.ClearPointFormats() + return _u +} + +// SetSignatureAlgorithms sets the "signature_algorithms" field. +func (_u *TLSFingerprintProfileUpdateOne) SetSignatureAlgorithms(v []uint16) *TLSFingerprintProfileUpdateOne { + _u.mutation.SetSignatureAlgorithms(v) + return _u +} + +// AppendSignatureAlgorithms appends value to the "signature_algorithms" field. +func (_u *TLSFingerprintProfileUpdateOne) AppendSignatureAlgorithms(v []uint16) *TLSFingerprintProfileUpdateOne { + _u.mutation.AppendSignatureAlgorithms(v) + return _u +} + +// ClearSignatureAlgorithms clears the value of the "signature_algorithms" field. +func (_u *TLSFingerprintProfileUpdateOne) ClearSignatureAlgorithms() *TLSFingerprintProfileUpdateOne { + _u.mutation.ClearSignatureAlgorithms() + return _u +} + +// SetAlpnProtocols sets the "alpn_protocols" field. +func (_u *TLSFingerprintProfileUpdateOne) SetAlpnProtocols(v []string) *TLSFingerprintProfileUpdateOne { + _u.mutation.SetAlpnProtocols(v) + return _u +} + +// AppendAlpnProtocols appends value to the "alpn_protocols" field. +func (_u *TLSFingerprintProfileUpdateOne) AppendAlpnProtocols(v []string) *TLSFingerprintProfileUpdateOne { + _u.mutation.AppendAlpnProtocols(v) + return _u +} + +// ClearAlpnProtocols clears the value of the "alpn_protocols" field. +func (_u *TLSFingerprintProfileUpdateOne) ClearAlpnProtocols() *TLSFingerprintProfileUpdateOne { + _u.mutation.ClearAlpnProtocols() + return _u +} + +// SetSupportedVersions sets the "supported_versions" field. +func (_u *TLSFingerprintProfileUpdateOne) SetSupportedVersions(v []uint16) *TLSFingerprintProfileUpdateOne { + _u.mutation.SetSupportedVersions(v) + return _u +} + +// AppendSupportedVersions appends value to the "supported_versions" field. +func (_u *TLSFingerprintProfileUpdateOne) AppendSupportedVersions(v []uint16) *TLSFingerprintProfileUpdateOne { + _u.mutation.AppendSupportedVersions(v) + return _u +} + +// ClearSupportedVersions clears the value of the "supported_versions" field. +func (_u *TLSFingerprintProfileUpdateOne) ClearSupportedVersions() *TLSFingerprintProfileUpdateOne { + _u.mutation.ClearSupportedVersions() + return _u +} + +// SetKeyShareGroups sets the "key_share_groups" field. +func (_u *TLSFingerprintProfileUpdateOne) SetKeyShareGroups(v []uint16) *TLSFingerprintProfileUpdateOne { + _u.mutation.SetKeyShareGroups(v) + return _u +} + +// AppendKeyShareGroups appends value to the "key_share_groups" field. +func (_u *TLSFingerprintProfileUpdateOne) AppendKeyShareGroups(v []uint16) *TLSFingerprintProfileUpdateOne { + _u.mutation.AppendKeyShareGroups(v) + return _u +} + +// ClearKeyShareGroups clears the value of the "key_share_groups" field. +func (_u *TLSFingerprintProfileUpdateOne) ClearKeyShareGroups() *TLSFingerprintProfileUpdateOne { + _u.mutation.ClearKeyShareGroups() + return _u +} + +// SetPskModes sets the "psk_modes" field. +func (_u *TLSFingerprintProfileUpdateOne) SetPskModes(v []uint16) *TLSFingerprintProfileUpdateOne { + _u.mutation.SetPskModes(v) + return _u +} + +// AppendPskModes appends value to the "psk_modes" field. +func (_u *TLSFingerprintProfileUpdateOne) AppendPskModes(v []uint16) *TLSFingerprintProfileUpdateOne { + _u.mutation.AppendPskModes(v) + return _u +} + +// ClearPskModes clears the value of the "psk_modes" field. +func (_u *TLSFingerprintProfileUpdateOne) ClearPskModes() *TLSFingerprintProfileUpdateOne { + _u.mutation.ClearPskModes() + return _u +} + +// SetExtensions sets the "extensions" field. +func (_u *TLSFingerprintProfileUpdateOne) SetExtensions(v []uint16) *TLSFingerprintProfileUpdateOne { + _u.mutation.SetExtensions(v) + return _u +} + +// AppendExtensions appends value to the "extensions" field. +func (_u *TLSFingerprintProfileUpdateOne) AppendExtensions(v []uint16) *TLSFingerprintProfileUpdateOne { + _u.mutation.AppendExtensions(v) + return _u +} + +// ClearExtensions clears the value of the "extensions" field. +func (_u *TLSFingerprintProfileUpdateOne) ClearExtensions() *TLSFingerprintProfileUpdateOne { + _u.mutation.ClearExtensions() + return _u +} + +// Mutation returns the TLSFingerprintProfileMutation object of the builder. +func (_u *TLSFingerprintProfileUpdateOne) Mutation() *TLSFingerprintProfileMutation { + return _u.mutation +} + +// Where appends a list predicates to the TLSFingerprintProfileUpdate builder. +func (_u *TLSFingerprintProfileUpdateOne) Where(ps ...predicate.TLSFingerprintProfile) *TLSFingerprintProfileUpdateOne { + _u.mutation.Where(ps...) + return _u +} + +// Select allows selecting one or more fields (columns) of the returned entity. +// The default is selecting all fields defined in the entity schema. +func (_u *TLSFingerprintProfileUpdateOne) Select(field string, fields ...string) *TLSFingerprintProfileUpdateOne { + _u.fields = append([]string{field}, fields...) + return _u +} + +// Save executes the query and returns the updated TLSFingerprintProfile entity. +func (_u *TLSFingerprintProfileUpdateOne) Save(ctx context.Context) (*TLSFingerprintProfile, error) { + _u.defaults() + return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (_u *TLSFingerprintProfileUpdateOne) SaveX(ctx context.Context) *TLSFingerprintProfile { + node, err := _u.Save(ctx) + if err != nil { + panic(err) + } + return node +} + +// Exec executes the query on the entity. +func (_u *TLSFingerprintProfileUpdateOne) Exec(ctx context.Context) error { + _, err := _u.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_u *TLSFingerprintProfileUpdateOne) ExecX(ctx context.Context) { + if err := _u.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (_u *TLSFingerprintProfileUpdateOne) defaults() { + if _, ok := _u.mutation.UpdatedAt(); !ok { + v := tlsfingerprintprofile.UpdateDefaultUpdatedAt() + _u.mutation.SetUpdatedAt(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (_u *TLSFingerprintProfileUpdateOne) check() error { + if v, ok := _u.mutation.Name(); ok { + if err := tlsfingerprintprofile.NameValidator(v); err != nil { + return &ValidationError{Name: "name", err: fmt.Errorf(`ent: validator failed for field "TLSFingerprintProfile.name": %w`, err)} + } + } + return nil +} + +func (_u *TLSFingerprintProfileUpdateOne) sqlSave(ctx context.Context) (_node *TLSFingerprintProfile, err error) { + if err := _u.check(); err != nil { + return _node, err + } + _spec := sqlgraph.NewUpdateSpec(tlsfingerprintprofile.Table, tlsfingerprintprofile.Columns, sqlgraph.NewFieldSpec(tlsfingerprintprofile.FieldID, field.TypeInt64)) + id, ok := _u.mutation.ID() + if !ok { + return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "TLSFingerprintProfile.id" for update`)} + } + _spec.Node.ID.Value = id + if fields := _u.fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, tlsfingerprintprofile.FieldID) + for _, f := range fields { + if !tlsfingerprintprofile.ValidColumn(f) { + return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} + } + if f != tlsfingerprintprofile.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, f) + } + } + } + if ps := _u.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := _u.mutation.UpdatedAt(); ok { + _spec.SetField(tlsfingerprintprofile.FieldUpdatedAt, field.TypeTime, value) + } + if value, ok := _u.mutation.Name(); ok { + _spec.SetField(tlsfingerprintprofile.FieldName, field.TypeString, value) + } + if value, ok := _u.mutation.Description(); ok { + _spec.SetField(tlsfingerprintprofile.FieldDescription, field.TypeString, value) + } + if _u.mutation.DescriptionCleared() { + _spec.ClearField(tlsfingerprintprofile.FieldDescription, field.TypeString) + } + if value, ok := _u.mutation.EnableGrease(); ok { + _spec.SetField(tlsfingerprintprofile.FieldEnableGrease, field.TypeBool, value) + } + if value, ok := _u.mutation.CipherSuites(); ok { + _spec.SetField(tlsfingerprintprofile.FieldCipherSuites, field.TypeJSON, value) + } + if value, ok := _u.mutation.AppendedCipherSuites(); ok { + _spec.AddModifier(func(u *sql.UpdateBuilder) { + sqljson.Append(u, tlsfingerprintprofile.FieldCipherSuites, value) + }) + } + if _u.mutation.CipherSuitesCleared() { + _spec.ClearField(tlsfingerprintprofile.FieldCipherSuites, field.TypeJSON) + } + if value, ok := _u.mutation.Curves(); ok { + _spec.SetField(tlsfingerprintprofile.FieldCurves, field.TypeJSON, value) + } + if value, ok := _u.mutation.AppendedCurves(); ok { + _spec.AddModifier(func(u *sql.UpdateBuilder) { + sqljson.Append(u, tlsfingerprintprofile.FieldCurves, value) + }) + } + if _u.mutation.CurvesCleared() { + _spec.ClearField(tlsfingerprintprofile.FieldCurves, field.TypeJSON) + } + if value, ok := _u.mutation.PointFormats(); ok { + _spec.SetField(tlsfingerprintprofile.FieldPointFormats, field.TypeJSON, value) + } + if value, ok := _u.mutation.AppendedPointFormats(); ok { + _spec.AddModifier(func(u *sql.UpdateBuilder) { + sqljson.Append(u, tlsfingerprintprofile.FieldPointFormats, value) + }) + } + if _u.mutation.PointFormatsCleared() { + _spec.ClearField(tlsfingerprintprofile.FieldPointFormats, field.TypeJSON) + } + if value, ok := _u.mutation.SignatureAlgorithms(); ok { + _spec.SetField(tlsfingerprintprofile.FieldSignatureAlgorithms, field.TypeJSON, value) + } + if value, ok := _u.mutation.AppendedSignatureAlgorithms(); ok { + _spec.AddModifier(func(u *sql.UpdateBuilder) { + sqljson.Append(u, tlsfingerprintprofile.FieldSignatureAlgorithms, value) + }) + } + if _u.mutation.SignatureAlgorithmsCleared() { + _spec.ClearField(tlsfingerprintprofile.FieldSignatureAlgorithms, field.TypeJSON) + } + if value, ok := _u.mutation.AlpnProtocols(); ok { + _spec.SetField(tlsfingerprintprofile.FieldAlpnProtocols, field.TypeJSON, value) + } + if value, ok := _u.mutation.AppendedAlpnProtocols(); ok { + _spec.AddModifier(func(u *sql.UpdateBuilder) { + sqljson.Append(u, tlsfingerprintprofile.FieldAlpnProtocols, value) + }) + } + if _u.mutation.AlpnProtocolsCleared() { + _spec.ClearField(tlsfingerprintprofile.FieldAlpnProtocols, field.TypeJSON) + } + if value, ok := _u.mutation.SupportedVersions(); ok { + _spec.SetField(tlsfingerprintprofile.FieldSupportedVersions, field.TypeJSON, value) + } + if value, ok := _u.mutation.AppendedSupportedVersions(); ok { + _spec.AddModifier(func(u *sql.UpdateBuilder) { + sqljson.Append(u, tlsfingerprintprofile.FieldSupportedVersions, value) + }) + } + if _u.mutation.SupportedVersionsCleared() { + _spec.ClearField(tlsfingerprintprofile.FieldSupportedVersions, field.TypeJSON) + } + if value, ok := _u.mutation.KeyShareGroups(); ok { + _spec.SetField(tlsfingerprintprofile.FieldKeyShareGroups, field.TypeJSON, value) + } + if value, ok := _u.mutation.AppendedKeyShareGroups(); ok { + _spec.AddModifier(func(u *sql.UpdateBuilder) { + sqljson.Append(u, tlsfingerprintprofile.FieldKeyShareGroups, value) + }) + } + if _u.mutation.KeyShareGroupsCleared() { + _spec.ClearField(tlsfingerprintprofile.FieldKeyShareGroups, field.TypeJSON) + } + if value, ok := _u.mutation.PskModes(); ok { + _spec.SetField(tlsfingerprintprofile.FieldPskModes, field.TypeJSON, value) + } + if value, ok := _u.mutation.AppendedPskModes(); ok { + _spec.AddModifier(func(u *sql.UpdateBuilder) { + sqljson.Append(u, tlsfingerprintprofile.FieldPskModes, value) + }) + } + if _u.mutation.PskModesCleared() { + _spec.ClearField(tlsfingerprintprofile.FieldPskModes, field.TypeJSON) + } + if value, ok := _u.mutation.Extensions(); ok { + _spec.SetField(tlsfingerprintprofile.FieldExtensions, field.TypeJSON, value) + } + if value, ok := _u.mutation.AppendedExtensions(); ok { + _spec.AddModifier(func(u *sql.UpdateBuilder) { + sqljson.Append(u, tlsfingerprintprofile.FieldExtensions, value) + }) + } + if _u.mutation.ExtensionsCleared() { + _spec.ClearField(tlsfingerprintprofile.FieldExtensions, field.TypeJSON) + } + _node = &TLSFingerprintProfile{config: _u.config} + _spec.Assign = _node.assignValues + _spec.ScanValues = _node.scanValues + if err = sqlgraph.UpdateNode(ctx, _u.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{tlsfingerprintprofile.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + _u.mutation.done = true + return _node, nil +} diff --git a/backend/ent/tx.go b/backend/ent/tx.go index cd3b2296..b5aea447 100644 --- a/backend/ent/tx.go +++ b/backend/ent/tx.go @@ -42,6 +42,8 @@ type Tx struct { SecuritySecret *SecuritySecretClient // Setting is the client for interacting with the Setting builders. Setting *SettingClient + // TLSFingerprintProfile is the client for interacting with the TLSFingerprintProfile builders. + TLSFingerprintProfile *TLSFingerprintProfileClient // UsageCleanupTask is the client for interacting with the UsageCleanupTask builders. UsageCleanupTask *UsageCleanupTaskClient // UsageLog is the client for interacting with the UsageLog builders. @@ -201,6 +203,7 @@ func (tx *Tx) init() { tx.RedeemCode = NewRedeemCodeClient(tx.config) tx.SecuritySecret = NewSecuritySecretClient(tx.config) tx.Setting = NewSettingClient(tx.config) + tx.TLSFingerprintProfile = NewTLSFingerprintProfileClient(tx.config) tx.UsageCleanupTask = NewUsageCleanupTaskClient(tx.config) tx.UsageLog = NewUsageLogClient(tx.config) tx.User = NewUserClient(tx.config) diff --git a/backend/internal/config/config.go b/backend/internal/config/config.go index e90e56af..d1cb76db 100644 --- a/backend/internal/config/config.go +++ b/backend/internal/config/config.go @@ -656,17 +656,33 @@ type TLSFingerprintConfig struct { } // TLSProfileConfig 单个TLS指纹模板的配置 +// 所有列表字段为空时使用内置默认值(Claude CLI 2.x / Node.js 20.x) +// 建议通过 TLS 指纹采集工具 (tests/tls-fingerprint-web) 获取完整配置 type TLSProfileConfig struct { // Name: 模板显示名称 Name string `mapstructure:"name"` // EnableGREASE: 是否启用GREASE扩展(Chrome使用,Node.js不使用) EnableGREASE bool `mapstructure:"enable_grease"` - // CipherSuites: TLS加密套件列表(空则使用内置默认值) + // CipherSuites: TLS加密套件列表 CipherSuites []uint16 `mapstructure:"cipher_suites"` - // Curves: 椭圆曲线列表(空则使用内置默认值) + // Curves: 椭圆曲线列表 Curves []uint16 `mapstructure:"curves"` - // PointFormats: 点格式列表(空则使用内置默认值) - PointFormats []uint8 `mapstructure:"point_formats"` + // PointFormats: 点格式列表 + PointFormats []uint16 `mapstructure:"point_formats"` + // SignatureAlgorithms: 签名算法列表 + SignatureAlgorithms []uint16 `mapstructure:"signature_algorithms"` + // ALPNProtocols: ALPN协议列表(如 ["h2", "http/1.1"]) + ALPNProtocols []string `mapstructure:"alpn_protocols"` + // SupportedVersions: 支持的TLS版本列表(如 [0x0304, 0x0303] 即 TLS1.3, TLS1.2) + SupportedVersions []uint16 `mapstructure:"supported_versions"` + // KeyShareGroups: Key Share中发送的曲线组(如 [29] 即 X25519) + KeyShareGroups []uint16 `mapstructure:"key_share_groups"` + // PSKModes: PSK密钥交换模式(如 [1] 即 psk_dhe_ke) + PSKModes []uint16 `mapstructure:"psk_modes"` + // Extensions: TLS扩展类型ID列表,按发送顺序排列 + // 空则使用内置默认顺序 [0,11,10,35,16,22,23,13,43,45,51] + // GREASE值(如0x0a0a)会自动插入GREASE扩展 + Extensions []uint16 `mapstructure:"extensions"` } // GatewaySchedulingConfig accounts scheduling configuration. diff --git a/backend/internal/handler/admin/tls_fingerprint_profile_handler.go b/backend/internal/handler/admin/tls_fingerprint_profile_handler.go new file mode 100644 index 00000000..38f97555 --- /dev/null +++ b/backend/internal/handler/admin/tls_fingerprint_profile_handler.go @@ -0,0 +1,234 @@ +package admin + +import ( + "strconv" + + "github.com/Wei-Shaw/sub2api/internal/model" + "github.com/Wei-Shaw/sub2api/internal/pkg/response" + "github.com/Wei-Shaw/sub2api/internal/service" + "github.com/gin-gonic/gin" +) + +// TLSFingerprintProfileHandler 处理 TLS 指纹模板的 HTTP 请求 +type TLSFingerprintProfileHandler struct { + service *service.TLSFingerprintProfileService +} + +// NewTLSFingerprintProfileHandler 创建 TLS 指纹模板处理器 +func NewTLSFingerprintProfileHandler(service *service.TLSFingerprintProfileService) *TLSFingerprintProfileHandler { + return &TLSFingerprintProfileHandler{service: service} +} + +// CreateTLSFingerprintProfileRequest 创建模板请求 +type CreateTLSFingerprintProfileRequest struct { + Name string `json:"name" binding:"required"` + Description *string `json:"description"` + EnableGREASE *bool `json:"enable_grease"` + CipherSuites []uint16 `json:"cipher_suites"` + Curves []uint16 `json:"curves"` + PointFormats []uint16 `json:"point_formats"` + SignatureAlgorithms []uint16 `json:"signature_algorithms"` + ALPNProtocols []string `json:"alpn_protocols"` + SupportedVersions []uint16 `json:"supported_versions"` + KeyShareGroups []uint16 `json:"key_share_groups"` + PSKModes []uint16 `json:"psk_modes"` + Extensions []uint16 `json:"extensions"` +} + +// UpdateTLSFingerprintProfileRequest 更新模板请求(部分更新) +type UpdateTLSFingerprintProfileRequest struct { + Name *string `json:"name"` + Description *string `json:"description"` + EnableGREASE *bool `json:"enable_grease"` + CipherSuites []uint16 `json:"cipher_suites"` + Curves []uint16 `json:"curves"` + PointFormats []uint16 `json:"point_formats"` + SignatureAlgorithms []uint16 `json:"signature_algorithms"` + ALPNProtocols []string `json:"alpn_protocols"` + SupportedVersions []uint16 `json:"supported_versions"` + KeyShareGroups []uint16 `json:"key_share_groups"` + PSKModes []uint16 `json:"psk_modes"` + Extensions []uint16 `json:"extensions"` +} + +// List 获取所有模板 +// GET /api/v1/admin/tls-fingerprint-profiles +func (h *TLSFingerprintProfileHandler) List(c *gin.Context) { + profiles, err := h.service.List(c.Request.Context()) + if err != nil { + response.ErrorFrom(c, err) + return + } + response.Success(c, profiles) +} + +// GetByID 根据 ID 获取模板 +// GET /api/v1/admin/tls-fingerprint-profiles/:id +func (h *TLSFingerprintProfileHandler) GetByID(c *gin.Context) { + id, err := strconv.ParseInt(c.Param("id"), 10, 64) + if err != nil { + response.BadRequest(c, "Invalid profile ID") + return + } + + profile, err := h.service.GetByID(c.Request.Context(), id) + if err != nil { + response.ErrorFrom(c, err) + return + } + if profile == nil { + response.NotFound(c, "Profile not found") + return + } + + response.Success(c, profile) +} + +// Create 创建模板 +// POST /api/v1/admin/tls-fingerprint-profiles +func (h *TLSFingerprintProfileHandler) Create(c *gin.Context) { + var req CreateTLSFingerprintProfileRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, "Invalid request: "+err.Error()) + return + } + + profile := &model.TLSFingerprintProfile{ + Name: req.Name, + Description: req.Description, + CipherSuites: req.CipherSuites, + Curves: req.Curves, + PointFormats: req.PointFormats, + SignatureAlgorithms: req.SignatureAlgorithms, + ALPNProtocols: req.ALPNProtocols, + SupportedVersions: req.SupportedVersions, + KeyShareGroups: req.KeyShareGroups, + PSKModes: req.PSKModes, + Extensions: req.Extensions, + } + + if req.EnableGREASE != nil { + profile.EnableGREASE = *req.EnableGREASE + } + + created, err := h.service.Create(c.Request.Context(), profile) + if err != nil { + if _, ok := err.(*model.ValidationError); ok { + response.BadRequest(c, err.Error()) + return + } + response.ErrorFrom(c, err) + return + } + + response.Success(c, created) +} + +// Update 更新模板(支持部分更新) +// PUT /api/v1/admin/tls-fingerprint-profiles/:id +func (h *TLSFingerprintProfileHandler) Update(c *gin.Context) { + id, err := strconv.ParseInt(c.Param("id"), 10, 64) + if err != nil { + response.BadRequest(c, "Invalid profile ID") + return + } + + var req UpdateTLSFingerprintProfileRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, "Invalid request: "+err.Error()) + return + } + + existing, err := h.service.GetByID(c.Request.Context(), id) + if err != nil { + response.ErrorFrom(c, err) + return + } + if existing == nil { + response.NotFound(c, "Profile not found") + return + } + + // 部分更新 + profile := &model.TLSFingerprintProfile{ + ID: id, + Name: existing.Name, + Description: existing.Description, + EnableGREASE: existing.EnableGREASE, + CipherSuites: existing.CipherSuites, + Curves: existing.Curves, + PointFormats: existing.PointFormats, + SignatureAlgorithms: existing.SignatureAlgorithms, + ALPNProtocols: existing.ALPNProtocols, + SupportedVersions: existing.SupportedVersions, + KeyShareGroups: existing.KeyShareGroups, + PSKModes: existing.PSKModes, + Extensions: existing.Extensions, + } + + if req.Name != nil { + profile.Name = *req.Name + } + if req.Description != nil { + profile.Description = req.Description + } + if req.EnableGREASE != nil { + profile.EnableGREASE = *req.EnableGREASE + } + if req.CipherSuites != nil { + profile.CipherSuites = req.CipherSuites + } + if req.Curves != nil { + profile.Curves = req.Curves + } + if req.PointFormats != nil { + profile.PointFormats = req.PointFormats + } + if req.SignatureAlgorithms != nil { + profile.SignatureAlgorithms = req.SignatureAlgorithms + } + if req.ALPNProtocols != nil { + profile.ALPNProtocols = req.ALPNProtocols + } + if req.SupportedVersions != nil { + profile.SupportedVersions = req.SupportedVersions + } + if req.KeyShareGroups != nil { + profile.KeyShareGroups = req.KeyShareGroups + } + if req.PSKModes != nil { + profile.PSKModes = req.PSKModes + } + if req.Extensions != nil { + profile.Extensions = req.Extensions + } + + updated, err := h.service.Update(c.Request.Context(), profile) + if err != nil { + if _, ok := err.(*model.ValidationError); ok { + response.BadRequest(c, err.Error()) + return + } + response.ErrorFrom(c, err) + return + } + + response.Success(c, updated) +} + +// Delete 删除模板 +// DELETE /api/v1/admin/tls-fingerprint-profiles/:id +func (h *TLSFingerprintProfileHandler) Delete(c *gin.Context) { + id, err := strconv.ParseInt(c.Param("id"), 10, 64) + if err != nil { + response.BadRequest(c, "Invalid profile ID") + return + } + + if err := h.service.Delete(c.Request.Context(), id); err != nil { + response.ErrorFrom(c, err) + return + } + + response.Success(c, gin.H{"message": "Profile deleted successfully"}) +} diff --git a/backend/internal/handler/dto/mappers.go b/backend/internal/handler/dto/mappers.go index 8150aa8e..e39b36d3 100644 --- a/backend/internal/handler/dto/mappers.go +++ b/backend/internal/handler/dto/mappers.go @@ -252,6 +252,10 @@ func AccountFromServiceShallow(a *service.Account) *Account { enabled := true out.EnableTLSFingerprint = &enabled } + // TLS指纹模板ID + if profileID := a.GetTLSFingerprintProfileID(); profileID > 0 { + out.TLSFingerprintProfileID = &profileID + } // 会话ID伪装开关 if a.IsSessionIDMaskingEnabled() { enabled := true diff --git a/backend/internal/handler/dto/types.go b/backend/internal/handler/dto/types.go index d4a24e10..aa419d6b 100644 --- a/backend/internal/handler/dto/types.go +++ b/backend/internal/handler/dto/types.go @@ -185,7 +185,8 @@ type Account struct { // TLS指纹伪装(仅 Anthropic OAuth/SetupToken 账号有效) // 从 extra 字段提取,方便前端显示和编辑 - EnableTLSFingerprint *bool `json:"enable_tls_fingerprint,omitempty"` + EnableTLSFingerprint *bool `json:"enable_tls_fingerprint,omitempty"` + TLSFingerprintProfileID *int64 `json:"tls_fingerprint_profile_id,omitempty"` // 会话ID伪装(仅 Anthropic OAuth/SetupToken 账号有效) // 启用后将在15分钟内固定 metadata.user_id 中的 session ID diff --git a/backend/internal/handler/gateway_handler_warmup_intercept_unit_test.go b/backend/internal/handler/gateway_handler_warmup_intercept_unit_test.go index b9dbe0ce..69c8d1d5 100644 --- a/backend/internal/handler/gateway_handler_warmup_intercept_unit_test.go +++ b/backend/internal/handler/gateway_handler_warmup_intercept_unit_test.go @@ -75,8 +75,10 @@ func (f *fakeGroupRepo) ListActive(context.Context) ([]service.Group, error) { r func (f *fakeGroupRepo) ListActiveByPlatform(context.Context, string) ([]service.Group, error) { return nil, nil } -func (f *fakeGroupRepo) ExistsByName(context.Context, string) (bool, error) { return false, nil } -func (f *fakeGroupRepo) GetAccountCount(context.Context, int64) (int64, int64, error) { return 0, 0, nil } +func (f *fakeGroupRepo) ExistsByName(context.Context, string) (bool, error) { return false, nil } +func (f *fakeGroupRepo) GetAccountCount(context.Context, int64) (int64, int64, error) { + return 0, 0, nil +} func (f *fakeGroupRepo) DeleteAccountGroupsByGroupID(context.Context, int64) (int64, error) { return 0, nil } @@ -158,6 +160,7 @@ func newTestGatewayHandler(t *testing.T, group *service.Group, accounts []*servi nil, // rpmCache nil, // digestStore nil, // settingService + nil, // tlsFPProfileService ) // RunModeSimple:跳过计费检查,避免引入 repo/cache 依赖。 diff --git a/backend/internal/handler/handler.go b/backend/internal/handler/handler.go index 89d556cc..b2467eac 100644 --- a/backend/internal/handler/handler.go +++ b/backend/internal/handler/handler.go @@ -6,29 +6,30 @@ import ( // AdminHandlers contains all admin-related HTTP handlers type AdminHandlers struct { - Dashboard *admin.DashboardHandler - User *admin.UserHandler - Group *admin.GroupHandler - Account *admin.AccountHandler - Announcement *admin.AnnouncementHandler - DataManagement *admin.DataManagementHandler - Backup *admin.BackupHandler - OAuth *admin.OAuthHandler - OpenAIOAuth *admin.OpenAIOAuthHandler - GeminiOAuth *admin.GeminiOAuthHandler - AntigravityOAuth *admin.AntigravityOAuthHandler - Proxy *admin.ProxyHandler - Redeem *admin.RedeemHandler - Promo *admin.PromoHandler - Setting *admin.SettingHandler - Ops *admin.OpsHandler - System *admin.SystemHandler - Subscription *admin.SubscriptionHandler - Usage *admin.UsageHandler - UserAttribute *admin.UserAttributeHandler - ErrorPassthrough *admin.ErrorPassthroughHandler - APIKey *admin.AdminAPIKeyHandler - ScheduledTest *admin.ScheduledTestHandler + Dashboard *admin.DashboardHandler + User *admin.UserHandler + Group *admin.GroupHandler + Account *admin.AccountHandler + Announcement *admin.AnnouncementHandler + DataManagement *admin.DataManagementHandler + Backup *admin.BackupHandler + OAuth *admin.OAuthHandler + OpenAIOAuth *admin.OpenAIOAuthHandler + GeminiOAuth *admin.GeminiOAuthHandler + AntigravityOAuth *admin.AntigravityOAuthHandler + Proxy *admin.ProxyHandler + Redeem *admin.RedeemHandler + Promo *admin.PromoHandler + Setting *admin.SettingHandler + Ops *admin.OpsHandler + System *admin.SystemHandler + Subscription *admin.SubscriptionHandler + Usage *admin.UsageHandler + UserAttribute *admin.UserAttributeHandler + ErrorPassthrough *admin.ErrorPassthroughHandler + TLSFingerprintProfile *admin.TLSFingerprintProfileHandler + APIKey *admin.AdminAPIKeyHandler + ScheduledTest *admin.ScheduledTestHandler } // Handlers contains all HTTP handlers diff --git a/backend/internal/handler/sora_client_handler_test.go b/backend/internal/handler/sora_client_handler_test.go index 625f159d..fe035b6f 100644 --- a/backend/internal/handler/sora_client_handler_test.go +++ b/backend/internal/handler/sora_client_handler_test.go @@ -2224,7 +2224,7 @@ func (s *stubSoraClientForHandler) GetVideoTask(_ context.Context, _ *service.Ac func newMinimalGatewayService(accountRepo service.AccountRepository) *service.GatewayService { return service.NewGatewayService( accountRepo, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, ) } diff --git a/backend/internal/handler/sora_gateway_handler_test.go b/backend/internal/handler/sora_gateway_handler_test.go index 084e4ae1..c790a36c 100644 --- a/backend/internal/handler/sora_gateway_handler_test.go +++ b/backend/internal/handler/sora_gateway_handler_test.go @@ -464,6 +464,7 @@ func TestSoraGatewayHandler_ChatCompletions(t *testing.T) { nil, // rpmCache nil, // digestStore nil, // settingService + nil, // tlsFPProfileService ) soraClient := &stubSoraClient{imageURLs: []string{"https://example.com/a.png"}} diff --git a/backend/internal/handler/wire.go b/backend/internal/handler/wire.go index f3aadcf3..02ddd030 100644 --- a/backend/internal/handler/wire.go +++ b/backend/internal/handler/wire.go @@ -30,33 +30,35 @@ func ProvideAdminHandlers( usageHandler *admin.UsageHandler, userAttributeHandler *admin.UserAttributeHandler, errorPassthroughHandler *admin.ErrorPassthroughHandler, + tlsFingerprintProfileHandler *admin.TLSFingerprintProfileHandler, apiKeyHandler *admin.AdminAPIKeyHandler, scheduledTestHandler *admin.ScheduledTestHandler, ) *AdminHandlers { return &AdminHandlers{ - Dashboard: dashboardHandler, - User: userHandler, - Group: groupHandler, - Account: accountHandler, - Announcement: announcementHandler, - DataManagement: dataManagementHandler, - Backup: backupHandler, - OAuth: oauthHandler, - OpenAIOAuth: openaiOAuthHandler, - GeminiOAuth: geminiOAuthHandler, - AntigravityOAuth: antigravityOAuthHandler, - Proxy: proxyHandler, - Redeem: redeemHandler, - Promo: promoHandler, - Setting: settingHandler, - Ops: opsHandler, - System: systemHandler, - Subscription: subscriptionHandler, - Usage: usageHandler, - UserAttribute: userAttributeHandler, - ErrorPassthrough: errorPassthroughHandler, - APIKey: apiKeyHandler, - ScheduledTest: scheduledTestHandler, + Dashboard: dashboardHandler, + User: userHandler, + Group: groupHandler, + Account: accountHandler, + Announcement: announcementHandler, + DataManagement: dataManagementHandler, + Backup: backupHandler, + OAuth: oauthHandler, + OpenAIOAuth: openaiOAuthHandler, + GeminiOAuth: geminiOAuthHandler, + AntigravityOAuth: antigravityOAuthHandler, + Proxy: proxyHandler, + Redeem: redeemHandler, + Promo: promoHandler, + Setting: settingHandler, + Ops: opsHandler, + System: systemHandler, + Subscription: subscriptionHandler, + Usage: usageHandler, + UserAttribute: userAttributeHandler, + ErrorPassthrough: errorPassthroughHandler, + TLSFingerprintProfile: tlsFingerprintProfileHandler, + APIKey: apiKeyHandler, + ScheduledTest: scheduledTestHandler, } } @@ -145,6 +147,7 @@ var ProviderSet = wire.NewSet( admin.NewUsageHandler, admin.NewUserAttributeHandler, admin.NewErrorPassthroughHandler, + admin.NewTLSFingerprintProfileHandler, admin.NewAdminAPIKeyHandler, admin.NewScheduledTestHandler, diff --git a/backend/internal/model/tls_fingerprint_profile.go b/backend/internal/model/tls_fingerprint_profile.go new file mode 100644 index 00000000..ef57af7a --- /dev/null +++ b/backend/internal/model/tls_fingerprint_profile.go @@ -0,0 +1,54 @@ +// Package model 定义服务层使用的数据模型。 +package model + +import ( + "time" + + "github.com/Wei-Shaw/sub2api/internal/pkg/tlsfingerprint" +) + +// TLSFingerprintProfile TLS 指纹配置模板 +// 包含完整的 ClientHello 参数,用于模拟特定客户端的 TLS 握手特征 +type TLSFingerprintProfile struct { + ID int64 `json:"id"` + Name string `json:"name"` + Description *string `json:"description"` + EnableGREASE bool `json:"enable_grease"` + CipherSuites []uint16 `json:"cipher_suites"` + Curves []uint16 `json:"curves"` + PointFormats []uint16 `json:"point_formats"` + SignatureAlgorithms []uint16 `json:"signature_algorithms"` + ALPNProtocols []string `json:"alpn_protocols"` + SupportedVersions []uint16 `json:"supported_versions"` + KeyShareGroups []uint16 `json:"key_share_groups"` + PSKModes []uint16 `json:"psk_modes"` + Extensions []uint16 `json:"extensions"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +// Validate 验证模板配置的有效性 +func (p *TLSFingerprintProfile) Validate() error { + if p.Name == "" { + return &ValidationError{Field: "name", Message: "name is required"} + } + return nil +} + +// ToTLSProfile 将领域模型转换为运行时使用的 tlsfingerprint.Profile +// 空切片字段会在 dialer 中 fallback 到内置默认值 +func (p *TLSFingerprintProfile) ToTLSProfile() *tlsfingerprint.Profile { + return &tlsfingerprint.Profile{ + Name: p.Name, + EnableGREASE: p.EnableGREASE, + CipherSuites: p.CipherSuites, + Curves: p.Curves, + PointFormats: p.PointFormats, + SignatureAlgorithms: p.SignatureAlgorithms, + ALPNProtocols: p.ALPNProtocols, + SupportedVersions: p.SupportedVersions, + KeyShareGroups: p.KeyShareGroups, + PSKModes: p.PSKModes, + Extensions: p.Extensions, + } +} diff --git a/backend/internal/pkg/tlsfingerprint/dialer.go b/backend/internal/pkg/tlsfingerprint/dialer.go index 4f25a34a..c8d8369f 100644 --- a/backend/internal/pkg/tlsfingerprint/dialer.go +++ b/backend/internal/pkg/tlsfingerprint/dialer.go @@ -17,12 +17,19 @@ import ( ) // Profile contains TLS fingerprint configuration. +// All slice fields use built-in defaults when empty. type Profile struct { - Name string // Profile name for identification - CipherSuites []uint16 - Curves []uint16 - PointFormats []uint8 - EnableGREASE bool + Name string // Profile name for identification + CipherSuites []uint16 + Curves []uint16 + PointFormats []uint16 + EnableGREASE bool + SignatureAlgorithms []uint16 // Empty uses defaultSignatureAlgorithms + ALPNProtocols []string // Empty uses ["http/1.1"] + SupportedVersions []uint16 // Empty uses [TLS1.3, TLS1.2] + KeyShareGroups []uint16 // Empty uses [X25519] + PSKModes []uint16 // Empty uses [psk_dhe_ke] + Extensions []uint16 // Extension type IDs in order; empty uses default Node.js 24.x order } // Dialer creates TLS connections with custom fingerprints. @@ -45,154 +52,67 @@ type SOCKS5ProxyDialer struct { proxyURL *url.URL } -// Default TLS fingerprint values captured from Claude CLI 2.x (Node.js 20.x + OpenSSL 3.x) -// Captured using: tshark -i lo -f "tcp port 8443" -Y "tls.handshake.type == 1" -V -// JA3 Hash: 1a28e69016765d92e3b381168d68922c -// -// Note: JA3/JA4 may have slight variations due to: -// - Session ticket presence/absence -// - Extension negotiation state +// Default TLS fingerprint values captured from Claude Code (Node.js 24.x) +// Captured via tls-fingerprint-web capture server +// JA3 Hash: 44f88fca027f27bab4bb08d4af15f23e +// JA4: t13d1714h1_5b57614c22b0_7baf387fc6ff var ( - // defaultCipherSuites contains all 59 cipher suites from Claude CLI + // defaultCipherSuites contains the 17 cipher suites from Node.js 24.x // Order is critical for JA3 fingerprint matching defaultCipherSuites = []uint16{ - // TLS 1.3 cipher suites (MUST be first) + // TLS 1.3 cipher suites + 0x1301, // TLS_AES_128_GCM_SHA256 0x1302, // TLS_AES_256_GCM_SHA384 0x1303, // TLS_CHACHA20_POLY1305_SHA256 - 0x1301, // TLS_AES_128_GCM_SHA256 // ECDHE + AES-GCM - 0xc02f, // TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 0xc02b, // TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 - 0xc030, // TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 + 0xc02f, // TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 0xc02c, // TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 + 0xc030, // TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 - // DHE + AES-GCM - 0x009e, // TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 - - // ECDHE/DHE + AES-CBC-SHA256/384 - 0xc027, // TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 - 0x0067, // TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 - 0xc028, // TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 - 0x006b, // TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 - - // DHE-DSS/RSA + AES-GCM - 0x00a3, // TLS_DHE_DSS_WITH_AES_256_GCM_SHA384 - 0x009f, // TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 - - // ChaCha20-Poly1305 + // ECDHE + ChaCha20-Poly1305 0xcca9, // TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 0xcca8, // TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 - 0xccaa, // TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256 - // AES-CCM (256-bit) - 0xc0af, // TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8 - 0xc0ad, // TLS_ECDHE_ECDSA_WITH_AES_256_CCM - 0xc0a3, // TLS_DHE_RSA_WITH_AES_256_CCM_8 - 0xc09f, // TLS_DHE_RSA_WITH_AES_256_CCM - - // ARIA (256-bit) - 0xc05d, // TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384 - 0xc061, // TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384 - 0xc057, // TLS_DHE_DSS_WITH_ARIA_256_GCM_SHA384 - 0xc053, // TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384 - - // DHE-DSS + AES-GCM (128-bit) - 0x00a2, // TLS_DHE_DSS_WITH_AES_128_GCM_SHA256 - - // AES-CCM (128-bit) - 0xc0ae, // TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8 - 0xc0ac, // TLS_ECDHE_ECDSA_WITH_AES_128_CCM - 0xc0a2, // TLS_DHE_RSA_WITH_AES_128_CCM_8 - 0xc09e, // TLS_DHE_RSA_WITH_AES_128_CCM - - // ARIA (128-bit) - 0xc05c, // TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256 - 0xc060, // TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256 - 0xc056, // TLS_DHE_DSS_WITH_ARIA_128_GCM_SHA256 - 0xc052, // TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256 - - // ECDHE/DHE + AES-CBC-SHA384/256 (more) - 0xc024, // TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 - 0x006a, // TLS_DHE_DSS_WITH_AES_256_CBC_SHA256 - 0xc023, // TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 - 0x0040, // TLS_DHE_DSS_WITH_AES_128_CBC_SHA256 - - // ECDHE/DHE + AES-CBC-SHA (legacy) - 0xc00a, // TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA - 0xc014, // TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA - 0x0039, // TLS_DHE_RSA_WITH_AES_256_CBC_SHA - 0x0038, // TLS_DHE_DSS_WITH_AES_256_CBC_SHA + // ECDHE + AES-CBC-SHA (legacy fallback) 0xc009, // TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA 0xc013, // TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA - 0x0033, // TLS_DHE_RSA_WITH_AES_128_CBC_SHA - 0x0032, // TLS_DHE_DSS_WITH_AES_128_CBC_SHA + 0xc00a, // TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA + 0xc014, // TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA - // RSA + AES-GCM/CCM/ARIA (non-PFS, 256-bit) - 0x009d, // TLS_RSA_WITH_AES_256_GCM_SHA384 - 0xc0a1, // TLS_RSA_WITH_AES_256_CCM_8 - 0xc09d, // TLS_RSA_WITH_AES_256_CCM - 0xc051, // TLS_RSA_WITH_ARIA_256_GCM_SHA384 - - // RSA + AES-GCM/CCM/ARIA (non-PFS, 128-bit) + // RSA + AES-GCM (non-PFS) 0x009c, // TLS_RSA_WITH_AES_128_GCM_SHA256 - 0xc0a0, // TLS_RSA_WITH_AES_128_CCM_8 - 0xc09c, // TLS_RSA_WITH_AES_128_CCM - 0xc050, // TLS_RSA_WITH_ARIA_128_GCM_SHA256 + 0x009d, // TLS_RSA_WITH_AES_256_GCM_SHA384 - // RSA + AES-CBC (non-PFS, legacy) - 0x003d, // TLS_RSA_WITH_AES_256_CBC_SHA256 - 0x003c, // TLS_RSA_WITH_AES_128_CBC_SHA256 - 0x0035, // TLS_RSA_WITH_AES_256_CBC_SHA + // RSA + AES-CBC-SHA (non-PFS, legacy) 0x002f, // TLS_RSA_WITH_AES_128_CBC_SHA - - // Renegotiation indication - 0x00ff, // TLS_EMPTY_RENEGOTIATION_INFO_SCSV + 0x0035, // TLS_RSA_WITH_AES_256_CBC_SHA } - // defaultCurves contains the 10 supported groups from Claude CLI (including FFDHE) + // defaultCurves contains the 3 supported groups from Node.js 24.x defaultCurves = []utls.CurveID{ - utls.X25519, // 0x001d - utls.CurveP256, // 0x0017 (secp256r1) - utls.CurveID(0x001e), // x448 - utls.CurveP521, // 0x0019 (secp521r1) - utls.CurveP384, // 0x0018 (secp384r1) - utls.CurveID(0x0100), // ffdhe2048 - utls.CurveID(0x0101), // ffdhe3072 - utls.CurveID(0x0102), // ffdhe4096 - utls.CurveID(0x0103), // ffdhe6144 - utls.CurveID(0x0104), // ffdhe8192 + utls.X25519, // 0x001d + utls.CurveP256, // 0x0017 (secp256r1) + utls.CurveP384, // 0x0018 (secp384r1) } - // defaultPointFormats contains all 3 point formats from Claude CLI - defaultPointFormats = []uint8{ + // defaultPointFormats contains point formats from Node.js 24.x + defaultPointFormats = []uint16{ 0, // uncompressed - 1, // ansiX962_compressed_prime - 2, // ansiX962_compressed_char2 } - // defaultSignatureAlgorithms contains the 20 signature algorithms from Claude CLI + // defaultSignatureAlgorithms contains the 9 signature algorithms from Node.js 24.x defaultSignatureAlgorithms = []utls.SignatureScheme{ 0x0403, // ecdsa_secp256r1_sha256 - 0x0503, // ecdsa_secp384r1_sha384 - 0x0603, // ecdsa_secp521r1_sha512 - 0x0807, // ed25519 - 0x0808, // ed448 - 0x0809, // rsa_pss_pss_sha256 - 0x080a, // rsa_pss_pss_sha384 - 0x080b, // rsa_pss_pss_sha512 0x0804, // rsa_pss_rsae_sha256 - 0x0805, // rsa_pss_rsae_sha384 - 0x0806, // rsa_pss_rsae_sha512 0x0401, // rsa_pkcs1_sha256 + 0x0503, // ecdsa_secp384r1_sha384 + 0x0805, // rsa_pss_rsae_sha384 0x0501, // rsa_pkcs1_sha384 + 0x0806, // rsa_pss_rsae_sha512 0x0601, // rsa_pkcs1_sha512 - 0x0303, // ecdsa_sha224 - 0x0301, // rsa_pkcs1_sha224 - 0x0302, // dsa_sha224 - 0x0402, // dsa_sha256 - 0x0502, // dsa_sha384 - 0x0602, // dsa_sha512 + 0x0201, // rsa_pkcs1_sha1 } ) @@ -256,49 +176,7 @@ func (d *SOCKS5ProxyDialer) DialTLSContext(ctx context.Context, network, addr st slog.Debug("tls_fingerprint_socks5_tunnel_established") // Step 3: Perform TLS handshake on the tunnel with utls fingerprint - host, _, err := net.SplitHostPort(addr) - if err != nil { - host = addr - } - slog.Debug("tls_fingerprint_socks5_starting_handshake", "host", host) - - // Build ClientHello specification from profile (Node.js/Claude CLI fingerprint) - spec := buildClientHelloSpecFromProfile(d.profile) - slog.Debug("tls_fingerprint_socks5_clienthello_spec", - "cipher_suites", len(spec.CipherSuites), - "extensions", len(spec.Extensions), - "compression_methods", spec.CompressionMethods, - "tls_vers_max", spec.TLSVersMax, - "tls_vers_min", spec.TLSVersMin) - - if d.profile != nil { - slog.Debug("tls_fingerprint_socks5_using_profile", "name", d.profile.Name, "grease", d.profile.EnableGREASE) - } - - // Create uTLS connection on the tunnel - tlsConn := utls.UClient(conn, &utls.Config{ - ServerName: host, - }, utls.HelloCustom) - - if err := tlsConn.ApplyPreset(spec); err != nil { - slog.Debug("tls_fingerprint_socks5_apply_preset_failed", "error", err) - _ = conn.Close() - return nil, fmt.Errorf("apply TLS preset: %w", err) - } - - if err := tlsConn.HandshakeContext(ctx); err != nil { - slog.Debug("tls_fingerprint_socks5_handshake_failed", "error", err) - _ = conn.Close() - return nil, fmt.Errorf("TLS handshake failed: %w", err) - } - - state := tlsConn.ConnectionState() - slog.Debug("tls_fingerprint_socks5_handshake_success", - "version", state.Version, - "cipher_suite", state.CipherSuite, - "alpn", state.NegotiatedProtocol) - - return tlsConn, nil + return performTLSHandshake(ctx, conn, d.profile, addr) } // DialTLSContext establishes a TLS connection through HTTP proxy with the configured fingerprint. @@ -358,7 +236,8 @@ func (d *HTTPProxyDialer) DialTLSContext(ctx context.Context, network, addr stri slog.Debug("tls_fingerprint_http_proxy_read_response_failed", "error", err) return nil, fmt.Errorf("read CONNECT response: %w", err) } - defer func() { _ = resp.Body.Close() }() + // CONNECT response has no body; do not defer resp.Body.Close() as it wraps the + // same conn that will be used for the TLS handshake. if resp.StatusCode != http.StatusOK { _ = conn.Close() @@ -368,47 +247,7 @@ func (d *HTTPProxyDialer) DialTLSContext(ctx context.Context, network, addr stri slog.Debug("tls_fingerprint_http_proxy_tunnel_established") // Step 4: Perform TLS handshake on the tunnel with utls fingerprint - host, _, err := net.SplitHostPort(addr) - if err != nil { - host = addr - } - slog.Debug("tls_fingerprint_http_proxy_starting_handshake", "host", host) - - // Build ClientHello specification (reuse the shared method) - spec := buildClientHelloSpecFromProfile(d.profile) - slog.Debug("tls_fingerprint_http_proxy_clienthello_spec", - "cipher_suites", len(spec.CipherSuites), - "extensions", len(spec.Extensions)) - - if d.profile != nil { - slog.Debug("tls_fingerprint_http_proxy_using_profile", "name", d.profile.Name, "grease", d.profile.EnableGREASE) - } - - // Create uTLS connection on the tunnel - // Note: TLS 1.3 cipher suites are handled automatically by utls when TLS 1.3 is in SupportedVersions - tlsConn := utls.UClient(conn, &utls.Config{ - ServerName: host, - }, utls.HelloCustom) - - if err := tlsConn.ApplyPreset(spec); err != nil { - slog.Debug("tls_fingerprint_http_proxy_apply_preset_failed", "error", err) - _ = conn.Close() - return nil, fmt.Errorf("apply TLS preset: %w", err) - } - - if err := tlsConn.HandshakeContext(ctx); err != nil { - slog.Debug("tls_fingerprint_http_proxy_handshake_failed", "error", err) - _ = conn.Close() - return nil, fmt.Errorf("TLS handshake failed: %w", err) - } - - state := tlsConn.ConnectionState() - slog.Debug("tls_fingerprint_http_proxy_handshake_success", - "version", state.Version, - "cipher_suite", state.CipherSuite, - "alpn", state.NegotiatedProtocol) - - return tlsConn, nil + return performTLSHandshake(ctx, conn, d.profile, addr) } // DialTLSContext establishes a TLS connection with the configured fingerprint. @@ -423,53 +262,35 @@ func (d *Dialer) DialTLSContext(ctx context.Context, network, addr string) (net. } slog.Debug("tls_fingerprint_tcp_connected", "addr", addr) - // Extract hostname for SNI + // Perform TLS handshake with utls fingerprint + return performTLSHandshake(ctx, conn, d.profile, addr) +} + +// performTLSHandshake performs the uTLS handshake on an established connection. +// It builds a ClientHello spec from the profile, applies it, and completes the handshake. +// On failure, conn is closed and an error is returned. +func performTLSHandshake(ctx context.Context, conn net.Conn, profile *Profile, addr string) (net.Conn, error) { host, _, err := net.SplitHostPort(addr) if err != nil { host = addr } - slog.Debug("tls_fingerprint_sni_hostname", "host", host) - // Build ClientHello specification - spec := d.buildClientHelloSpec() - slog.Debug("tls_fingerprint_clienthello_spec", - "cipher_suites", len(spec.CipherSuites), - "extensions", len(spec.Extensions)) + spec := buildClientHelloSpecFromProfile(profile) + tlsConn := utls.UClient(conn, &utls.Config{ServerName: host}, utls.HelloCustom) - // Log profile info - if d.profile != nil { - slog.Debug("tls_fingerprint_using_profile", "name", d.profile.Name, "grease", d.profile.EnableGREASE) - } else { - slog.Debug("tls_fingerprint_using_default_profile") - } - - // Create uTLS connection - // Note: TLS 1.3 cipher suites are handled automatically by utls when TLS 1.3 is in SupportedVersions - tlsConn := utls.UClient(conn, &utls.Config{ - ServerName: host, - }, utls.HelloCustom) - - // Apply fingerprint if err := tlsConn.ApplyPreset(spec); err != nil { - slog.Debug("tls_fingerprint_apply_preset_failed", "error", err) _ = conn.Close() - return nil, err + return nil, fmt.Errorf("apply TLS preset: %w", err) } - slog.Debug("tls_fingerprint_preset_applied") - // Perform TLS handshake if err := tlsConn.HandshakeContext(ctx); err != nil { - slog.Debug("tls_fingerprint_handshake_failed", - "error", err, - "local_addr", conn.LocalAddr(), - "remote_addr", conn.RemoteAddr()) _ = conn.Close() return nil, fmt.Errorf("TLS handshake failed: %w", err) } - // Log successful handshake details state := tlsConn.ConnectionState() slog.Debug("tls_fingerprint_handshake_success", + "host", host, "version", state.Version, "cipher_suite", state.CipherSuite, "alpn", state.NegotiatedProtocol) @@ -477,11 +298,6 @@ func (d *Dialer) DialTLSContext(ctx context.Context, network, addr string) (net. return tlsConn, nil } -// buildClientHelloSpec constructs the ClientHello specification based on the profile. -func (d *Dialer) buildClientHelloSpec() *utls.ClientHelloSpec { - return buildClientHelloSpecFromProfile(d.profile) -} - // toUTLSCurves converts uint16 slice to utls.CurveID slice. func toUTLSCurves(curves []uint16) []utls.CurveID { result := make([]utls.CurveID, len(curves)) @@ -491,70 +307,143 @@ func toUTLSCurves(curves []uint16) []utls.CurveID { return result } +// defaultExtensionOrder is the Node.js 24.x extension order. +// Used when Profile.Extensions is empty. +var defaultExtensionOrder = []uint16{ + 0, // server_name + 65037, // encrypted_client_hello + 23, // extended_master_secret + 65281, // renegotiation_info + 10, // supported_groups + 11, // ec_point_formats + 35, // session_ticket + 16, // alpn + 5, // status_request + 13, // signature_algorithms + 18, // signed_certificate_timestamp + 51, // key_share + 45, // psk_key_exchange_modes + 43, // supported_versions +} + +// isGREASEValue checks if a uint16 value matches the TLS GREASE pattern (0x?a?a). +func isGREASEValue(v uint16) bool { + return v&0x0f0f == 0x0a0a && v>>8 == v&0xff +} + // buildClientHelloSpecFromProfile constructs ClientHelloSpec from a Profile. // This is a standalone function that can be used by both Dialer and HTTPProxyDialer. func buildClientHelloSpecFromProfile(profile *Profile) *utls.ClientHelloSpec { - // Get cipher suites - var cipherSuites []uint16 + // Resolve effective values (profile overrides or built-in defaults) + cipherSuites := defaultCipherSuites if profile != nil && len(profile.CipherSuites) > 0 { cipherSuites = profile.CipherSuites - } else { - cipherSuites = defaultCipherSuites } - // Get curves - var curves []utls.CurveID + curves := defaultCurves if profile != nil && len(profile.Curves) > 0 { curves = toUTLSCurves(profile.Curves) - } else { - curves = defaultCurves } - // Get point formats - var pointFormats []uint8 + pointFormats := defaultPointFormats if profile != nil && len(profile.PointFormats) > 0 { pointFormats = profile.PointFormats - } else { - pointFormats = defaultPointFormats } - // Check if GREASE is enabled + signatureAlgorithms := defaultSignatureAlgorithms + if profile != nil && len(profile.SignatureAlgorithms) > 0 { + signatureAlgorithms = make([]utls.SignatureScheme, len(profile.SignatureAlgorithms)) + for i, s := range profile.SignatureAlgorithms { + signatureAlgorithms[i] = utls.SignatureScheme(s) + } + } + + alpnProtocols := []string{"http/1.1"} + if profile != nil && len(profile.ALPNProtocols) > 0 { + alpnProtocols = profile.ALPNProtocols + } + + supportedVersions := []uint16{utls.VersionTLS13, utls.VersionTLS12} + if profile != nil && len(profile.SupportedVersions) > 0 { + supportedVersions = profile.SupportedVersions + } + + keyShareGroups := []utls.CurveID{utls.X25519} + if profile != nil && len(profile.KeyShareGroups) > 0 { + keyShareGroups = toUTLSCurves(profile.KeyShareGroups) + } + + pskModes := []uint16{uint16(utls.PskModeDHE)} + if profile != nil && len(profile.PSKModes) > 0 { + pskModes = profile.PSKModes + } + enableGREASE := profile != nil && profile.EnableGREASE - extensions := make([]utls.TLSExtension, 0, 16) - - if enableGREASE { - extensions = append(extensions, &utls.UtlsGREASEExtension{}) + // Build key shares + keyShares := make([]utls.KeyShare, len(keyShareGroups)) + for i, g := range keyShareGroups { + keyShares[i] = utls.KeyShare{Group: g} } - // SNI extension - MUST be explicitly added for HelloCustom mode - // utls will populate the server name from Config.ServerName - extensions = append(extensions, &utls.SNIExtension{}) + // Determine extension order + extOrder := defaultExtensionOrder + if profile != nil && len(profile.Extensions) > 0 { + extOrder = profile.Extensions + } - // Claude CLI extension order (captured from tshark): - // server_name(0), ec_point_formats(11), supported_groups(10), session_ticket(35), - // alpn(16), encrypt_then_mac(22), extended_master_secret(23), - // signature_algorithms(13), supported_versions(43), - // psk_key_exchange_modes(45), key_share(51) - extensions = append(extensions, - &utls.SupportedPointsExtension{SupportedPoints: pointFormats}, - &utls.SupportedCurvesExtension{Curves: curves}, - &utls.SessionTicketExtension{}, - &utls.ALPNExtension{AlpnProtocols: []string{"http/1.1"}}, - &utls.GenericExtension{Id: 22}, - &utls.ExtendedMasterSecretExtension{}, - &utls.SignatureAlgorithmsExtension{SupportedSignatureAlgorithms: defaultSignatureAlgorithms}, - &utls.SupportedVersionsExtension{Versions: []uint16{ - utls.VersionTLS13, - utls.VersionTLS12, - }}, - &utls.PSKKeyExchangeModesExtension{Modes: []uint8{utls.PskModeDHE}}, - &utls.KeyShareExtension{KeyShares: []utls.KeyShare{ - {Group: utls.X25519}, - }}, - ) + // Build extensions list from the ordered IDs. + // Parametric extensions (curves, sigalgs, etc.) are populated with resolved profile values. + // Unknown IDs use GenericExtension (sends type ID with empty data). + extensions := make([]utls.TLSExtension, 0, len(extOrder)+2) + for _, id := range extOrder { + if isGREASEValue(id) { + extensions = append(extensions, &utls.UtlsGREASEExtension{}) + continue + } + switch id { + case 0: // server_name + extensions = append(extensions, &utls.SNIExtension{}) + case 5: // status_request (OCSP) + extensions = append(extensions, &utls.StatusRequestExtension{}) + case 10: // supported_groups + extensions = append(extensions, &utls.SupportedCurvesExtension{Curves: curves}) + case 11: // ec_point_formats + extensions = append(extensions, &utls.SupportedPointsExtension{SupportedPoints: toUint8s(pointFormats)}) + case 13: // signature_algorithms + extensions = append(extensions, &utls.SignatureAlgorithmsExtension{SupportedSignatureAlgorithms: signatureAlgorithms}) + case 16: // alpn + extensions = append(extensions, &utls.ALPNExtension{AlpnProtocols: alpnProtocols}) + case 18: // signed_certificate_timestamp + extensions = append(extensions, &utls.SCTExtension{}) + case 23: // extended_master_secret + extensions = append(extensions, &utls.ExtendedMasterSecretExtension{}) + case 35: // session_ticket + extensions = append(extensions, &utls.SessionTicketExtension{}) + case 43: // supported_versions + extensions = append(extensions, &utls.SupportedVersionsExtension{Versions: supportedVersions}) + case 45: // psk_key_exchange_modes + extensions = append(extensions, &utls.PSKKeyExchangeModesExtension{Modes: toUint8s(pskModes)}) + case 50: // signature_algorithms_cert + extensions = append(extensions, &utls.SignatureAlgorithmsCertExtension{SupportedSignatureAlgorithms: signatureAlgorithms}) + case 51: // key_share + extensions = append(extensions, &utls.KeyShareExtension{KeyShares: keyShares}) + case 0xfe0d: // encrypted_client_hello (ECH, 65037) + // Send GREASE ECH with random payload — mimics Node.js behavior when no real ECHConfig is available. + // An empty GenericExtension causes "error decoding message" from servers that validate ECH format. + extensions = append(extensions, &utls.GREASEEncryptedClientHelloExtension{}) + case 0xff01: // renegotiation_info + extensions = append(extensions, &utls.RenegotiationInfoExtension{}) + default: + // Unknown extension — send as GenericExtension (type ID + empty data). + // This covers encrypt_then_mac(22) and any future extensions. + extensions = append(extensions, &utls.GenericExtension{Id: id}) + } + } - if enableGREASE { + // For default extension order with EnableGREASE, wrap with GREASE bookends + if enableGREASE && (profile == nil || len(profile.Extensions) == 0) { + extensions = append([]utls.TLSExtension{&utls.UtlsGREASEExtension{}}, extensions...) extensions = append(extensions, &utls.UtlsGREASEExtension{}) } @@ -566,3 +455,12 @@ func buildClientHelloSpecFromProfile(profile *Profile) *utls.ClientHelloSpec { TLSVersMin: utls.VersionTLS10, } } + +// toUint8s converts []uint16 to []uint8 (for utls fields that require []uint8). +func toUint8s(vals []uint16) []uint8 { + out := make([]uint8, len(vals)) + for i, v := range vals { + out[i] = uint8(v) + } + return out +} diff --git a/backend/internal/pkg/tlsfingerprint/dialer_capture_test.go b/backend/internal/pkg/tlsfingerprint/dialer_capture_test.go new file mode 100644 index 00000000..de9d79a0 --- /dev/null +++ b/backend/internal/pkg/tlsfingerprint/dialer_capture_test.go @@ -0,0 +1,368 @@ +//go:build integration + +package tlsfingerprint + +import ( + "context" + "encoding/json" + "io" + "net/http" + "os" + "strings" + "testing" + "time" + + utls "github.com/refraction-networking/utls" +) + +// CapturedFingerprint mirrors the Fingerprint struct from tls-fingerprint-web. +// Used to deserialize the JSON response from the capture server. +type CapturedFingerprint struct { + JA3Raw string `json:"ja3_raw"` + JA3Hash string `json:"ja3_hash"` + JA4 string `json:"ja4"` + HTTP2 string `json:"http2"` + CipherSuites []int `json:"cipher_suites"` + Curves []int `json:"curves"` + PointFormats []int `json:"point_formats"` + Extensions []int `json:"extensions"` + SignatureAlgorithms []int `json:"signature_algorithms"` + ALPNProtocols []string `json:"alpn_protocols"` + SupportedVersions []int `json:"supported_versions"` + KeyShareGroups []int `json:"key_share_groups"` + PSKModes []int `json:"psk_modes"` + CompressCertAlgos []int `json:"compress_cert_algos"` + EnableGREASE bool `json:"enable_grease"` +} + +// TestDialerAgainstCaptureServer connects to the tls-fingerprint-web capture server +// and verifies that the dialer's TLS fingerprint matches the configured Profile. +// +// Default capture server: https://tls.sub2api.org:8090 +// Override with env: TLSFINGERPRINT_CAPTURE_URL=https://localhost:8443 +// +// Run: go test -v -run TestDialerAgainstCaptureServer ./internal/pkg/tlsfingerprint/... +func TestDialerAgainstCaptureServer(t *testing.T) { + captureURL := os.Getenv("TLSFINGERPRINT_CAPTURE_URL") + if captureURL == "" { + captureURL = "https://tls.sub2api.org:8090" + } + + tests := []struct { + name string + profile *Profile + }{ + { + name: "default_profile", + profile: &Profile{ + Name: "default", + EnableGREASE: false, + // All empty → uses built-in defaults + }, + }, + { + name: "linux_x64_node_v22171", + profile: &Profile{ + Name: "linux_x64_node_v22171", + EnableGREASE: false, + CipherSuites: []uint16{4866, 4867, 4865, 49199, 49195, 49200, 49196, 158, 49191, 103, 49192, 107, 163, 159, 52393, 52392, 52394, 49327, 49325, 49315, 49311, 49245, 49249, 49239, 49235, 162, 49326, 49324, 49314, 49310, 49244, 49248, 49238, 49234, 49188, 106, 49187, 64, 49162, 49172, 57, 56, 49161, 49171, 51, 50, 157, 49313, 49309, 49233, 156, 49312, 49308, 49232, 61, 60, 53, 47, 255}, + Curves: []uint16{29, 23, 30, 25, 24, 256, 257, 258, 259, 260}, + PointFormats: []uint16{0, 1, 2}, + SignatureAlgorithms: []uint16{0x0403, 0x0503, 0x0603, 0x0807, 0x0808, 0x0809, 0x080a, 0x080b, 0x0804, 0x0805, 0x0806, 0x0401, 0x0501, 0x0601, 0x0303, 0x0301, 0x0302, 0x0402, 0x0502, 0x0602}, + ALPNProtocols: []string{"http/1.1"}, + SupportedVersions: []uint16{0x0304, 0x0303}, + KeyShareGroups: []uint16{29}, + PSKModes: []uint16{1}, + Extensions: []uint16{0, 11, 10, 35, 16, 22, 23, 13, 43, 45, 51}, + }, + }, + { + name: "macos_arm64_node_v2430", + profile: &Profile{ + Name: "MacOS_arm64_node_v2430", + EnableGREASE: false, + CipherSuites: []uint16{4865, 4866, 4867, 49195, 49199, 49196, 49200, 52393, 52392, 49161, 49171, 49162, 49172, 156, 157, 47, 53}, + Curves: []uint16{29, 23, 24}, + PointFormats: []uint16{0}, + SignatureAlgorithms: []uint16{0x0403, 0x0804, 0x0401, 0x0503, 0x0805, 0x0501, 0x0806, 0x0601, 0x0201}, + ALPNProtocols: []string{"http/1.1"}, + SupportedVersions: []uint16{0x0304, 0x0303}, + KeyShareGroups: []uint16{29}, + PSKModes: []uint16{1}, + Extensions: []uint16{0, 65037, 23, 65281, 10, 11, 35, 16, 5, 13, 18, 51, 45, 43}, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + captured := fetchCapturedFingerprint(t, captureURL, tc.profile) + if captured == nil { + return + } + + t.Logf("JA3 Hash: %s", captured.JA3Hash) + t.Logf("JA4: %s", captured.JA4) + + // Resolve effective profile values (what the dialer actually uses) + effectiveCipherSuites := tc.profile.CipherSuites + if len(effectiveCipherSuites) == 0 { + effectiveCipherSuites = defaultCipherSuites + } + effectiveCurves := tc.profile.Curves + if len(effectiveCurves) == 0 { + effectiveCurves = make([]uint16, len(defaultCurves)) + for i, c := range defaultCurves { + effectiveCurves[i] = uint16(c) + } + } + effectivePointFormats := tc.profile.PointFormats + if len(effectivePointFormats) == 0 { + effectivePointFormats = defaultPointFormats + } + effectiveSigAlgs := tc.profile.SignatureAlgorithms + if len(effectiveSigAlgs) == 0 { + effectiveSigAlgs = make([]uint16, len(defaultSignatureAlgorithms)) + for i, s := range defaultSignatureAlgorithms { + effectiveSigAlgs[i] = uint16(s) + } + } + effectiveALPN := tc.profile.ALPNProtocols + if len(effectiveALPN) == 0 { + effectiveALPN = []string{"http/1.1"} + } + effectiveVersions := tc.profile.SupportedVersions + if len(effectiveVersions) == 0 { + effectiveVersions = []uint16{0x0304, 0x0303} + } + effectiveKeyShare := tc.profile.KeyShareGroups + if len(effectiveKeyShare) == 0 { + effectiveKeyShare = []uint16{29} // X25519 + } + effectivePSKModes := tc.profile.PSKModes + if len(effectivePSKModes) == 0 { + effectivePSKModes = []uint16{1} // psk_dhe_ke + } + + // Verify each field + assertIntSliceEqual(t, "cipher_suites", uint16sToInts(effectiveCipherSuites), captured.CipherSuites) + assertIntSliceEqual(t, "curves", uint16sToInts(effectiveCurves), captured.Curves) + assertIntSliceEqual(t, "point_formats", uint16sToInts(effectivePointFormats), captured.PointFormats) + assertIntSliceEqual(t, "signature_algorithms", uint16sToInts(effectiveSigAlgs), captured.SignatureAlgorithms) + assertStringSliceEqual(t, "alpn_protocols", effectiveALPN, captured.ALPNProtocols) + assertIntSliceEqual(t, "supported_versions", uint16sToInts(effectiveVersions), captured.SupportedVersions) + assertIntSliceEqual(t, "key_share_groups", uint16sToInts(effectiveKeyShare), captured.KeyShareGroups) + assertIntSliceEqual(t, "psk_modes", uint16sToInts(effectivePSKModes), captured.PSKModes) + + if captured.EnableGREASE != tc.profile.EnableGREASE { + t.Errorf("enable_grease: got %v, want %v", captured.EnableGREASE, tc.profile.EnableGREASE) + } else { + t.Logf(" enable_grease: %v OK", captured.EnableGREASE) + } + + // Verify extension order + // Use profile.Extensions if set, otherwise the default order (Node.js 24.x) + expectedExtOrder := uint16sToInts(defaultExtensionOrder) + if len(tc.profile.Extensions) > 0 { + expectedExtOrder = uint16sToInts(tc.profile.Extensions) + } + // Strip GREASE values from both expected and captured for comparison + var filteredExpected, filteredActual []int + for _, e := range expectedExtOrder { + if !isGREASEValue(uint16(e)) { + filteredExpected = append(filteredExpected, e) + } + } + for _, e := range captured.Extensions { + if !isGREASEValue(uint16(e)) { + filteredActual = append(filteredActual, e) + } + } + assertIntSliceEqual(t, "extensions (order, non-GREASE)", filteredExpected, filteredActual) + + // Print full captured data as JSON for debugging + capturedJSON, _ := json.MarshalIndent(captured, " ", " ") + t.Logf("Full captured fingerprint:\n %s", string(capturedJSON)) + }) + } +} + +func fetchCapturedFingerprint(t *testing.T, captureURL string, profile *Profile) *CapturedFingerprint { + t.Helper() + + dialer := NewDialer(profile, nil) + client := &http.Client{ + Transport: &http.Transport{ + DialTLSContext: dialer.DialTLSContext, + }, + Timeout: 10 * time.Second, + } + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + req, err := http.NewRequestWithContext(ctx, "POST", captureURL, strings.NewReader(`{"model":"test"}`)) + if err != nil { + t.Fatalf("create request: %v", err) + return nil + } + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer test-token") + + resp, err := client.Do(req) + if err != nil { + t.Fatalf("request failed: %v", err) + return nil + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + t.Fatalf("read body: %v", err) + return nil + } + + var fp CapturedFingerprint + if err := json.Unmarshal(body, &fp); err != nil { + t.Logf("Response body: %s", string(body)) + t.Fatalf("parse response: %v", err) + return nil + } + + return &fp +} + +func uint16sToInts(vals []uint16) []int { + result := make([]int, len(vals)) + for i, v := range vals { + result[i] = int(v) + } + return result +} + +func assertIntSliceEqual(t *testing.T, name string, expected, actual []int) { + t.Helper() + if len(expected) != len(actual) { + t.Errorf("%s: length mismatch: got %d, want %d", name, len(actual), len(expected)) + if len(actual) < 20 && len(expected) < 20 { + t.Errorf(" got: %v", actual) + t.Errorf(" want: %v", expected) + } + return + } + mismatches := 0 + for i := range expected { + if expected[i] != actual[i] { + if mismatches < 5 { + t.Errorf("%s[%d]: got %d (0x%04x), want %d (0x%04x)", name, i, actual[i], actual[i], expected[i], expected[i]) + } + mismatches++ + } + } + if mismatches == 0 { + t.Logf(" %s: %d items OK", name, len(expected)) + } else if mismatches > 5 { + t.Errorf(" %s: %d/%d mismatches (showing first 5)", name, mismatches, len(expected)) + } +} + +func assertStringSliceEqual(t *testing.T, name string, expected, actual []string) { + t.Helper() + if len(expected) != len(actual) { + t.Errorf("%s: length mismatch: got %d (%v), want %d (%v)", name, len(actual), actual, len(expected), expected) + return + } + for i := range expected { + if expected[i] != actual[i] { + t.Errorf("%s[%d]: got %q, want %q", name, i, actual[i], expected[i]) + return + } + } + t.Logf(" %s: %v OK", name, expected) +} + +// TestBuildClientHelloSpecNewFields tests that new Profile fields are correctly applied. +func TestBuildClientHelloSpecNewFields(t *testing.T) { + // Test custom ALPN, versions, key shares, PSK modes + profile := &Profile{ + Name: "custom_full", + EnableGREASE: false, + CipherSuites: []uint16{0x1301, 0x1302}, + Curves: []uint16{29, 23}, + PointFormats: []uint16{0}, + SignatureAlgorithms: []uint16{0x0403, 0x0804}, + ALPNProtocols: []string{"h2", "http/1.1"}, + SupportedVersions: []uint16{0x0304}, + KeyShareGroups: []uint16{29, 23}, + PSKModes: []uint16{1}, + } + + spec := buildClientHelloSpecFromProfile(profile) + + // Verify cipher suites + if len(spec.CipherSuites) != 2 || spec.CipherSuites[0] != 0x1301 { + t.Errorf("cipher suites: got %v", spec.CipherSuites) + } + + // Check extensions for expected values + var foundALPN, foundVersions, foundKeyShare, foundPSK, foundSigAlgs bool + for _, ext := range spec.Extensions { + switch e := ext.(type) { + case *utls.ALPNExtension: + foundALPN = true + if len(e.AlpnProtocols) != 2 || e.AlpnProtocols[0] != "h2" { + t.Errorf("ALPN: got %v, want [h2, http/1.1]", e.AlpnProtocols) + } + case *utls.SupportedVersionsExtension: + foundVersions = true + if len(e.Versions) != 1 || e.Versions[0] != 0x0304 { + t.Errorf("versions: got %v, want [0x0304]", e.Versions) + } + case *utls.KeyShareExtension: + foundKeyShare = true + if len(e.KeyShares) != 2 { + t.Errorf("key shares: got %d entries, want 2", len(e.KeyShares)) + } + case *utls.PSKKeyExchangeModesExtension: + foundPSK = true + if len(e.Modes) != 1 || e.Modes[0] != 1 { + t.Errorf("PSK modes: got %v, want [1]", e.Modes) + } + case *utls.SignatureAlgorithmsExtension: + foundSigAlgs = true + if len(e.SupportedSignatureAlgorithms) != 2 { + t.Errorf("sig algs: got %d, want 2", len(e.SupportedSignatureAlgorithms)) + } + } + } + + for name, found := range map[string]bool{ + "ALPN": foundALPN, "Versions": foundVersions, "KeyShare": foundKeyShare, + "PSK": foundPSK, "SigAlgs": foundSigAlgs, + } { + if !found { + t.Errorf("extension %s not found in spec", name) + } + } + + // Test nil profile uses all defaults + specDefault := buildClientHelloSpecFromProfile(nil) + for _, ext := range specDefault.Extensions { + switch e := ext.(type) { + case *utls.ALPNExtension: + if len(e.AlpnProtocols) != 1 || e.AlpnProtocols[0] != "http/1.1" { + t.Errorf("default ALPN: got %v, want [http/1.1]", e.AlpnProtocols) + } + case *utls.SupportedVersionsExtension: + if len(e.Versions) != 2 { + t.Errorf("default versions: got %v, want 2 entries", e.Versions) + } + case *utls.KeyShareExtension: + if len(e.KeyShares) != 1 { + t.Errorf("default key shares: got %d, want 1", len(e.KeyShares)) + } + } + } + + t.Log("TestBuildClientHelloSpecNewFields passed") +} diff --git a/backend/internal/pkg/tlsfingerprint/dialer_integration_test.go b/backend/internal/pkg/tlsfingerprint/dialer_integration_test.go index 3f668fbe..38cddd0d 100644 --- a/backend/internal/pkg/tlsfingerprint/dialer_integration_test.go +++ b/backend/internal/pkg/tlsfingerprint/dialer_integration_test.go @@ -40,16 +40,15 @@ func skipIfExternalServiceUnavailable(t *testing.T, err error) { // TestJA3Fingerprint verifies the JA3/JA4 fingerprint matches expected value. // This test uses tls.peet.ws to verify the fingerprint. -// Expected JA3 hash: 1a28e69016765d92e3b381168d68922c (Claude CLI / Node.js 20.x) -// Expected JA4: t13d5911h1_a33745022dd6_1f22a2ca17c4 (d=domain) or t13i5911h1_... (i=IP) +// Expected JA3 hash: 44f88fca027f27bab4bb08d4af15f23e (Node.js 24.x) +// Expected JA4: t13d1714h1_5b57614c22b0_7baf387fc6ff func TestJA3Fingerprint(t *testing.T) { - // Skip if network is unavailable or if running in short mode if testing.Short() { t.Skip("skipping integration test in short mode") } profile := &Profile{ - Name: "Claude CLI Test", + Name: "Default Profile Test", EnableGREASE: false, } dialer := NewDialer(profile, nil) @@ -61,7 +60,6 @@ func TestJA3Fingerprint(t *testing.T) { Timeout: 30 * time.Second, } - // Use tls.peet.ws fingerprint detection API ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() @@ -69,7 +67,7 @@ func TestJA3Fingerprint(t *testing.T) { if err != nil { t.Fatalf("failed to create request: %v", err) } - req.Header.Set("User-Agent", "Claude Code/2.0.0 Node.js/20.0.0") + req.Header.Set("User-Agent", "Claude Code/2.0.0 Node.js/24.3.0") resp, err := client.Do(req) skipIfExternalServiceUnavailable(t, err) @@ -86,71 +84,23 @@ func TestJA3Fingerprint(t *testing.T) { t.Fatalf("failed to parse fingerprint response: %v", err) } - // Log all fingerprint information t.Logf("JA3: %s", fpResp.TLS.JA3) t.Logf("JA3 Hash: %s", fpResp.TLS.JA3Hash) t.Logf("JA4: %s", fpResp.TLS.JA4) - t.Logf("PeetPrint: %s", fpResp.TLS.PeetPrint) - t.Logf("PeetPrint Hash: %s", fpResp.TLS.PeetPrintHash) - // Verify JA3 hash matches expected value - expectedJA3Hash := "1a28e69016765d92e3b381168d68922c" + expectedJA3Hash := "44f88fca027f27bab4bb08d4af15f23e" if fpResp.TLS.JA3Hash == expectedJA3Hash { - t.Logf("✓ JA3 hash matches expected value: %s", expectedJA3Hash) + t.Logf("✓ JA3 hash matches: %s", expectedJA3Hash) } else { t.Errorf("✗ JA3 hash mismatch: got %s, expected %s", fpResp.TLS.JA3Hash, expectedJA3Hash) } - // Verify JA4 fingerprint - // JA4 format: t[version][sni][cipher_count][ext_count][alpn]_[cipher_hash]_[ext_hash] - // Expected: t13d5910h1 (d=domain) or t13i5910h1 (i=IP) - // The suffix _a33745022dd6_1f22a2ca17c4 should match - expectedJA4Suffix := "_a33745022dd6_1f22a2ca17c4" - if strings.HasSuffix(fpResp.TLS.JA4, expectedJA4Suffix) { - t.Logf("✓ JA4 suffix matches expected value: %s", expectedJA4Suffix) + expectedJA4CipherHash := "_5b57614c22b0_" + if strings.Contains(fpResp.TLS.JA4, expectedJA4CipherHash) { + t.Logf("✓ JA4 cipher hash matches: %s", expectedJA4CipherHash) } else { - t.Errorf("✗ JA4 suffix mismatch: got %s, expected suffix %s", fpResp.TLS.JA4, expectedJA4Suffix) + t.Errorf("✗ JA4 cipher hash mismatch: got %s, expected containing %s", fpResp.TLS.JA4, expectedJA4CipherHash) } - - // Verify JA4 prefix (t13d5911h1 or t13i5911h1) - // d = domain (SNI present), i = IP (no SNI) - // Since we connect to tls.peet.ws (domain), we expect 'd' - expectedJA4Prefix := "t13d5911h1" - if strings.HasPrefix(fpResp.TLS.JA4, expectedJA4Prefix) { - t.Logf("✓ JA4 prefix matches: %s (t13=TLS1.3, d=domain, 59=ciphers, 11=extensions, h1=HTTP/1.1)", expectedJA4Prefix) - } else { - // Also accept 'i' variant for IP connections - altPrefix := "t13i5911h1" - if strings.HasPrefix(fpResp.TLS.JA4, altPrefix) { - t.Logf("✓ JA4 prefix matches (IP variant): %s", altPrefix) - } else { - t.Errorf("✗ JA4 prefix mismatch: got %s, expected %s or %s", fpResp.TLS.JA4, expectedJA4Prefix, altPrefix) - } - } - - // Verify JA3 contains expected cipher suites (TLS 1.3 ciphers at the beginning) - if strings.Contains(fpResp.TLS.JA3, "4866-4867-4865") { - t.Logf("✓ JA3 contains expected TLS 1.3 cipher suites") - } else { - t.Logf("Warning: JA3 does not contain expected TLS 1.3 cipher suites") - } - - // Verify extension list (should be 11 extensions including SNI) - // Expected: 0-11-10-35-16-22-23-13-43-45-51 - expectedExtensions := "0-11-10-35-16-22-23-13-43-45-51" - if strings.Contains(fpResp.TLS.JA3, expectedExtensions) { - t.Logf("✓ JA3 contains expected extension list: %s", expectedExtensions) - } else { - t.Logf("Warning: JA3 extension list may differ") - } -} - -// TestProfileExpectation defines expected fingerprint values for a profile. -type TestProfileExpectation struct { - Profile *Profile - ExpectedJA3 string // Expected JA3 hash (empty = don't check) - ExpectedJA4 string // Expected full JA4 (empty = don't check) - JA4CipherHash string // Expected JA4 cipher hash - the stable middle part (empty = don't check) } // TestAllProfiles tests multiple TLS fingerprint profiles against tls.peet.ws. @@ -164,30 +114,24 @@ func TestAllProfiles(t *testing.T) { // These profiles are from config.yaml gateway.tls_fingerprint.profiles profiles := []TestProfileExpectation{ { - // Linux x64 Node.js v22.17.1 - // Expected JA3 Hash: 1a28e69016765d92e3b381168d68922c - // Expected JA4: t13d5911h1_a33745022dd6_1f22a2ca17c4 + // Default profile (Node.js 24.x) + Profile: &Profile{ + Name: "default_node_v24", + EnableGREASE: false, + }, + JA4CipherHash: "5b57614c22b0", + }, + { + // Linux x64 Node.js v22.17.1 (explicit profile with v22 extensions) Profile: &Profile{ Name: "linux_x64_node_v22171", EnableGREASE: false, CipherSuites: []uint16{4866, 4867, 4865, 49199, 49195, 49200, 49196, 158, 49191, 103, 49192, 107, 163, 159, 52393, 52392, 52394, 49327, 49325, 49315, 49311, 49245, 49249, 49239, 49235, 162, 49326, 49324, 49314, 49310, 49244, 49248, 49238, 49234, 49188, 106, 49187, 64, 49162, 49172, 57, 56, 49161, 49171, 51, 50, 157, 49313, 49309, 49233, 156, 49312, 49308, 49232, 61, 60, 53, 47, 255}, Curves: []uint16{29, 23, 30, 25, 24, 256, 257, 258, 259, 260}, - PointFormats: []uint8{0, 1, 2}, + PointFormats: []uint16{0, 1, 2}, + Extensions: []uint16{0, 11, 10, 35, 16, 22, 23, 13, 43, 45, 51}, }, - JA4CipherHash: "a33745022dd6", // stable part - }, - { - // MacOS arm64 Node.js v22.18.0 - // Expected JA3 Hash: 70cb5ca646080902703ffda87036a5ea - // Expected JA4: t13d5912h1_a33745022dd6_dbd39dd1d406 - Profile: &Profile{ - Name: "macos_arm64_node_v22180", - EnableGREASE: false, - CipherSuites: []uint16{4866, 4867, 4865, 49199, 49195, 49200, 49196, 158, 49191, 103, 49192, 107, 163, 159, 52393, 52392, 52394, 49327, 49325, 49315, 49311, 49245, 49249, 49239, 49235, 162, 49326, 49324, 49314, 49310, 49244, 49248, 49238, 49234, 49188, 106, 49187, 64, 49162, 49172, 57, 56, 49161, 49171, 51, 50, 157, 49313, 49309, 49233, 156, 49312, 49308, 49232, 61, 60, 53, 47, 255}, - Curves: []uint16{29, 23, 30, 25, 24, 256, 257, 258, 259, 260}, - PointFormats: []uint8{0, 1, 2}, - }, - JA4CipherHash: "a33745022dd6", // stable part (same cipher suites) + JA4CipherHash: "a33745022dd6", }, } diff --git a/backend/internal/pkg/tlsfingerprint/dialer_test.go b/backend/internal/pkg/tlsfingerprint/dialer_test.go index 6d3db174..048418c9 100644 --- a/backend/internal/pkg/tlsfingerprint/dialer_test.go +++ b/backend/internal/pkg/tlsfingerprint/dialer_test.go @@ -55,13 +55,13 @@ func TestDialerBasicConnection(t *testing.T) { // TestJA3Fingerprint verifies the JA3/JA4 fingerprint matches expected value. // This test uses tls.peet.ws to verify the fingerprint. -// Expected JA3 hash: 1a28e69016765d92e3b381168d68922c (Claude CLI / Node.js 20.x) -// Expected JA4: t13d5911h1_a33745022dd6_1f22a2ca17c4 (d=domain) or t13i5911h1_... (i=IP) +// Expected JA3 hash: 44f88fca027f27bab4bb08d4af15f23e (Node.js 24.x) +// Expected JA4: t13d1714h1_5b57614c22b0_7baf387fc6ff func TestJA3Fingerprint(t *testing.T) { skipNetworkTest(t) profile := &Profile{ - Name: "Claude CLI Test", + Name: "Default Profile Test", EnableGREASE: false, } dialer := NewDialer(profile, nil) @@ -81,7 +81,7 @@ func TestJA3Fingerprint(t *testing.T) { if err != nil { t.Fatalf("failed to create request: %v", err) } - req.Header.Set("User-Agent", "Claude Code/2.0.0 Node.js/20.0.0") + req.Header.Set("User-Agent", "Claude Code/2.0.0 Node.js/24.3.0") resp, err := client.Do(req) if err != nil { @@ -107,34 +107,28 @@ func TestJA3Fingerprint(t *testing.T) { t.Logf("PeetPrint: %s", fpResp.TLS.PeetPrint) t.Logf("PeetPrint Hash: %s", fpResp.TLS.PeetPrintHash) - // Verify JA3 hash matches expected value - expectedJA3Hash := "1a28e69016765d92e3b381168d68922c" + // Verify JA3 hash matches expected value (Node.js 24.x default) + expectedJA3Hash := "44f88fca027f27bab4bb08d4af15f23e" if fpResp.TLS.JA3Hash == expectedJA3Hash { t.Logf("✓ JA3 hash matches expected value: %s", expectedJA3Hash) } else { t.Errorf("✗ JA3 hash mismatch: got %s, expected %s", fpResp.TLS.JA3Hash, expectedJA3Hash) } - // Verify JA4 fingerprint - // JA4 format: t[version][sni][cipher_count][ext_count][alpn]_[cipher_hash]_[ext_hash] - // Expected: t13d5910h1 (d=domain) or t13i5910h1 (i=IP) - // The suffix _a33745022dd6_1f22a2ca17c4 should match - expectedJA4Suffix := "_a33745022dd6_1f22a2ca17c4" - if strings.HasSuffix(fpResp.TLS.JA4, expectedJA4Suffix) { - t.Logf("✓ JA4 suffix matches expected value: %s", expectedJA4Suffix) + // Verify JA4 cipher hash (stable middle part) + expectedJA4CipherHash := "_5b57614c22b0_" + if strings.Contains(fpResp.TLS.JA4, expectedJA4CipherHash) { + t.Logf("✓ JA4 cipher hash matches: %s", expectedJA4CipherHash) } else { - t.Errorf("✗ JA4 suffix mismatch: got %s, expected suffix %s", fpResp.TLS.JA4, expectedJA4Suffix) + t.Errorf("✗ JA4 cipher hash mismatch: got %s, expected containing %s", fpResp.TLS.JA4, expectedJA4CipherHash) } - // Verify JA4 prefix (t13d5911h1 or t13i5911h1) - // d = domain (SNI present), i = IP (no SNI) - // Since we connect to tls.peet.ws (domain), we expect 'd' - expectedJA4Prefix := "t13d5911h1" + // Verify JA4 prefix (t13d1714h1 or t13i1714h1) + expectedJA4Prefix := "t13d1714h1" if strings.HasPrefix(fpResp.TLS.JA4, expectedJA4Prefix) { - t.Logf("✓ JA4 prefix matches: %s (t13=TLS1.3, d=domain, 59=ciphers, 11=extensions, h1=HTTP/1.1)", expectedJA4Prefix) + t.Logf("✓ JA4 prefix matches: %s (t13=TLS1.3, d=domain, 17=ciphers, 14=extensions, h1=HTTP/1.1)", expectedJA4Prefix) } else { - // Also accept 'i' variant for IP connections - altPrefix := "t13i5911h1" + altPrefix := "t13i1714h1" if strings.HasPrefix(fpResp.TLS.JA4, altPrefix) { t.Logf("✓ JA4 prefix matches (IP variant): %s", altPrefix) } else { @@ -142,16 +136,15 @@ func TestJA3Fingerprint(t *testing.T) { } } - // Verify JA3 contains expected cipher suites (TLS 1.3 ciphers at the beginning) - if strings.Contains(fpResp.TLS.JA3, "4866-4867-4865") { + // Verify JA3 contains expected TLS 1.3 cipher suites + if strings.Contains(fpResp.TLS.JA3, "4865-4866-4867") { t.Logf("✓ JA3 contains expected TLS 1.3 cipher suites") } else { t.Logf("Warning: JA3 does not contain expected TLS 1.3 cipher suites") } - // Verify extension list (should be 11 extensions including SNI) - // Expected: 0-11-10-35-16-22-23-13-43-45-51 - expectedExtensions := "0-11-10-35-16-22-23-13-43-45-51" + // Verify extension list (14 extensions, Node.js 24.x order) + expectedExtensions := "0-65037-23-65281-10-11-35-16-5-13-18-51-45-43" if strings.Contains(fpResp.TLS.JA3, expectedExtensions) { t.Logf("✓ JA3 contains expected extension list: %s", expectedExtensions) } else { @@ -186,8 +179,8 @@ func TestDialerWithProfile(t *testing.T) { // Build specs and compare // Note: We can't directly compare JA3 without making network requests // but we can verify the specs are different - spec1 := dialer1.buildClientHelloSpec() - spec2 := dialer2.buildClientHelloSpec() + spec1 := buildClientHelloSpecFromProfile(dialer1.profile) + spec2 := buildClientHelloSpecFromProfile(dialer2.profile) // Profile with GREASE should have more extensions if len(spec2.Extensions) <= len(spec1.Extensions) { @@ -296,47 +289,33 @@ func mustParseURL(rawURL string) *url.URL { return u } -// TestProfileExpectation defines expected fingerprint values for a profile. -type TestProfileExpectation struct { - Profile *Profile - ExpectedJA3 string // Expected JA3 hash (empty = don't check) - ExpectedJA4 string // Expected full JA4 (empty = don't check) - JA4CipherHash string // Expected JA4 cipher hash - the stable middle part (empty = don't check) -} - // TestAllProfiles tests multiple TLS fingerprint profiles against tls.peet.ws. // Run with: go test -v -run TestAllProfiles ./internal/pkg/tlsfingerprint/... func TestAllProfiles(t *testing.T) { skipNetworkTest(t) - // Define all profiles to test with their expected fingerprints - // These profiles are from config.yaml gateway.tls_fingerprint.profiles profiles := []TestProfileExpectation{ { - // Linux x64 Node.js v22.17.1 - // Expected JA3 Hash: 1a28e69016765d92e3b381168d68922c - // Expected JA4: t13d5911h1_a33745022dd6_1f22a2ca17c4 + // Default profile (Node.js 24.x) + // JA3 Hash: 44f88fca027f27bab4bb08d4af15f23e + // JA4: t13d1714h1_5b57614c22b0_7baf387fc6ff + Profile: &Profile{ + Name: "default_node_v24", + EnableGREASE: false, + }, + JA4CipherHash: "5b57614c22b0", + }, + { + // Linux x64 Node.js v22.17.1 (explicit profile) Profile: &Profile{ Name: "linux_x64_node_v22171", EnableGREASE: false, CipherSuites: []uint16{4866, 4867, 4865, 49199, 49195, 49200, 49196, 158, 49191, 103, 49192, 107, 163, 159, 52393, 52392, 52394, 49327, 49325, 49315, 49311, 49245, 49249, 49239, 49235, 162, 49326, 49324, 49314, 49310, 49244, 49248, 49238, 49234, 49188, 106, 49187, 64, 49162, 49172, 57, 56, 49161, 49171, 51, 50, 157, 49313, 49309, 49233, 156, 49312, 49308, 49232, 61, 60, 53, 47, 255}, Curves: []uint16{29, 23, 30, 25, 24, 256, 257, 258, 259, 260}, - PointFormats: []uint8{0, 1, 2}, + PointFormats: []uint16{0, 1, 2}, + Extensions: []uint16{0, 11, 10, 35, 16, 22, 23, 13, 43, 45, 51}, }, - JA4CipherHash: "a33745022dd6", // stable part - }, - { - // MacOS arm64 Node.js v22.18.0 - // Expected JA3 Hash: 70cb5ca646080902703ffda87036a5ea - // Expected JA4: t13d5912h1_a33745022dd6_dbd39dd1d406 - Profile: &Profile{ - Name: "macos_arm64_node_v22180", - EnableGREASE: false, - CipherSuites: []uint16{4866, 4867, 4865, 49199, 49195, 49200, 49196, 158, 49191, 103, 49192, 107, 163, 159, 52393, 52392, 52394, 49327, 49325, 49315, 49311, 49245, 49249, 49239, 49235, 162, 49326, 49324, 49314, 49310, 49244, 49248, 49238, 49234, 49188, 106, 49187, 64, 49162, 49172, 57, 56, 49161, 49171, 51, 50, 157, 49313, 49309, 49233, 156, 49312, 49308, 49232, 61, 60, 53, 47, 255}, - Curves: []uint16{29, 23, 30, 25, 24, 256, 257, 258, 259, 260}, - PointFormats: []uint8{0, 1, 2}, - }, - JA4CipherHash: "a33745022dd6", // stable part (same cipher suites) + JA4CipherHash: "a33745022dd6", }, } diff --git a/backend/internal/pkg/tlsfingerprint/registry.go b/backend/internal/pkg/tlsfingerprint/registry.go deleted file mode 100644 index 6e9dc539..00000000 --- a/backend/internal/pkg/tlsfingerprint/registry.go +++ /dev/null @@ -1,171 +0,0 @@ -// Package tlsfingerprint provides TLS fingerprint simulation for HTTP clients. -package tlsfingerprint - -import ( - "log/slog" - "sort" - "sync" - - "github.com/Wei-Shaw/sub2api/internal/config" -) - -// DefaultProfileName is the name of the built-in Claude CLI profile. -const DefaultProfileName = "claude_cli_v2" - -// Registry manages TLS fingerprint profiles. -// It holds a collection of profiles that can be used for TLS fingerprint simulation. -// Profiles are selected based on account ID using modulo operation. -type Registry struct { - mu sync.RWMutex - profiles map[string]*Profile - profileNames []string // Sorted list of profile names for deterministic selection -} - -// NewRegistry creates a new TLS fingerprint profile registry. -// It initializes with the built-in default profile. -func NewRegistry() *Registry { - r := &Registry{ - profiles: make(map[string]*Profile), - profileNames: make([]string, 0), - } - - // Register the built-in default profile - r.registerBuiltinProfile() - - return r -} - -// NewRegistryFromConfig creates a new registry and loads profiles from config. -// If the config has custom profiles defined, they will be merged with the built-in default. -func NewRegistryFromConfig(cfg *config.TLSFingerprintConfig) *Registry { - r := NewRegistry() - - if cfg == nil || !cfg.Enabled { - slog.Debug("tls_registry_disabled", "reason", "disabled or no config") - return r - } - - // Load custom profiles from config - for name, profileCfg := range cfg.Profiles { - profile := &Profile{ - Name: profileCfg.Name, - EnableGREASE: profileCfg.EnableGREASE, - CipherSuites: profileCfg.CipherSuites, - Curves: profileCfg.Curves, - PointFormats: profileCfg.PointFormats, - } - - // If the profile has empty values, they will use defaults in dialer - r.RegisterProfile(name, profile) - slog.Debug("tls_registry_loaded_profile", "key", name, "name", profileCfg.Name) - } - - slog.Debug("tls_registry_initialized", "profile_count", len(r.profileNames), "profiles", r.profileNames) - return r -} - -// registerBuiltinProfile adds the default Claude CLI profile to the registry. -func (r *Registry) registerBuiltinProfile() { - defaultProfile := &Profile{ - Name: "Claude CLI 2.x (Node.js 20.x + OpenSSL 3.x)", - EnableGREASE: false, // Node.js does not use GREASE - // Empty slices will cause dialer to use built-in defaults - CipherSuites: nil, - Curves: nil, - PointFormats: nil, - } - r.RegisterProfile(DefaultProfileName, defaultProfile) -} - -// RegisterProfile adds or updates a profile in the registry. -func (r *Registry) RegisterProfile(name string, profile *Profile) { - r.mu.Lock() - defer r.mu.Unlock() - - // Check if this is a new profile - _, exists := r.profiles[name] - r.profiles[name] = profile - - if !exists { - r.profileNames = append(r.profileNames, name) - // Keep names sorted for deterministic selection - sort.Strings(r.profileNames) - } -} - -// GetProfile returns a profile by name. -// Returns nil if the profile does not exist. -func (r *Registry) GetProfile(name string) *Profile { - r.mu.RLock() - defer r.mu.RUnlock() - return r.profiles[name] -} - -// GetDefaultProfile returns the built-in default profile. -func (r *Registry) GetDefaultProfile() *Profile { - return r.GetProfile(DefaultProfileName) -} - -// GetProfileByAccountID returns a profile for the given account ID. -// The profile is selected using: profileNames[accountID % len(profiles)] -// This ensures deterministic profile assignment for each account. -func (r *Registry) GetProfileByAccountID(accountID int64) *Profile { - r.mu.RLock() - defer r.mu.RUnlock() - - if len(r.profileNames) == 0 { - return nil - } - - // Use modulo to select profile index - // Use absolute value to handle negative IDs (though unlikely) - idx := accountID - if idx < 0 { - idx = -idx - } - selectedIndex := int(idx % int64(len(r.profileNames))) - selectedName := r.profileNames[selectedIndex] - - return r.profiles[selectedName] -} - -// ProfileCount returns the number of registered profiles. -func (r *Registry) ProfileCount() int { - r.mu.RLock() - defer r.mu.RUnlock() - return len(r.profiles) -} - -// ProfileNames returns a sorted list of all registered profile names. -func (r *Registry) ProfileNames() []string { - r.mu.RLock() - defer r.mu.RUnlock() - - // Return a copy to prevent modification - names := make([]string, len(r.profileNames)) - copy(names, r.profileNames) - return names -} - -// Global registry instance for convenience -var globalRegistry *Registry -var globalRegistryOnce sync.Once - -// GlobalRegistry returns the global TLS fingerprint registry. -// The registry is lazily initialized with the default profile. -func GlobalRegistry() *Registry { - globalRegistryOnce.Do(func() { - globalRegistry = NewRegistry() - }) - return globalRegistry -} - -// InitGlobalRegistry initializes the global registry with configuration. -// This should be called during application startup. -// It is safe to call multiple times; subsequent calls will update the registry. -func InitGlobalRegistry(cfg *config.TLSFingerprintConfig) *Registry { - globalRegistryOnce.Do(func() { - globalRegistry = NewRegistryFromConfig(cfg) - }) - return globalRegistry -} diff --git a/backend/internal/pkg/tlsfingerprint/registry_test.go b/backend/internal/pkg/tlsfingerprint/registry_test.go deleted file mode 100644 index 752ba0cc..00000000 --- a/backend/internal/pkg/tlsfingerprint/registry_test.go +++ /dev/null @@ -1,243 +0,0 @@ -package tlsfingerprint - -import ( - "testing" - - "github.com/Wei-Shaw/sub2api/internal/config" -) - -func TestNewRegistry(t *testing.T) { - r := NewRegistry() - - // Should have exactly one profile (the default) - if r.ProfileCount() != 1 { - t.Errorf("expected 1 profile, got %d", r.ProfileCount()) - } - - // Should have the default profile - profile := r.GetDefaultProfile() - if profile == nil { - t.Error("expected default profile to exist") - } - - // Default profile name should be in the list - names := r.ProfileNames() - if len(names) != 1 || names[0] != DefaultProfileName { - t.Errorf("expected profile names to be [%s], got %v", DefaultProfileName, names) - } -} - -func TestRegisterProfile(t *testing.T) { - r := NewRegistry() - - // Register a new profile - customProfile := &Profile{ - Name: "Custom Profile", - EnableGREASE: true, - } - r.RegisterProfile("custom", customProfile) - - // Should now have 2 profiles - if r.ProfileCount() != 2 { - t.Errorf("expected 2 profiles, got %d", r.ProfileCount()) - } - - // Should be able to retrieve the custom profile - retrieved := r.GetProfile("custom") - if retrieved == nil { - t.Fatal("expected custom profile to exist") - } - if retrieved.Name != "Custom Profile" { - t.Errorf("expected profile name 'Custom Profile', got '%s'", retrieved.Name) - } - if !retrieved.EnableGREASE { - t.Error("expected EnableGREASE to be true") - } -} - -func TestGetProfile(t *testing.T) { - r := NewRegistry() - - // Get existing profile - profile := r.GetProfile(DefaultProfileName) - if profile == nil { - t.Error("expected default profile to exist") - } - - // Get non-existing profile - nonExistent := r.GetProfile("nonexistent") - if nonExistent != nil { - t.Error("expected nil for non-existent profile") - } -} - -func TestGetProfileByAccountID(t *testing.T) { - r := NewRegistry() - - // With only default profile, all account IDs should return the same profile - for i := int64(0); i < 10; i++ { - profile := r.GetProfileByAccountID(i) - if profile == nil { - t.Errorf("expected profile for account %d, got nil", i) - } - } - - // Add more profiles - r.RegisterProfile("profile_a", &Profile{Name: "Profile A"}) - r.RegisterProfile("profile_b", &Profile{Name: "Profile B"}) - - // Now we have 3 profiles: claude_cli_v2, profile_a, profile_b - // Names are sorted, so order is: claude_cli_v2, profile_a, profile_b - expectedOrder := []string{DefaultProfileName, "profile_a", "profile_b"} - names := r.ProfileNames() - for i, name := range expectedOrder { - if names[i] != name { - t.Errorf("expected name at index %d to be %s, got %s", i, name, names[i]) - } - } - - // Test modulo selection - // Account ID 0 % 3 = 0 -> claude_cli_v2 - // Account ID 1 % 3 = 1 -> profile_a - // Account ID 2 % 3 = 2 -> profile_b - // Account ID 3 % 3 = 0 -> claude_cli_v2 - testCases := []struct { - accountID int64 - expectedName string - }{ - {0, "Claude CLI 2.x (Node.js 20.x + OpenSSL 3.x)"}, - {1, "Profile A"}, - {2, "Profile B"}, - {3, "Claude CLI 2.x (Node.js 20.x + OpenSSL 3.x)"}, - {4, "Profile A"}, - {5, "Profile B"}, - {100, "Profile A"}, // 100 % 3 = 1 - {-1, "Profile A"}, // |-1| % 3 = 1 - {-3, "Claude CLI 2.x (Node.js 20.x + OpenSSL 3.x)"}, // |-3| % 3 = 0 - } - - for _, tc := range testCases { - profile := r.GetProfileByAccountID(tc.accountID) - if profile == nil { - t.Errorf("expected profile for account %d, got nil", tc.accountID) - continue - } - if profile.Name != tc.expectedName { - t.Errorf("account %d: expected profile name '%s', got '%s'", tc.accountID, tc.expectedName, profile.Name) - } - } -} - -func TestNewRegistryFromConfig(t *testing.T) { - // Test with nil config - r := NewRegistryFromConfig(nil) - if r.ProfileCount() != 1 { - t.Errorf("expected 1 profile with nil config, got %d", r.ProfileCount()) - } - - // Test with disabled config - disabledCfg := &config.TLSFingerprintConfig{ - Enabled: false, - } - r = NewRegistryFromConfig(disabledCfg) - if r.ProfileCount() != 1 { - t.Errorf("expected 1 profile with disabled config, got %d", r.ProfileCount()) - } - - // Test with enabled config and custom profiles - enabledCfg := &config.TLSFingerprintConfig{ - Enabled: true, - Profiles: map[string]config.TLSProfileConfig{ - "custom1": { - Name: "Custom Profile 1", - EnableGREASE: true, - }, - "custom2": { - Name: "Custom Profile 2", - EnableGREASE: false, - }, - }, - } - r = NewRegistryFromConfig(enabledCfg) - - // Should have 3 profiles: default + 2 custom - if r.ProfileCount() != 3 { - t.Errorf("expected 3 profiles, got %d", r.ProfileCount()) - } - - // Check custom profiles exist - custom1 := r.GetProfile("custom1") - if custom1 == nil || custom1.Name != "Custom Profile 1" { - t.Error("expected custom1 profile to exist with correct name") - } - custom2 := r.GetProfile("custom2") - if custom2 == nil || custom2.Name != "Custom Profile 2" { - t.Error("expected custom2 profile to exist with correct name") - } -} - -func TestProfileNames(t *testing.T) { - r := NewRegistry() - - // Add profiles in non-alphabetical order - r.RegisterProfile("zebra", &Profile{Name: "Zebra"}) - r.RegisterProfile("alpha", &Profile{Name: "Alpha"}) - r.RegisterProfile("beta", &Profile{Name: "Beta"}) - - names := r.ProfileNames() - - // Should be sorted alphabetically - expected := []string{"alpha", "beta", DefaultProfileName, "zebra"} - if len(names) != len(expected) { - t.Errorf("expected %d names, got %d", len(expected), len(names)) - } - for i, name := range expected { - if names[i] != name { - t.Errorf("expected name at index %d to be %s, got %s", i, name, names[i]) - } - } - - // Test that returned slice is a copy (modifying it shouldn't affect registry) - names[0] = "modified" - originalNames := r.ProfileNames() - if originalNames[0] == "modified" { - t.Error("modifying returned slice should not affect registry") - } -} - -func TestConcurrentAccess(t *testing.T) { - r := NewRegistry() - - // Run concurrent reads and writes - done := make(chan bool) - - // Writers - for i := 0; i < 10; i++ { - go func(id int) { - for j := 0; j < 100; j++ { - r.RegisterProfile("concurrent"+string(rune('0'+id)), &Profile{Name: "Concurrent"}) - } - done <- true - }(i) - } - - // Readers - for i := 0; i < 10; i++ { - go func(id int) { - for j := 0; j < 100; j++ { - _ = r.ProfileCount() - _ = r.ProfileNames() - _ = r.GetProfileByAccountID(int64(id * j)) - _ = r.GetProfile(DefaultProfileName) - } - done <- true - }(i) - } - - // Wait for all goroutines - for i := 0; i < 20; i++ { - <-done - } - - // Test should pass without data races (run with -race flag) -} diff --git a/backend/internal/pkg/tlsfingerprint/test_types_test.go b/backend/internal/pkg/tlsfingerprint/test_types_test.go index 2bbf2d22..1711100d 100644 --- a/backend/internal/pkg/tlsfingerprint/test_types_test.go +++ b/backend/internal/pkg/tlsfingerprint/test_types_test.go @@ -8,6 +8,14 @@ type FingerprintResponse struct { HTTP2 any `json:"http2"` } +// TestProfileExpectation defines expected fingerprint values for a profile. +type TestProfileExpectation struct { + Profile *Profile + ExpectedJA3 string // Expected JA3 hash (empty = don't check) + ExpectedJA4 string // Expected full JA4 (empty = don't check) + JA4CipherHash string // Expected JA4 cipher hash - the stable middle part (empty = don't check) +} + // TLSInfo contains TLS fingerprint details. type TLSInfo struct { JA3 string `json:"ja3"` diff --git a/backend/internal/repository/claude_usage_service.go b/backend/internal/repository/claude_usage_service.go index 1264f6bb..b44adde2 100644 --- a/backend/internal/repository/claude_usage_service.go +++ b/backend/internal/repository/claude_usage_service.go @@ -68,10 +68,9 @@ func (s *claudeUsageService) FetchUsageWithOptions(ctx context.Context, opts *se var resp *http.Response - // 如果启用 TLS 指纹且有 HTTPUpstream,使用 DoWithTLS - if opts.EnableTLSFingerprint && s.httpUpstream != nil { - // accountConcurrency 传 0 使用默认连接池配置,usage 请求不需要特殊的并发设置 - resp, err = s.httpUpstream.DoWithTLS(req, opts.ProxyURL, opts.AccountID, 0, true) + // 如果有 TLS Profile 且有 HTTPUpstream,使用 DoWithTLS + if opts.TLSProfile != nil && s.httpUpstream != nil { + resp, err = s.httpUpstream.DoWithTLS(req, opts.ProxyURL, opts.AccountID, 0, opts.TLSProfile) if err != nil { return nil, fmt.Errorf("request with TLS fingerprint failed: %w", err) } diff --git a/backend/internal/repository/http_upstream.go b/backend/internal/repository/http_upstream.go index 12523a91..4309e997 100644 --- a/backend/internal/repository/http_upstream.go +++ b/backend/internal/repository/http_upstream.go @@ -161,26 +161,14 @@ func (s *httpUpstreamService) Do(req *http.Request, proxyURL string, accountID i } // DoWithTLS 执行带 TLS 指纹伪装的 HTTP 请求 -// 根据 enableTLSFingerprint 参数决定是否使用 TLS 指纹 // -// 参数: -// - req: HTTP 请求对象 -// - proxyURL: 代理地址,空字符串表示直连 -// - accountID: 账户 ID,用于账户级隔离和 TLS 指纹模板选择 -// - accountConcurrency: 账户并发限制,用于动态调整连接池大小 -// - enableTLSFingerprint: 是否启用 TLS 指纹伪装 -// -// TLS 指纹说明: -// - 当 enableTLSFingerprint=true 时,使用 utls 库模拟 Claude CLI 的 TLS 指纹 -// - 指纹模板根据 accountID % len(profiles) 自动选择 -// - 支持直连、HTTP/HTTPS 代理、SOCKS5 代理三种场景 -func (s *httpUpstreamService) DoWithTLS(req *http.Request, proxyURL string, accountID int64, accountConcurrency int, enableTLSFingerprint bool) (*http.Response, error) { - // 如果未启用 TLS 指纹,直接使用标准请求路径 - if !enableTLSFingerprint { +// profile 为 nil 时不启用 TLS 指纹,行为与 Do 方法相同。 +// profile 非 nil 时使用指定的 Profile 进行 TLS 指纹伪装。 +func (s *httpUpstreamService) DoWithTLS(req *http.Request, proxyURL string, accountID int64, accountConcurrency int, profile *tlsfingerprint.Profile) (*http.Response, error) { + if profile == nil { return s.Do(req, proxyURL, accountID, accountConcurrency) } - // TLS 指纹已启用,记录调试日志 targetHost := "" if req != nil && req.URL != nil { targetHost = req.URL.Host @@ -189,46 +177,28 @@ func (s *httpUpstreamService) DoWithTLS(req *http.Request, proxyURL string, acco if proxyURL != "" { proxyInfo = proxyURL } - slog.Debug("tls_fingerprint_enabled", "account_id", accountID, "target", targetHost, "proxy", proxyInfo) + slog.Debug("tls_fingerprint_enabled", "account_id", accountID, "target", targetHost, "proxy", proxyInfo, "profile", profile.Name) if err := s.validateRequestHost(req); err != nil { return nil, err } - // 获取 TLS 指纹 Profile - registry := tlsfingerprint.GlobalRegistry() - profile := registry.GetProfileByAccountID(accountID) - if profile == nil { - // 如果获取不到 profile,回退到普通请求 - slog.Debug("tls_fingerprint_no_profile", "account_id", accountID, "fallback", "standard_request") - return s.Do(req, proxyURL, accountID, accountConcurrency) - } - - slog.Debug("tls_fingerprint_using_profile", "account_id", accountID, "profile", profile.Name, "grease", profile.EnableGREASE) - - // 获取或创建带 TLS 指纹的客户端 entry, err := s.acquireClientWithTLS(proxyURL, accountID, accountConcurrency, profile) if err != nil { slog.Debug("tls_fingerprint_acquire_client_failed", "account_id", accountID, "error", err) return nil, err } - // 执行请求 resp, err := entry.client.Do(req) if err != nil { - // 请求失败,立即减少计数 atomic.AddInt64(&entry.inFlight, -1) atomic.StoreInt64(&entry.lastUsed, time.Now().UnixNano()) slog.Debug("tls_fingerprint_request_failed", "account_id", accountID, "error", err) return nil, err } - slog.Debug("tls_fingerprint_request_success", "account_id", accountID, "status", resp.StatusCode) - - // 如果上游返回了压缩内容,解压后再交给业务层 decompressResponseBody(resp) - // 包装响应体,在关闭时自动减少计数并更新时间戳 resp.Body = wrapTrackedBody(resp.Body, func() { atomic.AddInt64(&entry.inFlight, -1) atomic.StoreInt64(&entry.lastUsed, time.Now().UnixNano()) diff --git a/backend/internal/repository/tls_fingerprint_profile_cache.go b/backend/internal/repository/tls_fingerprint_profile_cache.go new file mode 100644 index 00000000..81ee0434 --- /dev/null +++ b/backend/internal/repository/tls_fingerprint_profile_cache.go @@ -0,0 +1,122 @@ +package repository + +import ( + "context" + "encoding/json" + "log/slog" + "sync" + "time" + + "github.com/Wei-Shaw/sub2api/internal/model" + "github.com/Wei-Shaw/sub2api/internal/service" + "github.com/redis/go-redis/v9" +) + +const ( + tlsFPProfileCacheKey = "tls_fingerprint_profiles" + tlsFPProfilePubSubKey = "tls_fingerprint_profiles_updated" + tlsFPProfileCacheTTL = 24 * time.Hour +) + +type tlsFingerprintProfileCache struct { + rdb *redis.Client + localCache []*model.TLSFingerprintProfile + localMu sync.RWMutex +} + +// NewTLSFingerprintProfileCache 创建 TLS 指纹模板缓存 +func NewTLSFingerprintProfileCache(rdb *redis.Client) service.TLSFingerprintProfileCache { + return &tlsFingerprintProfileCache{ + rdb: rdb, + } +} + +// Get 从缓存获取模板列表 +func (c *tlsFingerprintProfileCache) Get(ctx context.Context) ([]*model.TLSFingerprintProfile, bool) { + c.localMu.RLock() + if c.localCache != nil { + profiles := c.localCache + c.localMu.RUnlock() + return profiles, true + } + c.localMu.RUnlock() + + data, err := c.rdb.Get(ctx, tlsFPProfileCacheKey).Bytes() + if err != nil { + if err != redis.Nil { + slog.Warn("tls_fp_profile_cache_get_failed", "error", err) + } + return nil, false + } + + var profiles []*model.TLSFingerprintProfile + if err := json.Unmarshal(data, &profiles); err != nil { + slog.Warn("tls_fp_profile_cache_unmarshal_failed", "error", err) + return nil, false + } + + c.localMu.Lock() + c.localCache = profiles + c.localMu.Unlock() + + return profiles, true +} + +// Set 设置缓存 +func (c *tlsFingerprintProfileCache) Set(ctx context.Context, profiles []*model.TLSFingerprintProfile) error { + data, err := json.Marshal(profiles) + if err != nil { + return err + } + + if err := c.rdb.Set(ctx, tlsFPProfileCacheKey, data, tlsFPProfileCacheTTL).Err(); err != nil { + return err + } + + c.localMu.Lock() + c.localCache = profiles + c.localMu.Unlock() + + return nil +} + +// Invalidate 使缓存失效 +func (c *tlsFingerprintProfileCache) Invalidate(ctx context.Context) error { + c.localMu.Lock() + c.localCache = nil + c.localMu.Unlock() + + return c.rdb.Del(ctx, tlsFPProfileCacheKey).Err() +} + +// NotifyUpdate 通知其他实例刷新缓存 +func (c *tlsFingerprintProfileCache) NotifyUpdate(ctx context.Context) error { + return c.rdb.Publish(ctx, tlsFPProfilePubSubKey, "refresh").Err() +} + +// SubscribeUpdates 订阅缓存更新通知 +func (c *tlsFingerprintProfileCache) SubscribeUpdates(ctx context.Context, handler func()) { + go func() { + sub := c.rdb.Subscribe(ctx, tlsFPProfilePubSubKey) + defer func() { _ = sub.Close() }() + + ch := sub.Channel() + for { + select { + case <-ctx.Done(): + slog.Debug("tls_fp_profile_cache_subscriber_stopped", "reason", "context_done") + return + case msg := <-ch: + if msg == nil { + slog.Warn("tls_fp_profile_cache_subscriber_stopped", "reason", "channel_closed") + return + } + c.localMu.Lock() + c.localCache = nil + c.localMu.Unlock() + + handler() + } + } + }() +} diff --git a/backend/internal/repository/tls_fingerprint_profile_repo.go b/backend/internal/repository/tls_fingerprint_profile_repo.go new file mode 100644 index 00000000..40bebdc3 --- /dev/null +++ b/backend/internal/repository/tls_fingerprint_profile_repo.go @@ -0,0 +1,213 @@ +package repository + +import ( + "context" + + "github.com/Wei-Shaw/sub2api/ent" + "github.com/Wei-Shaw/sub2api/ent/tlsfingerprintprofile" + "github.com/Wei-Shaw/sub2api/internal/model" + "github.com/Wei-Shaw/sub2api/internal/service" +) + +type tlsFingerprintProfileRepository struct { + client *ent.Client +} + +// NewTLSFingerprintProfileRepository 创建 TLS 指纹模板仓库 +func NewTLSFingerprintProfileRepository(client *ent.Client) service.TLSFingerprintProfileRepository { + return &tlsFingerprintProfileRepository{client: client} +} + +// List 获取所有模板 +func (r *tlsFingerprintProfileRepository) List(ctx context.Context) ([]*model.TLSFingerprintProfile, error) { + profiles, err := r.client.TLSFingerprintProfile.Query(). + Order(ent.Asc(tlsfingerprintprofile.FieldName)). + All(ctx) + if err != nil { + return nil, err + } + + result := make([]*model.TLSFingerprintProfile, len(profiles)) + for i, p := range profiles { + result[i] = r.toModel(p) + } + return result, nil +} + +// GetByID 根据 ID 获取模板 +func (r *tlsFingerprintProfileRepository) GetByID(ctx context.Context, id int64) (*model.TLSFingerprintProfile, error) { + p, err := r.client.TLSFingerprintProfile.Get(ctx, id) + if err != nil { + if ent.IsNotFound(err) { + return nil, nil + } + return nil, err + } + return r.toModel(p), nil +} + +// Create 创建模板 +func (r *tlsFingerprintProfileRepository) Create(ctx context.Context, p *model.TLSFingerprintProfile) (*model.TLSFingerprintProfile, error) { + builder := r.client.TLSFingerprintProfile.Create(). + SetName(p.Name). + SetEnableGrease(p.EnableGREASE) + + if p.Description != nil { + builder.SetDescription(*p.Description) + } + if len(p.CipherSuites) > 0 { + builder.SetCipherSuites(p.CipherSuites) + } + if len(p.Curves) > 0 { + builder.SetCurves(p.Curves) + } + if len(p.PointFormats) > 0 { + builder.SetPointFormats(p.PointFormats) + } + if len(p.SignatureAlgorithms) > 0 { + builder.SetSignatureAlgorithms(p.SignatureAlgorithms) + } + if len(p.ALPNProtocols) > 0 { + builder.SetAlpnProtocols(p.ALPNProtocols) + } + if len(p.SupportedVersions) > 0 { + builder.SetSupportedVersions(p.SupportedVersions) + } + if len(p.KeyShareGroups) > 0 { + builder.SetKeyShareGroups(p.KeyShareGroups) + } + if len(p.PSKModes) > 0 { + builder.SetPskModes(p.PSKModes) + } + if len(p.Extensions) > 0 { + builder.SetExtensions(p.Extensions) + } + + created, err := builder.Save(ctx) + if err != nil { + return nil, err + } + return r.toModel(created), nil +} + +// Update 更新模板 +func (r *tlsFingerprintProfileRepository) Update(ctx context.Context, p *model.TLSFingerprintProfile) (*model.TLSFingerprintProfile, error) { + builder := r.client.TLSFingerprintProfile.UpdateOneID(p.ID). + SetName(p.Name). + SetEnableGrease(p.EnableGREASE) + + if p.Description != nil { + builder.SetDescription(*p.Description) + } else { + builder.ClearDescription() + } + + if len(p.CipherSuites) > 0 { + builder.SetCipherSuites(p.CipherSuites) + } else { + builder.ClearCipherSuites() + } + if len(p.Curves) > 0 { + builder.SetCurves(p.Curves) + } else { + builder.ClearCurves() + } + if len(p.PointFormats) > 0 { + builder.SetPointFormats(p.PointFormats) + } else { + builder.ClearPointFormats() + } + if len(p.SignatureAlgorithms) > 0 { + builder.SetSignatureAlgorithms(p.SignatureAlgorithms) + } else { + builder.ClearSignatureAlgorithms() + } + if len(p.ALPNProtocols) > 0 { + builder.SetAlpnProtocols(p.ALPNProtocols) + } else { + builder.ClearAlpnProtocols() + } + if len(p.SupportedVersions) > 0 { + builder.SetSupportedVersions(p.SupportedVersions) + } else { + builder.ClearSupportedVersions() + } + if len(p.KeyShareGroups) > 0 { + builder.SetKeyShareGroups(p.KeyShareGroups) + } else { + builder.ClearKeyShareGroups() + } + if len(p.PSKModes) > 0 { + builder.SetPskModes(p.PSKModes) + } else { + builder.ClearPskModes() + } + if len(p.Extensions) > 0 { + builder.SetExtensions(p.Extensions) + } else { + builder.ClearExtensions() + } + + updated, err := builder.Save(ctx) + if err != nil { + return nil, err + } + return r.toModel(updated), nil +} + +// Delete 删除模板 +func (r *tlsFingerprintProfileRepository) Delete(ctx context.Context, id int64) error { + return r.client.TLSFingerprintProfile.DeleteOneID(id).Exec(ctx) +} + +// toModel 将 Ent 实体转换为服务模型 +func (r *tlsFingerprintProfileRepository) toModel(e *ent.TLSFingerprintProfile) *model.TLSFingerprintProfile { + p := &model.TLSFingerprintProfile{ + ID: e.ID, + Name: e.Name, + Description: e.Description, + EnableGREASE: e.EnableGrease, + CipherSuites: e.CipherSuites, + Curves: e.Curves, + PointFormats: e.PointFormats, + SignatureAlgorithms: e.SignatureAlgorithms, + ALPNProtocols: e.AlpnProtocols, + SupportedVersions: e.SupportedVersions, + KeyShareGroups: e.KeyShareGroups, + PSKModes: e.PskModes, + Extensions: e.Extensions, + CreatedAt: e.CreatedAt, + UpdatedAt: e.UpdatedAt, + } + + // 确保切片不为 nil + if p.CipherSuites == nil { + p.CipherSuites = []uint16{} + } + if p.Curves == nil { + p.Curves = []uint16{} + } + if p.PointFormats == nil { + p.PointFormats = []uint16{} + } + if p.SignatureAlgorithms == nil { + p.SignatureAlgorithms = []uint16{} + } + if p.ALPNProtocols == nil { + p.ALPNProtocols = []string{} + } + if p.SupportedVersions == nil { + p.SupportedVersions = []uint16{} + } + if p.KeyShareGroups == nil { + p.KeyShareGroups = []uint16{} + } + if p.PSKModes == nil { + p.PSKModes = []uint16{} + } + if p.Extensions == nil { + p.Extensions = []uint16{} + } + + return p +} diff --git a/backend/internal/repository/wire.go b/backend/internal/repository/wire.go index 138bf59e..f65f9beb 100644 --- a/backend/internal/repository/wire.go +++ b/backend/internal/repository/wire.go @@ -73,6 +73,7 @@ var ProviderSet = wire.NewSet( NewUserAttributeValueRepository, NewUserGroupRateRepository, NewErrorPassthroughRepository, + NewTLSFingerprintProfileRepository, // Cache implementations NewGatewayCache, @@ -96,6 +97,7 @@ var ProviderSet = wire.NewSet( NewTotpCache, NewRefreshTokenCache, NewErrorPassthroughCache, + NewTLSFingerprintProfileCache, // Encryptors NewAESEncryptor, diff --git a/backend/internal/server/routes/admin.go b/backend/internal/server/routes/admin.go index 6fd239bb..e04dae85 100644 --- a/backend/internal/server/routes/admin.go +++ b/backend/internal/server/routes/admin.go @@ -79,6 +79,9 @@ func RegisterAdminRoutes( // 错误透传规则管理 registerErrorPassthroughRoutes(admin, h) + // TLS 指纹模板管理 + registerTLSFingerprintProfileRoutes(admin, h) + // API Key 管理 registerAdminAPIKeyRoutes(admin, h) @@ -553,3 +556,14 @@ func registerErrorPassthroughRoutes(admin *gin.RouterGroup, h *handler.Handlers) rules.DELETE("/:id", h.Admin.ErrorPassthrough.Delete) } } + +func registerTLSFingerprintProfileRoutes(admin *gin.RouterGroup, h *handler.Handlers) { + profiles := admin.Group("/tls-fingerprint-profiles") + { + profiles.GET("", h.Admin.TLSFingerprintProfile.List) + profiles.GET("/:id", h.Admin.TLSFingerprintProfile.GetByID) + profiles.POST("", h.Admin.TLSFingerprintProfile.Create) + profiles.PUT("/:id", h.Admin.TLSFingerprintProfile.Update) + profiles.DELETE("/:id", h.Admin.TLSFingerprintProfile.Delete) + } +} diff --git a/backend/internal/service/account.go b/backend/internal/service/account.go index d42c6a11..741e33e8 100644 --- a/backend/internal/service/account.go +++ b/backend/internal/service/account.go @@ -1165,6 +1165,31 @@ func (a *Account) IsTLSFingerprintEnabled() bool { return false } +// GetTLSFingerprintProfileID 获取账号绑定的 TLS 指纹模板 ID +// 返回 0 表示未绑定(使用内置默认 profile) +func (a *Account) GetTLSFingerprintProfileID() int64 { + if a.Extra == nil { + return 0 + } + v, ok := a.Extra["tls_fingerprint_profile_id"] + if !ok { + return 0 + } + switch id := v.(type) { + case float64: + return int64(id) + case int64: + return id + case int: + return int64(id) + case json.Number: + if i, err := id.Int64(); err == nil { + return i + } + } + return 0 +} + // GetUserMsgQueueMode 获取用户消息队列模式 // "serialize" = 串行队列, "throttle" = 软性限速, "" = 未设置(使用全局配置) func (a *Account) GetUserMsgQueueMode() string { diff --git a/backend/internal/service/account_test_service.go b/backend/internal/service/account_test_service.go index 12617336..fec98e12 100644 --- a/backend/internal/service/account_test_service.go +++ b/backend/internal/service/account_test_service.go @@ -23,6 +23,7 @@ import ( "github.com/Wei-Shaw/sub2api/internal/pkg/claude" "github.com/Wei-Shaw/sub2api/internal/pkg/geminicli" "github.com/Wei-Shaw/sub2api/internal/pkg/openai" + "github.com/Wei-Shaw/sub2api/internal/pkg/tlsfingerprint" "github.com/Wei-Shaw/sub2api/internal/util/soraerror" "github.com/Wei-Shaw/sub2api/internal/util/urlvalidator" "github.com/gin-gonic/gin" @@ -69,6 +70,7 @@ type AccountTestService struct { antigravityGatewayService *AntigravityGatewayService httpUpstream HTTPUpstream cfg *config.Config + tlsFPProfileService *TLSFingerprintProfileService soraTestGuardMu sync.Mutex soraTestLastRun map[int64]time.Time soraTestCooldown time.Duration @@ -83,6 +85,7 @@ func NewAccountTestService( antigravityGatewayService *AntigravityGatewayService, httpUpstream HTTPUpstream, cfg *config.Config, + tlsFPProfileService *TLSFingerprintProfileService, ) *AccountTestService { return &AccountTestService{ accountRepo: accountRepo, @@ -90,6 +93,7 @@ func NewAccountTestService( antigravityGatewayService: antigravityGatewayService, httpUpstream: httpUpstream, cfg: cfg, + tlsFPProfileService: tlsFPProfileService, soraTestLastRun: make(map[int64]time.Time), soraTestCooldown: defaultSoraTestCooldown, } @@ -300,7 +304,7 @@ func (s *AccountTestService) testClaudeAccountConnection(c *gin.Context, account proxyURL = account.Proxy.URL() } - resp, err := s.httpUpstream.DoWithTLS(req, proxyURL, account.ID, account.Concurrency, account.IsTLSFingerprintEnabled()) + resp, err := s.httpUpstream.DoWithTLS(req, proxyURL, account.ID, account.Concurrency, s.tlsFPProfileService.ResolveTLSProfile(account)) if err != nil { return s.sendErrorAndEnd(c, fmt.Sprintf("Request failed: %s", err.Error())) } @@ -390,7 +394,7 @@ func (s *AccountTestService) testBedrockAccountConnection(c *gin.Context, ctx co proxyURL = account.Proxy.URL() } - resp, err := s.httpUpstream.DoWithTLS(req, proxyURL, account.ID, account.Concurrency, false) + resp, err := s.httpUpstream.DoWithTLS(req, proxyURL, account.ID, account.Concurrency, nil) if err != nil { return s.sendErrorAndEnd(c, fmt.Sprintf("Request failed: %s", err.Error())) } @@ -520,7 +524,7 @@ func (s *AccountTestService) testOpenAIAccountConnection(c *gin.Context, account proxyURL = account.Proxy.URL() } - resp, err := s.httpUpstream.DoWithTLS(req, proxyURL, account.ID, account.Concurrency, account.IsTLSFingerprintEnabled()) + resp, err := s.httpUpstream.DoWithTLS(req, proxyURL, account.ID, account.Concurrency, s.tlsFPProfileService.ResolveTLSProfile(account)) if err != nil { return s.sendErrorAndEnd(c, fmt.Sprintf("Request failed: %s", err.Error())) } @@ -610,7 +614,7 @@ func (s *AccountTestService) testGeminiAccountConnection(c *gin.Context, account proxyURL = account.Proxy.URL() } - resp, err := s.httpUpstream.DoWithTLS(req, proxyURL, account.ID, account.Concurrency, account.IsTLSFingerprintEnabled()) + resp, err := s.httpUpstream.DoWithTLS(req, proxyURL, account.ID, account.Concurrency, s.tlsFPProfileService.ResolveTLSProfile(account)) if err != nil { return s.sendErrorAndEnd(c, fmt.Sprintf("Request failed: %s", err.Error())) } @@ -881,9 +885,9 @@ func (s *AccountTestService) testSoraAccountConnection(c *gin.Context, account * if account.ProxyID != nil && account.Proxy != nil { proxyURL = account.Proxy.URL() } - enableSoraTLSFingerprint := s.shouldEnableSoraTLSFingerprint() + soraTLSProfile := s.resolveSoraTLSProfile() - resp, err := s.httpUpstream.DoWithTLS(req, proxyURL, account.ID, account.Concurrency, enableSoraTLSFingerprint) + resp, err := s.httpUpstream.DoWithTLS(req, proxyURL, account.ID, account.Concurrency, soraTLSProfile) if err != nil { recorder.addStep("me", "failed", 0, "network_error", err.Error()) s.emitSoraProbeSummary(c, recorder) @@ -948,7 +952,7 @@ func (s *AccountTestService) testSoraAccountConnection(c *gin.Context, account * subReq.Header.Set("Origin", "https://sora.chatgpt.com") subReq.Header.Set("Referer", "https://sora.chatgpt.com/") - subResp, subErr := s.httpUpstream.DoWithTLS(subReq, proxyURL, account.ID, account.Concurrency, enableSoraTLSFingerprint) + subResp, subErr := s.httpUpstream.DoWithTLS(subReq, proxyURL, account.ID, account.Concurrency, soraTLSProfile) if subErr != nil { recorder.addStep("subscription", "failed", 0, "network_error", subErr.Error()) s.sendEvent(c, TestEvent{Type: "content", Text: fmt.Sprintf("Subscription check skipped: %s", subErr.Error())}) @@ -977,7 +981,7 @@ func (s *AccountTestService) testSoraAccountConnection(c *gin.Context, account * } // 追加 Sora2 能力探测(对齐 sora2api 的测试思路):邀请码 + 剩余额度。 - s.testSora2Capabilities(c, ctx, account, authToken, proxyURL, enableSoraTLSFingerprint, recorder) + s.testSora2Capabilities(c, ctx, account, authToken, proxyURL, soraTLSProfile, recorder) s.emitSoraProbeSummary(c, recorder) s.sendEvent(c, TestEvent{Type: "test_complete", Success: true}) @@ -990,7 +994,7 @@ func (s *AccountTestService) testSora2Capabilities( account *Account, authToken string, proxyURL string, - enableTLSFingerprint bool, + tlsProfile *tlsfingerprint.Profile, recorder *soraProbeRecorder, ) { inviteStatus, inviteHeader, inviteBody, err := s.fetchSoraTestEndpoint( @@ -999,7 +1003,7 @@ func (s *AccountTestService) testSora2Capabilities( authToken, soraInviteMineURL, proxyURL, - enableTLSFingerprint, + tlsProfile, ) if err != nil { if recorder != nil { @@ -1016,7 +1020,7 @@ func (s *AccountTestService) testSora2Capabilities( authToken, soraBootstrapURL, proxyURL, - enableTLSFingerprint, + tlsProfile, ) if bootstrapErr == nil && bootstrapStatus == http.StatusOK { if recorder != nil { @@ -1029,7 +1033,7 @@ func (s *AccountTestService) testSora2Capabilities( authToken, soraInviteMineURL, proxyURL, - enableTLSFingerprint, + tlsProfile, ) if err != nil { if recorder != nil { @@ -1081,7 +1085,7 @@ func (s *AccountTestService) testSora2Capabilities( authToken, soraRemainingURL, proxyURL, - enableTLSFingerprint, + tlsProfile, ) if remainingErr != nil { if recorder != nil { @@ -1122,7 +1126,7 @@ func (s *AccountTestService) fetchSoraTestEndpoint( authToken string, url string, proxyURL string, - enableTLSFingerprint bool, + tlsProfile *tlsfingerprint.Profile, ) (int, http.Header, []byte, error) { req, err := http.NewRequestWithContext(ctx, "GET", url, nil) if err != nil { @@ -1135,7 +1139,7 @@ func (s *AccountTestService) fetchSoraTestEndpoint( req.Header.Set("Origin", "https://sora.chatgpt.com") req.Header.Set("Referer", "https://sora.chatgpt.com/") - resp, err := s.httpUpstream.DoWithTLS(req, proxyURL, account.ID, account.Concurrency, enableTLSFingerprint) + resp, err := s.httpUpstream.DoWithTLS(req, proxyURL, account.ID, account.Concurrency, tlsProfile) if err != nil { return 0, nil, nil, err } @@ -1224,11 +1228,12 @@ func parseSoraRemainingSummary(body []byte) string { return strings.Join(parts, " | ") } -func (s *AccountTestService) shouldEnableSoraTLSFingerprint() bool { - if s == nil || s.cfg == nil { - return true +func (s *AccountTestService) resolveSoraTLSProfile() *tlsfingerprint.Profile { + if s == nil || s.cfg == nil || !s.cfg.Sora.Client.DisableTLSFingerprint { + // Sora TLS fingerprint enabled — use built-in default profile + return &tlsfingerprint.Profile{Name: "Built-in Default (Sora)"} } - return !s.cfg.Sora.Client.DisableTLSFingerprint + return nil // disabled } func isCloudflareChallengeResponse(statusCode int, headers http.Header, body []byte) bool { diff --git a/backend/internal/service/account_test_service_sora_test.go b/backend/internal/service/account_test_service_sora_test.go index 3dfac786..52f832a9 100644 --- a/backend/internal/service/account_test_service_sora_test.go +++ b/backend/internal/service/account_test_service_sora_test.go @@ -10,6 +10,7 @@ import ( "time" "github.com/Wei-Shaw/sub2api/internal/config" + "github.com/Wei-Shaw/sub2api/internal/pkg/tlsfingerprint" "github.com/gin-gonic/gin" "github.com/stretchr/testify/require" ) @@ -24,9 +25,9 @@ func (u *queuedHTTPUpstream) Do(_ *http.Request, _ string, _ int64, _ int) (*htt return nil, fmt.Errorf("unexpected Do call") } -func (u *queuedHTTPUpstream) DoWithTLS(req *http.Request, _ string, _ int64, _ int, enableTLSFingerprint bool) (*http.Response, error) { +func (u *queuedHTTPUpstream) DoWithTLS(req *http.Request, _ string, _ int64, _ int, profile *tlsfingerprint.Profile) (*http.Response, error) { u.requests = append(u.requests, req) - u.tlsFlags = append(u.tlsFlags, enableTLSFingerprint) + u.tlsFlags = append(u.tlsFlags, profile != nil) if len(u.responses) == 0 { return nil, fmt.Errorf("no mocked response") } diff --git a/backend/internal/service/account_usage_service.go b/backend/internal/service/account_usage_service.go index 2761d9c8..0e5741d8 100644 --- a/backend/internal/service/account_usage_service.go +++ b/backend/internal/service/account_usage_service.go @@ -17,6 +17,7 @@ import ( openaipkg "github.com/Wei-Shaw/sub2api/internal/pkg/openai" "github.com/Wei-Shaw/sub2api/internal/pkg/pagination" "github.com/Wei-Shaw/sub2api/internal/pkg/timezone" + "github.com/Wei-Shaw/sub2api/internal/pkg/tlsfingerprint" "github.com/Wei-Shaw/sub2api/internal/pkg/usagestats" "golang.org/x/sync/errgroup" "golang.org/x/sync/singleflight" @@ -241,11 +242,11 @@ type ClaudeUsageResponse struct { // ClaudeUsageFetchOptions 包含获取 Claude 用量数据所需的所有选项 type ClaudeUsageFetchOptions struct { - AccessToken string // OAuth access token - ProxyURL string // 代理 URL(可选) - AccountID int64 // 账号 ID(用于 TLS 指纹选择) - EnableTLSFingerprint bool // 是否启用 TLS 指纹伪装 - Fingerprint *Fingerprint // 缓存的指纹信息(User-Agent 等) + AccessToken string // OAuth access token + ProxyURL string // 代理 URL(可选) + AccountID int64 // 账号 ID(用于连接池隔离) + TLSProfile *tlsfingerprint.Profile // TLS 指纹 Profile(nil 表示不启用) + Fingerprint *Fingerprint // 缓存的指纹信息(User-Agent 等) } // ClaudeUsageFetcher fetches usage data from Anthropic OAuth API @@ -264,6 +265,7 @@ type AccountUsageService struct { antigravityQuotaFetcher *AntigravityQuotaFetcher cache *UsageCache identityCache IdentityCache + tlsFPProfileService *TLSFingerprintProfileService } // NewAccountUsageService 创建AccountUsageService实例 @@ -275,6 +277,7 @@ func NewAccountUsageService( antigravityQuotaFetcher *AntigravityQuotaFetcher, cache *UsageCache, identityCache IdentityCache, + tlsFPProfileService *TLSFingerprintProfileService, ) *AccountUsageService { return &AccountUsageService{ accountRepo: accountRepo, @@ -284,6 +287,7 @@ func NewAccountUsageService( antigravityQuotaFetcher: antigravityQuotaFetcher, cache: cache, identityCache: identityCache, + tlsFPProfileService: tlsFPProfileService, } } @@ -1155,10 +1159,10 @@ func (s *AccountUsageService) fetchOAuthUsageRaw(ctx context.Context, account *A // 构建完整的选项 opts := &ClaudeUsageFetchOptions{ - AccessToken: accessToken, - ProxyURL: proxyURL, - AccountID: account.ID, - EnableTLSFingerprint: account.IsTLSFingerprintEnabled(), + AccessToken: accessToken, + ProxyURL: proxyURL, + AccountID: account.ID, + TLSProfile: s.tlsFPProfileService.ResolveTLSProfile(account), } // 尝试获取缓存的 Fingerprint(包含 User-Agent 等信息) diff --git a/backend/internal/service/antigravity_gateway_service_test.go b/backend/internal/service/antigravity_gateway_service_test.go index f5f9434c..1eb1451e 100644 --- a/backend/internal/service/antigravity_gateway_service_test.go +++ b/backend/internal/service/antigravity_gateway_service_test.go @@ -15,6 +15,7 @@ import ( "github.com/Wei-Shaw/sub2api/internal/config" "github.com/Wei-Shaw/sub2api/internal/pkg/antigravity" + "github.com/Wei-Shaw/sub2api/internal/pkg/tlsfingerprint" "github.com/gin-gonic/gin" "github.com/stretchr/testify/require" ) @@ -130,7 +131,7 @@ func (s *httpUpstreamStub) Do(_ *http.Request, _ string, _ int64, _ int) (*http. return s.resp, s.err } -func (s *httpUpstreamStub) DoWithTLS(_ *http.Request, _ string, _ int64, _ int, _ bool) (*http.Response, error) { +func (s *httpUpstreamStub) DoWithTLS(_ *http.Request, _ string, _ int64, _ int, _ *tlsfingerprint.Profile) (*http.Response, error) { return s.resp, s.err } @@ -171,7 +172,7 @@ func (s *queuedHTTPUpstreamStub) Do(req *http.Request, _ string, _ int64, _ int) return resp, err } -func (s *queuedHTTPUpstreamStub) DoWithTLS(req *http.Request, proxyURL string, accountID int64, concurrency int, _ bool) (*http.Response, error) { +func (s *queuedHTTPUpstreamStub) DoWithTLS(req *http.Request, proxyURL string, accountID int64, concurrency int, _ *tlsfingerprint.Profile) (*http.Response, error) { return s.Do(req, proxyURL, accountID, concurrency) } diff --git a/backend/internal/service/antigravity_rate_limit_test.go b/backend/internal/service/antigravity_rate_limit_test.go index df1ce9b9..35e130dc 100644 --- a/backend/internal/service/antigravity_rate_limit_test.go +++ b/backend/internal/service/antigravity_rate_limit_test.go @@ -12,6 +12,7 @@ import ( "time" "github.com/Wei-Shaw/sub2api/internal/pkg/antigravity" + "github.com/Wei-Shaw/sub2api/internal/pkg/tlsfingerprint" "github.com/stretchr/testify/require" ) @@ -40,7 +41,7 @@ func (r *recordingOKUpstream) Do(req *http.Request, proxyURL string, accountID i }, nil } -func (r *recordingOKUpstream) DoWithTLS(req *http.Request, proxyURL string, accountID int64, accountConcurrency int, enableTLSFingerprint bool) (*http.Response, error) { +func (r *recordingOKUpstream) DoWithTLS(req *http.Request, proxyURL string, accountID int64, accountConcurrency int, profile *tlsfingerprint.Profile) (*http.Response, error) { return r.Do(req, proxyURL, accountID, accountConcurrency) } @@ -61,7 +62,7 @@ func (s *stubAntigravityUpstream) Do(req *http.Request, proxyURL string, account }, nil } -func (s *stubAntigravityUpstream) DoWithTLS(req *http.Request, proxyURL string, accountID int64, accountConcurrency int, enableTLSFingerprint bool) (*http.Response, error) { +func (s *stubAntigravityUpstream) DoWithTLS(req *http.Request, proxyURL string, accountID int64, accountConcurrency int, profile *tlsfingerprint.Profile) (*http.Response, error) { return s.Do(req, proxyURL, accountID, accountConcurrency) } diff --git a/backend/internal/service/antigravity_smart_retry_test.go b/backend/internal/service/antigravity_smart_retry_test.go index 218a1288..ecaffcbc 100644 --- a/backend/internal/service/antigravity_smart_retry_test.go +++ b/backend/internal/service/antigravity_smart_retry_test.go @@ -10,6 +10,7 @@ import ( "strings" "testing" + "github.com/Wei-Shaw/sub2api/internal/pkg/tlsfingerprint" "github.com/stretchr/testify/require" ) @@ -93,7 +94,7 @@ func (m *mockSmartRetryUpstream) Do(req *http.Request, proxyURL string, accountI }, respErr } -func (m *mockSmartRetryUpstream) DoWithTLS(req *http.Request, proxyURL string, accountID int64, accountConcurrency int, enableTLSFingerprint bool) (*http.Response, error) { +func (m *mockSmartRetryUpstream) DoWithTLS(req *http.Request, proxyURL string, accountID int64, accountConcurrency int, profile *tlsfingerprint.Profile) (*http.Response, error) { return m.Do(req, proxyURL, accountID, accountConcurrency) } diff --git a/backend/internal/service/error_policy_integration_test.go b/backend/internal/service/error_policy_integration_test.go index a8b42a2c..aa3e6ec4 100644 --- a/backend/internal/service/error_policy_integration_test.go +++ b/backend/internal/service/error_policy_integration_test.go @@ -12,6 +12,7 @@ import ( "github.com/Wei-Shaw/sub2api/internal/config" "github.com/Wei-Shaw/sub2api/internal/pkg/antigravity" + "github.com/Wei-Shaw/sub2api/internal/pkg/tlsfingerprint" "github.com/stretchr/testify/require" ) @@ -35,7 +36,7 @@ func (u *epFixedUpstream) Do(req *http.Request, proxyURL string, accountID int64 }, nil } -func (u *epFixedUpstream) DoWithTLS(req *http.Request, proxyURL string, accountID int64, accountConcurrency int, enableTLSFingerprint bool) (*http.Response, error) { +func (u *epFixedUpstream) DoWithTLS(req *http.Request, proxyURL string, accountID int64, accountConcurrency int, profile *tlsfingerprint.Profile) (*http.Response, error) { return u.Do(req, proxyURL, accountID, accountConcurrency) } diff --git a/backend/internal/service/gateway_anthropic_apikey_passthrough_test.go b/backend/internal/service/gateway_anthropic_apikey_passthrough_test.go index f4e1b533..6e19db32 100644 --- a/backend/internal/service/gateway_anthropic_apikey_passthrough_test.go +++ b/backend/internal/service/gateway_anthropic_apikey_passthrough_test.go @@ -15,6 +15,7 @@ import ( "github.com/Wei-Shaw/sub2api/internal/config" "github.com/Wei-Shaw/sub2api/internal/pkg/claude" + "github.com/Wei-Shaw/sub2api/internal/pkg/tlsfingerprint" "github.com/gin-gonic/gin" "github.com/stretchr/testify/require" "github.com/tidwall/gjson" @@ -60,7 +61,7 @@ func (u *anthropicHTTPUpstreamRecorder) Do(req *http.Request, proxyURL string, a return u.resp, nil } -func (u *anthropicHTTPUpstreamRecorder) DoWithTLS(req *http.Request, proxyURL string, accountID int64, accountConcurrency int, enableTLSFingerprint bool) (*http.Response, error) { +func (u *anthropicHTTPUpstreamRecorder) DoWithTLS(req *http.Request, proxyURL string, accountID int64, accountConcurrency int, profile *tlsfingerprint.Profile) (*http.Response, error) { return u.Do(req, proxyURL, accountID, accountConcurrency) } diff --git a/backend/internal/service/gateway_forward_as_chat_completions.go b/backend/internal/service/gateway_forward_as_chat_completions.go index d3c611e2..37b38f76 100644 --- a/backend/internal/service/gateway_forward_as_chat_completions.go +++ b/backend/internal/service/gateway_forward_as_chat_completions.go @@ -120,7 +120,7 @@ func (s *GatewayService) ForwardAsChatCompletions( } // 11. Send request - resp, err := s.httpUpstream.DoWithTLS(upstreamReq, proxyURL, account.ID, account.Concurrency, account.IsTLSFingerprintEnabled()) + resp, err := s.httpUpstream.DoWithTLS(upstreamReq, proxyURL, account.ID, account.Concurrency, s.tlsFPProfileService.ResolveTLSProfile(account)) if err != nil { if resp != nil && resp.Body != nil { _ = resp.Body.Close() diff --git a/backend/internal/service/gateway_forward_as_responses.go b/backend/internal/service/gateway_forward_as_responses.go index 5dca57f9..2c917112 100644 --- a/backend/internal/service/gateway_forward_as_responses.go +++ b/backend/internal/service/gateway_forward_as_responses.go @@ -117,7 +117,7 @@ func (s *GatewayService) ForwardAsResponses( } // 11. Send request - resp, err := s.httpUpstream.DoWithTLS(upstreamReq, proxyURL, account.ID, account.Concurrency, account.IsTLSFingerprintEnabled()) + resp, err := s.httpUpstream.DoWithTLS(upstreamReq, proxyURL, account.ID, account.Concurrency, s.tlsFPProfileService.ResolveTLSProfile(account)) if err != nil { if resp != nil && resp.Body != nil { _ = resp.Body.Close() diff --git a/backend/internal/service/gateway_record_usage_test.go b/backend/internal/service/gateway_record_usage_test.go index 1b2f5f51..48488dc8 100644 --- a/backend/internal/service/gateway_record_usage_test.go +++ b/backend/internal/service/gateway_record_usage_test.go @@ -40,6 +40,7 @@ func newGatewayRecordUsageServiceForTest(usageRepo UsageLogRepository, userRepo nil, nil, nil, + nil, ) } diff --git a/backend/internal/service/gateway_service.go b/backend/internal/service/gateway_service.go index 5de6dcae..cb90343b 100644 --- a/backend/internal/service/gateway_service.go +++ b/backend/internal/service/gateway_service.go @@ -566,6 +566,7 @@ type GatewayService struct { debugModelRouting atomic.Bool debugClaudeMimic atomic.Bool debugGatewayBodyFile atomic.Pointer[os.File] // non-nil when SUB2API_DEBUG_GATEWAY_BODY is set + tlsFPProfileService *TLSFingerprintProfileService } // NewGatewayService creates a new GatewayService @@ -592,6 +593,7 @@ func NewGatewayService( rpmCache RPMCache, digestStore *DigestSessionStore, settingService *SettingService, + tlsFPProfileService *TLSFingerprintProfileService, ) *GatewayService { userGroupRateTTL := resolveUserGroupRateCacheTTL(cfg) modelsListTTL := resolveModelsListCacheTTL(cfg) @@ -623,6 +625,7 @@ func NewGatewayService( modelsListCache: gocache.New(modelsListTTL, time.Minute), modelsListCacheTTL: modelsListTTL, responseHeaderFilter: compileResponseHeaderFilter(cfg), + tlsFPProfileService: tlsFPProfileService, } svc.userGroupRateResolver = newUserGroupRateResolver( userGroupRateRepo, @@ -4133,9 +4136,12 @@ func (s *GatewayService) Forward(ctx context.Context, c *gin.Context, account *A proxyURL = account.Proxy.URL() } + // 解析 TLS 指纹 profile(同一请求生命周期内不变,避免重试循环中重复解析) + tlsProfile := s.tlsFPProfileService.ResolveTLSProfile(account) + // 调试日志:记录即将转发的账号信息 logger.LegacyPrintf("service.gateway", "[Forward] Using account: ID=%d Name=%s Platform=%s Type=%s TLSFingerprint=%v Proxy=%s", - account.ID, account.Name, account.Platform, account.Type, account.IsTLSFingerprintEnabled(), proxyURL) + account.ID, account.Name, account.Platform, account.Type, tlsProfile, proxyURL) // Pre-filter: strip empty text blocks (including nested in tool_result) to prevent upstream 400. body = StripEmptyTextBlocks(body) @@ -4155,7 +4161,7 @@ func (s *GatewayService) Forward(ctx context.Context, c *gin.Context, account *A } // 发送请求 - resp, err = s.httpUpstream.DoWithTLS(upstreamReq, proxyURL, account.ID, account.Concurrency, account.IsTLSFingerprintEnabled()) + resp, err = s.httpUpstream.DoWithTLS(upstreamReq, proxyURL, account.ID, account.Concurrency, tlsProfile) if err != nil { if resp != nil && resp.Body != nil { _ = resp.Body.Close() @@ -4233,7 +4239,7 @@ func (s *GatewayService) Forward(ctx context.Context, c *gin.Context, account *A retryReq, buildErr := s.buildUpstreamRequest(retryCtx, c, account, filteredBody, token, tokenType, reqModel, reqStream, shouldMimicClaudeCode) releaseRetryCtx() if buildErr == nil { - retryResp, retryErr := s.httpUpstream.DoWithTLS(retryReq, proxyURL, account.ID, account.Concurrency, account.IsTLSFingerprintEnabled()) + retryResp, retryErr := s.httpUpstream.DoWithTLS(retryReq, proxyURL, account.ID, account.Concurrency, tlsProfile) if retryErr == nil { if retryResp.StatusCode < 400 { logger.LegacyPrintf("service.gateway", "Account %d: thinking block retry succeeded (blocks downgraded)", account.ID) @@ -4268,7 +4274,7 @@ func (s *GatewayService) Forward(ctx context.Context, c *gin.Context, account *A retryReq2, buildErr2 := s.buildUpstreamRequest(retryCtx2, c, account, filteredBody2, token, tokenType, reqModel, reqStream, shouldMimicClaudeCode) releaseRetryCtx2() if buildErr2 == nil { - retryResp2, retryErr2 := s.httpUpstream.DoWithTLS(retryReq2, proxyURL, account.ID, account.Concurrency, account.IsTLSFingerprintEnabled()) + retryResp2, retryErr2 := s.httpUpstream.DoWithTLS(retryReq2, proxyURL, account.ID, account.Concurrency, tlsProfile) if retryErr2 == nil { resp = retryResp2 break @@ -4339,7 +4345,7 @@ func (s *GatewayService) Forward(ctx context.Context, c *gin.Context, account *A budgetRetryReq, buildErr := s.buildUpstreamRequest(budgetRetryCtx, c, account, rectifiedBody, token, tokenType, reqModel, reqStream, shouldMimicClaudeCode) releaseBudgetRetryCtx() if buildErr == nil { - budgetRetryResp, retryErr := s.httpUpstream.DoWithTLS(budgetRetryReq, proxyURL, account.ID, account.Concurrency, account.IsTLSFingerprintEnabled()) + budgetRetryResp, retryErr := s.httpUpstream.DoWithTLS(budgetRetryReq, proxyURL, account.ID, account.Concurrency, tlsProfile) if retryErr == nil { resp = budgetRetryResp break @@ -4645,7 +4651,7 @@ func (s *GatewayService) forwardAnthropicAPIKeyPassthroughWithInput( return nil, err } - resp, err = s.httpUpstream.DoWithTLS(upstreamReq, proxyURL, account.ID, account.Concurrency, account.IsTLSFingerprintEnabled()) + resp, err = s.httpUpstream.DoWithTLS(upstreamReq, proxyURL, account.ID, account.Concurrency, s.tlsFPProfileService.ResolveTLSProfile(account)) if err != nil { if resp != nil && resp.Body != nil { _ = resp.Body.Close() @@ -5364,7 +5370,7 @@ func (s *GatewayService) executeBedrockUpstream( return nil, err } - resp, err = s.httpUpstream.DoWithTLS(upstreamReq, proxyURL, account.ID, account.Concurrency, false) + resp, err = s.httpUpstream.DoWithTLS(upstreamReq, proxyURL, account.ID, account.Concurrency, nil) if err != nil { if resp != nil && resp.Body != nil { _ = resp.Body.Close() @@ -8044,7 +8050,7 @@ func (s *GatewayService) ForwardCountTokens(ctx context.Context, c *gin.Context, } // 发送请求 - resp, err := s.httpUpstream.DoWithTLS(upstreamReq, proxyURL, account.ID, account.Concurrency, account.IsTLSFingerprintEnabled()) + resp, err := s.httpUpstream.DoWithTLS(upstreamReq, proxyURL, account.ID, account.Concurrency, s.tlsFPProfileService.ResolveTLSProfile(account)) if err != nil { setOpsUpstreamError(c, 0, sanitizeUpstreamErrorMessage(err.Error()), "") s.countTokensError(c, http.StatusBadGateway, "upstream_error", "Request failed") @@ -8072,7 +8078,7 @@ func (s *GatewayService) ForwardCountTokens(ctx context.Context, c *gin.Context, filteredBody := FilterThinkingBlocksForRetry(body) retryReq, buildErr := s.buildCountTokensRequest(ctx, c, account, filteredBody, token, tokenType, reqModel, shouldMimicClaudeCode) if buildErr == nil { - retryResp, retryErr := s.httpUpstream.DoWithTLS(retryReq, proxyURL, account.ID, account.Concurrency, account.IsTLSFingerprintEnabled()) + retryResp, retryErr := s.httpUpstream.DoWithTLS(retryReq, proxyURL, account.ID, account.Concurrency, s.tlsFPProfileService.ResolveTLSProfile(account)) if retryErr == nil { resp = retryResp respBody, err = readUpstreamResponseBodyLimited(resp.Body, maxReadBytes) @@ -8161,7 +8167,7 @@ func (s *GatewayService) forwardCountTokensAnthropicAPIKeyPassthrough(ctx contex proxyURL = account.Proxy.URL() } - resp, err := s.httpUpstream.DoWithTLS(upstreamReq, proxyURL, account.ID, account.Concurrency, account.IsTLSFingerprintEnabled()) + resp, err := s.httpUpstream.DoWithTLS(upstreamReq, proxyURL, account.ID, account.Concurrency, s.tlsFPProfileService.ResolveTLSProfile(account)) if err != nil { setOpsUpstreamError(c, 0, sanitizeUpstreamErrorMessage(err.Error()), "") appendOpsUpstreamError(c, OpsUpstreamErrorEvent{ diff --git a/backend/internal/service/gemini_messages_compat_service_test.go b/backend/internal/service/gemini_messages_compat_service_test.go index 17f7e74e..f659f0e6 100644 --- a/backend/internal/service/gemini_messages_compat_service_test.go +++ b/backend/internal/service/gemini_messages_compat_service_test.go @@ -12,6 +12,7 @@ import ( "time" "github.com/Wei-Shaw/sub2api/internal/config" + "github.com/Wei-Shaw/sub2api/internal/pkg/tlsfingerprint" "github.com/gin-gonic/gin" "github.com/stretchr/testify/require" ) @@ -36,7 +37,7 @@ func (s *geminiCompatHTTPUpstreamStub) Do(req *http.Request, proxyURL string, ac return &resp, nil } -func (s *geminiCompatHTTPUpstreamStub) DoWithTLS(req *http.Request, proxyURL string, accountID int64, accountConcurrency int, enableTLSFingerprint bool) (*http.Response, error) { +func (s *geminiCompatHTTPUpstreamStub) DoWithTLS(req *http.Request, proxyURL string, accountID int64, accountConcurrency int, profile *tlsfingerprint.Profile) (*http.Response, error) { return s.Do(req, proxyURL, accountID, accountConcurrency) } diff --git a/backend/internal/service/http_upstream_port.go b/backend/internal/service/http_upstream_port.go index 0e4cfbec..e8e76957 100644 --- a/backend/internal/service/http_upstream_port.go +++ b/backend/internal/service/http_upstream_port.go @@ -1,55 +1,24 @@ package service -import "net/http" +import ( + "net/http" + + "github.com/Wei-Shaw/sub2api/internal/pkg/tlsfingerprint" +) // HTTPUpstream 上游 HTTP 请求接口 // 用于向上游 API(Claude、OpenAI、Gemini 等)发送请求 -// 这是一个通用接口,可用于任何基于 HTTP 的上游服务 -// -// 设计说明: -// - 支持可选代理配置 -// - 支持账户级连接池隔离 -// - 实现类负责连接池管理和复用 -// - 支持可选的 TLS 指纹伪装 type HTTPUpstream interface { - // Do 执行 HTTP 请求 - // - // 参数: - // - req: HTTP 请求对象,由调用方构建 - // - proxyURL: 代理服务器地址,空字符串表示直连 - // - accountID: 账户 ID,用于连接池隔离(隔离策略为 account 或 account_proxy 时生效) - // - accountConcurrency: 账户并发限制,用于动态调整连接池大小 - // - // 返回: - // - *http.Response: HTTP 响应,调用方必须关闭 Body - // - error: 请求错误(网络错误、超时等) - // - // 注意: - // - 调用方必须关闭 resp.Body,否则会导致连接泄漏 - // - 响应体可能已被包装以跟踪请求生命周期 + // Do 执行 HTTP 请求(不启用 TLS 指纹) Do(req *http.Request, proxyURL string, accountID int64, accountConcurrency int) (*http.Response, error) // DoWithTLS 执行带 TLS 指纹伪装的 HTTP 请求 // - // 参数: - // - req: HTTP 请求对象,由调用方构建 - // - proxyURL: 代理服务器地址,空字符串表示直连 - // - accountID: 账户 ID,用于连接池隔离和 TLS 指纹模板选择 - // - accountConcurrency: 账户并发限制,用于动态调整连接池大小 - // - enableTLSFingerprint: 是否启用 TLS 指纹伪装 + // profile 参数: + // - nil: 不启用 TLS 指纹,行为与 Do 方法相同 + // - non-nil: 使用指定的 Profile 进行 TLS 指纹伪装 // - // 返回: - // - *http.Response: HTTP 响应,调用方必须关闭 Body - // - error: 请求错误(网络错误、超时等) - // - // TLS 指纹说明: - // - 当 enableTLSFingerprint=true 时,使用 utls 库模拟 Claude CLI 的 TLS 指纹 - // - TLS 指纹模板根据 accountID % len(profiles) 自动选择 - // - 支持直连、HTTP/HTTPS 代理、SOCKS5 代理三种场景 - // - 如果 enableTLSFingerprint=false,行为与 Do 方法相同 - // - // 注意: - // - 调用方必须关闭 resp.Body,否则会导致连接泄漏 - // - TLS 指纹客户端与普通客户端使用不同的缓存键,互不影响 - DoWithTLS(req *http.Request, proxyURL string, accountID int64, accountConcurrency int, enableTLSFingerprint bool) (*http.Response, error) + // Profile 由调用方通过 TLSFingerprintProfileService 解析后传入, + // 支持按账号绑定的数据库 profile 或内置默认 profile。 + DoWithTLS(req *http.Request, proxyURL string, accountID int64, accountConcurrency int, profile *tlsfingerprint.Profile) (*http.Response, error) } diff --git a/backend/internal/service/openai_oauth_passthrough_test.go b/backend/internal/service/openai_oauth_passthrough_test.go index fe639576..97fa218d 100644 --- a/backend/internal/service/openai_oauth_passthrough_test.go +++ b/backend/internal/service/openai_oauth_passthrough_test.go @@ -14,6 +14,7 @@ import ( "github.com/Wei-Shaw/sub2api/internal/config" "github.com/Wei-Shaw/sub2api/internal/pkg/logger" + "github.com/Wei-Shaw/sub2api/internal/pkg/tlsfingerprint" "github.com/gin-gonic/gin" "github.com/stretchr/testify/require" "github.com/tidwall/gjson" @@ -43,7 +44,7 @@ func (u *httpUpstreamRecorder) Do(req *http.Request, proxyURL string, accountID return u.resp, nil } -func (u *httpUpstreamRecorder) DoWithTLS(req *http.Request, proxyURL string, accountID int64, accountConcurrency int, enableTLSFingerprint bool) (*http.Response, error) { +func (u *httpUpstreamRecorder) DoWithTLS(req *http.Request, proxyURL string, accountID int64, accountConcurrency int, profile *tlsfingerprint.Profile) (*http.Response, error) { return u.Do(req, proxyURL, accountID, accountConcurrency) } diff --git a/backend/internal/service/openai_ws_protocol_forward_test.go b/backend/internal/service/openai_ws_protocol_forward_test.go index 76c66f2f..8c5c9368 100644 --- a/backend/internal/service/openai_ws_protocol_forward_test.go +++ b/backend/internal/service/openai_ws_protocol_forward_test.go @@ -14,6 +14,7 @@ import ( "time" "github.com/Wei-Shaw/sub2api/internal/config" + "github.com/Wei-Shaw/sub2api/internal/pkg/tlsfingerprint" "github.com/gin-gonic/gin" "github.com/gorilla/websocket" "github.com/stretchr/testify/require" @@ -57,7 +58,7 @@ func (u *httpUpstreamSequenceRecorder) Do(req *http.Request, proxyURL string, ac return u.responses[len(u.responses)-1], nil } -func (u *httpUpstreamSequenceRecorder) DoWithTLS(req *http.Request, proxyURL string, accountID int64, accountConcurrency int, enableTLSFingerprint bool) (*http.Response, error) { +func (u *httpUpstreamSequenceRecorder) DoWithTLS(req *http.Request, proxyURL string, accountID int64, accountConcurrency int, profile *tlsfingerprint.Profile) (*http.Response, error) { return u.Do(req, proxyURL, accountID, accountConcurrency) } diff --git a/backend/internal/service/tls_fingerprint_profile_service.go b/backend/internal/service/tls_fingerprint_profile_service.go new file mode 100644 index 00000000..33937cc7 --- /dev/null +++ b/backend/internal/service/tls_fingerprint_profile_service.go @@ -0,0 +1,259 @@ +package service + +import ( + "context" + "math/rand/v2" + "sync" + "time" + + "github.com/Wei-Shaw/sub2api/internal/model" + "github.com/Wei-Shaw/sub2api/internal/pkg/logger" + "github.com/Wei-Shaw/sub2api/internal/pkg/tlsfingerprint" +) + +// TLSFingerprintProfileRepository 定义 TLS 指纹模板的数据访问接口 +type TLSFingerprintProfileRepository interface { + List(ctx context.Context) ([]*model.TLSFingerprintProfile, error) + GetByID(ctx context.Context, id int64) (*model.TLSFingerprintProfile, error) + Create(ctx context.Context, profile *model.TLSFingerprintProfile) (*model.TLSFingerprintProfile, error) + Update(ctx context.Context, profile *model.TLSFingerprintProfile) (*model.TLSFingerprintProfile, error) + Delete(ctx context.Context, id int64) error +} + +// TLSFingerprintProfileCache 定义 TLS 指纹模板的缓存接口 +type TLSFingerprintProfileCache interface { + Get(ctx context.Context) ([]*model.TLSFingerprintProfile, bool) + Set(ctx context.Context, profiles []*model.TLSFingerprintProfile) error + Invalidate(ctx context.Context) error + NotifyUpdate(ctx context.Context) error + SubscribeUpdates(ctx context.Context, handler func()) +} + +// TLSFingerprintProfileService TLS 指纹模板管理服务 +type TLSFingerprintProfileService struct { + repo TLSFingerprintProfileRepository + cache TLSFingerprintProfileCache + + // 本地 ID→Profile 映射缓存,用于 DoWithTLS 热路径快速查找 + localCache map[int64]*model.TLSFingerprintProfile + localMu sync.RWMutex +} + +// NewTLSFingerprintProfileService 创建 TLS 指纹模板服务 +func NewTLSFingerprintProfileService( + repo TLSFingerprintProfileRepository, + cache TLSFingerprintProfileCache, +) *TLSFingerprintProfileService { + svc := &TLSFingerprintProfileService{ + repo: repo, + cache: cache, + localCache: make(map[int64]*model.TLSFingerprintProfile), + } + + ctx := context.Background() + if err := svc.reloadFromDB(ctx); err != nil { + logger.LegacyPrintf("service.tls_fp_profile", "[TLSFPProfileService] Failed to load profiles from DB on startup: %v", err) + if fallbackErr := svc.refreshLocalCache(ctx); fallbackErr != nil { + logger.LegacyPrintf("service.tls_fp_profile", "[TLSFPProfileService] Failed to load profiles from cache fallback on startup: %v", fallbackErr) + } + } + + if cache != nil { + cache.SubscribeUpdates(ctx, func() { + if err := svc.refreshLocalCache(context.Background()); err != nil { + logger.LegacyPrintf("service.tls_fp_profile", "[TLSFPProfileService] Failed to refresh cache on notification: %v", err) + } + }) + } + + return svc +} + +// --- CRUD --- + +// List 获取所有模板 +func (s *TLSFingerprintProfileService) List(ctx context.Context) ([]*model.TLSFingerprintProfile, error) { + return s.repo.List(ctx) +} + +// GetByID 根据 ID 获取模板 +func (s *TLSFingerprintProfileService) GetByID(ctx context.Context, id int64) (*model.TLSFingerprintProfile, error) { + return s.repo.GetByID(ctx, id) +} + +// Create 创建模板 +func (s *TLSFingerprintProfileService) Create(ctx context.Context, profile *model.TLSFingerprintProfile) (*model.TLSFingerprintProfile, error) { + if err := profile.Validate(); err != nil { + return nil, err + } + + created, err := s.repo.Create(ctx, profile) + if err != nil { + return nil, err + } + + refreshCtx, cancel := s.newCacheRefreshContext() + defer cancel() + s.invalidateAndNotify(refreshCtx) + + return created, nil +} + +// Update 更新模板 +func (s *TLSFingerprintProfileService) Update(ctx context.Context, profile *model.TLSFingerprintProfile) (*model.TLSFingerprintProfile, error) { + if err := profile.Validate(); err != nil { + return nil, err + } + + updated, err := s.repo.Update(ctx, profile) + if err != nil { + return nil, err + } + + refreshCtx, cancel := s.newCacheRefreshContext() + defer cancel() + s.invalidateAndNotify(refreshCtx) + + return updated, nil +} + +// Delete 删除模板 +func (s *TLSFingerprintProfileService) Delete(ctx context.Context, id int64) error { + if err := s.repo.Delete(ctx, id); err != nil { + return err + } + + refreshCtx, cancel := s.newCacheRefreshContext() + defer cancel() + s.invalidateAndNotify(refreshCtx) + + return nil +} + +// --- 热路径:运行时 Profile 查找 --- + +// GetProfileByID 根据 ID 从本地缓存获取 Profile(用于 DoWithTLS 热路径) +// 返回 nil 表示未找到,调用方应 fallback 到内置默认 Profile +func (s *TLSFingerprintProfileService) GetProfileByID(id int64) *tlsfingerprint.Profile { + s.localMu.RLock() + p, ok := s.localCache[id] + s.localMu.RUnlock() + + if ok && p != nil { + return p.ToTLSProfile() + } + return nil +} + +// getRandomProfile 从本地缓存中随机选择一个 Profile +func (s *TLSFingerprintProfileService) getRandomProfile() *tlsfingerprint.Profile { + s.localMu.RLock() + defer s.localMu.RUnlock() + + if len(s.localCache) == 0 { + return nil + } + + // 收集所有 profile + profiles := make([]*model.TLSFingerprintProfile, 0, len(s.localCache)) + for _, p := range s.localCache { + if p != nil { + profiles = append(profiles, p) + } + } + if len(profiles) == 0 { + return nil + } + + return profiles[rand.IntN(len(profiles))].ToTLSProfile() +} + +// ResolveTLSProfile 根据 Account 的配置解析出运行时 TLS Profile +// +// 逻辑: +// 1. 未启用 TLS 指纹 → 返回 nil(不伪装) +// 2. 启用 + 绑定了 profile_id → 从缓存查找对应 profile +// 3. 启用 + 未绑定或找不到 → 返回空 Profile(使用代码内置默认值) +func (s *TLSFingerprintProfileService) ResolveTLSProfile(account *Account) *tlsfingerprint.Profile { + if account == nil || !account.IsTLSFingerprintEnabled() { + return nil + } + id := account.GetTLSFingerprintProfileID() + if id > 0 { + if p := s.GetProfileByID(id); p != nil { + return p + } + } + if id == -1 { + // 随机选择一个 profile + if p := s.getRandomProfile(); p != nil { + return p + } + } + // TLS 启用但无绑定 profile → 空 Profile → dialer 使用内置默认值 + return &tlsfingerprint.Profile{Name: "Built-in Default (Node.js 24.x)"} +} + +// --- 缓存管理 --- + +func (s *TLSFingerprintProfileService) refreshLocalCache(ctx context.Context) error { + if s.cache != nil { + if profiles, ok := s.cache.Get(ctx); ok { + s.setLocalCache(profiles) + return nil + } + } + return s.reloadFromDB(ctx) +} + +func (s *TLSFingerprintProfileService) reloadFromDB(ctx context.Context) error { + profiles, err := s.repo.List(ctx) + if err != nil { + return err + } + + if s.cache != nil { + if err := s.cache.Set(ctx, profiles); err != nil { + logger.LegacyPrintf("service.tls_fp_profile", "[TLSFPProfileService] Failed to set cache: %v", err) + } + } + + s.setLocalCache(profiles) + return nil +} + +func (s *TLSFingerprintProfileService) setLocalCache(profiles []*model.TLSFingerprintProfile) { + m := make(map[int64]*model.TLSFingerprintProfile, len(profiles)) + for _, p := range profiles { + m[p.ID] = p + } + + s.localMu.Lock() + s.localCache = m + s.localMu.Unlock() +} + +func (s *TLSFingerprintProfileService) newCacheRefreshContext() (context.Context, context.CancelFunc) { + return context.WithTimeout(context.Background(), 3*time.Second) +} + +func (s *TLSFingerprintProfileService) invalidateAndNotify(ctx context.Context) { + if s.cache != nil { + if err := s.cache.Invalidate(ctx); err != nil { + logger.LegacyPrintf("service.tls_fp_profile", "[TLSFPProfileService] Failed to invalidate cache: %v", err) + } + } + + if err := s.reloadFromDB(ctx); err != nil { + logger.LegacyPrintf("service.tls_fp_profile", "[TLSFPProfileService] Failed to refresh local cache: %v", err) + s.localMu.Lock() + s.localCache = make(map[int64]*model.TLSFingerprintProfile) + s.localMu.Unlock() + } + + if s.cache != nil { + if err := s.cache.NotifyUpdate(ctx); err != nil { + logger.LegacyPrintf("service.tls_fp_profile", "[TLSFPProfileService] Failed to notify cache update: %v", err) + } + } +} diff --git a/backend/internal/service/wire.go b/backend/internal/service/wire.go index fca8fd6d..d79a3531 100644 --- a/backend/internal/service/wire.go +++ b/backend/internal/service/wire.go @@ -482,6 +482,7 @@ var ProviderSet = wire.NewSet( NewUsageCache, NewTotpService, NewErrorPassthroughService, + NewTLSFingerprintProfileService, NewDigestSessionStore, ProvideIdempotencyCoordinator, ProvideSystemOperationLockService, diff --git a/backend/migrations/080_create_tls_fingerprint_profiles.sql b/backend/migrations/080_create_tls_fingerprint_profiles.sql new file mode 100644 index 00000000..c13c21f8 --- /dev/null +++ b/backend/migrations/080_create_tls_fingerprint_profiles.sql @@ -0,0 +1,29 @@ +-- Create tls_fingerprint_profiles table for managing TLS fingerprint templates. +-- Each profile contains ClientHello parameters to simulate specific client TLS handshake characteristics. + +SET LOCAL lock_timeout = '5s'; +SET LOCAL statement_timeout = '10min'; + +CREATE TABLE IF NOT EXISTS tls_fingerprint_profiles ( + id BIGSERIAL PRIMARY KEY, + name VARCHAR(100) NOT NULL UNIQUE, + description TEXT, + enable_grease BOOLEAN NOT NULL DEFAULT false, + cipher_suites JSONB, + curves JSONB, + point_formats JSONB, + signature_algorithms JSONB, + alpn_protocols JSONB, + supported_versions JSONB, + key_share_groups JSONB, + psk_modes JSONB, + extensions JSONB, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +COMMENT ON TABLE tls_fingerprint_profiles IS 'TLS fingerprint templates for simulating specific client TLS handshake characteristics'; +COMMENT ON COLUMN tls_fingerprint_profiles.name IS 'Unique profile name, e.g. "macOS Node.js v24"'; +COMMENT ON COLUMN tls_fingerprint_profiles.enable_grease IS 'Whether to insert GREASE values in ClientHello extensions'; +COMMENT ON COLUMN tls_fingerprint_profiles.cipher_suites IS 'TLS cipher suite list as JSON array of uint16 (order-sensitive, affects JA3)'; +COMMENT ON COLUMN tls_fingerprint_profiles.extensions IS 'TLS extension type IDs in send order as JSON array of uint16'; diff --git a/frontend/src/api/admin/index.ts b/frontend/src/api/admin/index.ts index a6ebfc2c..9a3fb8c5 100644 --- a/frontend/src/api/admin/index.ts +++ b/frontend/src/api/admin/index.ts @@ -24,6 +24,7 @@ import dataManagementAPI from './dataManagement' import apiKeysAPI from './apiKeys' import scheduledTestsAPI from './scheduledTests' import backupAPI from './backup' +import tlsFingerprintProfileAPI from './tlsFingerprintProfile' /** * Unified admin API object for convenient access @@ -49,7 +50,8 @@ export const adminAPI = { dataManagement: dataManagementAPI, apiKeys: apiKeysAPI, scheduledTests: scheduledTestsAPI, - backup: backupAPI + backup: backupAPI, + tlsFingerprintProfiles: tlsFingerprintProfileAPI } export { @@ -73,7 +75,8 @@ export { dataManagementAPI, apiKeysAPI, scheduledTestsAPI, - backupAPI + backupAPI, + tlsFingerprintProfileAPI } export default adminAPI @@ -82,3 +85,4 @@ export default adminAPI export type { BalanceHistoryItem } from './users' export type { ErrorPassthroughRule, CreateRuleRequest, UpdateRuleRequest } from './errorPassthrough' export type { BackupAgentHealth, DataManagementConfig } from './dataManagement' +export type { TLSFingerprintProfile, CreateProfileRequest, UpdateProfileRequest } from './tlsFingerprintProfile' diff --git a/frontend/src/api/admin/tlsFingerprintProfile.ts b/frontend/src/api/admin/tlsFingerprintProfile.ts new file mode 100644 index 00000000..f6a26dd5 --- /dev/null +++ b/frontend/src/api/admin/tlsFingerprintProfile.ts @@ -0,0 +1,98 @@ +/** + * Admin TLS Fingerprint Profile API endpoints + * Handles TLS fingerprint profile CRUD for administrators + */ + +import { apiClient } from '../client' + +/** + * TLS fingerprint profile interface + */ +export interface TLSFingerprintProfile { + id: number + name: string + description: string | null + enable_grease: boolean + cipher_suites: number[] + curves: number[] + point_formats: number[] + signature_algorithms: number[] + alpn_protocols: string[] + supported_versions: number[] + key_share_groups: number[] + psk_modes: number[] + extensions: number[] + created_at: string + updated_at: string +} + +/** + * Create profile request + */ +export interface CreateProfileRequest { + name: string + description?: string | null + enable_grease?: boolean + cipher_suites?: number[] + curves?: number[] + point_formats?: number[] + signature_algorithms?: number[] + alpn_protocols?: string[] + supported_versions?: number[] + key_share_groups?: number[] + psk_modes?: number[] + extensions?: number[] +} + +/** + * Update profile request + */ +export interface UpdateProfileRequest { + name?: string + description?: string | null + enable_grease?: boolean + cipher_suites?: number[] + curves?: number[] + point_formats?: number[] + signature_algorithms?: number[] + alpn_protocols?: string[] + supported_versions?: number[] + key_share_groups?: number[] + psk_modes?: number[] + extensions?: number[] +} + +export async function list(): Promise { + const { data } = await apiClient.get('/admin/tls-fingerprint-profiles') + return data +} + +export async function getById(id: number): Promise { + const { data } = await apiClient.get(`/admin/tls-fingerprint-profiles/${id}`) + return data +} + +export async function create(profileData: CreateProfileRequest): Promise { + const { data } = await apiClient.post('/admin/tls-fingerprint-profiles', profileData) + return data +} + +export async function update(id: number, updates: UpdateProfileRequest): Promise { + const { data } = await apiClient.put(`/admin/tls-fingerprint-profiles/${id}`, updates) + return data +} + +export async function deleteProfile(id: number): Promise<{ message: string }> { + const { data } = await apiClient.delete<{ message: string }>(`/admin/tls-fingerprint-profiles/${id}`) + return data +} + +export const tlsFingerprintProfileAPI = { + list, + getById, + create, + update, + delete: deleteProfile +} + +export default tlsFingerprintProfileAPI diff --git a/frontend/src/components/account/CreateAccountModal.vue b/frontend/src/components/account/CreateAccountModal.vue index cff7ae1c..806b57db 100644 --- a/frontend/src/components/account/CreateAccountModal.vue +++ b/frontend/src/components/account/CreateAccountModal.vue @@ -2169,6 +2169,14 @@ /> + +
+ +
@@ -3082,6 +3090,8 @@ const umqModeOptions = computed(() => [ { value: 'serialize', label: t('admin.accounts.quotaControl.rpmLimit.umqModeSerialize') }, ]) const tlsFingerprintEnabled = ref(false) +const tlsFingerprintProfileId = ref(null) +const tlsFingerprintProfiles = ref<{ id: number; name: string }[]>([]) const sessionIdMaskingEnabled = ref(false) const cacheTTLOverrideEnabled = ref(false) const cacheTTLOverrideTarget = ref('5m') @@ -3247,6 +3257,10 @@ watch( () => props.show, (newVal) => { if (newVal) { + // Load TLS fingerprint profiles + adminAPI.tlsFingerprintProfiles.list() + .then(profiles => { tlsFingerprintProfiles.value = profiles.map(p => ({ id: p.id, name: p.name })) }) + .catch(() => { tlsFingerprintProfiles.value = [] }) // Modal opened - fill related models allowedModels.value = [...getModelsByPlatform(form.platform)] // Antigravity: 默认使用映射模式并填充默认映射 @@ -3747,6 +3761,7 @@ const resetForm = () => { rpmStickyBuffer.value = null userMsgQueueMode.value = '' tlsFingerprintEnabled.value = false + tlsFingerprintProfileId.value = null sessionIdMaskingEnabled.value = false cacheTTLOverrideEnabled.value = false cacheTTLOverrideTarget.value = '5m' @@ -4825,6 +4840,9 @@ const handleAnthropicExchange = async (authCode: string) => { // Add TLS fingerprint settings if (tlsFingerprintEnabled.value) { extra.enable_tls_fingerprint = true + if (tlsFingerprintProfileId.value) { + extra.tls_fingerprint_profile_id = tlsFingerprintProfileId.value + } } // Add session ID masking settings @@ -4940,6 +4958,9 @@ const handleCookieAuth = async (sessionKey: string) => { // Add TLS fingerprint settings if (tlsFingerprintEnabled.value) { extra.enable_tls_fingerprint = true + if (tlsFingerprintProfileId.value) { + extra.tls_fingerprint_profile_id = tlsFingerprintProfileId.value + } } // Add session ID masking settings diff --git a/frontend/src/components/account/EditAccountModal.vue b/frontend/src/components/account/EditAccountModal.vue index 5f3da1b7..da6c9715 100644 --- a/frontend/src/components/account/EditAccountModal.vue +++ b/frontend/src/components/account/EditAccountModal.vue @@ -1504,6 +1504,14 @@ /> + +
+ +
@@ -1841,6 +1849,8 @@ const umqModeOptions = computed(() => [ { value: 'serialize', label: t('admin.accounts.quotaControl.rpmLimit.umqModeSerialize') }, ]) const tlsFingerprintEnabled = ref(false) +const tlsFingerprintProfileId = ref(null) +const tlsFingerprintProfiles = ref<{ id: number; name: string }[]>([]) const sessionIdMaskingEnabled = ref(false) const cacheTTLOverrideEnabled = ref(false) const cacheTTLOverrideTarget = ref('5m') @@ -2255,11 +2265,21 @@ watch( } if (!wasShow || newAccount !== previousAccount) { syncFormFromAccount(newAccount) + loadTLSProfiles() } }, { immediate: true } ) +const loadTLSProfiles = async () => { + try { + const profiles = await adminAPI.tlsFingerprintProfiles.list() + tlsFingerprintProfiles.value = profiles.map(p => ({ id: p.id, name: p.name })) + } catch { + tlsFingerprintProfiles.value = [] + } +} + // Model mapping helpers const addModelMapping = () => { modelMappings.value.push({ from: '', to: '' }) @@ -2458,6 +2478,7 @@ function loadQuotaControlSettings(account: Account) { rpmStickyBuffer.value = null userMsgQueueMode.value = '' tlsFingerprintEnabled.value = false + tlsFingerprintProfileId.value = null sessionIdMaskingEnabled.value = false cacheTTLOverrideEnabled.value = false cacheTTLOverrideTarget.value = '5m' @@ -2495,6 +2516,7 @@ function loadQuotaControlSettings(account: Account) { if (account.enable_tls_fingerprint === true) { tlsFingerprintEnabled.value = true } + tlsFingerprintProfileId.value = account.tls_fingerprint_profile_id ?? null // Load session ID masking setting if (account.session_id_masking_enabled === true) { @@ -2932,8 +2954,14 @@ const handleSubmit = async () => { // TLS fingerprint setting if (tlsFingerprintEnabled.value) { newExtra.enable_tls_fingerprint = true + if (tlsFingerprintProfileId.value) { + newExtra.tls_fingerprint_profile_id = tlsFingerprintProfileId.value + } else { + delete newExtra.tls_fingerprint_profile_id + } } else { delete newExtra.enable_tls_fingerprint + delete newExtra.tls_fingerprint_profile_id } // Session ID masking setting diff --git a/frontend/src/components/admin/TLSFingerprintProfilesModal.vue b/frontend/src/components/admin/TLSFingerprintProfilesModal.vue new file mode 100644 index 00000000..69465346 --- /dev/null +++ b/frontend/src/components/admin/TLSFingerprintProfilesModal.vue @@ -0,0 +1,625 @@ +