import { GetResourceAuthInfoResponse, GetExchangeTokenResponse } from "@server/routers/resource"; import ResourceAuthPortal from "@app/components/ResourceAuthPortal"; import { formatAxiosError, internal, priv } from "@app/lib/api"; import { AxiosResponse } from "axios"; import { authCookieHeader } from "@app/lib/api/cookies"; import { cache } from "react"; import { verifySession } from "@app/lib/auth/verifySession"; import { redirect } from "next/navigation"; import ResourceNotFound from "@app/components/ResourceNotFound"; import ResourceAccessDenied from "@app/components/ResourceAccessDenied"; import AccessToken from "@app/components/AccessToken"; import { pullEnv } from "@app/lib/pullEnv"; import { LoginFormIDP } from "@app/components/LoginForm"; import { ListIdpsResponse } from "@server/routers/idp"; import { ListOrgIdpsResponse } from "@server/routers/orgIdp/types"; import AutoLoginHandler from "@app/components/AutoLoginHandler"; import { build } from "@server/build"; import { headers } from "next/headers"; import type { LoadLoginPageBrandingResponse, LoadLoginPageResponse } from "@server/routers/loginPage/types"; import { GetOrgTierResponse } from "@server/routers/billing/types"; import { TierId } from "@server/lib/billing/tiers"; import { CheckOrgUserAccessResponse } from "@server/routers/org"; import OrgPolicyRequired from "@app/components/OrgPolicyRequired"; import { isOrgSubscribed } from "@app/lib/api/isOrgSubscribed"; export const dynamic = "force-dynamic"; export default async function ResourceAuthPage(props: { params: Promise<{ resourceGuid: number }>; searchParams: Promise<{ redirect: string | undefined; token: string | undefined; }>; }) { const params = await props.params; const searchParams = await props.searchParams; const env = pullEnv(); const authHeader = await authCookieHeader(); let authInfo: GetResourceAuthInfoResponse | undefined; try { const res = await internal.get< AxiosResponse >(`/resource/${params.resourceGuid}/auth`, authHeader); if (res && res.status === 200) { authInfo = res.data.data; } } catch (e) {} const user = await verifySession({ skipCheckVerifyEmail: true }); if (!authInfo) { return (
); } const subscribed = await isOrgSubscribed(authInfo.orgId); const allHeaders = await headers(); const host = allHeaders.get("host"); const expectedHost = env.app.dashboardUrl.split("//")[1]; if (host !== expectedHost) { if (build === "saas" && !subscribed) { redirect(env.app.dashboardUrl); } let loginPage: LoadLoginPageResponse | undefined; try { const res = await priv.get>( `/login-page?resourceId=${authInfo.resourceId}&fullDomain=${host}` ); if (res && res.status === 200) { loginPage = res.data.data; } } catch (e) {} if (!loginPage) { redirect(env.app.dashboardUrl); } } let redirectUrl = authInfo.url; if (searchParams.redirect) { try { const serverResourceHost = new URL(authInfo.url).host; const redirectHost = new URL(searchParams.redirect).host; const redirectPort = new URL(searchParams.redirect).port; const serverResourceHostWithPort = `${serverResourceHost}:${redirectPort}`; if (serverResourceHost === redirectHost) { redirectUrl = searchParams.redirect; } else if (serverResourceHostWithPort === redirectHost) { redirectUrl = searchParams.redirect; } } catch (e) {} } const hasAuth = authInfo.password || authInfo.pincode || authInfo.sso || authInfo.whitelist; const isSSOOnly = authInfo.sso && !authInfo.password && !authInfo.pincode && !authInfo.whitelist; if (user && !user.emailVerified && env.flags.emailVerificationRequired) { redirect( `/auth/verify-email?redirect=/auth/resource/${authInfo.resourceGuid}` ); } const cookie = await authCookieHeader(); // Check org policy compliance before proceeding let orgPolicyCheck: CheckOrgUserAccessResponse | null = null; if (user && authInfo.orgId) { try { const policyRes = await internal.get< AxiosResponse >(`/org/${authInfo.orgId}/user/${user.userId}/check`, cookie); orgPolicyCheck = policyRes.data.data; } catch (e) { console.error(formatAxiosError(e)); } } // If user is not compliant with org policies, show policy requirements if (orgPolicyCheck && !orgPolicyCheck.allowed && orgPolicyCheck.policies) { return (
); } if (!hasAuth) { // no authentication so always go straight to the resource redirect(redirectUrl); } // convert the dashboard token into a resource session token let userIsUnauthorized = false; if (user && authInfo.sso) { let redirectToUrl: string | undefined; try { const res = await priv.post< AxiosResponse >( `/resource/${authInfo.resourceId}/get-exchange-token`, {}, cookie ); if (res.data.data.requestToken) { const paramName = env.server.resourceSessionRequestParam; // append the param with the token to the redirect url const fullUrl = new URL(redirectUrl); fullUrl.searchParams.append( paramName, res.data.data.requestToken ); redirectToUrl = fullUrl.toString(); } } catch (e) { userIsUnauthorized = true; } if (redirectToUrl) { redirect(redirectToUrl); } } if (searchParams.token) { return (
); } let loginIdps: LoginFormIDP[] = []; if (build === "saas" || env.flags.useOrgOnlyIdp) { if (subscribed) { const idpsRes = await cache( async () => await priv.get>( `/org/${authInfo!.orgId}/idp` ) )(); loginIdps = idpsRes.data.data.idps.map((idp) => ({ idpId: idp.idpId, name: idp.name, variant: idp.variant })) as LoginFormIDP[]; } } else { const idpsRes = await priv.get>("/idp"); loginIdps = idpsRes.data.data.idps.map((idp) => ({ idpId: idp.idpId, name: idp.name, variant: idp.type })) as LoginFormIDP[]; } if ( !userIsUnauthorized && isSSOOnly && authInfo.skipToIdpId && authInfo.skipToIdpId !== null ) { const idp = loginIdps.find((idp) => idp.idpId === authInfo.skipToIdpId); if (idp) { return ( ); } } let branding: LoadLoginPageBrandingResponse | null = null; try { if (subscribed) { const res = await priv.get< AxiosResponse >(`/login-page-branding?orgId=${authInfo.orgId}`); if (res.status === 200) { branding = res.data.data; } } } catch (error) {} return ( <> {userIsUnauthorized && isSSOOnly ? (
) : (
)} ); }