Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 48 additions & 66 deletions cda-gui/src/pages/swagger-ui/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import "../../css/swagger.css";

import {
createCwmsLoginAuthMethod,
createKeycloakAuthMethod,
useAuth,
} from "@usace-watermanagement/groundwork-water";
import { useEffect, useMemo, useRef, useState } from "react";
import { useAuthConfiguration } from "../../components/auth-configuration-context";
import { getBasePath } from "../../utils/base";
import {
getKeycloakConfig,
Expand All @@ -26,38 +27,36 @@ function isExternalOpenIdOnLocalhost(keycloakConfig) {
}

export default function SwaggerUI() {
const appAuth = useAuth();
const { error: appAuthError } = useAuthConfiguration();
const [authStatus, setAuthStatus] = useState("checking");
const [customAuthType, setCustomAuthType] = useState(null);
const [authUiMode, setAuthUiMode] = useState("hidden");
const [isOpenIdAuthReady, setIsOpenIdAuthReady] = useState(false);
const [isLocalOpenIdAuth, setIsLocalOpenIdAuth] = useState(false);
const [authError, setAuthError] = useState(null);
const [swaggerError, setSwaggerError] = useState(null);
const autoLoginAttemptedRef = useRef(false);
const openIdAuthMethodRef = useRef(null);
const openIdAuthConfigSignatureRef = useRef(null);
const cwmsAuthMethod = useMemo(() => {
const basePath = getBasePath();
return createCwmsLoginAuthMethod({
authUrl: `${window.location.origin}/CWMSLogin`,
authCheckUrl: `${basePath}/auth/keys`,
});
}, []);
const openIdAuthMethod = isOpenIdAuthReady ? openIdAuthMethodRef.current : null;
const authMethod =
customAuthType === "cwms"
? cwmsAuthMethod
: customAuthType === "openid"
? openIdAuthMethod
: null;
const authMethod = customAuthType === "cwms" ? cwmsAuthMethod : null;
const hasAuthMethod =
customAuthType === "openid" ? !appAuthError : Boolean(authMethod);
const authLabel = authUiMode === "cwms-login" ? "CWMS Login" : "CWBI Login";

const getUnavailableMessage = () => {
if (authMethod) {
if (hasAuthMethod) {
return null;
}

if (customAuthType === "openid") {
if (appAuthError) {
return appAuthError;
}
return isLoopbackHost(window.location.hostname)
? "Local sign-in is not available for this OpenID configuration. Start the local CDA/Keycloak stack, or use Swagger's Authorize controls with an API key."
: "Sign-in is not available because this Swagger spec does not advertise a usable OpenID client.";
Expand Down Expand Up @@ -109,7 +108,6 @@ export default function SwaggerUI() {
setCustomAuthType(null);
setAuthUiMode("hidden");
setAuthStatus("anonymous");
setIsOpenIdAuthReady(false);
setIsLocalOpenIdAuth(false);
document.querySelector("#swagger-ui").innerHTML = "";
}
Expand All @@ -135,46 +133,24 @@ export default function SwaggerUI() {
if (nextCustomAuthType === "cwms") {
setAuthUiMode("cwms-login");
} else if (nextCustomAuthType === "openid" && keycloakConfig) {
const keycloakConfigSignature = JSON.stringify(keycloakConfig);
if (isExternalOpenIdOnLocalhost(keycloakConfig)) {
openIdAuthMethodRef.current = null;
openIdAuthConfigSignatureRef.current = null;
setAuthUiMode("hidden");
setAuthError(null);
setAuthStatus("anonymous");
setIsLocalOpenIdAuth(false);
setIsOpenIdAuthReady(false);
} else {
try {
if (openIdAuthConfigSignatureRef.current !== keycloakConfigSignature) {
openIdAuthMethodRef.current = createKeycloakAuthMethod(keycloakConfig);
openIdAuthConfigSignatureRef.current = keycloakConfigSignature;
}
setAuthError(null);
setIsLocalOpenIdAuth(keycloakConfig.flow === "direct-grant");
setAuthUiMode(
keycloakConfig.flow === "direct-grant"
? "local-keycloak"
: "external-openid",
);
setIsOpenIdAuthReady(true);
} catch (error) {
openIdAuthMethodRef.current = null;
openIdAuthConfigSignatureRef.current = null;
setAuthUiMode("external-openid");
setAuthError(error?.message ?? "Unable to configure OpenID sign-in.");
setAuthStatus("anonymous");
setIsLocalOpenIdAuth(false);
setIsOpenIdAuthReady(false);
}
setAuthError(null);
setIsLocalOpenIdAuth(keycloakConfig.flow === "direct-grant");
setAuthUiMode(
keycloakConfig.flow === "direct-grant"
? "local-keycloak"
: "external-openid",
);
}
} else {
openIdAuthMethodRef.current = null;
openIdAuthConfigSignatureRef.current = null;
setAuthUiMode("hidden");
setAuthError(null);
setIsLocalOpenIdAuth(false);
setIsOpenIdAuthReady(false);
}

if (cancelled) {
Expand Down Expand Up @@ -204,8 +180,10 @@ export default function SwaggerUI() {
req.url = requestUrl.toString();
req.headers = req.headers ?? {};

if (authMethod?.token) {
req.headers.Authorization = `Bearer ${authMethod.token}`;
const token =
customAuthType === "openid" ? appAuth.token : authMethod?.token;
if (token) {
req.headers.Authorization = `Bearer ${token}`;
}

req.cache = "no-store";
Expand Down Expand Up @@ -247,10 +225,10 @@ export default function SwaggerUI() {
cancelled = true;
document.querySelector("#swagger-ui").innerHTML = "";
};
}, [authMethod, customAuthType]);
}, [appAuth.token, authMethod, customAuthType]);

useEffect(() => {
if (!authMethod) {
if (customAuthType !== "cwms" || !authMethod) {
setAuthStatus("anonymous");
return;
}
Expand All @@ -271,12 +249,12 @@ export default function SwaggerUI() {
return () => {
mounted = false;
};
}, [authMethod]);
}, [authMethod, customAuthType]);

useEffect(() => {
if (
customAuthType !== "openid" ||
!authMethod ||
!hasAuthMethod ||
!isLocalOpenIdAuth ||
autoLoginAttemptedRef.current
) {
Expand All @@ -287,14 +265,8 @@ export default function SwaggerUI() {
let mounted = true;
setAuthStatus("checking");
setAuthError(null);
authMethod
appAuth
.login()
.then(() => authMethod.isAuth())
.then((isAuth) => {
if (mounted) {
setAuthStatus(isAuth ? "authenticated" : "anonymous");
}
})
.catch((error) => {
if (mounted) {
setAuthError(error?.message ?? "Automatic local sign-in failed.");
Expand All @@ -305,47 +277,57 @@ export default function SwaggerUI() {
return () => {
mounted = false;
};
}, [authMethod, customAuthType, isLocalOpenIdAuth]);
}, [appAuth, customAuthType, hasAuthMethod, isLocalOpenIdAuth]);

const startLogin = async () => {
if (!authMethod) {
if (!hasAuthMethod) {
setAuthError(getUnavailableMessage());
return;
}

setAuthError(null);
setAuthStatus("checking");
try {
await authMethod.login();
await checkAuth();
if (customAuthType === "openid") {
await appAuth.login();
} else {
await authMethod.login();
await checkAuth();
}
} catch (error) {
setAuthError(error?.message ?? "Sign-in failed.");
setAuthStatus("anonymous");
}
};

const signOut = async () => {
if (!authMethod) {
if (!hasAuthMethod) {
return;
}

setAuthError(null);
setAuthStatus("checking");
try {
await authMethod.logout();
await checkAuth();
if (customAuthType === "openid") {
await appAuth.logout();
} else {
await authMethod.logout();
await checkAuth();
}
} catch (error) {
setAuthError(error?.message ?? "Sign-out failed.");
setAuthStatus("anonymous");
}
};

const isAuthenticated = authStatus === "authenticated";
const isCheckingAuth = authStatus === "checking";
const isAuthenticated =
customAuthType === "openid" ? appAuth.isAuth : authStatus === "authenticated";
const isCheckingAuth =
customAuthType === "openid" ? appAuth.isLoading : authStatus === "checking";
const unavailableMessage = getUnavailableMessage();
const showAuthBar = authUiMode !== "hidden";
const showUnavailableMessage =
showAuthBar && !authMethod && authStatus !== "checking";
showAuthBar && !hasAuthMethod && !isCheckingAuth;

return (
<>
Expand Down Expand Up @@ -374,7 +356,7 @@ export default function SwaggerUI() {
)}
</div>
<div className="flex flex-wrap items-center gap-2 sm:justify-end">
{authMethod && (
{hasAuthMethod && (
<button
className="h-9 rounded border border-blue-700 bg-blue-600 px-4 text-sm font-semibold text-white hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-55"
disabled={isCheckingAuth}
Expand Down
Loading