@@ -5,37 +5,115 @@ import NextAuth from 'next-auth';
55import authConfig from './auth.config' ;
66import mongoClientPromise from './lib/mongo-client' ;
77
8+ // database but jwt ¯\_(ツ)_/¯
9+ // https://authjs.dev/guides/edge-compatibility#middleware
10+
811export const { handlers, signIn, signOut, auth } = NextAuth ( {
912 adapter : MongoDBAdapter ( mongoClientPromise ) ,
1013 session : { strategy : 'jwt' } ,
1114 ...authConfig ,
1215 events : {
13- signIn : async ( { account, user, profile, isNewUser } ) => {
14- if ( isNewUser && account ?. provider === 'github' ) {
16+ signIn : async ( { account, user, profile } ) => {
17+ // profile - user entity from github
18+ // user - user entity from the database
19+ // account - OAuth account information
20+
21+ if ( account ) {
1522 const client = await mongoClientPromise ;
16- const db = client . db ( 'auth' ) ;
17-
18- await db
19- . collection ( 'accounts' )
20- . updateOne (
21- { userId : new ObjectId ( user . id ) } ,
22- { $set : { githubId : profile ?. node_id , githubLogin : profile ?. login } } ,
23- ) ;
23+ const db = client . db ( 'auth' ) . collection ( 'accounts' ) ;
24+
25+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
26+ const { expires_in, refresh_token_expires_in, ...accountData } = account ;
27+
28+ await db . updateOne (
29+ { userId : new ObjectId ( user . id ) , provider : 'github' } ,
30+ { $set : { ...accountData , githubId : profile ?. node_id , githubLogin : profile ?. login } } ,
31+ ) ;
2432 }
2533 } ,
2634 } ,
2735 callbacks : {
2836 async jwt ( { token, profile, account } ) {
29- if ( profile && account ?. provider === 'github' ) {
30- token . githubLogin = profile . login ;
37+ // token: the existing token (an object you can modify)
38+ // user: only present on first sign-in (the returned user object)
39+ // account: only on first sign-in (OAuth/account info)
40+ // profile: only on first sign-in (OAuth profile)
41+ // isNewUser: boolean flag on first sign-in
42+
43+ token . error = undefined ;
44+
45+ if ( account && profile ) {
46+ // First-time login, save the `access_token`, its expiry and the `refresh_token`
47+ return {
48+ ...token ,
49+ access_token : account . access_token ,
50+ expires_at : account . expires_at ,
51+ refresh_token : account . refresh_token ,
52+ githubLogin : profile . login ,
53+ } ;
54+ } else if ( Date . now ( ) < ( token . expires_at as number ) * 1000 ) {
55+ // Subsequent logins, but the `access_token` is still valid
56+ return token ;
57+ }
58+
59+ // Subsequent logins, but the `access_token` has expired, try to refresh it
60+ if ( ! token . refresh_token ) {
61+ throw new TypeError ( 'Missing refresh_token' ) ;
62+ }
63+
64+ try {
65+ const response = await fetch ( 'https://github.com/login/oauth/access_token' , {
66+ method : 'POST' ,
67+ headers : {
68+ 'Content-Type' : 'application/x-www-form-urlencoded' ,
69+ Accept : 'application/json' ,
70+ } ,
71+ body : new URLSearchParams ( {
72+ client_id : process . env . AUTH_GITHUB_ID ! ,
73+ client_secret : process . env . AUTH_GITHUB_SECRET ! ,
74+ grant_type : 'refresh_token' ,
75+ refresh_token : token . refresh_token as string ,
76+ } ) ,
77+ } ) ;
78+
79+ const tokensOrError = await response . json ( ) ;
80+
81+ if ( ! response . ok ) {
82+ throw tokensOrError ;
83+ }
84+
85+ const newTokens = tokensOrError as {
86+ access_token : string ;
87+ expires_in : number ;
88+ refresh_token ?: string ;
89+ } ;
90+
91+ const newData = {
92+ access_token : newTokens . access_token ,
93+ expires_at : Math . floor ( Date . now ( ) / 1000 + newTokens . expires_in ) ,
94+ refresh_token : newTokens . refresh_token ,
95+ } ;
96+
97+ const client = await mongoClientPromise ;
98+ const db = client . db ( 'auth' ) . collection ( 'accounts' ) ;
99+
100+ await db . updateOne ( { githubLogin : token . githubLogin , provider : 'github' } , { $set : newData } ) ;
101+
102+ return { ...token , ...newData } ;
103+ } catch {
104+ // If we fail to refresh the token, return an error so we can handle it on the page
105+ token . error = 'RefreshTokenError' ;
106+ return token ;
31107 }
32- return token ;
33108 } ,
34109
35110 async session ( { session, token } ) {
36- if ( token ?. githubLogin ) {
37- session . user . githubLogin = token . githubLogin as string ;
38- }
111+ // session: what will be returned to the client
112+ // token: the latest JWT (as returned by your jwt() callback)
113+ // user: database user (if you’re using a database session strategy)
114+
115+ session . error = token . error as string | undefined ;
116+ session . user . githubLogin = token . githubLogin as string ;
39117 return session ;
40118 } ,
41119 } ,
0 commit comments