Guide Feishu app setup and surface tenant lookup codes
This commit is contained in:
1 parent
eb3861bbe8
commit
7e50443bba
6 files changed
+76
-5
No files matched your search
@@ -575,6 +575,7 @@
|
||||
},
|
||||
"identity": {
|
||||
"tab": "Feishu identity", "title": "Built-in Feishu identity service", "description": "Issue OIDC identities for VPN using your existing HTTPS domain. Feishu app credentials stay upstream; this platform creates separate VPN client credentials.",
|
||||
"guideTitle": "Configure your Feishu app", "guideApp": "In Feishu Open Platform → Enterprise apps, open your app. Copy the App ID and App Secret from Credentials & Basic Information into the fields below. Use enterprise app credentials.", "guidePermission": "In Permissions, request read access to enterprise information (search for tenant:tenant:readonly or 'get enterprise information'; use the actual name shown by Feishu) for automatic tenant lookup. Allow the user_profile scope for browser sign-in user info.", "guidePublish": "Publish or enable the new app version, complete administrator review if required, and add VPN users to the app's availability scope. Creating the app or adding a permission alone does not activate it.", "guideCallback": "In Security Settings → Redirect URLs, add this exact Feishu sign-in callback:", "guideCallbackPending": "Configure the HTTPS domain under Public Gateway first to see the callback URL.", "guideVerify": "Save and enable the identity service here, then sync its public routes and try a user in the app's availability scope. The Feishu callback differs from the VPN client callback below.", "guideManual": "If automatic lookup fails, enter a tenant_key verified from user information in the same enterprise. The Feishu error code appears below the field; retry after changing credentials or permissions.",
|
||||
"appId": "Feishu App ID", "appSecret": "Feishu App Secret", "secretPlaceholder": "Leave blank to retain the secret; use a rotated secret for new setup", "tenant": "Allowed Feishu tenant_key", "tenantPlaceholder": "Fetch automatically or enter manually", "fetchTenant": "Fetch tenant ID", "tenantFetched": "Feishu tenant ID retrieved", "tenantFailed": "Could not fetch Feishu tenant", "enabled": "Enable built-in issuer", "httpsOnly": "Enter app credentials only through the HTTPS management site. Switch to its secure address.", "openHttps": "Open HTTPS management site",
|
||||
"httpsRequired": "Configure and activate your public HTTPS site and certificate first.", "gatewayPending": "The identity provider is saved but Nginx does not yet route /id/; sync the gateway.",
|
||||
"addresses": "Addresses and client credentials", "issuer": "OIDC issuer", "feishuCallback": "Callback registered in Feishu", "vpnCallback": "VPN client callback", "clientId": "VPN Client ID",
|
||||
|
||||
@@ -575,6 +575,7 @@
|
||||
},
|
||||
"identity": {
|
||||
"tab": "飞书身份", "title": "内置飞书身份服务", "description": "使用现有 HTTPS 域名签发 VPN 所需 OIDC 身份令牌。飞书 App 凭据仅用于飞书上游,VPN 客户端凭据由本平台生成。",
|
||||
"guideTitle": "飞书开放平台配置步骤", "guideApp": "在飞书开放平台 → 企业自建应用中打开你的应用;在「凭证与基础信息」复制 App ID 和 App Secret,填入下方。不要使用商店应用或智能体的其他凭据。", "guidePermission": "在「权限管理」申请企业信息读取权限(搜索 tenant:tenant:readonly 或“获取企业信息”,以飞书控制台实际名称为准),供“自动获取企业标识”使用;网页授权还需允许 user_profile 范围以读取登录用户信息。", "guidePublish": "按企业要求发布/启用新版本,等待管理员审核,并在「应用可用范围」加入需要登录 VPN 的用户;只创建应用或添加权限不代表已生效。", "guideCallback": "在飞书应用的「安全设置 → 重定向 URL」添加飞书登录回调(完整地址):", "guideCallbackPending": "先在「公网入口」完成 HTTPS 域名配置,页面才会显示回调地址。", "guideVerify": "保存并启用本页身份服务,再同步身份公网路由;用应用可用范围内的账号试登录。飞书回调地址与下方 VPN 客户端回调地址不同。", "guideManual": "自动获取失败时可手填 tenant_key;请从同一企业的飞书用户信息核对。错误码会显示在输入框下方,凭据或权限变更后可重试。",
|
||||
"appId": "飞书 App ID", "appSecret": "飞书 App Secret", "secretPlaceholder": "留空保持当前密钥;新配置请输入轮换后的密钥", "tenant": "允许的飞书 tenant_key", "tenantPlaceholder": "可自动获取,也可手动填写", "fetchTenant": "自动获取企业标识", "tenantFetched": "已读取飞书企业标识", "tenantFailed": "读取飞书企业信息失败", "enabled": "启用内置发行者", "httpsOnly": "应用密钥只能通过 HTTPS 管理入口填写;请切换到安全地址。", "openHttps": "打开 HTTPS 管理入口",
|
||||
"httpsRequired": "请先完成公网域名 HTTPS 证书和站点配置,再启用身份服务。", "gatewayPending": "身份服务已保存,但 Nginx 尚未转发 /id/;请同步公网入口。",
|
||||
"addresses": "接入地址与客户端凭据", "issuer": "OIDC 发行者", "feishuCallback": "登记到飞书应用的回调", "vpnCallback": "VPN 客户端回调", "clientId": "VPN Client ID",
|
||||
|
||||
@@ -42,6 +42,17 @@
|
||||
<el-card class="page-card" shadow="never">
|
||||
<template #header><b>{{ $t('identity.title') }}</b></template>
|
||||
<el-alert type="info" :closable="false" :title="$t('identity.description')" style="margin-bottom:16px" />
|
||||
<el-card shadow="never" class="identity-guide">
|
||||
<template #header><b>{{ $t('identity.guideTitle') }}</b></template>
|
||||
<ol>
|
||||
<li>{{ $t('identity.guideApp') }}</li>
|
||||
<li>{{ $t('identity.guidePermission') }}</li>
|
||||
<li>{{ $t('identity.guidePublish') }}</li>
|
||||
<li>{{ $t('identity.guideCallback') }} <code v-if="identityStatus?.feishu_callback">{{ identityStatus.feishu_callback }}</code><span v-else>{{ $t('identity.guideCallbackPending') }}</span></li>
|
||||
<li>{{ $t('identity.guideVerify') }}</li>
|
||||
</ol>
|
||||
<p class="tls-hint">{{ $t('identity.guideManual') }}</p>
|
||||
</el-card>
|
||||
<el-alert v-if="!identitySecure" type="warning" :closable="false" :title="$t('identity.httpsOnly')" style="margin-bottom:16px" />
|
||||
<el-button v-if="!identitySecure && identityHttpsUrl" type="primary" tag="a" :href="identityHttpsUrl" style="margin-bottom:16px">{{ $t('identity.openHttps') }}</el-button>
|
||||
<el-form v-if="identitySecure" label-position="top" @submit.prevent="saveIdentity">
|
||||
@@ -52,6 +63,7 @@
|
||||
<el-button :loading="tenantLoading" :disabled="!identityForm.app_id" style="margin-top:8px" @click="fetchIdentityTenant">{{ $t('identity.fetchTenant') }}</el-button>
|
||||
<span v-if="tenantName" class="tls-hint" style="margin-left:10px">{{ tenantName }}</span>
|
||||
</el-form-item>
|
||||
<el-alert v-if="tenantError" type="warning" :closable="false" :title="tenantError" style="margin-bottom:16px" />
|
||||
<el-form-item :label="$t('identity.enabled')"><el-switch v-model="identityForm.enabled" :disabled="!identityStatus?.tls_ready" /></el-form-item>
|
||||
<el-button type="primary" :loading="identitySaving" @click="saveIdentity">{{ $t('common.save') }}</el-button>
|
||||
<el-button :loading="identityLoading" @click="loadIdentity">{{ $t('tls.refresh') }}</el-button>
|
||||
@@ -212,7 +224,9 @@ const identitySaving = ref(false)
|
||||
const identityGatewayLoading = ref(false)
|
||||
const tenantLoading = ref(false)
|
||||
const tenantName = ref('')
|
||||
const tenantError = ref('')
|
||||
function onIdentityAppChange() {
|
||||
tenantError.value = ''
|
||||
if (identityForm.value.app_id !== identityStatus.value?.app_id) {
|
||||
identityForm.value.tenant_key = ''
|
||||
tenantName.value = ''
|
||||
@@ -220,6 +234,7 @@ function onIdentityAppChange() {
|
||||
}
|
||||
async function fetchIdentityTenant() {
|
||||
if (!identitySecure) return ElMessage.warning(t('identity.httpsOnly'))
|
||||
tenantError.value = ''
|
||||
tenantLoading.value = true
|
||||
try {
|
||||
const appId = identityForm.value.app_id
|
||||
@@ -229,7 +244,7 @@ async function fetchIdentityTenant() {
|
||||
tenantName.value = data.name || ''
|
||||
ElMessage.success(t('identity.tenantFetched'))
|
||||
}
|
||||
} catch (e) { ElMessage.error(e.response?.data?.error || t('identity.tenantFailed')) }
|
||||
} catch (e) { tenantError.value = e.response?.data?.error || t('identity.tenantFailed') }
|
||||
finally { tenantLoading.value = false }
|
||||
}
|
||||
async function loadIdentity() {
|
||||
@@ -239,6 +254,7 @@ async function loadIdentity() {
|
||||
identityStatus.value = data
|
||||
identityForm.value = { enabled: data.enabled, app_id: data.app_id || '', app_secret: '', tenant_key: data.tenant_key || '' }
|
||||
tenantName.value = ''
|
||||
tenantError.value = ''
|
||||
} catch (e) { ElMessage.error(e.response?.data?.error || t('identity.loadFailed')) }
|
||||
finally { identityLoading.value = false }
|
||||
}
|
||||
@@ -523,5 +539,9 @@ function copyBackup() {
|
||||
background: var(--bg-card); border-radius: 4px;
|
||||
}
|
||||
.tls-hint { color: var(--text-secondary); line-height: 1.6; font-size: 13px; }
|
||||
.identity-guide { margin-bottom: 16px; }
|
||||
.identity-guide ol { margin: 0; padding-left: 22px; line-height: 1.8; color: var(--text-secondary); font-size: 13px; }
|
||||
.identity-guide li + li { margin-top: 6px; }
|
||||
.identity-guide code { display: inline-block; padding: 1px 5px; overflow-wrap: anywhere; color: var(--el-color-primary); background: var(--el-fill-color-light); border-radius: 4px; }
|
||||
.nginx-preview { max-height: 300px; overflow: auto; background: #172239; color: #e7eefc; border-radius: 8px; padding: 14px; font-size: 12px; line-height: 1.5; }
|
||||
</style>
|
||||
Reference in new issue
Block a user