@@ -58,6 +58,39 @@ async function exchangeForToken(
5858 } ;
5959}
6060
61+ function getSafeRedirectPath ( path : unknown ) {
62+ if (
63+ typeof path !== 'string' ||
64+ ! path . startsWith ( '/' ) ||
65+ path . startsWith ( '//' )
66+ ) {
67+ return '/' ;
68+ }
69+
70+ return path ;
71+ }
72+
73+ function parseOAuthState ( state : string | undefined ) {
74+ if ( ! state ) {
75+ throw new Error ( 'Missing OAuth state.' ) ;
76+ }
77+
78+ const decoded = Buffer . from ( state , 'base64' ) . toString ( 'utf-8' ) ;
79+ const parsed = JSON . parse ( decoded ) as {
80+ userId ?: string ;
81+ afterCallbackRedirect ?: string ;
82+ } ;
83+
84+ if ( ! parsed . userId ) {
85+ throw new Error ( 'Invalid OAuth state payload.' ) ;
86+ }
87+
88+ return {
89+ userId : parsed . userId ,
90+ afterCallbackRedirect : getSafeRedirectPath ( parsed . afterCallbackRedirect )
91+ } ;
92+ }
93+
6194async function publishPreviouslyUnverifiedEmailsToCleaning (
6295 contacts : Contacts ,
6396 userId : string ,
@@ -137,10 +170,11 @@ export default function initializeMiningController(
137170 const user = res . locals . user as User ;
138171 const provider = req . params . provider as OAuthMiningSourceProvider ;
139172 const { redirect } = req . body ;
173+ const afterCallbackRedirect = getSafeRedirectPath ( redirect ) ;
140174
141175 const stateObj = JSON . stringify ( {
142176 userId : user . id ,
143- afterCallbackRedirect : redirect ?? '/'
177+ afterCallbackRedirect
144178 } ) ;
145179
146180 const authorizationUri = getAuthClient ( provider ) . authorizeURL ( {
@@ -156,15 +190,7 @@ export default function initializeMiningController(
156190 const provider = req . params . provider as OAuthMiningSourceProvider ;
157191 let redirect = '/' ;
158192 try {
159- const {
160- userId,
161- afterCallbackRedirect
162- } : {
163- userId : string ;
164- afterCallbackRedirect : string ;
165- } = JSON . parse (
166- Buffer . from ( state as string , 'base64' ) . toString ( 'utf-8' )
167- ) ;
193+ const { userId, afterCallbackRedirect } = parseOAuthState ( state ) ;
168194
169195 redirect = afterCallbackRedirect ;
170196 const exchangedTokens = await exchangeForToken ( code , provider ) ;
0 commit comments