mirror of
https://github.com/logto-io/logto.git
synced 2025-01-06 20:40:08 -05:00
fix: add redirectURI validation on frontend & backend (#1874)
Co-authored-by: Charles Zhao <charleszhao@silverhand.io>
This commit is contained in:
parent
ee5fe004bb
commit
4b0970b6d8
4 changed files with 95 additions and 4 deletions
|
@ -1,4 +1,9 @@
|
|||
import { Application, ApplicationType, SnakeCaseOidcConfig } from '@logto/schemas';
|
||||
import {
|
||||
Application,
|
||||
ApplicationType,
|
||||
SnakeCaseOidcConfig,
|
||||
validateRedirectUrl,
|
||||
} from '@logto/schemas';
|
||||
import { useEffect } from 'react';
|
||||
import { Controller, useFormContext } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
@ -10,7 +15,7 @@ import { MultiTextInputRule } from '@/components/MultiTextInput/types';
|
|||
import { createValidatorForRhf, convertRhfErrorMessage } from '@/components/MultiTextInput/utils';
|
||||
import TextInput from '@/components/TextInput';
|
||||
import UnsavedChangesAlertModal from '@/components/UnsavedChangesAlertModal';
|
||||
import { uriOriginValidator, uriValidator } from '@/utilities/validator';
|
||||
import { uriOriginValidator } from '@/utilities/validator';
|
||||
|
||||
import * as styles from '../index.module.scss';
|
||||
|
||||
|
@ -38,9 +43,10 @@ const Settings = ({ applicationType, oidcConfig, defaultData, isDeleted }: Props
|
|||
};
|
||||
}, [reset, defaultData]);
|
||||
|
||||
const isNativeApp = applicationType === ApplicationType.Native;
|
||||
const uriPatternRules: MultiTextInputRule = {
|
||||
pattern: {
|
||||
verify: (value) => !value || uriValidator(value),
|
||||
verify: (value) => !value || validateRedirectUrl(value, isNativeApp ? 'mobile' : 'web'),
|
||||
message: t('errors.invalid_uri_format'),
|
||||
},
|
||||
};
|
||||
|
|
|
@ -41,6 +41,11 @@ const customClientMetadata = {
|
|||
refreshTokenTtl: 100_000_000,
|
||||
};
|
||||
|
||||
const customOidcClientMetadata = {
|
||||
redirectUris: [],
|
||||
postLogoutRedirectUris: [],
|
||||
};
|
||||
|
||||
describe('application route', () => {
|
||||
const applicationRequest = createRequester({ authedRoutes: applicationRoutes });
|
||||
|
||||
|
@ -146,6 +151,57 @@ describe('application route', () => {
|
|||
).resolves.toHaveProperty('status', 400);
|
||||
});
|
||||
|
||||
it('PATCH /applications/:applicationId should save the formatted URIs as per RFC', async () => {
|
||||
await expect(
|
||||
applicationRequest.patch('/applications/foo').send({
|
||||
oidcClientMetadata: {
|
||||
redirectUris: [
|
||||
'https://example.com/callback?auth=true',
|
||||
'https://Example.com',
|
||||
'http://127.0.0.1',
|
||||
'http://localhost:3002',
|
||||
],
|
||||
},
|
||||
})
|
||||
).resolves.toHaveProperty('status', 200);
|
||||
});
|
||||
|
||||
it('PATCH /application/:applicationId expect to throw with invalid redirectURI', async () => {
|
||||
await expect(
|
||||
applicationRequest.patch('/applications/foo').send({
|
||||
oidcClientMetadata: {
|
||||
redirectUris: ['www.example.com', 'com.example://callback'],
|
||||
},
|
||||
})
|
||||
).resolves.toHaveProperty('status', 400);
|
||||
});
|
||||
|
||||
it('PATCH /application/:applicationId should save the formatted custom scheme URIs for native apps', async () => {
|
||||
await expect(
|
||||
applicationRequest.patch('/applications/foo').send({
|
||||
type: ApplicationType.Native,
|
||||
oidcClientMetadata: {
|
||||
redirectUris: [
|
||||
'com.example://demo-app/callback',
|
||||
'com.example://callback',
|
||||
'io.logto://Abc123',
|
||||
],
|
||||
},
|
||||
})
|
||||
).resolves.toHaveProperty('status', 200);
|
||||
});
|
||||
|
||||
it('PATCH /application/:applicationId expect to throw with invalid custom scheme for native apps', async () => {
|
||||
await expect(
|
||||
applicationRequest.patch('/applications/foo').send({
|
||||
type: ApplicationType.Native,
|
||||
oidcClientMetadata: {
|
||||
redirectUris: ['https://www.example.com', 'com.example/callback'],
|
||||
},
|
||||
})
|
||||
).resolves.toHaveProperty('status', 400);
|
||||
});
|
||||
|
||||
it('DELETE /applications/:applicationId', async () => {
|
||||
await expect(applicationRequest.delete('/applications/foo')).resolves.toHaveProperty(
|
||||
'status',
|
||||
|
|
|
@ -5,6 +5,7 @@ import { OpenAPIV3 } from 'openapi-types';
|
|||
import {
|
||||
ZodArray,
|
||||
ZodBoolean,
|
||||
ZodEffects,
|
||||
ZodEnum,
|
||||
ZodLiteral,
|
||||
ZodNativeEnum,
|
||||
|
@ -218,5 +219,13 @@ export const zodTypeToSwagger = (
|
|||
};
|
||||
}
|
||||
|
||||
// TO-DO: Improve swagger output for zod schema with refinement (validate through JS functions)
|
||||
if (config instanceof ZodEffects && config._def.effect.type === 'refinement') {
|
||||
return {
|
||||
type: 'object',
|
||||
description: 'Validator function',
|
||||
};
|
||||
}
|
||||
|
||||
throw new RequestError('swagger.invalid_zod_type', config);
|
||||
};
|
||||
|
|
|
@ -30,8 +30,28 @@ export const oidcModelInstancePayloadGuard = z
|
|||
|
||||
export type OidcModelInstancePayload = z.infer<typeof oidcModelInstancePayloadGuard>;
|
||||
|
||||
// Import from @logto/core-kit later, pending for new version publish
|
||||
export const webRedirectUriProtocolRegEx = /^https?:$/;
|
||||
export const mobileUriSchemeProtocolRegEx = /^[a-z][\d_a-z]*(\.[\d_a-z]+)+:$/;
|
||||
|
||||
export const validateRedirectUrl = (urlString: string, type: 'web' | 'mobile') => {
|
||||
try {
|
||||
const { protocol } = new URL(urlString);
|
||||
const protocolRegEx =
|
||||
type === 'mobile' ? mobileUriSchemeProtocolRegEx : webRedirectUriProtocolRegEx;
|
||||
|
||||
return protocolRegEx.test(protocol);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export const oidcClientMetadataGuard = z.object({
|
||||
redirectUris: z.string().url().array(),
|
||||
redirectUris: z
|
||||
.string()
|
||||
.refine((url) => validateRedirectUrl(url, 'web'))
|
||||
.or(z.string().refine((url) => validateRedirectUrl(url, 'mobile')))
|
||||
.array(),
|
||||
postLogoutRedirectUris: z.string().url().array(),
|
||||
logoUri: z.string().optional(),
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue