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

feat(web): expose Web API compatible version of next-auth #5536

Merged
merged 113 commits into from
Dec 13, 2022
Merged
Show file tree
Hide file tree
Changes from 49 commits
Commits
Show all changes
113 commits
Select commit Hold shift + click to select a range
e428b17
WIP use `Request` and `Response` for core
balazsorban44 Jun 26, 2022
1268301
Merge branch 'main' into chore/core-request-response
balazsorban44 Aug 11, 2022
8c21589
bump Next.js
balazsorban44 Aug 11, 2022
504f384
rename ts types
balazsorban44 Aug 11, 2022
c736948
refactor
balazsorban44 Aug 11, 2022
b274e98
simplify
balazsorban44 Aug 11, 2022
b933003
upgrade Next.js
balazsorban44 Aug 12, 2022
db6b1c9
implement body reader
balazsorban44 Aug 12, 2022
fe9c6a5
use `Request`/`Response` in `next-auth/next`
balazsorban44 Aug 12, 2022
bc9ddae
make linter happy
balazsorban44 Aug 12, 2022
a5cd972
revert
balazsorban44 Aug 12, 2022
2cf11bb
fix tests
balazsorban44 Aug 12, 2022
63cdea4
remove workaround for middleware return type
balazsorban44 Aug 12, 2022
eb77e23
Merge branch 'main' into chore/core-request-response
balazsorban44 Oct 5, 2022
1903e3c
return session in protected api route example
balazsorban44 Oct 5, 2022
b63e843
don't export internal handler
balazsorban44 Oct 5, 2022
07ab4f8
fall back host to localhost
balazsorban44 Oct 5, 2022
415d299
refactor `getBody`
balazsorban44 Oct 5, 2022
b41bdeb
refactor `next-auth/next`
balazsorban44 Oct 5, 2022
8f7288a
Merge branch 'main' into chore/core-request-response
balazsorban44 Oct 6, 2022
bd4d78b
chore: add `@edge-runtime/jest-environment`
balazsorban44 Oct 6, 2022
1a2e752
fix tests, using Node 18 as runtime
balazsorban44 Oct 6, 2022
195822c
fix test
balazsorban44 Oct 6, 2022
57a7d3f
remove patch
balazsorban44 Oct 7, 2022
3d14536
upgrade/add dependencies
balazsorban44 Oct 8, 2022
bc314ef
type and default import on one line
balazsorban44 Oct 8, 2022
0723747
don't import all adapters by default in dev
balazsorban44 Oct 8, 2022
b8ed735
simplify internal endpoint config
balazsorban44 Oct 8, 2022
9bea473
assert if both endpoint and issuer config is missing
balazsorban44 Oct 8, 2022
cbd7f18
allow internal redirect to be `URL`
balazsorban44 Oct 8, 2022
9466f65
mark clientId as always internally, fix comments
balazsorban44 Oct 9, 2022
dbf4293
add web-compatible authorization URL handling
balazsorban44 Oct 9, 2022
0c71f26
fix type
balazsorban44 Oct 9, 2022
5d6643b
Merge branch 'main' into chore/core-request-response
balazsorban44 Oct 9, 2022
0e635bd
Merge remote-tracking branch 'origin/chore/core-request-response' int…
balazsorban44 Oct 9, 2022
1bed118
fix neo4j build
balazsorban44 Oct 10, 2022
4a2c89d
Merge remote-tracking branch 'origin/chore/core-request-response' int…
balazsorban44 Oct 10, 2022
6843007
Merge branch 'main' into chore/core-request-response
balazsorban44 Oct 10, 2022
6d3e4af
Merge remote-tracking branch 'origin/chore/core-request-response' int…
balazsorban44 Oct 10, 2022
b2de56c
remove new-line
balazsorban44 Oct 10, 2022
8041a59
reduce file changes in the PR
balazsorban44 Oct 10, 2022
49cfcf1
Merge remote-tracking branch 'origin/chore/core-request-response' int…
balazsorban44 Oct 10, 2022
7b46d1a
simplify types
balazsorban44 Oct 10, 2022
79d6299
refactor `crypto` usage
balazsorban44 Oct 10, 2022
68d5387
add `next-auth/web`
balazsorban44 Oct 10, 2022
a063349
refactor
balazsorban44 Oct 10, 2022
697443d
send header instead of body to indicate redirect response
balazsorban44 Oct 10, 2022
119de7e
fix eslint
balazsorban44 Oct 10, 2022
7a47d68
fix tests
balazsorban44 Oct 10, 2022
9aaa058
Merge branch 'main' into feat/oauth4webapi-balazs
balazsorban44 Dec 3, 2022
b0b9939
Merge branch 'main' into feat/oauth4webapi-balazs
balazsorban44 Dec 3, 2022
6b46f37
chore: upgrade dep
balazsorban44 Dec 3, 2022
b0fb174
Merge branch 'main' into feat/oauth4webapi-balazs
balazsorban44 Dec 3, 2022
1ee62b6
Merge branch 'main' into feat/oauth4webapi-balazs
balazsorban44 Dec 3, 2022
5a22790
fix import
balazsorban44 Dec 3, 2022
9f3e33e
refactor: more renames
balazsorban44 Dec 5, 2022
40f11ed
Merge branch 'main' into feat/oauth4webapi-balazs
balazsorban44 Dec 5, 2022
7d8f468
Merge branch 'main' into feat/oauth4webapi-balazs
balazsorban44 Dec 5, 2022
9d079a5
wip core
balazsorban44 Dec 6, 2022
166d4a8
support OIDC
balazsorban44 Dec 6, 2022
cc098bd
remove `openid-client`
balazsorban44 Dec 6, 2022
808d9a2
temprarily remove duplicate logos
balazsorban44 Dec 7, 2022
99abfbe
revert
balazsorban44 Dec 7, 2022
8299b97
move redirect logic to core
balazsorban44 Dec 7, 2022
2730249
wip fix css
balazsorban44 Dec 7, 2022
598b3ef
revert Logo component
balazsorban44 Dec 7, 2022
c3932f1
Merge branch 'main' into feat/oauth4webapi-balazs
balazsorban44 Dec 7, 2022
df61660
output ESM
balazsorban44 Dec 8, 2022
104f944
fix logout
balazsorban44 Dec 8, 2022
5c96c8f
deprecate OAuth 1, simplify internals, improve defaults
balazsorban44 Dec 8, 2022
5945d9b
refactor providers, test facebook
balazsorban44 Dec 8, 2022
5633959
fix providers
balazsorban44 Dec 8, 2022
860c8b5
target es2020
balazsorban44 Dec 8, 2022
187d38b
fix CSS
balazsorban44 Dec 8, 2022
7444ab3
Merge branch 'main' into feat/oauth4webapi-balazs
balazsorban44 Dec 8, 2022
7f3a6ca
Merge branch 'main' into feat/oauth4webapi-balazs
balazsorban44 Dec 11, 2022
0b8d3fd
update lock file
balazsorban44 Dec 11, 2022
67c525b
Merge branch 'main' into feat/oauth4webapi-balazs
balazsorban44 Dec 12, 2022
fa864e1
make logos optional
balazsorban44 Dec 12, 2022
92cfb91
sync with `next-auth`
balazsorban44 Dec 12, 2022
0469fc6
clean up `next-auth/edge`
balazsorban44 Dec 12, 2022
73026a4
sync
balazsorban44 Dec 12, 2022
5d15ac5
remove uuid
balazsorban44 Dec 13, 2022
e651df5
make secret required in dev
balazsorban44 Dec 13, 2022
9d0c138
remove todo comments
balazsorban44 Dec 13, 2022
e9f0828
pass through OAuth client options
balazsorban44 Dec 13, 2022
f231016
generate declaration map
balazsorban44 Dec 13, 2022
b2e66cc
default env secret to AUTH_SECRET
balazsorban44 Dec 13, 2022
f2d193b
temporary Headers fix
balazsorban44 Dec 13, 2022
90df63e
move pages to lib
balazsorban44 Dec 13, 2022
c229183
move errors to lib
balazsorban44 Dec 13, 2022
ffee34d
move pages/index to lib
balazsorban44 Dec 13, 2022
d96947c
move routes to lib
balazsorban44 Dec 13, 2022
1d9e591
move init to lib
balazsorban44 Dec 13, 2022
cb5a2ad
move styles to lib
balazsorban44 Dec 13, 2022
3e3f9a9
move types to lib
balazsorban44 Dec 13, 2022
0939b13
move utils to lib
balazsorban44 Dec 13, 2022
9fcf854
fix imports
balazsorban44 Dec 13, 2022
cea5969
update ignore/clean patterns
balazsorban44 Dec 13, 2022
24f329a
fix imports
balazsorban44 Dec 13, 2022
249ebf4
update styles ts
balazsorban44 Dec 13, 2022
2c209d7
update gitignore
balazsorban44 Dec 13, 2022
4582348
update exports field
balazsorban44 Dec 13, 2022
fef8457
revert `next-auth`
balazsorban44 Dec 13, 2022
ced1ca4
remove extra tsconfig files
balazsorban44 Dec 13, 2022
369a27b
remove `private` from package.json
balazsorban44 Dec 13, 2022
df061b9
remove unused file, expose type
balazsorban44 Dec 13, 2022
18b8247
move gitignore, reduce exposed types
balazsorban44 Dec 13, 2022
6c098d7
add back tsconfig files
balazsorban44 Dec 13, 2022
1d1dfb7
remove leftover
balazsorban44 Dec 13, 2022
c2e40f0
Merge branch 'main' into feat/oauth4webapi-balazs
balazsorban44 Dec 13, 2022
34e4471
revert gitignore
balazsorban44 Dec 13, 2022
ef3dbfb
remove test script
balazsorban44 Dec 13, 2022
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ packages/next-auth/utils
packages/next-auth/core
packages/next-auth/jwt
packages/next-auth/react
packages/next-auth/web
packages/next-auth/adapters.d.ts
packages/next-auth/adapters.js
packages/next-auth/index.d.ts
Expand Down
3 changes: 1 addition & 2 deletions apps/dev/pages/api/auth/[...nextauth].ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import NextAuth from "next-auth"
import type { NextAuthOptions } from "next-auth"
import NextAuth, { type NextAuthOptions } from "next-auth"

// Providers
import Apple from "next-auth/providers/apple"
Expand Down
11 changes: 9 additions & 2 deletions packages/next-auth/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,15 @@
"./react": "./react/index.js",
"./core": "./core/index.js",
"./next": "./next/index.js",
"./web": "./web/index.js",
"./middleware": "./middleware.js",
"./client/_utils": "./client/_utils.js",
"./providers/*": "./providers/*.js"
},
"scripts": {
"build": "pnpm clean && pnpm build:js && pnpm build:css",
"build:js": "pnpm clean && pnpm generate-providers && pnpm tsc --project tsconfig.json && babel --config-file ./config/babel.config.js src --out-dir . --extensions \".tsx,.ts,.js,.jsx\"",
"clean": "rm -rf coverage client css utils providers core jwt react next index.d.ts index.js adapters.d.ts middleware.d.ts middleware.js",
"clean": "rm -rf coverage client css utils providers core jwt react next web index.d.ts index.js adapters.d.ts middleware.d.ts middleware.js",
"build:css": "postcss --config config/postcss.config.js src/**/*.css --base src --dir . && node config/wrap-css.js",
"dev": "pnpm clean && pnpm generate-providers && concurrently \"pnpm watch:css\" \"pnpm watch:ts\"",
"watch:ts": "pnpm tsc --project tsconfig.dev.json",
Expand All @@ -63,7 +64,8 @@
"adapters.d.ts",
"middleware.d.ts",
"middleware.js",
"utils"
"utils",
"web"
],
"license": "ISC",
"dependencies": {
Expand All @@ -78,12 +80,16 @@
"uuid": "^8.3.2"
},
"peerDependencies": {
"@panva/oauth4webapi": "1.2.0",
balazsorban44 marked this conversation as resolved.
Show resolved Hide resolved
"next": "^12.2.5",
"nodemailer": "^6.6.5",
"react": "^17.0.2 || ^18",
"react-dom": "^17.0.2 || ^18"
},
"peerDependenciesMeta": {
"@panva/oauth4webapi": {
"optional": true
},
"nodemailer": {
"optional": true
}
Expand All @@ -98,6 +104,7 @@
"@babel/preset-typescript": "^7.17.12",
"@edge-runtime/jest-environment": "1.1.0-beta.35",
"@next-auth/tsconfig": "workspace:*",
"@panva/oauth4webapi": "1.2.0",
"@swc/core": "^1.2.198",
"@swc/jest": "^0.2.21",
"@testing-library/dom": "^8.13.0",
Expand Down
6 changes: 3 additions & 3 deletions packages/next-auth/src/client/_utils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { IncomingMessage } from "http"
import type { LoggerInstance, Session } from ".."

export interface NextAuthClientConfig {
export interface AuthClientConfig {
baseUrl: string
basePath: string
baseUrlServer: string
Expand Down Expand Up @@ -31,7 +31,7 @@ export interface CtxOrReq {
*/
export async function fetchData<T = any>(
path: string,
__NEXTAUTH: NextAuthClientConfig,
__NEXTAUTH: AuthClientConfig,
logger: LoggerInstance,
{ ctx, req = ctx?.req }: CtxOrReq = {}
): Promise<T | null> {
Expand All @@ -50,7 +50,7 @@ export async function fetchData<T = any>(
}
}

export function apiBaseUrl(__NEXTAUTH: NextAuthClientConfig) {
export function apiBaseUrl(__NEXTAUTH: AuthClientConfig) {
if (typeof window === "undefined") {
// Return absolute path when called server side
return `${__NEXTAUTH.baseUrlServer}${__NEXTAUTH.basePathServer}`
Expand Down
7 changes: 6 additions & 1 deletion packages/next-auth/src/core/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,15 @@ export class UnsupportedStrategy extends UnknownError {
}

export class InvalidCallbackUrl extends UnknownError {
name = "InvalidCallbackUrl"
name = "InvalidCallbackUrlError"
code = "INVALID_CALLBACK_URL_ERROR"
}

export class InvalidEndpoints extends UnknownError {
name = "InvalidEndpoints"
code = "INVALID_ENDPOINTS_ERROR"
}

type Method = (...args: any[]) => Promise<any>

export function upperSnake(s: string) {
Expand Down
88 changes: 34 additions & 54 deletions packages/next-auth/src/core/index.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import logger, { setLogger } from "../utils/logger"
import { detectHost } from "../utils/detect-host"
import { toInternalRequest, toResponse } from "./lib/web"
import * as routes from "./routes"
import renderPage from "./pages"
import { init } from "./init"
import { assertConfig } from "./lib/assert"
import { SessionStore } from "./lib/cookie"

import type { NextAuthAction, NextAuthOptions } from "./types"
import type { AuthAction, AuthOptions } from "./types"
import type { Cookie } from "./lib/cookie"
import type { ErrorType } from "./pages/error"
import { parse as parseCookie } from "cookie"

export interface RequestInternal {
/** @default "http://localhost:3000" */
Expand All @@ -19,73 +18,35 @@ export interface RequestInternal {
headers?: Record<string, any>
query?: Record<string, any>
body?: Record<string, any>
action: NextAuthAction
action: AuthAction
providerId?: string
error?: string
}

export interface NextAuthHeader {
interface AuthHeader {
key: string
value: string
}

export interface OutgoingResponse<
export interface ResponseInternal<
Body extends string | Record<string, any> | any[] = any
> {
status?: number
headers?: NextAuthHeader[]
headers?: AuthHeader[]
body?: Body
redirect?: string
redirect?: URL | string // TODO: refactor to only allow URL
cookies?: Cookie[]
}

export interface NextAuthHandlerParams {
req: Request | RequestInternal
options: NextAuthOptions
}

async function getBody(req: Request): Promise<Record<string, any> | undefined> {
try {
return await req.json()
} catch {}
}

// TODO:
async function toInternalRequest(
req: RequestInternal | Request
): Promise<RequestInternal> {
if (req instanceof Request) {
const url = new URL(req.url)
// TODO: handle custom paths?
const nextauth = url.pathname.split("/").slice(3)
const headers = Object.fromEntries(req.headers.entries())
const query: Record<string, any> = Object.fromEntries(
url.searchParams.entries()
)
query.nextauth = nextauth

return {
action: nextauth[0] as NextAuthAction,
method: req.method,
headers,
body: await getBody(req),
cookies: parseCookie(req.headers.get("cookie") ?? ""),
providerId: nextauth[1],
error: url.searchParams.get("error") ?? nextauth[1],
host: detectHost(headers["x-forwarded-host"] ?? headers.host),
query,
}
}
return req
}

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

const req = await toInternalRequest(incomingRequest)

>(params: {
req: RequestInternal
options: AuthOptions
/** REVIEW: Is this the best way to skip parsing the body in Node.js? */
parsedBody?: any
}): Promise<ResponseInternal<Body>> {
const { options: userOptions, req } = params
setLogger(userOptions.logger, userOptions.debug)

const assertionResult = assertConfig({ options: userOptions, req })
Expand All @@ -105,6 +66,10 @@ export async function NextAuthHandler<
body: { message } as any,
}
}

// We can throw in development to surface the issue in the browser too.
if (process.env.NODE_ENV === "development") throw assertionResult

const { pages, theme } = userOptions

const authOnErrorPage =
Expand Down Expand Up @@ -156,6 +121,7 @@ export async function NextAuthHandler<
case "session": {
const session = await routes.session({ options, sessionStore })
if (session.cookies) cookies.push(...session.cookies)
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
return { ...session, cookies } as any
}
case "csrf":
Expand Down Expand Up @@ -296,3 +262,17 @@ export async function NextAuthHandler<
body: `Error: This action with HTTP ${method} is not supported by NextAuth.js` as any,
}
}

/**
* The core functionality of `next-auth`.
* It receives a standard [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request)
* and returns a standard [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response).
*/
export async function AuthHandler(
request: Request,
options: AuthOptions
): Promise<Response> {
const req = await toInternalRequest(request)
const internalResponse = await AuthHandlerInternal({ req, options })
return toResponse(internalResponse)
}
26 changes: 16 additions & 10 deletions packages/next-auth/src/core/init.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { randomBytes, randomUUID } from "crypto"
import { NextAuthOptions } from ".."
import { createHash, randomUUID } from "./lib/web"
import { AuthOptions } from ".."
import logger from "../utils/logger"
import parseUrl from "../utils/parse-url"
import { adapterErrorHandler, eventsErrorHandler } from "./errors"
import parseProviders from "./lib/providers"
import { createSecret } from "./lib/utils"
import * as cookie from "./lib/cookie"
import * as jwt from "../jwt"
import { defaultCallbacks } from "./lib/default-callbacks"
Expand All @@ -16,7 +15,7 @@ import type { InternalOptions } from "./types"

interface InitParams {
host?: string
userOptions: NextAuthOptions
userOptions: AuthOptions
providerId?: string
action: InternalOptions["action"]
/** Callback URL value extracted from the incoming request. */
Expand Down Expand Up @@ -44,7 +43,17 @@ export async function init({
}> {
const url = parseUrl(host)

const secret = createSecret({ userOptions, url })
/**
* Secret used to salt cookies and tokens (e.g. for CSRF protection).
* If no secret option is specified then it creates one on the fly
* based on options passed here. If options contains unique data, such as
* OAuth provider secrets and database credentials it should be sufficent.
* If no secret provided in production, we throw an error.
*/
const secret =
userOptions.secret ??
// TODO: Remove this, always ask the user for a secret, even in dev! (Fix assert.ts too)
(await createHash(JSON.stringify({ ...url, ...userOptions })))

const { providers, provider } = parseProviders({
providers: userOptions.providers,
Expand Down Expand Up @@ -88,10 +97,7 @@ export async function init({
strategy: userOptions.adapter ? "database" : "jwt",
maxAge,
updateAge: 24 * 60 * 60,
generateSessionToken: () => {
// Use `randomUUID` if available. (Node 15.6+)
return randomUUID?.() ?? randomBytes(32).toString("hex")
},
generateSessionToken: randomUUID,
...userOptions.session,
},
// JWT options
Expand Down Expand Up @@ -119,7 +125,7 @@ export async function init({
csrfToken,
cookie: csrfCookie,
csrfTokenVerified,
} = createCSRFToken({
} = await createCSRFToken({
options,
cookieValue: reqCookies?.[options.cookies.csrfToken.name],
isPost,
Expand Down
31 changes: 25 additions & 6 deletions packages/next-auth/src/core/lib/assert.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,30 @@
import {
MissingAdapter,
MissingAdapterMethods,
MissingAPIRoute,
MissingAuthorize,
MissingSecret,
UnsupportedStrategy,
InvalidCallbackUrl,
MissingAdapterMethods,
InvalidEndpoints,
UnsupportedStrategy,
} from "../errors"
import parseUrl from "../../utils/parse-url"
import { defaultCookies } from "./cookie"

import type { RequestInternal } from ".."
import type { WarningCode } from "../../utils/logger"
import type { NextAuthOptions } from "../types"
import type { AuthOptions } from "../types"

type ConfigError =
| MissingAdapter
| MissingAdapterMethods
| MissingAPIRoute
| MissingAuthorize
| MissingSecret
| InvalidCallbackUrl
| UnsupportedStrategy
| InvalidEndpoints
| UnsupportedStrategy
| MissingAuthorize
| MissingAdapter

let warned = false

Expand All @@ -40,7 +45,7 @@ function isValidHttpUrl(url: string, baseUrl: string) {
* REVIEW: Make some of these and corresponding docs less Next.js specific?
*/
export function assertConfig(params: {
options: NextAuthOptions
options: AuthOptions
req: RequestInternal
}): ConfigError | WarningCode[] {
const { options, req } = params
Expand Down Expand Up @@ -94,6 +99,20 @@ export function assertConfig(params: {
let hasTwitterOAuth2

for (const provider of options.providers) {
if (provider.type === "oauth" && !provider.issuer) {
Copy link

@hmnd hmnd Oct 15, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (provider.type === "oauth" && !provider.issuer) {
if (provider.type === "oauth" && !(provider.wellKnown || provider.issuer)) {

wellKnown would provide the endpoint configs. This check currently breaks providers such as Azure AD who rely on wellKnown.

const { authorization: a, token: t, userinfo: u } = provider
let key
if (typeof a !== "string" && !a?.url) key = "authorization"
else if (typeof t !== "string" && !t?.url) key = "token"
else if (typeof u !== "string" && !u?.url) key = "userinfo"

if (key) {
return new InvalidEndpoints(
`Provider "${provider.id}" is missing both \`issuer\` and \`${key}\` endpoint config. At least one of them is required.`
)
}
}

if (provider.type === "credentials") hasCredentials = true
else if (provider.type === "email") hasEmail = true
else if (provider.id === "twitter" && provider.version === "2.0")
Expand Down
Loading