mirror of https://github.com/boxyhq/jackson.git
169 lines
6.5 KiB
TypeScript
169 lines
6.5 KiB
TypeScript
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
|
|
import { NextPage } from 'next';
|
|
import { useRouter } from 'next/router';
|
|
import type { SAMLTrace } from '@boxyhq/saml-jackson';
|
|
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
|
import { materialOceanic } from 'react-syntax-highlighter/dist/cjs/styles/prism';
|
|
import useSWR from 'swr';
|
|
import { ApiSuccess, ApiError } from 'types';
|
|
import { fetcher } from '@lib/ui/utils';
|
|
import { errorToast } from '@components/Toaster';
|
|
import Loading from '@components/Loading';
|
|
import { useTranslation } from 'next-i18next';
|
|
import { LinkBack } from '@components/LinkBack';
|
|
import { Badge } from 'react-daisyui';
|
|
import { CopyToClipboardButton } from '@components/ClipboardButton';
|
|
|
|
const DescriptionListItem = ({ term, value }: { term: string; value: string | JSX.Element }) => (
|
|
<div className='px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6'>
|
|
<dt className='text-sm font-medium text-gray-500'>{term}</dt>
|
|
<dd className='mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0'>{value}</dd>
|
|
</div>
|
|
);
|
|
|
|
const SAMLTraceInspector: NextPage = () => {
|
|
const { t } = useTranslation('common');
|
|
|
|
const router = useRouter();
|
|
|
|
const { traceId } = router.query as { traceId: string };
|
|
|
|
const { data, error, isLoading } = useSWR<ApiSuccess<SAMLTrace>, ApiError>(
|
|
`/api/admin/saml-tracer/${traceId}`,
|
|
fetcher,
|
|
{
|
|
revalidateOnFocus: false,
|
|
}
|
|
);
|
|
|
|
if (error) {
|
|
errorToast(error.message);
|
|
return null;
|
|
}
|
|
|
|
if (isLoading) {
|
|
return <Loading />;
|
|
}
|
|
|
|
if (!data) return null;
|
|
|
|
const trace = data.data;
|
|
const assertionType = trace.context.samlResponse ? 'Response' : 'Request';
|
|
|
|
return (
|
|
<>
|
|
<LinkBack onClick={() => router.back()} />
|
|
<div className='mt-5 overflow-hidden bg-white shadow sm:rounded-lg'>
|
|
<div className='px-4 py-5 sm:px-6'>
|
|
<h3 className='text-base font-semibold leading-6 text-gray-900'>{t('trace_details')}</h3>
|
|
<p className='mt-1 flex max-w-2xl gap-6 text-sm text-gray-500'>
|
|
<span className='whitespace-nowrap'>
|
|
<span className='font-medium text-gray-500'>{t('trace_id')}</span>
|
|
<span className='ml-2 font-bold text-gray-700'> {traceId}</span>
|
|
</span>
|
|
<span className='whitespace-nowrap'>
|
|
<span className='font-medium text-gray-500'>{t('assertion_type')}:</span>
|
|
<span className='ml-2 font-bold text-gray-700'>{assertionType}</span>
|
|
</span>
|
|
<span className='whitespace-nowrap'>
|
|
<span className='font-medium text-gray-500'>{t('sp_protocol')}:</span>
|
|
<Badge
|
|
color='primary'
|
|
size='md'
|
|
className='ml-2 font-mono uppercase text-white'
|
|
aria-label='SP Protocol'>
|
|
{trace.context.requestedOIDCFlow
|
|
? 'OIDC'
|
|
: trace.context.isSAMLFederated
|
|
? t('saml_federation')
|
|
: trace.context.isIdPFlow
|
|
? t('idp_login')
|
|
: 'OAuth 2.0'}
|
|
</Badge>
|
|
</span>
|
|
</p>
|
|
</div>
|
|
<div className='border-t border-gray-200'>
|
|
<dl>
|
|
{typeof trace.timestamp === 'number' && (
|
|
<DescriptionListItem term={t('timestamp')} value={new Date(trace.timestamp).toLocaleString()} />
|
|
)}
|
|
<DescriptionListItem term={t('error')} value={trace.error} />
|
|
{trace.context.tenant && <DescriptionListItem term={t('tenant')} value={trace.context.tenant} />}
|
|
{trace.context.product && (
|
|
<DescriptionListItem term={t('product')} value={trace.context.product} />
|
|
)}
|
|
{trace.context.relayState && (
|
|
<DescriptionListItem term={t('relay_state')} value={trace.context.relayState} />
|
|
)}
|
|
{trace.context.redirectUri && (
|
|
<DescriptionListItem
|
|
term={t(trace.context.isIDPFlow ? 'default_redirect_url' : 'redirect_uri')}
|
|
value={trace.context.redirectUri}
|
|
/>
|
|
)}
|
|
{trace.context.clientID && (
|
|
<DescriptionListItem term={t('sso_connection_client_id')} value={trace.context.clientID} />
|
|
)}
|
|
{trace.context.issuer && <DescriptionListItem term={t('issuer')} value={trace.context.issuer} />}
|
|
{trace.context.acsUrl && <DescriptionListItem term={t('acs_url')} value={trace.context.acsUrl} />}
|
|
{trace.context.entityId && (
|
|
<DescriptionListItem term={t('trace_entity_id')} value={trace.context.entityId} />
|
|
)}
|
|
{trace.context.providerName && (
|
|
<DescriptionListItem term={t('provider')} value={trace.context.providerName} />
|
|
)}
|
|
{assertionType === 'Response' && trace.context.samlResponse && (
|
|
<DescriptionListItem
|
|
term={t('saml_response')}
|
|
value={
|
|
<>
|
|
<CopyToClipboardButton text={trace.context.samlResponse}></CopyToClipboardButton>
|
|
<SyntaxHighlighter language='xml' style={materialOceanic}>
|
|
{trace.context.samlResponse}
|
|
</SyntaxHighlighter>
|
|
</>
|
|
}
|
|
/>
|
|
)}
|
|
{assertionType === 'Request' && trace.context.samlRequest && (
|
|
<DescriptionListItem
|
|
term={t('saml_request')}
|
|
value={
|
|
<>
|
|
<CopyToClipboardButton text={trace.context.samlRequest}></CopyToClipboardButton>
|
|
<SyntaxHighlighter language='xml' style={materialOceanic}>
|
|
{trace.context.samlRequest}
|
|
</SyntaxHighlighter>
|
|
</>
|
|
}
|
|
/>
|
|
)}
|
|
|
|
{typeof trace.context.profile === 'object' && trace.context.profile && (
|
|
<DescriptionListItem
|
|
term={t('profile')}
|
|
value={
|
|
<SyntaxHighlighter language='json' style={materialOceanic}>
|
|
{JSON.stringify(trace.context.profile)}
|
|
</SyntaxHighlighter>
|
|
}
|
|
/>
|
|
)}
|
|
</dl>
|
|
</div>
|
|
</div>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default SAMLTraceInspector;
|
|
|
|
export async function getServerSideProps({ locale }) {
|
|
return {
|
|
props: {
|
|
...(await serverSideTranslations(locale, ['common'])),
|
|
},
|
|
};
|
|
}
|