Skip to content

Commit f2ad693

Browse files
balazsorban44Balazs Orban
and
Balazs Orban
authored
refactor: code base improvements (#959)
* chore: fix casing of OAuth * refacotr: simplify default callbacks lib file * refactor: use native URL instead of string concats * refactor: move redirect to res.redirect, done to res.end * refactor: move options to req * refactor: improve IntelliSense, name all functions * fix(lint): fix lint errors * refactor: remove jwt-decode dependency * refactor: refactor some callbacks to Promises * revert: "refactor: use native URL instead of string concats" Refs: 690c55b * chore: misc changes Co-authored-by: Balazs Orban <[email protected]>
1 parent ca06976 commit f2ad693

27 files changed

+433
-461
lines changed

package-lock.json

Lines changed: 0 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@
4747
"futoin-hkdf": "^1.3.2",
4848
"jose": "^1.27.2",
4949
"jsonwebtoken": "^8.5.1",
50-
"jwt-decode": "^2.2.0",
5150
"nodemailer": "^6.4.16",
5251
"oauth": "^0.9.15",
5352
"preact": "^10.4.1",

src/lib/errors.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class CreateUserError extends UnknownError {
2525
}
2626

2727
// Thrown when an Email address is already associated with an account
28-
// but the user is trying an oAuth account that is not linked to it.
28+
// but the user is trying an OAuth account that is not linked to it.
2929
class AccountNotLinkedError extends UnknownError {
3030
constructor (message) {
3131
super(message)

src/lib/parse-url.js

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1-
// Simple universal (client/server) function to split host and path
2-
// We use this rather than a library because we need to use the same logic both
3-
// client and server side and we only need to parse out the host and path, while
4-
// supporting a default value, so a simple split is sufficent.
5-
export default (url) => {
1+
/**
2+
* Simple universal (client/server) function to split host and path
3+
* We use this rather than a library because we need to use the same logic both
4+
* client and server side and we only need to parse out the host and path, while
5+
* supporting a default value, so a simple split is sufficent.
6+
* @param {string} url
7+
*/
8+
export default function parseUrl (url) {
69
// Default values
710
const defaultHost = 'http://localhost:3000'
811
const defaultPath = '/api/auth'
@@ -20,8 +23,5 @@ export default (url) => {
2023
const baseUrl = _host ? `${protocol}://${_host}` : defaultHost
2124
const basePath = _path.length > 0 ? `/${_path.join('/')}` : defaultPath
2225

23-
return {
24-
baseUrl,
25-
basePath
26-
}
26+
return { baseUrl, basePath }
2727
}

src/server/index.js

Lines changed: 59 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,28 @@
11
import { createHash, randomBytes } from 'crypto'
22
import jwt from '../lib/jwt'
33
import parseUrl from '../lib/parse-url'
4-
import cookie from './lib/cookie'
4+
import * as cookie from './lib/cookie'
55
import callbackUrlHandler from './lib/callback-url-handler'
66
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'
99
import providers from './routes/providers'
1010
import signin from './routes/signin'
1111
import signout from './routes/signout'
1212
import callback from './routes/callback'
1313
import session from './routes/session'
14-
import pages from './pages'
14+
import renderPage from './pages'
1515
import adapters from '../adapters'
1616
import logger from '../lib/logger'
17+
import redirect from './lib/redirect'
1718

1819
// To work properly in production with OAuth providers the NEXTAUTH_URL
1920
// environment variable must be set.
2021
if (!process.env.NEXTAUTH_URL) {
2122
logger.warn('NEXTAUTH_URL', 'NEXTAUTH_URL environment variable not set')
2223
}
2324

24-
async function NextAuth (req, res, userSuppliedOptions) {
25+
async function NextAuthHandler (req, res, userSuppliedOptions) {
2526
// To the best of my knowledge, we need to return a promise here
2627
// to avoid early termination of calls to the serverless function
2728
// (and then return that promise when we are done) - eslint
@@ -30,18 +31,18 @@ async function NextAuth (req, res, userSuppliedOptions) {
3031
// This is passed to all methods that handle responses, and must be called
3132
// when they are complete so that the serverless function knows when it is
3233
// 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)
3440

3541
if (!req.query.nextauth) {
3642
const error = 'Cannot find [...nextauth].js in pages/api/auth. Make sure the filename is written correctly.'
3743

3844
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()
4546
}
4647

4748
const { url, query, body } = req
@@ -56,10 +57,8 @@ async function NextAuth (req, res, userSuppliedOptions) {
5657
csrfToken: csrfTokenFromPost
5758
} = body
5859

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)
6362

6463
// Parse database / adapter
6564
let adapter
@@ -74,8 +73,10 @@ async function NextAuth (req, res, userSuppliedOptions) {
7473
// Secret used salt cookies and tokens (e.g. for CSRF protection).
7574
// If no secret option is specified then it creates one on the fly
7675
// 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')
7980

8081
// Use secure cookies if the site uses HTTPS
8182
// This being conditional allows cookies to work non-HTTPS development URLs
@@ -151,7 +152,7 @@ async function NextAuth (req, res, userSuppliedOptions) {
151152

152153
// Callback functions
153154
const callbacksOptions = {
154-
...callbacks,
155+
...defaultCallbacks,
155156
...userSuppliedOptions.callbacks
156157
}
157158

@@ -188,26 +189,11 @@ async function NextAuth (req, res, userSuppliedOptions) {
188189
cookie.set(res, cookies.csrfToken.name, newCsrfTokenCookie, cookies.csrfToken.options)
189190
}
190191

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-
205192
// User provided options are overriden by other options,
206193
// except for the options with special handling above
207194
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: {},
211197
// Custom options override defaults
212198
...userSuppliedOptions,
213199
// These computed settings can values in userSuppliedOptions but override them
@@ -220,116 +206,115 @@ async function NextAuth (req, res, userSuppliedOptions) {
220206
cookies,
221207
secret,
222208
csrfToken,
223-
providers: parseProviders(userSuppliedOptions.providers, baseUrl, basePath),
209+
providers: parseProviders(userSuppliedOptions.providers, basePath, baseUrl),
224210
session: sessionOptions,
225211
jwt: jwtOptions,
226212
events: eventsOptions,
227-
callbacks: callbacksOptions,
228-
callbackUrl: baseUrl,
229-
redirect
213+
callbacks: callbacksOptions
230214
}
215+
req.options = options
231216

232217
// 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+
}
234221

235222
// 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)
237224

238225
if (req.method === 'GET') {
239226
switch (action) {
240227
case 'providers':
241-
providers(req, res, options, done)
228+
providers(req, res)
242229
break
243230
case 'session':
244-
session(req, res, options, done)
231+
session(req, res)
245232
break
246233
case 'csrf':
247234
res.json({ csrfToken })
248-
return done()
235+
return res.end()
249236
case 'signin':
250237
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}`
252239
if (req.query.error) { redirectUrl = `${redirectUrl}&error=${req.query.error}` }
253-
return redirect(redirectUrl)
240+
return res.redirect(redirectUrl)
254241
}
255242

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 })
257244
break
258245
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+
}
260249

261-
pages.render(req, res, 'signout', { baseUrl, basePath, csrfToken, callbackUrl: options.callbackUrl }, done)
250+
renderPage(req, res, 'signout', { csrfToken, callbackUrl })
262251
break
263252
case 'callback':
264253
if (provider && options.providers[provider]) {
265-
callback(req, res, options, done)
254+
callback(req, res)
266255
} 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()
269257
}
270258
break
271259
case 'verify-request':
272-
if (options.pages.verifyRequest) { return redirect(options.pages.verifyRequest) }
260+
if (options.pages.verifyRequest) { return res.redirect(options.pages.verifyRequest) }
273261

274-
pages.render(req, res, 'verify-request', { baseUrl }, done)
262+
renderPage(req, res, 'verify-request')
275263
break
276264
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}`) }
278266

279-
pages.render(req, res, 'error', { baseUrl, basePath, error }, done)
267+
renderPage(req, res, 'error', { error })
280268
break
281269
default:
282-
res.status(404).end()
283-
return done()
270+
return res.status(404).end()
284271
}
285272
} else if (req.method === 'POST') {
286273
switch (action) {
287274
case 'signin':
288275
// Verified CSRF Token required for all sign in routes
289276
if (!csrfTokenVerified) {
290-
return redirect(`${baseUrl}${basePath}/signin?csrf=true`)
277+
return res.redirect(`${baseUrl}${basePath}/signin?csrf=true`)
291278
}
292279

293280
if (provider && options.providers[provider]) {
294-
signin(req, res, options, done)
281+
signin(req, res)
295282
}
296283
break
297284
case 'signout':
298285
// Verified CSRF Token required for signout
299286
if (!csrfTokenVerified) {
300-
return redirect(`${baseUrl}${basePath}/signout?csrf=true`)
287+
return res.redirect(`${baseUrl}${basePath}/signout?csrf=true`)
301288
}
302289

303-
signout(req, res, options, done)
290+
signout(req, res)
304291
break
305292
case 'callback':
306293
if (provider && options.providers[provider]) {
307294
// Verified CSRF Token required for credentials providers only
308295
if (options.providers[provider].type === 'credentials' && !csrfTokenVerified) {
309-
return redirect(`${baseUrl}${basePath}/signin?csrf=true`)
296+
return res.redirect(`${baseUrl}${basePath}/signin?csrf=true`)
310297
}
311298

312-
callback(req, res, options, done)
299+
callback(req, res)
313300
} 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()
316302
}
317303
break
318304
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()
321306
}
322307
} 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()
325309
}
326310
})
327311
}
328312

329-
export default async (...args) => {
313+
/** Tha main entry point to next-auth */
314+
export default async function NextAuth (...args) {
330315
if (args.length === 1) {
331-
return (req, res) => NextAuth(req, res, args[0])
316+
return (req, res) => NextAuthHandler(req, res, args[0])
332317
}
333318

334-
return NextAuth(...args)
319+
return NextAuthHandler(...args)
335320
}

0 commit comments

Comments
 (0)