diff --git a/package-lock.json b/package-lock.json index bdd33943..9e2d7980 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "@hookform/resolvers": "^3.3.4", "@opengovsg/design-system-react": "^1.15.0", "@opengovsg/sgid-client": "^2.2.0", + "@opengovsg/starter-kitty-validators": "^1.0.1", "@paralleldrive/cuid2": "^2.2.2", "@prisma/client": "^5.7.1", "@sendgrid/mail": "^8.1.3", @@ -6125,6 +6126,15 @@ "openid-client": "5.4.0" } }, + "node_modules/@opengovsg/starter-kitty-validators": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@opengovsg/starter-kitty-validators/-/starter-kitty-validators-1.0.1.tgz", + "integrity": "sha512-jPJyR9yFvUnkTJc4xgNLyo6bzzbPWKw6V0aYSZOl/SHR9Ll613VhAOIJ1YUEkIwq4TB9o/ciDnWCuDuJMmFtFQ==", + "dependencies": { + "zod": "^3.23.8", + "zod-validation-error": "^3.3.0" + } + }, "node_modules/@paralleldrive/cuid2": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.2.2.tgz", @@ -28418,9 +28428,10 @@ } }, "node_modules/zod": { - "version": "3.22.4", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", - "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==", + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" } @@ -28432,6 +28443,18 @@ "peerDependencies": { "zod": "^3.21.4" } + }, + "node_modules/zod-validation-error": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-3.3.0.tgz", + "integrity": "sha512-Syib9oumw1NTqEv4LT0e6U83Td9aVRk9iTXPUQr1otyV1PuXQKOvOwhMNqZIq5hluzHP2pMgnOmHEo7kPdI2mw==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.18.0" + } } }, "dependencies": { @@ -32842,6 +32865,15 @@ "openid-client": "5.4.0" } }, + "@opengovsg/starter-kitty-validators": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@opengovsg/starter-kitty-validators/-/starter-kitty-validators-1.0.1.tgz", + "integrity": "sha512-jPJyR9yFvUnkTJc4xgNLyo6bzzbPWKw6V0aYSZOl/SHR9Ll613VhAOIJ1YUEkIwq4TB9o/ciDnWCuDuJMmFtFQ==", + "requires": { + "zod": "^3.23.8", + "zod-validation-error": "^3.3.0" + } + }, "@paralleldrive/cuid2": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.2.2.tgz", @@ -48958,15 +48990,21 @@ "devOptional": true }, "zod": { - "version": "3.22.4", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", - "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==" + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==" }, "zod-to-json-schema": { "version": "3.21.4", "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.21.4.tgz", "integrity": "sha512-fjUZh4nQ1s6HMccgIeE0VP4QG/YRGPmyjO9sAh890aQKPEk3nqbfUXhMFaC+Dr5KvYBm8BCyvfpZf2jY9aGSsw==", "requires": {} + }, + "zod-validation-error": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-3.3.0.tgz", + "integrity": "sha512-Syib9oumw1NTqEv4LT0e6U83Td9aVRk9iTXPUQr1otyV1PuXQKOvOwhMNqZIq5hluzHP2pMgnOmHEo7kPdI2mw==", + "requires": {} } } } diff --git a/package.json b/package.json index 5db39079..9af574be 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "@hookform/resolvers": "^3.3.4", "@opengovsg/design-system-react": "^1.15.0", "@opengovsg/sgid-client": "^2.2.0", + "@opengovsg/starter-kitty-validators": "^1.0.1", "@paralleldrive/cuid2": "^2.2.2", "@prisma/client": "^5.7.1", "@sendgrid/mail": "^8.1.3", diff --git a/src/schemas/url.ts b/src/schemas/url.ts index afcad666..62ff49d5 100644 --- a/src/schemas/url.ts +++ b/src/schemas/url.ts @@ -1,10 +1,31 @@ import { z } from 'zod' import { HOME } from '~/lib/routes' -import { isRelativeUrl } from '~/utils/url' +import { UrlValidator } from '@opengovsg/starter-kitty-validators' +import { getBaseUrl } from '~/utils/getBaseUrl' + +const baseUrl = getBaseUrl() + +const validator = new UrlValidator({ + baseOrigin: new URL(baseUrl).origin, + whitelist: { + protocols: ['http', 'https'], + hosts: [new URL(baseUrl).host], + }, +}) export const callbackUrlSchema = z .string() .optional() .default(HOME) - .refine((url) => url && isRelativeUrl(url)) - .catch(HOME) + .transform((url, ctx) => { + try { + return validator.parse(url) + } catch (error) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: (error as Error).message, + }) + return z.NEVER + } + }) + .catch(new URL(HOME, baseUrl))