refactor: v2.2.0 移除本地 sim 模式,Cloudflare OAuth 只走真实授权
- 删除 SimCFApi / WMAILHUB_SIM_CF / isRealOAuth / POST /api/oauth/cloudflare/sim/approve - 删除前端 OAuthSim 模拟授权页与 /oauth/sim/authorize 路由;账号页「添加账号」整页跳 Cloudflare - 迁移 0005 删除 cf_sim_resources 表 - 未配置 CF_OAUTH_CLIENT_ID/SECRET 时 start/callback 一律 503(不再降级出假账号假资源) - 修 OAuth 回调端点:跨站重定向不携带 SameSite=Strict 会话 cookie,改为由一次性 state 承担 CSRF 防护 - 测试改用 tests/cf-mock.ts 在 fetch 层拦截 Cloudflare API,断言真实 HTTP 调用(72/72 通过) - 本地 dev 无账号时只拉起 Console 并提示人工授权,不再自动造账号
This commit is contained in:
@@ -153,14 +153,13 @@ export const getAccountWorkerSecret = (id: string) =>
|
||||
|
||||
/* ---- OAuth ---- */
|
||||
|
||||
export const oauthMode = () => api<{ mode: "cloudflare" | "sim" }>("/api/oauth/mode");
|
||||
/** OAuth 是否已配置(cloudflare = 可用;unconfigured = 缺少 client id/secret) */
|
||||
export const oauthMode = () =>
|
||||
api<{ mode: "cloudflare" | "unconfigured"; authorize_url_base: string | null }>("/api/oauth/mode");
|
||||
|
||||
/** 开始授权(自动创建账号记录;授权后自动接入) */
|
||||
export const oauthStart = () => api<{ url: string; state: string }>("/api/oauth/cloudflare/start");
|
||||
|
||||
export const oauthSimApprove = (state: string) =>
|
||||
api<{ ok: boolean; error?: string; account_id: string }>("/api/oauth/cloudflare/sim/approve", "POST", { state });
|
||||
|
||||
/* ---- 域名 ---- */
|
||||
|
||||
export interface DomainRow {
|
||||
|
||||
@@ -9,7 +9,6 @@ import { ThemeContext } from "./theme-context.ts";
|
||||
import "./styles.css";
|
||||
import { App } from "./App.tsx";
|
||||
import { LoginPage } from "./pages/Login.tsx";
|
||||
import { OAuthSimPage } from "./pages/OAuthSim.tsx";
|
||||
import { DashboardPage } from "./pages/Dashboard.tsx";
|
||||
import { AccountsPage } from "./pages/Accounts.tsx";
|
||||
import { DomainsPage } from "./pages/Domains.tsx";
|
||||
@@ -20,7 +19,6 @@ import { setUnauthorizedHandler } from "./api.ts";
|
||||
/** # 成员变量: 路由表(邮件页路由 /inboxs,v2.1.0) **/
|
||||
const router = createBrowserRouter([
|
||||
{ path: "/login", element: <LoginPage /> },
|
||||
{ path: "/oauth/sim/authorize", element: <OAuthSimPage /> },
|
||||
{
|
||||
path: "/",
|
||||
element: <App />,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/** 账号页:添加账号直达 Cloudflare OAuth,授权后自动接入并同步域名;搜索、详情、Worker 凭据管理 */
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { useNavigate, useSearchParams } from "react-router-dom";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
@@ -18,7 +18,6 @@ import {
|
||||
deleteAccount,
|
||||
getAccount,
|
||||
listAccounts,
|
||||
oauthMode,
|
||||
oauthStart,
|
||||
rotateAccount,
|
||||
updateAccount,
|
||||
@@ -47,7 +46,6 @@ const STEP_LABEL: Record<string, string> = {
|
||||
*/
|
||||
export function AccountsPage() {
|
||||
const { message, modal } = App.useApp();
|
||||
const navigate = useNavigate();
|
||||
const [params] = useSearchParams();
|
||||
const [items, setItems] = useState<AccountRow[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -90,13 +88,11 @@ export function AccountsPage() {
|
||||
[items, q],
|
||||
);
|
||||
|
||||
/** 发起 OAuth:real 模式跳 Cloudflare;sim 模式跳本地模拟授权页 */
|
||||
/** 发起 OAuth:整页跳转到 Cloudflare 授权页(授权完成后浏览器重定向回 Console 回调端点) */
|
||||
const startOAuth = async () => {
|
||||
try {
|
||||
const mode = (await oauthMode()).mode;
|
||||
const { url } = await oauthStart();
|
||||
if (mode === "cloudflare") window.location.href = url;
|
||||
else navigate(url); // /oauth/sim/authorize?state=...
|
||||
window.location.href = url;
|
||||
} catch (err) {
|
||||
message.error(err instanceof Error ? err.message : "发起授权失败");
|
||||
}
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
/** sim 模式的 Cloudflare 授权模拟页:确认后调用 approve,再跳回节点页 */
|
||||
import { useEffect, useState } from "react";
|
||||
import { useNavigate, useSearchParams } from "react-router-dom";
|
||||
import { Button, Card, Space, App } from "antd";
|
||||
import { oauthSimApprove } from "../api.ts";
|
||||
|
||||
/** # 函数
|
||||
* 模拟授权页(仅本地 sim 模式;真实部署直接跳 Cloudflare 授权页)
|
||||
*/
|
||||
export function OAuthSimPage() {
|
||||
const [params] = useSearchParams();
|
||||
const navigate = useNavigate();
|
||||
const { message } = App.useApp();
|
||||
const state = params.get("state") ?? "";
|
||||
const [busy, setBusy] = useState(false);
|
||||
|
||||
const approve = async () => {
|
||||
setBusy(true);
|
||||
try {
|
||||
const res = await oauthSimApprove(state);
|
||||
if (res.ok) {
|
||||
message.success("Cloudflare 授权与自动安装完成");
|
||||
navigate(`/accounts?provision=${encodeURIComponent(res.account_id)}`, { replace: true });
|
||||
} else {
|
||||
message.error(res.error ?? "安装失败");
|
||||
navigate("/accounts", { replace: true });
|
||||
}
|
||||
} catch (err) {
|
||||
message.error(err instanceof Error ? err.message : "授权失败");
|
||||
navigate("/accounts", { replace: true });
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
};
|
||||
|
||||
// state 缺失直接回节点页
|
||||
useEffect(() => {
|
||||
if (!state) navigate("/accounts", { replace: true });
|
||||
}, [state, navigate]);
|
||||
|
||||
return (
|
||||
<div className="login-page">
|
||||
<Card className="login-card" title="Cloudflare 授权(本地模拟)" styles={{ header: { textAlign: "center" } }}>
|
||||
<p>sim 模式下,该页面模拟 Cloudflare OAuth 授权同意流程。</p>
|
||||
<p className="muted">真实部署时,这里会跳转到 dash.cloudflare.com 的授权页面。</p>
|
||||
<Space style={{ width: "100%", justifyContent: "center", marginTop: 16 }}>
|
||||
<Button onClick={() => navigate("/accounts")}>拒绝</Button>
|
||||
<Button type="primary" loading={busy} onClick={() => void approve()}>
|
||||
同意授权
|
||||
</Button>
|
||||
</Space>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user