1
1
import { createHash , randomBytes } from 'crypto'
2
2
import jwt from '../lib/jwt'
3
3
import parseUrl from '../lib/parse-url'
4
- import cookie from './lib/cookie'
4
+ import * as cookie from './lib/cookie'
5
5
import callbackUrlHandler from './lib/callback-url-handler'
6
6
import parseProviders from './lib/providers'
7
- import events from './lib/events'
8
- import callbacks from './lib/callbacks '
7
+ import * as events from './lib/events'
8
+ import * as defaultCallbacks from './lib/defaultCallbacks '
9
9
import providers from './routes/providers'
10
10
import signin from './routes/signin'
11
11
import signout from './routes/signout'
12
12
import callback from './routes/callback'
13
13
import session from './routes/session'
14
- import pages from './pages'
14
+ import renderPage from './pages'
15
15
import adapters from '../adapters'
16
16
import logger from '../lib/logger'
17
+ import redirect from './lib/redirect'
17
18
18
19
// To work properly in production with OAuth providers the NEXTAUTH_URL
19
20
// environment variable must be set.
20
21
if ( ! process . env . NEXTAUTH_URL ) {
21
22
logger . warn ( 'NEXTAUTH_URL' , 'NEXTAUTH_URL environment variable not set' )
22
23
}
23
24
24
- async function NextAuth ( req , res , userSuppliedOptions ) {
25
+ async function NextAuthHandler ( req , res , userSuppliedOptions ) {
25
26
// To the best of my knowledge, we need to return a promise here
26
27
// to avoid early termination of calls to the serverless function
27
28
// (and then return that promise when we are done) - eslint
@@ -30,18 +31,18 @@ async function NextAuth (req, res, userSuppliedOptions) {
30
31
// This is passed to all methods that handle responses, and must be called
31
32
// when they are complete so that the serverless function knows when it is
32
33
// safe to return and that no more data will be sent.
33
- const done = resolve
34
+ // REVIEW: Why not just call res.end() as is, and remove the Promise wrapper?
35
+ res . end = ( ) => {
36
+ resolve ( )
37
+ res . end ( )
38
+ }
39
+ res . redirect = redirect ( req , res )
34
40
35
41
if ( ! req . query . nextauth ) {
36
42
const error = 'Cannot find [...nextauth].js in pages/api/auth. Make sure the filename is written correctly.'
37
43
38
44
logger . error ( 'MISSING_NEXTAUTH_API_ROUTE_ERROR' , error )
39
- res
40
- . status ( 500 )
41
- . end (
42
- `Error: ${ error } `
43
- )
44
- return done ( )
45
+ return res . status ( 500 ) . end ( `Error: ${ error } ` ) . end ( )
45
46
}
46
47
47
48
const { url, query, body } = req
@@ -56,10 +57,8 @@ async function NextAuth (req, res, userSuppliedOptions) {
56
57
csrfToken : csrfTokenFromPost
57
58
} = body
58
59
59
- // @todo refactor all existing references to site, baseUrl and basePath
60
- const parsedUrl = parseUrl ( process . env . NEXTAUTH_URL || process . env . VERCEL_URL )
61
- const baseUrl = parsedUrl . baseUrl
62
- const basePath = parsedUrl . basePath
60
+ // @todo refactor all existing references to baseUrl and basePath
61
+ const { basePath, baseUrl } = parseUrl ( process . env . NEXTAUTH_URL || process . env . VERCEL_URL )
63
62
64
63
// Parse database / adapter
65
64
let adapter
@@ -74,8 +73,10 @@ async function NextAuth (req, res, userSuppliedOptions) {
74
73
// Secret used salt cookies and tokens (e.g. for CSRF protection).
75
74
// If no secret option is specified then it creates one on the fly
76
75
// based on options passed here. A options contains unique data, such as
77
- // oAuth provider secrets and database credentials it should be sufficent.
78
- const secret = userSuppliedOptions . secret || createHash ( 'sha256' ) . update ( JSON . stringify ( { baseUrl, basePath, ...userSuppliedOptions } ) ) . digest ( 'hex' )
76
+ // OAuth provider secrets and database credentials it should be sufficent.
77
+ const secret = userSuppliedOptions . secret || createHash ( 'sha256' ) . update ( JSON . stringify ( {
78
+ baseUrl, basePath, ...userSuppliedOptions
79
+ } ) ) . digest ( 'hex' )
79
80
80
81
// Use secure cookies if the site uses HTTPS
81
82
// This being conditional allows cookies to work non-HTTPS development URLs
@@ -151,7 +152,7 @@ async function NextAuth (req, res, userSuppliedOptions) {
151
152
152
153
// Callback functions
153
154
const callbacksOptions = {
154
- ...callbacks ,
155
+ ...defaultCallbacks ,
155
156
...userSuppliedOptions . callbacks
156
157
}
157
158
@@ -188,26 +189,11 @@ async function NextAuth (req, res, userSuppliedOptions) {
188
189
cookie . set ( res , cookies . csrfToken . name , newCsrfTokenCookie , cookies . csrfToken . options )
189
190
}
190
191
191
- // Helper method for handling redirects, this is passed to all routes
192
- // @TODO Refactor into a lib instead of passing as an option
193
- // e.g. and call as redirect(req, res, url)
194
- const redirect = ( redirectUrl ) => {
195
- const reponseAsJson = ! ! ( ( req . body && req . body . json === 'true' ) )
196
- if ( reponseAsJson ) {
197
- res . json ( { url : redirectUrl } )
198
- } else {
199
- res . status ( 302 ) . setHeader ( 'Location' , redirectUrl )
200
- res . end ( )
201
- }
202
- return done ( )
203
- }
204
-
205
192
// User provided options are overriden by other options,
206
193
// except for the options with special handling above
207
194
const options = {
208
- // Defaults options can be overidden
209
- debug : false , // Enable debug messages to be displayed
210
- pages : { } , // Custom pages (e.g. sign in, sign out, errors)
195
+ debug : false ,
196
+ pages : { } ,
211
197
// Custom options override defaults
212
198
...userSuppliedOptions ,
213
199
// These computed settings can values in userSuppliedOptions but override them
@@ -220,116 +206,115 @@ async function NextAuth (req, res, userSuppliedOptions) {
220
206
cookies,
221
207
secret,
222
208
csrfToken,
223
- providers : parseProviders ( userSuppliedOptions . providers , baseUrl , basePath ) ,
209
+ providers : parseProviders ( userSuppliedOptions . providers , basePath , baseUrl ) ,
224
210
session : sessionOptions ,
225
211
jwt : jwtOptions ,
226
212
events : eventsOptions ,
227
- callbacks : callbacksOptions ,
228
- callbackUrl : baseUrl ,
229
- redirect
213
+ callbacks : callbacksOptions
230
214
}
215
+ req . options = options
231
216
232
217
// If debug enabled, set ENV VAR so that logger logs debug messages
233
- if ( options . debug === true ) { process . env . _NEXTAUTH_DEBUG = true }
218
+ if ( options . debug ) {
219
+ process . env . _NEXTAUTH_DEBUG = true
220
+ }
234
221
235
222
// Get / Set callback URL based on query param / cookie + validation
236
- options . callbackUrl = await callbackUrlHandler ( req , res , options )
223
+ const callbackUrl = await callbackUrlHandler ( req , res )
237
224
238
225
if ( req . method === 'GET' ) {
239
226
switch ( action ) {
240
227
case 'providers' :
241
- providers ( req , res , options , done )
228
+ providers ( req , res )
242
229
break
243
230
case 'session' :
244
- session ( req , res , options , done )
231
+ session ( req , res )
245
232
break
246
233
case 'csrf' :
247
234
res . json ( { csrfToken } )
248
- return done ( )
235
+ return res . end ( )
249
236
case 'signin' :
250
237
if ( options . pages . signIn ) {
251
- let redirectUrl = `${ options . pages . signIn } ${ options . pages . signIn . includes ( '?' ) ? '&' : '?' } callbackUrl=${ options . callbackUrl } `
238
+ let redirectUrl = `${ options . pages . signIn } ${ options . pages . signIn . includes ( '?' ) ? '&' : '?' } callbackUrl=${ callbackUrl } `
252
239
if ( req . query . error ) { redirectUrl = `${ redirectUrl } &error=${ req . query . error } ` }
253
- return redirect ( redirectUrl )
240
+ return res . redirect ( redirectUrl )
254
241
}
255
242
256
- pages . render ( req , res , 'signin' , { baseUrl , basePath , providers : Object . values ( options . providers ) , callbackUrl : options . callbackUrl , csrfToken } , done )
243
+ renderPage ( req , res , 'signin' , { providers : Object . values ( options . providers ) , callbackUrl, csrfToken } )
257
244
break
258
245
case 'signout' :
259
- if ( options . pages . signOut ) { return redirect ( `${ options . pages . signOut } ${ options . pages . signOut . includes ( '?' ) ? '&' : '?' } error=${ error } ` ) }
246
+ if ( options . pages . signOut ) {
247
+ return res . redirect ( `${ options . pages . signOut } ${ options . pages . signOut . includes ( '?' ) ? '&' : '?' } error=${ error } ` )
248
+ }
260
249
261
- pages . render ( req , res , 'signout' , { baseUrl , basePath , csrfToken, callbackUrl : options . callbackUrl } , done )
250
+ renderPage ( req , res , 'signout' , { csrfToken, callbackUrl } )
262
251
break
263
252
case 'callback' :
264
253
if ( provider && options . providers [ provider ] ) {
265
- callback ( req , res , options , done )
254
+ callback ( req , res )
266
255
} else {
267
- res . status ( 400 ) . end ( `Error: HTTP GET is not supported for ${ url } ` )
268
- return done ( )
256
+ return res . status ( 400 ) . end ( `Error: HTTP GET is not supported for ${ url } ` ) . end ( )
269
257
}
270
258
break
271
259
case 'verify-request' :
272
- if ( options . pages . verifyRequest ) { return redirect ( options . pages . verifyRequest ) }
260
+ if ( options . pages . verifyRequest ) { return res . redirect ( options . pages . verifyRequest ) }
273
261
274
- pages . render ( req , res , 'verify-request' , { baseUrl } , done )
262
+ renderPage ( req , res , 'verify-request' )
275
263
break
276
264
case 'error' :
277
- if ( options . pages . error ) { return redirect ( `${ options . pages . error } ${ options . pages . error . includes ( '?' ) ? '&' : '?' } error=${ error } ` ) }
265
+ if ( options . pages . error ) { return res . redirect ( `${ options . pages . error } ${ options . pages . error . includes ( '?' ) ? '&' : '?' } error=${ error } ` ) }
278
266
279
- pages . render ( req , res , 'error' , { baseUrl , basePath , error } , done )
267
+ renderPage ( req , res , 'error' , { error } )
280
268
break
281
269
default :
282
- res . status ( 404 ) . end ( )
283
- return done ( )
270
+ return res . status ( 404 ) . end ( )
284
271
}
285
272
} else if ( req . method === 'POST' ) {
286
273
switch ( action ) {
287
274
case 'signin' :
288
275
// Verified CSRF Token required for all sign in routes
289
276
if ( ! csrfTokenVerified ) {
290
- return redirect ( `${ baseUrl } ${ basePath } /signin?csrf=true` )
277
+ return res . redirect ( `${ baseUrl } ${ basePath } /signin?csrf=true` )
291
278
}
292
279
293
280
if ( provider && options . providers [ provider ] ) {
294
- signin ( req , res , options , done )
281
+ signin ( req , res )
295
282
}
296
283
break
297
284
case 'signout' :
298
285
// Verified CSRF Token required for signout
299
286
if ( ! csrfTokenVerified ) {
300
- return redirect ( `${ baseUrl } ${ basePath } /signout?csrf=true` )
287
+ return res . redirect ( `${ baseUrl } ${ basePath } /signout?csrf=true` )
301
288
}
302
289
303
- signout ( req , res , options , done )
290
+ signout ( req , res )
304
291
break
305
292
case 'callback' :
306
293
if ( provider && options . providers [ provider ] ) {
307
294
// Verified CSRF Token required for credentials providers only
308
295
if ( options . providers [ provider ] . type === 'credentials' && ! csrfTokenVerified ) {
309
- return redirect ( `${ baseUrl } ${ basePath } /signin?csrf=true` )
296
+ return res . redirect ( `${ baseUrl } ${ basePath } /signin?csrf=true` )
310
297
}
311
298
312
- callback ( req , res , options , done )
299
+ callback ( req , res )
313
300
} else {
314
- res . status ( 400 ) . end ( `Error: HTTP POST is not supported for ${ url } ` )
315
- return done ( )
301
+ return res . status ( 400 ) . end ( `Error: HTTP POST is not supported for ${ url } ` ) . end ( )
316
302
}
317
303
break
318
304
default :
319
- res . status ( 400 ) . end ( `Error: HTTP POST is not supported for ${ url } ` )
320
- return done ( )
305
+ return res . status ( 400 ) . end ( `Error: HTTP POST is not supported for ${ url } ` ) . end ( )
321
306
}
322
307
} else {
323
- res . status ( 400 ) . end ( `Error: HTTP ${ req . method } is not supported for ${ url } ` )
324
- return done ( )
308
+ return res . status ( 400 ) . end ( `Error: HTTP ${ req . method } is not supported for ${ url } ` ) . end ( )
325
309
}
326
310
} )
327
311
}
328
312
329
- export default async ( ...args ) => {
313
+ /** Tha main entry point to next-auth */
314
+ export default async function NextAuth ( ...args ) {
330
315
if ( args . length === 1 ) {
331
- return ( req , res ) => NextAuth ( req , res , args [ 0 ] )
316
+ return ( req , res ) => NextAuthHandler ( req , res , args [ 0 ] )
332
317
}
333
318
334
- return NextAuth ( ...args )
319
+ return NextAuthHandler ( ...args )
335
320
}
0 commit comments