0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-03-10 22:22:45 -05:00

feat(console): add unknown session redirect url input field (#6797)

* feat(console): add unknown session redirect url input field

add unknown session redirect url input field to console

* feat(core): redirect to redirect url from sie on session not found (#6798)

read and redierct user to redirect url from sie settings on session not found
This commit is contained in:
simeng-li 2024-11-13 12:36:06 +08:00 committed by GitHub
parent ec0f0c35f8
commit 3ee2e03ac8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 78 additions and 9 deletions

View file

@ -1,10 +1,13 @@
import { useFormContext } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { isDevFeaturesEnabled } from '@/consts/env';
import Card from '@/ds-components/Card';
import FormField from '@/ds-components/FormField';
import Switch from '@/ds-components/Switch';
import TextInput from '@/ds-components/TextInput';
import TextLink from '@/ds-components/TextLink';
import { uriValidator } from '@/utils/validator';
import type { SignInExperienceForm } from '../../../types';
import FormSectionTitle from '../../components/FormSectionTitle';
@ -13,9 +16,12 @@ import styles from './index.module.scss';
function AdvancedOptions() {
const { t } = useTranslation(undefined, {
keyPrefix: 'admin_console.sign_in_exp.sign_up_and_sign_in.advanced_options',
keyPrefix: 'admin_console',
});
const { register } = useFormContext<SignInExperienceForm>();
const {
register,
formState: { errors },
} = useFormContext<SignInExperienceForm>();
return (
<Card>
@ -23,22 +29,42 @@ function AdvancedOptions() {
<FormField title="sign_in_exp.sign_up_and_sign_in.advanced_options.enable_single_sign_on">
<Switch
{...register('singleSignOnEnabled')}
label={t('enable_single_sign_on_description')}
label={t(
'sign_in_exp.sign_up_and_sign_in.advanced_options.enable_single_sign_on_description'
)}
/>
</FormField>
<div className={styles.setUpHint}>
{t('single_sign_on_hint.prefix')}
{t('sign_in_exp.sign_up_and_sign_in.advanced_options.single_sign_on_hint.prefix')}
<TextLink to="/enterprise-sso" className={styles.setup}>
{t('single_sign_on_hint.link')}
{t('sign_in_exp.sign_up_and_sign_in.advanced_options.single_sign_on_hint.link')}
</TextLink>
{t('single_sign_on_hint.suffix')}
{t('sign_in_exp.sign_up_and_sign_in.advanced_options.single_sign_on_hint.suffix')}
</div>
<FormField title="sign_in_exp.sign_up_and_sign_in.advanced_options.enable_user_registration">
<Switch
{...register('createAccountEnabled')}
label={t('enable_user_registration_description')}
label={t(
'sign_in_exp.sign_up_and_sign_in.advanced_options.enable_user_registration_description'
)}
/>
</FormField>
{isDevFeaturesEnabled && (
<FormField
title="sign_in_exp.sign_up_and_sign_in.advanced_options.unknown_session_redirect_url"
tip={t(
'sign_in_exp.sign_up_and_sign_in.advanced_options.unknown_session_redirect_url_tip'
)}
>
<TextInput
{...register('unknownSessionRedirectUrl', {
validate: (value) => !value || uriValidator(value) || t('errors.invalid_uri_format'),
})}
error={errors.unknownSessionRedirectUrl?.message}
placeholder="https://"
/>
</FormField>
)}
</Card>
);
}

View file

@ -74,7 +74,6 @@ export const sieFormDataParser = {
createAccountEnabled,
signUp,
customCss,
customUiAssets,
/** Remove the custom words related properties since they are not used in the remote model. */
passwordPolicy: { isCustomWordsEnabled, customWords, ...passwordPolicy },
} = formData;

View file

@ -25,7 +25,11 @@ describe('koaSpaSessionGuard', () => {
const provider = new Provider('https://logto.test');
const interactionDetails = jest.spyOn(provider, 'interactionDetails');
const getRowsByKeys = jest.fn().mockResolvedValue({ rows: [] });
const queries = new MockQueries({ logtoConfigs: { getRowsByKeys } });
const findDefaultSignInExperience = jest.fn().mockResolvedValue({});
const queries = new MockQueries({
logtoConfigs: { getRowsByKeys },
signInExperiences: { findDefaultSignInExperience },
});
beforeEach(() => {
process.env = { ...envBackup };
@ -91,6 +95,28 @@ describe('koaSpaSessionGuard', () => {
});
}
it('should redirect to configured unknown session redirect URL in SIE if session not found for a selected path', async () => {
const stub = Sinon.stub(EnvSet, 'values').value({
...EnvSet.values,
isDevFeaturesEnabled: true,
});
const unknownSessionRedirectUrl = 'https://foo.bar/redirect';
interactionDetails.mockRejectedValue(new Error('session not found'));
findDefaultSignInExperience.mockResolvedValueOnce({
unknownSessionRedirectUrl,
});
const ctx = createContextWithRouteParameters({
url: `${guardedPath[0]!}/foo`,
});
await koaSpaSessionGuard(provider, queries)(ctx, next);
expect(ctx.redirect).toBeCalledWith(unknownSessionRedirectUrl);
stub.restore();
});
it('should redirect to configured URL if session not found for a selected path', async () => {
interactionDetails.mockRejectedValue(new Error('session not found'));
getRowsByKeys.mockResolvedValueOnce({ rows: [{ value: { url: 'https://foo.bar' } }] });

View file

@ -37,6 +37,20 @@ export default function koaSpaSessionGuard<
try {
await provider.interactionDetails(ctx.req, ctx.res);
} catch {
// TODO: remove this check after the feature is stable
if (EnvSet.values.isDevFeaturesEnabled) {
// For unknown session, check if there is a redirect URL set in the SignInExperience
const { unknownSessionRedirectUrl } =
await queries.signInExperiences.findDefaultSignInExperience();
if (unknownSessionRedirectUrl) {
ctx.redirect(unknownSessionRedirectUrl);
return;
}
}
// If not, check if there is a redirect URL set in the tenant level LogtoConfigs
const {
rows: [data],
} = await queries.logtoConfigs.getRowsByKeys([
@ -52,6 +66,7 @@ export default function koaSpaSessionGuard<
return;
}
// Redirect to the tenant's own session not found page
const [tenantId] = await getTenantId(ctx.URL);
if (!tenantId) {

View file

@ -68,6 +68,9 @@ const sign_up_and_sign_in = {
enable_user_registration: 'Enable user registration',
enable_user_registration_description:
'Enable or disallow user registration. Once disabled, users can still be added in the admin console but users can no longer establish accounts through the sign-in UI.',
unknown_session_redirect_url: 'Unknown session redirect URL',
unknown_session_redirect_url_tip:
'In certain cases, Logto may be unable to properly identify a users authentication session when they land on the sign-in page. This can happen if the session has expired, if the user bookmarks the sign-in URL for future access, or if they directly share the sign-in link. By default, an "unknown session" 404 error is displayed. To improve user experience, set a fallback URL to redirect users back to your application and reinitiate the authentication process.',
},
};