fix(auth): wire Microsoft SSO launch
build-and-publish / test (push) Has been cancelled
build-and-publish / image (push) Has been cancelled

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-06-16 21:59:14 +04:00
co-authored by Sisyphus
parent 14a98056ed
commit 01179d6850
2 changed files with 27 additions and 46 deletions
+22
View File
@@ -27,6 +27,28 @@ server {
# way it does from the public internet.
resolver 1.1.1.1 8.8.8.8 valid=300s ipv6=off;
# Microsoft SSO is implemented by auth-service, but the demo API
# gateway currently does not expose /api/v1/auth/microsoft/*. Keep the
# FlowMaster/fm-shell contract same-origin by routing this one auth
# surface directly to auth-service; the callback keeps X-Forwarded-Host
# so auth-service returns to https://canvas.flow-master.ai/auth/sso-callback#...
location ^~ /api/v1/auth/microsoft/ {
# Use the Kubernetes ClusterIP directly. The server-wide resolver is
# deliberately pinned to public DNS for external demo/dev hostnames,
# and public DNS resolves *.svc.cluster.local to the node wildcard,
# not the in-cluster Service.
proxy_pass http://10.43.128.185:9001;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_http_version 1.1;
proxy_connect_timeout 8s;
proxy_send_timeout 30s;
proxy_read_timeout 30s;
proxy_buffering off;
}
# Reverse-proxy /api to the demo backend so live mode is same-origin.
# Uses $variable form so nginx queries the PUBLIC resolver above per
# request (cached 5 minutes). Combined with proxy_next_upstream this
+5 -46
View File
@@ -12,7 +12,6 @@ export default function Login() {
const buildAllowsDev = (import.meta.env.VITE_ENABLE_DEV_LOGIN ?? "true") !== "false";
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 [backendState, setBackendState] = useState<"unknown" | "up" | "auth-down" | "proxy-down">("unknown");
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
@@ -31,13 +30,6 @@ export default function Login() {
})
.catch(() => { /* endpoint absent → keep state */ });
}
fetch("/api/v1/auth/microsoft/login", { method: "GET", redirect: "manual" })
.then((r) => {
if (r.status >= 300 && r.status < 400) setSsoState("ready");
else if (r.status === 503) setSsoState("not-configured");
else setSsoState("not-wired");
})
.catch(() => setSsoState("not-wired"));
// Backend health precheck. /api/v1/auth/me with no token should return 401
// when the backend is up. 502/504 means the proxy is down; 503 means the
// auth service is down. We surface this so the operator knows BEFORE they
@@ -52,7 +44,7 @@ export default function Login() {
.catch(() => setBackendState("proxy-down"));
}, [buildAllowsDev]);
const handleSubmit = async (e: React.FormEvent) => {
const handleSubmit = async (e: { preventDefault: () => void }) => {
e.preventDefault();
if (!email) return;
if (!password) {
@@ -93,31 +85,6 @@ export default function Login() {
}
};
const handleSSO = async () => {
setError(null);
setLoading(true);
try {
const probe = await fetch("/api/v1/auth/microsoft/login", { method: "GET", redirect: "manual" });
if (probe.status >= 300 && probe.status < 400) {
window.location.href = "/api/v1/auth/microsoft/login";
return;
}
if (probe.status === 503) {
setError("Microsoft sign-in is not yet enabled on this tenant. Use developer sign-in below for now.");
return;
}
if (probe.status === 404) {
setError("Microsoft sign-in is not wired to this environment yet. Use developer sign-in below for now.");
return;
}
window.location.href = "/api/v1/auth/microsoft/login";
} catch (e) {
setError(`Microsoft sign-in unreachable: ${(e as Error).message}`);
} finally {
setLoading(false);
}
};
return (
<div className="login-page">
<div className="login-card">
@@ -197,14 +164,9 @@ export default function Login() {
<button
type="button"
className="sso-btn"
onClick={handleSSO}
disabled={loading || ssoState !== "ready"}
title={
ssoState === "ready" ? "Sign in with Microsoft"
: ssoState === "not-configured" ? "Microsoft sign-in is not yet configured for this tenant"
: ssoState === "not-wired" ? "Microsoft sign-in is not wired to this environment yet"
: "Checking Microsoft sign-in availability…"
}
onClick={() => { window.location.href = "/api/v1/auth/microsoft/login"; }}
disabled={loading}
title="Sign in with Microsoft"
>
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 21 21">
<rect x="1" y="1" width="9" height="9" fill="#f25022"/>
@@ -212,10 +174,7 @@ export default function Login() {
<rect x="1" y="11" width="9" height="9" fill="#00a4ef"/>
<rect x="11" y="11" width="9" height="9" fill="#ffb900"/>
</svg>
{ssoState === "unknown" ? "MICROSOFT SIGN-IN · CHECKING…"
: ssoState === "not-configured" ? "MICROSOFT SIGN-IN · NOT CONFIGURED"
: ssoState === "not-wired" ? "MICROSOFT SIGN-IN · NOT WIRED"
: "CONTINUE WITH MICROSOFT"}
CONTINUE WITH MICROSOFT
</button>
{devLoginEnabled && (