@@ -2,12 +2,15 @@ import { type NextRequest } from 'next/server'
22import { cookies } from 'next/headers'
33import { db } from '@/lib/db/client'
44import { users , accounts , tasks , connectors , keys } from '@/lib/db/schema'
5- import { eq , and } from 'drizzle-orm'
5+ import { eq , and , inArray } from 'drizzle-orm'
66import { getAppBaseUrl , getGitHubClientId } from '@/lib/auth/oauth'
77import { createGitHubSession , saveSession } from '@/lib/session/create-github'
88import { encrypt } from '@/lib/crypto'
99import { generateId } from '@/lib/utils/id'
10+ import { planUserKeyMerge } from '@/lib/auth/account-merge'
11+ import { getAuthCookiePolicyFromRequest } from '@/lib/auth/cookie-policy'
1012import {
13+ GITHUB_AUTH_BROADCAST_CHANNEL ,
1114 GITHUB_AUTH_ERROR_MESSAGE_TYPE ,
1215 GITHUB_AUTH_POPUP_COOKIE ,
1316 GITHUB_AUTH_POPUP_VALUE ,
@@ -37,6 +40,7 @@ function cleanupGitHubAuthCookies(cookieStore: CookieStore): void {
3740function createGitHubPopupResponse ( req : NextRequest , status : PopupStatus , responseInit ?: ResponseInit ) : Response {
3841 const origin = new URL ( getAppBaseUrl ( req ) ) . origin
3942 const messageType = status === 'success' ? GITHUB_AUTH_SUCCESS_MESSAGE_TYPE : GITHUB_AUTH_ERROR_MESSAGE_TYPE
43+ const closeDelayMs = 250
4044 const html = `<!doctype html>
4145<html>
4246 <head>
@@ -45,8 +49,32 @@ function createGitHubPopupResponse(req: NextRequest, status: PopupStatus, respon
4549 </head>
4650 <body>
4751 <script>
48- window.opener?.postMessage({ type: ${ JSON . stringify ( messageType ) } , status: ${ JSON . stringify ( status ) } }, ${ JSON . stringify ( origin ) } );
49- window.close();
52+ const githubAuthMessage = { type: ${ JSON . stringify ( messageType ) } , status: ${ JSON . stringify ( status ) } };
53+ const githubAuthOrigin = ${ JSON . stringify ( origin ) } ;
54+
55+ function postGitHubAuthMessage(target) {
56+ try {
57+ target?.postMessage(githubAuthMessage, githubAuthOrigin);
58+ } catch {}
59+ }
60+
61+ postGitHubAuthMessage(window.opener);
62+
63+ try {
64+ if (window.opener?.frames) {
65+ for (let index = 0; index < window.opener.frames.length; index += 1) {
66+ postGitHubAuthMessage(window.opener.frames[index]);
67+ }
68+ }
69+ } catch {}
70+
71+ try {
72+ const channel = new BroadcastChannel(${ JSON . stringify ( GITHUB_AUTH_BROADCAST_CHANNEL ) } );
73+ channel.postMessage(githubAuthMessage);
74+ channel.close();
75+ } catch {}
76+
77+ window.setTimeout(() => window.close(), ${ closeDelayMs } );
5078 </script>
5179 </body>
5280</html>`
@@ -65,6 +93,7 @@ export async function GET(req: NextRequest): Promise<Response> {
6593 const code = req . nextUrl . searchParams . get ( 'code' )
6694 const state = req . nextUrl . searchParams . get ( 'state' )
6795 const cookieStore = await cookies ( )
96+ const authCookiePolicy = getAuthCookiePolicyFromRequest ( req )
6897
6998 const popupCookie = cookieStore . get ( GITHUB_AUTH_POPUP_COOKIE ) ?. value ?? null
7099 if ( popupCookie !== GITHUB_AUTH_POPUP_VALUE ) {
@@ -161,13 +190,14 @@ export async function GET(req: NextRequest): Promise<Response> {
161190 }
162191
163192 const response = createGitHubPopupResponse ( req , 'success' )
164- await saveSession ( response , session )
193+ await saveSession ( response , session , authCookiePolicy )
165194 cleanupGitHubAuthCookies ( cookieStore )
166195
167196 return response
168197 }
169198
170199 const encryptedToken = encrypt ( tokenData . access_token )
200+ const targetUserId = storedUserId !
171201
172202 const existingAccount = await db
173203 . select ( )
@@ -178,48 +208,106 @@ export async function GET(req: NextRequest): Promise<Response> {
178208 if ( existingAccount . length > 0 ) {
179209 const connectedUserId = existingAccount [ 0 ] . userId
180210
181- if ( connectedUserId !== storedUserId ) {
211+ if ( connectedUserId !== targetUserId ) {
182212 console . info ( 'GitHub OAuth account merge started' )
183213
184- await db . update ( tasks ) . set ( { userId : storedUserId ! } ) . where ( eq ( tasks . userId , connectedUserId ) )
185- await db . update ( connectors ) . set ( { userId : storedUserId ! } ) . where ( eq ( connectors . userId , connectedUserId ) )
186- await db . update ( accounts ) . set ( { userId : storedUserId ! } ) . where ( eq ( accounts . userId , connectedUserId ) )
187- await db . update ( keys ) . set ( { userId : storedUserId ! } ) . where ( eq ( keys . userId , connectedUserId ) )
188- await db . delete ( users ) . where ( eq ( users . id , connectedUserId ) )
214+ await db . transaction ( async ( tx ) => {
215+ await tx . update ( tasks ) . set ( { userId : targetUserId } ) . where ( eq ( tasks . userId , connectedUserId ) )
216+ await tx . update ( connectors ) . set ( { userId : targetUserId } ) . where ( eq ( connectors . userId , connectedUserId ) )
217+
218+ const [ targetAccount ] = await tx
219+ . select ( { id : accounts . id } )
220+ . from ( accounts )
221+ . where ( and ( eq ( accounts . userId , targetUserId ) , eq ( accounts . provider , 'github' ) ) )
222+ . limit ( 1 )
223+
224+ const sourceKeys = await tx
225+ . select ( { id : keys . id , provider : keys . provider } )
226+ . from ( keys )
227+ . where ( eq ( keys . userId , connectedUserId ) )
228+ const targetKeys = await tx
229+ . select ( { id : keys . id , provider : keys . provider } )
230+ . from ( keys )
231+ . where ( eq ( keys . userId , targetUserId ) )
232+ const keyMergePlan = planUserKeyMerge ( { sourceKeys, targetKeys } )
233+
234+ if ( keyMergePlan . moveKeyIds . length > 0 ) {
235+ await tx . update ( keys ) . set ( { userId : targetUserId } ) . where ( inArray ( keys . id , keyMergePlan . moveKeyIds ) )
236+ }
237+
238+ if ( keyMergePlan . deleteKeyIds . length > 0 ) {
239+ await tx . delete ( keys ) . where ( inArray ( keys . id , keyMergePlan . deleteKeyIds ) )
240+ }
241+
242+ if ( targetAccount ) {
243+ await tx
244+ . update ( accounts )
245+ . set ( {
246+ accessToken : encryptedToken ,
247+ externalUserId : `${ githubUser . id } ` ,
248+ scope : tokenData . scope ,
249+ username : githubUser . login ,
250+ updatedAt : new Date ( ) ,
251+ } )
252+ . where ( eq ( accounts . id , targetAccount . id ) )
253+ await tx . delete ( accounts ) . where ( eq ( accounts . id , existingAccount [ 0 ] . id ) )
254+ } else {
255+ await tx
256+ . update ( accounts )
257+ . set ( {
258+ userId : targetUserId ,
259+ accessToken : encryptedToken ,
260+ scope : tokenData . scope ,
261+ username : githubUser . login ,
262+ updatedAt : new Date ( ) ,
263+ } )
264+ . where ( eq ( accounts . id , existingAccount [ 0 ] . id ) )
265+ }
266+
267+ await tx . delete ( users ) . where ( eq ( users . id , connectedUserId ) )
268+ } )
189269
190270 console . info ( 'GitHub OAuth account merge completed' )
191-
271+ } else {
192272 await db
193273 . update ( accounts )
194274 . set ( {
195- userId : storedUserId ! ,
196275 accessToken : encryptedToken ,
197276 scope : tokenData . scope ,
198277 username : githubUser . login ,
199278 updatedAt : new Date ( ) ,
200279 } )
201280 . where ( eq ( accounts . id , existingAccount [ 0 ] . id ) )
202- } else {
281+ }
282+ } else {
283+ const [ currentAccount ] = await db
284+ . select ( { id : accounts . id } )
285+ . from ( accounts )
286+ . where ( and ( eq ( accounts . userId , targetUserId ) , eq ( accounts . provider , 'github' ) ) )
287+ . limit ( 1 )
288+
289+ if ( currentAccount ) {
203290 await db
204291 . update ( accounts )
205292 . set ( {
293+ externalUserId : `${ githubUser . id } ` ,
206294 accessToken : encryptedToken ,
207295 scope : tokenData . scope ,
208296 username : githubUser . login ,
209297 updatedAt : new Date ( ) ,
210298 } )
211- . where ( eq ( accounts . id , existingAccount [ 0 ] . id ) )
299+ . where ( eq ( accounts . id , currentAccount . id ) )
300+ } else {
301+ await db . insert ( accounts ) . values ( {
302+ id : generateId ( 21 ) ,
303+ userId : targetUserId ,
304+ provider : 'github' ,
305+ externalUserId : `${ githubUser . id } ` ,
306+ accessToken : encryptedToken ,
307+ scope : tokenData . scope ,
308+ username : githubUser . login ,
309+ } )
212310 }
213- } else {
214- await db . insert ( accounts ) . values ( {
215- id : generateId ( 21 ) ,
216- userId : storedUserId ! ,
217- provider : 'github' ,
218- externalUserId : `${ githubUser . id } ` ,
219- accessToken : encryptedToken ,
220- scope : tokenData . scope ,
221- username : githubUser . login ,
222- } )
223311 }
224312
225313 cleanupGitHubAuthCookies ( cookieStore )
0 commit comments