Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: decouple Next.js from core #2857

Merged
merged 31 commits into from
Oct 27, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
d6522be
refactor: decouple Next.js from core (WIP)
balazsorban44 Sep 29, 2021
ae04178
refactor: use `base` instead of `baseUrl`+`basePath`
balazsorban44 Sep 29, 2021
019b1d5
fix: signout route
balazsorban44 Sep 29, 2021
2d2f35a
refactor(ts): convert files to TS
balazsorban44 Sep 29, 2021
92ac44e
fix: imports
balazsorban44 Sep 29, 2021
9a549e3
refactor: convert callback route
balazsorban44 Sep 29, 2021
1d7a945
fix: add `next` files to package
balazsorban44 Sep 29, 2021
7166da3
chore(dev): alias npm email
balazsorban44 Sep 29, 2021
d931ff4
refactor: do not merge req with user options
balazsorban44 Sep 29, 2021
3bac68d
refactor: rename userOptions to options
balazsorban44 Sep 29, 2021
f517629
refactor: use native `URL` in `parseUrl`
balazsorban44 Sep 30, 2021
539f3e2
refactor: move Next.js specific code to `next` module
balazsorban44 Sep 30, 2021
c45dbaa
refactor(ts): return `OutgoingResponse` on all routes
balazsorban44 Sep 30, 2021
118d09f
fix: change `base` to `url`
balazsorban44 Sep 30, 2021
b09a4bb
feat: introduce `getServerSession`
balazsorban44 Sep 30, 2021
3abdab5
refactor: move main logic to `handler` file
balazsorban44 Sep 30, 2021
9aba573
chore(dev): showcase `getServerSession`
balazsorban44 Sep 30, 2021
8eb98cb
feat: extract `sessionToken` from Authorization header
balazsorban44 Oct 1, 2021
b9fe245
fix: pass headers to getServerSession
balazsorban44 Oct 1, 2021
3ba16de
refactor: rename `server` to `core`
balazsorban44 Oct 1, 2021
4096ad8
refactor: re-export `next-auth/next` in `next-auth`
balazsorban44 Oct 1, 2021
e1db283
fix: add `core` to npm package
balazsorban44 Oct 1, 2021
bfaf360
fix: re-export default method
balazsorban44 Oct 2, 2021
c6ddcb7
feat: return `body`+`header` instead of `json`,`text`
balazsorban44 Oct 2, 2021
10fbb5a
Merge branch 'beta' into feat/decouple
balazsorban44 Oct 2, 2021
0b8b5dd
feat: pass `NEXTAUTH_URL` as a variable to core
balazsorban44 Oct 3, 2021
8844846
refactor: simplify Next.js wrapper
balazsorban44 Oct 3, 2021
e2fcba1
Merge branch 'beta' into feat/decouple
balazsorban44 Oct 3, 2021
fc5ba18
feat: export `client/_utils`
balazsorban44 Oct 13, 2021
1b00587
Merge branch 'beta'
balazsorban44 Oct 13, 2021
f71bfe0
fix(ts): suppress TS errors
balazsorban44 Oct 13, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,13 @@ node_modules
/client
/css
/lib
/server
/core
/jwt
/react
/adapters.d.ts
/index.d.ts
/index.js
/next

# Development app
app/src/css
Expand Down
3 changes: 2 additions & 1 deletion app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
"copy:css": "cpx \"../css/**/*\" src/css --watch",
"watch:css": "cd .. && npm run watch:css",
"start": "next start",
"start:email": "npx fake-smtp-server"
"email": "npx fake-smtp-server",
"start:email": "email"
},
"license": "ISC",
"dependencies": {
Expand Down
7 changes: 2 additions & 5 deletions app/pages/_app.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
import { SessionProvider } from "next-auth/react"
import "./styles.css"

export default function App({
Component,
pageProps: { session, ...pageProps },
}) {
export default function App({ Component, pageProps }) {
return (
<SessionProvider session={session}>
<SessionProvider session={pageProps.session}>
<Component {...pageProps} />
</SessionProvider>
)
Expand Down
13 changes: 7 additions & 6 deletions app/pages/api/auth/[...nextauth].ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import NextAuth from "next-auth"
import NextAuth, { NextAuthOptions } from "next-auth"
import EmailProvider from "next-auth/providers/email"
import GitHubProvider from "next-auth/providers/github"
import Auth0Provider from "next-auth/providers/auth0"
Expand Down Expand Up @@ -37,8 +37,7 @@ import AzureB2C from "next-auth/providers/azure-ad-b2c"
// domain: process.env.FAUNA_DOMAIN,
// })
// const adapter = FaunaAdapter(client)

export default NextAuth({
export const authOptions: NextAuthOptions = {
// adapter,
providers: [
// E-mail
Expand All @@ -58,8 +57,8 @@ export default NextAuth({
credentials: {
password: { label: "Password", type: "password" },
},
async authorize(credentials, req) {
if (credentials.password === "password") {
async authorize(credentials) {
if (credentials.password === "pw") {
return {
name: "Fill Murray",
email: "bill@fillmurray.com",
Expand Down Expand Up @@ -179,4 +178,6 @@ export default NextAuth({
logo: "https://next-auth.js.org/img/logo/logo-sm.png",
brandColor: "#1786fb",
},
})
}

export default NextAuth(authOptions)
5 changes: 3 additions & 2 deletions app/pages/protected-ssr.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// This is an example of how to protect content using server rendering
import { getSession } from "next-auth/react"
import { getServerSession } from "next-auth/next"
import { authOptions } from "./api/auth/[...nextauth]"
import Layout from "../components/layout"
import AccessDenied from "../components/access-denied"

Expand All @@ -25,7 +26,7 @@ export default function Page({ content, session }) {
}

export async function getServerSideProps(context) {
const session = await getSession(context)
const session = await getServerSession(context, authOptions)
let content = null

if (session) {
Expand Down
2 changes: 1 addition & 1 deletion config/babel.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ module.exports = (api) => {
],
},
{
test: ["../src/server/pages/*.tsx"],
test: ["../src/core/pages/*.tsx"],
presets: ["preact"],
plugins: [
[
Expand Down
11 changes: 8 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,14 @@
".": "./index.js",
"./jwt": "./jwt/index.js",
"./react": "./react/index.js",
"./core": "./core/index.js",
"./next": "./next/index.js",
"./client/_utils": "./client/_utils.js",
"./providers/*": "./providers/*.js"
},
"scripts": {
"build": "npm run build:js && npm run build:css",
"clean": "rm -rf client css lib providers server jwt react index.d.ts index.js adapters.d.ts",
"clean": "rm -rf client css lib providers core jwt react next index.d.ts index.js adapters.d.ts",
"build:js": "npm run clean && npm run generate-providers && tsc && babel --config-file ./config/babel.config.js src --out-dir . --extensions \".tsx,.ts,.js,.jsx\"",
"build:css": "postcss --config config/postcss.config.js src/**/*.css --base src --dir . && node config/wrap-css.js",
"dev:setup": "npm i && npm run generate-providers && npm run build:css && cd app && npm i",
Expand All @@ -47,8 +50,10 @@
"css",
"jwt",
"react",
"next",
"client",
"providers",
"server",
"core",
"index.d.ts",
"index.js",
"adapters.d.ts"
Expand Down Expand Up @@ -137,7 +142,7 @@
"types",
".next",
"dist",
"/server",
"/core",
"/react.js"
],
"globals": {
Expand Down
File renamed without changes.
File renamed without changes.
210 changes: 210 additions & 0 deletions src/core/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
import logger from "../lib/logger"
import * as routes from "./routes"
import renderPage from "./pages"
import type { NextAuthOptions } from "./types"
import { init } from "./init"
import { Cookie } from "./lib/cookie"

import { NextAuthAction } from "../lib/types"

export interface IncomingRequest {
/** @default "http://localhost:3000" */
host?: string
method: string
cookies?: Record<string, any>
headers?: Record<string, any>
query?: Record<string, any>
body?: Record<string, any>
action: NextAuthAction
providerId?: string
error?: string
}

export interface NextAuthHeader {
key: string
value: string
}

export interface OutgoingResponse<
Body extends string | Record<string, any> | any[] = any
> {
status?: number
headers?: NextAuthHeader[]
body?: Body
redirect?: string
cookies?: Cookie[]
}

interface NextAuthHandlerParams {
req: IncomingRequest
options: NextAuthOptions
}

export async function NextAuthHandler<
Body extends string | Record<string, any> | any[]
>(params: NextAuthHandlerParams): Promise<OutgoingResponse<Body>> {
const { options: userOptions, req } = params
const { action, providerId, error } = req

const { options, cookies } = await init({
userOptions,
action,
providerId,
host: req.host,
callbackUrl: req.body?.callbackUrl ?? req.query?.callbackUrl,
csrfToken: req.body?.csrfToken,
cookies: req.cookies,
isPost: req.method === "POST",
})

const sessionToken =
req.cookies?.[options.cookies.sessionToken.name] ||
req.headers?.Authorization?.replace("Bearer ", "")

const codeVerifier = req.cookies?.[options.cookies.pkceCodeVerifier.name]

if (req.method === "GET") {
const render = renderPage({ options, query: req.query, cookies })
const { pages } = options
switch (action) {
case "providers":
return (await routes.providers(options.providers)) as any
case "session":
return (await routes.session({ options, sessionToken })) as any
case "csrf":
return {
headers: [{ key: "Content-Type", value: "application/json" }],
body: { csrfToken: options.csrfToken } as any,
cookies,
}
case "signin":
if (pages.signIn) {
let signinUrl = `${pages.signIn}${
pages.signIn.includes("?") ? "&" : "?"
}callbackUrl=${options.callbackUrl}`
if (error) signinUrl = `${signinUrl}&error=${error}`
return { redirect: signinUrl, cookies }
}

return render.signin()
case "signout":
if (pages.signOut) return { redirect: pages.signOut, cookies }

return render.signout()
case "callback":
if (options.provider) {
const callback = await routes.callback({
body: req.body,
query: req.query,
method: req.method,
headers: req.headers,
options,
sessionToken,
codeVerifier,
})
if (callback.cookies) cookies.push(...callback.cookies)
return { ...callback, cookies }
}
break
case "verify-request":
if (pages.verifyRequest) {
return { redirect: pages.verifyRequest, cookies }
}
return render.verifyRequest()
case "error":
if (pages.error) {
return {
redirect: `${pages.error}${
pages.error.includes("?") ? "&" : "?"
}error=${error}`,
cookies,
}
}

// These error messages are displayed in line on the sign in page
if (
[
"Signin",
"OAuthSignin",
"OAuthCallback",
"OAuthCreateAccount",
"EmailCreateAccount",
"Callback",
"OAuthAccountNotLinked",
"EmailSignin",
"CredentialsSignin",
"SessionRequired",
].includes(error as string)
) {
return { redirect: `${options.url}/signin?error=${error}`, cookies }
}

return render.error({ error })
default:
}
} else if (req.method === "POST") {
switch (action) {
case "signin":
// Verified CSRF Token required for all sign in routes
if (options.csrfTokenVerified && options.provider) {
const signin = await routes.signin({
query: req.query,
body: req.body,
options,
})
if (signin.cookies) cookies.push(...signin.cookies)
return { ...signin, cookies }
}

return { redirect: `${options.url}/signin?csrf=true`, cookies }
case "signout":
// Verified CSRF Token required for signout
if (options.csrfTokenVerified) {
const signout = await routes.signout({ options, sessionToken })
if (signout.cookies) cookies.push(...signout.cookies)
return { ...signout, cookies }
}
return { redirect: `${options.url}/signout?csrf=true`, cookies }
case "callback":
if (options.provider) {
// Verified CSRF Token required for credentials providers only
if (
options.provider.type === "credentials" &&
!options.csrfTokenVerified
) {
return { redirect: `${options.url}/signin?csrf=true`, cookies }
}

const callback = await routes.callback({
body: req.body,
query: req.query,
method: req.method,
headers: req.headers,
options,
sessionToken,
codeVerifier,
})
if (callback.cookies) cookies.push(...callback.cookies)
return { ...callback, cookies }
}
break
case "_log":
if (userOptions.logger) {
try {
const { code, level, ...metadata } = req.body ?? {}
logger[level](code, metadata)
} catch (error) {
// If logging itself failed...
logger.error("LOGGER_ERROR", error)
}
}
return {}
default:
}
}

return {
status: 400,
body: `Error: Action ${action} with HTTP ${req.method} is not supported by NextAuth.js` as any,
}
}
Loading