publishing: unfenced vault accessor for the amplify path

createPublishingCredentialAccessor is execution-fenced (Temporal contexts
only) — the API-process amplify route threw EXECUTION_CREDENTIAL_FENCE.
Use the established PgConnectionStore+vault accessor instead (same trust
level as the archive amplify route's bot-token path).
This commit is contained in:
glass beadian 2026-10-13 18:32:38 +00:00
parent 6145281b3f
commit 96e5a63dd8
11 changed files with 1501 additions and 8 deletions

View file

@ -0,0 +1,94 @@
"use client";
import { useEffect, useRef, useState } from "react";
import Link from "next/link";
import { Card } from "../../../components/ui/primitives";
import { LoadingPanel } from "../../../components/brand/loading";
import { api, ApiError, errorMessage } from "../../../lib/api-client";
import { getSessionToken } from "../../../lib/session-token";
/**
* /oidc/consent — the Hub half of files (oCIS) single sign-on.
*
* The API's GET /oidc/authorize validates oCIS's request and sends the browser
* here, because only this origin holds the Hub session (a Bearer token in
* localStorage — there is no session cookie the API could read). We relay the
* authorize params to POST /oidc/authorize/consent with that session; the API
* re-validates everything, issues a single-use code and answers with the oCIS
* callback URL, which we navigate to. client_id=web is first-party, so there
* is no consent screen: the user only ever sees a brief "Signing in…".
*/
const AUTHORIZE_PARAMS = [
"response_type", "client_id", "redirect_uri", "scope", "state", "nonce", "code_challenge", "code_challenge_method", "prompt",
] as const;
type Phase = { kind: "working" } | { kind: "signin" } | { kind: "error"; message: string };
function authorizeParams(search: string): Record<string, string> {
const query = new URLSearchParams(search);
const params: Record<string, string> = {};
for (const name of AUTHORIZE_PARAMS) {
const value = query.get(name);
if (value !== null) params[name] = value;
}
return params;
}
export default function OidcConsentPage(): React.ReactElement {
const [phase, setPhase] = useState<Phase>({ kind: "working" });
// Each run issues a single-use code; never mint twice (StrictMode double effects).
const started = useRef(false);
useEffect(() => {
if (started.current) return;
started.current = true;
if (getSessionToken() === null) {
setPhase({ kind: "signin" });
return;
}
api
.post<{ redirectTo: string }>("/oidc/authorize/consent", authorizeParams(window.location.search), { redirectOn401: false })
.then(({ redirectTo }) => {
const target = new URL(redirectTo);
if (target.protocol !== "https:" && target.protocol !== "http:") throw new Error("Unexpected sign-in redirect");
// replace(): Back must not return here and re-run a spent authorization.
window.location.replace(target.toString());
})
.catch((err: unknown) => {
if (err instanceof ApiError && err.status === 401) setPhase({ kind: "signin" });
else setPhase({ kind: "error", message: errorMessage(err) });
});
}, []);
if (phase.kind === "working") {
return (
<div className="flex min-h-screen items-center justify-center bg-bg">
<LoadingPanel label="Signing in to files…" />
</div>
);
}
return (
<div className="flex min-h-full items-center justify-center bg-bg p-6">
<Card className="w-[420px] max-w-full p-6 text-center">
<h1 className="text-xl font-semibold tracking-tight">Files sign-in</h1>
{phase.kind === "signin" ? (
<>
<p className="mt-2 text-xs text-text-faint">Sign in to the Hub first, then open Drive again.</p>
<Link href="/login" className="mt-6 inline-block text-xs text-text underline">
Sign in
</Link>
</>
) : (
<>
<p className="mt-2 text-xs text-danger">{phase.message}</p>
<Link href="/files/drive" className="mt-6 inline-block text-xs text-text-faint underline hover:text-text">
Back to Drive
</Link>
</>
)}
</Card>
</div>
);
}