mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-29 00:10:31 +00:00
Merge pull request #2492 from DaydreamCoding/feat/dingtalk-login
feat(dingtalk): 钉钉 OAuth 登录接入 + internal_only 用户属性同步
This commit is contained in:
@@ -2333,6 +2333,294 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- DingTalk Connect OAuth 登录 -->
|
||||
<div class="card">
|
||||
<div
|
||||
class="border-b border-gray-100 px-6 py-4 dark:border-dark-700"
|
||||
>
|
||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">
|
||||
{{ t("admin.settings.dingtalk.title") }}
|
||||
</h2>
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ t("admin.settings.dingtalk.description") }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="space-y-5 p-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<label class="font-medium text-gray-900 dark:text-white">{{
|
||||
t("admin.settings.dingtalk.enable")
|
||||
}}</label>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ t("admin.settings.dingtalk.enableHint") }}
|
||||
</p>
|
||||
</div>
|
||||
<Toggle v-model="form.dingtalk_connect_enabled" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="form.dingtalk_connect_enabled"
|
||||
class="border-t border-gray-100 pt-4 dark:border-dark-700"
|
||||
>
|
||||
<div class="grid grid-cols-1 gap-6">
|
||||
<div>
|
||||
<label
|
||||
class="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300"
|
||||
>
|
||||
{{ t("admin.settings.dingtalk.clientId") }}
|
||||
</label>
|
||||
<input
|
||||
v-model="form.dingtalk_connect_client_id"
|
||||
type="text"
|
||||
class="input font-mono text-sm"
|
||||
:placeholder="
|
||||
t('admin.settings.dingtalk.clientIdPlaceholder')
|
||||
"
|
||||
/>
|
||||
<p class="mt-1.5 text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t("admin.settings.dingtalk.clientIdHint") }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
class="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300"
|
||||
>
|
||||
{{ t("admin.settings.dingtalk.clientSecret") }}
|
||||
</label>
|
||||
<input
|
||||
v-model="form.dingtalk_connect_client_secret"
|
||||
type="password"
|
||||
class="input font-mono text-sm"
|
||||
:placeholder="
|
||||
form.dingtalk_connect_client_secret_configured
|
||||
? t(
|
||||
'admin.settings.dingtalk.clientSecretConfiguredPlaceholder',
|
||||
)
|
||||
: t('admin.settings.dingtalk.clientSecretPlaceholder')
|
||||
"
|
||||
/>
|
||||
<p class="mt-1.5 text-xs text-gray-500 dark:text-gray-400">
|
||||
{{
|
||||
form.dingtalk_connect_client_secret_configured
|
||||
? t(
|
||||
"admin.settings.dingtalk.clientSecretConfiguredHint",
|
||||
)
|
||||
: t("admin.settings.dingtalk.clientSecretHint")
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
class="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300"
|
||||
>
|
||||
{{ t("admin.settings.dingtalk.redirectUrl") }}
|
||||
</label>
|
||||
<input
|
||||
v-model="form.dingtalk_connect_redirect_url"
|
||||
type="url"
|
||||
class="input font-mono text-sm"
|
||||
:placeholder="
|
||||
t('admin.settings.dingtalk.redirectUrlPlaceholder')
|
||||
"
|
||||
/>
|
||||
<p class="mt-1.5 text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t("admin.settings.dingtalk.redirectUrlHint") }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Corp Restriction Policy -->
|
||||
<div class="border-t border-gray-100 pt-4 dark:border-dark-700">
|
||||
<label class="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
{{ t("admin.settings.dingtalk.corpPolicy.label") }}
|
||||
</label>
|
||||
<p class="mb-3 text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t("admin.settings.dingtalk.corpPolicy.hint") }}
|
||||
</p>
|
||||
<div class="space-y-2">
|
||||
<label class="flex cursor-pointer items-center gap-3">
|
||||
<input
|
||||
v-model="form.dingtalk_connect_corp_restriction_policy"
|
||||
type="radio"
|
||||
value="none"
|
||||
class="h-4 w-4 text-primary-600"
|
||||
/>
|
||||
<span class="text-sm text-gray-700 dark:text-gray-300">
|
||||
{{ t("admin.settings.dingtalk.corpPolicy.none") }}
|
||||
</span>
|
||||
</label>
|
||||
<label class="flex cursor-pointer items-center gap-3">
|
||||
<input
|
||||
v-model="form.dingtalk_connect_corp_restriction_policy"
|
||||
type="radio"
|
||||
value="internal_only"
|
||||
class="h-4 w-4 text-primary-600"
|
||||
/>
|
||||
<span class="text-sm text-gray-700 dark:text-gray-300">
|
||||
{{ t("admin.settings.dingtalk.corpPolicy.internalOnly") }}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- bypass_registration toggle(仅 internal_only 模式下可见可用) -->
|
||||
<div
|
||||
v-if="form.dingtalk_connect_corp_restriction_policy === 'internal_only'"
|
||||
class="flex items-center justify-between pt-4 border-t border-gray-100 dark:border-dark-700"
|
||||
>
|
||||
<div>
|
||||
<label class="font-medium text-gray-900 dark:text-white">{{
|
||||
t("admin.settings.dingtalk.bypassRegistration")
|
||||
}}</label>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ t("admin.settings.dingtalk.bypassRegistrationHint") }}
|
||||
</p>
|
||||
</div>
|
||||
<Toggle v-model="form.dingtalk_connect_bypass_registration" />
|
||||
</div>
|
||||
|
||||
<!-- 身份同步开关(仅 internal_only 模式下可见) -->
|
||||
<div
|
||||
v-if="form.dingtalk_connect_corp_restriction_policy === 'internal_only'"
|
||||
class="pt-4 border-t border-gray-100 dark:border-dark-700 space-y-2"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<label class="font-medium text-gray-900 dark:text-white">{{
|
||||
t("admin.settings.dingtalk.syncDisplayName")
|
||||
}}</label>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ t("admin.settings.dingtalk.syncDisplayNameHint") }}
|
||||
</p>
|
||||
</div>
|
||||
<Toggle v-model="form.dingtalk_connect_sync_display_name" />
|
||||
</div>
|
||||
<div v-if="form.dingtalk_connect_sync_display_name" class="space-y-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<label class="text-sm text-gray-600 dark:text-gray-400 whitespace-nowrap min-w-[5rem]">
|
||||
{{ t("admin.settings.dingtalk.syncDisplayNameTarget") }}
|
||||
</label>
|
||||
<input
|
||||
v-model="form.dingtalk_connect_sync_display_name_attr_key"
|
||||
type="text"
|
||||
placeholder="dingtalk_name"
|
||||
class="input text-sm flex-1 max-w-xs"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<label class="text-sm text-gray-600 dark:text-gray-400 whitespace-nowrap min-w-[5rem]">
|
||||
{{ t("admin.settings.dingtalk.syncAttrDisplayName") }}
|
||||
</label>
|
||||
<input
|
||||
v-model="form.dingtalk_connect_sync_display_name_attr_name"
|
||||
type="text"
|
||||
placeholder="钉钉姓名"
|
||||
class="input text-sm flex-1 max-w-xs"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p v-if="form.dingtalk_connect_sync_display_name" class="text-xs text-gray-400 dark:text-gray-500">
|
||||
{{ t("admin.settings.dingtalk.syncDisplayNameTargetHint") }}
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
v-if="form.dingtalk_connect_corp_restriction_policy === 'internal_only'"
|
||||
class="pt-4 border-t border-gray-100 dark:border-dark-700 space-y-2"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<label class="font-medium text-gray-900 dark:text-white">{{
|
||||
t("admin.settings.dingtalk.syncCorpEmail")
|
||||
}}</label>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ t("admin.settings.dingtalk.syncCorpEmailHint") }}
|
||||
</p>
|
||||
<p class="text-xs text-amber-600 dark:text-amber-400 mt-1">
|
||||
{{ t("admin.settings.dingtalk.syncCorpEmailPermissionHint") }}
|
||||
</p>
|
||||
</div>
|
||||
<Toggle v-model="form.dingtalk_connect_sync_corp_email" />
|
||||
</div>
|
||||
<div v-if="form.dingtalk_connect_sync_corp_email" class="space-y-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<label class="text-sm text-gray-600 dark:text-gray-400 whitespace-nowrap min-w-[5rem]">
|
||||
{{ t("admin.settings.dingtalk.syncCorpEmailTarget") }}
|
||||
</label>
|
||||
<input
|
||||
v-model="form.dingtalk_connect_sync_corp_email_attr_key"
|
||||
type="text"
|
||||
placeholder="dingtalk_email"
|
||||
class="input text-sm flex-1 max-w-xs"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<label class="text-sm text-gray-600 dark:text-gray-400 whitespace-nowrap min-w-[5rem]">
|
||||
{{ t("admin.settings.dingtalk.syncAttrDisplayName") }}
|
||||
</label>
|
||||
<input
|
||||
v-model="form.dingtalk_connect_sync_corp_email_attr_name"
|
||||
type="text"
|
||||
placeholder="钉钉企业邮箱"
|
||||
class="input text-sm flex-1 max-w-xs"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p v-if="form.dingtalk_connect_sync_corp_email" class="text-xs text-gray-400 dark:text-gray-500">
|
||||
{{ t("admin.settings.dingtalk.syncCorpEmailTargetHint") }}
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
v-if="form.dingtalk_connect_corp_restriction_policy === 'internal_only'"
|
||||
class="pt-4 border-t border-gray-100 dark:border-dark-700 space-y-2"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<label class="font-medium text-gray-900 dark:text-white">{{
|
||||
t("admin.settings.dingtalk.syncDept")
|
||||
}}</label>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ t("admin.settings.dingtalk.syncDeptHint") }}
|
||||
</p>
|
||||
<p class="text-xs text-amber-600 dark:text-amber-400 mt-1">
|
||||
{{ t("admin.settings.dingtalk.syncDeptPermissionHint") }}
|
||||
</p>
|
||||
</div>
|
||||
<Toggle v-model="form.dingtalk_connect_sync_dept" />
|
||||
</div>
|
||||
<div v-if="form.dingtalk_connect_sync_dept" class="space-y-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<label class="text-sm text-gray-600 dark:text-gray-400 whitespace-nowrap min-w-[5rem]">
|
||||
{{ t("admin.settings.dingtalk.syncDeptTarget") }}
|
||||
</label>
|
||||
<input
|
||||
v-model="form.dingtalk_connect_sync_dept_attr_key"
|
||||
type="text"
|
||||
placeholder="dingtalk_department"
|
||||
class="input text-sm flex-1 max-w-xs"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<label class="text-sm text-gray-600 dark:text-gray-400 whitespace-nowrap min-w-[5rem]">
|
||||
{{ t("admin.settings.dingtalk.syncAttrDisplayName") }}
|
||||
</label>
|
||||
<input
|
||||
v-model="form.dingtalk_connect_sync_dept_attr_name"
|
||||
type="text"
|
||||
placeholder="钉钉部门"
|
||||
class="input text-sm flex-1 max-w-xs"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p v-if="form.dingtalk_connect_sync_dept" class="text-xs text-gray-400 dark:text-gray-500">
|
||||
{{ t("admin.settings.dingtalk.syncDeptTargetHint") }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Generic OIDC OAuth 登录 -->
|
||||
<div class="card">
|
||||
<div
|
||||
@@ -6417,6 +6705,7 @@ type SettingsForm = Omit<
|
||||
smtp_password: string;
|
||||
turnstile_secret_key: string;
|
||||
linuxdo_connect_client_secret: string;
|
||||
dingtalk_connect_client_secret: string;
|
||||
wechat_connect_app_secret: string;
|
||||
wechat_connect_open_app_secret: string;
|
||||
wechat_connect_mp_app_secret: string;
|
||||
@@ -6518,6 +6807,24 @@ const form = reactive<SettingsForm>({
|
||||
linuxdo_connect_client_secret: "",
|
||||
linuxdo_connect_client_secret_configured: false,
|
||||
linuxdo_connect_redirect_url: "",
|
||||
// DingTalk Connect OAuth 登录
|
||||
dingtalk_connect_enabled: false,
|
||||
dingtalk_connect_client_id: "",
|
||||
dingtalk_connect_client_secret: "",
|
||||
dingtalk_connect_client_secret_configured: false,
|
||||
dingtalk_connect_redirect_url: "",
|
||||
dingtalk_connect_corp_restriction_policy: "none",
|
||||
dingtalk_connect_internal_corp_id: "",
|
||||
dingtalk_connect_bypass_registration: false,
|
||||
dingtalk_connect_sync_corp_email: false,
|
||||
dingtalk_connect_sync_display_name: false,
|
||||
dingtalk_connect_sync_dept: false,
|
||||
dingtalk_connect_sync_corp_email_attr_key: "dingtalk_email",
|
||||
dingtalk_connect_sync_display_name_attr_key: "dingtalk_name",
|
||||
dingtalk_connect_sync_dept_attr_key: "dingtalk_department",
|
||||
dingtalk_connect_sync_corp_email_attr_name: "钉钉企业邮箱",
|
||||
dingtalk_connect_sync_display_name_attr_name: "钉钉姓名",
|
||||
dingtalk_connect_sync_dept_attr_name: "钉钉部门",
|
||||
wechat_connect_enabled: false,
|
||||
wechat_connect_app_id: "",
|
||||
wechat_connect_app_secret: "",
|
||||
@@ -6658,6 +6965,14 @@ const authSourceDefaultsMeta = computed(() => [
|
||||
"Applied on first signup or first bind through a verified Google email.",
|
||||
),
|
||||
},
|
||||
{
|
||||
source: "dingtalk" as AuthSourceType,
|
||||
title: "钉钉",
|
||||
description: localText(
|
||||
"通过钉钉首次注册或首次绑定时应用。",
|
||||
"Applied on first signup or first bind through DingTalk.",
|
||||
),
|
||||
},
|
||||
]);
|
||||
|
||||
// Proxies for web search emulation ProxySelector
|
||||
@@ -7241,6 +7556,7 @@ async function loadSettings() {
|
||||
smtpPasswordManuallyEdited.value = false;
|
||||
form.turnstile_secret_key = "";
|
||||
form.linuxdo_connect_client_secret = "";
|
||||
form.dingtalk_connect_client_secret = "";
|
||||
form.github_oauth_client_secret = "";
|
||||
form.google_oauth_client_secret = "";
|
||||
form.wechat_connect_app_secret = "";
|
||||
@@ -7593,6 +7909,24 @@ async function saveSettings() {
|
||||
linuxdo_connect_client_secret:
|
||||
form.linuxdo_connect_client_secret || undefined,
|
||||
linuxdo_connect_redirect_url: form.linuxdo_connect_redirect_url,
|
||||
dingtalk_connect_enabled: form.dingtalk_connect_enabled,
|
||||
dingtalk_connect_client_id: form.dingtalk_connect_client_id,
|
||||
dingtalk_connect_client_secret:
|
||||
form.dingtalk_connect_client_secret || undefined,
|
||||
dingtalk_connect_redirect_url: form.dingtalk_connect_redirect_url,
|
||||
dingtalk_connect_corp_restriction_policy:
|
||||
form.dingtalk_connect_corp_restriction_policy,
|
||||
dingtalk_connect_internal_corp_id: form.dingtalk_connect_internal_corp_id,
|
||||
dingtalk_connect_bypass_registration: form.dingtalk_connect_bypass_registration,
|
||||
dingtalk_connect_sync_corp_email: form.dingtalk_connect_sync_corp_email,
|
||||
dingtalk_connect_sync_display_name: form.dingtalk_connect_sync_display_name,
|
||||
dingtalk_connect_sync_dept: form.dingtalk_connect_sync_dept,
|
||||
dingtalk_connect_sync_corp_email_attr_key: form.dingtalk_connect_sync_corp_email_attr_key,
|
||||
dingtalk_connect_sync_display_name_attr_key: form.dingtalk_connect_sync_display_name_attr_key,
|
||||
dingtalk_connect_sync_dept_attr_key: form.dingtalk_connect_sync_dept_attr_key,
|
||||
dingtalk_connect_sync_corp_email_attr_name: form.dingtalk_connect_sync_corp_email_attr_name,
|
||||
dingtalk_connect_sync_display_name_attr_name: form.dingtalk_connect_sync_display_name_attr_name,
|
||||
dingtalk_connect_sync_dept_attr_name: form.dingtalk_connect_sync_dept_attr_name,
|
||||
wechat_connect_enabled: form.wechat_connect_enabled,
|
||||
wechat_connect_app_id:
|
||||
form.wechat_connect_open_app_id ||
|
||||
@@ -7775,6 +8109,7 @@ async function saveSettings() {
|
||||
smtpPasswordManuallyEdited.value = false;
|
||||
form.turnstile_secret_key = "";
|
||||
form.linuxdo_connect_client_secret = "";
|
||||
form.dingtalk_connect_client_secret = "";
|
||||
form.github_oauth_client_secret = "";
|
||||
form.google_oauth_client_secret = "";
|
||||
form.wechat_connect_app_secret = "";
|
||||
@@ -8992,6 +9327,21 @@ watch(
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// bypass_registration 与身份同步三开关仅在 internal_only 模式下生效。切换 policy 到其它值时,
|
||||
// 立即把相关字段重置为 false,避免保存请求里残留旧值。后端 admin handler 与
|
||||
// 配置加载层都有 coerce 兜底,这里是 UX 层的同步而非安全防线。
|
||||
watch(
|
||||
() => form.dingtalk_connect_corp_restriction_policy,
|
||||
(policy) => {
|
||||
if (policy !== "internal_only") {
|
||||
if (form.dingtalk_connect_bypass_registration) form.dingtalk_connect_bypass_registration = false;
|
||||
if (form.dingtalk_connect_sync_corp_email) form.dingtalk_connect_sync_corp_email = false;
|
||||
if (form.dingtalk_connect_sync_display_name) form.dingtalk_connect_sync_display_name = false;
|
||||
if (form.dingtalk_connect_sync_dept) form.dingtalk_connect_sync_dept = false;
|
||||
}
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
Reference in New Issue
Block a user