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:
parent
ec0f0c35f8
commit
3ee2e03ac8
5 changed files with 78 additions and 9 deletions
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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' } }] });
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 user’s 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.',
|
||||
},
|
||||
};
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue