1+ import { NextRequest , NextResponse } from "next/server" ;
2+ import { eq } from "drizzle-orm" ;
3+
4+ import { db } from "@/db" ;
5+ import { users } from "@/db/schema/users" ;
6+ import { signAuthToken , setAuthCookie } from "@/lib/auth" ;
7+ import { env } from "@/lib/env" ;
8+
9+ type GithubTokenResponse = {
10+ access_token : string ;
11+ token_type : string ;
12+ scope : string ;
13+ } ;
14+
15+ type GithubUser = {
16+ id : number ;
17+ login : string ;
18+ name : string | null ;
19+ avatar_url : string ;
20+ } ;
21+
22+ type GithubEmail = {
23+ email : string ;
24+ primary : boolean ;
25+ verified : boolean ;
26+ } ;
27+
28+ export 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 : env . github . clientId ! ,
44+ client_secret : env . github . clientSecret ! ,
45+ code,
46+ redirect_uri : env . github . redirectUri ! ,
47+ } ) ,
48+ }
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 ;
124+ } 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 ;
138+ }
139+ }
140+
141+ const token = signAuthToken ( {
142+ userId : user . id ,
143+ email : user . email ,
144+ role : user . role === "admin" ? "admin" : "user" ,
145+ } ) ;
146+
147+ await setAuthCookie ( token ) ;
148+
149+ return NextResponse . redirect ( new URL ( "/" , req . url ) ) ;
150+ }
0 commit comments