diff --git a/backend/consts/user.go b/backend/consts/user.go index 36089c9..64cc308 100644 --- a/backend/consts/user.go +++ b/backend/consts/user.go @@ -21,12 +21,14 @@ type UserPlatform string const ( UserPlatformEmail UserPlatform = "email" UserPlatformDingTalk UserPlatform = "dingtalk" + UserPlatformCustom UserPlatform = "custom" ) type OAuthKind string const ( - OAuthKindSignUpOrIn OAuthKind = "signup_or_in" + OAuthKindInvite OAuthKind = "invite" + OAuthKindLogin OAuthKind = "login" ) type InviteCodeStatus string diff --git a/backend/db/migrate/schema.go b/backend/db/migrate/schema.go index 33faaab..232087d 100644 --- a/backend/db/migrate/schema.go +++ b/backend/db/migrate/schema.go @@ -243,9 +243,8 @@ var ( {Name: "enable_sso", Type: field.TypeBool, Default: false}, {Name: "force_two_factor_auth", Type: field.TypeBool, Default: false}, {Name: "disable_password_login", Type: field.TypeBool, Default: false}, - {Name: "enable_dingtalk_oauth", Type: field.TypeBool, Default: false}, - {Name: "dingtalk_client_id", Type: field.TypeString, Nullable: true}, - {Name: "dingtalk_client_secret", Type: field.TypeString, Nullable: true}, + {Name: "dingtalk_oauth", Type: field.TypeJSON, Nullable: true}, + {Name: "custom_oauth", Type: field.TypeJSON, Nullable: true}, {Name: "created_at", Type: field.TypeTime}, {Name: "updated_at", Type: field.TypeTime}, } diff --git a/backend/db/mutation.go b/backend/db/mutation.go index 0140a20..3debddc 100644 --- a/backend/db/mutation.go +++ b/backend/db/mutation.go @@ -31,6 +31,7 @@ import ( "github.com/chaitin/MonkeyCode/backend/db/user" "github.com/chaitin/MonkeyCode/backend/db/useridentity" "github.com/chaitin/MonkeyCode/backend/db/userloginhistory" + "github.com/chaitin/MonkeyCode/backend/ent/types" "github.com/google/uuid" ) @@ -8816,9 +8817,8 @@ type SettingMutation struct { enable_sso *bool force_two_factor_auth *bool disable_password_login *bool - enable_dingtalk_oauth *bool - dingtalk_client_id *string - dingtalk_client_secret *string + dingtalk_oauth **types.DingtalkOAuth + custom_oauth **types.CustomOAuth created_at *time.Time updated_at *time.Time clearedFields map[string]struct{} @@ -9039,138 +9039,102 @@ func (m *SettingMutation) ResetDisablePasswordLogin() { m.disable_password_login = nil } -// SetEnableDingtalkOauth sets the "enable_dingtalk_oauth" field. -func (m *SettingMutation) SetEnableDingtalkOauth(b bool) { - m.enable_dingtalk_oauth = &b +// SetDingtalkOauth sets the "dingtalk_oauth" field. +func (m *SettingMutation) SetDingtalkOauth(to *types.DingtalkOAuth) { + m.dingtalk_oauth = &to } -// EnableDingtalkOauth returns the value of the "enable_dingtalk_oauth" field in the mutation. -func (m *SettingMutation) EnableDingtalkOauth() (r bool, exists bool) { - v := m.enable_dingtalk_oauth +// DingtalkOauth returns the value of the "dingtalk_oauth" field in the mutation. +func (m *SettingMutation) DingtalkOauth() (r *types.DingtalkOAuth, exists bool) { + v := m.dingtalk_oauth if v == nil { return } return *v, true } -// OldEnableDingtalkOauth returns the old "enable_dingtalk_oauth" field's value of the Setting entity. +// OldDingtalkOauth returns the old "dingtalk_oauth" field's value of the Setting entity. // If the Setting 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 *SettingMutation) OldEnableDingtalkOauth(ctx context.Context) (v bool, err error) { +func (m *SettingMutation) OldDingtalkOauth(ctx context.Context) (v *types.DingtalkOAuth, err error) { if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldEnableDingtalkOauth is only allowed on UpdateOne operations") + return v, errors.New("OldDingtalkOauth is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { - return v, errors.New("OldEnableDingtalkOauth requires an ID field in the mutation") + return v, errors.New("OldDingtalkOauth requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { - return v, fmt.Errorf("querying old value for OldEnableDingtalkOauth: %w", err) + return v, fmt.Errorf("querying old value for OldDingtalkOauth: %w", err) } - return oldValue.EnableDingtalkOauth, nil + return oldValue.DingtalkOauth, nil } -// ResetEnableDingtalkOauth resets all changes to the "enable_dingtalk_oauth" field. -func (m *SettingMutation) ResetEnableDingtalkOauth() { - m.enable_dingtalk_oauth = nil +// ClearDingtalkOauth clears the value of the "dingtalk_oauth" field. +func (m *SettingMutation) ClearDingtalkOauth() { + m.dingtalk_oauth = nil + m.clearedFields[setting.FieldDingtalkOauth] = struct{}{} } -// SetDingtalkClientID sets the "dingtalk_client_id" field. -func (m *SettingMutation) SetDingtalkClientID(s string) { - m.dingtalk_client_id = &s -} - -// DingtalkClientID returns the value of the "dingtalk_client_id" field in the mutation. -func (m *SettingMutation) DingtalkClientID() (r string, exists bool) { - v := m.dingtalk_client_id - if v == nil { - return - } - return *v, true -} - -// OldDingtalkClientID returns the old "dingtalk_client_id" field's value of the Setting entity. -// If the Setting 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 *SettingMutation) OldDingtalkClientID(ctx context.Context) (v string, err error) { - if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldDingtalkClientID is only allowed on UpdateOne operations") - } - if m.id == nil || m.oldValue == nil { - return v, errors.New("OldDingtalkClientID requires an ID field in the mutation") - } - oldValue, err := m.oldValue(ctx) - if err != nil { - return v, fmt.Errorf("querying old value for OldDingtalkClientID: %w", err) - } - return oldValue.DingtalkClientID, nil -} - -// ClearDingtalkClientID clears the value of the "dingtalk_client_id" field. -func (m *SettingMutation) ClearDingtalkClientID() { - m.dingtalk_client_id = nil - m.clearedFields[setting.FieldDingtalkClientID] = struct{}{} -} - -// DingtalkClientIDCleared returns if the "dingtalk_client_id" field was cleared in this mutation. -func (m *SettingMutation) DingtalkClientIDCleared() bool { - _, ok := m.clearedFields[setting.FieldDingtalkClientID] +// DingtalkOauthCleared returns if the "dingtalk_oauth" field was cleared in this mutation. +func (m *SettingMutation) DingtalkOauthCleared() bool { + _, ok := m.clearedFields[setting.FieldDingtalkOauth] return ok } -// ResetDingtalkClientID resets all changes to the "dingtalk_client_id" field. -func (m *SettingMutation) ResetDingtalkClientID() { - m.dingtalk_client_id = nil - delete(m.clearedFields, setting.FieldDingtalkClientID) +// ResetDingtalkOauth resets all changes to the "dingtalk_oauth" field. +func (m *SettingMutation) ResetDingtalkOauth() { + m.dingtalk_oauth = nil + delete(m.clearedFields, setting.FieldDingtalkOauth) } -// SetDingtalkClientSecret sets the "dingtalk_client_secret" field. -func (m *SettingMutation) SetDingtalkClientSecret(s string) { - m.dingtalk_client_secret = &s +// SetCustomOauth sets the "custom_oauth" field. +func (m *SettingMutation) SetCustomOauth(to *types.CustomOAuth) { + m.custom_oauth = &to } -// DingtalkClientSecret returns the value of the "dingtalk_client_secret" field in the mutation. -func (m *SettingMutation) DingtalkClientSecret() (r string, exists bool) { - v := m.dingtalk_client_secret +// CustomOauth returns the value of the "custom_oauth" field in the mutation. +func (m *SettingMutation) CustomOauth() (r *types.CustomOAuth, exists bool) { + v := m.custom_oauth if v == nil { return } return *v, true } -// OldDingtalkClientSecret returns the old "dingtalk_client_secret" field's value of the Setting entity. +// OldCustomOauth returns the old "custom_oauth" field's value of the Setting entity. // If the Setting 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 *SettingMutation) OldDingtalkClientSecret(ctx context.Context) (v string, err error) { +func (m *SettingMutation) OldCustomOauth(ctx context.Context) (v *types.CustomOAuth, err error) { if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldDingtalkClientSecret is only allowed on UpdateOne operations") + return v, errors.New("OldCustomOauth is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { - return v, errors.New("OldDingtalkClientSecret requires an ID field in the mutation") + return v, errors.New("OldCustomOauth requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { - return v, fmt.Errorf("querying old value for OldDingtalkClientSecret: %w", err) + return v, fmt.Errorf("querying old value for OldCustomOauth: %w", err) } - return oldValue.DingtalkClientSecret, nil + return oldValue.CustomOauth, nil } -// ClearDingtalkClientSecret clears the value of the "dingtalk_client_secret" field. -func (m *SettingMutation) ClearDingtalkClientSecret() { - m.dingtalk_client_secret = nil - m.clearedFields[setting.FieldDingtalkClientSecret] = struct{}{} +// ClearCustomOauth clears the value of the "custom_oauth" field. +func (m *SettingMutation) ClearCustomOauth() { + m.custom_oauth = nil + m.clearedFields[setting.FieldCustomOauth] = struct{}{} } -// DingtalkClientSecretCleared returns if the "dingtalk_client_secret" field was cleared in this mutation. -func (m *SettingMutation) DingtalkClientSecretCleared() bool { - _, ok := m.clearedFields[setting.FieldDingtalkClientSecret] +// CustomOauthCleared returns if the "custom_oauth" field was cleared in this mutation. +func (m *SettingMutation) CustomOauthCleared() bool { + _, ok := m.clearedFields[setting.FieldCustomOauth] return ok } -// ResetDingtalkClientSecret resets all changes to the "dingtalk_client_secret" field. -func (m *SettingMutation) ResetDingtalkClientSecret() { - m.dingtalk_client_secret = nil - delete(m.clearedFields, setting.FieldDingtalkClientSecret) +// ResetCustomOauth resets all changes to the "custom_oauth" field. +func (m *SettingMutation) ResetCustomOauth() { + m.custom_oauth = nil + delete(m.clearedFields, setting.FieldCustomOauth) } // SetCreatedAt sets the "created_at" field. @@ -9279,7 +9243,7 @@ func (m *SettingMutation) Type() string { // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *SettingMutation) Fields() []string { - fields := make([]string, 0, 8) + fields := make([]string, 0, 7) if m.enable_sso != nil { fields = append(fields, setting.FieldEnableSSO) } @@ -9289,14 +9253,11 @@ func (m *SettingMutation) Fields() []string { if m.disable_password_login != nil { fields = append(fields, setting.FieldDisablePasswordLogin) } - if m.enable_dingtalk_oauth != nil { - fields = append(fields, setting.FieldEnableDingtalkOauth) + if m.dingtalk_oauth != nil { + fields = append(fields, setting.FieldDingtalkOauth) } - if m.dingtalk_client_id != nil { - fields = append(fields, setting.FieldDingtalkClientID) - } - if m.dingtalk_client_secret != nil { - fields = append(fields, setting.FieldDingtalkClientSecret) + if m.custom_oauth != nil { + fields = append(fields, setting.FieldCustomOauth) } if m.created_at != nil { fields = append(fields, setting.FieldCreatedAt) @@ -9318,12 +9279,10 @@ func (m *SettingMutation) Field(name string) (ent.Value, bool) { return m.ForceTwoFactorAuth() case setting.FieldDisablePasswordLogin: return m.DisablePasswordLogin() - case setting.FieldEnableDingtalkOauth: - return m.EnableDingtalkOauth() - case setting.FieldDingtalkClientID: - return m.DingtalkClientID() - case setting.FieldDingtalkClientSecret: - return m.DingtalkClientSecret() + case setting.FieldDingtalkOauth: + return m.DingtalkOauth() + case setting.FieldCustomOauth: + return m.CustomOauth() case setting.FieldCreatedAt: return m.CreatedAt() case setting.FieldUpdatedAt: @@ -9343,12 +9302,10 @@ func (m *SettingMutation) OldField(ctx context.Context, name string) (ent.Value, return m.OldForceTwoFactorAuth(ctx) case setting.FieldDisablePasswordLogin: return m.OldDisablePasswordLogin(ctx) - case setting.FieldEnableDingtalkOauth: - return m.OldEnableDingtalkOauth(ctx) - case setting.FieldDingtalkClientID: - return m.OldDingtalkClientID(ctx) - case setting.FieldDingtalkClientSecret: - return m.OldDingtalkClientSecret(ctx) + case setting.FieldDingtalkOauth: + return m.OldDingtalkOauth(ctx) + case setting.FieldCustomOauth: + return m.OldCustomOauth(ctx) case setting.FieldCreatedAt: return m.OldCreatedAt(ctx) case setting.FieldUpdatedAt: @@ -9383,26 +9340,19 @@ func (m *SettingMutation) SetField(name string, value ent.Value) error { } m.SetDisablePasswordLogin(v) return nil - case setting.FieldEnableDingtalkOauth: - v, ok := value.(bool) + case setting.FieldDingtalkOauth: + v, ok := value.(*types.DingtalkOAuth) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } - m.SetEnableDingtalkOauth(v) + m.SetDingtalkOauth(v) return nil - case setting.FieldDingtalkClientID: - v, ok := value.(string) + case setting.FieldCustomOauth: + v, ok := value.(*types.CustomOAuth) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } - m.SetDingtalkClientID(v) - return nil - case setting.FieldDingtalkClientSecret: - v, ok := value.(string) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.SetDingtalkClientSecret(v) + m.SetCustomOauth(v) return nil case setting.FieldCreatedAt: v, ok := value.(time.Time) @@ -9448,11 +9398,11 @@ func (m *SettingMutation) AddField(name string, value ent.Value) error { // mutation. func (m *SettingMutation) ClearedFields() []string { var fields []string - if m.FieldCleared(setting.FieldDingtalkClientID) { - fields = append(fields, setting.FieldDingtalkClientID) + if m.FieldCleared(setting.FieldDingtalkOauth) { + fields = append(fields, setting.FieldDingtalkOauth) } - if m.FieldCleared(setting.FieldDingtalkClientSecret) { - fields = append(fields, setting.FieldDingtalkClientSecret) + if m.FieldCleared(setting.FieldCustomOauth) { + fields = append(fields, setting.FieldCustomOauth) } return fields } @@ -9468,11 +9418,11 @@ func (m *SettingMutation) FieldCleared(name string) bool { // error if the field is not defined in the schema. func (m *SettingMutation) ClearField(name string) error { switch name { - case setting.FieldDingtalkClientID: - m.ClearDingtalkClientID() + case setting.FieldDingtalkOauth: + m.ClearDingtalkOauth() return nil - case setting.FieldDingtalkClientSecret: - m.ClearDingtalkClientSecret() + case setting.FieldCustomOauth: + m.ClearCustomOauth() return nil } return fmt.Errorf("unknown Setting nullable field %s", name) @@ -9491,14 +9441,11 @@ func (m *SettingMutation) ResetField(name string) error { case setting.FieldDisablePasswordLogin: m.ResetDisablePasswordLogin() return nil - case setting.FieldEnableDingtalkOauth: - m.ResetEnableDingtalkOauth() + case setting.FieldDingtalkOauth: + m.ResetDingtalkOauth() return nil - case setting.FieldDingtalkClientID: - m.ResetDingtalkClientID() - return nil - case setting.FieldDingtalkClientSecret: - m.ResetDingtalkClientSecret() + case setting.FieldCustomOauth: + m.ResetCustomOauth() return nil case setting.FieldCreatedAt: m.ResetCreatedAt() diff --git a/backend/db/runtime/runtime.go b/backend/db/runtime/runtime.go index c1ba6bb..447abef 100644 --- a/backend/db/runtime/runtime.go +++ b/backend/db/runtime/runtime.go @@ -232,16 +232,12 @@ func init() { settingDescDisablePasswordLogin := settingFields[3].Descriptor() // setting.DefaultDisablePasswordLogin holds the default value on creation for the disable_password_login field. setting.DefaultDisablePasswordLogin = settingDescDisablePasswordLogin.Default.(bool) - // settingDescEnableDingtalkOauth is the schema descriptor for enable_dingtalk_oauth field. - settingDescEnableDingtalkOauth := settingFields[4].Descriptor() - // setting.DefaultEnableDingtalkOauth holds the default value on creation for the enable_dingtalk_oauth field. - setting.DefaultEnableDingtalkOauth = settingDescEnableDingtalkOauth.Default.(bool) // settingDescCreatedAt is the schema descriptor for created_at field. - settingDescCreatedAt := settingFields[7].Descriptor() + settingDescCreatedAt := settingFields[6].Descriptor() // setting.DefaultCreatedAt holds the default value on creation for the created_at field. setting.DefaultCreatedAt = settingDescCreatedAt.Default.(func() time.Time) // settingDescUpdatedAt is the schema descriptor for updated_at field. - settingDescUpdatedAt := settingFields[8].Descriptor() + settingDescUpdatedAt := settingFields[7].Descriptor() // setting.DefaultUpdatedAt holds the default value on creation for the updated_at field. setting.DefaultUpdatedAt = settingDescUpdatedAt.Default.(func() time.Time) // setting.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. diff --git a/backend/db/setting.go b/backend/db/setting.go index 51bcc21..9188159 100644 --- a/backend/db/setting.go +++ b/backend/db/setting.go @@ -3,6 +3,7 @@ package db import ( + "encoding/json" "fmt" "strings" "time" @@ -10,6 +11,7 @@ import ( "entgo.io/ent" "entgo.io/ent/dialect/sql" "github.com/chaitin/MonkeyCode/backend/db/setting" + "github.com/chaitin/MonkeyCode/backend/ent/types" "github.com/google/uuid" ) @@ -24,12 +26,10 @@ type Setting struct { ForceTwoFactorAuth bool `json:"force_two_factor_auth,omitempty"` // DisablePasswordLogin holds the value of the "disable_password_login" field. DisablePasswordLogin bool `json:"disable_password_login,omitempty"` - // EnableDingtalkOauth holds the value of the "enable_dingtalk_oauth" field. - EnableDingtalkOauth bool `json:"enable_dingtalk_oauth,omitempty"` - // DingtalkClientID holds the value of the "dingtalk_client_id" field. - DingtalkClientID string `json:"dingtalk_client_id,omitempty"` - // DingtalkClientSecret holds the value of the "dingtalk_client_secret" field. - DingtalkClientSecret string `json:"dingtalk_client_secret,omitempty"` + // DingtalkOauth holds the value of the "dingtalk_oauth" field. + DingtalkOauth *types.DingtalkOAuth `json:"dingtalk_oauth,omitempty"` + // CustomOauth holds the value of the "custom_oauth" field. + CustomOauth *types.CustomOAuth `json:"custom_oauth,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. @@ -42,10 +42,10 @@ func (*Setting) scanValues(columns []string) ([]any, error) { values := make([]any, len(columns)) for i := range columns { switch columns[i] { - case setting.FieldEnableSSO, setting.FieldForceTwoFactorAuth, setting.FieldDisablePasswordLogin, setting.FieldEnableDingtalkOauth: + case setting.FieldDingtalkOauth, setting.FieldCustomOauth: + values[i] = new([]byte) + case setting.FieldEnableSSO, setting.FieldForceTwoFactorAuth, setting.FieldDisablePasswordLogin: values[i] = new(sql.NullBool) - case setting.FieldDingtalkClientID, setting.FieldDingtalkClientSecret: - values[i] = new(sql.NullString) case setting.FieldCreatedAt, setting.FieldUpdatedAt: values[i] = new(sql.NullTime) case setting.FieldID: @@ -89,23 +89,21 @@ func (s *Setting) assignValues(columns []string, values []any) error { } else if value.Valid { s.DisablePasswordLogin = value.Bool } - case setting.FieldEnableDingtalkOauth: - if value, ok := values[i].(*sql.NullBool); !ok { - return fmt.Errorf("unexpected type %T for field enable_dingtalk_oauth", values[i]) - } else if value.Valid { - s.EnableDingtalkOauth = value.Bool + case setting.FieldDingtalkOauth: + if value, ok := values[i].(*[]byte); !ok { + return fmt.Errorf("unexpected type %T for field dingtalk_oauth", values[i]) + } else if value != nil && len(*value) > 0 { + if err := json.Unmarshal(*value, &s.DingtalkOauth); err != nil { + return fmt.Errorf("unmarshal field dingtalk_oauth: %w", err) + } } - case setting.FieldDingtalkClientID: - if value, ok := values[i].(*sql.NullString); !ok { - return fmt.Errorf("unexpected type %T for field dingtalk_client_id", values[i]) - } else if value.Valid { - s.DingtalkClientID = value.String - } - case setting.FieldDingtalkClientSecret: - if value, ok := values[i].(*sql.NullString); !ok { - return fmt.Errorf("unexpected type %T for field dingtalk_client_secret", values[i]) - } else if value.Valid { - s.DingtalkClientSecret = value.String + case setting.FieldCustomOauth: + if value, ok := values[i].(*[]byte); !ok { + return fmt.Errorf("unexpected type %T for field custom_oauth", values[i]) + } else if value != nil && len(*value) > 0 { + if err := json.Unmarshal(*value, &s.CustomOauth); err != nil { + return fmt.Errorf("unmarshal field custom_oauth: %w", err) + } } case setting.FieldCreatedAt: if value, ok := values[i].(*sql.NullTime); !ok { @@ -164,14 +162,11 @@ func (s *Setting) String() string { builder.WriteString("disable_password_login=") builder.WriteString(fmt.Sprintf("%v", s.DisablePasswordLogin)) builder.WriteString(", ") - builder.WriteString("enable_dingtalk_oauth=") - builder.WriteString(fmt.Sprintf("%v", s.EnableDingtalkOauth)) + builder.WriteString("dingtalk_oauth=") + builder.WriteString(fmt.Sprintf("%v", s.DingtalkOauth)) builder.WriteString(", ") - builder.WriteString("dingtalk_client_id=") - builder.WriteString(s.DingtalkClientID) - builder.WriteString(", ") - builder.WriteString("dingtalk_client_secret=") - builder.WriteString(s.DingtalkClientSecret) + builder.WriteString("custom_oauth=") + builder.WriteString(fmt.Sprintf("%v", s.CustomOauth)) builder.WriteString(", ") builder.WriteString("created_at=") builder.WriteString(s.CreatedAt.Format(time.ANSIC)) diff --git a/backend/db/setting/setting.go b/backend/db/setting/setting.go index 6a8fd21..d0037bd 100644 --- a/backend/db/setting/setting.go +++ b/backend/db/setting/setting.go @@ -19,12 +19,10 @@ const ( FieldForceTwoFactorAuth = "force_two_factor_auth" // FieldDisablePasswordLogin holds the string denoting the disable_password_login field in the database. FieldDisablePasswordLogin = "disable_password_login" - // FieldEnableDingtalkOauth holds the string denoting the enable_dingtalk_oauth field in the database. - FieldEnableDingtalkOauth = "enable_dingtalk_oauth" - // FieldDingtalkClientID holds the string denoting the dingtalk_client_id field in the database. - FieldDingtalkClientID = "dingtalk_client_id" - // FieldDingtalkClientSecret holds the string denoting the dingtalk_client_secret field in the database. - FieldDingtalkClientSecret = "dingtalk_client_secret" + // FieldDingtalkOauth holds the string denoting the dingtalk_oauth field in the database. + FieldDingtalkOauth = "dingtalk_oauth" + // FieldCustomOauth holds the string denoting the custom_oauth field in the database. + FieldCustomOauth = "custom_oauth" // 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. @@ -39,9 +37,8 @@ var Columns = []string{ FieldEnableSSO, FieldForceTwoFactorAuth, FieldDisablePasswordLogin, - FieldEnableDingtalkOauth, - FieldDingtalkClientID, - FieldDingtalkClientSecret, + FieldDingtalkOauth, + FieldCustomOauth, FieldCreatedAt, FieldUpdatedAt, } @@ -63,8 +60,6 @@ var ( DefaultForceTwoFactorAuth bool // DefaultDisablePasswordLogin holds the default value on creation for the "disable_password_login" field. DefaultDisablePasswordLogin bool - // DefaultEnableDingtalkOauth holds the default value on creation for the "enable_dingtalk_oauth" field. - DefaultEnableDingtalkOauth bool // 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. @@ -96,21 +91,6 @@ func ByDisablePasswordLogin(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldDisablePasswordLogin, opts...).ToFunc() } -// ByEnableDingtalkOauth orders the results by the enable_dingtalk_oauth field. -func ByEnableDingtalkOauth(opts ...sql.OrderTermOption) OrderOption { - return sql.OrderByField(FieldEnableDingtalkOauth, opts...).ToFunc() -} - -// ByDingtalkClientID orders the results by the dingtalk_client_id field. -func ByDingtalkClientID(opts ...sql.OrderTermOption) OrderOption { - return sql.OrderByField(FieldDingtalkClientID, opts...).ToFunc() -} - -// ByDingtalkClientSecret orders the results by the dingtalk_client_secret field. -func ByDingtalkClientSecret(opts ...sql.OrderTermOption) OrderOption { - return sql.OrderByField(FieldDingtalkClientSecret, opts...).ToFunc() -} - // ByCreatedAt orders the results by the created_at field. func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldCreatedAt, opts...).ToFunc() diff --git a/backend/db/setting/where.go b/backend/db/setting/where.go index b2db507..1ec5ce7 100644 --- a/backend/db/setting/where.go +++ b/backend/db/setting/where.go @@ -70,21 +70,6 @@ func DisablePasswordLogin(v bool) predicate.Setting { return predicate.Setting(sql.FieldEQ(FieldDisablePasswordLogin, v)) } -// EnableDingtalkOauth applies equality check predicate on the "enable_dingtalk_oauth" field. It's identical to EnableDingtalkOauthEQ. -func EnableDingtalkOauth(v bool) predicate.Setting { - return predicate.Setting(sql.FieldEQ(FieldEnableDingtalkOauth, v)) -} - -// DingtalkClientID applies equality check predicate on the "dingtalk_client_id" field. It's identical to DingtalkClientIDEQ. -func DingtalkClientID(v string) predicate.Setting { - return predicate.Setting(sql.FieldEQ(FieldDingtalkClientID, v)) -} - -// DingtalkClientSecret applies equality check predicate on the "dingtalk_client_secret" field. It's identical to DingtalkClientSecretEQ. -func DingtalkClientSecret(v string) predicate.Setting { - return predicate.Setting(sql.FieldEQ(FieldDingtalkClientSecret, v)) -} - // CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. func CreatedAt(v time.Time) predicate.Setting { return predicate.Setting(sql.FieldEQ(FieldCreatedAt, v)) @@ -125,164 +110,24 @@ func DisablePasswordLoginNEQ(v bool) predicate.Setting { return predicate.Setting(sql.FieldNEQ(FieldDisablePasswordLogin, v)) } -// EnableDingtalkOauthEQ applies the EQ predicate on the "enable_dingtalk_oauth" field. -func EnableDingtalkOauthEQ(v bool) predicate.Setting { - return predicate.Setting(sql.FieldEQ(FieldEnableDingtalkOauth, v)) +// DingtalkOauthIsNil applies the IsNil predicate on the "dingtalk_oauth" field. +func DingtalkOauthIsNil() predicate.Setting { + return predicate.Setting(sql.FieldIsNull(FieldDingtalkOauth)) } -// EnableDingtalkOauthNEQ applies the NEQ predicate on the "enable_dingtalk_oauth" field. -func EnableDingtalkOauthNEQ(v bool) predicate.Setting { - return predicate.Setting(sql.FieldNEQ(FieldEnableDingtalkOauth, v)) +// DingtalkOauthNotNil applies the NotNil predicate on the "dingtalk_oauth" field. +func DingtalkOauthNotNil() predicate.Setting { + return predicate.Setting(sql.FieldNotNull(FieldDingtalkOauth)) } -// DingtalkClientIDEQ applies the EQ predicate on the "dingtalk_client_id" field. -func DingtalkClientIDEQ(v string) predicate.Setting { - return predicate.Setting(sql.FieldEQ(FieldDingtalkClientID, v)) +// CustomOauthIsNil applies the IsNil predicate on the "custom_oauth" field. +func CustomOauthIsNil() predicate.Setting { + return predicate.Setting(sql.FieldIsNull(FieldCustomOauth)) } -// DingtalkClientIDNEQ applies the NEQ predicate on the "dingtalk_client_id" field. -func DingtalkClientIDNEQ(v string) predicate.Setting { - return predicate.Setting(sql.FieldNEQ(FieldDingtalkClientID, v)) -} - -// DingtalkClientIDIn applies the In predicate on the "dingtalk_client_id" field. -func DingtalkClientIDIn(vs ...string) predicate.Setting { - return predicate.Setting(sql.FieldIn(FieldDingtalkClientID, vs...)) -} - -// DingtalkClientIDNotIn applies the NotIn predicate on the "dingtalk_client_id" field. -func DingtalkClientIDNotIn(vs ...string) predicate.Setting { - return predicate.Setting(sql.FieldNotIn(FieldDingtalkClientID, vs...)) -} - -// DingtalkClientIDGT applies the GT predicate on the "dingtalk_client_id" field. -func DingtalkClientIDGT(v string) predicate.Setting { - return predicate.Setting(sql.FieldGT(FieldDingtalkClientID, v)) -} - -// DingtalkClientIDGTE applies the GTE predicate on the "dingtalk_client_id" field. -func DingtalkClientIDGTE(v string) predicate.Setting { - return predicate.Setting(sql.FieldGTE(FieldDingtalkClientID, v)) -} - -// DingtalkClientIDLT applies the LT predicate on the "dingtalk_client_id" field. -func DingtalkClientIDLT(v string) predicate.Setting { - return predicate.Setting(sql.FieldLT(FieldDingtalkClientID, v)) -} - -// DingtalkClientIDLTE applies the LTE predicate on the "dingtalk_client_id" field. -func DingtalkClientIDLTE(v string) predicate.Setting { - return predicate.Setting(sql.FieldLTE(FieldDingtalkClientID, v)) -} - -// DingtalkClientIDContains applies the Contains predicate on the "dingtalk_client_id" field. -func DingtalkClientIDContains(v string) predicate.Setting { - return predicate.Setting(sql.FieldContains(FieldDingtalkClientID, v)) -} - -// DingtalkClientIDHasPrefix applies the HasPrefix predicate on the "dingtalk_client_id" field. -func DingtalkClientIDHasPrefix(v string) predicate.Setting { - return predicate.Setting(sql.FieldHasPrefix(FieldDingtalkClientID, v)) -} - -// DingtalkClientIDHasSuffix applies the HasSuffix predicate on the "dingtalk_client_id" field. -func DingtalkClientIDHasSuffix(v string) predicate.Setting { - return predicate.Setting(sql.FieldHasSuffix(FieldDingtalkClientID, v)) -} - -// DingtalkClientIDIsNil applies the IsNil predicate on the "dingtalk_client_id" field. -func DingtalkClientIDIsNil() predicate.Setting { - return predicate.Setting(sql.FieldIsNull(FieldDingtalkClientID)) -} - -// DingtalkClientIDNotNil applies the NotNil predicate on the "dingtalk_client_id" field. -func DingtalkClientIDNotNil() predicate.Setting { - return predicate.Setting(sql.FieldNotNull(FieldDingtalkClientID)) -} - -// DingtalkClientIDEqualFold applies the EqualFold predicate on the "dingtalk_client_id" field. -func DingtalkClientIDEqualFold(v string) predicate.Setting { - return predicate.Setting(sql.FieldEqualFold(FieldDingtalkClientID, v)) -} - -// DingtalkClientIDContainsFold applies the ContainsFold predicate on the "dingtalk_client_id" field. -func DingtalkClientIDContainsFold(v string) predicate.Setting { - return predicate.Setting(sql.FieldContainsFold(FieldDingtalkClientID, v)) -} - -// DingtalkClientSecretEQ applies the EQ predicate on the "dingtalk_client_secret" field. -func DingtalkClientSecretEQ(v string) predicate.Setting { - return predicate.Setting(sql.FieldEQ(FieldDingtalkClientSecret, v)) -} - -// DingtalkClientSecretNEQ applies the NEQ predicate on the "dingtalk_client_secret" field. -func DingtalkClientSecretNEQ(v string) predicate.Setting { - return predicate.Setting(sql.FieldNEQ(FieldDingtalkClientSecret, v)) -} - -// DingtalkClientSecretIn applies the In predicate on the "dingtalk_client_secret" field. -func DingtalkClientSecretIn(vs ...string) predicate.Setting { - return predicate.Setting(sql.FieldIn(FieldDingtalkClientSecret, vs...)) -} - -// DingtalkClientSecretNotIn applies the NotIn predicate on the "dingtalk_client_secret" field. -func DingtalkClientSecretNotIn(vs ...string) predicate.Setting { - return predicate.Setting(sql.FieldNotIn(FieldDingtalkClientSecret, vs...)) -} - -// DingtalkClientSecretGT applies the GT predicate on the "dingtalk_client_secret" field. -func DingtalkClientSecretGT(v string) predicate.Setting { - return predicate.Setting(sql.FieldGT(FieldDingtalkClientSecret, v)) -} - -// DingtalkClientSecretGTE applies the GTE predicate on the "dingtalk_client_secret" field. -func DingtalkClientSecretGTE(v string) predicate.Setting { - return predicate.Setting(sql.FieldGTE(FieldDingtalkClientSecret, v)) -} - -// DingtalkClientSecretLT applies the LT predicate on the "dingtalk_client_secret" field. -func DingtalkClientSecretLT(v string) predicate.Setting { - return predicate.Setting(sql.FieldLT(FieldDingtalkClientSecret, v)) -} - -// DingtalkClientSecretLTE applies the LTE predicate on the "dingtalk_client_secret" field. -func DingtalkClientSecretLTE(v string) predicate.Setting { - return predicate.Setting(sql.FieldLTE(FieldDingtalkClientSecret, v)) -} - -// DingtalkClientSecretContains applies the Contains predicate on the "dingtalk_client_secret" field. -func DingtalkClientSecretContains(v string) predicate.Setting { - return predicate.Setting(sql.FieldContains(FieldDingtalkClientSecret, v)) -} - -// DingtalkClientSecretHasPrefix applies the HasPrefix predicate on the "dingtalk_client_secret" field. -func DingtalkClientSecretHasPrefix(v string) predicate.Setting { - return predicate.Setting(sql.FieldHasPrefix(FieldDingtalkClientSecret, v)) -} - -// DingtalkClientSecretHasSuffix applies the HasSuffix predicate on the "dingtalk_client_secret" field. -func DingtalkClientSecretHasSuffix(v string) predicate.Setting { - return predicate.Setting(sql.FieldHasSuffix(FieldDingtalkClientSecret, v)) -} - -// DingtalkClientSecretIsNil applies the IsNil predicate on the "dingtalk_client_secret" field. -func DingtalkClientSecretIsNil() predicate.Setting { - return predicate.Setting(sql.FieldIsNull(FieldDingtalkClientSecret)) -} - -// DingtalkClientSecretNotNil applies the NotNil predicate on the "dingtalk_client_secret" field. -func DingtalkClientSecretNotNil() predicate.Setting { - return predicate.Setting(sql.FieldNotNull(FieldDingtalkClientSecret)) -} - -// DingtalkClientSecretEqualFold applies the EqualFold predicate on the "dingtalk_client_secret" field. -func DingtalkClientSecretEqualFold(v string) predicate.Setting { - return predicate.Setting(sql.FieldEqualFold(FieldDingtalkClientSecret, v)) -} - -// DingtalkClientSecretContainsFold applies the ContainsFold predicate on the "dingtalk_client_secret" field. -func DingtalkClientSecretContainsFold(v string) predicate.Setting { - return predicate.Setting(sql.FieldContainsFold(FieldDingtalkClientSecret, v)) +// CustomOauthNotNil applies the NotNil predicate on the "custom_oauth" field. +func CustomOauthNotNil() predicate.Setting { + return predicate.Setting(sql.FieldNotNull(FieldCustomOauth)) } // CreatedAtEQ applies the EQ predicate on the "created_at" field. diff --git a/backend/db/setting_create.go b/backend/db/setting_create.go index 069217b..5fda099 100644 --- a/backend/db/setting_create.go +++ b/backend/db/setting_create.go @@ -13,6 +13,7 @@ import ( "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" "github.com/chaitin/MonkeyCode/backend/db/setting" + "github.com/chaitin/MonkeyCode/backend/ent/types" "github.com/google/uuid" ) @@ -66,45 +67,15 @@ func (sc *SettingCreate) SetNillableDisablePasswordLogin(b *bool) *SettingCreate return sc } -// SetEnableDingtalkOauth sets the "enable_dingtalk_oauth" field. -func (sc *SettingCreate) SetEnableDingtalkOauth(b bool) *SettingCreate { - sc.mutation.SetEnableDingtalkOauth(b) +// SetDingtalkOauth sets the "dingtalk_oauth" field. +func (sc *SettingCreate) SetDingtalkOauth(to *types.DingtalkOAuth) *SettingCreate { + sc.mutation.SetDingtalkOauth(to) return sc } -// SetNillableEnableDingtalkOauth sets the "enable_dingtalk_oauth" field if the given value is not nil. -func (sc *SettingCreate) SetNillableEnableDingtalkOauth(b *bool) *SettingCreate { - if b != nil { - sc.SetEnableDingtalkOauth(*b) - } - return sc -} - -// SetDingtalkClientID sets the "dingtalk_client_id" field. -func (sc *SettingCreate) SetDingtalkClientID(s string) *SettingCreate { - sc.mutation.SetDingtalkClientID(s) - return sc -} - -// SetNillableDingtalkClientID sets the "dingtalk_client_id" field if the given value is not nil. -func (sc *SettingCreate) SetNillableDingtalkClientID(s *string) *SettingCreate { - if s != nil { - sc.SetDingtalkClientID(*s) - } - return sc -} - -// SetDingtalkClientSecret sets the "dingtalk_client_secret" field. -func (sc *SettingCreate) SetDingtalkClientSecret(s string) *SettingCreate { - sc.mutation.SetDingtalkClientSecret(s) - return sc -} - -// SetNillableDingtalkClientSecret sets the "dingtalk_client_secret" field if the given value is not nil. -func (sc *SettingCreate) SetNillableDingtalkClientSecret(s *string) *SettingCreate { - if s != nil { - sc.SetDingtalkClientSecret(*s) - } +// SetCustomOauth sets the "custom_oauth" field. +func (sc *SettingCreate) SetCustomOauth(to *types.CustomOAuth) *SettingCreate { + sc.mutation.SetCustomOauth(to) return sc } @@ -189,10 +160,6 @@ func (sc *SettingCreate) defaults() { v := setting.DefaultDisablePasswordLogin sc.mutation.SetDisablePasswordLogin(v) } - if _, ok := sc.mutation.EnableDingtalkOauth(); !ok { - v := setting.DefaultEnableDingtalkOauth - sc.mutation.SetEnableDingtalkOauth(v) - } if _, ok := sc.mutation.CreatedAt(); !ok { v := setting.DefaultCreatedAt() sc.mutation.SetCreatedAt(v) @@ -214,9 +181,6 @@ func (sc *SettingCreate) check() error { if _, ok := sc.mutation.DisablePasswordLogin(); !ok { return &ValidationError{Name: "disable_password_login", err: errors.New(`db: missing required field "Setting.disable_password_login"`)} } - if _, ok := sc.mutation.EnableDingtalkOauth(); !ok { - return &ValidationError{Name: "enable_dingtalk_oauth", err: errors.New(`db: missing required field "Setting.enable_dingtalk_oauth"`)} - } if _, ok := sc.mutation.CreatedAt(); !ok { return &ValidationError{Name: "created_at", err: errors.New(`db: missing required field "Setting.created_at"`)} } @@ -271,17 +235,13 @@ func (sc *SettingCreate) createSpec() (*Setting, *sqlgraph.CreateSpec) { _spec.SetField(setting.FieldDisablePasswordLogin, field.TypeBool, value) _node.DisablePasswordLogin = value } - if value, ok := sc.mutation.EnableDingtalkOauth(); ok { - _spec.SetField(setting.FieldEnableDingtalkOauth, field.TypeBool, value) - _node.EnableDingtalkOauth = value + if value, ok := sc.mutation.DingtalkOauth(); ok { + _spec.SetField(setting.FieldDingtalkOauth, field.TypeJSON, value) + _node.DingtalkOauth = value } - if value, ok := sc.mutation.DingtalkClientID(); ok { - _spec.SetField(setting.FieldDingtalkClientID, field.TypeString, value) - _node.DingtalkClientID = value - } - if value, ok := sc.mutation.DingtalkClientSecret(); ok { - _spec.SetField(setting.FieldDingtalkClientSecret, field.TypeString, value) - _node.DingtalkClientSecret = value + if value, ok := sc.mutation.CustomOauth(); ok { + _spec.SetField(setting.FieldCustomOauth, field.TypeJSON, value) + _node.CustomOauth = value } if value, ok := sc.mutation.CreatedAt(); ok { _spec.SetField(setting.FieldCreatedAt, field.TypeTime, value) @@ -379,51 +339,39 @@ func (u *SettingUpsert) UpdateDisablePasswordLogin() *SettingUpsert { return u } -// SetEnableDingtalkOauth sets the "enable_dingtalk_oauth" field. -func (u *SettingUpsert) SetEnableDingtalkOauth(v bool) *SettingUpsert { - u.Set(setting.FieldEnableDingtalkOauth, v) +// SetDingtalkOauth sets the "dingtalk_oauth" field. +func (u *SettingUpsert) SetDingtalkOauth(v *types.DingtalkOAuth) *SettingUpsert { + u.Set(setting.FieldDingtalkOauth, v) return u } -// UpdateEnableDingtalkOauth sets the "enable_dingtalk_oauth" field to the value that was provided on create. -func (u *SettingUpsert) UpdateEnableDingtalkOauth() *SettingUpsert { - u.SetExcluded(setting.FieldEnableDingtalkOauth) +// UpdateDingtalkOauth sets the "dingtalk_oauth" field to the value that was provided on create. +func (u *SettingUpsert) UpdateDingtalkOauth() *SettingUpsert { + u.SetExcluded(setting.FieldDingtalkOauth) return u } -// SetDingtalkClientID sets the "dingtalk_client_id" field. -func (u *SettingUpsert) SetDingtalkClientID(v string) *SettingUpsert { - u.Set(setting.FieldDingtalkClientID, v) +// ClearDingtalkOauth clears the value of the "dingtalk_oauth" field. +func (u *SettingUpsert) ClearDingtalkOauth() *SettingUpsert { + u.SetNull(setting.FieldDingtalkOauth) return u } -// UpdateDingtalkClientID sets the "dingtalk_client_id" field to the value that was provided on create. -func (u *SettingUpsert) UpdateDingtalkClientID() *SettingUpsert { - u.SetExcluded(setting.FieldDingtalkClientID) +// SetCustomOauth sets the "custom_oauth" field. +func (u *SettingUpsert) SetCustomOauth(v *types.CustomOAuth) *SettingUpsert { + u.Set(setting.FieldCustomOauth, v) return u } -// ClearDingtalkClientID clears the value of the "dingtalk_client_id" field. -func (u *SettingUpsert) ClearDingtalkClientID() *SettingUpsert { - u.SetNull(setting.FieldDingtalkClientID) +// UpdateCustomOauth sets the "custom_oauth" field to the value that was provided on create. +func (u *SettingUpsert) UpdateCustomOauth() *SettingUpsert { + u.SetExcluded(setting.FieldCustomOauth) return u } -// SetDingtalkClientSecret sets the "dingtalk_client_secret" field. -func (u *SettingUpsert) SetDingtalkClientSecret(v string) *SettingUpsert { - u.Set(setting.FieldDingtalkClientSecret, v) - return u -} - -// UpdateDingtalkClientSecret sets the "dingtalk_client_secret" field to the value that was provided on create. -func (u *SettingUpsert) UpdateDingtalkClientSecret() *SettingUpsert { - u.SetExcluded(setting.FieldDingtalkClientSecret) - return u -} - -// ClearDingtalkClientSecret clears the value of the "dingtalk_client_secret" field. -func (u *SettingUpsert) ClearDingtalkClientSecret() *SettingUpsert { - u.SetNull(setting.FieldDingtalkClientSecret) +// ClearCustomOauth clears the value of the "custom_oauth" field. +func (u *SettingUpsert) ClearCustomOauth() *SettingUpsert { + u.SetNull(setting.FieldCustomOauth) return u } @@ -541,59 +489,45 @@ func (u *SettingUpsertOne) UpdateDisablePasswordLogin() *SettingUpsertOne { }) } -// SetEnableDingtalkOauth sets the "enable_dingtalk_oauth" field. -func (u *SettingUpsertOne) SetEnableDingtalkOauth(v bool) *SettingUpsertOne { +// SetDingtalkOauth sets the "dingtalk_oauth" field. +func (u *SettingUpsertOne) SetDingtalkOauth(v *types.DingtalkOAuth) *SettingUpsertOne { return u.Update(func(s *SettingUpsert) { - s.SetEnableDingtalkOauth(v) + s.SetDingtalkOauth(v) }) } -// UpdateEnableDingtalkOauth sets the "enable_dingtalk_oauth" field to the value that was provided on create. -func (u *SettingUpsertOne) UpdateEnableDingtalkOauth() *SettingUpsertOne { +// UpdateDingtalkOauth sets the "dingtalk_oauth" field to the value that was provided on create. +func (u *SettingUpsertOne) UpdateDingtalkOauth() *SettingUpsertOne { return u.Update(func(s *SettingUpsert) { - s.UpdateEnableDingtalkOauth() + s.UpdateDingtalkOauth() }) } -// SetDingtalkClientID sets the "dingtalk_client_id" field. -func (u *SettingUpsertOne) SetDingtalkClientID(v string) *SettingUpsertOne { +// ClearDingtalkOauth clears the value of the "dingtalk_oauth" field. +func (u *SettingUpsertOne) ClearDingtalkOauth() *SettingUpsertOne { return u.Update(func(s *SettingUpsert) { - s.SetDingtalkClientID(v) + s.ClearDingtalkOauth() }) } -// UpdateDingtalkClientID sets the "dingtalk_client_id" field to the value that was provided on create. -func (u *SettingUpsertOne) UpdateDingtalkClientID() *SettingUpsertOne { +// SetCustomOauth sets the "custom_oauth" field. +func (u *SettingUpsertOne) SetCustomOauth(v *types.CustomOAuth) *SettingUpsertOne { return u.Update(func(s *SettingUpsert) { - s.UpdateDingtalkClientID() + s.SetCustomOauth(v) }) } -// ClearDingtalkClientID clears the value of the "dingtalk_client_id" field. -func (u *SettingUpsertOne) ClearDingtalkClientID() *SettingUpsertOne { +// UpdateCustomOauth sets the "custom_oauth" field to the value that was provided on create. +func (u *SettingUpsertOne) UpdateCustomOauth() *SettingUpsertOne { return u.Update(func(s *SettingUpsert) { - s.ClearDingtalkClientID() + s.UpdateCustomOauth() }) } -// SetDingtalkClientSecret sets the "dingtalk_client_secret" field. -func (u *SettingUpsertOne) SetDingtalkClientSecret(v string) *SettingUpsertOne { +// ClearCustomOauth clears the value of the "custom_oauth" field. +func (u *SettingUpsertOne) ClearCustomOauth() *SettingUpsertOne { return u.Update(func(s *SettingUpsert) { - s.SetDingtalkClientSecret(v) - }) -} - -// UpdateDingtalkClientSecret sets the "dingtalk_client_secret" field to the value that was provided on create. -func (u *SettingUpsertOne) UpdateDingtalkClientSecret() *SettingUpsertOne { - return u.Update(func(s *SettingUpsert) { - s.UpdateDingtalkClientSecret() - }) -} - -// ClearDingtalkClientSecret clears the value of the "dingtalk_client_secret" field. -func (u *SettingUpsertOne) ClearDingtalkClientSecret() *SettingUpsertOne { - return u.Update(func(s *SettingUpsert) { - s.ClearDingtalkClientSecret() + s.ClearCustomOauth() }) } @@ -882,59 +816,45 @@ func (u *SettingUpsertBulk) UpdateDisablePasswordLogin() *SettingUpsertBulk { }) } -// SetEnableDingtalkOauth sets the "enable_dingtalk_oauth" field. -func (u *SettingUpsertBulk) SetEnableDingtalkOauth(v bool) *SettingUpsertBulk { +// SetDingtalkOauth sets the "dingtalk_oauth" field. +func (u *SettingUpsertBulk) SetDingtalkOauth(v *types.DingtalkOAuth) *SettingUpsertBulk { return u.Update(func(s *SettingUpsert) { - s.SetEnableDingtalkOauth(v) + s.SetDingtalkOauth(v) }) } -// UpdateEnableDingtalkOauth sets the "enable_dingtalk_oauth" field to the value that was provided on create. -func (u *SettingUpsertBulk) UpdateEnableDingtalkOauth() *SettingUpsertBulk { +// UpdateDingtalkOauth sets the "dingtalk_oauth" field to the value that was provided on create. +func (u *SettingUpsertBulk) UpdateDingtalkOauth() *SettingUpsertBulk { return u.Update(func(s *SettingUpsert) { - s.UpdateEnableDingtalkOauth() + s.UpdateDingtalkOauth() }) } -// SetDingtalkClientID sets the "dingtalk_client_id" field. -func (u *SettingUpsertBulk) SetDingtalkClientID(v string) *SettingUpsertBulk { +// ClearDingtalkOauth clears the value of the "dingtalk_oauth" field. +func (u *SettingUpsertBulk) ClearDingtalkOauth() *SettingUpsertBulk { return u.Update(func(s *SettingUpsert) { - s.SetDingtalkClientID(v) + s.ClearDingtalkOauth() }) } -// UpdateDingtalkClientID sets the "dingtalk_client_id" field to the value that was provided on create. -func (u *SettingUpsertBulk) UpdateDingtalkClientID() *SettingUpsertBulk { +// SetCustomOauth sets the "custom_oauth" field. +func (u *SettingUpsertBulk) SetCustomOauth(v *types.CustomOAuth) *SettingUpsertBulk { return u.Update(func(s *SettingUpsert) { - s.UpdateDingtalkClientID() + s.SetCustomOauth(v) }) } -// ClearDingtalkClientID clears the value of the "dingtalk_client_id" field. -func (u *SettingUpsertBulk) ClearDingtalkClientID() *SettingUpsertBulk { +// UpdateCustomOauth sets the "custom_oauth" field to the value that was provided on create. +func (u *SettingUpsertBulk) UpdateCustomOauth() *SettingUpsertBulk { return u.Update(func(s *SettingUpsert) { - s.ClearDingtalkClientID() + s.UpdateCustomOauth() }) } -// SetDingtalkClientSecret sets the "dingtalk_client_secret" field. -func (u *SettingUpsertBulk) SetDingtalkClientSecret(v string) *SettingUpsertBulk { +// ClearCustomOauth clears the value of the "custom_oauth" field. +func (u *SettingUpsertBulk) ClearCustomOauth() *SettingUpsertBulk { return u.Update(func(s *SettingUpsert) { - s.SetDingtalkClientSecret(v) - }) -} - -// UpdateDingtalkClientSecret sets the "dingtalk_client_secret" field to the value that was provided on create. -func (u *SettingUpsertBulk) UpdateDingtalkClientSecret() *SettingUpsertBulk { - return u.Update(func(s *SettingUpsert) { - s.UpdateDingtalkClientSecret() - }) -} - -// ClearDingtalkClientSecret clears the value of the "dingtalk_client_secret" field. -func (u *SettingUpsertBulk) ClearDingtalkClientSecret() *SettingUpsertBulk { - return u.Update(func(s *SettingUpsert) { - s.ClearDingtalkClientSecret() + s.ClearCustomOauth() }) } diff --git a/backend/db/setting_update.go b/backend/db/setting_update.go index 6d57184..13d233f 100644 --- a/backend/db/setting_update.go +++ b/backend/db/setting_update.go @@ -13,6 +13,7 @@ import ( "entgo.io/ent/schema/field" "github.com/chaitin/MonkeyCode/backend/db/predicate" "github.com/chaitin/MonkeyCode/backend/db/setting" + "github.com/chaitin/MonkeyCode/backend/ent/types" ) // SettingUpdate is the builder for updating Setting entities. @@ -71,57 +72,27 @@ func (su *SettingUpdate) SetNillableDisablePasswordLogin(b *bool) *SettingUpdate return su } -// SetEnableDingtalkOauth sets the "enable_dingtalk_oauth" field. -func (su *SettingUpdate) SetEnableDingtalkOauth(b bool) *SettingUpdate { - su.mutation.SetEnableDingtalkOauth(b) +// SetDingtalkOauth sets the "dingtalk_oauth" field. +func (su *SettingUpdate) SetDingtalkOauth(to *types.DingtalkOAuth) *SettingUpdate { + su.mutation.SetDingtalkOauth(to) return su } -// SetNillableEnableDingtalkOauth sets the "enable_dingtalk_oauth" field if the given value is not nil. -func (su *SettingUpdate) SetNillableEnableDingtalkOauth(b *bool) *SettingUpdate { - if b != nil { - su.SetEnableDingtalkOauth(*b) - } +// ClearDingtalkOauth clears the value of the "dingtalk_oauth" field. +func (su *SettingUpdate) ClearDingtalkOauth() *SettingUpdate { + su.mutation.ClearDingtalkOauth() return su } -// SetDingtalkClientID sets the "dingtalk_client_id" field. -func (su *SettingUpdate) SetDingtalkClientID(s string) *SettingUpdate { - su.mutation.SetDingtalkClientID(s) +// SetCustomOauth sets the "custom_oauth" field. +func (su *SettingUpdate) SetCustomOauth(to *types.CustomOAuth) *SettingUpdate { + su.mutation.SetCustomOauth(to) return su } -// SetNillableDingtalkClientID sets the "dingtalk_client_id" field if the given value is not nil. -func (su *SettingUpdate) SetNillableDingtalkClientID(s *string) *SettingUpdate { - if s != nil { - su.SetDingtalkClientID(*s) - } - return su -} - -// ClearDingtalkClientID clears the value of the "dingtalk_client_id" field. -func (su *SettingUpdate) ClearDingtalkClientID() *SettingUpdate { - su.mutation.ClearDingtalkClientID() - return su -} - -// SetDingtalkClientSecret sets the "dingtalk_client_secret" field. -func (su *SettingUpdate) SetDingtalkClientSecret(s string) *SettingUpdate { - su.mutation.SetDingtalkClientSecret(s) - return su -} - -// SetNillableDingtalkClientSecret sets the "dingtalk_client_secret" field if the given value is not nil. -func (su *SettingUpdate) SetNillableDingtalkClientSecret(s *string) *SettingUpdate { - if s != nil { - su.SetDingtalkClientSecret(*s) - } - return su -} - -// ClearDingtalkClientSecret clears the value of the "dingtalk_client_secret" field. -func (su *SettingUpdate) ClearDingtalkClientSecret() *SettingUpdate { - su.mutation.ClearDingtalkClientSecret() +// ClearCustomOauth clears the value of the "custom_oauth" field. +func (su *SettingUpdate) ClearCustomOauth() *SettingUpdate { + su.mutation.ClearCustomOauth() return su } @@ -210,20 +181,17 @@ func (su *SettingUpdate) sqlSave(ctx context.Context) (n int, err error) { if value, ok := su.mutation.DisablePasswordLogin(); ok { _spec.SetField(setting.FieldDisablePasswordLogin, field.TypeBool, value) } - if value, ok := su.mutation.EnableDingtalkOauth(); ok { - _spec.SetField(setting.FieldEnableDingtalkOauth, field.TypeBool, value) + if value, ok := su.mutation.DingtalkOauth(); ok { + _spec.SetField(setting.FieldDingtalkOauth, field.TypeJSON, value) } - if value, ok := su.mutation.DingtalkClientID(); ok { - _spec.SetField(setting.FieldDingtalkClientID, field.TypeString, value) + if su.mutation.DingtalkOauthCleared() { + _spec.ClearField(setting.FieldDingtalkOauth, field.TypeJSON) } - if su.mutation.DingtalkClientIDCleared() { - _spec.ClearField(setting.FieldDingtalkClientID, field.TypeString) + if value, ok := su.mutation.CustomOauth(); ok { + _spec.SetField(setting.FieldCustomOauth, field.TypeJSON, value) } - if value, ok := su.mutation.DingtalkClientSecret(); ok { - _spec.SetField(setting.FieldDingtalkClientSecret, field.TypeString, value) - } - if su.mutation.DingtalkClientSecretCleared() { - _spec.ClearField(setting.FieldDingtalkClientSecret, field.TypeString) + if su.mutation.CustomOauthCleared() { + _spec.ClearField(setting.FieldCustomOauth, field.TypeJSON) } if value, ok := su.mutation.CreatedAt(); ok { _spec.SetField(setting.FieldCreatedAt, field.TypeTime, value) @@ -295,57 +263,27 @@ func (suo *SettingUpdateOne) SetNillableDisablePasswordLogin(b *bool) *SettingUp return suo } -// SetEnableDingtalkOauth sets the "enable_dingtalk_oauth" field. -func (suo *SettingUpdateOne) SetEnableDingtalkOauth(b bool) *SettingUpdateOne { - suo.mutation.SetEnableDingtalkOauth(b) +// SetDingtalkOauth sets the "dingtalk_oauth" field. +func (suo *SettingUpdateOne) SetDingtalkOauth(to *types.DingtalkOAuth) *SettingUpdateOne { + suo.mutation.SetDingtalkOauth(to) return suo } -// SetNillableEnableDingtalkOauth sets the "enable_dingtalk_oauth" field if the given value is not nil. -func (suo *SettingUpdateOne) SetNillableEnableDingtalkOauth(b *bool) *SettingUpdateOne { - if b != nil { - suo.SetEnableDingtalkOauth(*b) - } +// ClearDingtalkOauth clears the value of the "dingtalk_oauth" field. +func (suo *SettingUpdateOne) ClearDingtalkOauth() *SettingUpdateOne { + suo.mutation.ClearDingtalkOauth() return suo } -// SetDingtalkClientID sets the "dingtalk_client_id" field. -func (suo *SettingUpdateOne) SetDingtalkClientID(s string) *SettingUpdateOne { - suo.mutation.SetDingtalkClientID(s) +// SetCustomOauth sets the "custom_oauth" field. +func (suo *SettingUpdateOne) SetCustomOauth(to *types.CustomOAuth) *SettingUpdateOne { + suo.mutation.SetCustomOauth(to) return suo } -// SetNillableDingtalkClientID sets the "dingtalk_client_id" field if the given value is not nil. -func (suo *SettingUpdateOne) SetNillableDingtalkClientID(s *string) *SettingUpdateOne { - if s != nil { - suo.SetDingtalkClientID(*s) - } - return suo -} - -// ClearDingtalkClientID clears the value of the "dingtalk_client_id" field. -func (suo *SettingUpdateOne) ClearDingtalkClientID() *SettingUpdateOne { - suo.mutation.ClearDingtalkClientID() - return suo -} - -// SetDingtalkClientSecret sets the "dingtalk_client_secret" field. -func (suo *SettingUpdateOne) SetDingtalkClientSecret(s string) *SettingUpdateOne { - suo.mutation.SetDingtalkClientSecret(s) - return suo -} - -// SetNillableDingtalkClientSecret sets the "dingtalk_client_secret" field if the given value is not nil. -func (suo *SettingUpdateOne) SetNillableDingtalkClientSecret(s *string) *SettingUpdateOne { - if s != nil { - suo.SetDingtalkClientSecret(*s) - } - return suo -} - -// ClearDingtalkClientSecret clears the value of the "dingtalk_client_secret" field. -func (suo *SettingUpdateOne) ClearDingtalkClientSecret() *SettingUpdateOne { - suo.mutation.ClearDingtalkClientSecret() +// ClearCustomOauth clears the value of the "custom_oauth" field. +func (suo *SettingUpdateOne) ClearCustomOauth() *SettingUpdateOne { + suo.mutation.ClearCustomOauth() return suo } @@ -464,20 +402,17 @@ func (suo *SettingUpdateOne) sqlSave(ctx context.Context) (_node *Setting, err e if value, ok := suo.mutation.DisablePasswordLogin(); ok { _spec.SetField(setting.FieldDisablePasswordLogin, field.TypeBool, value) } - if value, ok := suo.mutation.EnableDingtalkOauth(); ok { - _spec.SetField(setting.FieldEnableDingtalkOauth, field.TypeBool, value) + if value, ok := suo.mutation.DingtalkOauth(); ok { + _spec.SetField(setting.FieldDingtalkOauth, field.TypeJSON, value) } - if value, ok := suo.mutation.DingtalkClientID(); ok { - _spec.SetField(setting.FieldDingtalkClientID, field.TypeString, value) + if suo.mutation.DingtalkOauthCleared() { + _spec.ClearField(setting.FieldDingtalkOauth, field.TypeJSON) } - if suo.mutation.DingtalkClientIDCleared() { - _spec.ClearField(setting.FieldDingtalkClientID, field.TypeString) + if value, ok := suo.mutation.CustomOauth(); ok { + _spec.SetField(setting.FieldCustomOauth, field.TypeJSON, value) } - if value, ok := suo.mutation.DingtalkClientSecret(); ok { - _spec.SetField(setting.FieldDingtalkClientSecret, field.TypeString, value) - } - if suo.mutation.DingtalkClientSecretCleared() { - _spec.ClearField(setting.FieldDingtalkClientSecret, field.TypeString) + if suo.mutation.CustomOauthCleared() { + _spec.ClearField(setting.FieldCustomOauth, field.TypeJSON) } if value, ok := suo.mutation.CreatedAt(); ok { _spec.SetField(setting.FieldCreatedAt, field.TypeTime, value) diff --git a/backend/docs/swagger.json b/backend/docs/swagger.json index e20e3a3..8f27fa5 100644 --- a/backend/docs/swagger.json +++ b/backend/docs/swagger.json @@ -17,7 +17,7 @@ "application/json" ], "tags": [ - "User" + "Admin" ], "summary": "创建管理员", "operationId": "create-admin", @@ -64,7 +64,7 @@ "application/json" ], "tags": [ - "User" + "Admin" ], "summary": "删除管理员", "operationId": "delete-admin", @@ -109,7 +109,7 @@ "application/json" ], "tags": [ - "User" + "Admin" ], "summary": "获取管理员用户列表", "operationId": "list-admin-user", @@ -165,7 +165,7 @@ "application/json" ], "tags": [ - "User" + "Admin" ], "summary": "管理员登录", "operationId": "admin-login", @@ -212,7 +212,7 @@ "application/json" ], "tags": [ - "User" + "Admin" ], "summary": "获取管理员登录历史", "operationId": "admin-login-history", @@ -268,7 +268,7 @@ "application/json" ], "tags": [ - "User" + "Admin" ], "summary": "获取系统设置", "operationId": "get-setting", @@ -302,7 +302,7 @@ "application/json" ], "tags": [ - "User" + "Admin" ], "summary": "更新系统设置", "operationId": "update-setting", @@ -1604,15 +1604,23 @@ "summary": "用户 OAuth 登录或注册", "operationId": "user-oauth-signup-or-in", "parameters": [ + { + "type": "string", + "description": "邀请码", + "name": "inviate_code", + "in": "query" + }, { "enum": [ "email", - "dingtalk" + "dingtalk", + "custom" ], "type": "string", "x-enum-varnames": [ "UserPlatformEmail", - "UserPlatformDingTalk" + "UserPlatformDingTalk", + "UserPlatformCustom" ], "description": "第三方平台 dingtalk", "name": "platform", @@ -1947,11 +1955,13 @@ "type": "string", "enum": [ "email", - "dingtalk" + "dingtalk", + "custom" ], "x-enum-varnames": [ "UserPlatformEmail", - "UserPlatformDingTalk" + "UserPlatformDingTalk", + "UserPlatformCustom" ] }, "consts.UserStatus": { @@ -2289,6 +2299,75 @@ } } }, + "domain.CustomOAuth": { + "type": "object", + "properties": { + "access_token_url": { + "description": "自定义OAuth访问令牌URL", + "type": "string" + }, + "authorize_url": { + "description": "自定义OAuth授权URL", + "type": "string" + }, + "avatar_field": { + "description": "用户信息回包中的头像URL字段名", + "type": "string" + }, + "client_id": { + "description": "自定义客户端ID", + "type": "string" + }, + "client_secret": { + "description": "自定义客户端密钥", + "type": "string" + }, + "email_field": { + "description": "用户信息回包中的邮箱字段名", + "type": "string" + }, + "enable": { + "description": "自定义OAuth开关", + "type": "boolean" + }, + "id_field": { + "description": "用户信息回包中的ID字段名", + "type": "string" + }, + "name_field": { + "description": "用户信息回包中的用户名字段名", + "type": "string" + }, + "scopes": { + "description": "自定义OAuth Scope列表", + "type": "array", + "items": { + "type": "string" + } + }, + "userinfo_url": { + "description": "自定义OAuth用户信息URL", + "type": "string" + } + } + }, + "domain.DingtalkOAuth": { + "type": "object", + "properties": { + "client_id": { + "description": "钉钉客户端ID", + "type": "string" + }, + "client_secret": { + "description": "钉钉客户端密钥", + "type": "string" + }, + "enable": { + "description": "钉钉OAuth开关", + "type": "boolean" + } + } + }, "domain.IPInfo": { "type": "object", "properties": { @@ -2695,14 +2774,26 @@ "description": "创建时间", "type": "integer" }, + "custom_oauth": { + "description": "自定义OAuth接入", + "allOf": [ + { + "$ref": "#/definitions/domain.CustomOAuth" + } + ] + }, + "dingtalk_oauth": { + "description": "钉钉OAuth接入", + "allOf": [ + { + "$ref": "#/definitions/domain.DingtalkOAuth" + } + ] + }, "disable_password_login": { "description": "是否禁用密码登录", "type": "boolean" }, - "enable_dingtalk_oauth": { - "description": "是否开启钉钉OAuth", - "type": "boolean" - }, "enable_sso": { "description": "是否开启SSO", "type": "boolean" @@ -2893,22 +2984,26 @@ "domain.UpdateSettingReq": { "type": "object", "properties": { - "dingtalk_client_id": { - "description": "钉钉客户端ID", - "type": "string" + "custom_oauth": { + "description": "自定义OAuth配置", + "allOf": [ + { + "$ref": "#/definitions/domain.CustomOAuth" + } + ] }, - "dingtalk_client_secret": { - "description": "钉钉客户端密钥", - "type": "string" + "dingtalk_oauth": { + "description": "钉钉OAuth配置", + "allOf": [ + { + "$ref": "#/definitions/domain.DingtalkOAuth" + } + ] }, "disable_password_login": { "description": "是否禁用密码登录", "type": "boolean" }, - "enable_dingtalk_oauth": { - "description": "是否开启钉钉OAuth", - "type": "boolean" - }, "enable_sso": { "description": "是否开启SSO", "type": "boolean" @@ -2946,6 +3041,10 @@ "domain.User": { "type": "object", "properties": { + "avatar_url": { + "description": "头像URL", + "type": "string" + }, "created_at": { "description": "创建时间", "type": "integer" diff --git a/backend/domain/oauth.go b/backend/domain/oauth.go index 776053c..76c0357 100644 --- a/backend/domain/oauth.go +++ b/backend/domain/oauth.go @@ -13,6 +13,13 @@ type OAuthConfig struct { ClientID string ClientSecret string RedirectURI string + Scope string + AuthorizeURL string + TokenURL string + UserInfoURL string + IDField string + NameField string + AvatarField string } type OAuthUserInfo struct { @@ -27,6 +34,14 @@ type OAuthSignUpOrInReq struct { Platform consts.UserPlatform `json:"platform" query:"platform" validate:"required"` // 第三方平台 dingtalk SessionID string `json:"session_id" query:"session_id"` // 会话ID RedirectURL string `json:"redirect_url" query:"redirect_url"` // 登录成功后跳转的 URL + InviteCode string `json:"inviate_code" query:"inviate_code"` // 邀请码 +} + +func (o OAuthSignUpOrInReq) OAuthKind() consts.OAuthKind { + if o.InviteCode == "" { + return consts.OAuthKindLogin + } + return consts.OAuthKindInvite } type OAuthCallbackReq struct { @@ -43,4 +58,20 @@ type OAuthState struct { SessionID string `json:"session_id"` // 会话ID Platform consts.UserPlatform `json:"platform" query:"platform" validate:"required"` // 第三方平台 dingtalk RedirectURL string `json:"redirect_url" query:"redirect_url"` // 登录成功后跳转的 URL + InviteCode string `json:"inviate_code"` // 邀请码 +} + +type OAuthAccessToken struct { + AccessToken string `json:"access_token"` + RefreshToken string `json:"refresh_token"` + ExpiresIn int64 `json:"expires_in"` + Scope string `json:"scope"` +} + +type GetAccessTokenReq struct { + GrantType string `json:"grant_type"` + ClientID string `json:"client_id"` + ClientSecret string `json:"client_secret"` + Code string `json:"code"` + RedirectURL string `json:"redirect_uri"` } diff --git a/backend/domain/user.go b/backend/domain/user.go index 9854cb8..fcb7b66 100644 --- a/backend/domain/user.go +++ b/backend/domain/user.go @@ -7,6 +7,7 @@ import ( "github.com/chaitin/MonkeyCode/backend/consts" "github.com/chaitin/MonkeyCode/backend/db" + "github.com/chaitin/MonkeyCode/backend/ent/types" "github.com/chaitin/MonkeyCode/backend/pkg/cvt" ) @@ -48,8 +49,9 @@ type UserRepo interface { UserLoginHistory(ctx context.Context, page *web.Pagination) ([]*db.UserLoginHistory, *db.PageInfo, error) AdminLoginHistory(ctx context.Context, page *web.Pagination) ([]*db.AdminLoginHistory, *db.PageInfo, error) GetSetting(ctx context.Context) (*db.Setting, error) - UpdateSetting(ctx context.Context, fn func(*db.SettingUpdateOne)) (*db.Setting, error) - SignUpOrIn(ctx context.Context, platform consts.UserPlatform, req *OAuthUserInfo) (*db.User, error) + UpdateSetting(ctx context.Context, fn func(*db.Setting, *db.SettingUpdateOne)) (*db.Setting, error) + OAuthRegister(ctx context.Context, platform consts.UserPlatform, inviteCode string, req *OAuthUserInfo) (*db.User, error) + OAuthLogin(ctx context.Context, platform consts.UserPlatform, req *OAuthUserInfo) (*db.User, error) } type UpdateUserReq struct { @@ -202,6 +204,7 @@ type User struct { Email string `json:"email"` // 邮箱 TwoStepAuth bool `json:"two_step_auth"` // 是否开启两步验证 Status consts.UserStatus `json:"status"` // 用户状态 active: 正常 locked: 锁定 inactive: 禁用 + AvatarURL string `json:"avatar_url"` // 头像URL CreatedAt int64 `json:"created_at"` // 创建时间 LastActiveAt int64 `json:"last_active_at"` // 最后活跃时间 } @@ -215,6 +218,7 @@ func (u *User) From(e *db.User) *User { u.Username = e.Username u.Email = e.Email u.Status = e.Status + u.AvatarURL = e.AvatarURL u.CreatedAt = e.CreatedAt.Unix() return u @@ -249,21 +253,91 @@ type VSCodeSession struct { } type UpdateSettingReq struct { - EnableSSO *bool `json:"enable_sso"` // 是否开启SSO - ForceTwoFactorAuth *bool `json:"force_two_factor_auth"` // 是否强制两步验证 - DisablePasswordLogin *bool `json:"disable_password_login"` // 是否禁用密码登录 - EnableDingtalkOAuth *bool `json:"enable_dingtalk_oauth"` // 是否开启钉钉OAuth - DingtalkClientID *string `json:"dingtalk_client_id"` // 钉钉客户端ID - DingtalkClientSecret *string `json:"dingtalk_client_secret"` // 钉钉客户端密钥 + EnableSSO *bool `json:"enable_sso"` // 是否开启SSO + ForceTwoFactorAuth *bool `json:"force_two_factor_auth"` // 是否强制两步验证 + DisablePasswordLogin *bool `json:"disable_password_login"` // 是否禁用密码登录 + DingtalkOAuth *DingtalkOAuthReq `json:"dingtalk_oauth"` // 钉钉OAuth配置 + CustomOAuth *CustomOAuthReq `json:"custom_oauth"` // 自定义OAuth配置 +} + +type DingtalkOAuthReq struct { + Enable *bool `json:"enable"` // 钉钉OAuth开关 + ClientID *string `json:"client_id"` // 钉钉客户端ID + ClientSecret *string `json:"client_secret"` // 钉钉客户端密钥 +} + +type DingtalkOAuth struct { + Enable bool `json:"enable"` // 钉钉OAuth开关 + ClientID string `json:"client_id"` // 钉钉客户端ID + ClientSecret string `json:"client_secret"` // 钉钉客户端密钥 +} + +func (d *DingtalkOAuth) From(e *types.DingtalkOAuth) *DingtalkOAuth { + if e == nil { + d.Enable = false + return d + } + + d.Enable = e.Enable + d.ClientID = e.ClientID + return d +} + +type CustomOAuthReq struct { + Enable *bool `json:"enable"` // 自定义OAuth开关 + ClientID *string `json:"client_id"` // 自定义客户端ID + ClientSecret *string `json:"client_secret"` // 自定义客户端密钥 + AuthorizeURL *string `json:"authorize_url"` // 自定义OAuth授权URL + AccessTokenURL *string `json:"access_token_url"` // 自定义OAuth访问令牌URL + UserInfoURL *string `json:"userinfo_url"` // 自定义OAuth用户信息URL + Scopes []string `json:"scopes"` // 自定义OAuth Scope列表 + IDField *string `json:"id_field"` // 用户信息回包中的ID字段名 + NameField *string `json:"name_field"` // 用户信息回包中的用户名字段名 + AvatarField *string `json:"avatar_field"` // 用户信息回包中的头像URL字段名 + EmailField *string `json:"email_field"` // 用户信息回包中的邮箱字段名 +} + +type CustomOAuth struct { + Enable bool `json:"enable"` // 自定义OAuth开关 + ClientID string `json:"client_id"` // 自定义客户端ID + ClientSecret string `json:"client_secret"` // 自定义客户端密钥 + AuthorizeURL string `json:"authorize_url"` // 自定义OAuth授权URL + AccessTokenURL string `json:"access_token_url"` // 自定义OAuth访问令牌URL + UserInfoURL string `json:"userinfo_url"` // 自定义OAuth用户信息URL + Scopes []string `json:"scopes"` // 自定义OAuth Scope列表 + IDField string `json:"id_field"` // 用户信息回包中的ID字段名 + NameField string `json:"name_field"` // 用户信息回包中的用户名字段名 + AvatarField string `json:"avatar_field"` // 用户信息回包中的头像URL字段名 + EmailField string `json:"email_field"` // 用户信息回包中的邮箱字段名 +} + +func (c *CustomOAuth) From(e *types.CustomOAuth) *CustomOAuth { + if e == nil { + c.Enable = false + return c + } + + c.Enable = e.Enable + c.ClientID = e.ClientID + c.AuthorizeURL = e.AuthorizeURL + c.AccessTokenURL = e.AccessTokenURL + c.UserInfoURL = e.UserInfoURL + c.Scopes = e.Scopes + c.IDField = e.IDField + c.NameField = e.NameField + c.AvatarField = e.AvatarField + c.EmailField = e.EmailField + return c } type Setting struct { - EnableSSO bool `json:"enable_sso"` // 是否开启SSO - ForceTwoFactorAuth bool `json:"force_two_factor_auth"` // 是否强制两步验证 - DisablePasswordLogin bool `json:"disable_password_login"` // 是否禁用密码登录 - EnableDingtalkOAuth bool `json:"enable_dingtalk_oauth"` // 是否开启钉钉OAuth - CreatedAt int64 `json:"created_at"` // 创建时间 - UpdatedAt int64 `json:"updated_at"` // 更新时间 + EnableSSO bool `json:"enable_sso"` // 是否开启SSO + ForceTwoFactorAuth bool `json:"force_two_factor_auth"` // 是否强制两步验证 + DisablePasswordLogin bool `json:"disable_password_login"` // 是否禁用密码登录 + DingtalkOAuth DingtalkOAuth `json:"dingtalk_oauth"` // 钉钉OAuth接入 + CustomOAuth CustomOAuth `json:"custom_oauth"` // 自定义OAuth接入 + CreatedAt int64 `json:"created_at"` // 创建时间 + UpdatedAt int64 `json:"updated_at"` // 更新时间 } func (s *Setting) From(e *db.Setting) *Setting { @@ -274,7 +348,8 @@ func (s *Setting) From(e *db.Setting) *Setting { s.EnableSSO = e.EnableSSO s.ForceTwoFactorAuth = e.ForceTwoFactorAuth s.DisablePasswordLogin = e.DisablePasswordLogin - s.EnableDingtalkOAuth = e.EnableDingtalkOauth + s.DingtalkOAuth = *cvt.From(e.DingtalkOauth, &DingtalkOAuth{}) + s.CustomOAuth = *cvt.From(e.CustomOauth, &CustomOAuth{}) s.CreatedAt = e.CreatedAt.Unix() s.UpdatedAt = e.UpdatedAt.Unix() diff --git a/backend/ent/schema/setting.go b/backend/ent/schema/setting.go index d8e85ee..d631634 100644 --- a/backend/ent/schema/setting.go +++ b/backend/ent/schema/setting.go @@ -9,6 +9,8 @@ import ( "entgo.io/ent/schema/field" "github.com/google/uuid" + + "github.com/chaitin/MonkeyCode/backend/ent/types" ) // Setting holds the schema definition for the Setting entity. @@ -31,9 +33,8 @@ func (Setting) Fields() []ent.Field { field.Bool("enable_sso").Default(false), field.Bool("force_two_factor_auth").Default(false), field.Bool("disable_password_login").Default(false), - field.Bool("enable_dingtalk_oauth").Default(false), - field.String("dingtalk_client_id").Optional(), - field.String("dingtalk_client_secret").Optional(), + field.JSON("dingtalk_oauth", &types.DingtalkOAuth{}).Optional(), + field.JSON("custom_oauth", &types.CustomOAuth{}).Optional(), field.Time("created_at").Default(time.Now), field.Time("updated_at").Default(time.Now).UpdateDefault(time.Now), } diff --git a/backend/ent/types/types.go b/backend/ent/types/types.go new file mode 100644 index 0000000..06f6cd5 --- /dev/null +++ b/backend/ent/types/types.go @@ -0,0 +1,21 @@ +package types + +type DingtalkOAuth struct { + Enable bool `json:"enable"` // 钉钉OAuth开关 + ClientID string `json:"client_id"` // 钉钉客户端ID + ClientSecret string `json:"client_secret"` // 钉钉客户端密钥 +} + +type CustomOAuth struct { + Enable bool `json:"enable"` // 自定义OAuth开关 + ClientID string `json:"client_id"` // 自定义客户端ID + ClientSecret string `json:"client_secret"` // 自定义客户端密钥 + AuthorizeURL string `json:"authorize_url"` // 自定义OAuth授权URL + AccessTokenURL string `json:"access_token_url"` // 自定义OAuth访问令牌URL + UserInfoURL string `json:"userinfo_url"` // 自定义OAuth用户信息URL + Scopes []string `json:"scopes"` // 自定义OAuth Scope列表 + IDField string `json:"id_field"` // 用户信息回包中的ID字段名 + NameField string `json:"name_field"` // 用户信息回包中的用户名字段名` + AvatarField string `json:"avatar_field"` // 用户信息回包中的头像URL字段名` + EmailField string `json:"email_field"` // 用户信息回包中的邮箱字段名 +} diff --git a/backend/errcode/errcode.go b/backend/errcode/errcode.go index a825b9c..23f0c3d 100644 --- a/backend/errcode/errcode.go +++ b/backend/errcode/errcode.go @@ -10,10 +10,14 @@ import ( var LocalFS embed.FS var ( - ErrPermission = web.NewBadRequestErr("err-permission") - ErrUserNotFound = web.NewBadRequestErr("err-user-not-found") - ErrPassword = web.NewBadRequestErr("err-password") - ErrInviteCodeInvalid = web.NewBadRequestErr("err-invite-code-invalid") - ErrEmailInvalid = web.NewBadRequestErr("err-email-invalid") - ErrOAuthStateInvalid = web.NewBadRequestErr("err-oauth-state-invalid") + ErrPermission = web.NewBadRequestErr("err-permission") + ErrUserNotFound = web.NewBadRequestErr("err-user-not-found") + ErrPassword = web.NewBadRequestErr("err-password") + ErrInviteCodeInvalid = web.NewBadRequestErr("err-invite-code-invalid") + ErrEmailInvalid = web.NewBadRequestErr("err-email-invalid") + ErrOAuthStateInvalid = web.NewBadRequestErr("err-oauth-state-invalid") + ErrUnsupportedPlatform = web.NewBadRequestErr("err-unsupported-platform") + ErrNotInvited = web.NewBadRequestErr("err-not-invited") + ErrDingtalkNotEnabled = web.NewBadRequestErr("err-dingtalk-not-enabled") + ErrCustomNotEnabled = web.NewBadRequestErr("err-custom-not-enabled") ) diff --git a/backend/errcode/locale.zh.toml b/backend/errcode/locale.zh.toml index 5423c71..6000dd0 100644 --- a/backend/errcode/locale.zh.toml +++ b/backend/errcode/locale.zh.toml @@ -14,4 +14,16 @@ other = "邀请码无效" other = "邮箱格式错误" [err-oauth-state-invalid] -other = "OAuth 状态无效" \ No newline at end of file +other = "OAuth 状态无效" + +[err-unsupported-platform] +other = "不支持的平台" + +[err-not-invited] +other = "未被邀请" + +[err-dingtalk-not-enabled] +other = "钉钉未启用" + +[err-custom-not-enabled] +other = "OAuth未启用" \ No newline at end of file diff --git a/backend/internal/user/handler/v1/user.go b/backend/internal/user/handler/v1/user.go index 5f05b44..e532b0c 100644 --- a/backend/internal/user/handler/v1/user.go +++ b/backend/internal/user/handler/v1/user.go @@ -58,6 +58,7 @@ func NewUserHandler( admin.GET("/login-history", web.BaseHandler(u.AdminLoginHistory, web.WithPage())) admin.DELETE("/delete", web.BaseHandler(u.DeleteAdmin)) + // user g := w.Group("/api/v1/user") g.GET("/oauth/signup-or-in", web.BindHandler(u.OAuthSignUpOrIn)) g.GET("/oauth/callback", web.BindHandler(u.OAuthCallback)) @@ -165,7 +166,7 @@ func (h *UserHandler) Delete(c *web.Context) error { // DeleteAdmin 删除管理员 // -// @Tags User +// @Tags Admin // @Summary 删除管理员 // @Description 删除管理员 // @ID delete-admin @@ -184,7 +185,7 @@ func (h *UserHandler) DeleteAdmin(c *web.Context) error { // AdminLogin 管理员登录 // -// @Tags User +// @Tags Admin // @Summary 管理员登录 // @Description 管理员登录 // @ID admin-login @@ -285,7 +286,7 @@ func (h *UserHandler) Register(c *web.Context, req domain.RegisterReq) error { // CreateAdmin 创建管理员 // -// @Tags User +// @Tags Admin // @Summary 创建管理员 // @Description 创建管理员 // @ID create-admin @@ -308,7 +309,7 @@ func (h *UserHandler) CreateAdmin(c *web.Context, req domain.CreateAdminReq) err // AdminList 获取管理员用户列表 // -// @Tags User +// @Tags Admin // @Summary 获取管理员用户列表 // @Description 获取管理员用户列表 // @ID list-admin-user @@ -327,7 +328,7 @@ func (h *UserHandler) AdminList(c *web.Context) error { // AdminLoginHistory 获取管理员登录历史 // -// @Tags User +// @Tags Admin // @Summary 获取管理员登录历史 // @Description 获取管理员登录历史 // @ID admin-login-history @@ -346,7 +347,7 @@ func (h *UserHandler) AdminLoginHistory(c *web.Context) error { // GetSetting 获取系统设置 // -// @Tags User +// @Tags Admin // @Summary 获取系统设置 // @Description 获取系统设置 // @ID get-setting @@ -364,7 +365,7 @@ func (h *UserHandler) GetSetting(c *web.Context) error { // UpdateSetting 更新系统设置 // -// @Tags User +// @Tags Admin // @Summary 更新系统设置 // @Description 更新系统设置 // @ID update-setting diff --git a/backend/internal/user/repo/user.go b/backend/internal/user/repo/user.go index 2706e98..b8a85d9 100644 --- a/backend/internal/user/repo/user.go +++ b/backend/internal/user/repo/user.go @@ -3,6 +3,7 @@ package repo import ( "context" "errors" + "fmt" "time" "github.com/google/uuid" @@ -17,6 +18,7 @@ import ( "github.com/chaitin/MonkeyCode/backend/db/user" "github.com/chaitin/MonkeyCode/backend/db/useridentity" "github.com/chaitin/MonkeyCode/backend/domain" + "github.com/chaitin/MonkeyCode/backend/errcode" "github.com/chaitin/MonkeyCode/backend/pkg/entx" ) @@ -68,32 +70,40 @@ func (r *UserRepo) GetByName(ctx context.Context, username string) (*db.User, er func (r *UserRepo) ValidateInviteCode(ctx context.Context, code string) (*db.InviteCode, error) { var res *db.InviteCode err := entx.WithTx(ctx, r.db, func(tx *db.Tx) error { - ic, err := tx.InviteCode.Query().Where(invitecode.Code(code)).Only(ctx) + ic, err := r.innerValidateInviteCode(ctx, tx, code) if err != nil { return err } - - if ic.ExpiredAt.Before(time.Now()) { - return errors.New("invite code has expired") - } - if ic.Status == consts.InviteCodeStatusUsed { - return errors.New("invite code has been used") - } - - ic, err = tx.InviteCode.UpdateOneID(ic.ID). - SetStatus(consts.InviteCodeStatusUsed). - Save(ctx) - - if err != nil { - return err - } - res = ic return nil }) return res, err } +func (r *UserRepo) innerValidateInviteCode(ctx context.Context, tx *db.Tx, code string) (*db.InviteCode, error) { + ic, err := tx.InviteCode.Query().Where(invitecode.Code(code)).Only(ctx) + if err != nil { + return nil, err + } + + if ic.ExpiredAt.Before(time.Now()) { + return nil, errors.New("invite code has expired") + } + if ic.Status == consts.InviteCodeStatusUsed { + return nil, errors.New("invite code has been used") + } + + ic, err = tx.InviteCode.UpdateOneID(ic.ID). + SetStatus(consts.InviteCodeStatusUsed). + Save(ctx) + + if err != nil { + return nil, err + } + + return ic, nil +} + func (r *UserRepo) CreateUser(ctx context.Context, user *db.User) (*db.User, error) { return r.db.User.Create(). SetUsername(user.Username). @@ -184,18 +194,23 @@ func (r *UserRepo) GetSetting(ctx context.Context) (*db.Setting, error) { return s, nil } -func (r *UserRepo) UpdateSetting(ctx context.Context, fn func(*db.SettingUpdateOne)) (*db.Setting, error) { - var s *db.Setting +func (r *UserRepo) UpdateSetting(ctx context.Context, fn func(*db.Setting, *db.SettingUpdateOne)) (*db.Setting, error) { + var res *db.Setting err := entx.WithTx(ctx, r.db, func(tx *db.Tx) error { s, err := tx.Setting.Query().First(ctx) if err != nil { return err } up := tx.Setting.UpdateOneID(s.ID) - fn(up) - return up.Exec(ctx) + fn(s, up) + s, err = up.Save(ctx) + if err != nil { + return err + } + res = s + return nil }) - return s, err + return res, err } func (r *UserRepo) Update(ctx context.Context, id string, fn func(*db.UserUpdateOne) error) (*db.User, error) { @@ -241,16 +256,19 @@ func (r *UserRepo) DeleteAdmin(ctx context.Context, id string) error { return r.db.Admin.DeleteOne(admin).Exec(ctx) } -func (r *UserRepo) SignUpOrIn(ctx context.Context, platform consts.UserPlatform, req *domain.OAuthUserInfo) (*db.User, error) { +func (r *UserRepo) OAuthRegister(ctx context.Context, platform consts.UserPlatform, inviteCode string, req *domain.OAuthUserInfo) (*db.User, error) { var u *db.User err := entx.WithTx(ctx, r.db, func(tx *db.Tx) error { - ui, err := tx.UserIdentity.Query(). + if _, err := r.innerValidateInviteCode(ctx, tx, inviteCode); err != nil { + return errcode.ErrInviteCodeInvalid.Wrap(err) + } + + _, err := tx.UserIdentity.Query(). WithUser(). Where(useridentity.Platform(platform), useridentity.IdentityID(req.ID)). First(ctx) if err == nil { - u = ui.Edges.User - return nil + return fmt.Errorf("user already exists for platform %s and identity ID %s", platform, req.ID) } if !db.IsNotFound(err) { return err @@ -282,3 +300,15 @@ func (r *UserRepo) SignUpOrIn(ctx context.Context, platform consts.UserPlatform, }) return u, err } + +func (r *UserRepo) OAuthLogin(ctx context.Context, platform consts.UserPlatform, req *domain.OAuthUserInfo) (*db.User, error) { + ui, err := r.db.UserIdentity.Query(). + WithUser(). + Where(useridentity.Platform(platform), useridentity.IdentityID(req.ID)). + Where(useridentity.HasUser()). + Only(ctx) + if err != nil { + return nil, errcode.ErrNotInvited.Wrap(err) + } + return ui.Edges.User, nil +} diff --git a/backend/internal/user/usecase/user.go b/backend/internal/user/usecase/user.go index 3d2f957..a39c8f8 100644 --- a/backend/internal/user/usecase/user.go +++ b/backend/internal/user/usecase/user.go @@ -6,6 +6,7 @@ import ( "fmt" "log/slog" "net/url" + "strings" "time" "github.com/google/uuid" @@ -14,6 +15,7 @@ import ( "github.com/GoYoko/web" "github.com/chaitin/MonkeyCode/backend/consts" + "github.com/chaitin/MonkeyCode/backend/ent/types" "github.com/chaitin/MonkeyCode/backend/pkg/cvt" "github.com/chaitin/MonkeyCode/backend/pkg/oauth" @@ -268,24 +270,65 @@ func (u *UserUsecase) GetSetting(ctx context.Context) (*domain.Setting, error) { } func (u *UserUsecase) UpdateSetting(ctx context.Context, req *domain.UpdateSettingReq) (*domain.Setting, error) { - s, err := u.repo.UpdateSetting(ctx, func(s *db.SettingUpdateOne) { + s, err := u.repo.UpdateSetting(ctx, func(old *db.Setting, up *db.SettingUpdateOne) { if req.EnableSSO != nil { - s.SetEnableSSO(*req.EnableSSO) + up.SetEnableSSO(*req.EnableSSO) } if req.ForceTwoFactorAuth != nil { - s.SetForceTwoFactorAuth(*req.ForceTwoFactorAuth) + up.SetForceTwoFactorAuth(*req.ForceTwoFactorAuth) } if req.DisablePasswordLogin != nil { - s.SetDisablePasswordLogin(*req.DisablePasswordLogin) + up.SetDisablePasswordLogin(*req.DisablePasswordLogin) } - if req.EnableDingtalkOAuth != nil { - s.SetEnableDingtalkOauth(*req.EnableDingtalkOAuth) + if req.DingtalkOAuth != nil { + dingtalk := cvt.NilWithDefault(old.DingtalkOauth, &types.DingtalkOAuth{}) + if req.DingtalkOAuth.Enable != nil { + dingtalk.Enable = *req.DingtalkOAuth.Enable + } + if req.DingtalkOAuth.ClientID != nil { + dingtalk.ClientID = *req.DingtalkOAuth.ClientID + } + if req.DingtalkOAuth.ClientSecret != nil { + dingtalk.ClientSecret = *req.DingtalkOAuth.ClientSecret + } + up.SetDingtalkOauth(dingtalk) } - if req.DingtalkClientID != nil { - s.SetDingtalkClientID(*req.DingtalkClientID) - } - if req.DingtalkClientSecret != nil { - s.SetDingtalkClientSecret(*req.DingtalkClientSecret) + if req.CustomOAuth != nil { + custom := cvt.NilWithDefault(old.CustomOauth, &types.CustomOAuth{}) + if req.CustomOAuth.Enable != nil { + custom.Enable = *req.CustomOAuth.Enable + } + if req.CustomOAuth.ClientID != nil { + custom.ClientID = *req.CustomOAuth.ClientID + } + if req.CustomOAuth.ClientSecret != nil { + custom.ClientSecret = *req.CustomOAuth.ClientSecret + } + if req.CustomOAuth.AuthorizeURL != nil { + custom.AuthorizeURL = *req.CustomOAuth.AuthorizeURL + } + if req.CustomOAuth.AccessTokenURL != nil { + custom.AccessTokenURL = *req.CustomOAuth.AccessTokenURL + } + if req.CustomOAuth.UserInfoURL != nil { + custom.UserInfoURL = *req.CustomOAuth.UserInfoURL + } + if req.CustomOAuth.Scopes != nil { + custom.Scopes = req.CustomOAuth.Scopes + } + if req.CustomOAuth.IDField != nil { + custom.IDField = *req.CustomOAuth.IDField + } + if req.CustomOAuth.NameField != nil { + custom.NameField = *req.CustomOAuth.NameField + } + if req.CustomOAuth.AvatarField != nil { + custom.AvatarField = *req.CustomOAuth.AvatarField + } + if req.CustomOAuth.EmailField != nil { + custom.EmailField = *req.CustomOAuth.EmailField + } + up.SetCustomOauth(custom) } }) if err != nil { @@ -335,8 +378,26 @@ func (u *UserUsecase) OAuthSignUpOrIn(ctx context.Context, req *domain.OAuthSign switch req.Platform { case consts.UserPlatformDingTalk: - cfg.ClientID = setting.DingtalkClientID - cfg.ClientSecret = setting.DingtalkClientSecret + if setting.DingtalkOauth == nil || !setting.DingtalkOauth.Enable { + return nil, errcode.ErrDingtalkNotEnabled + } + cfg.ClientID = setting.DingtalkOauth.ClientID + cfg.ClientSecret = setting.DingtalkOauth.ClientSecret + case consts.UserPlatformCustom: + if setting.CustomOauth == nil || !setting.CustomOauth.Enable { + return nil, errcode.ErrCustomNotEnabled + } + cfg.ClientID = setting.CustomOauth.ClientID + cfg.ClientSecret = setting.CustomOauth.ClientSecret + cfg.AuthorizeURL = setting.CustomOauth.AuthorizeURL + cfg.Scope = strings.Join(setting.CustomOauth.Scopes, " ") + cfg.TokenURL = setting.CustomOauth.AccessTokenURL + cfg.UserInfoURL = setting.CustomOauth.UserInfoURL + cfg.IDField = setting.CustomOauth.IDField + cfg.NameField = setting.CustomOauth.NameField + cfg.AvatarField = setting.CustomOauth.AvatarField + default: + return nil, errcode.ErrUnsupportedPlatform } oauth, err := oauth.NewOAuther(cfg) @@ -347,9 +408,10 @@ func (u *UserUsecase) OAuthSignUpOrIn(ctx context.Context, req *domain.OAuthSign session := &domain.OAuthState{ SessionID: req.SessionID, - Kind: consts.OAuthKindSignUpOrIn, + Kind: req.OAuthKind(), Platform: req.Platform, RedirectURL: req.RedirectURL, + InviteCode: req.InviteCode, } b, err := json.Marshal(session) if err != nil { @@ -375,38 +437,74 @@ func (u *UserUsecase) OAuthCallback(ctx context.Context, req *domain.OAuthCallba } switch session.Kind { - case consts.OAuthKindSignUpOrIn: - return u.OAuthSignUpOrInCallback(ctx, req, &session) + case consts.OAuthKindInvite: + return u.WithOAuthCallback(ctx, req, &session, func(ctx context.Context, s *domain.OAuthState, oui *domain.OAuthUserInfo) (*db.User, error) { + return u.repo.OAuthRegister(ctx, s.Platform, s.InviteCode, oui) + }) + + case consts.OAuthKindLogin: + return u.WithOAuthCallback(ctx, req, &session, func(ctx context.Context, s *domain.OAuthState, oui *domain.OAuthUserInfo) (*db.User, error) { + return u.repo.OAuthLogin(ctx, s.Platform, oui) + }) default: return "", errcode.ErrOAuthStateInvalid } } -func (u *UserUsecase) OAuthSignUpOrInCallback(ctx context.Context, req *domain.OAuthCallbackReq, session *domain.OAuthState) (string, error) { +func (u *UserUsecase) FetchUserInfo(ctx context.Context, req *domain.OAuthCallbackReq, session *domain.OAuthState) (*domain.OAuthUserInfo, error) { setting, err := u.repo.GetSetting(ctx) if err != nil { - return "", err + return nil, err } cfg := domain.OAuthConfig{ Debug: u.cfg.Debug, Platform: session.Platform, } + switch session.Platform { case consts.UserPlatformDingTalk: - cfg.ClientID = setting.DingtalkClientID - cfg.ClientSecret = setting.DingtalkClientSecret + if setting.DingtalkOauth == nil || !setting.DingtalkOauth.Enable { + return nil, errcode.ErrDingtalkNotEnabled + } + cfg.ClientID = setting.DingtalkOauth.ClientID + cfg.ClientSecret = setting.DingtalkOauth.ClientSecret + case consts.UserPlatformCustom: + if setting.CustomOauth == nil || !setting.CustomOauth.Enable { + return nil, errcode.ErrCustomNotEnabled + } + cfg.ClientID = setting.CustomOauth.ClientID + cfg.ClientSecret = setting.CustomOauth.ClientSecret + cfg.AuthorizeURL = setting.CustomOauth.AuthorizeURL + cfg.Scope = strings.Join(setting.CustomOauth.Scopes, " ") + cfg.TokenURL = setting.CustomOauth.AccessTokenURL + cfg.UserInfoURL = setting.CustomOauth.UserInfoURL + cfg.IDField = setting.CustomOauth.IDField + cfg.NameField = setting.CustomOauth.NameField + cfg.AvatarField = setting.CustomOauth.AvatarField + default: + return nil, errcode.ErrUnsupportedPlatform } oauth, err := oauth.NewOAuther(cfg) if err != nil { - return "", err + return nil, err } userInfo, err := oauth.GetUserInfo(req.Code) + if err != nil { + return nil, err + } + return userInfo, nil +} + +type OAuthUserRepoHandle func(context.Context, *domain.OAuthState, *domain.OAuthUserInfo) (*db.User, error) + +func (u *UserUsecase) WithOAuthCallback(ctx context.Context, req *domain.OAuthCallbackReq, session *domain.OAuthState, handle OAuthUserRepoHandle) (string, error) { + info, err := u.FetchUserInfo(ctx, req, session) if err != nil { return "", err } - user, err := u.repo.SignUpOrIn(ctx, session.Platform, userInfo) + user, err := handle(ctx, session, info) if err != nil { return "", err } diff --git a/backend/migration/000006_alter_settings_table.down.sql b/backend/migration/000006_alter_settings_table.down.sql new file mode 100644 index 0000000..e69de29 diff --git a/backend/migration/000006_alter_settings_table.up.sql b/backend/migration/000006_alter_settings_table.up.sql new file mode 100644 index 0000000..09c2289 --- /dev/null +++ b/backend/migration/000006_alter_settings_table.up.sql @@ -0,0 +1,6 @@ +ALTER TABLE settings DROP COLUMN IF EXISTS enable_dingtalk_oauth; +ALTER TABLE settings DROP COLUMN IF EXISTS dingtalk_client_id; +ALTER TABLE settings DROP COLUMN IF EXISTS dingtalk_client_secret; + +ALTER TABLE settings ADD COLUMN dingtalk_oauth JSONB DEFAULT '{}'::JSONB; +ALTER TABLE settings ADD COLUMN custom_oauth JSONB DEFAULT '{}'::JSONB; \ No newline at end of file diff --git a/backend/pkg/oauth/custom.go b/backend/pkg/oauth/custom.go new file mode 100644 index 0000000..229e404 --- /dev/null +++ b/backend/pkg/oauth/custom.go @@ -0,0 +1,96 @@ +package oauth + +import ( + "fmt" + "net/url" + "strings" + "time" + + "github.com/google/uuid" + + "github.com/chaitin/MonkeyCode/backend/domain" + "github.com/chaitin/MonkeyCode/backend/pkg/request" +) + +type CustomOAuth struct { + cfg domain.OAuthConfig +} + +func NewCustomOAuth(config domain.OAuthConfig) domain.OAuther { + c := &CustomOAuth{ + cfg: config, + } + + return c +} + +// GetAuthorizeURL implements domain.OAuther. +func (c *CustomOAuth) GetAuthorizeURL() (string, string) { + state := uuid.NewString() + url := fmt.Sprintf("%s?response_type=code&client_id=%s&state=%s&redirect_uri=%s", c.cfg.AuthorizeURL, c.cfg.ClientID, state, c.cfg.RedirectURI) + return state, url +} + +// GetUserInfo implements domain.OAuther. +func (c *CustomOAuth) GetUserInfo(code string) (*domain.OAuthUserInfo, error) { + accessToken, err := c.getAccessToken(code) + if err != nil { + return nil, err + } + info, err := c.getUserInfo(accessToken) + if err != nil { + return nil, err + } + return &domain.OAuthUserInfo{ + ID: fmt.Sprint(info[c.cfg.IDField]), + AvatarURL: fmt.Sprint(info[c.cfg.AvatarField]), + Name: fmt.Sprint(info[c.cfg.NameField]), + }, nil +} + +func (c *CustomOAuth) getAccessToken(code string) (string, error) { + u, err := url.Parse(c.cfg.TokenURL) + if err != nil { + return "", fmt.Errorf("[CustomOAuth] 无效的Token URL: %w", err) + } + client := request.NewClient(u.Scheme, u.Host, 30*time.Second) + client.SetDebug(c.cfg.Debug) + req := domain.GetAccessTokenReq{ + GrantType: "authorization_code", + Code: code, + RedirectURL: c.cfg.RedirectURI, + ClientID: c.cfg.ClientID, + ClientSecret: c.cfg.ClientSecret, + } + resp, err := request.Post[domain.OAuthAccessToken](client, u.Path, req, request.WithHeader(request.Header{ + "Accept": "application/json", + })) + if err != nil { + return "", fmt.Errorf("[CustomOAuth] 获取access token失败: %w", err) + } + return resp.AccessToken, nil +} + +type UserInfo map[string]any + +func (c *CustomOAuth) getUserInfo(accessToken string) (UserInfo, error) { + u, err := url.Parse(c.cfg.UserInfoURL) + if err != nil { + return nil, fmt.Errorf("[CustomOAuth] 无效的UseInfo URL: %w", err) + } + client := request.NewClient(u.Scheme, u.Host, 30*time.Second) + client.SetDebug(c.cfg.Debug) + h := request.Header{ + "Authorization": fmt.Sprintf("Bearer %s", accessToken), + } + if strings.Contains(c.cfg.UserInfoURL, "github") { + h["Accept"] = "application/vnd.github.v3+json" + } + + resp, err := request.Get[UserInfo](client, u.Path, request.WithHeader(h)) + if err != nil { + return nil, fmt.Errorf("[CustomOAuth] 获取用户信息失败: %w", err) + } + + return *resp, nil +} diff --git a/backend/pkg/oauth/oauth.go b/backend/pkg/oauth/oauth.go index 16c83b4..6977849 100644 --- a/backend/pkg/oauth/oauth.go +++ b/backend/pkg/oauth/oauth.go @@ -11,6 +11,8 @@ func NewOAuther(config domain.OAuthConfig) (domain.OAuther, error) { switch config.Platform { case consts.UserPlatformDingTalk: return NewDingTalk(config), nil + case consts.UserPlatformCustom: + return NewCustomOAuth(config), nil default: return nil, fmt.Errorf("unsupported platform: %s", config.Platform) }