Skip to content

Commit 77e7e86

Browse files
authoredJan 6, 2025
chore: improve sgid state validation (#386)
* chore: improve sgid state validation * fix: add validation for redirectUrl after sgid oidc login * fix: validate landingUrl clientside in first leg of sgid flow * fix: revert wrong validation on oidc redirection * fix: wrong type on redirectUrl * fix: wrong type passed in sgid login mutation * fix: update callbackUrlSchema to fail gracefully
1 parent 5593894 commit 77e7e86

File tree

4 files changed

+27
-19
lines changed

4 files changed

+27
-19
lines changed
 

‎src/features/sign-in/components/SgidErrorFallback/SgidErrorFallback.tsx

+3-2
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,20 @@ import { z } from 'zod'
55

66
import { safeSchemaJsonParse } from '~/utils/zod'
77
import { HOME } from '~/lib/routes'
8+
import { callbackUrlSchema } from '~/schemas/url'
89
import { SgidErrorModal } from './SgidErrorModal'
910

1011
export const SgidErrorFallback: ComponentType<FallbackProps> = ({ error }) => {
1112
const router = useRouter()
1213
const redirectUrl = useMemo(() => {
1314
const parsed = safeSchemaJsonParse(
1415
z.object({
15-
landingUrl: z.string(),
16+
landingUrl: callbackUrlSchema,
1617
}),
1718
String(router.query.state),
1819
)
1920
if (parsed.success) {
20-
return parsed.data.landingUrl
21+
return parsed.data.landingUrl.href
2122
}
2223
return HOME
2324
}, [router.query.state])

‎src/features/sign-in/components/SgidLogin/SgidLoginButton.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { Button } from '@opengovsg/design-system-react'
55
import { trpc } from '~/utils/trpc'
66
import { getRedirectUrl } from '~/utils/url'
77
import { SingpassFullLogo } from '~/components/Svg/SingpassFullLogo'
8+
import { callbackUrlSchema } from '~/schemas/url'
89

910
export const SgidLoginButton = (): JSX.Element | null => {
1011
const router = useRouter()
@@ -14,7 +15,7 @@ export const SgidLoginButton = (): JSX.Element | null => {
1415
},
1516
})
1617

17-
const landingUrl = getRedirectUrl(router.query)
18+
const landingUrl = callbackUrlSchema.parse(getRedirectUrl(router.query)).href
1819

1920
const handleSgidLogin = () => {
2021
return sgidLoginMutation.mutate({

‎src/schemas/url.ts

+19-9
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,27 @@ import { HOME } from '~/lib/routes'
66

77
const baseUrl = new URL(getBaseUrl())
88

9+
const urlSchema = createUrlSchema({
10+
baseOrigin: baseUrl.origin,
11+
whitelist: {
12+
protocols: ['http', 'https'],
13+
hosts: [baseUrl.host],
14+
},
15+
})
16+
917
export const callbackUrlSchema = z
1018
.string()
1119
.optional()
1220
.default(HOME)
13-
.pipe(
14-
createUrlSchema({
15-
baseOrigin: baseUrl.origin,
16-
whitelist: {
17-
protocols: ['http', 'https'],
18-
hosts: [baseUrl.host],
19-
},
20-
}),
21-
)
21+
.transform((val, ctx) => {
22+
try {
23+
return urlSchema.parse(val)
24+
} catch {
25+
ctx.addIssue({
26+
code: z.ZodIssueCode.custom,
27+
message: 'URL validation error',
28+
})
29+
return z.NEVER
30+
}
31+
})
2232
.catch(new URL(HOME, baseUrl.origin))

‎src/server/modules/auth/sgid/sgid.router.ts

+3-7
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,12 @@ import {
2222
} from './sgid.utils'
2323

2424
const sgidCallbackStateSchema = z.object({
25-
landingUrl: z.string(),
25+
landingUrl: callbackUrlSchema,
2626
})
2727

2828
export const sgidRouter = router({
2929
login: publicProcedure
30-
.input(
31-
z.object({
32-
landingUrl: callbackUrlSchema,
33-
}),
34-
)
30+
.input(sgidCallbackStateSchema)
3531
.mutation(async ({ ctx, input: { landingUrl } }) => {
3632
if (!sgid) {
3733
throw new TRPCError({
@@ -158,7 +154,7 @@ export const sgidRouter = router({
158154
selectProfileStep: true,
159155
redirectUrl: appendWithRedirect(
160156
`${SIGN_IN}${SIGN_IN_SELECT_PROFILE_SUBROUTE}`,
161-
parsedState.data.landingUrl,
157+
parsedState.data.landingUrl.href,
162158
),
163159
}
164160
}),

0 commit comments

Comments
 (0)