0
Fork 0
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:
Mahesh Vagicherla 2022-10-10 18:50:20 +05:30 committed by GitHub
parent ee5fe004bb
commit 4b0970b6d8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 95 additions and 4 deletions

View file

@ -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'),
},
};

View file

@ -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',

View file

@ -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);
};

View file

@ -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(),
});