fix: enable canvas dev login
build-and-publish / test (push) Has been cancelled
build-and-publish / image (push) Has been cancelled

This commit is contained in:
Kua Agent
2026-06-16 10:24:27 +04:00
parent 99203267aa
commit 76ce4c23b2
4 changed files with 42 additions and 9 deletions
+9
View File
@@ -85,6 +85,15 @@ server {
proxy_buffering off; proxy_buffering off;
} }
# Dev/demo auth discovery. The SPA uses this to decide whether to show and
# enable the dev-login affordance. Keep this exact route ahead of the SPA
# fallback or nginx will return index.html and the client will disable it.
location = /internal/dev-login-config {
default_type application/json;
add_header Cache-Control "no-store" always;
return 200 '{"enabled":true,"email":"dev@flow-master.ai"}';
}
# SPA fallback to index.html for client-side routing. # SPA fallback to index.html for client-side routing.
location / { location / {
try_files $uri $uri/ /index.html; try_files $uri $uri/ /index.html;
+3 -3
View File
@@ -244,13 +244,13 @@ export const api = {
return data; return data;
}, },
async devLoginConfig(signal?: AbortSignal): Promise<{ enabled: boolean | null }> { async devLoginConfig(signal?: AbortSignal): Promise<{ enabled: boolean | null; email?: string }> {
try { try {
const init: RequestInit = { method: "GET", headers: { "Accept": "application/json" }, signal }; const init: RequestInit = { method: "GET", headers: { "Accept": "application/json" }, signal };
const r = await instrumentedFetch("GET", `${this.config.baseUrl}/internal/dev-login-config`, init); const r = await instrumentedFetch("GET", `${this.config.baseUrl}/internal/dev-login-config`, init);
if (r.ok) { if (r.ok) {
const body = await r.json() as { enabled: boolean }; const body = await r.json() as { enabled: boolean; email?: string };
return { enabled: !!body.enabled }; return { enabled: !!body.enabled, email: body.email };
} }
} catch { } catch {
// network/parse error — caller treats null as "no opinion" // network/parse error — caller treats null as "no opinion"
+16
View File
@@ -71,6 +71,22 @@ describe("Login Scene", () => {
}); });
}); });
it("uses configured dev-login email without requiring typed credentials", async () => {
(api.devLoginConfig as any).mockResolvedValueOnce({ enabled: true, email: "dev@flow-master.ai" });
mockLoginAs.mockResolvedValueOnce(undefined);
render(<Login />);
const devButton = await screen.findByRole("button", { name: /DEV-LOGIN/i });
expect((devButton as HTMLButtonElement).disabled).toBe(false);
fireEvent.click(devButton);
await waitFor(() => {
expect(mockLoginAs).toHaveBeenCalledWith("dev@flow-master.ai", undefined, { method: "dev" });
expect(mockSetScene).toHaveBeenCalledWith("mission");
});
});
it("SSO button is disabled until probe resolves to ready", async () => { it("SSO button is disabled until probe resolves to ready", async () => {
render(<Login />); render(<Login />);
const ssoBtn = screen.getAllByRole("button", { name: /MICROSOFT SIGN-IN|CONTINUE WITH MICROSOFT/i })[0] as HTMLButtonElement; const ssoBtn = screen.getAllByRole("button", { name: /MICROSOFT SIGN-IN|CONTINUE WITH MICROSOFT/i })[0] as HTMLButtonElement;
+13 -5
View File
@@ -11,6 +11,7 @@ export default function Login() {
const [rememberMe, setRememberMe] = useState(false); const [rememberMe, setRememberMe] = useState(false);
const buildAllowsDev = (import.meta.env.VITE_ENABLE_DEV_LOGIN ?? "true") !== "false"; const buildAllowsDev = (import.meta.env.VITE_ENABLE_DEV_LOGIN ?? "true") !== "false";
const [devLoginEnabled, setDevLoginEnabled] = useState(buildAllowsDev); const [devLoginEnabled, setDevLoginEnabled] = useState(buildAllowsDev);
const [devLoginEmail, setDevLoginEmail] = useState("dev@flow-master.ai");
const [ssoState, setSsoState] = useState<"unknown" | "ready" | "not-configured" | "not-wired">("unknown"); const [ssoState, setSsoState] = useState<"unknown" | "ready" | "not-configured" | "not-wired">("unknown");
const [backendState, setBackendState] = useState<"unknown" | "up" | "auth-down" | "proxy-down">("unknown"); const [backendState, setBackendState] = useState<"unknown" | "up" | "auth-down" | "proxy-down">("unknown");
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@@ -21,7 +22,13 @@ export default function Login() {
setDevLoginEnabled(false); setDevLoginEnabled(false);
} else { } else {
api.devLoginConfig() api.devLoginConfig()
.then((cfg) => { if (cfg.enabled === false) setDevLoginEnabled(false); }) .then((cfg) => {
if (cfg.enabled === false) setDevLoginEnabled(false);
if (cfg.email) {
setDevLoginEmail(cfg.email);
setEmail((current) => current || cfg.email || "");
}
})
.catch(() => { /* endpoint absent → keep state */ }); .catch(() => { /* endpoint absent → keep state */ });
} }
fetch("/api/v1/auth/microsoft/login", { method: "GET", redirect: "manual" }) fetch("/api/v1/auth/microsoft/login", { method: "GET", redirect: "manual" })
@@ -69,14 +76,15 @@ export default function Login() {
setError("Developer sign-in is disabled in this build."); setError("Developer sign-in is disabled in this build.");
return; return;
} }
if (!email) { const selectedEmail = email || devLoginEmail;
setError("Email required for dev-login"); if (!selectedEmail) {
setError("Developer sign-in has no configured email.");
return; return;
} }
setLoading(true); setLoading(true);
setError(null); setError(null);
try { try {
await loginAs(email, undefined, { method: "dev" }); await loginAs(selectedEmail, undefined, { method: "dev" });
setScene("mission"); setScene("mission");
} catch (err) { } catch (err) {
setError((err as Error).message); setError((err as Error).message);
@@ -211,7 +219,7 @@ export default function Login() {
</button> </button>
{devLoginEnabled && ( {devLoginEnabled && (
<button type="button" className="dev-login-btn" onClick={handleDevLogin} disabled={loading || !email}> <button type="button" className="dev-login-btn" onClick={handleDevLogin} disabled={loading}>
<Bot size={14} /> SIGN IN AS DEVELOPER (DEV-LOGIN) <Bot size={14} /> SIGN IN AS DEVELOPER (DEV-LOGIN)
</button> </button>
)} )}