11
22import { type NextRequest , NextResponse } from 'next/server' ;
3- import { auth } from '@/lib/authEdge' ;
4- import { storeUserGithubOAuthToken } from '@/lib/db' ;
53import { Octokit } from 'octokit' ;
4+ import { storeUserGithubOAuthToken , getUserByEmail , createUser } from '@/lib/db' ;
5+ import { createSessionForUser } from '@/lib/authService' ;
6+ import type { User } from '@/types' ;
67
78export async function GET ( request : NextRequest ) {
89 const searchParams = request . nextUrl . searchParams ;
@@ -15,97 +16,99 @@ export async function GET(request: NextRequest) {
1516
1617 if ( ! GITHUB_CLIENT_ID || ! GITHUB_CLIENT_SECRET || ! NEXT_PUBLIC_APP_URL ) {
1718 console . error ( '[GitHub OAuth Callback] OAuth environment variables not configured.' ) ;
18- return NextResponse . redirect ( new URL ( '/dashboard ?error=oauth_config_error' , request . url ) ) ;
19+ return NextResponse . redirect ( new URL ( '/login ?error=oauth_config_error' , request . url ) ) ;
1920 }
2021
2122 const storedStateCookie = request . cookies . get ( 'github_oauth_state' ) ;
22- request . cookies . delete ( 'github_oauth_state' ) ; // Clean up state cookie
23+ request . cookies . delete ( 'github_oauth_state' ) ;
2324
2425 if ( ! storedStateCookie ) {
2526 console . error ( '[GitHub OAuth Callback] Missing OAuth state cookie.' ) ;
26- return NextResponse . redirect ( new URL ( '/dashboard ?error=oauth_state_missing' , request . url ) ) ;
27+ return NextResponse . redirect ( new URL ( '/login ?error=oauth_state_missing' , request . url ) ) ;
2728 }
2829
2930 let storedStateData ;
3031 try {
3132 storedStateData = JSON . parse ( storedStateCookie . value ) ;
3233 } catch ( e ) {
3334 console . error ( '[GitHub OAuth Callback] Error parsing OAuth state cookie:' , e ) ;
34- return NextResponse . redirect ( new URL ( '/dashboard ?error=oauth_state_invalid_parse' , request . url ) ) ;
35+ return NextResponse . redirect ( new URL ( '/login ?error=oauth_state_invalid_parse' , request . url ) ) ;
3536 }
3637
3738 if ( ! stateFromGitHub || stateFromGitHub !== storedStateData . csrf ) {
3839 console . error ( '[GitHub OAuth Callback] OAuth state mismatch.' , { stateFromGitHub, storedStateCSRF : storedStateData . csrf } ) ;
39- return NextResponse . redirect ( new URL ( '/dashboard?error=oauth_state_mismatch' , request . url ) ) ;
40- }
41-
42- const session = await auth ( ) ;
43- if ( ! session ?. user ?. uuid ) {
44- console . error ( '[GitHub OAuth Callback] No active FlowUp user session found.' ) ;
45- return NextResponse . redirect ( new URL ( '/login?error=oauth_no_session' , request . url ) ) ;
40+ return NextResponse . redirect ( new URL ( '/login?error=oauth_state_mismatch' , request . url ) ) ;
4641 }
4742
4843 if ( ! code ) {
49- console . error ( '[GitHub OAuth Callback] Missing authorization code from GitHub.' ) ;
5044 const error = searchParams . get ( 'error' ) ;
5145 const errorDescription = searchParams . get ( 'error_description' ) ;
52- return NextResponse . redirect ( new URL ( `/dashboard?error=oauth_missing_code&gh_error=${ error || '' } &gh_desc=${ errorDescription || '' } ` , request . url ) ) ;
46+ console . error ( `[GitHub OAuth Callback] Authorization failed on GitHub's side. Error: ${ error } , Desc: ${ errorDescription } ` ) ;
47+ return NextResponse . redirect ( new URL ( `/login?error=oauth_provider_error&message=${ encodeURIComponent ( errorDescription || error || 'Unknown GitHub error' ) } ` , request . url ) ) ;
5348 }
5449
5550 try {
56- console . log ( '[GitHub OAuth Callback] Exchanging code for access token...' ) ;
5751 const tokenResponse = await fetch ( 'https://github.com/login/oauth/access_token' , {
5852 method : 'POST' ,
59- headers : {
60- 'Content-Type' : 'application/json' ,
61- 'Accept' : 'application/json' ,
62- } ,
53+ headers : { 'Content-Type' : 'application/json' , 'Accept' : 'application/json' } ,
6354 body : JSON . stringify ( {
6455 client_id : GITHUB_CLIENT_ID ,
6556 client_secret : GITHUB_CLIENT_SECRET ,
66- code : code ,
57+ code,
6758 redirect_uri : `${ NEXT_PUBLIC_APP_URL } /api/auth/github/oauth/callback` ,
6859 } ) ,
6960 } ) ;
7061
7162 if ( ! tokenResponse . ok ) {
72- const errorBody = await tokenResponse . text ( ) ;
73- console . error ( '[GitHub OAuth Callback] Error exchanging code for token:' , tokenResponse . status , errorBody ) ;
74- throw new Error ( `GitHub token exchange failed with status ${ tokenResponse . status } : ${ errorBody } ` ) ;
63+ throw new Error ( `GitHub token exchange failed: ${ await tokenResponse . text ( ) } ` ) ;
7564 }
76-
7765 const tokenData = await tokenResponse . json ( ) ;
78-
7966 if ( tokenData . error || ! tokenData . access_token ) {
80- console . error ( '[GitHub OAuth Callback] GitHub returned error or no access_token:' , tokenData ) ;
8167 throw new Error ( tokenData . error_description || 'Failed to retrieve access token from GitHub.' ) ;
8268 }
8369
70+ const octokit = new Octokit ( { auth : tokenData . access_token } ) ;
71+ const { data : githubUser } = await octokit . rest . users . getAuthenticated ( ) ;
72+
73+ // Find primary, verified email
74+ const { data : emails } = await octokit . rest . users . listEmailsForAuthenticatedUser ( ) ;
75+ const primaryEmail = emails . find ( email => email . primary && email . verified ) ?. email ;
76+
77+ if ( ! primaryEmail ) {
78+ return NextResponse . redirect ( new URL ( '/login?error=github_no_verified_email' , request . url ) ) ;
79+ }
80+
81+ let appUser : ( User & { hashedPassword ?: string } ) | null = await getUserByEmail ( primaryEmail ) ;
82+
83+ if ( ! appUser ) {
84+ console . log ( `[GitHub OAuth Callback] No user found for email ${ primaryEmail } . Creating new user.` ) ;
85+ const newUserInfo = await createUser ( githubUser . name || githubUser . login , primaryEmail ) ;
86+ appUser = { ...newUserInfo } ;
87+ } else {
88+ console . log ( `[GitHub OAuth Callback] Found existing user for email ${ primaryEmail } . Logging in.` ) ;
89+ }
90+
91+ if ( ! appUser || ! appUser . uuid ) {
92+ throw new Error ( "User session could not be established after DB operation." ) ;
93+ }
94+
95+ const { hashedPassword, ...userToReturn } = appUser ;
96+
97+ await createSessionForUser ( userToReturn ) ;
8498 await storeUserGithubOAuthToken (
85- session . user . uuid ,
99+ userToReturn . uuid ,
86100 tokenData . access_token ,
87101 tokenData . scope ,
88102 tokenData . token_type ,
89- tokenData . refresh_token , // May not always be present
90- tokenData . expires_in // May not always be present
103+ tokenData . refresh_token ,
104+ tokenData . expires_in
91105 ) ;
92- console . log ( `[GitHub OAuth Callback] Stored OAuth token for FlowUp user ${ session . user . uuid } .` ) ;
93-
94- // Determine redirect URL from stored state
95- let redirectPath = storedStateData . redirectTo || '/dashboard' ;
96- if ( storedStateData . projectUuid ) {
97- redirectPath = `/projects/${ storedStateData . projectUuid } ?tab=codespace&oauth_status=success` ;
98- } else {
99- const redirectUrlObj = new URL ( redirectPath , request . nextUrl . origin ) ;
100- redirectUrlObj . searchParams . set ( 'oauth_status' , 'success' ) ;
101- redirectPath = `${ redirectUrlObj . pathname } ${ redirectUrlObj . search } ` ;
102- }
103106
104- console . log ( `[GitHub OAuth Callback] Redirecting to: ${ redirectPath } ` ) ;
105- return NextResponse . redirect ( new URL ( redirectPath , request . url ) ) ;
107+ console . log ( `[GitHub OAuth Callback] Successfully logged in/signed up user ${ userToReturn . email } . Redirecting to dashboard. ` ) ;
108+ return NextResponse . redirect ( new URL ( '/dashboard' , request . url ) ) ;
106109
107110 } catch ( error : any ) {
108111 console . error ( '[GitHub OAuth Callback] Error in callback:' , error ) ;
109- return NextResponse . redirect ( new URL ( `/dashboard ?error=oauth_callback_error&message=${ encodeURIComponent ( error . message || 'Unknown error' ) } ` , request . url ) ) ;
112+ return NextResponse . redirect ( new URL ( `/login ?error=oauth_callback_error&message=${ encodeURIComponent ( error . message || 'Unknown error' ) } ` , request . url ) ) ;
110113 }
111114}
0 commit comments