@@ -8,14 +8,19 @@ interface OAuthCallbackQuery {
88 state ?: string ;
99}
1010
11+ interface ParsedOAuthState {
12+ userId : string ;
13+ nonce : string ;
14+ }
15+
1116export async function connectRoutes ( app : FastifyInstance ) {
1217 // ─── Status ───
1318
1419 app . get ( '/status' , {
1520 preHandler : [ app . authenticate ] ,
1621 } , async ( request : FastifyRequest , reply : FastifyReply ) => {
1722 const userId = ( request . user as any ) . id ;
18-
23+
1924 const tokens = await app . prisma . oAuthToken . findMany ( {
2025 where : { userId } ,
2126 select : { platform : true , createdAt : true , scopes : true } ,
@@ -43,24 +48,28 @@ export async function connectRoutes(app: FastifyInstance) {
4348 scope : 'user:follow' , // ONLY asking for follow scope to avoid full profile access
4449 state : Buffer . from ( state ) . toString ( 'base64' ) ,
4550 } ) ;
46-
51+
4752 return reply . redirect ( `${ GITHUB_AUTH_URL } ?${ params } ` ) ;
4853 } ) ;
4954
5055 app . get ( '/github/callback' , async ( request : FastifyRequest < { Querystring : OAuthCallbackQuery } > , reply : FastifyReply ) => {
5156 const { code, state } = request . query ;
52-
57+
5358 if ( ! code || ! state ) {
5459 return reply . redirect ( `${ process . env . PUBLIC_APP_URL } /settings?error=missing_params` ) ;
5560 }
5661
5762 try {
5863 // Decode state to find which user requested the connect
59- const decodedState = JSON . parse ( Buffer . from ( state , 'base64' ) . toString ( 'utf-8' ) ) ;
64+ const decodedState = parseGoogleState ( state ) ;
65+
66+ if ( ! decodedState ) {
67+ return reply . redirect ( `${ process . env . PUBLIC_APP_URL } /settings?error=connect_failed` ) ;
68+ }
6069 const userId = decodedState . userId ;
6170
6271 if ( ! userId ) {
63- return reply . redirect ( `${ process . env . PUBLIC_APP_URL } /settings?error=invalid_state` ) ;
72+ return reply . redirect ( `${ process . env . PUBLIC_APP_URL } /settings?error=invalid_state` ) ;
6473 }
6574
6675 // Exchange code for token
@@ -77,7 +86,7 @@ export async function connectRoutes(app: FastifyInstance) {
7786 redirect_uri : `${ process . env . BACKEND_URL } /api/connect/github/callback` ,
7887 } ) ,
7988 } ) ;
80-
89+
8190 const tokenData = ( await tokenRes . json ( ) ) as any ;
8291
8392 if ( tokenData . error ) {
@@ -87,7 +96,7 @@ export async function connectRoutes(app: FastifyInstance) {
8796
8897 // Encrypt and store the token
8998 const encryptedToken = app . encryption . encrypt ( tokenData . access_token ) ;
90-
99+
91100 await app . prisma . oAuthToken . upsert ( {
92101 where : {
93102 userId_platform : {
@@ -110,17 +119,18 @@ export async function connectRoutes(app: FastifyInstance) {
110119 // Redirect back to app settings
111120 // If mobile, use custom scheme
112121 if ( decodedState . nonce . startsWith ( 'mobile_' ) ) {
113- return reply . redirect ( `${ process . env . MOBILE_REDIRECT_URI } ?connected=github` ) ;
122+ return reply . redirect ( `${ process . env . MOBILE_REDIRECT_URI } ?connected=github` ) ;
114123 }
115-
124+
116125 return reply . redirect ( `${ process . env . PUBLIC_APP_URL } /settings?connected=github` ) ;
117-
126+
118127 } catch ( err ) {
119128 app . log . error ( 'GitHub connect error:' , err ) ;
120129 return reply . redirect ( `${ process . env . PUBLIC_APP_URL } /settings?error=server_error` ) ;
121130 }
122131 } ) ;
123132
133+
124134 // ─── Disconnect ───
125135
126136 app . delete ( '/:platform' , {
@@ -145,6 +155,20 @@ export async function connectRoutes(app: FastifyInstance) {
145155 } ) ;
146156}
147157
158+ function parseGoogleState ( state : string ) : ParsedOAuthState | null {
159+ try {
160+ const decoded = JSON . parse ( Buffer . from ( state , 'base64' ) . toString ( 'utf-8' ) ) ;
161+
162+ // validating the OAuth state structure which is expected
163+ if ( typeof decoded . userId !== "string" || typeof decoded . nonce !== "string" ) {
164+ return null ;
165+ }
166+ return decoded ;
167+ } catch {
168+ return null ;
169+ }
170+ }
171+
148172function generateState ( ) : string {
149173 return Math . random ( ) . toString ( 36 ) . substring ( 2 , 15 ) ;
150174}
0 commit comments