Compare commits

...

3 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
818fff6252 Use constants for autoLinkPolicy and fix logic
Co-authored-by: hsluoyz <3787410+hsluoyz@users.noreply.github.com>
2026-02-12 08:00:38 +00:00
copilot-swe-agent[bot]
a4229e6cde Add autoLinkPolicy field to backend and frontend
Co-authored-by: hsluoyz <3787410+hsluoyz@users.noreply.github.com>
2026-02-12 07:57:26 +00:00
copilot-swe-agent[bot]
7474d38170 Initial plan 2026-02-12 07:50:56 +00:00
6 changed files with 73 additions and 17 deletions

View File

@@ -824,13 +824,20 @@ 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
autoLinkPolicy := providerItem.AutoLinkPolicy
if autoLinkPolicy == "" {
autoLinkPolicy = object.AutoLinkPolicyAllow // Default to allow for backward compatibility
}
if autoLinkPolicy != object.AutoLinkPolicyDisabled && application.EnableLinkWithEmail {
if autoLinkPolicy == object.AutoLinkPolicyAllow || autoLinkPolicy == object.AutoLinkPolicyEmailOnly {
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
}
}
}
@@ -848,11 +855,13 @@ func (c *ApiController) Login() {
// 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
if autoLinkPolicy == object.AutoLinkPolicyAllow || autoLinkPolicy == object.AutoLinkPolicyUsernameOnly {
if user == nil && userInfo.Username != "" {
user, err = object.GetUserByFields(application.Organization, userInfo.Username)
if err != nil {
c.ResponseError(err.Error())
return
}
}
}

View File

@@ -28,6 +28,15 @@ import (
"github.com/xorm-io/core"
)
// Auto-link policy constants
const (
AutoLinkPolicyAllow = "Allow"
AutoLinkPolicyUsernameOnly = "Username only"
AutoLinkPolicyEmailOnly = "Email only"
AutoLinkPolicyDisabled = "Disabled"
)
type Provider struct {
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
Name string `xorm:"varchar(100) notnull pk unique" json:"name"`
@@ -78,6 +87,8 @@ type Provider struct {
ProviderUrl string `xorm:"varchar(200)" json:"providerUrl"`
EnableProxy bool `json:"enableProxy"`
EnablePkce bool `json:"enablePkce"`
AutoLinkPolicy string `xorm:"varchar(100)" json:"autoLinkPolicy"`
}
func GetMaskedProvider(provider *Provider, isMaskEnabled bool) *Provider {

View File

@@ -26,6 +26,8 @@ type ProviderItem struct {
SignupGroup string `json:"signupGroup"`
Rule string `json:"rule"`
Provider *Provider `json:"provider"`
AutoLinkPolicy string `json:"autoLinkPolicy"`
}
func (application *Application) GetProviderItem(providerName string) *ProviderItem {

View File

@@ -564,7 +564,7 @@
"Physical": "Physical",
"Show all": "Show all",
"Virtual": "Virtual",
"You need to delete all subgroups first. You can view the subgroups in the left group tree of the [Organizations] -\u003e [Groups] page": "You need to delete all subgroups first. You can view the subgroups in the left group tree of the [Organizations] -\u003e [Groups] page"
"You need to delete all subgroups first. You can view the subgroups in the left group tree of the [Organizations] -> [Groups] page": "You need to delete all subgroups first. You can view the subgroups in the left group tree of the [Organizations] -> [Groups] page"
},
"home": {
"New users past 30 days": "New users past 30 days",
@@ -972,6 +972,11 @@
"Can signin": "Can signin",
"Can signup": "Can signup",
"Can unlink": "Can unlink",
"Allow": "Allow",
"Auto link policy": "Auto link policy",
"Disabled": "Disabled",
"Email only": "Email only",
"Username only": "Username only",
"Category": "Category",
"Category - Tooltip": "Identifier for categorizing and grouping items or content, facilitating filtering and management",
"Channel No.": "Channel No.",
@@ -1493,4 +1498,4 @@
"Single org only - Tooltip": "Triggered only in the organization that the webhook belongs to",
"Value": "Value"
}
}
}

View File

@@ -564,7 +564,7 @@
"Physical": "实体组",
"Show all": "显示全部",
"Virtual": "虚拟组",
"You need to delete all subgroups first. You can view the subgroups in the left group tree of the [Organizations] -\u003e [Groups] page": "您需要先删除所有子组。您可以在 [组织] -\u003e [群组] 页面左侧的群组树中查看子组"
"You need to delete all subgroups first. You can view the subgroups in the left group tree of the [Organizations] -> [Groups] page": "您需要先删除所有子组。您可以在 [组织] -> [群组] 页面左侧的群组树中查看子组"
},
"home": {
"New users past 30 days": "过去 30 天新增的用户",
@@ -972,6 +972,11 @@
"Can signin": "可用于登录",
"Can signup": "可用于注册",
"Can unlink": "可解绑定",
"Allow": "允许",
"Auto link policy": "自动关联策略",
"Disabled": "禁用",
"Email only": "仅邮箱",
"Username only": "仅用户名",
"Category": "分类",
"Category - Tooltip": "用于对项目或内容进行归类分组的标识",
"Channel No.": "Channel号码",
@@ -1493,4 +1498,4 @@
"Single org only - Tooltip": "仅在Webhook所在组织触发",
"Value": "值"
}
}
}

View File

@@ -44,7 +44,7 @@ class ProviderTable extends React.Component {
}
addRow(table) {
const row = {name: Setting.getNewRowNameForTable(table, "Please select a provider"), canSignUp: true, canSignIn: true, canUnlink: true, prompted: false, signupGroup: "", rule: "None"};
const row = {name: Setting.getNewRowNameForTable(table, "Please select a provider"), canSignUp: true, canSignIn: true, canUnlink: true, prompted: false, signupGroup: "", rule: "None", autoLinkPolicy: "Allow"};
if (table === undefined) {
table = [];
}
@@ -292,6 +292,30 @@ class ProviderTable extends React.Component {
}
},
},
{
title: i18next.t("provider:Auto link policy"),
dataIndex: "autoLinkPolicy",
key: "autoLinkPolicy",
width: "160px",
render: (text, record, index) => {
if (!["OAuth", "Web3", "SAML"].includes(record.provider?.category)) {
return null;
}
return (
<Select virtual={false} style={{width: "100%"}}
value={text || "Allow"}
onChange={value => {
this.updateField(table, index, "autoLinkPolicy", value);
}} >
<Option key="Allow" value="Allow">{i18next.t("provider:Allow")}</Option>
<Option key="Username only" value="Username only">{i18next.t("provider:Username only")}</Option>
<Option key="Email only" value="Email only">{i18next.t("provider:Email only")}</Option>
<Option key="Disabled" value="Disabled">{i18next.t("provider:Disabled")}</Option>
</Select>
);
},
},
{
title: i18next.t("general:Action"),
key: "action",