11import { NextRequest , NextResponse } from "next/server" ;
2- import { eq } from "drizzle-orm" ;
2+ import { and , eq } from "drizzle-orm" ;
33
44import { db } from "@/db" ;
55import { users } from "@/db/schema/users" ;
66import { signAuthToken , setAuthCookie } from "@/lib/auth" ;
77import { authEnv } from "@/lib/env/auth" ;
8+ import { consumeOAuthState } from "@/lib/auth/oauth-state" ;
89
910type GithubTokenResponse = {
10- access_token : string ;
11- token_type : string ;
12- scope : string ;
11+ access_token : string ;
12+ token_type : string ;
13+ scope : string ;
1314} ;
1415
1516type GithubUser = {
16- id : number ;
17- login : string ;
18- name : string | null ;
19- avatar_url : string ;
17+ id : number ;
18+ login : string ;
19+ name : string | null ;
20+ avatar_url : string ;
2021} ;
2122
2223type GithubEmail = {
23- email : string ;
24- primary : boolean ;
25- verified : boolean ;
24+ email : string ;
25+ primary : boolean ;
26+ verified : boolean ;
27+ } ;
28+
29+ const GITHUB_HEADERS = {
30+ "User-Agent" : "devlovers-app" ,
2631} ;
2732
2833export async function GET ( req : NextRequest ) {
29- const code = req . nextUrl . searchParams . get ( "code" ) ;
30-
31- if ( ! code ) {
32- return NextResponse . redirect ( new URL ( "/login" , req . url ) ) ;
33- }
34-
35- const tokenRes = await fetch (
36- "https://github.com/login/oauth/access_token" ,
37- {
38- method : "POST" ,
39- headers : {
40- Accept : "application/json" ,
41- } ,
42- body : new URLSearchParams ( {
43- client_id : authEnv . github . clientId ,
44- client_secret : authEnv . github . clientSecret ,
45- code,
46- redirect_uri : authEnv . github . redirectUri ,
47- } ) ,
34+ const code = req . nextUrl . searchParams . get ( "code" ) ;
35+ const state = req . nextUrl . searchParams . get ( "state" ) ;
36+
37+ if ( ! ( await consumeOAuthState ( state ) ) ) {
38+ return NextResponse . redirect ( new URL ( "/login" , req . url ) ) ;
39+ }
40+
41+ if ( ! code ) {
42+ return NextResponse . redirect ( new URL ( "/login" , req . url ) ) ;
43+ }
44+
45+ const tokenRes = await fetch (
46+ "https://github.com/login/oauth/access_token" ,
47+ {
48+ method : "POST" ,
49+ headers : {
50+ Accept : "application/json" ,
51+ } ,
52+ body : new URLSearchParams ( {
53+ client_id : authEnv . github . clientId ,
54+ client_secret : authEnv . github . clientSecret ,
55+ code,
56+ redirect_uri : authEnv . github . redirectUri ,
57+ } ) ,
58+ }
59+ ) ;
60+
61+ if ( ! tokenRes . ok ) {
62+ console . error (
63+ "GitHub token exchange failed" ,
64+ await tokenRes . text ( )
65+ ) ;
66+ return NextResponse . redirect ( new URL ( "/login" , req . url ) ) ;
67+ }
68+
69+ const tokenData = ( await tokenRes . json ( ) ) as GithubTokenResponse ;
70+
71+ const userRes = await fetch ( "https://api.github.com/user" , {
72+ headers : {
73+ ...GITHUB_HEADERS ,
74+ Authorization : `Bearer ${ tokenData . access_token } ` ,
75+ } ,
76+ } ) ;
77+
78+ if ( ! userRes . ok ) {
79+ return NextResponse . redirect ( new URL ( "/login" , req . url ) ) ;
4880 }
49- ) ;
50-
51- if ( ! tokenRes . ok ) {
52- console . error (
53- "GitHub token exchange failed" ,
54- await tokenRes . text ( )
55- ) ;
56- return NextResponse . redirect ( new URL ( "/login" , req . url ) ) ;
57- }
58-
59- const tokenData = ( await tokenRes . json ( ) ) as GithubTokenResponse ;
60-
61- const userRes = await fetch ( "https://api.github.com/user" , {
62- headers : {
63- Authorization : `Bearer ${ tokenData . access_token } ` ,
64- } ,
65- } ) ;
66-
67- if ( ! userRes . ok ) {
68- return NextResponse . redirect ( new URL ( "/login" , req . url ) ) ;
69- }
70-
71- const ghUser = ( await userRes . json ( ) ) as GithubUser ;
72-
73- const emailsRes = await fetch ( "https://api.github.com/user/emails" , {
74- headers : {
75- Authorization : `Bearer ${ tokenData . access_token } ` ,
76- } ,
77- } ) ;
78-
79- if ( ! emailsRes . ok ) {
80- return NextResponse . redirect ( new URL ( "/login" , req . url ) ) ;
81- }
82-
83- const emails = ( await emailsRes . json ( ) ) as GithubEmail [ ] ;
84-
85- const primaryEmail = emails . find (
86- ( e ) => e . primary && e . verified
87- ) ?. email ;
88-
89- if ( ! primaryEmail ) {
90- return NextResponse . redirect ( new URL ( "/login" , req . url ) ) ;
91- }
92-
93- const githubId = String ( ghUser . id ) ;
94- let user = null ;
95-
96- const [ githubUser ] = await db
97- . select ( )
98- . from ( users )
99- . where ( eq ( users . providerId , githubId ) )
100- . limit ( 1 ) ;
101-
102- if ( githubUser ) {
103- user = githubUser ;
104- } else {
105- const [ emailUser ] = await db
106- . select ( )
107- . from ( users )
108- . where ( eq ( users . email , primaryEmail ) )
109- . limit ( 1 ) ;
110-
111- if ( emailUser ) {
112- await db
113- . update ( users )
114- . set ( {
115- provider : "github" ,
116- providerId : githubId ,
117- emailVerified : emailUser . emailVerified ?? new Date ( ) ,
118- image : emailUser . image ?? ghUser . avatar_url ,
119- name : emailUser . name ?? ghUser . name ?? ghUser . login ,
120- } )
121- . where ( eq ( users . id , emailUser . id ) ) ;
122-
123- user = emailUser ;
81+
82+ const ghUser = ( await userRes . json ( ) ) as GithubUser ;
83+
84+ const emailsRes = await fetch ( "https://api.github.com/user/emails" , {
85+ headers : {
86+ ...GITHUB_HEADERS ,
87+ Authorization : `Bearer ${ tokenData . access_token } ` ,
88+ } ,
89+ } ) ;
90+
91+ if ( ! emailsRes . ok ) {
92+ return NextResponse . redirect ( new URL ( "/login" , req . url ) ) ;
93+ }
94+
95+ const emails = ( await emailsRes . json ( ) ) as GithubEmail [ ] ;
96+
97+ const primaryEmail = emails . find (
98+ ( e ) => e . primary && e . verified
99+ ) ?. email ;
100+
101+ if ( ! primaryEmail ) {
102+ return NextResponse . redirect ( new URL ( "/login" , req . url ) ) ;
103+ }
104+
105+ const githubId = String ( ghUser . id ) ;
106+ let user = null ;
107+
108+ const [ githubUser ] = await db
109+ . select ( )
110+ . from ( users )
111+ . where ( and ( eq ( users . providerId , githubId ) , eq ( users . provider , "github" ) ) )
112+ . limit ( 1 ) ;
113+
114+ if ( githubUser ) {
115+ user = githubUser ;
124116 } else {
125- const [ created ] = await db
126- . insert ( users )
127- . values ( {
128- email : primaryEmail ,
129- name : ghUser . name ?? ghUser . login ,
130- image : ghUser . avatar_url ,
131- provider : "github" ,
132- providerId : githubId ,
133- emailVerified : new Date ( ) ,
134- } )
135- . returning ( ) ;
136-
137- user = created ;
117+ const [ emailUser ] = await db
118+ . select ( )
119+ . from ( users )
120+ . where ( eq ( users . email , primaryEmail ) )
121+ . limit ( 1 ) ;
122+
123+ if ( emailUser ) {
124+ await db
125+ . update ( users )
126+ . set ( {
127+ provider : "github" ,
128+ providerId : githubId ,
129+ emailVerified : emailUser . emailVerified ?? new Date ( ) ,
130+ image : emailUser . image ?? ghUser . avatar_url ,
131+ name : emailUser . name ?? ghUser . name ?? ghUser . login ,
132+ } )
133+ . where ( eq ( users . id , emailUser . id ) ) ;
134+
135+ user = emailUser ;
136+ } else {
137+ const [ created ] = await db
138+ . insert ( users )
139+ . values ( {
140+ email : primaryEmail ,
141+ name : ghUser . name ?? ghUser . login ,
142+ image : ghUser . avatar_url ,
143+ provider : "github" ,
144+ providerId : githubId ,
145+ emailVerified : new Date ( ) ,
146+ } )
147+ . returning ( ) ;
148+
149+ user = created ;
150+ }
138151 }
139- }
140152
141- const token = signAuthToken ( {
142- userId : user . id ,
143- email : user . email ,
144- role : user . role === "admin" ? "admin" : "user" ,
145- } ) ;
153+ const token = signAuthToken ( {
154+ userId : user . id ,
155+ email : user . email ,
156+ role : user . role === "admin" ? "admin" : "user" ,
157+ } ) ;
146158
147- await setAuthCookie ( token ) ;
159+ await setAuthCookie ( token ) ;
148160
149- return NextResponse . redirect ( new URL ( "/" , req . url ) ) ;
161+ return NextResponse . redirect ( new URL ( "/" , req . url ) ) ;
150162}
0 commit comments