jackson/pages/setup/[token]/sso-connection/new.tsx

192 lines
5.5 KiB
TypeScript

import fs from 'fs';
import path from 'path';
import Link from 'next/link';
import { useEffect } from 'react';
import remarkGfm from 'remark-gfm';
import mediumZoom from 'medium-zoom';
import { MDXRemote } from 'next-mdx-remote';
import { serialize } from 'next-mdx-remote/serialize';
import type { GetServerSidePropsContext, InferGetServerSidePropsType } from 'next';
import type { MDXRemoteSerializeResult } from 'next-mdx-remote';
import ArrowsRightLeftIcon from '@heroicons/react/24/outline/ArrowsRightLeftIcon';
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
import { useTranslation } from 'next-i18next';
import jackson from '@lib/jackson';
import { jacksonOptions } from '@lib/env';
import { identityProviders } from '@lib/constants';
import Footer from '@components/setup-link-instructions/Footer';
import NextButton from '@components/setup-link-instructions/NextButton';
import { InputWithCopyButton } from '@boxyhq/internal-ui';
import PreviousButton from '@components/setup-link-instructions/PreviousButton';
import CreateSSOConnection from '@components/setup-link-instructions/CreateSSOConnection';
import SelectIdentityProviders from '@components/setup-link-instructions/SelectIdentityProviders';
type NewConnectionProps = InferGetServerSidePropsType<typeof getServerSideProps>;
const AdvancedSPConfigLink = () => {
const { t } = useTranslation('common');
return (
<div className='py-2'>
<Link href='/.well-known/saml-configuration' target='_blank' className='underline-offset-4'>
<span className='text-xs'>{t('advanced_sp_saml_configuration')}</span>
</Link>
</div>
);
};
const components = {
Footer,
NextButton,
PreviousButton,
InputWithCopyButton,
CreateSSOConnection,
AdvancedSPConfigLink,
};
const proseClassNames = [
'prose',
'prose-sm',
'prose-h2:text-lg',
'prose-h2:text-slate-700',
'prose-h2:font-semibold',
'prose-p:text-base',
'prose-img:rounded',
'prose-img:h-96',
'prose-img:w-full',
'prose-table:table',
'prose-table:border',
'prose-a:text-sm',
];
const findIdp = (idp: string) => {
return identityProviders.find((provider) => provider.id === idp);
};
const NewConnection = ({
step,
idp,
setupLinkToken,
idpEntityId,
source,
spConfig,
spMetadataUrl,
publicCertUrl,
oidcCallbackUrl,
}: NewConnectionProps) => {
const { t } = useTranslation('common');
const linkSelectIdp = { pathname: '/setup/[token]/sso-connection/new', query: { token: setupLinkToken } };
const scope = {
idpEntityId,
spConfig,
setupLinkToken,
spMetadataUrl,
publicCertUrl,
oidcCallbackUrl,
};
let heading = '';
let progress = 0;
// Add zoom to images
useEffect(() => {
if (typeof window === 'undefined') return;
const zoom = mediumZoom('img', {
scrollOffset: 0,
margin: 50,
});
return () => {
zoom.detach();
};
}, [step]);
if (source && idp && step) {
const selectedIdP = findIdp(idp);
if (!selectedIdP) {
throw new Error('Identity Provider not found');
}
progress = (100 / selectedIdP.stepCount) * parseInt(step);
heading = t('configure_identity_provider', { provider: selectedIdP.name });
} else {
heading = t('select_identity_provider');
}
return (
<>
<div className='flex space-y-4 flex-col pb-6'>
<div className='flex justify-between items-center'>
<h1 className='text-xl font-noraml'>{heading}</h1>
{source && (
<Link className='btn btn-xs h-0' href={linkSelectIdp}>
<ArrowsRightLeftIcon className='w-5 h-5' />
{t('change_identity_provider')}
</Link>
)}
</div>
{source && <progress className='progress progress-primary w-full' value={progress} max={100} />}
</div>
<article className={`${proseClassNames.join(' ')} max-w-4xl`}>
{source ? (
<MDXRemote {...source} components={components} scope={scope} />
) : (
<SelectIdentityProviders />
)}
</article>
</>
);
};
export async function getServerSideProps({ locale, query }: GetServerSidePropsContext) {
const { spConfig, setupLinkController, connectionAPIController } = await jackson();
const { idp, step, token } = query as { idp: string; step: string; token: string };
const { tenant, product } = await setupLinkController.getByToken(token);
const idpEntityId = connectionAPIController.getIDPEntityID({
tenant,
product,
});
let mdxSource: MDXRemoteSerializeResult<Record<string, unknown>> | null = null;
// Read the MDX file based on the idp and step
if (idp && step) {
try {
const mdxDirectory = path.join(process.cwd(), `components/setup-link-instructions/${idp}`);
const source = fs.readFileSync(`${mdxDirectory}/${step}.mdx`, 'utf8');
mdxSource = await serialize(source, { mdxOptions: { remarkPlugins: [remarkGfm] } });
} catch (error: any) {
return {
redirect: {
destination: `/setup/${token}/sso-connection/new`,
},
};
}
}
return {
props: {
idp: idp || null,
step: step || null,
setupLinkToken: token,
idpEntityId,
source: mdxSource,
spConfig: await spConfig.get(),
spMetadataUrl: `${jacksonOptions.externalUrl}/.well-known/sp-metadata`,
publicCertUrl: `${jacksonOptions.externalUrl}/.well-known/saml.cer`,
oidcCallbackUrl: `${jacksonOptions.externalUrl}${jacksonOptions.oidcPath}`,
...(locale ? await serverSideTranslations(locale, ['common']) : {}),
},
};
}
export default NewConnection;