v2.9.1: 统一登录单用户化——删除 JIT 开关与 oidc_identities 映射表(0007),任意 ZITADEL 身份登录唯一用户,上次登录身份存 settings 展示

This commit is contained in:
bigcode
2026-10-01 21:30:46 +08:00
parent fabafbd513
commit 52d5827932
13 changed files with 155 additions and 194 deletions
+5 -7
View File
@@ -338,21 +338,19 @@ export interface SettingsInfo {
/** 实际请求的 scope(点号分隔,空格分隔多个) */
scope: string;
};
/** ZITADEL 统一登录配置与当前账号绑定情况 */
/** ZITADEL 统一登录配置与上次登录身份 */
oidc: {
issuer: string;
client_id: string;
enabled: boolean;
/** JIT:未绑定的统一登录身份自动创建本地用户(即管理员) */
jit: boolean;
/** settings = 后台设置;env = 部署变量兜底;none = 未配置 */
source: "settings" | "env" | "none";
/** 授权 scope(固定 openid profile) */
scopes: string;
/** 需要在 ZITADEL 控制台应用里登记的回调地址 */
redirect_uri: string;
/** 当前账号已绑定的统一登录身份(未绑定为 null) */
identity: { issuer: string; subject: string; display_name: string | null; email: string | null } | null;
/** 上次统一登录身份(展示用;单用户系统,登录即当前唯一账号) */
last_login: { issuer: string; subject: string; display_name: string; email: string | null; at: string } | null;
};
/** 需要在 Cloudflare Dashboard 登记的授权回调地址 */
redirect_uri: string;
@@ -362,11 +360,11 @@ export const getSettings = () => api<SettingsInfo>("/api/settings");
/** 保存设置
* webhook.key / cf_oauth.secret 语义一致:undefined 保留原值、空串清除、非空设置新值
* oidc.enabled / oidc.jit 缺省表示沿用已保存的开关状态 */
* oidc.enabled 缺省表示沿用已保存的开关状态 */
export const putSettings = (body: {
webhook?: { url: string; headers: Record<string, string>; key?: string };
cf_oauth?: { client_id: string; secret?: string; scope?: string };
oidc?: { issuer: string; client_id: string; enabled?: boolean; jit?: boolean };
oidc?: { issuer: string; client_id: string; enabled?: boolean };
}) => api("/api/settings", "PUT", body);
/** # 成员变量: 授权配置自检结果 **/
@@ -5,7 +5,7 @@
* - Cloudflare OAuth 客户端(Client ID / Secret)也在设置里维护,Secret 加密存储只回 last4,
* 保存后立即生效,无需重新部署;未配置时账号页「添加账号」不可用
* - ZITADEL 统一登录:issuer + Client ID(PKCE 公共客户端无 secret),回调地址 {origin}/auth/callback;
* 登录成功即管理员(本平台没有权限设置),JIT 自动建号可开关
* 单用户系统(v2.9.1):统一登录成功即登录唯一管理员账号,没有权限设置与账号映射
* - 内容高度不超过屏幕(超出滚动)
*/
import { useCallback, useEffect, useState } from "react";
@@ -620,22 +620,20 @@ interface OidcFormValues {
}
/** # 函数
* ZITADEL 统一登录 tab(v2.9.0):issuer + Client ID(PKCE 无 secret)+ 启用/JIT 开关;
* 回调地址需在 ZITADEL 控制台应用里登记;登录成功即管理员(本平台没有权限设置)
* ZITADEL 统一登录 tab(v2.9.0;v2.9.1 单用户化):issuer + Client ID(PKCE 无 secret)+ 启用开关;
* 回调地址需在 ZITADEL 控制台应用里登记;单用户系统——统一登录成功即登录本平台唯一账号
*/
function ZitadelTab({ info, onSaved }: { info: SettingsInfo; onSaved: () => void }) {
const { message } = App.useApp();
const [form] = Form.useForm<OidcFormValues>();
const [saving, setSaving] = useState(false);
const [enabled, setEnabled] = useState(info.oidc.enabled);
const [jit, setJit] = useState(info.oidc.jit);
const meta = info.oidc;
useEffect(() => {
form.setFieldsValue({ issuer: meta.issuer, client_id: meta.client_id });
setEnabled(meta.enabled);
setJit(meta.jit);
}, [meta.issuer, meta.client_id, meta.enabled, meta.jit, form]);
}, [meta.issuer, meta.client_id, meta.enabled, form]);
/** 保存统一登录配置 */
const save = async () => {
@@ -643,7 +641,7 @@ function ZitadelTab({ info, onSaved }: { info: SettingsInfo; onSaved: () => void
setSaving(true);
try {
await putSettings({
oidc: { issuer: values.issuer.trim(), client_id: values.client_id.trim(), enabled, jit },
oidc: { issuer: values.issuer.trim(), client_id: values.client_id.trim(), enabled },
});
message.success("ZITADEL 统一登录配置已保存");
onSaved();
@@ -681,8 +679,8 @@ function ZitadelTab({ info, onSaved }: { info: SettingsInfo; onSaved: () => void
}
description={
<>
在 ZITADEL 控制台的应用 Redirect URI 里登记下面的回调地址;所有通过统一登录登录的用户都是
<b>管理员</b>(本平台没有权限设置)。
在 ZITADEL 控制台的应用 Redirect URI 里登记下面的回调地址。本平台是<b>单用户系统</b>:统一登录成功即登录
唯一的管理员账号,没有权限设置。
</>
}
/>
@@ -719,12 +717,6 @@ function ZitadelTab({ info, onSaved }: { info: SettingsInfo; onSaved: () => void
启用后登录页显示「ZITADEL 统一登录」按钮;密码登录不受影响(双通道并存)
</Typography.Text>
</Form.Item>
<Form.Item label="自动创建本地账号(JIT)" style={{ marginBottom: 8 }}>
<Switch checked={jit} onChange={setJit} />
<Typography.Text type="secondary" style={{ marginLeft: 12 }}>
开启:未绑定的统一登录身份首次登录自动创建本地用户(即管理员);关闭:未绑定身份拒绝登录
</Typography.Text>
</Form.Item>
<Space>
<Button type="primary" loading={saving} onClick={() => void save()}>
保存
@@ -732,7 +724,7 @@ function ZitadelTab({ info, onSaved }: { info: SettingsInfo; onSaved: () => void
{meta.source === "settings" && (
<Popconfirm
title="清除后台保存的统一登录配置?"
description="若未配置部署环境变量兜底,清除后登录页不再显示统一登录入口;已建立的绑定保留。"
description="若未配置部署环境变量兜底,清除后登录页不再显示统一登录入口。"
onConfirm={() => void clear()}
>
<Button danger icon={<DeleteOutlined />} disabled={saving}>
@@ -743,27 +735,24 @@ function ZitadelTab({ info, onSaved }: { info: SettingsInfo; onSaved: () => void
</Space>
</Form>
<Card size="small" title="当前账号绑定" style={{ width: "100%" }}>
{meta.identity ? (
<Card size="small" title="上次统一登录" style={{ width: "100%" }}>
{meta.last_login ? (
<Descriptions size="small" column={1}>
<Descriptions.Item label="身份">
<Tag color="green">已绑定</Tag>
<span className="mono">{meta.identity.display_name ?? "(无展示名)"}</span>
<span className="mono">{meta.last_login.display_name}</span>
</Descriptions.Item>
<Descriptions.Item label="Subject">
<span className="mono">{meta.identity.subject}</span>
<span className="mono">{meta.last_login.subject}</span>
</Descriptions.Item>
<Descriptions.Item label="Issuer">
<span className="mono">{meta.identity.issuer}</span>
<span className="mono">{meta.last_login.issuer}</span>
</Descriptions.Item>
<Descriptions.Item label="邮箱">
{meta.identity.email ? <span className="mono">{meta.identity.email}</span> : <Typography.Text type="secondary">未提供(IdP 未配置 SMTP,正常现象)</Typography.Text>}
<Descriptions.Item label="时间">
{new Date(meta.last_login.at).toLocaleString()}
</Descriptions.Item>
</Descriptions>
) : (
<Typography.Text type="secondary">
当前账号未绑定统一登录身份;绑定后可直接用 ZITADEL 登录本站(启用 JIT 时首次登录会自动建新账号)。
</Typography.Text>
<Typography.Text type="secondary">还没有统一登录记录;启用并配置后,在登录页点「ZITADEL 统一登录」即可。</Typography.Text>
)}
</Card>
</Space>
-1
View File
@@ -11,7 +11,6 @@ const OIDC_ERROR_TEXT: Record<string, string> = {
oidc_unavailable: "统一登录服务暂时不可用,请稍后重试或使用密码登录",
state_invalid: "登录状态校验失败(可能已过期),请重新登录",
oidc_exchange_failed: "统一登录兑换失败,请重新登录",
oidc_not_linked: "该统一登录账号未绑定本站用户,请使用密码登录或联系管理员",
};
/** # 函数