feat: allow user to set binding rule in provider items (#5224)

This commit is contained in:
DacongDA
2026-03-07 22:20:48 +08:00
committed by GitHub
parent 74e6b73e7b
commit dbc2a676ba
3 changed files with 79 additions and 30 deletions

View File

@@ -456,6 +456,55 @@ func checkMfaEnable(c *ApiController, user *object.User, organization *object.Or
return false
}
func getExistUserByBindingRule(providerItem *object.ProviderItem, application *object.Application, userInfo *idp.UserInfo) (user *object.User, err error) {
if providerItem.BindingRule == nil {
providerItem.BindingRule = &[]string{"Email", "Phone", "Name"}
}
if len(*providerItem.BindingRule) == 0 {
return nil, nil
}
for _, rule := range *providerItem.BindingRule {
// Find existing user with Email
if rule == "Email" {
user, err = object.GetUserByField(application.Organization, "email", userInfo.Email)
if err != nil {
return nil, err
}
if user != nil {
return user, nil
}
}
// Find existing user with phone number
if rule == "Phone" {
user, err = object.GetUserByField(application.Organization, "phone", userInfo.Phone)
if err != nil {
return nil, err
}
if user != nil {
return user, nil
}
}
// Try to find existing user by username (case-insensitive)
// This allows OAuth providers (e.g., Wecom) to automatically associate with
// existing users when usernames match, particularly useful for enterprise
// scenarios where signup is disabled and users already exist in Casdoor
if rule == "Name" {
user, err = object.GetUserByFields(application.Organization, userInfo.Username)
if err != nil {
return nil, err
}
if user != nil {
return user, nil
}
}
}
return user, nil
}
// Login ...
// @Title Login
// @Tag Login API
@@ -847,36 +896,10 @@ func (c *ApiController) Login() {
c.Ctx.Input.SetParam("recordUserId", user.GetId())
} else if provider.Category == "OAuth" || provider.Category == "Web3" || provider.Category == "SAML" {
// Sign up via OAuth
if application.EnableLinkWithEmail {
if userInfo.Email != "" {
// Find existing user with Email
user, err = object.GetUserByField(application.Organization, "email", userInfo.Email)
if err != nil {
c.ResponseError(err.Error())
return
}
}
if user == nil && userInfo.Phone != "" {
// Find existing user with phone number
user, err = object.GetUserByField(application.Organization, "phone", userInfo.Phone)
if err != nil {
c.ResponseError(err.Error())
return
}
}
}
// Try to find existing user by username (case-insensitive)
// This allows OAuth providers (e.g., Wecom) to automatically associate with
// existing users when usernames match, particularly useful for enterprise
// scenarios where signup is disabled and users already exist in Casdoor
if user == nil && userInfo.Username != "" {
user, err = object.GetUserByFields(application.Organization, userInfo.Username)
if err != nil {
c.ResponseError(err.Error())
return
}
user, err = getExistUserByBindingRule(providerItem, application, userInfo)
if err != nil {
c.ResponseError(err.Error())
return
}
if user == nil {

View File

@@ -21,6 +21,7 @@ type ProviderItem struct {
CanSignUp bool `json:"canSignUp"`
CanSignIn bool `json:"canSignIn"`
CanUnlink bool `json:"canUnlink"`
BindingRule *[]string `json:"bindingRule"`
CountryCodes []string `json:"countryCodes"`
Prompted bool `json:"prompted"`
SignupGroup string `json:"signupGroup"`

View File

@@ -218,6 +218,31 @@ class ProviderTable extends React.Component {
);
},
},
{
title: i18next.t("provider:Binding rule"),
dataIndex: "bindingRule",
key: "bindingRule",
width: "120px",
render: (text, record, index) => {
if (!["OAuth", "Web3", "SAML"].includes(record.provider?.category)) {
return null;
}
return (
<Select virtual={false} style={{width: "100%"}}
value={text || ["Email", "Phone", "Name"]}
mode={"multiple"}
onChange={value => {
text = Array.isArray(text) ? text : [];
this.updateField(table, index, "bindingRule", value);
}} >
<Option key="Email" value="Email">{i18next.t("general:Email")}</Option>
<Option key="Name" value="Name">{i18next.t("general:Name")}</Option>
<Option key="Phone" value="Phone">{i18next.t("general:Phone")}</Option>
</Select>
);
},
},
{
title: i18next.t("provider:Prompted"),
dataIndex: "prompted",