From 9606173b465799816e5f5258a84df274424828d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Tue, 27 Apr 2021 21:45:55 +0200 Subject: [PATCH 01/10] feat(adapter): take away error handling from adapter --- src/adapters/error-handler.js | 146 ++++++++++++++++++++++++++++++++++ src/server/index.js | 3 +- 2 files changed, 148 insertions(+), 1 deletion(-) create mode 100644 src/adapters/error-handler.js diff --git a/src/adapters/error-handler.js b/src/adapters/error-handler.js new file mode 100644 index 0000000000..0fdb461c18 --- /dev/null +++ b/src/adapters/error-handler.js @@ -0,0 +1,146 @@ +import { + CreateSessionError, + GetSessionError, + UpdateSessionError, + DeleteSessionError, + CreateUserError, + GetUserByEmailError, + GetUserByIdError, + GetUserByProviderAccountIdError, + UpdateUserError, + DeleteUserError, + CreateVerificationRequestError, + GetVerificationRequestError, + DeleteVerificationRequestError, + LinkAccountError, + UnlinkAccountError, +} from "../lib/errors" + +/** + * Takes away the error handling responsibility + * from the actual adapter. + * @param {import("types/adapters").Adapter | undefined} adapter + * @return {import("types/adapters").Adapter | undefined} + */ +export default function adapterErrorHandler(adapter) { + if (!adapter) { + return + } + return function Adapter(...args) { + const _adapter = adapter(...args) + return { + async getAdapter(options) { + const _getAdapter = await _adapter.getAdapter(options) + return { + async getSession(...args) { + try { + return await _getAdapter.getSession(...args) + } catch (error) { + throw new GetSessionError(error) + } + }, + async createSession(...args) { + try { + return await _getAdapter.createSession(...args) + } catch (error) { + throw new CreateSessionError(error) + } + }, + async updateSession(...args) { + try { + return await _getAdapter.updateSession(...args) + } catch (error) { + throw new UpdateSessionError(error) + } + }, + async deleteSession(...args) { + try { + return await _getAdapter.deleteSession(...args) + } catch (error) { + throw new DeleteSessionError(error) + } + }, + async createUser(...args) { + try { + return await _getAdapter.createUser(...args) + } catch (error) { + throw new CreateUserError(error) + } + }, + async getUser(...args) { + try { + return await _getAdapter.getUser(...args) + } catch (error) { + throw new GetUserByIdError(error) + } + }, + async getUserByEmail(...args) { + try { + return await _getAdapter.getUserByEmail(...args) + } catch (error) { + throw new GetUserByEmailError(error) + } + }, + async getUserByProviderAccountId(...args) { + try { + return await _getAdapter.getUserByProviderAccountId(...args) + } catch (error) { + throw new GetUserByProviderAccountIdError(error) + } + }, + async deleteUser(...args) { + try { + return await _getAdapter.deleteUser?.(...args) + } catch (error) { + throw new DeleteUserError(error) + } + }, + async updateUser(...args) { + try { + return await _getAdapter.updateUser(...args) + } catch (error) { + throw new UpdateUserError(error) + } + }, + async createVerificationRequest(...args) { + try { + return await _getAdapter.createVerificationRequest?.(...args) + } catch (error) { + throw new CreateVerificationRequestError(error) + } + }, + async getVerificationRequest(...args) { + try { + return ( + (await _getAdapter.getVerificationRequest?.(...args)) ?? null + ) + } catch (error) { + throw new GetVerificationRequestError(error) + } + }, + async deleteVerificationRequest(...args) { + try { + return await _getAdapter.deleteVerificationRequest?.(...args) + } catch (error) { + throw new DeleteVerificationRequestError(error) + } + }, + async linkAccount(...args) { + try { + return await _getAdapter.linkAccount(...args) + } catch (error) { + throw new LinkAccountError(error) + } + }, + async unlinkAccont(...args) { + try { + return await _getAdapter.unlinkAccont?.(...args) + } catch (error) { + throw new UnlinkAccountError(error) + } + }, + } + }, + } + } +} diff --git a/src/server/index.js b/src/server/index.js index be963b0df0..5065cfab02 100644 --- a/src/server/index.js +++ b/src/server/index.js @@ -14,6 +14,7 @@ import extendRes from './lib/extend-res' import csrfTokenHandler from './lib/csrf-token-handler' import * as pkce from './lib/oauth/pkce-handler' import * as state from './lib/oauth/state-handler' +import adapterErrorHandler from 'src/adapters/error-handler' // To work properly in production with OAuth providers the NEXTAUTH_URL // environment variable must be set. @@ -86,7 +87,7 @@ async function NextAuthHandler (req, res, userOptions) { // Parse database / adapter // If adapter is provided, use it (advanced usage, overrides database) // If database URI or config object is provided, use it (simple usage) - const adapter = userOptions.adapter ?? (userOptions.database && adapters.Default(userOptions.database)) + const adapter = adapterErrorHandler(userOptions.adapter ?? (userOptions.database && adapters.Default(userOptions.database))) // User provided options are overriden by other options, // except for the options with special handling above From ad10af1c1a8f149ef3d06763218698054f2cfa5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Fri, 30 Apr 2021 19:16:11 +0200 Subject: [PATCH 02/10] refactor: wrap getAdapter calls with error handler --- src/adapters/error-handler.js | 155 ++-------------- src/server/index.js | 176 ++++++++++-------- src/server/lib/callback-handler.js | 71 +++++--- src/server/lib/signin/email.js | 26 ++- src/server/routes/callback.js | 278 +++++++++++++++++++++-------- src/server/routes/session.js | 69 ++++--- src/server/routes/signin.js | 56 +++--- src/server/routes/signout.js | 26 +-- 8 files changed, 489 insertions(+), 368 deletions(-) diff --git a/src/adapters/error-handler.js b/src/adapters/error-handler.js index 0fdb461c18..0099ad4298 100644 --- a/src/adapters/error-handler.js +++ b/src/adapters/error-handler.js @@ -1,146 +1,21 @@ -import { - CreateSessionError, - GetSessionError, - UpdateSessionError, - DeleteSessionError, - CreateUserError, - GetUserByEmailError, - GetUserByIdError, - GetUserByProviderAccountIdError, - UpdateUserError, - DeleteUserError, - CreateVerificationRequestError, - GetVerificationRequestError, - DeleteVerificationRequestError, - LinkAccountError, - UnlinkAccountError, -} from "../lib/errors" +import { UnknownError } from "../lib/errors" /** - * Takes away the error handling responsibility - * from the actual adapter. - * @param {import("types/adapters").Adapter | undefined} adapter - * @return {import("types/adapters").Adapter | undefined} + * Handles adapter induced errors. + * @param {import("types/adapters").AdapterInstance} adapter + * @return {import("types/adapters").AdapterInstance} */ export default function adapterErrorHandler(adapter) { - if (!adapter) { - return - } - return function Adapter(...args) { - const _adapter = adapter(...args) - return { - async getAdapter(options) { - const _getAdapter = await _adapter.getAdapter(options) - return { - async getSession(...args) { - try { - return await _getAdapter.getSession(...args) - } catch (error) { - throw new GetSessionError(error) - } - }, - async createSession(...args) { - try { - return await _getAdapter.createSession(...args) - } catch (error) { - throw new CreateSessionError(error) - } - }, - async updateSession(...args) { - try { - return await _getAdapter.updateSession(...args) - } catch (error) { - throw new UpdateSessionError(error) - } - }, - async deleteSession(...args) { - try { - return await _getAdapter.deleteSession(...args) - } catch (error) { - throw new DeleteSessionError(error) - } - }, - async createUser(...args) { - try { - return await _getAdapter.createUser(...args) - } catch (error) { - throw new CreateUserError(error) - } - }, - async getUser(...args) { - try { - return await _getAdapter.getUser(...args) - } catch (error) { - throw new GetUserByIdError(error) - } - }, - async getUserByEmail(...args) { - try { - return await _getAdapter.getUserByEmail(...args) - } catch (error) { - throw new GetUserByEmailError(error) - } - }, - async getUserByProviderAccountId(...args) { - try { - return await _getAdapter.getUserByProviderAccountId(...args) - } catch (error) { - throw new GetUserByProviderAccountIdError(error) - } - }, - async deleteUser(...args) { - try { - return await _getAdapter.deleteUser?.(...args) - } catch (error) { - throw new DeleteUserError(error) - } - }, - async updateUser(...args) { - try { - return await _getAdapter.updateUser(...args) - } catch (error) { - throw new UpdateUserError(error) - } - }, - async createVerificationRequest(...args) { - try { - return await _getAdapter.createVerificationRequest?.(...args) - } catch (error) { - throw new CreateVerificationRequestError(error) - } - }, - async getVerificationRequest(...args) { - try { - return ( - (await _getAdapter.getVerificationRequest?.(...args)) ?? null - ) - } catch (error) { - throw new GetVerificationRequestError(error) - } - }, - async deleteVerificationRequest(...args) { - try { - return await _getAdapter.deleteVerificationRequest?.(...args) - } catch (error) { - throw new DeleteVerificationRequestError(error) - } - }, - async linkAccount(...args) { - try { - return await _getAdapter.linkAccount(...args) - } catch (error) { - throw new LinkAccountError(error) - } - }, - async unlinkAccont(...args) { - try { - return await _getAdapter.unlinkAccont?.(...args) - } catch (error) { - throw new UnlinkAccountError(error) - } - }, - } - }, + return Object.keys(adapter).reduce((acc, method) => { + acc[method] = async (...args) => { + try { + return await acc[method](...args) + } catch (error) { + const e = new UnknownError(error) + e.name = `${method[0].toUpperCase()}${method.slice(1)}Error` + throw e + } } - } + return acc + }, {}) } diff --git a/src/server/index.js b/src/server/index.js index 5065cfab02..88fac60966 100644 --- a/src/server/index.js +++ b/src/server/index.js @@ -1,25 +1,24 @@ -import adapters from '../adapters' -import jwt from '../lib/jwt' -import parseUrl from '../lib/parse-url' -import logger, { setLogger } from '../lib/logger' -import * as cookie from './lib/cookie' -import * as defaultEvents from './lib/default-events' -import * as defaultCallbacks from './lib/default-callbacks' -import parseProviders from './lib/providers' -import * as routes from './routes' -import renderPage from './pages' -import createSecret from './lib/create-secret' -import callbackUrlHandler from './lib/callback-url-handler' -import extendRes from './lib/extend-res' -import csrfTokenHandler from './lib/csrf-token-handler' -import * as pkce from './lib/oauth/pkce-handler' -import * as state from './lib/oauth/state-handler' -import adapterErrorHandler from 'src/adapters/error-handler' +import adapters from "../adapters" +import jwt from "../lib/jwt" +import parseUrl from "../lib/parse-url" +import logger, { setLogger } from "../lib/logger" +import * as cookie from "./lib/cookie" +import * as defaultEvents from "./lib/default-events" +import * as defaultCallbacks from "./lib/default-callbacks" +import parseProviders from "./lib/providers" +import * as routes from "./routes" +import renderPage from "./pages" +import createSecret from "./lib/create-secret" +import callbackUrlHandler from "./lib/callback-url-handler" +import extendRes from "./lib/extend-res" +import csrfTokenHandler from "./lib/csrf-token-handler" +import * as pkce from "./lib/oauth/pkce-handler" +import * as state from "./lib/oauth/state-handler" // To work properly in production with OAuth providers the NEXTAUTH_URL // environment variable must be set. if (!process.env.NEXTAUTH_URL) { - logger.warn('NEXTAUTH_URL', 'NEXTAUTH_URL environment variable not set') + logger.warn("NEXTAUTH_URL", "NEXTAUTH_URL environment variable not set") } /** @@ -27,7 +26,7 @@ if (!process.env.NEXTAUTH_URL) { * @param {import("next").NextApiResponse} res * @param {import("types").NextAuthOptions} userOptions */ -async function NextAuthHandler (req, res, userOptions) { +async function NextAuthHandler(req, res, userOptions) { if (userOptions.logger) { setLogger(userOptions.logger) } @@ -40,13 +39,15 @@ async function NextAuthHandler (req, res, userOptions) { // to avoid early termination of calls to the serverless function // (and then return that promise when we are done) - eslint // complains but I'm not sure there is another way to do this. - return new Promise(async resolve => { // eslint-disable-line no-async-promise-executor + // eslint-disable-next-line no-async-promise-executor + return new Promise(async (resolve) => { extendRes(req, res, resolve) if (!req.query.nextauth) { - const error = 'Cannot find [...nextauth].js in pages/api/auth. Make sure the filename is written correctly.' + const error = + "Cannot find [...nextauth].js in pages/api/auth. Make sure the filename is written correctly." - logger.error('MISSING_NEXTAUTH_API_ROUTE_ERROR', error) + logger.error("MISSING_NEXTAUTH_API_ROUTE_ERROR", error) return res.status(500).end(`Error: ${error}`) } @@ -54,30 +55,38 @@ async function NextAuthHandler (req, res, userOptions) { nextauth, action = nextauth[0], providerId = nextauth[1], - error = nextauth[1] + error = nextauth[1], } = req.query // @todo refactor all existing references to baseUrl and basePath - const { basePath, baseUrl } = parseUrl(process.env.NEXTAUTH_URL || process.env.VERCEL_URL) + const { basePath, baseUrl } = parseUrl( + process.env.NEXTAUTH_URL || process.env.VERCEL_URL + ) const cookies = { - ...cookie.defaultCookies(userOptions.useSecureCookies || baseUrl.startsWith('https://')), + ...cookie.defaultCookies( + userOptions.useSecureCookies || baseUrl.startsWith("https://") + ), // Allow user cookie options to override any cookie settings above - ...userOptions.cookies + ...userOptions.cookies, } const secret = createSecret({ userOptions, basePath, baseUrl }) - const providers = parseProviders({ providers: userOptions.providers, baseUrl, basePath }) + const providers = parseProviders({ + providers: userOptions.providers, + baseUrl, + basePath, + }) const provider = providers.find(({ id }) => id === providerId) // Protection only works on OAuth 2.x providers - if (provider?.type === 'oauth' && provider.version?.startsWith('2')) { + if (provider?.type === "oauth" && provider.version?.startsWith("2")) { // When provider.state is undefined, we still want this to pass if (!provider.protection && provider.state !== false) { // Default to state, as we did in 3.1 REVIEW: should we use "pkce" or "none" as default? - provider.protection = ['state'] - } else if (typeof provider.protection === 'string') { + provider.protection = ["state"] + } else if (typeof provider.protection === "string") { provider.protection = [provider.protection] } } @@ -87,14 +96,16 @@ async function NextAuthHandler (req, res, userOptions) { // Parse database / adapter // If adapter is provided, use it (advanced usage, overrides database) // If database URI or config object is provided, use it (simple usage) - const adapter = adapterErrorHandler(userOptions.adapter ?? (userOptions.database && adapters.Default(userOptions.database))) + const adapter = + userOptions.adapter ?? + (userOptions.database && adapters.Default(userOptions.database)) // User provided options are overriden by other options, // except for the options with special handling above req.options = { debug: false, pages: {}, - theme: 'auto', + theme: "auto", // Custom options override defaults ...userOptions, // These computed settings can have values in userOptions but we override them @@ -112,7 +123,7 @@ async function NextAuthHandler (req, res, userOptions) { jwt: !adapter, // If no adapter specified, force use of JSON Web Tokens (stateless) maxAge, updateAge: 24 * 60 * 60, // Sessions updated only if session is greater than this value (0 = always, 24*60*60 = every 24 hours) - ...userOptions.session + ...userOptions.session, }, // JWT options jwt: { @@ -120,20 +131,20 @@ async function NextAuthHandler (req, res, userOptions) { maxAge, // same as session maxAge, encode: jwt.encode, decode: jwt.decode, - ...userOptions.jwt + ...userOptions.jwt, }, // Event messages events: { ...defaultEvents, - ...userOptions.events + ...userOptions.events, }, // Callback functions callbacks: { ...defaultCallbacks, - ...userOptions.callbacks + ...userOptions.callbacks, }, pkce: {}, - logger + logger, } csrfTokenHandler(req, res) @@ -142,65 +153,79 @@ async function NextAuthHandler (req, res, userOptions) { const render = renderPage(req, res) const { pages } = req.options - if (req.method === 'GET') { + if (req.method === "GET") { switch (action) { - case 'providers': + case "providers": return routes.providers(req, res) - case 'session': + case "session": return routes.session(req, res) - case 'csrf': + case "csrf": return res.json({ csrfToken: req.options.csrfToken }) - case 'signin': + case "signin": if (pages.signIn) { - let signinUrl = `${pages.signIn}${pages.signIn.includes('?') ? '&' : '?'}callbackUrl=${req.options.callbackUrl}` - if (error) { signinUrl = `${signinUrl}&error=${error}` } + let signinUrl = `${pages.signIn}${ + pages.signIn.includes("?") ? "&" : "?" + }callbackUrl=${req.options.callbackUrl}` + if (error) { + signinUrl = `${signinUrl}&error=${error}` + } return res.redirect(signinUrl) } return render.signin() - case 'signout': + case "signout": if (pages.signOut) { - return res.redirect(`${pages.signOut}${pages.signOut.includes('?') ? '&' : '?'}error=${error}`) + return res.redirect( + `${pages.signOut}${ + pages.signOut.includes("?") ? "&" : "?" + }error=${error}` + ) } return render.signout() - case 'callback': + case "callback": if (provider) { if (await pkce.handleCallback(req, res)) return if (await state.handleCallback(req, res)) return return routes.callback(req, res) } break - case 'verify-request': + case "verify-request": if (pages.verifyRequest) { return res.redirect(pages.verifyRequest) } return render.verifyRequest() - case 'error': + case "error": if (pages.error) { - return res.redirect(`${pages.error}${pages.error.includes('?') ? '&' : '?'}error=${error}`) + return res.redirect( + `${pages.error}${ + pages.error.includes("?") ? "&" : "?" + }error=${error}` + ) } // These error messages are displayed in line on the sign in page - if ([ - 'Signin', - 'OAuthSignin', - 'OAuthCallback', - 'OAuthCreateAccount', - 'EmailCreateAccount', - 'Callback', - 'OAuthAccountNotLinked', - 'EmailSignin', - 'CredentialsSignin' - ].includes(error)) { + if ( + [ + "Signin", + "OAuthSignin", + "OAuthCallback", + "OAuthCreateAccount", + "EmailCreateAccount", + "Callback", + "OAuthAccountNotLinked", + "EmailSignin", + "CredentialsSignin", + ].includes(error) + ) { return res.redirect(`${baseUrl}${basePath}/signin?error=${error}`) } return render.error({ error }) default: } - } else if (req.method === 'POST') { + } else if (req.method === "POST") { switch (action) { - case 'signin': + case "signin": // Verified CSRF Token required for all sign in routes if (req.options.csrfTokenVerified && provider) { if (await pkce.handleSignin(req, res)) return @@ -209,16 +234,19 @@ async function NextAuthHandler (req, res, userOptions) { } return res.redirect(`${baseUrl}${basePath}/signin?csrf=true`) - case 'signout': + case "signout": // Verified CSRF Token required for signout if (req.options.csrfTokenVerified) { return routes.signout(req, res) } return res.redirect(`${baseUrl}${basePath}/signout?csrf=true`) - case 'callback': + case "callback": if (provider) { // Verified CSRF Token required for credentials providers only - if (provider.type === 'credentials' && !req.options.csrfTokenVerified) { + if ( + provider.type === "credentials" && + !req.options.csrfTokenVerified + ) { return res.redirect(`${baseUrl}${basePath}/signin?csrf=true`) } @@ -227,31 +255,33 @@ async function NextAuthHandler (req, res, userOptions) { return routes.callback(req, res) } break - case '_log': + case "_log": if (userOptions.logger) { try { const { - code = 'CLIENT_ERROR', - level = 'error', - message = '[]' + code = "CLIENT_ERROR", + level = "error", + message = "[]", } = req.body logger[level](code, ...JSON.parse(message)) } catch (error) { // If logging itself failed... - logger.error('LOGGER_ERROR', error) + logger.error("LOGGER_ERROR", error) } } return res.end() default: } } - return res.status(400).end(`Error: HTTP ${req.method} is not supported for ${req.url}`) + return res + .status(400) + .end(`Error: HTTP ${req.method} is not supported for ${req.url}`) }) } /** Tha main entry point to next-auth */ -export default function NextAuth (...args) { +export default function NextAuth(...args) { if (args.length === 1) { return (req, res) => NextAuthHandler(req, res, args[0]) } diff --git a/src/server/lib/callback-handler.js b/src/server/lib/callback-handler.js index ce51fe514e..37f28792e6 100644 --- a/src/server/lib/callback-handler.js +++ b/src/server/lib/callback-handler.js @@ -1,5 +1,6 @@ -import { AccountNotLinkedError } from '../../lib/errors' -import dispatchEvent from '../lib/dispatch-event' +import { AccountNotLinkedError } from "../../lib/errors" +import dispatchEvent from "../lib/dispatch-event" +import adapterErrorHandler from "../../adapters/error-handler" /** * This function handles the complex flow of signing users in, and either creating, @@ -13,19 +14,24 @@ import dispatchEvent from '../lib/dispatch-event' * done prior to this handler being called to avoid additonal complexity in this * handler. */ -export default async function callbackHandler (sessionToken, profile, providerAccount, options) { +export default async function callbackHandler( + sessionToken, + profile, + providerAccount, + options +) { // Input validation - if (!profile) throw new Error('Missing profile') - if (!providerAccount?.id || !providerAccount.type) throw new Error('Missing or invalid provider account') - if (!['email', 'oauth'].includes(providerAccount.type)) throw new Error('Provider not supported') + if (!profile) throw new Error("Missing profile") + if (!providerAccount?.id || !providerAccount.type) + throw new Error("Missing or invalid provider account") + if (!["email", "oauth"].includes(providerAccount.type)) + throw new Error("Provider not supported") const { adapter, jwt, events, - session: { - jwt: useJwtSession - } + session: { jwt: useJwtSession }, } = options // If no adapter is configured then we don't have a database and cannot @@ -34,7 +40,7 @@ export default async function callbackHandler (sessionToken, profile, providerAc return { user: profile, account: providerAccount, - session: {} + session: {}, } } @@ -47,8 +53,8 @@ export default async function callbackHandler (sessionToken, profile, providerAc linkAccount, createSession, getSession, - deleteSession - } = await adapter.getAdapter(options) + deleteSession, + } = adapterErrorHandler(await adapter.getAdapter(options)) let session = null let user = null @@ -74,9 +80,11 @@ export default async function callbackHandler (sessionToken, profile, providerAc } } - if (providerAccount.type === 'email') { + if (providerAccount.type === "email") { // If signing in with an email, check if an account with the same email address exists already - const userByEmail = profile.email ? await getUserByEmail(profile.email) : null + const userByEmail = profile.email + ? await getUserByEmail(profile.email) + : null if (userByEmail) { // If they are not already signed in as the same user, this flow will // sign them out of the current session and sign them in as the new user @@ -107,11 +115,14 @@ export default async function callbackHandler (sessionToken, profile, providerAc return { session, user, - isNewUser + isNewUser, } - } else if (providerAccount.type === 'oauth') { + } else if (providerAccount.type === "oauth") { // If signing in with oauth account, check to see if the account exists already - const userByProviderAccountId = await getUserByProviderAccountId(providerAccount.provider, providerAccount.id) + const userByProviderAccountId = await getUserByProviderAccountId( + providerAccount.provider, + providerAccount.id + ) if (userByProviderAccountId) { if (isSignedIn) { // If the user is already signed in with this account, we don't need to do anything @@ -122,7 +133,7 @@ export default async function callbackHandler (sessionToken, profile, providerAc return { session, user, - isNewUser + isNewUser, } } // If the user is currently signed in, but the new account they are signing in @@ -132,11 +143,13 @@ export default async function callbackHandler (sessionToken, profile, providerAc } // If there is no active session, but the account being signed in with is already // associated with a valid user then create session to sign the user in. - session = useJwtSession ? {} : await createSession(userByProviderAccountId) + session = useJwtSession + ? {} + : await createSession(userByProviderAccountId) return { session, user: userByProviderAccountId, - isNewUser + isNewUser, } } else { if (isSignedIn) { @@ -151,13 +164,16 @@ export default async function callbackHandler (sessionToken, profile, providerAc providerAccount.accessToken, providerAccount.accessTokenExpires ) - await dispatchEvent(events.linkAccount, { user, providerAccount: providerAccount }) + await dispatchEvent(events.linkAccount, { + user, + providerAccount: providerAccount, + }) // As they are already signed in, we don't need to do anything after linking them return { session, user, - isNewUser + isNewUser, } } @@ -178,7 +194,9 @@ export default async function callbackHandler (sessionToken, profile, providerAc // // OAuth providers should require email address verification to prevent this, but in // practice that is not always the case; this helps protect against that. - const userByEmail = profile.email ? await getUserByEmail(profile.email) : null + const userByEmail = profile.email + ? await getUserByEmail(profile.email) + : null if (userByEmail) { // We end up here when we don't have an account with the same [provider].id *BUT* // we do already have an account with the same email address as the one in the @@ -207,14 +225,17 @@ export default async function callbackHandler (sessionToken, profile, providerAc providerAccount.accessToken, providerAccount.accessTokenExpires ) - await dispatchEvent(events.linkAccount, { user, providerAccount: providerAccount }) + await dispatchEvent(events.linkAccount, { + user, + providerAccount: providerAccount, + }) session = useJwtSession ? {} : await createSession(user) isNewUser = true return { session, user, - isNewUser + isNewUser, } } } diff --git a/src/server/lib/signin/email.js b/src/server/lib/signin/email.js index 5e7916acdd..dcc912fbd5 100644 --- a/src/server/lib/signin/email.js +++ b/src/server/lib/signin/email.js @@ -1,22 +1,36 @@ -import { randomBytes } from 'crypto' +import { randomBytes } from "crypto" +import adapterErrorHandler from "../../../adapters/error-handler" -export default async function email (email, provider, options) { +export default async function email(email, provider, options) { try { const { baseUrl, basePath, adapter } = options - const { createVerificationRequest } = await adapter.getAdapter(options) + const { createVerificationRequest } = adapterErrorHandler( + await adapter.getAdapter(options) + ) // Prefer provider specific secret, but use default secret if none specified const secret = provider.secret || options.secret // Generate token - const token = await provider.generateVerificationToken?.() ?? randomBytes(32).toString('hex') + const token = + (await provider.generateVerificationToken?.()) ?? + randomBytes(32).toString("hex") // Send email with link containing token (the unhashed version) - const url = `${baseUrl}${basePath}/callback/${encodeURIComponent(provider.id)}?email=${encodeURIComponent(email)}&token=${encodeURIComponent(token)}` + const url = `${baseUrl}${basePath}/callback/${encodeURIComponent( + provider.id + )}?email=${encodeURIComponent(email)}&token=${encodeURIComponent(token)}` // @TODO Create invite (send secret so can be hashed) - await createVerificationRequest(email, url, token, secret, provider, options) + await createVerificationRequest( + email, + url, + token, + secret, + provider, + options + ) // Return promise return Promise.resolve() diff --git a/src/server/routes/callback.js b/src/server/routes/callback.js index c97a189696..f3af02c2aa 100644 --- a/src/server/routes/callback.js +++ b/src/server/routes/callback.js @@ -1,15 +1,15 @@ -import oAuthCallback from '../lib/oauth/callback' -import callbackHandler from '../lib/callback-handler' -import * as cookie from '../lib/cookie' -import logger from '../../lib/logger' -import dispatchEvent from '../lib/dispatch-event' +import oAuthCallback from "../lib/oauth/callback" +import callbackHandler from "../lib/callback-handler" +import * as cookie from "../lib/cookie" +import dispatchEvent from "../lib/dispatch-event" +import adapterErrorHandler from "../../adapters/error-handler" /** * Handle callbacks from login services * @param {import("types/internals").NextAuthRequest} req * @param {import("types/internals").NextAuthResponse} res */ -export default async function callback (req, res) { +export default async function callback(req, res) { const { provider, adapter, @@ -22,21 +22,23 @@ export default async function callback (req, res) { jwt, events, callbacks, - session: { - jwt: useJwtSession, - maxAge: sessionMaxAge - } + session: { jwt: useJwtSession, maxAge: sessionMaxAge }, + logger, } = req.options // Get session ID (if set) const sessionToken = req.cookies?.[cookies.sessionToken.name] ?? null - if (provider.type === 'oauth') { + if (provider.type === "oauth") { try { const { profile, account, OAuthProfile } = await oAuthCallback(req) try { // Make it easier to debug when adding a new provider - logger.debug('OAUTH_CALLBACK_RESPONSE', { profile, account, OAuthProfile }) + logger.debug("OAUTH_CALLBACK_RESPONSE", { + profile, + account, + OAuthProfile, + }) // If we don't have a profile object then either something went wrong // or the user cancelled signing in. We don't know which, so we just @@ -56,52 +58,84 @@ export default async function callback (req, res) { // (that just means it's a new user signing in for the first time). let userOrProfile = profile if (adapter) { - const { getUserByProviderAccountId } = await adapter.getAdapter(req.options) - const userFromProviderAccountId = await getUserByProviderAccountId(account.provider, account.id) + const { getUserByProviderAccountId } = adapterErrorHandler( + await adapter.getAdapter(req.options) + ) + const userFromProviderAccountId = await getUserByProviderAccountId( + account.provider, + account.id + ) if (userFromProviderAccountId) { userOrProfile = userFromProviderAccountId } } try { - const signInCallbackResponse = await callbacks.signIn(userOrProfile, account, OAuthProfile) + const signInCallbackResponse = await callbacks.signIn( + userOrProfile, + account, + OAuthProfile + ) if (signInCallbackResponse === false) { - return res.redirect(`${baseUrl}${basePath}/error?error=AccessDenied`) - } else if (typeof signInCallbackResponse === 'string') { + return res.redirect( + `${baseUrl}${basePath}/error?error=AccessDenied` + ) + } else if (typeof signInCallbackResponse === "string") { return res.redirect(signInCallbackResponse) } } catch (error) { if (error instanceof Error) { - return res.redirect(`${baseUrl}${basePath}/error?error=${encodeURIComponent(error.message)}`) + return res.redirect( + `${baseUrl}${basePath}/error?error=${encodeURIComponent( + error.message + )}` + ) } // TODO: Remove in a future major release - logger.warn('SIGNIN_CALLBACK_REJECT_REDIRECT') + logger.warn("SIGNIN_CALLBACK_REJECT_REDIRECT") return res.redirect(error) } // Sign user in - const { user, session, isNewUser } = await callbackHandler(sessionToken, profile, account, req.options) + const { user, session, isNewUser } = await callbackHandler( + sessionToken, + profile, + account, + req.options + ) if (useJwtSession) { const defaultJwtPayload = { name: user.name, email: user.email, picture: user.image, - sub: user.id?.toString() + sub: user.id?.toString(), } - const jwtPayload = await callbacks.jwt(defaultJwtPayload, user, account, OAuthProfile, isNewUser) + const jwtPayload = await callbacks.jwt( + defaultJwtPayload, + user, + account, + OAuthProfile, + isNewUser + ) // Sign and encrypt token const newEncodedJwt = await jwt.encode({ ...jwt, token: jwtPayload }) // Set cookie expiry date const cookieExpires = new Date() - cookieExpires.setTime(cookieExpires.getTime() + (sessionMaxAge * 1000)) + cookieExpires.setTime(cookieExpires.getTime() + sessionMaxAge * 1000) - cookie.set(res, cookies.sessionToken.name, newEncodedJwt, { expires: cookieExpires.toISOString(), ...cookies.sessionToken.options }) + cookie.set(res, cookies.sessionToken.name, newEncodedJwt, { + expires: cookieExpires.toISOString(), + ...cookies.sessionToken.options, + }) } else { // Save Session Token in cookie - cookie.set(res, cookies.sessionToken.name, session.sessionToken, { expires: session.expires || null, ...cookies.sessionToken.options }) + cookie.set(res, cookies.sessionToken.name, session.sessionToken, { + expires: session.expires || null, + ...cookies.sessionToken.options, + }) } await dispatchEvent(events.signIn, { user, account, isNewUser }) @@ -110,94 +144,145 @@ export default async function callback (req, res) { // e.g. option to send users to a new account landing page on initial login // Note that the callback URL is preserved, so the journey can still be resumed if (isNewUser && pages.newUser) { - return res.redirect(`${pages.newUser}${pages.newUser.includes('?') ? '&' : '?'}callbackUrl=${encodeURIComponent(callbackUrl)}`) + return res.redirect( + `${pages.newUser}${ + pages.newUser.includes("?") ? "&" : "?" + }callbackUrl=${encodeURIComponent(callbackUrl)}` + ) } // Callback URL is already verified at this point, so safe to use if specified return res.redirect(callbackUrl || baseUrl) } catch (error) { - if (error.name === 'AccountNotLinkedError') { + if (error.name === "AccountNotLinkedError") { // If the email on the account is already linked, but not with this OAuth account - return res.redirect(`${baseUrl}${basePath}/error?error=OAuthAccountNotLinked`) - } else if (error.name === 'CreateUserError') { - return res.redirect(`${baseUrl}${basePath}/error?error=OAuthCreateAccount`) + return res.redirect( + `${baseUrl}${basePath}/error?error=OAuthAccountNotLinked` + ) + } else if (error.name === "CreateUserError") { + return res.redirect( + `${baseUrl}${basePath}/error?error=OAuthCreateAccount` + ) } - logger.error('OAUTH_CALLBACK_HANDLER_ERROR', error) + logger.error("OAUTH_CALLBACK_HANDLER_ERROR", error) return res.redirect(`${baseUrl}${basePath}/error?error=Callback`) } } catch (error) { - if (error.name === 'OAuthCallbackError') { - logger.error('CALLBACK_OAUTH_ERROR', error) + if (error.name === "OAuthCallbackError") { + logger.error("CALLBACK_OAUTH_ERROR", error) return res.redirect(`${baseUrl}${basePath}/error?error=OAuthCallback`) } - logger.error('OAUTH_CALLBACK_ERROR', error) + logger.error("OAUTH_CALLBACK_ERROR", error) return res.redirect(`${baseUrl}${basePath}/error?error=Callback`) } - } else if (provider.type === 'email') { + } else if (provider.type === "email") { try { if (!adapter) { - logger.error('EMAIL_REQUIRES_ADAPTER_ERROR') + logger.error("EMAIL_REQUIRES_ADAPTER_ERROR") return res.redirect(`${baseUrl}${basePath}/error?error=Configuration`) } - const { getVerificationRequest, deleteVerificationRequest, getUserByEmail } = await adapter.getAdapter(req.options) + const { + getVerificationRequest, + deleteVerificationRequest, + getUserByEmail, + } = adapterErrorHandler(await adapter.getAdapter(req.options)) const verificationToken = req.query.token const email = req.query.email // Verify email and verification token exist in database - const invite = await getVerificationRequest(email, verificationToken, secret, provider) + const invite = await getVerificationRequest( + email, + verificationToken, + secret, + provider + ) if (!invite) { return res.redirect(`${baseUrl}${basePath}/error?error=Verification`) } // If verification token is valid, delete verification request token from // the database so it cannot be used again - await deleteVerificationRequest(email, verificationToken, secret, provider) + await deleteVerificationRequest( + email, + verificationToken, + secret, + provider + ) // If is an existing user return a user object (otherwise use placeholder) - const profile = await getUserByEmail(email) || { email } - const account = { id: provider.id, type: 'email', providerAccountId: email } + const profile = (await getUserByEmail(email)) || { email } + const account = { + id: provider.id, + type: "email", + providerAccountId: email, + } // Check if user is allowed to sign in try { - const signInCallbackResponse = await callbacks.signIn(profile, account, { email }) + const signInCallbackResponse = await callbacks.signIn( + profile, + account, + { email } + ) if (signInCallbackResponse === false) { return res.redirect(`${baseUrl}${basePath}/error?error=AccessDenied`) - } else if (typeof signInCallbackResponse === 'string') { + } else if (typeof signInCallbackResponse === "string") { return res.redirect(signInCallbackResponse) } } catch (error) { if (error instanceof Error) { - return res.redirect(`${baseUrl}${basePath}/error?error=${encodeURIComponent(error.message)}`) + return res.redirect( + `${baseUrl}${basePath}/error?error=${encodeURIComponent( + error.message + )}` + ) } // TODO: Remove in a future major release - logger.warn('SIGNIN_CALLBACK_REJECT_REDIRECT') + logger.warn("SIGNIN_CALLBACK_REJECT_REDIRECT") return res.redirect(error) } // Sign user in - const { user, session, isNewUser } = await callbackHandler(sessionToken, profile, account, req.options) + const { user, session, isNewUser } = await callbackHandler( + sessionToken, + profile, + account, + req.options + ) if (useJwtSession) { const defaultJwtPayload = { name: user.name, email: user.email, picture: user.image, - sub: user.id?.toString() + sub: user.id?.toString(), } - const jwtPayload = await callbacks.jwt(defaultJwtPayload, user, account, profile, isNewUser) + const jwtPayload = await callbacks.jwt( + defaultJwtPayload, + user, + account, + profile, + isNewUser + ) // Sign and encrypt token const newEncodedJwt = await jwt.encode({ ...jwt, token: jwtPayload }) // Set cookie expiry date const cookieExpires = new Date() - cookieExpires.setTime(cookieExpires.getTime() + (sessionMaxAge * 1000)) + cookieExpires.setTime(cookieExpires.getTime() + sessionMaxAge * 1000) - cookie.set(res, cookies.sessionToken.name, newEncodedJwt, { expires: cookieExpires.toISOString(), ...cookies.sessionToken.options }) + cookie.set(res, cookies.sessionToken.name, newEncodedJwt, { + expires: cookieExpires.toISOString(), + ...cookies.sessionToken.options, + }) } else { // Save Session Token in cookie - cookie.set(res, cookies.sessionToken.name, session.sessionToken, { expires: session.expires || null, ...cookies.sessionToken.options }) + cookie.set(res, cookies.sessionToken.name, session.sessionToken, { + expires: session.expires || null, + ...cookies.sessionToken.options, + }) } await dispatchEvent(events.signIn, { user, account, isNewUser }) @@ -206,55 +291,93 @@ export default async function callback (req, res) { // e.g. option to send users to a new account landing page on initial login // Note that the callback URL is preserved, so the journey can still be resumed if (isNewUser && pages.newUser) { - return res.redirect(`${pages.newUser}${pages.newUser.includes('?') ? '&' : '?'}callbackUrl=${encodeURIComponent(callbackUrl)}`) + return res.redirect( + `${pages.newUser}${ + pages.newUser.includes("?") ? "&" : "?" + }callbackUrl=${encodeURIComponent(callbackUrl)}` + ) } // Callback URL is already verified at this point, so safe to use if specified return res.redirect(callbackUrl || baseUrl) } catch (error) { - if (error.name === 'CreateUserError') { - return res.redirect(`${baseUrl}${basePath}/error?error=EmailCreateAccount`) + if (error.name === "CreateUserError") { + return res.redirect( + `${baseUrl}${basePath}/error?error=EmailCreateAccount` + ) } - logger.error('CALLBACK_EMAIL_ERROR', error) + logger.error("CALLBACK_EMAIL_ERROR", error) return res.redirect(`${baseUrl}${basePath}/error?error=Callback`) } - } else if (provider.type === 'credentials' && req.method === 'POST') { + } else if (provider.type === "credentials" && req.method === "POST") { if (!useJwtSession) { - logger.error('CALLBACK_CREDENTIALS_JWT_ERROR', 'Signin in with credentials is only supported if JSON Web Tokens are enabled') - return res.status(500).redirect(`${baseUrl}${basePath}/error?error=Configuration`) + logger.error( + "CALLBACK_CREDENTIALS_JWT_ERROR", + "Signin in with credentials is only supported if JSON Web Tokens are enabled" + ) + return res + .status(500) + .redirect(`${baseUrl}${basePath}/error?error=Configuration`) } if (!provider.authorize) { - logger.error('CALLBACK_CREDENTIALS_HANDLER_ERROR', 'Must define an authorize() handler to use credentials authentication provider') - return res.status(500).redirect(`${baseUrl}${basePath}/error?error=Configuration`) + logger.error( + "CALLBACK_CREDENTIALS_HANDLER_ERROR", + "Must define an authorize() handler to use credentials authentication provider" + ) + return res + .status(500) + .redirect(`${baseUrl}${basePath}/error?error=Configuration`) } const credentials = req.body let userObjectReturnedFromAuthorizeHandler try { - userObjectReturnedFromAuthorizeHandler = await provider.authorize(credentials) + userObjectReturnedFromAuthorizeHandler = await provider.authorize( + credentials + ) if (!userObjectReturnedFromAuthorizeHandler) { - return res.status(401).redirect(`${baseUrl}${basePath}/error?error=CredentialsSignin&provider=${encodeURIComponent(provider.id)}`) + return res + .status(401) + .redirect( + `${baseUrl}${basePath}/error?error=CredentialsSignin&provider=${encodeURIComponent( + provider.id + )}` + ) } } catch (error) { if (error instanceof Error) { - return res.redirect(`${baseUrl}${basePath}/error?error=${encodeURIComponent(error.message)}`) + return res.redirect( + `${baseUrl}${basePath}/error?error=${encodeURIComponent( + error.message + )}` + ) } return res.redirect(error) } const user = userObjectReturnedFromAuthorizeHandler - const account = { id: provider.id, type: 'credentials' } + const account = { id: provider.id, type: "credentials" } try { - const signInCallbackResponse = await callbacks.signIn(user, account, credentials) + const signInCallbackResponse = await callbacks.signIn( + user, + account, + credentials + ) if (signInCallbackResponse === false) { - return res.status(403).redirect(`${baseUrl}${basePath}/error?error=AccessDenied`) + return res + .status(403) + .redirect(`${baseUrl}${basePath}/error?error=AccessDenied`) } } catch (error) { if (error instanceof Error) { - return res.redirect(`${baseUrl}${basePath}/error?error=${encodeURIComponent(error.message)}`) + return res.redirect( + `${baseUrl}${basePath}/error?error=${encodeURIComponent( + error.message + )}` + ) } return res.redirect(error) } @@ -263,22 +386,33 @@ export default async function callback (req, res) { name: user.name, email: user.email, picture: user.image, - sub: user.id?.toString() + sub: user.id?.toString(), } - const jwtPayload = await callbacks.jwt(defaultJwtPayload, user, account, userObjectReturnedFromAuthorizeHandler, false) + const jwtPayload = await callbacks.jwt( + defaultJwtPayload, + user, + account, + userObjectReturnedFromAuthorizeHandler, + false + ) // Sign and encrypt token const newEncodedJwt = await jwt.encode({ ...jwt, token: jwtPayload }) // Set cookie expiry date const cookieExpires = new Date() - cookieExpires.setTime(cookieExpires.getTime() + (sessionMaxAge * 1000)) + cookieExpires.setTime(cookieExpires.getTime() + sessionMaxAge * 1000) - cookie.set(res, cookies.sessionToken.name, newEncodedJwt, { expires: cookieExpires.toISOString(), ...cookies.sessionToken.options }) + cookie.set(res, cookies.sessionToken.name, newEncodedJwt, { + expires: cookieExpires.toISOString(), + ...cookies.sessionToken.options, + }) await dispatchEvent(events.signIn, { user, account }) return res.redirect(callbackUrl || baseUrl) } - return res.status(500).end(`Error: Callback for provider type ${provider.type} not supported`) + return res + .status(500) + .end(`Error: Callback for provider type ${provider.type} not supported`) } diff --git a/src/server/routes/session.js b/src/server/routes/session.js index d8d3431625..4af1a33dd7 100644 --- a/src/server/routes/session.js +++ b/src/server/routes/session.js @@ -1,13 +1,15 @@ -import * as cookie from '../lib/cookie' -import logger from '../../lib/logger' -import dispatchEvent from '../lib/dispatch-event' +import * as cookie from "../lib/cookie" +import dispatchEvent from "../lib/dispatch-event" +import adapterErrorHandler from "../../adapters/error-handler" /** * Return a session object (without any private fields) * for Single Page App clients + * @param {import("types/internals").NextAuthRequest} req + * @param {import("types/internals").NextAuthResponse} res */ -export default async function session (req, res) { - const { cookies, adapter, jwt, events, callbacks } = req.options +export default async function session(req, res) { + const { cookies, adapter, jwt, events, callbacks, logger } = req.options const useJwtSession = req.options.session.jwt const sessionMaxAge = req.options.session.maxAge const sessionToken = req.cookies[cookies.sessionToken.name] @@ -24,7 +26,9 @@ export default async function session (req, res) { // Generate new session expiry date const sessionExpiresDate = new Date() - sessionExpiresDate.setTime(sessionExpiresDate.getTime() + (sessionMaxAge * 1000)) + sessionExpiresDate.setTime( + sessionExpiresDate.getTime() + sessionMaxAge * 1000 + ) const sessionExpires = sessionExpiresDate.toISOString() // By default, only exposes a limited subset of information to the client @@ -33,14 +37,17 @@ export default async function session (req, res) { user: { name: decodedJwt.name || null, email: decodedJwt.email || null, - image: decodedJwt.picture || null + image: decodedJwt.picture || null, }, - expires: sessionExpires + expires: sessionExpires, } // Pass Session and JSON Web Token through to the session callback const jwtPayload = await callbacks.jwt(decodedJwt) - const sessionPayload = await callbacks.session(defaultSessionPayload, jwtPayload) + const sessionPayload = await callbacks.session( + defaultSessionPayload, + jwtPayload + ) // Return session payload as response response = sessionPayload @@ -49,17 +56,28 @@ export default async function session (req, res) { const newEncodedJwt = await jwt.encode({ ...jwt, token: jwtPayload }) // Set cookie, to also update expiry date on cookie - cookie.set(res, cookies.sessionToken.name, newEncodedJwt, { expires: sessionExpires, ...cookies.sessionToken.options }) - - await dispatchEvent(events.session, { session: sessionPayload, jwt: jwtPayload }) + cookie.set(res, cookies.sessionToken.name, newEncodedJwt, { + expires: sessionExpires, + ...cookies.sessionToken.options, + }) + + await dispatchEvent(events.session, { + session: sessionPayload, + jwt: jwtPayload, + }) } catch (error) { // If JWT not verifiable, make sure the cookie for it is removed and return empty object - logger.error('JWT_SESSION_ERROR', error) - cookie.set(res, cookies.sessionToken.name, '', { ...cookies.sessionToken.options, maxAge: 0 }) + logger.error("JWT_SESSION_ERROR", error) + cookie.set(res, cookies.sessionToken.name, "", { + ...cookies.sessionToken.options, + maxAge: 0, + }) } } else { try { - const { getUser, getSession, updateSession } = await adapter.getAdapter(req.options) + const { getUser, getSession, updateSession } = adapterErrorHandler( + await adapter.getAdapter(req.options) + ) const session = await getSession(sessionToken) if (session) { // Trigger update to session object to update session expiry @@ -73,29 +91,38 @@ export default async function session (req, res) { user: { name: user.name, email: user.email, - image: user.image + image: user.image, }, accessToken: session.accessToken, - expires: session.expires + expires: session.expires, } // Pass Session through to the session callback - const sessionPayload = await callbacks.session(defaultSessionPayload, user) + const sessionPayload = await callbacks.session( + defaultSessionPayload, + user + ) // Return session payload as response response = sessionPayload // Set cookie again to update expiry - cookie.set(res, cookies.sessionToken.name, sessionToken, { expires: session.expires, ...cookies.sessionToken.options }) + cookie.set(res, cookies.sessionToken.name, sessionToken, { + expires: session.expires, + ...cookies.sessionToken.options, + }) await dispatchEvent(events.session, { session: sessionPayload }) } else if (sessionToken) { // If sessionToken was found set but it's not valid for a session then // remove the sessionToken cookie from browser. - cookie.set(res, cookies.sessionToken.name, '', { ...cookies.sessionToken.options, maxAge: 0 }) + cookie.set(res, cookies.sessionToken.name, "", { + ...cookies.sessionToken.options, + maxAge: 0, + }) } } catch (error) { - logger.error('SESSION_ERROR', error) + logger.error("SESSION_ERROR", error) } } diff --git a/src/server/routes/signin.js b/src/server/routes/signin.js index 5d09e49d7e..b0c5252a70 100644 --- a/src/server/routes/signin.js +++ b/src/server/routes/signin.js @@ -1,35 +1,42 @@ -import getAuthorizationUrl from '../lib/signin/oauth' -import emailSignin from '../lib/signin/email' -import logger from '../../lib/logger' +import getAuthorizationUrl from "../lib/signin/oauth" +import emailSignin from "../lib/signin/email" +import adapterErrorHandler from "../../adapters/error-handler" -/** Handle requests to /api/auth/signin */ -export default async function signin (req, res) { +/** + * Handle requests to /api/auth/signin + * @param {import("types/internals").NextAuthRequest} req + * @param {import("types/internals").NextAuthResponse} res + */ +export default async function signin(req, res) { const { provider, baseUrl, basePath, adapter, - callbacks + callbacks, + logger, } = req.options if (!provider.type) { return res.status(500).end(`Error: Type not specified for ${provider.name}`) } - if (provider.type === 'oauth' && req.method === 'POST') { + if (provider.type === "oauth" && req.method === "POST") { try { const authorizationUrl = await getAuthorizationUrl(req) return res.redirect(authorizationUrl) } catch (error) { - logger.error('SIGNIN_OAUTH_ERROR', error) + logger.error("SIGNIN_OAUTH_ERROR", error) return res.redirect(`${baseUrl}${basePath}/error?error=OAuthSignin`) } - } else if (provider.type === 'email' && req.method === 'POST') { + } else if (provider.type === "email" && req.method === "POST") { if (!adapter) { - logger.error('EMAIL_REQUIRES_ADAPTER_ERROR') + logger.error("EMAIL_REQUIRES_ADAPTER_ERROR") return res.redirect(`${baseUrl}${basePath}/error?error=Configuration`) } - const { getUserByEmail } = await adapter.getAdapter(req.options) + const { getUserByEmail } = adapterErrorHandler( + await adapter.getAdapter(req.options) + ) // Note: Technically the part of the email address local mailbox element // (everything before the @ symbol) should be treated as 'case sensitive' @@ -39,36 +46,43 @@ export default async function signin (req, res) { const email = req.body.email?.toLowerCase() ?? null // If is an existing user return a user object (otherwise use placeholder) - const profile = await getUserByEmail(email) || { email } - const account = { id: provider.id, type: 'email', providerAccountId: email } + const profile = (await getUserByEmail(email)) || { email } + const account = { id: provider.id, type: "email", providerAccountId: email } // Check if user is allowed to sign in try { - const signInCallbackResponse = await callbacks.signIn(profile, account, { email, verificationRequest: true }) + const signInCallbackResponse = await callbacks.signIn(profile, account, { + email, + verificationRequest: true, + }) if (signInCallbackResponse === false) { return res.redirect(`${baseUrl}${basePath}/error?error=AccessDenied`) - } else if (typeof signInCallbackResponse === 'string') { + } else if (typeof signInCallbackResponse === "string") { return res.redirect(signInCallbackResponse) } } catch (error) { if (error instanceof Error) { - return res.redirect(`${baseUrl}${basePath}/error?error=${encodeURIComponent(error)}`) + return res.redirect( + `${baseUrl}${basePath}/error?error=${encodeURIComponent(error)}` + ) } // TODO: Remove in a future major release - logger.warn('SIGNIN_CALLBACK_REJECT_REDIRECT') + logger.warn("SIGNIN_CALLBACK_REJECT_REDIRECT") return res.redirect(error) } try { await emailSignin(email, provider, req.options) } catch (error) { - logger.error('SIGNIN_EMAIL_ERROR', error) + logger.error("SIGNIN_EMAIL_ERROR", error) return res.redirect(`${baseUrl}${basePath}/error?error=EmailSignin`) } - return res.redirect(`${baseUrl}${basePath}/verify-request?provider=${encodeURIComponent( - provider.id - )}&type=${encodeURIComponent(provider.type)}`) + return res.redirect( + `${baseUrl}${basePath}/verify-request?provider=${encodeURIComponent( + provider.id + )}&type=${encodeURIComponent(provider.type)}` + ) } return res.redirect(`${baseUrl}${basePath}/signin`) } diff --git a/src/server/routes/signout.js b/src/server/routes/signout.js index 175cbfb238..ffc548bc18 100644 --- a/src/server/routes/signout.js +++ b/src/server/routes/signout.js @@ -1,10 +1,14 @@ -import * as cookie from '../lib/cookie' -import logger from '../../lib/logger' -import dispatchEvent from '../lib/dispatch-event' +import * as cookie from "../lib/cookie" +import dispatchEvent from "../lib/dispatch-event" +import adapterErrorHandler from "../../adapters/error-handler" -/** Handle requests to /api/auth/signout */ -export default async function signout (req, res) { - const { adapter, cookies, events, jwt, callbackUrl } = req.options +/** + * Handle requests to /api/auth/signout + * @param {import("types/internals").NextAuthRequest} req + * @param {import("types/internals").NextAuthResponse} res + */ +export default async function signout(req, res) { + const { adapter, cookies, events, jwt, callbackUrl, logger } = req.options const useJwtSession = req.options.session.jwt const sessionToken = req.cookies[cookies.sessionToken.name] @@ -18,7 +22,9 @@ export default async function signout (req, res) { } } else { // Get session from database - const { getSession, deleteSession } = await adapter.getAdapter(req.options) + const { getSession, deleteSession } = adapterErrorHandler( + await adapter.getAdapter(req.options) + ) try { // Dispatch signout event @@ -33,14 +39,14 @@ export default async function signout (req, res) { await deleteSession(sessionToken) } catch (error) { // If error, log it but continue - logger.error('SIGNOUT_ERROR', error) + logger.error("SIGNOUT_ERROR", error) } } // Remove Session Token - cookie.set(res, cookies.sessionToken.name, '', { + cookie.set(res, cookies.sessionToken.name, "", { ...cookies.sessionToken.options, - maxAge: 0 + maxAge: 0, }) return res.redirect(callbackUrl) From b0b57997f8c348b102258ee8c20ed39b1da14b96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Fri, 30 Apr 2021 19:23:06 +0200 Subject: [PATCH 03/10] fix: correct adapterMethod --- src/adapters/error-handler.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/adapters/error-handler.js b/src/adapters/error-handler.js index 0099ad4298..0028f19183 100644 --- a/src/adapters/error-handler.js +++ b/src/adapters/error-handler.js @@ -7,9 +7,10 @@ import { UnknownError } from "../lib/errors" */ export default function adapterErrorHandler(adapter) { return Object.keys(adapter).reduce((acc, method) => { + const adapterMethod = adapter[method] acc[method] = async (...args) => { try { - return await acc[method](...args) + return await adapterMethod(...args) } catch (error) { const e = new UnknownError(error) e.name = `${method[0].toUpperCase()}${method.slice(1)}Error` From d790e8e3eb9d12d25386653c8d1697801c2966fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Fri, 30 Apr 2021 20:15:24 +0200 Subject: [PATCH 04/10] build: more sane build targets --- config/babel.config.js | 22 +++ config/babel.config.json | 15 -- package-lock.json | 395 ++++++++++++++++++++++++++++++++++++++- package.json | 8 +- 4 files changed, 413 insertions(+), 27 deletions(-) create mode 100644 config/babel.config.js delete mode 100644 config/babel.config.json diff --git a/config/babel.config.js b/config/babel.config.js new file mode 100644 index 0000000000..2a290a3ea2 --- /dev/null +++ b/config/babel.config.js @@ -0,0 +1,22 @@ +// We aim to have the same support as Next.js +// https://nextjs.org/docs/getting-started#system-requirements +// https://nextjs.org/docs/basic-features/supported-browsers-features + +module.exports = { + presets: [["@babel/preset-env", { targets: { node: "10.13" } }]], + plugins: [ + "@babel/plugin-proposal-class-properties", + "@babel/plugin-transform-runtime", + ], + comments: false, + overrides: [ + { + test: ["../src/client/**"], + presets: [["@babel/preset-env", { targets: { ie: "11" } }]], + }, + { + test: ["../src/server/pages/**"], + presets: ["preact"], + }, + ], +} diff --git a/config/babel.config.json b/config/babel.config.json deleted file mode 100644 index e530a844cf..0000000000 --- a/config/babel.config.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "presets": [ - ["@babel/preset-env", { "targets": { "esmodules": true } }] - ], - "plugins": [ - "@babel/plugin-proposal-class-properties" - ], - "comments": false, - "overrides": [ - { - "test": ["../src/server/pages/**"], - "presets": ["preact"] - } - ] -} diff --git a/package-lock.json b/package-lock.json index d204911c8b..16e5e010da 100644 --- a/package-lock.json +++ b/package-lock.json @@ -489,6 +489,227 @@ } } }, + "@babel/helper-define-polyfill-provider": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.2.0.tgz", + "integrity": "sha512-JT8tHuFjKBo8NnaUbblz7mIu1nnvUDiHVjXXkulZULyidvo/7P6TY7+YqpV37IfF+KUFxmlK04elKtGKXaiVgw==", + "dev": true, + "requires": { + "@babel/helper-compilation-targets": "^7.13.0", + "@babel/helper-module-imports": "^7.12.13", + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/traverse": "^7.13.0", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2", + "semver": "^6.1.2" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", + "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", + "dev": true, + "requires": { + "@babel/highlight": "^7.12.13" + } + }, + "@babel/compat-data": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.0.tgz", + "integrity": "sha512-vu9V3uMM/1o5Hl5OekMUowo3FqXLJSw+s+66nt0fSWVWTtmosdzn45JHOB3cPtZoe6CTBDzvSw0RdOY85Q37+Q==", + "dev": true + }, + "@babel/generator": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.0.tgz", + "integrity": "sha512-C6u00HbmsrNPug6A+CiNl8rEys7TsdcXwg12BHi2ca5rUfAs3+UwZsuDQSXnc+wCElCXMB8gMaJ3YXDdh8fAlg==", + "dev": true, + "requires": { + "@babel/types": "^7.14.0", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-compilation-targets": { + "version": "7.13.16", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.13.16.tgz", + "integrity": "sha512-3gmkYIrpqsLlieFwjkGgLaSHmhnvlAYzZLlYVjlW+QwI+1zE17kGxuJGmIqDQdYp56XdmGeD+Bswx0UTyG18xA==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.13.15", + "@babel/helper-validator-option": "^7.12.17", + "browserslist": "^4.14.5", + "semver": "^6.3.0" + } + }, + "@babel/helper-function-name": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz", + "integrity": "sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.12.13", + "@babel/template": "^7.12.13", + "@babel/types": "^7.12.13" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz", + "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==", + "dev": true, + "requires": { + "@babel/types": "^7.12.13" + } + }, + "@babel/helper-module-imports": { + "version": "7.13.12", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.13.12.tgz", + "integrity": "sha512-4cVvR2/1B693IuOvSI20xqqa/+bl7lqAMR59R4iu39R9aOX8/JoYY1sFaNvUMyMBGnHdwvJgUrzNLoUZxXypxA==", + "dev": true, + "requires": { + "@babel/types": "^7.13.12" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", + "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==", + "dev": true + }, + "@babel/helper-split-export-declaration": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz", + "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==", + "dev": true, + "requires": { + "@babel/types": "^7.12.13" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz", + "integrity": "sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==", + "dev": true + }, + "@babel/helper-validator-option": { + "version": "7.12.17", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.17.tgz", + "integrity": "sha512-TopkMDmLzq8ngChwRlyjR6raKD6gMSae4JdYDB8bByKreQgG0RBTuKe9LRxW3wFtUnjxOPRKBDwEH6Mg5KeDfw==", + "dev": true + }, + "@babel/highlight": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.0.tgz", + "integrity": "sha512-YSCOwxvTYEIMSGaBQb5kDDsCopDdiUGsqpatp3fOlI4+2HQSkTmEVWnVuySdAC5EWCqSWWTv0ib63RjR7dTBdg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.14.0", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.0.tgz", + "integrity": "sha512-AHbfoxesfBALg33idaTBVUkLnfXtsgvJREf93p4p0Lwsz4ppfE7g1tpEXVm4vrxUcH4DVhAa9Z1m1zqf9WUC7Q==", + "dev": true + }, + "@babel/template": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz", + "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@babel/parser": "^7.12.13", + "@babel/types": "^7.12.13" + } + }, + "@babel/traverse": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.0.tgz", + "integrity": "sha512-dZ/a371EE5XNhTHomvtuLTUyx6UEoJmYX+DT5zBCQN3McHemsuIaKKYqsc/fs26BEkHs/lBZy0J571LP5z9kQA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@babel/generator": "^7.14.0", + "@babel/helper-function-name": "^7.12.13", + "@babel/helper-split-export-declaration": "^7.12.13", + "@babel/parser": "^7.14.0", + "@babel/types": "^7.14.0", + "debug": "^4.1.0", + "globals": "^11.1.0" + } + }, + "@babel/types": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.0.tgz", + "integrity": "sha512-O2LVLdcnWplaGxiPBz12d0HcdN8QdxdsWYhz5LSeuukV/5mn2xUUc3gBeU4QBYPJ18g/UToe8F532XJ608prmg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.14.0", + "to-fast-properties": "^2.0.0" + } + }, + "browserslist": { + "version": "4.16.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.6.tgz", + "integrity": "sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001219", + "colorette": "^1.2.2", + "electron-to-chromium": "^1.3.723", + "escalade": "^3.1.1", + "node-releases": "^1.1.71" + } + }, + "caniuse-lite": { + "version": "1.0.30001219", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001219.tgz", + "integrity": "sha512-c0yixVG4v9KBc/tQ2rlbB3A/bgBFRvl8h8M4IeUbqCca4gsiCfvtaheUssbnux/Mb66Vjz7x8yYjDgYcNQOhyQ==", + "dev": true + }, + "colorette": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz", + "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==", + "dev": true + }, + "electron-to-chromium": { + "version": "1.3.723", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.723.tgz", + "integrity": "sha512-L+WXyXI7c7+G1V8ANzRsPI5giiimLAUDC6Zs1ojHHPhYXb3k/iTABFmWjivEtsWrRQymjnO66/rO2ZTABGdmWg==", + "dev": true + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node-releases": { + "version": "1.1.71", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.71.tgz", + "integrity": "sha512-zR6HoT6LrLCRBwukmrVbHv0EpEQjksO6GmFcZQQuCAy139BEsoVKPYnf3jongYW83fAa1torLGYwxxky/p28sg==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, "@babel/helper-explode-assignable-expression": { "version": "7.12.1", "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.12.1.tgz", @@ -2029,6 +2250,59 @@ } } }, + "@babel/plugin-transform-runtime": { + "version": "7.13.15", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.13.15.tgz", + "integrity": "sha512-d+ezl76gx6Jal08XngJUkXM4lFXK/5Ikl9Mh4HKDxSfGJXmZ9xG64XT2oivBzfxb/eQ62VfvoMkaCZUKJMVrBA==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.13.12", + "@babel/helper-plugin-utils": "^7.13.0", + "babel-plugin-polyfill-corejs2": "^0.2.0", + "babel-plugin-polyfill-corejs3": "^0.2.0", + "babel-plugin-polyfill-regenerator": "^0.2.0", + "semver": "^6.3.0" + }, + "dependencies": { + "@babel/helper-module-imports": { + "version": "7.13.12", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.13.12.tgz", + "integrity": "sha512-4cVvR2/1B693IuOvSI20xqqa/+bl7lqAMR59R4iu39R9aOX8/JoYY1sFaNvUMyMBGnHdwvJgUrzNLoUZxXypxA==", + "dev": true, + "requires": { + "@babel/types": "^7.13.12" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", + "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==", + "dev": true + }, + "@babel/helper-validator-identifier": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz", + "integrity": "sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==", + "dev": true + }, + "@babel/types": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.0.tgz", + "integrity": "sha512-O2LVLdcnWplaGxiPBz12d0HcdN8QdxdsWYhz5LSeuukV/5mn2xUUc3gBeU4QBYPJ18g/UToe8F532XJ608prmg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.14.0", + "to-fast-properties": "^2.0.0" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, "@babel/plugin-transform-shorthand-properties": { "version": "7.12.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.12.1.tgz", @@ -2272,10 +2546,9 @@ } }, "@babel/runtime": { - "version": "7.12.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.5.tgz", - "integrity": "sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==", - "dev": true, + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.0.tgz", + "integrity": "sha512-JELkvo/DlpNdJ7dlyw/eY7E0suy5i5GQH+Vlxaq1nsNJ+H7f4Vtv3jMeCEgRhZZQFXTjldYfQgv2qmM6M1v5wA==", "requires": { "regenerator-runtime": "^0.13.4" } @@ -2514,9 +2787,9 @@ "integrity": "sha512-rIIisYBvVtxDbY9Lbm+HOLbZyOaaEmtGc9wDN3tJLDUu3sLJOXNN7Pz29ThS+gf2lpMxXnfvk587hxGrnhCghQ==" }, "@next-auth/typeorm-legacy-adapter": { - "version": "0.0.2-canary.116", - "resolved": "https://registry.npmjs.org/@next-auth/typeorm-legacy-adapter/-/typeorm-legacy-adapter-0.0.2-canary.116.tgz", - "integrity": "sha512-06knawdYdHkiMVw5GfR6Ku5ryITdaOLWIGCbRwAtgCZE5Pf6am+nhnqQVeGq18odu84SmzA+hTtkGSAYbR6MIA==", + "version": "0.0.2-canary.117", + "resolved": "https://registry.npmjs.org/@next-auth/typeorm-legacy-adapter/-/typeorm-legacy-adapter-0.0.2-canary.117.tgz", + "integrity": "sha512-sYJZPWMsM1ZTJcl749UojYDF4q8+ZiYcrR7rM4SACc0qiA9VBdOYUUMUMpQoazKOiwo1rWDKu4wHPt6CdV0ctQ==", "requires": { "crypto-js": "^4.0.0", "require_optional": "^1.0.1", @@ -4080,6 +4353,105 @@ "object.assign": "^4.1.0" } }, + "babel-plugin-polyfill-corejs2": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.2.0.tgz", + "integrity": "sha512-9bNwiR0dS881c5SHnzCmmGlMkJLl0OUZvxrxHo9w/iNoRuqaPjqlvBf4HrovXtQs/au5yKkpcdgfT1cC5PAZwg==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.13.11", + "@babel/helper-define-polyfill-provider": "^0.2.0", + "semver": "^6.1.1" + }, + "dependencies": { + "@babel/compat-data": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.0.tgz", + "integrity": "sha512-vu9V3uMM/1o5Hl5OekMUowo3FqXLJSw+s+66nt0fSWVWTtmosdzn45JHOB3cPtZoe6CTBDzvSw0RdOY85Q37+Q==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "babel-plugin-polyfill-corejs3": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.2.0.tgz", + "integrity": "sha512-zZyi7p3BCUyzNxLx8KV61zTINkkV65zVkDAFNZmrTCRVhjo1jAS+YLvDJ9Jgd/w2tsAviCwFHReYfxO3Iql8Yg==", + "dev": true, + "requires": { + "@babel/helper-define-polyfill-provider": "^0.2.0", + "core-js-compat": "^3.9.1" + }, + "dependencies": { + "browserslist": { + "version": "4.16.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.6.tgz", + "integrity": "sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001219", + "colorette": "^1.2.2", + "electron-to-chromium": "^1.3.723", + "escalade": "^3.1.1", + "node-releases": "^1.1.71" + } + }, + "caniuse-lite": { + "version": "1.0.30001219", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001219.tgz", + "integrity": "sha512-c0yixVG4v9KBc/tQ2rlbB3A/bgBFRvl8h8M4IeUbqCca4gsiCfvtaheUssbnux/Mb66Vjz7x8yYjDgYcNQOhyQ==", + "dev": true + }, + "colorette": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz", + "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==", + "dev": true + }, + "core-js-compat": { + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.11.1.tgz", + "integrity": "sha512-aZ0e4tmlG/aOBHj92/TuOuZwp6jFvn1WNabU5VOVixzhu5t5Ao+JZkQOPlgNXu6ynwLrwJxklT4Gw1G1VGEh+g==", + "dev": true, + "requires": { + "browserslist": "^4.16.5", + "semver": "7.0.0" + } + }, + "electron-to-chromium": { + "version": "1.3.723", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.723.tgz", + "integrity": "sha512-L+WXyXI7c7+G1V8ANzRsPI5giiimLAUDC6Zs1ojHHPhYXb3k/iTABFmWjivEtsWrRQymjnO66/rO2ZTABGdmWg==", + "dev": true + }, + "node-releases": { + "version": "1.1.71", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.71.tgz", + "integrity": "sha512-zR6HoT6LrLCRBwukmrVbHv0EpEQjksO6GmFcZQQuCAy139BEsoVKPYnf3jongYW83fAa1torLGYwxxky/p28sg==", + "dev": true + }, + "semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "dev": true + } + } + }, + "babel-plugin-polyfill-regenerator": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.2.0.tgz", + "integrity": "sha512-J7vKbCuD2Xi/eEHxquHN14bXAW9CXtecwuLrOIDJtcZzTaPzV1VdEfoUf9AzcRBMolKUQKM9/GVojeh0hFiqMg==", + "dev": true, + "requires": { + "@babel/helper-define-polyfill-provider": "^0.2.0" + } + }, "babel-plugin-syntax-jsx": { "version": "6.18.0", "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", @@ -8903,6 +9275,12 @@ "integrity": "sha1-+CbJtOKoUR2E46yinbBeGk87cqk=", "dev": true }, + "lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", + "dev": true + }, "lodash.escaperegexp": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", @@ -15935,8 +16313,7 @@ "regenerator-runtime": { "version": "0.13.7", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", - "dev": true + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" }, "regenerator-transform": { "version": "0.14.5", diff --git a/package.json b/package.json index 63b10c17d2..8bb09e50ca 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "next-auth", - "version": "0.0.0-semantically-released", + "version": "3.20.0-canary.3", "description": "Authentication for Next.js", "homepage": "https://next-auth.js.org", "repository": "https://github.com/nextauthjs/next-auth.git", @@ -30,12 +30,12 @@ }, "scripts": { "build": "npm run build:js && npm run build:css", - "build:js": "node ./config/build.js && babel --config-file ./config/babel.config.json src --out-dir dist", + "build:js": "node ./config/build.js && babel --config-file ./config/babel.config.js src --out-dir dist", "build:css": "postcss --config config/postcss.config.js src/**/*.css --base src --dir dist && node config/wrap-css.js", "dev:setup": "npm run build:css && cd app && npm i", "dev": "cd app && npm run dev", "watch": "npm run watch:js | npm run watch:css", - "watch:js": "babel --config-file ./config/babel.config.json --watch src --out-dir dist", + "watch:js": "babel --config-file ./config/babel.config.js --watch src --out-dir dist", "watch:css": "postcss --config config/postcss.config.js --watch src/**/*.css --base src --dir dist", "test": "echo \"Write some tests...\"; npm run test:types", "test:types": "dtslint types", @@ -61,6 +61,7 @@ ], "license": "ISC", "dependencies": { + "@babel/runtime": "^7.14.0", "@next-auth/prisma-legacy-adapter": "canary", "@next-auth/typeorm-legacy-adapter": "canary", "crypto-js": "^4.0.0", @@ -91,6 +92,7 @@ "@babel/cli": "^7.8.4", "@babel/core": "^7.9.6", "@babel/plugin-proposal-class-properties": "^7.13.0", + "@babel/plugin-transform-runtime": "^7.13.15", "@babel/preset-env": "^7.9.6", "@prisma/client": "^2.16.1", "@semantic-release/commit-analyzer": "^8.0.1", From dd4be24e070d67395d7fb42f5592211e782783e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Sat, 1 May 2021 12:52:03 +0200 Subject: [PATCH 05/10] feat(adapter): add built-in logging --- src/adapters/error-handler.js | 18 ++++++++++++++++-- src/server/lib/callback-handler.js | 2 +- src/server/lib/signin/email.js | 12 ++++++++++-- src/server/routes/callback.js | 3 ++- src/server/routes/session.js | 3 ++- src/server/routes/signin.js | 3 ++- src/server/routes/signout.js | 3 ++- 7 files changed, 35 insertions(+), 9 deletions(-) diff --git a/src/adapters/error-handler.js b/src/adapters/error-handler.js index 0028f19183..f111deaaad 100644 --- a/src/adapters/error-handler.js +++ b/src/adapters/error-handler.js @@ -3,20 +3,34 @@ import { UnknownError } from "../lib/errors" /** * Handles adapter induced errors. * @param {import("types/adapters").AdapterInstance} adapter + * @param {import("types").LoggerInstance} logger * @return {import("types/adapters").AdapterInstance} */ -export default function adapterErrorHandler(adapter) { +export default function adapterErrorHandler(adapter, logger) { return Object.keys(adapter).reduce((acc, method) => { + const name = capitalize(method) + const code = upperSnake(name, adapter.displayName) + const adapterMethod = adapter[method] acc[method] = async (...args) => { try { + logger.debug(code, ...args) return await adapterMethod(...args) } catch (error) { + logger.error(`${code}_ERROR`, error) const e = new UnknownError(error) - e.name = `${method[0].toUpperCase()}${method.slice(1)}Error` + e.name = `${name}Error` throw e } } return acc }, {}) } + +function capitalize(s) { + return `${s[0].toUpperCase()}${s.slice(1)}` +} + +function upperSnake(s, prefix = "ADAPTER") { + return `${prefix}_${s.replace(/([A-Z])/g, "_$1")}`.toUpperCase() +} diff --git a/src/server/lib/callback-handler.js b/src/server/lib/callback-handler.js index 37f28792e6..d518328999 100644 --- a/src/server/lib/callback-handler.js +++ b/src/server/lib/callback-handler.js @@ -54,7 +54,7 @@ export default async function callbackHandler( createSession, getSession, deleteSession, - } = adapterErrorHandler(await adapter.getAdapter(options)) + } = adapterErrorHandler(await adapter.getAdapter(options), logger) let session = null let user = null diff --git a/src/server/lib/signin/email.js b/src/server/lib/signin/email.js index dcc912fbd5..0716dc9f39 100644 --- a/src/server/lib/signin/email.js +++ b/src/server/lib/signin/email.js @@ -1,12 +1,20 @@ import { randomBytes } from "crypto" import adapterErrorHandler from "../../../adapters/error-handler" +/** + * + * @param {string} email + * @param {import("types/providers").EmailConfig} provider + * @param {import("types/internals").AppOptions} options + * @returns + */ export default async function email(email, provider, options) { try { - const { baseUrl, basePath, adapter } = options + const { baseUrl, basePath, adapter, logger } = options const { createVerificationRequest } = adapterErrorHandler( - await adapter.getAdapter(options) + await adapter.getAdapter(options), + logger ) // Prefer provider specific secret, but use default secret if none specified diff --git a/src/server/routes/callback.js b/src/server/routes/callback.js index f3af02c2aa..6de511be16 100644 --- a/src/server/routes/callback.js +++ b/src/server/routes/callback.js @@ -59,7 +59,8 @@ export default async function callback(req, res) { let userOrProfile = profile if (adapter) { const { getUserByProviderAccountId } = adapterErrorHandler( - await adapter.getAdapter(req.options) + await adapter.getAdapter(req.options), + logger ) const userFromProviderAccountId = await getUserByProviderAccountId( account.provider, diff --git a/src/server/routes/session.js b/src/server/routes/session.js index 4af1a33dd7..b27622a13e 100644 --- a/src/server/routes/session.js +++ b/src/server/routes/session.js @@ -76,7 +76,8 @@ export default async function session(req, res) { } else { try { const { getUser, getSession, updateSession } = adapterErrorHandler( - await adapter.getAdapter(req.options) + await adapter.getAdapter(req.options), + logger ) const session = await getSession(sessionToken) if (session) { diff --git a/src/server/routes/signin.js b/src/server/routes/signin.js index b0c5252a70..93935c5600 100644 --- a/src/server/routes/signin.js +++ b/src/server/routes/signin.js @@ -35,7 +35,8 @@ export default async function signin(req, res) { return res.redirect(`${baseUrl}${basePath}/error?error=Configuration`) } const { getUserByEmail } = adapterErrorHandler( - await adapter.getAdapter(req.options) + await adapter.getAdapter(req.options), + logger ) // Note: Technically the part of the email address local mailbox element diff --git a/src/server/routes/signout.js b/src/server/routes/signout.js index ffc548bc18..8e5e9ee336 100644 --- a/src/server/routes/signout.js +++ b/src/server/routes/signout.js @@ -23,7 +23,8 @@ export default async function signout(req, res) { } else { // Get session from database const { getSession, deleteSession } = adapterErrorHandler( - await adapter.getAdapter(req.options) + await adapter.getAdapter(req.options), + logger ) try { From c1be7afdc22fa8e39b1e8f3ace3839373751d8fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Sat, 1 May 2021 12:53:32 +0200 Subject: [PATCH 06/10] chore: revert version number --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8bb09e50ca..82736d6b0e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "next-auth", - "version": "3.20.0-canary.3", + "version": "0.0.0-semantically-released", "description": "Authentication for Next.js", "homepage": "https://next-auth.js.org", "repository": "https://github.com/nextauthjs/next-auth.git", From 3524264a21efcf2d3208fad9b47ebe2e33b2e903 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Sat, 1 May 2021 12:55:38 +0200 Subject: [PATCH 07/10] chore: revert unnecessary file formatting --- src/server/index.js | 175 ++++++++++++++++++-------------------------- 1 file changed, 72 insertions(+), 103 deletions(-) diff --git a/src/server/index.js b/src/server/index.js index 88fac60966..be963b0df0 100644 --- a/src/server/index.js +++ b/src/server/index.js @@ -1,24 +1,24 @@ -import adapters from "../adapters" -import jwt from "../lib/jwt" -import parseUrl from "../lib/parse-url" -import logger, { setLogger } from "../lib/logger" -import * as cookie from "./lib/cookie" -import * as defaultEvents from "./lib/default-events" -import * as defaultCallbacks from "./lib/default-callbacks" -import parseProviders from "./lib/providers" -import * as routes from "./routes" -import renderPage from "./pages" -import createSecret from "./lib/create-secret" -import callbackUrlHandler from "./lib/callback-url-handler" -import extendRes from "./lib/extend-res" -import csrfTokenHandler from "./lib/csrf-token-handler" -import * as pkce from "./lib/oauth/pkce-handler" -import * as state from "./lib/oauth/state-handler" +import adapters from '../adapters' +import jwt from '../lib/jwt' +import parseUrl from '../lib/parse-url' +import logger, { setLogger } from '../lib/logger' +import * as cookie from './lib/cookie' +import * as defaultEvents from './lib/default-events' +import * as defaultCallbacks from './lib/default-callbacks' +import parseProviders from './lib/providers' +import * as routes from './routes' +import renderPage from './pages' +import createSecret from './lib/create-secret' +import callbackUrlHandler from './lib/callback-url-handler' +import extendRes from './lib/extend-res' +import csrfTokenHandler from './lib/csrf-token-handler' +import * as pkce from './lib/oauth/pkce-handler' +import * as state from './lib/oauth/state-handler' // To work properly in production with OAuth providers the NEXTAUTH_URL // environment variable must be set. if (!process.env.NEXTAUTH_URL) { - logger.warn("NEXTAUTH_URL", "NEXTAUTH_URL environment variable not set") + logger.warn('NEXTAUTH_URL', 'NEXTAUTH_URL environment variable not set') } /** @@ -26,7 +26,7 @@ if (!process.env.NEXTAUTH_URL) { * @param {import("next").NextApiResponse} res * @param {import("types").NextAuthOptions} userOptions */ -async function NextAuthHandler(req, res, userOptions) { +async function NextAuthHandler (req, res, userOptions) { if (userOptions.logger) { setLogger(userOptions.logger) } @@ -39,15 +39,13 @@ async function NextAuthHandler(req, res, userOptions) { // to avoid early termination of calls to the serverless function // (and then return that promise when we are done) - eslint // complains but I'm not sure there is another way to do this. - // eslint-disable-next-line no-async-promise-executor - return new Promise(async (resolve) => { + return new Promise(async resolve => { // eslint-disable-line no-async-promise-executor extendRes(req, res, resolve) if (!req.query.nextauth) { - const error = - "Cannot find [...nextauth].js in pages/api/auth. Make sure the filename is written correctly." + const error = 'Cannot find [...nextauth].js in pages/api/auth. Make sure the filename is written correctly.' - logger.error("MISSING_NEXTAUTH_API_ROUTE_ERROR", error) + logger.error('MISSING_NEXTAUTH_API_ROUTE_ERROR', error) return res.status(500).end(`Error: ${error}`) } @@ -55,38 +53,30 @@ async function NextAuthHandler(req, res, userOptions) { nextauth, action = nextauth[0], providerId = nextauth[1], - error = nextauth[1], + error = nextauth[1] } = req.query // @todo refactor all existing references to baseUrl and basePath - const { basePath, baseUrl } = parseUrl( - process.env.NEXTAUTH_URL || process.env.VERCEL_URL - ) + const { basePath, baseUrl } = parseUrl(process.env.NEXTAUTH_URL || process.env.VERCEL_URL) const cookies = { - ...cookie.defaultCookies( - userOptions.useSecureCookies || baseUrl.startsWith("https://") - ), + ...cookie.defaultCookies(userOptions.useSecureCookies || baseUrl.startsWith('https://')), // Allow user cookie options to override any cookie settings above - ...userOptions.cookies, + ...userOptions.cookies } const secret = createSecret({ userOptions, basePath, baseUrl }) - const providers = parseProviders({ - providers: userOptions.providers, - baseUrl, - basePath, - }) + const providers = parseProviders({ providers: userOptions.providers, baseUrl, basePath }) const provider = providers.find(({ id }) => id === providerId) // Protection only works on OAuth 2.x providers - if (provider?.type === "oauth" && provider.version?.startsWith("2")) { + if (provider?.type === 'oauth' && provider.version?.startsWith('2')) { // When provider.state is undefined, we still want this to pass if (!provider.protection && provider.state !== false) { // Default to state, as we did in 3.1 REVIEW: should we use "pkce" or "none" as default? - provider.protection = ["state"] - } else if (typeof provider.protection === "string") { + provider.protection = ['state'] + } else if (typeof provider.protection === 'string') { provider.protection = [provider.protection] } } @@ -96,16 +86,14 @@ async function NextAuthHandler(req, res, userOptions) { // Parse database / adapter // If adapter is provided, use it (advanced usage, overrides database) // If database URI or config object is provided, use it (simple usage) - const adapter = - userOptions.adapter ?? - (userOptions.database && adapters.Default(userOptions.database)) + const adapter = userOptions.adapter ?? (userOptions.database && adapters.Default(userOptions.database)) // User provided options are overriden by other options, // except for the options with special handling above req.options = { debug: false, pages: {}, - theme: "auto", + theme: 'auto', // Custom options override defaults ...userOptions, // These computed settings can have values in userOptions but we override them @@ -123,7 +111,7 @@ async function NextAuthHandler(req, res, userOptions) { jwt: !adapter, // If no adapter specified, force use of JSON Web Tokens (stateless) maxAge, updateAge: 24 * 60 * 60, // Sessions updated only if session is greater than this value (0 = always, 24*60*60 = every 24 hours) - ...userOptions.session, + ...userOptions.session }, // JWT options jwt: { @@ -131,20 +119,20 @@ async function NextAuthHandler(req, res, userOptions) { maxAge, // same as session maxAge, encode: jwt.encode, decode: jwt.decode, - ...userOptions.jwt, + ...userOptions.jwt }, // Event messages events: { ...defaultEvents, - ...userOptions.events, + ...userOptions.events }, // Callback functions callbacks: { ...defaultCallbacks, - ...userOptions.callbacks, + ...userOptions.callbacks }, pkce: {}, - logger, + logger } csrfTokenHandler(req, res) @@ -153,79 +141,65 @@ async function NextAuthHandler(req, res, userOptions) { const render = renderPage(req, res) const { pages } = req.options - if (req.method === "GET") { + if (req.method === 'GET') { switch (action) { - case "providers": + case 'providers': return routes.providers(req, res) - case "session": + case 'session': return routes.session(req, res) - case "csrf": + case 'csrf': return res.json({ csrfToken: req.options.csrfToken }) - case "signin": + case 'signin': if (pages.signIn) { - let signinUrl = `${pages.signIn}${ - pages.signIn.includes("?") ? "&" : "?" - }callbackUrl=${req.options.callbackUrl}` - if (error) { - signinUrl = `${signinUrl}&error=${error}` - } + let signinUrl = `${pages.signIn}${pages.signIn.includes('?') ? '&' : '?'}callbackUrl=${req.options.callbackUrl}` + if (error) { signinUrl = `${signinUrl}&error=${error}` } return res.redirect(signinUrl) } return render.signin() - case "signout": + case 'signout': if (pages.signOut) { - return res.redirect( - `${pages.signOut}${ - pages.signOut.includes("?") ? "&" : "?" - }error=${error}` - ) + return res.redirect(`${pages.signOut}${pages.signOut.includes('?') ? '&' : '?'}error=${error}`) } return render.signout() - case "callback": + case 'callback': if (provider) { if (await pkce.handleCallback(req, res)) return if (await state.handleCallback(req, res)) return return routes.callback(req, res) } break - case "verify-request": + case 'verify-request': if (pages.verifyRequest) { return res.redirect(pages.verifyRequest) } return render.verifyRequest() - case "error": + case 'error': if (pages.error) { - return res.redirect( - `${pages.error}${ - pages.error.includes("?") ? "&" : "?" - }error=${error}` - ) + return res.redirect(`${pages.error}${pages.error.includes('?') ? '&' : '?'}error=${error}`) } // These error messages are displayed in line on the sign in page - if ( - [ - "Signin", - "OAuthSignin", - "OAuthCallback", - "OAuthCreateAccount", - "EmailCreateAccount", - "Callback", - "OAuthAccountNotLinked", - "EmailSignin", - "CredentialsSignin", - ].includes(error) - ) { + if ([ + 'Signin', + 'OAuthSignin', + 'OAuthCallback', + 'OAuthCreateAccount', + 'EmailCreateAccount', + 'Callback', + 'OAuthAccountNotLinked', + 'EmailSignin', + 'CredentialsSignin' + ].includes(error)) { return res.redirect(`${baseUrl}${basePath}/signin?error=${error}`) } return render.error({ error }) default: } - } else if (req.method === "POST") { + } else if (req.method === 'POST') { switch (action) { - case "signin": + case 'signin': // Verified CSRF Token required for all sign in routes if (req.options.csrfTokenVerified && provider) { if (await pkce.handleSignin(req, res)) return @@ -234,19 +208,16 @@ async function NextAuthHandler(req, res, userOptions) { } return res.redirect(`${baseUrl}${basePath}/signin?csrf=true`) - case "signout": + case 'signout': // Verified CSRF Token required for signout if (req.options.csrfTokenVerified) { return routes.signout(req, res) } return res.redirect(`${baseUrl}${basePath}/signout?csrf=true`) - case "callback": + case 'callback': if (provider) { // Verified CSRF Token required for credentials providers only - if ( - provider.type === "credentials" && - !req.options.csrfTokenVerified - ) { + if (provider.type === 'credentials' && !req.options.csrfTokenVerified) { return res.redirect(`${baseUrl}${basePath}/signin?csrf=true`) } @@ -255,33 +226,31 @@ async function NextAuthHandler(req, res, userOptions) { return routes.callback(req, res) } break - case "_log": + case '_log': if (userOptions.logger) { try { const { - code = "CLIENT_ERROR", - level = "error", - message = "[]", + code = 'CLIENT_ERROR', + level = 'error', + message = '[]' } = req.body logger[level](code, ...JSON.parse(message)) } catch (error) { // If logging itself failed... - logger.error("LOGGER_ERROR", error) + logger.error('LOGGER_ERROR', error) } } return res.end() default: } } - return res - .status(400) - .end(`Error: HTTP ${req.method} is not supported for ${req.url}`) + return res.status(400).end(`Error: HTTP ${req.method} is not supported for ${req.url}`) }) } /** Tha main entry point to next-auth */ -export default function NextAuth(...args) { +export default function NextAuth (...args) { if (args.length === 1) { return (req, res) => NextAuthHandler(req, res, args[0]) } From a7e6d411c1c0b05528ed91202452554a22eadf8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Sat, 1 May 2021 13:04:03 +0200 Subject: [PATCH 08/10] fix(ts): add displayName to AdapterInstance --- types/adapters.d.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/types/adapters.d.ts b/types/adapters.d.ts index 7648e5a2be..033f5e32c0 100644 --- a/types/adapters.d.ts +++ b/types/adapters.d.ts @@ -22,6 +22,8 @@ export default Adapters * [Create a custom adapter](https://next-auth.js.org/tutorials/creating-a-database-adapter) */ export interface AdapterInstance { + /** Used as a prefix for adapter related log messages. (Defaults to `ADAPTER_`) */ + displayName?: string createUser(profile: P): Promise getUser(id: string): Promise getUserByEmail(email: string): Promise From cd853142edb8374261bd8ca20c06999083885633 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Sat, 1 May 2021 13:07:30 +0200 Subject: [PATCH 09/10] fix: define logger in callback-handler --- src/server/lib/callback-handler.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/server/lib/callback-handler.js b/src/server/lib/callback-handler.js index d518328999..a3c67dfcb9 100644 --- a/src/server/lib/callback-handler.js +++ b/src/server/lib/callback-handler.js @@ -13,6 +13,10 @@ import adapterErrorHandler from "../../adapters/error-handler" * All verification (e.g. OAuth flows or email address verificaiton flows) are * done prior to this handler being called to avoid additonal complexity in this * handler. + * @param {import("types").Session} sessionToken + * @param {import("types").Profile} profile + * @param {import("types").Account} account + * @param {import("types/internals").AppOptions} options */ export default async function callbackHandler( sessionToken, @@ -54,7 +58,7 @@ export default async function callbackHandler( createSession, getSession, deleteSession, - } = adapterErrorHandler(await adapter.getAdapter(options), logger) + } = adapterErrorHandler(await adapter.getAdapter(options), options.logger) let session = null let user = null From 6ec8eccf6e9b8e7366e4a941e33689befa77d7c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Tue, 4 May 2021 00:16:27 +0200 Subject: [PATCH 10/10] fix(logger): pass logger to adapterErrorHandler --- src/server/routes/callback.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/routes/callback.js b/src/server/routes/callback.js index 6de511be16..5a63681953 100644 --- a/src/server/routes/callback.js +++ b/src/server/routes/callback.js @@ -187,7 +187,7 @@ export default async function callback(req, res) { getVerificationRequest, deleteVerificationRequest, getUserByEmail, - } = adapterErrorHandler(await adapter.getAdapter(req.options)) + } = adapterErrorHandler(await adapter.getAdapter(req.options), logger) const verificationToken = req.query.token const email = req.query.email