From e428b17b990c90a410f624c76fe2c7b101e99480 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Sun, 26 Jun 2022 21:24:49 +0200 Subject: [PATCH 01/93] WIP use `Request` and `Response` for core --- packages/next-auth/src/core/index.ts | 149 ++++++++++------------ packages/next-auth/src/core/lib/assert.ts | 11 +- packages/next-auth/src/core/lib/spec.ts | 89 +++++++++++++ packages/next-auth/src/next/index.ts | 65 ++++------ packages/next-auth/src/next/utils.ts | 9 +- 5 files changed, 190 insertions(+), 133 deletions(-) create mode 100644 packages/next-auth/src/core/lib/spec.ts diff --git a/packages/next-auth/src/core/index.ts b/packages/next-auth/src/core/index.ts index 396d4b3520..107f2647fe 100644 --- a/packages/next-auth/src/core/index.ts +++ b/packages/next-auth/src/core/index.ts @@ -1,10 +1,10 @@ import logger, { setLogger } from "../utils/logger" -import { detectHost } from "../utils/detect-host" import * as routes from "./routes" import renderPage from "./pages" import { init } from "./init" -import { assertConfig } from "./lib/assert" +import { assertConfig as assert } from "./lib/assert" import { SessionStore } from "./lib/cookie" +import { fromRequest, toResponse } from "./lib/spec" import type { NextAuthAction, NextAuthOptions } from "./types" import type { Cookie } from "./lib/cookie" @@ -39,92 +39,48 @@ export interface OutgoingResponse< } export interface NextAuthHandlerParams { - req: Request | RequestInternal + req: Request options: NextAuthOptions } -async function getBody(req: Request): Promise | undefined> { - try { - return await req.json() - } catch {} -} - -// TODO: -async function toInternalRequest( - req: RequestInternal | Request -): Promise { - 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 = Object.fromEntries( - url.searchParams.entries() - ) - query.nextauth = nextauth - - return { - action: nextauth[0] as NextAuthAction, - method: req.method, - headers, - body: await getBody(req), - cookies: {}, - 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 NextAuthHandlerInternal< Body extends string | Record | any[] ->(params: NextAuthHandlerParams): Promise> { - const { options: userOptions, req: incomingRequest } = params - - const req = await toInternalRequest(incomingRequest) - - setLogger(userOptions.logger, userOptions.debug) - - const assertionResult = assertConfig({ options: userOptions, req }) - - if (typeof assertionResult === "string") { - logger.warn(assertionResult) - } else if (assertionResult instanceof Error) { - // Bail out early if there's an error in the user config - const { pages, theme } = userOptions - logger.error(assertionResult.code, assertionResult) - if (pages?.error) { - return { - redirect: `${pages.error}?error=Configuration`, - } - } - const render = renderPage({ theme }) - return render.error({ error: "configuration" }) - } +>( + request: Request, + userOptions: NextAuthOptions +): Promise> { + const internalRequest = await fromRequest(request) - const { action, providerId, error, method = "GET" } = req + const { + action, + providerId, + error, + method = "GET", + body, + query, + host, + headers, + } = internalRequest 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, + host: host, + callbackUrl: body?.callbackUrl ?? query?.callbackUrl, + csrfToken: body?.csrfToken, + cookies: internalRequest.cookies, isPost: method === "POST", }) const sessionStore = new SessionStore( options.cookies.sessionToken, - req, + internalRequest, options.logger ) if (method === "GET") { - const render = renderPage({ ...options, query: req.query, cookies }) + const render = renderPage({ ...options, query, cookies }) const { pages } = options switch (action) { case "providers": @@ -158,10 +114,10 @@ export async function NextAuthHandler< case "callback": if (options.provider) { const callback = await routes.callback({ - body: req.body, - query: req.query, - headers: req.headers, - cookies: req.cookies, + body, + query, + headers, + cookies: internalRequest.cookies, method, options, sessionStore, @@ -211,11 +167,7 @@ export async function NextAuthHandler< 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, - }) + const signin = await routes.signin({ query, body, options }) if (signin.cookies) cookies.push(...signin.cookies) return { ...signin, cookies } } @@ -240,10 +192,10 @@ export async function NextAuthHandler< } const callback = await routes.callback({ - body: req.body, - query: req.query, - headers: req.headers, - cookies: req.cookies, + body, + query, + headers, + cookies: internalRequest.cookies, method, options, sessionStore, @@ -255,7 +207,7 @@ export async function NextAuthHandler< case "_log": if (userOptions.logger) { try { - const { code, level, ...metadata } = req.body ?? {} + const { code, level, ...metadata } = body ?? {} logger[level](code, metadata) } catch (error) { // If logging itself failed... @@ -272,3 +224,34 @@ 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 NextAuthHandler( + req: Request, + options: NextAuthHandlerParams["options"] +): Promise { + setLogger(options.logger, options.debug) + const assertionResult = assert({ req, options }) + + if (typeof assertionResult === "string") { + logger.warn(assertionResult) + } else if (assertionResult instanceof Error) { + // Bail out early if there's an error in the user config + const { pages, theme } = options + logger.error(assertionResult.code, assertionResult) + if (pages?.error) { + return new Response(null, { + status: 302, + headers: { Location: `${pages.error}?error=Configuration` }, + }) + } + const render = renderPage({ theme }) + return toResponse(render.error({ error: "configuration" })) + } + + return toResponse(await NextAuthHandlerInternal(req, options)) +} diff --git a/packages/next-auth/src/core/lib/assert.ts b/packages/next-auth/src/core/lib/assert.ts index 49f058c43d..8a45878a10 100644 --- a/packages/next-auth/src/core/lib/assert.ts +++ b/packages/next-auth/src/core/lib/assert.ts @@ -9,7 +9,7 @@ import { import parseUrl from "../../utils/parse-url" import { defaultCookies } from "./cookie" -import type { NextAuthHandlerParams, RequestInternal } from ".." +import type { NextAuthOptions } from "../types" import type { WarningCode } from "../../utils/logger" type ConfigError = @@ -37,11 +37,10 @@ function isValidHttpUrl(url: string, baseUrl: string) { * * REVIEW: Make some of these and corresponding docs less Next.js specific? */ -export function assertConfig( - params: NextAuthHandlerParams & { - req: RequestInternal - } -): ConfigError | WarningCode | undefined { +export function assertConfig(params: { + req: Request + options: NextAuthOptions +}): ConfigError | WarningCode | undefined { const { options, req } = params // req.query isn't defined when asserting `unstable_getServerSession` for example diff --git a/packages/next-auth/src/core/lib/spec.ts b/packages/next-auth/src/core/lib/spec.ts new file mode 100644 index 0000000000..b6962b914e --- /dev/null +++ b/packages/next-auth/src/core/lib/spec.ts @@ -0,0 +1,89 @@ +import { serialize, parse as parseCookie } from "cookie" +import { detectHost } from "../../utils/detect-host" +import type { OutgoingResponse, RequestInternal } from ".." +import type { NextAuthAction } from "../types" + +async function readBody( + body: ReadableStream | null +): Promise | undefined> { + try { + const reader = body?.getReader() + + if (!reader) return undefined + + const chunks: Uint8Array[] = [] + while (true) { + const { value, done } = await reader.read() + if (done) break + chunks.push(value) + } + + return JSON.parse(Buffer.concat(chunks).toString()) + } catch {} +} + +// TODO: +export async function fromRequest(req: Request): Promise { + // TODO: handle custom url + const url = new URL(req.url, "http://localhost:3000") + const nextauth = url.pathname.split("/").slice(3) + const headers = Object.fromEntries(req.headers.entries()) + const query: Record = Object.fromEntries( + url.searchParams.entries() + ) + query.nextauth = nextauth + + const cookieHeader = req.headers.get("cookie") + const cookies = + parseCookie( + Array.isArray(cookieHeader) ? cookieHeader.join(";") : cookieHeader + ) ?? {} + + return { + action: nextauth[0] as NextAuthAction, + method: req.method, + headers, + body: await readBody(req.body), + cookies: cookies, + providerId: nextauth[1], + error: url.searchParams.get("error") ?? undefined, + host: detectHost(headers["x-forwarded-host"] ?? headers.host), + query, + } +} + +export function toResponse(_res: OutgoingResponse): Response { + const headers = new Headers( + _res.headers?.reduce((acc, { key, value }) => { + acc[key] = value + return acc + }, {}) + ) + + _res.cookies?.forEach((cookie) => { + const { name, value, options } = cookie + const cookieHeader = serialize(name, value, options) + if (headers.has("Set-Cookie")) { + headers.append("Set-Cookie", cookieHeader) + } else { + headers.set("Set-Cookie", cookieHeader) + } + }) + + const body = + _res.headers?.find(({ key }) => key === "Content-Type")?.value === + "application/json" + ? JSON.stringify(_res.body) + : _res.body + + const response = new Response(body, { + headers, + status: _res.redirect ? 301 : _res.status ?? 200, + }) + + if (_res.redirect) { + response.headers.set("Location", _res.redirect) + } + + return response +} diff --git a/packages/next-auth/src/next/index.ts b/packages/next-auth/src/next/index.ts index 2be4071317..e538ef72e1 100644 --- a/packages/next-auth/src/next/index.ts +++ b/packages/next-auth/src/next/index.ts @@ -8,56 +8,43 @@ import type { NextApiResponse, } from "next" import type { NextAuthOptions, Session } from ".." -import type { - NextAuthAction, - NextAuthRequest, - NextAuthResponse, -} from "../core/types" +import type { NextAuthRequest, NextAuthResponse } from "../core/types" async function NextAuthNextHandler( req: NextApiRequest, res: NextApiResponse, options: NextAuthOptions ) { - const { nextauth, ...query } = req.query - options.secret = options.secret ?? options.jwt?.secret ?? process.env.NEXTAUTH_SECRET - const handler = await NextAuthHandler({ - req: { - host: detectHost(req.headers["x-forwarded-host"]), - body: req.body, - query, - cookies: req.cookies, - headers: req.headers, - method: req.method, - action: nextauth?.[0] as NextAuthAction, - providerId: nextauth?.[1], - error: (req.query.error as string | undefined) ?? nextauth?.[1], - }, - options, + const shouldUseBody = + req.method !== "GET" && req.headers["content-type"] === "application/json" + + const _req = new Request(req.url!, { + headers: new Headers(req.headers as any), + method: req.method, + ...((shouldUseBody + ? { body: JSON.stringify(Object.fromEntries(Object.entries(req.body))) } + : {}) as any), }) - res.status(handler.status ?? 200) + const _res = await NextAuthHandler(_req, options) - handler.cookies?.forEach((cookie) => setCookie(res, cookie)) + res.status(_res.status ?? 200) - handler.headers?.forEach((h) => res.setHeader(h.key, h.value)) + for (const [key, value] of _res.headers.entries()) { + res.setHeader(key, value) + } - if (handler.redirect) { - // If the request expects a return URL, send it as JSON - // instead of doing an actual redirect. - if (req.body?.json !== "true") { - // Could chain. .end() when lowest target is Node 14 - // https://github.com/nodejs/node/issues/33148 - res.status(302).setHeader("Location", handler.redirect) - return res.end() - } - return res.json({ url: handler.redirect }) + // If the request expects a return URL, send it as JSON + // instead of doing an actual redirect. + const redirect = _res.headers.get("Location") + if (req.body?.json === "true" && redirect) { + return res.json({ url: redirect }) } - return res.send(handler.body) + return res.send(_res.body) } function NextAuth(options: NextAuthOptions): any @@ -85,7 +72,11 @@ export default NextAuth export async function unstable_getServerSession( ...args: - | [GetServerSidePropsContext['req'], GetServerSidePropsContext['res'], NextAuthOptions] + | [ + GetServerSidePropsContext["req"], + GetServerSidePropsContext["res"], + NextAuthOptions + ] | [NextApiRequest, NextApiResponse, NextAuthOptions] ): Promise { console.warn( @@ -93,9 +84,9 @@ export async function unstable_getServerSession( "\n`unstable_getServerSession` is experimental and may be removed or changed in the future, as the name suggested.", `\nhttps://next-auth.js.org/configuration/nextjs#unstable_getServerSession}`, `\nhttps://next-auth.js.org/warnings#EXPERIMENTAL_API` - ) + ) - const [req, res, options] = args; + const [req, res, options] = args const session = await NextAuthHandler({ options, req: { diff --git a/packages/next-auth/src/next/utils.ts b/packages/next-auth/src/next/utils.ts index 6e5769a10f..9075bcaaf6 100644 --- a/packages/next-auth/src/next/utils.ts +++ b/packages/next-auth/src/next/utils.ts @@ -1,15 +1,10 @@ -import { serialize } from "cookie" -import { Cookie } from "../core/lib/cookie" - -export function setCookie(res, cookie: Cookie) { +export function setCookie(res, value: string) { // Preserve any existing cookies that have already been set in the same session let setCookieHeader = res.getHeader("Set-Cookie") ?? [] // If not an array (i.e. a string with a single cookie) convert it into an array if (!Array.isArray(setCookieHeader)) { setCookieHeader = [setCookieHeader] } - const { name, value, options } = cookie - const cookieHeader = serialize(name, value, options) - setCookieHeader.push(cookieHeader) + setCookieHeader.push(value) res.setHeader("Set-Cookie", setCookieHeader) } From 8c21589518ec12e2761c5c50ee5717a3b052d40b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Thu, 11 Aug 2022 12:09:59 +0200 Subject: [PATCH 02/93] bump Next.js --- apps/dev/package.json | 2 +- packages/next-auth/package.json | 2 +- pnpm-lock.yaml | 203 +++++++++----------------------- 3 files changed, 58 insertions(+), 149 deletions(-) diff --git a/apps/dev/package.json b/apps/dev/package.json index dfa7250e67..e86237d931 100644 --- a/apps/dev/package.json +++ b/apps/dev/package.json @@ -21,7 +21,7 @@ "@next-auth/typeorm-legacy-adapter": "workspace:*", "@prisma/client": "^3", "faunadb": "^4", - "next": "12.2.0", + "next": "12.2.5-canary.4", "nodemailer": "^6", "react": "^18", "react-dom": "^18" diff --git a/packages/next-auth/package.json b/packages/next-auth/package.json index bc909bdd5a..1b693c8a3d 100644 --- a/packages/next-auth/package.json +++ b/packages/next-auth/package.json @@ -116,7 +116,7 @@ "jest-environment-jsdom": "^28.1.1", "jest-watch-typeahead": "^1.1.0", "msw": "^0.42.3", - "next": "12.2.0", + "next": "12.2.5-canary.4", "postcss": "^8.4.14", "postcss-cli": "^9.1.0", "postcss-nested": "^5.0.6", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0f3318df36..49b7c290ab 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -52,7 +52,7 @@ importers: cpx: ^1.5.0 fake-smtp-server: ^0.8.0 faunadb: ^4 - next: 12.2.0 + next: 12.2.5-canary.4 nodemailer: ^6 pg: ^8.7.3 prisma: ^3 @@ -66,7 +66,7 @@ importers: '@next-auth/typeorm-legacy-adapter': link:../../packages/adapter-typeorm-legacy '@prisma/client': 3.15.2_prisma@3.15.2 faunadb: 4.6.0 - next: 12.2.0_biqbaboplfbrettd7655fr4n2y + next: 12.2.5-canary.4_biqbaboplfbrettd7655fr4n2y nodemailer: 6.7.5 react: 18.2.0 react-dom: 18.2.0_react@18.2.0 @@ -428,7 +428,7 @@ importers: jest-watch-typeahead: ^1.1.0 jose: ^4.3.7 msw: ^0.42.3 - next: 12.2.0 + next: 12.2.5-canary.4 oauth: ^0.9.15 openid-client: ^5.1.0 postcss: ^8.4.14 @@ -480,7 +480,7 @@ importers: jest-environment-jsdom: 28.1.1 jest-watch-typeahead: 1.1.0_jest@28.1.1 msw: 0.42.3 - next: 12.2.0_4cc5zw5azim2bix77d63le72su + next: 12.2.5-canary.4_biqbaboplfbrettd7655fr4n2y postcss: 8.4.14 postcss-cli: 9.1.0_postcss@8.4.14 postcss-nested: 5.0.6_postcss@8.4.14 @@ -5701,107 +5701,107 @@ packages: - supports-color dev: true - /@next/env/12.2.0: - resolution: {integrity: sha512-/FCkDpL/8SodJEXvx/DYNlOD5ijTtkozf4PPulYPtkPOJaMPpBSOkzmsta4fnrnbdH6eZjbwbiXFdr6gSQCV4w==} + /@next/env/12.2.5-canary.4: + resolution: {integrity: sha512-zg1HLMDwV+tSArHTytwtHDk1WNkC/jkLj1c6QsJ09J4pcA716YNsh5RAe+4mniKk/S5PQoMwM+hDT2LIiWas2g==} - /@next/swc-android-arm-eabi/12.2.0: - resolution: {integrity: sha512-hbneH8DNRB2x0Nf5fPCYoL8a0osvdTCe4pvOc9Rv5CpDsoOlf8BWBs2OWpeP0U2BktGvIsuUhmISmdYYGyrvTw==} + /@next/swc-android-arm-eabi/12.2.5-canary.4: + resolution: {integrity: sha512-Akd9rpyrsmmv6+5LBvV6ZGMr4hj+QyYoWUn2mnXYnNRi9XKeu2yX9lQWGCYVGRPVxrZ7/Fu2Egqp4xBId/EsTw==} engines: {node: '>= 10'} cpu: [arm] os: [android] requiresBuild: true optional: true - /@next/swc-android-arm64/12.2.0: - resolution: {integrity: sha512-1eEk91JHjczcJomxJ8X0XaUeNcp5Lx1U2Ic7j15ouJ83oRX+3GIslOuabW2oPkSgXbHkThMClhirKpvG98kwZg==} + /@next/swc-android-arm64/12.2.5-canary.4: + resolution: {integrity: sha512-UR9WtowrjQB3+PnG1Wk5liaUGoOchg1dQelQTuTN2fGBwX0OCboKvk+O3e4BXSr+t3Ugi7UlgoRmWUe/Yl2Ekg==} engines: {node: '>= 10'} cpu: [arm64] os: [android] requiresBuild: true optional: true - /@next/swc-darwin-arm64/12.2.0: - resolution: {integrity: sha512-x5U5gJd7ZvrEtTFnBld9O2bUlX8opu7mIQUqRzj7KeWzBwPhrIzTTsQXAiNqsaMuaRPvyHBVW/5d/6g6+89Y8g==} + /@next/swc-darwin-arm64/12.2.5-canary.4: + resolution: {integrity: sha512-dtkI6lx96Br10KlIt0OP12j49Anu26XwpAVUAqgttmCdpU3IsoTEJEWhlXDU5JqjSEZEX9WcB+FMXeiSAanM4w==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] requiresBuild: true optional: true - /@next/swc-darwin-x64/12.2.0: - resolution: {integrity: sha512-iwMNFsrAPjfedjKDv9AXPAV16PWIomP3qw/FfPaxkDVRbUls7BNdofBLzkQmqxqWh93WrawLwaqyXpJuAaiwJA==} + /@next/swc-darwin-x64/12.2.5-canary.4: + resolution: {integrity: sha512-6hwXnGD9hYOm7FVXhSDnLC2lMHXqeFQn4vviNF0wLzeFTjB3Tb2R0Db45L1g7gfs2KI6U0xdwrpL842LsY4Wlg==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] requiresBuild: true optional: true - /@next/swc-freebsd-x64/12.2.0: - resolution: {integrity: sha512-gRiAw8g3Akf6niTDLEm1Emfa7jXDjvaAj/crDO8hKASKA4Y1fS4kbi/tyWw5VtoFI4mUzRmCPmZ8eL0tBSG58A==} + /@next/swc-freebsd-x64/12.2.5-canary.4: + resolution: {integrity: sha512-mv8IrHIxxaOgah5P8pfhmqSCBpvzbmnyNBIIp1UMh0x0jElUBrCrNbz1dHPu5XmROEuigvfVp3yEBRrM5AIp/w==} engines: {node: '>= 10'} cpu: [x64] os: [freebsd] requiresBuild: true optional: true - /@next/swc-linux-arm-gnueabihf/12.2.0: - resolution: {integrity: sha512-/TJZkxaIpeEwnXh6A40trgwd40C5+LJroLUOEQwMOJdavLl62PjCA6dGl1pgooWLCIb5YdBQ0EG4ylzvLwS2+Q==} + /@next/swc-linux-arm-gnueabihf/12.2.5-canary.4: + resolution: {integrity: sha512-PP0LXA9g1byV9j6+eLuLdPpS+DII0AkXvkgBJvcLAY5tuZeMfloRsPUuglbX/K+4WIlNU3kpf24SEBsuM0tVIA==} engines: {node: '>= 10'} cpu: [arm] os: [linux] requiresBuild: true optional: true - /@next/swc-linux-arm64-gnu/12.2.0: - resolution: {integrity: sha512-++WAB4ElXCSOKG9H8r4ENF8EaV+w0QkrpjehmryFkQXmt5juVXz+nKDVlCRMwJU7A1O0Mie82XyEoOrf6Np1pA==} + /@next/swc-linux-arm64-gnu/12.2.5-canary.4: + resolution: {integrity: sha512-GqzYxIuQeEbja8KUTW/Uj/ksP4cnYMj9SyOjQ3Ijl5bnszylsQgaHyJwwODoX/qbqhDeYj77z7ZJktfgBKo0Bg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] requiresBuild: true optional: true - /@next/swc-linux-arm64-musl/12.2.0: - resolution: {integrity: sha512-XrqkHi/VglEn5zs2CYK6ofJGQySrd+Lr4YdmfJ7IhsCnMKkQY1ma9Hv5THwhZVof3e+6oFHrQ9bWrw9K4WTjFA==} + /@next/swc-linux-arm64-musl/12.2.5-canary.4: + resolution: {integrity: sha512-f7OHc0l2LIY7Q+898Zk53Qj6jx6Ay9CIram1JT4xDmOBx34D34WFnbmwf5hcKxbK0SVaWB6rYynJVAgH1DkkzQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] requiresBuild: true optional: true - /@next/swc-linux-x64-gnu/12.2.0: - resolution: {integrity: sha512-MyhHbAKVjpn065WzRbqpLu2krj4kHLi6RITQdD1ee+uxq9r2yg5Qe02l24NxKW+1/lkmpusl4Y5Lks7rBiJn4w==} + /@next/swc-linux-x64-gnu/12.2.5-canary.4: + resolution: {integrity: sha512-2tMSC9BEeXsmNvzu0gdLapr4R9KQS/1Ir+O/j2sfmVSIvCZl49k6JTpsbP7YAB2vBnflwvA3Mt9KtYu9C39AVg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] requiresBuild: true optional: true - /@next/swc-linux-x64-musl/12.2.0: - resolution: {integrity: sha512-Tz1tJZ5egE0S/UqCd5V6ZPJsdSzv/8aa7FkwFmIJ9neLS8/00za+OY5pq470iZQbPrkTwpKzmfTTIPRVD5iqDg==} + /@next/swc-linux-x64-musl/12.2.5-canary.4: + resolution: {integrity: sha512-9ASdeW9WIvw3/HC5gxaPvGd9ozNCZE2bPqYYZeAY55dC5JcEQlx66xeCh7Nxz7oRVyGHP1E5b+y598rBQ/HNwQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] requiresBuild: true optional: true - /@next/swc-win32-arm64-msvc/12.2.0: - resolution: {integrity: sha512-0iRO/CPMCdCYUzuH6wXLnsfJX1ykBX4emOOvH0qIgtiZM0nVYbF8lkEyY2ph4XcsurpinS+ziWuYCXVqrOSqiw==} + /@next/swc-win32-arm64-msvc/12.2.5-canary.4: + resolution: {integrity: sha512-CK/EyHxmpRzTm3Yg9CxSdxy9HRDtniQOD907KZ2nrd1cdfGrY7ZJMgGOv6uxdNhHlGWNWYiVOk6RI5yGM8IX5w==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] requiresBuild: true optional: true - /@next/swc-win32-ia32-msvc/12.2.0: - resolution: {integrity: sha512-8A26RJVcJHwIKm8xo/qk2ePRquJ6WCI2keV2qOW/Qm+ZXrPXHMIWPYABae/nKN243YFBNyPiHytjX37VrcpUhg==} + /@next/swc-win32-ia32-msvc/12.2.5-canary.4: + resolution: {integrity: sha512-+Xi/2l61HxlAsex6T28D64XZAfXPfn+UGnXl2iXtzKqYO9rhFO4erbLpMkJXKMJaH5Nn7MnT4nX3hVSl9adQ2A==} engines: {node: '>= 10'} cpu: [ia32] os: [win32] requiresBuild: true optional: true - /@next/swc-win32-x64-msvc/12.2.0: - resolution: {integrity: sha512-OI14ozFLThEV3ey6jE47zrzSTV/6eIMsvbwozo+XfdWqOPwQ7X00YkRx4GVMKMC0rM44oGS2gmwMKYpe4EblnA==} + /@next/swc-win32-x64-msvc/12.2.5-canary.4: + resolution: {integrity: sha512-VgEYUZYQnSCZMahezzROwPeMu9paH8EvCtAd0koRs7e6Zi67ERpwSCbRnaT+foKha69Re0WzGUrkvTjo1hP7ZQ==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -6315,8 +6315,8 @@ packages: '@swc/core-win32-x64-msvc': 1.2.204 dev: true - /@swc/helpers/0.4.2: - resolution: {integrity: sha512-556Az0VX7WR6UdoTn4htt/l3zPQ7bsQWK+HqdG4swV7beUCxo/BqmvbOpUkTIm/9ih86LIf1qsUnywNL3obGHw==} + /@swc/helpers/0.4.3: + resolution: {integrity: sha512-6JrF+fdUK2zbGpJIlN7G3v966PQjyx/dPt1T9km2wj+EUBqgrxCk3uX4Kct16MIm9gGxfKRcfax2hVf5jvlTzA==} dependencies: tslib: 2.4.0 @@ -9252,8 +9252,8 @@ packages: engines: {node: '>=10'} hasBin: true dependencies: - is-text-path: 1.0.1 JSONStream: 1.3.5 + is-text-path: 1.0.1 lodash: 4.17.21 meow: 8.1.2 split2: 3.2.2 @@ -16580,8 +16580,8 @@ packages: resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==} dev: true - /next/12.2.0_4cc5zw5azim2bix77d63le72su: - resolution: {integrity: sha512-B4j7D3SHYopLYx6/Ark0fenwIar9tEaZZFAaxmKjgcMMexhVJzB3jt7X+6wcdXPPMeUD6r09weUtnDpjox/vIA==} + /next/12.2.5-canary.4_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-RHXm6ZfG5HsLM63E/NgOvl8CT5kUy+RlEdW4rqpF0gSVK5qj8H/E/zXcc1QKwGQy2ndyKwVo2n1s00E1Fy661g==} engines: {node: '>=12.22.0'} hasBin: true peerDependencies: @@ -16598,77 +16598,27 @@ packages: sass: optional: true dependencies: - '@next/env': 12.2.0 - '@swc/helpers': 0.4.2 + '@next/env': 12.2.5-canary.4 + '@swc/helpers': 0.4.3 caniuse-lite: 1.0.30001358 - postcss: 8.4.5 + postcss: 8.4.14 react: 18.2.0 react-dom: 18.2.0_react@18.2.0 - styled-jsx: 5.0.2_5wvcx74lvxq2lfpc5x4ihgp2jm - use-sync-external-store: 1.1.0_react@18.2.0 + use-sync-external-store: 1.2.0_react@18.2.0 optionalDependencies: - '@next/swc-android-arm-eabi': 12.2.0 - '@next/swc-android-arm64': 12.2.0 - '@next/swc-darwin-arm64': 12.2.0 - '@next/swc-darwin-x64': 12.2.0 - '@next/swc-freebsd-x64': 12.2.0 - '@next/swc-linux-arm-gnueabihf': 12.2.0 - '@next/swc-linux-arm64-gnu': 12.2.0 - '@next/swc-linux-arm64-musl': 12.2.0 - '@next/swc-linux-x64-gnu': 12.2.0 - '@next/swc-linux-x64-musl': 12.2.0 - '@next/swc-win32-arm64-msvc': 12.2.0 - '@next/swc-win32-ia32-msvc': 12.2.0 - '@next/swc-win32-x64-msvc': 12.2.0 - transitivePeerDependencies: - - '@babel/core' - - babel-plugin-macros - dev: true - - /next/12.2.0_biqbaboplfbrettd7655fr4n2y: - resolution: {integrity: sha512-B4j7D3SHYopLYx6/Ark0fenwIar9tEaZZFAaxmKjgcMMexhVJzB3jt7X+6wcdXPPMeUD6r09weUtnDpjox/vIA==} - engines: {node: '>=12.22.0'} - hasBin: true - peerDependencies: - fibers: '>= 3.1.0' - node-sass: ^6.0.0 || ^7.0.0 - react: ^17.0.2 || ^18.0.0-0 - react-dom: ^17.0.2 || ^18.0.0-0 - sass: ^1.3.0 - peerDependenciesMeta: - fibers: - optional: true - node-sass: - optional: true - sass: - optional: true - dependencies: - '@next/env': 12.2.0 - '@swc/helpers': 0.4.2 - caniuse-lite: 1.0.30001358 - postcss: 8.4.5 - react: 18.2.0 - react-dom: 18.2.0_react@18.2.0 - styled-jsx: 5.0.2_react@18.2.0 - use-sync-external-store: 1.1.0_react@18.2.0 - optionalDependencies: - '@next/swc-android-arm-eabi': 12.2.0 - '@next/swc-android-arm64': 12.2.0 - '@next/swc-darwin-arm64': 12.2.0 - '@next/swc-darwin-x64': 12.2.0 - '@next/swc-freebsd-x64': 12.2.0 - '@next/swc-linux-arm-gnueabihf': 12.2.0 - '@next/swc-linux-arm64-gnu': 12.2.0 - '@next/swc-linux-arm64-musl': 12.2.0 - '@next/swc-linux-x64-gnu': 12.2.0 - '@next/swc-linux-x64-musl': 12.2.0 - '@next/swc-win32-arm64-msvc': 12.2.0 - '@next/swc-win32-ia32-msvc': 12.2.0 - '@next/swc-win32-x64-msvc': 12.2.0 - transitivePeerDependencies: - - '@babel/core' - - babel-plugin-macros - dev: false + '@next/swc-android-arm-eabi': 12.2.5-canary.4 + '@next/swc-android-arm64': 12.2.5-canary.4 + '@next/swc-darwin-arm64': 12.2.5-canary.4 + '@next/swc-darwin-x64': 12.2.5-canary.4 + '@next/swc-freebsd-x64': 12.2.5-canary.4 + '@next/swc-linux-arm-gnueabihf': 12.2.5-canary.4 + '@next/swc-linux-arm64-gnu': 12.2.5-canary.4 + '@next/swc-linux-arm64-musl': 12.2.5-canary.4 + '@next/swc-linux-x64-gnu': 12.2.5-canary.4 + '@next/swc-linux-x64-musl': 12.2.5-canary.4 + '@next/swc-win32-arm64-msvc': 12.2.5-canary.4 + '@next/swc-win32-ia32-msvc': 12.2.5-canary.4 + '@next/swc-win32-x64-msvc': 12.2.5-canary.4 /nice-try/1.0.5: resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==} @@ -17925,14 +17875,6 @@ packages: picocolors: 1.0.0 source-map-js: 1.0.2 - /postcss/8.4.5: - resolution: {integrity: sha512-jBDboWM8qpaqwkMwItqTQTiFikhs/67OYVvblFFTM7MrZjt6yMKd6r2kgXizEbTTljacm4NldIlZnhbjr84QYg==} - engines: {node: ^10 || ^12 || >=14} - dependencies: - nanoid: 3.3.4 - picocolors: 1.0.0 - source-map-js: 1.0.2 - /postgres-array/2.0.0: resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} engines: {node: '>=4'} @@ -20267,39 +20209,6 @@ packages: supports-color: 5.5.0 dev: false - /styled-jsx/5.0.2_5wvcx74lvxq2lfpc5x4ihgp2jm: - resolution: {integrity: sha512-LqPQrbBh3egD57NBcHET4qcgshPks+yblyhPlH2GY8oaDgKs8SK4C3dBh3oSJjgzJ3G5t1SYEZGHkP+QEpX9EQ==} - engines: {node: '>= 12.0.0'} - peerDependencies: - '@babel/core': '*' - babel-plugin-macros: '*' - react: '>= 16.8.0 || 17.x.x || ^18.0.0-0' - peerDependenciesMeta: - '@babel/core': - optional: true - babel-plugin-macros: - optional: true - dependencies: - '@babel/core': 7.18.5 - react: 18.2.0 - dev: true - - /styled-jsx/5.0.2_react@18.2.0: - resolution: {integrity: sha512-LqPQrbBh3egD57NBcHET4qcgshPks+yblyhPlH2GY8oaDgKs8SK4C3dBh3oSJjgzJ3G5t1SYEZGHkP+QEpX9EQ==} - engines: {node: '>= 12.0.0'} - peerDependencies: - '@babel/core': '*' - babel-plugin-macros: '*' - react: '>= 16.8.0 || 17.x.x || ^18.0.0-0' - peerDependenciesMeta: - '@babel/core': - optional: true - babel-plugin-macros: - optional: true - dependencies: - react: 18.2.0 - dev: false - /stylehacks/5.1.0_postcss@8.4.14: resolution: {integrity: sha512-SzLmvHQTrIWfSgljkQCw2++C9+Ne91d/6Sp92I8c5uHTcy/PgeHamwITIbBW9wnFTY/3ZfSXR9HIL6Ikqmcu6Q==} engines: {node: ^10 || ^12 || >=14.0} @@ -21640,8 +21549,8 @@ packages: use-isomorphic-layout-effect: 1.1.2_react@18.2.0 dev: false - /use-sync-external-store/1.1.0_react@18.2.0: - resolution: {integrity: sha512-SEnieB2FPKEVne66NpXPd1Np4R1lTNKfjuy3XdIoPQKYBAFdzbzSZlSn1KJZUiihQLQC5Znot4SBz1EOTBwQAQ==} + /use-sync-external-store/1.2.0_react@18.2.0: + resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 dependencies: From 504f3843520beea5a10940fb08578eed18a9fca0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Thu, 11 Aug 2022 12:51:30 +0200 Subject: [PATCH 03/93] rename ts types --- packages/next-auth/src/core/index.ts | 10 ++-------- packages/next-auth/src/core/lib/assert.ts | 4 ++-- .../src/core/lib/oauth/authorization-url.ts | 4 ++-- packages/next-auth/src/core/lib/oauth/callback.ts | 12 ++++++------ packages/next-auth/src/core/lib/spec.ts | 6 +++--- packages/next-auth/src/core/pages/index.ts | 6 +++--- packages/next-auth/src/core/routes/callback.ts | 14 +++++++------- packages/next-auth/src/core/routes/providers.ts | 4 ++-- packages/next-auth/src/core/routes/session.ts | 6 +++--- packages/next-auth/src/core/routes/signin.ts | 8 ++++---- packages/next-auth/src/core/routes/signout.ts | 4 ++-- packages/next-auth/src/index.ts | 2 -- packages/next-auth/src/providers/credentials.ts | 4 ++-- 13 files changed, 38 insertions(+), 46 deletions(-) diff --git a/packages/next-auth/src/core/index.ts b/packages/next-auth/src/core/index.ts index 895f89b3d8..e3a2b2e10f 100644 --- a/packages/next-auth/src/core/index.ts +++ b/packages/next-auth/src/core/index.ts @@ -9,9 +9,8 @@ import { fromRequest, toResponse } from "./lib/spec" import type { NextAuthAction, NextAuthOptions } from "./types" import type { Cookie } from "./lib/cookie" import type { ErrorType } from "./pages/error" -import { parse as parseCookie } from "cookie" -export interface RequestInternal { +export interface InternalRequest { /** @default "http://localhost:3000" */ host?: string method?: string @@ -24,12 +23,7 @@ export interface RequestInternal { error?: string } -export interface NextAuthHeader { - key: string - value: string -} - -export interface OutgoingResponse< +export interface InternalResponse< Body extends string | Record | any[] = any > { status?: number diff --git a/packages/next-auth/src/core/lib/assert.ts b/packages/next-auth/src/core/lib/assert.ts index a22790d359..ed09c39d6a 100644 --- a/packages/next-auth/src/core/lib/assert.ts +++ b/packages/next-auth/src/core/lib/assert.ts @@ -9,7 +9,7 @@ import { import parseUrl from "../../utils/parse-url" import { defaultCookies } from "./cookie" -import type { RequestInternal } from ".." +import type { InternalRequest } from ".." import type { WarningCode } from "../../utils/logger" import type { NextAuthOptions } from "../types" @@ -40,7 +40,7 @@ function isValidHttpUrl(url: string, baseUrl: string) { */ export function assertConfig(params: { options: NextAuthOptions - req: RequestInternal + req: InternalRequest }): ConfigError | WarningCode[] { const { options, req } = params diff --git a/packages/next-auth/src/core/lib/oauth/authorization-url.ts b/packages/next-auth/src/core/lib/oauth/authorization-url.ts index 1f74acf630..4ac6a5f671 100644 --- a/packages/next-auth/src/core/lib/oauth/authorization-url.ts +++ b/packages/next-auth/src/core/lib/oauth/authorization-url.ts @@ -5,7 +5,7 @@ import { createPKCE } from "./pkce-handler" import type { AuthorizationParameters } from "openid-client" import type { InternalOptions } from "../../types" -import type { RequestInternal } from "../.." +import type { InternalRequest } from "../.." import type { Cookie } from "../cookie" /** @@ -19,7 +19,7 @@ export default async function getAuthorizationUrl({ query, }: { options: InternalOptions<"oauth"> - query: RequestInternal["query"] + query: InternalRequest["query"] }) { const { logger, provider } = options let params: any = {} diff --git a/packages/next-auth/src/core/lib/oauth/callback.ts b/packages/next-auth/src/core/lib/oauth/callback.ts index 0904216d7e..ef1c552720 100644 --- a/packages/next-auth/src/core/lib/oauth/callback.ts +++ b/packages/next-auth/src/core/lib/oauth/callback.ts @@ -9,16 +9,16 @@ import type { CallbackParamsType } from "openid-client" import type { Account, LoggerInstance, Profile } from "../../.." import type { OAuthChecks, OAuthConfig } from "../../../providers" import type { InternalOptions } from "../../types" -import type { RequestInternal, OutgoingResponse } from "../.." +import type { InternalRequest, InternalResponse } from "../.." import type { Cookie } from "../cookie" export default async function oAuthCallback(params: { options: InternalOptions<"oauth"> - query: RequestInternal["query"] - body: RequestInternal["body"] - method: Required["method"] - cookies: RequestInternal["cookies"] -}): Promise { + query: InternalRequest["query"] + body: InternalRequest["body"] + method: Required["method"] + cookies: InternalRequest["cookies"] +}): Promise { const { options, query, body, method, cookies } = params const { logger, provider } = options diff --git a/packages/next-auth/src/core/lib/spec.ts b/packages/next-auth/src/core/lib/spec.ts index b6962b914e..ebae8dc22f 100644 --- a/packages/next-auth/src/core/lib/spec.ts +++ b/packages/next-auth/src/core/lib/spec.ts @@ -1,6 +1,6 @@ import { serialize, parse as parseCookie } from "cookie" import { detectHost } from "../../utils/detect-host" -import type { OutgoingResponse, RequestInternal } from ".." +import type { InternalResponse, InternalRequest } from ".." import type { NextAuthAction } from "../types" async function readBody( @@ -23,7 +23,7 @@ async function readBody( } // TODO: -export async function fromRequest(req: Request): Promise { +): Promise { // TODO: handle custom url const url = new URL(req.url, "http://localhost:3000") const nextauth = url.pathname.split("/").slice(3) @@ -52,7 +52,7 @@ export async function fromRequest(req: Request): Promise { } } -export function toResponse(_res: OutgoingResponse): Response { +export function toResponse(res: InternalResponse): Response { const headers = new Headers( _res.headers?.reduce((acc, { key, value }) => { acc[key] = value diff --git a/packages/next-auth/src/core/pages/index.ts b/packages/next-auth/src/core/pages/index.ts index 9812497aa0..b79dcf65c6 100644 --- a/packages/next-auth/src/core/pages/index.ts +++ b/packages/next-auth/src/core/pages/index.ts @@ -6,12 +6,12 @@ import ErrorPage from "./error" import css from "../../css" import type { InternalOptions } from "../types" -import type { RequestInternal, OutgoingResponse } from ".." +import type { InternalRequest, InternalResponse } from ".." import type { Cookie } from "../lib/cookie" import type { ErrorType } from "./error" type RenderPageParams = { - query?: RequestInternal["query"] + query?: InternalRequest["query"] cookies?: Cookie[] } & Partial< Pick< @@ -27,7 +27,7 @@ type RenderPageParams = { export default function renderPage(params: RenderPageParams) { const { url, theme, query, cookies } = params - function send({ html, title, status }: any): OutgoingResponse { + function send({ html, title, status }: any): InternalResponse { return { cookies, status, diff --git a/packages/next-auth/src/core/routes/callback.ts b/packages/next-auth/src/core/routes/callback.ts index b23b63e8d3..7fb036dbfe 100644 --- a/packages/next-auth/src/core/routes/callback.ts +++ b/packages/next-auth/src/core/routes/callback.ts @@ -3,20 +3,20 @@ import callbackHandler from "../lib/callback-handler" import { hashToken } from "../lib/utils" import type { InternalOptions } from "../types" -import type { RequestInternal, OutgoingResponse } from ".." +import type { InternalRequest, InternalResponse } from ".." import type { Cookie, SessionStore } from "../lib/cookie" import type { User } from "../.." /** Handle callbacks from login services */ export default async function callback(params: { options: InternalOptions<"oauth" | "credentials" | "email"> - query: RequestInternal["query"] - method: Required["method"] - body: RequestInternal["body"] - headers: RequestInternal["headers"] - cookies: RequestInternal["cookies"] + query: InternalRequest["query"] + method: Required["method"] + body: InternalRequest["body"] + headers: InternalRequest["headers"] + cookies: InternalRequest["cookies"] sessionStore: SessionStore -}): Promise { +}): Promise { const { options, query, body, method, headers, sessionStore } = params const { provider, diff --git a/packages/next-auth/src/core/routes/providers.ts b/packages/next-auth/src/core/routes/providers.ts index f946538c5f..5a2bb979a8 100644 --- a/packages/next-auth/src/core/routes/providers.ts +++ b/packages/next-auth/src/core/routes/providers.ts @@ -1,4 +1,4 @@ -import type { OutgoingResponse } from ".." +import type { InternalResponse } from ".." import type { InternalProvider } from "../types" export interface PublicProvider { @@ -16,7 +16,7 @@ export interface PublicProvider { */ export default function providers( providers: InternalProvider[] -): OutgoingResponse> { +): InternalResponse> { return { headers: [{ key: "Content-Type", value: "application/json" }], body: providers.reduce>( diff --git a/packages/next-auth/src/core/routes/session.ts b/packages/next-auth/src/core/routes/session.ts index 8989bc7138..aadfd45e22 100644 --- a/packages/next-auth/src/core/routes/session.ts +++ b/packages/next-auth/src/core/routes/session.ts @@ -2,7 +2,7 @@ import { fromDate } from "../lib/utils" import type { Adapter } from "../../adapters" import type { InternalOptions } from "../types" -import type { OutgoingResponse } from ".." +import type { InternalResponse } from ".." import type { Session } from "../.." import type { SessionStore } from "../lib/cookie" @@ -18,7 +18,7 @@ interface SessionParams { export default async function session( params: SessionParams -): Promise> { +): Promise> { const { options, sessionStore } = params const { adapter, @@ -29,7 +29,7 @@ export default async function session( session: { strategy: sessionStrategy, maxAge: sessionMaxAge }, } = options - const response: OutgoingResponse = { + const response: InternalResponse = { body: {}, headers: [{ key: "Content-Type", value: "application/json" }], cookies: [], diff --git a/packages/next-auth/src/core/routes/signin.ts b/packages/next-auth/src/core/routes/signin.ts index 3db306a05e..586275fe87 100644 --- a/packages/next-auth/src/core/routes/signin.ts +++ b/packages/next-auth/src/core/routes/signin.ts @@ -1,15 +1,15 @@ import getAuthorizationUrl from "../lib/oauth/authorization-url" import emailSignin from "../lib/email/signin" -import type { RequestInternal, OutgoingResponse } from ".." +import type { InternalRequest, InternalResponse } from ".." import type { InternalOptions } from "../types" import type { Account, User } from "../.." /** Handle requests to /api/auth/signin */ export default async function signin(params: { options: InternalOptions<"oauth" | "email"> - query: RequestInternal["query"] - body: RequestInternal["body"] -}): Promise { + query: InternalRequest["query"] + body: InternalRequest["body"] +}): Promise { const { options, query, body } = params const { url, adapter, callbacks, logger, provider } = options diff --git a/packages/next-auth/src/core/routes/signout.ts b/packages/next-auth/src/core/routes/signout.ts index 69648aabcd..5009a27bb5 100644 --- a/packages/next-auth/src/core/routes/signout.ts +++ b/packages/next-auth/src/core/routes/signout.ts @@ -1,13 +1,13 @@ import type { Adapter } from "../../adapters" import type { InternalOptions } from "../types" -import type { OutgoingResponse } from ".." +import type { InternalResponse } from ".." import type { SessionStore } from "../lib/cookie" /** Handle requests to /api/auth/signout */ export default async function signout(params: { options: InternalOptions sessionStore: SessionStore -}): Promise { +}): Promise { const { options, sessionStore } = params const { adapter, events, jwt, callbackUrl, logger, session } = options diff --git a/packages/next-auth/src/index.ts b/packages/next-auth/src/index.ts index 401838bba1..f27a296aaf 100644 --- a/packages/next-auth/src/index.ts +++ b/packages/next-auth/src/index.ts @@ -1,6 +1,4 @@ export * from "./core/types" -export type { RequestInternal, OutgoingResponse } from "./core" - export * from "./next" export { default } from "./next" diff --git a/packages/next-auth/src/providers/credentials.ts b/packages/next-auth/src/providers/credentials.ts index 4c17b2f393..055c1a7b80 100644 --- a/packages/next-auth/src/providers/credentials.ts +++ b/packages/next-auth/src/providers/credentials.ts @@ -1,4 +1,4 @@ -import type { RequestInternal } from "../core" +import type { InternalRequest } from "../core" import type { CommonProviderOptions } from "." import type { User, Awaitable } from ".." @@ -16,7 +16,7 @@ export interface CredentialsConfig< credentials: C authorize: ( credentials: Record | undefined, - req: Pick + req: Pick ) => Awaitable<(Omit | { id?: string }) | null> } From c73694801fa91eb945cbdca732dc28556bd30f74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Thu, 11 Aug 2022 12:53:03 +0200 Subject: [PATCH 04/93] refactor --- packages/next-auth/src/core/index.ts | 124 ++++++++---------------- packages/next-auth/src/core/init.ts | 4 +- packages/next-auth/src/core/lib/spec.ts | 39 +++----- packages/next-auth/src/next/index.ts | 87 ++++++++++------- 4 files changed, 110 insertions(+), 144 deletions(-) diff --git a/packages/next-auth/src/core/index.ts b/packages/next-auth/src/core/index.ts index e3a2b2e10f..0bd482afe5 100644 --- a/packages/next-auth/src/core/index.ts +++ b/packages/next-auth/src/core/index.ts @@ -2,7 +2,7 @@ import logger, { setLogger } from "../utils/logger" import * as routes from "./routes" import renderPage from "./pages" import { init } from "./init" -import { assertConfig as assert } from "./lib/assert" +import { assertConfig } from "./lib/assert" import { SessionStore } from "./lib/cookie" import { fromRequest, toResponse } from "./lib/spec" @@ -33,53 +33,20 @@ export interface InternalResponse< cookies?: Cookie[] } -export interface NextAuthHandlerParams { - req: Request - options: NextAuthOptions -} - -async function getBody(req: Request): Promise | undefined> { - try { - return await req.json() - } catch {} -} - -// TODO: -async function toInternalRequest( - req: RequestInternal | Request -): Promise { - 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 = 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 interface NextAuthHeader { + key: string + value: string } -export async function NextAuthHandler< +export async function AuthHandlerInternal< Body extends string | Record | any[] ->(params: NextAuthHandlerParams): Promise> { - const { options: userOptions, req: incomingRequest } = params - - const req = await toInternalRequest(incomingRequest) - +>(params: { + req: InternalRequest + options: NextAuthOptions + /** REVIEW: Is this the best way to skip parsing the body in Node.js? */ + parsedBody?: any +}): Promise> { + const { options: userOptions, req } = params setLogger(userOptions.logger, userOptions.debug) const assertionResult = assertConfig({ options: userOptions, req }) @@ -120,21 +87,21 @@ export async function NextAuthHandler< userOptions, action, providerId, - host: host, - callbackUrl: body?.callbackUrl ?? query?.callbackUrl, - csrfToken: body?.csrfToken, - cookies: internalRequest.cookies, + host: req.host, + callbackUrl: req.body?.callbackUrl ?? req.query?.callbackUrl, + csrfToken: req.body?.csrfToken, + cookies: req.cookies, isPost: method === "POST", }) const sessionStore = new SessionStore( options.cookies.sessionToken, - internalRequest, + req, options.logger ) if (method === "GET") { - const render = renderPage({ ...options, query, cookies }) + const render = renderPage({ ...options, query: req.query, cookies }) const { pages } = options switch (action) { case "providers": @@ -168,10 +135,10 @@ export async function NextAuthHandler< case "callback": if (options.provider) { const callback = await routes.callback({ - body, - query, - headers, - cookies: internalRequest.cookies, + body: req.body, + query: req.query, + headers: req.headers, + cookies: req.cookies, method, options, sessionStore, @@ -221,7 +188,11 @@ export async function NextAuthHandler< case "signin": // Verified CSRF Token required for all sign in routes if (options.csrfTokenVerified && options.provider) { - const signin = await routes.signin({ query, body, options }) + const signin = await routes.signin({ + query: req.query, + body: req.body, + options, + }) if (signin.cookies) cookies.push(...signin.cookies) return { ...signin, cookies } } @@ -246,10 +217,10 @@ export async function NextAuthHandler< } const callback = await routes.callback({ - body, - query, - headers, - cookies: internalRequest.cookies, + body: req.body, + query: req.query, + headers: req.headers, + cookies: req.cookies, method, options, sessionStore, @@ -261,7 +232,7 @@ export async function NextAuthHandler< case "_log": if (userOptions.logger) { try { - const { code, level, ...metadata } = body ?? {} + const { code, level, ...metadata } = req.body ?? {} logger[level](code, metadata) } catch (error) { // If logging itself failed... @@ -284,28 +255,11 @@ export async function NextAuthHandler< * 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 NextAuthHandler( - req: Request, - options: NextAuthHandlerParams["options"] -): Promise { - setLogger(options.logger, options.debug) - const assertionResult = assert({ req, options }) - - if (typeof assertionResult === "string") { - logger.warn(assertionResult) - } else if (assertionResult instanceof Error) { - // Bail out early if there's an error in the user config - const { pages, theme } = options - logger.error(assertionResult.code, assertionResult) - if (pages?.error) { - return new Response(null, { - status: 302, - headers: { Location: `${pages.error}?error=Configuration` }, - }) - } - const render = renderPage({ theme }) - return toResponse(render.error({ error: "configuration" })) - } - - return toResponse(await NextAuthHandlerInternal(req, options)) +export async function AuthHandler(params: { + req: Request + options: NextAuthOptions + parsedBody?: any +}): Promise { + const req = await fromRequest(params.req, params.parsedBody) + return toResponse(await AuthHandlerInternal({ ...params, req })) } diff --git a/packages/next-auth/src/core/init.ts b/packages/next-auth/src/core/init.ts index 478519cb17..b3ab89647e 100644 --- a/packages/next-auth/src/core/init.ts +++ b/packages/next-auth/src/core/init.ts @@ -9,7 +9,7 @@ import * as jwt from "../jwt" import { defaultCallbacks } from "./lib/default-callbacks" import { createCSRFToken } from "./lib/csrf-token" import { createCallbackUrl } from "./lib/callback-url" -import { RequestInternal } from "." +import { InternalRequest } from "." import type { InternalOptions } from "./types" @@ -24,7 +24,7 @@ interface InitParams { csrfToken?: string /** Is the incoming request a POST request? */ isPost: boolean - cookies: RequestInternal["cookies"] + cookies: InternalRequest["cookies"] } /** Initialize all internal options and cookies. */ diff --git a/packages/next-auth/src/core/lib/spec.ts b/packages/next-auth/src/core/lib/spec.ts index ebae8dc22f..1051ec888f 100644 --- a/packages/next-auth/src/core/lib/spec.ts +++ b/packages/next-auth/src/core/lib/spec.ts @@ -3,26 +3,17 @@ import { detectHost } from "../../utils/detect-host" import type { InternalResponse, InternalRequest } from ".." import type { NextAuthAction } from "../types" +// TODO: Implement async function readBody( body: ReadableStream | null ): Promise | undefined> { - try { - const reader = body?.getReader() - - if (!reader) return undefined - - const chunks: Uint8Array[] = [] - while (true) { - const { value, done } = await reader.read() - if (done) break - chunks.push(value) - } - - return JSON.parse(Buffer.concat(chunks).toString()) - } catch {} + throw new Error("Not implemented") } // TODO: +export async function fromRequest( + req: Request, + parsedBody?: any ): Promise { // TODO: handle custom url const url = new URL(req.url, "http://localhost:3000") @@ -33,7 +24,7 @@ async function readBody( ) query.nextauth = nextauth - const cookieHeader = req.headers.get("cookie") + const cookieHeader = req.headers.get("cookie") ?? "" const cookies = parseCookie( Array.isArray(cookieHeader) ? cookieHeader.join(";") : cookieHeader @@ -43,7 +34,7 @@ async function readBody( action: nextauth[0] as NextAuthAction, method: req.method, headers, - body: await readBody(req.body), + body: parsedBody ?? (await readBody(req.body)), cookies: cookies, providerId: nextauth[1], error: url.searchParams.get("error") ?? undefined, @@ -54,13 +45,13 @@ async function readBody( export function toResponse(res: InternalResponse): Response { const headers = new Headers( - _res.headers?.reduce((acc, { key, value }) => { + res.headers?.reduce((acc, { key, value }) => { acc[key] = value return acc }, {}) ) - _res.cookies?.forEach((cookie) => { + res.cookies?.forEach((cookie) => { const { name, value, options } = cookie const cookieHeader = serialize(name, value, options) if (headers.has("Set-Cookie")) { @@ -71,18 +62,18 @@ export function toResponse(res: InternalResponse): Response { }) const body = - _res.headers?.find(({ key }) => key === "Content-Type")?.value === + res.headers?.find(({ key }) => key === "Content-Type")?.value === "application/json" - ? JSON.stringify(_res.body) - : _res.body + ? JSON.stringify(res.body) + : res.body const response = new Response(body, { headers, - status: _res.redirect ? 301 : _res.status ?? 200, + status: res.redirect ? 301 : res.status ?? 200, }) - if (_res.redirect) { - response.headers.set("Location", _res.redirect) + if (res.redirect) { + response.headers.set("Location", res.redirect) } return response diff --git a/packages/next-auth/src/next/index.ts b/packages/next-auth/src/next/index.ts index e00986b57f..78b205e04c 100644 --- a/packages/next-auth/src/next/index.ts +++ b/packages/next-auth/src/next/index.ts @@ -1,4 +1,5 @@ -import { NextAuthHandler } from "../core" +import { serialize } from "cookie" +import { AuthHandlerInternal } from "../core" import { detectHost } from "../utils/detect-host" import { setCookie } from "./utils" @@ -8,68 +9,84 @@ import type { NextApiResponse, } from "next" import type { NextAuthOptions, Session } from ".." -import type { NextAuthRequest, NextAuthResponse } from "../core/types" +import type { + NextAuthAction, + NextAuthRequest, + NextAuthResponse, +} from "../core/types" -async function NextAuthNextHandler( +async function NextAuthHandler( req: NextApiRequest, res: NextApiResponse, options: NextAuthOptions ) { + const { nextauth, ...query } = req.query + options.secret = options.secret ?? options.jwt?.secret ?? process.env.NEXTAUTH_SECRET - const shouldUseBody = - req.method !== "GET" && req.headers["content-type"] === "application/json" - - const _req = new Request(req.url!, { - headers: new Headers(req.headers as any), - method: req.method, - ...((shouldUseBody - ? { body: JSON.stringify(Object.fromEntries(Object.entries(req.body))) } - : {}) as any), + const handler = await AuthHandlerInternal({ + req: { + host: detectHost(req.headers["x-forwarded-host"]), + body: req.body, + query, + cookies: req.cookies, + headers: req.headers, + method: req.method, + action: nextauth?.[0] as NextAuthAction, + providerId: nextauth?.[1], + error: (req.query.error as string | undefined) ?? nextauth?.[1], + }, + options, }) - const _res = await NextAuthHandler(_req, options) + res.status(handler.status ?? 200) - res.status(_res.status ?? 200) + handler.cookies?.forEach((cookie) => { + const { name, value, options } = cookie + const cookieHeader = serialize(name, value, options) + setCookie(res, cookieHeader) + }) - for (const [key, value] of _res.headers.entries()) { - res.setHeader(key, value) - } + handler.headers?.forEach((h) => res.setHeader(h.key, h.value)) - // If the request expects a return URL, send it as JSON - // instead of doing an actual redirect. - const redirect = _res.headers.get("Location") - if (req.body?.json === "true" && redirect) { - return res.json({ url: redirect }) + if (handler.redirect) { + // If the request expects a return URL, send it as JSON + // instead of doing an actual redirect. + if (req.body?.json !== "true") { + // Could chain. .end() when lowest target is Node 14 + // https://github.com/nodejs/node/issues/33148 + res.status(302).setHeader("Location", handler.redirect) + return res.end() + } + return res.json({ url: handler.redirect }) } - return res.send(_res.body) + return res.send(handler.body) } -function NextAuth(options: NextAuthOptions): any -function NextAuth( +export default function NextAuth(options: NextAuthOptions): any +export default function NextAuth( req: NextApiRequest, res: NextApiResponse, options: NextAuthOptions ): any /** The main entry point to next-auth */ -function NextAuth( +export default function NextAuth( ...args: | [NextAuthOptions] | [NextApiRequest, NextApiResponse, NextAuthOptions] ) { if (args.length === 1) { - return async (req: NextAuthRequest, res: NextAuthResponse) => - await NextAuthNextHandler(req, res, args[0]) + return function handler(req: NextAuthRequest, res: NextAuthResponse) { + return NextAuthHandler(req, res, args[0]) + } } - return NextAuthNextHandler(args[0], args[1], args[2]) + return NextAuthHandler(args[0], args[1], args[2]) } -export default NextAuth - let experimentalWarningShown = false export async function unstable_getServerSession( ...args: @@ -94,7 +111,7 @@ export async function unstable_getServerSession( options.secret = options.secret ?? process.env.NEXTAUTH_SECRET - const session = await NextAuthHandler({ + const session = await AuthHandlerInternal({ options, req: { host: detectHost(req.headers["x-forwarded-host"]), @@ -107,7 +124,11 @@ export async function unstable_getServerSession( const { body, cookies } = session - cookies?.forEach((cookie) => setCookie(res, cookie)) + cookies?.forEach((cookie) => { + const { name, value, options } = cookie + const cookieHeader = serialize(name, value, options) + setCookie(res, cookieHeader) + }) if (body && Object.keys(body).length) return body as Session return null From b274e98f8bd93f284390a1f1ec5eb527a5fa3ff0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Thu, 11 Aug 2022 12:55:29 +0200 Subject: [PATCH 05/93] simplify --- packages/next-auth/src/core/lib/spec.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/next-auth/src/core/lib/spec.ts b/packages/next-auth/src/core/lib/spec.ts index 1051ec888f..a310a39865 100644 --- a/packages/next-auth/src/core/lib/spec.ts +++ b/packages/next-auth/src/core/lib/spec.ts @@ -11,10 +11,7 @@ async function readBody( } // TODO: -export async function fromRequest( - req: Request, - parsedBody?: any -): Promise { +export async function fromRequest(req: Request): Promise { // TODO: handle custom url const url = new URL(req.url, "http://localhost:3000") const nextauth = url.pathname.split("/").slice(3) @@ -34,7 +31,7 @@ export async function fromRequest( action: nextauth[0] as NextAuthAction, method: req.method, headers, - body: parsedBody ?? (await readBody(req.body)), + body: await readBody(req.body), cookies: cookies, providerId: nextauth[1], error: url.searchParams.get("error") ?? undefined, From b9330038e54b044f9bcd3d20e75115a050dd5ba6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Fri, 12 Aug 2022 13:45:24 +0200 Subject: [PATCH 06/93] upgrade Next.js --- apps/dev/package.json | 2 +- packages/next-auth/package.json | 2 +- pnpm-lock.yaml | 179 +++++++++++++++++++++++--------- 3 files changed, 133 insertions(+), 50 deletions(-) diff --git a/apps/dev/package.json b/apps/dev/package.json index e86237d931..5718ee36d7 100644 --- a/apps/dev/package.json +++ b/apps/dev/package.json @@ -21,7 +21,7 @@ "@next-auth/typeorm-legacy-adapter": "workspace:*", "@prisma/client": "^3", "faunadb": "^4", - "next": "12.2.5-canary.4", + "next": "12.2.5", "nodemailer": "^6", "react": "^18", "react-dom": "^18" diff --git a/packages/next-auth/package.json b/packages/next-auth/package.json index 1b693c8a3d..dbd788bed7 100644 --- a/packages/next-auth/package.json +++ b/packages/next-auth/package.json @@ -116,7 +116,7 @@ "jest-environment-jsdom": "^28.1.1", "jest-watch-typeahead": "^1.1.0", "msw": "^0.42.3", - "next": "12.2.5-canary.4", + "next": "12.2.5", "postcss": "^8.4.14", "postcss-cli": "^9.1.0", "postcss-nested": "^5.0.6", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 49b7c290ab..5a5fb75213 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -52,7 +52,7 @@ importers: cpx: ^1.5.0 fake-smtp-server: ^0.8.0 faunadb: ^4 - next: 12.2.5-canary.4 + next: 12.2.5 nodemailer: ^6 pg: ^8.7.3 prisma: ^3 @@ -66,7 +66,7 @@ importers: '@next-auth/typeorm-legacy-adapter': link:../../packages/adapter-typeorm-legacy '@prisma/client': 3.15.2_prisma@3.15.2 faunadb: 4.6.0 - next: 12.2.5-canary.4_biqbaboplfbrettd7655fr4n2y + next: 12.2.5_biqbaboplfbrettd7655fr4n2y nodemailer: 6.7.5 react: 18.2.0 react-dom: 18.2.0_react@18.2.0 @@ -428,7 +428,7 @@ importers: jest-watch-typeahead: ^1.1.0 jose: ^4.3.7 msw: ^0.42.3 - next: 12.2.5-canary.4 + next: 12.2.5 oauth: ^0.9.15 openid-client: ^5.1.0 postcss: ^8.4.14 @@ -480,7 +480,7 @@ importers: jest-environment-jsdom: 28.1.1 jest-watch-typeahead: 1.1.0_jest@28.1.1 msw: 0.42.3 - next: 12.2.5-canary.4_biqbaboplfbrettd7655fr4n2y + next: 12.2.5_4cc5zw5azim2bix77d63le72su postcss: 8.4.14 postcss-cli: 9.1.0_postcss@8.4.14 postcss-nested: 5.0.6_postcss@8.4.14 @@ -5701,107 +5701,107 @@ packages: - supports-color dev: true - /@next/env/12.2.5-canary.4: - resolution: {integrity: sha512-zg1HLMDwV+tSArHTytwtHDk1WNkC/jkLj1c6QsJ09J4pcA716YNsh5RAe+4mniKk/S5PQoMwM+hDT2LIiWas2g==} + /@next/env/12.2.5: + resolution: {integrity: sha512-vLPLV3cpPGjUPT3PjgRj7e3nio9t6USkuew3JE/jMeon/9Mvp1WyR18v3iwnCuX7eUAm1HmAbJHHLAbcu/EJcw==} - /@next/swc-android-arm-eabi/12.2.5-canary.4: - resolution: {integrity: sha512-Akd9rpyrsmmv6+5LBvV6ZGMr4hj+QyYoWUn2mnXYnNRi9XKeu2yX9lQWGCYVGRPVxrZ7/Fu2Egqp4xBId/EsTw==} + /@next/swc-android-arm-eabi/12.2.5: + resolution: {integrity: sha512-cPWClKxGhgn2dLWnspW+7psl3MoLQUcNqJqOHk2BhNcou9ARDtC0IjQkKe5qcn9qg7I7U83Gp1yh2aesZfZJMA==} engines: {node: '>= 10'} cpu: [arm] os: [android] requiresBuild: true optional: true - /@next/swc-android-arm64/12.2.5-canary.4: - resolution: {integrity: sha512-UR9WtowrjQB3+PnG1Wk5liaUGoOchg1dQelQTuTN2fGBwX0OCboKvk+O3e4BXSr+t3Ugi7UlgoRmWUe/Yl2Ekg==} + /@next/swc-android-arm64/12.2.5: + resolution: {integrity: sha512-vMj0efliXmC5b7p+wfcQCX0AfU8IypjkzT64GiKJD9PgiA3IILNiGJr1fw2lyUDHkjeWx/5HMlMEpLnTsQslwg==} engines: {node: '>= 10'} cpu: [arm64] os: [android] requiresBuild: true optional: true - /@next/swc-darwin-arm64/12.2.5-canary.4: - resolution: {integrity: sha512-dtkI6lx96Br10KlIt0OP12j49Anu26XwpAVUAqgttmCdpU3IsoTEJEWhlXDU5JqjSEZEX9WcB+FMXeiSAanM4w==} + /@next/swc-darwin-arm64/12.2.5: + resolution: {integrity: sha512-VOPWbO5EFr6snla/WcxUKtvzGVShfs302TEMOtzYyWni6f9zuOetijJvVh9CCTzInnXAZMtHyNhefijA4HMYLg==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] requiresBuild: true optional: true - /@next/swc-darwin-x64/12.2.5-canary.4: - resolution: {integrity: sha512-6hwXnGD9hYOm7FVXhSDnLC2lMHXqeFQn4vviNF0wLzeFTjB3Tb2R0Db45L1g7gfs2KI6U0xdwrpL842LsY4Wlg==} + /@next/swc-darwin-x64/12.2.5: + resolution: {integrity: sha512-5o8bTCgAmtYOgauO/Xd27vW52G2/m3i5PX7MUYePquxXAnX73AAtqA3WgPXBRitEB60plSKZgOTkcpqrsh546A==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] requiresBuild: true optional: true - /@next/swc-freebsd-x64/12.2.5-canary.4: - resolution: {integrity: sha512-mv8IrHIxxaOgah5P8pfhmqSCBpvzbmnyNBIIp1UMh0x0jElUBrCrNbz1dHPu5XmROEuigvfVp3yEBRrM5AIp/w==} + /@next/swc-freebsd-x64/12.2.5: + resolution: {integrity: sha512-yYUbyup1JnznMtEBRkK4LT56N0lfK5qNTzr6/DEyDw5TbFVwnuy2hhLBzwCBkScFVjpFdfiC6SQAX3FrAZzuuw==} engines: {node: '>= 10'} cpu: [x64] os: [freebsd] requiresBuild: true optional: true - /@next/swc-linux-arm-gnueabihf/12.2.5-canary.4: - resolution: {integrity: sha512-PP0LXA9g1byV9j6+eLuLdPpS+DII0AkXvkgBJvcLAY5tuZeMfloRsPUuglbX/K+4WIlNU3kpf24SEBsuM0tVIA==} + /@next/swc-linux-arm-gnueabihf/12.2.5: + resolution: {integrity: sha512-2ZE2/G921Acks7UopJZVMgKLdm4vN4U0yuzvAMJ6KBavPzqESA2yHJlm85TV/K9gIjKhSk5BVtauIUntFRP8cg==} engines: {node: '>= 10'} cpu: [arm] os: [linux] requiresBuild: true optional: true - /@next/swc-linux-arm64-gnu/12.2.5-canary.4: - resolution: {integrity: sha512-GqzYxIuQeEbja8KUTW/Uj/ksP4cnYMj9SyOjQ3Ijl5bnszylsQgaHyJwwODoX/qbqhDeYj77z7ZJktfgBKo0Bg==} + /@next/swc-linux-arm64-gnu/12.2.5: + resolution: {integrity: sha512-/I6+PWVlz2wkTdWqhlSYYJ1pWWgUVva6SgX353oqTh8njNQp1SdFQuWDqk8LnM6ulheVfSsgkDzxrDaAQZnzjQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] requiresBuild: true optional: true - /@next/swc-linux-arm64-musl/12.2.5-canary.4: - resolution: {integrity: sha512-f7OHc0l2LIY7Q+898Zk53Qj6jx6Ay9CIram1JT4xDmOBx34D34WFnbmwf5hcKxbK0SVaWB6rYynJVAgH1DkkzQ==} + /@next/swc-linux-arm64-musl/12.2.5: + resolution: {integrity: sha512-LPQRelfX6asXyVr59p5sTpx5l+0yh2Vjp/R8Wi4X9pnqcayqT4CUJLiHqCvZuLin3IsFdisJL0rKHMoaZLRfmg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] requiresBuild: true optional: true - /@next/swc-linux-x64-gnu/12.2.5-canary.4: - resolution: {integrity: sha512-2tMSC9BEeXsmNvzu0gdLapr4R9KQS/1Ir+O/j2sfmVSIvCZl49k6JTpsbP7YAB2vBnflwvA3Mt9KtYu9C39AVg==} + /@next/swc-linux-x64-gnu/12.2.5: + resolution: {integrity: sha512-0szyAo8jMCClkjNK0hknjhmAngUppoRekW6OAezbEYwHXN/VNtsXbfzgYOqjKWxEx3OoAzrT3jLwAF0HdX2MEw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] requiresBuild: true optional: true - /@next/swc-linux-x64-musl/12.2.5-canary.4: - resolution: {integrity: sha512-9ASdeW9WIvw3/HC5gxaPvGd9ozNCZE2bPqYYZeAY55dC5JcEQlx66xeCh7Nxz7oRVyGHP1E5b+y598rBQ/HNwQ==} + /@next/swc-linux-x64-musl/12.2.5: + resolution: {integrity: sha512-zg/Y6oBar1yVnW6Il1I/08/2ukWtOG6s3acdJdEyIdsCzyQi4RLxbbhkD/EGQyhqBvd3QrC6ZXQEXighQUAZ0g==} engines: {node: '>= 10'} cpu: [x64] os: [linux] requiresBuild: true optional: true - /@next/swc-win32-arm64-msvc/12.2.5-canary.4: - resolution: {integrity: sha512-CK/EyHxmpRzTm3Yg9CxSdxy9HRDtniQOD907KZ2nrd1cdfGrY7ZJMgGOv6uxdNhHlGWNWYiVOk6RI5yGM8IX5w==} + /@next/swc-win32-arm64-msvc/12.2.5: + resolution: {integrity: sha512-3/90DRNSqeeSRMMEhj4gHHQlLhhKg5SCCoYfE3kBjGpE63EfnblYUqsszGGZ9ekpKL/R4/SGB40iCQr8tR5Jiw==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] requiresBuild: true optional: true - /@next/swc-win32-ia32-msvc/12.2.5-canary.4: - resolution: {integrity: sha512-+Xi/2l61HxlAsex6T28D64XZAfXPfn+UGnXl2iXtzKqYO9rhFO4erbLpMkJXKMJaH5Nn7MnT4nX3hVSl9adQ2A==} + /@next/swc-win32-ia32-msvc/12.2.5: + resolution: {integrity: sha512-hGLc0ZRAwnaPL4ulwpp4D2RxmkHQLuI8CFOEEHdzZpS63/hMVzv81g8jzYA0UXbb9pus/iTc3VRbVbAM03SRrw==} engines: {node: '>= 10'} cpu: [ia32] os: [win32] requiresBuild: true optional: true - /@next/swc-win32-x64-msvc/12.2.5-canary.4: - resolution: {integrity: sha512-VgEYUZYQnSCZMahezzROwPeMu9paH8EvCtAd0koRs7e6Zi67ERpwSCbRnaT+foKha69Re0WzGUrkvTjo1hP7ZQ==} + /@next/swc-win32-x64-msvc/12.2.5: + resolution: {integrity: sha512-7h5/ahY7NeaO2xygqVrSG/Y8Vs4cdjxIjowTZ5W6CKoTKn7tmnuxlUc2h74x06FKmbhAd9agOjr/AOKyxYYm9Q==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -16580,8 +16580,8 @@ packages: resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==} dev: true - /next/12.2.5-canary.4_biqbaboplfbrettd7655fr4n2y: - resolution: {integrity: sha512-RHXm6ZfG5HsLM63E/NgOvl8CT5kUy+RlEdW4rqpF0gSVK5qj8H/E/zXcc1QKwGQy2ndyKwVo2n1s00E1Fy661g==} + /next/12.2.5_4cc5zw5azim2bix77d63le72su: + resolution: {integrity: sha512-tBdjqX5XC/oFs/6gxrZhjmiq90YWizUYU6qOWAfat7zJwrwapJ+BYgX2PmiacunXMaRpeVT4vz5MSPSLgNkrpA==} engines: {node: '>=12.22.0'} hasBin: true peerDependencies: @@ -16598,27 +16598,77 @@ packages: sass: optional: true dependencies: - '@next/env': 12.2.5-canary.4 + '@next/env': 12.2.5 '@swc/helpers': 0.4.3 caniuse-lite: 1.0.30001358 postcss: 8.4.14 react: 18.2.0 react-dom: 18.2.0_react@18.2.0 + styled-jsx: 5.0.4_5wvcx74lvxq2lfpc5x4ihgp2jm use-sync-external-store: 1.2.0_react@18.2.0 optionalDependencies: - '@next/swc-android-arm-eabi': 12.2.5-canary.4 - '@next/swc-android-arm64': 12.2.5-canary.4 - '@next/swc-darwin-arm64': 12.2.5-canary.4 - '@next/swc-darwin-x64': 12.2.5-canary.4 - '@next/swc-freebsd-x64': 12.2.5-canary.4 - '@next/swc-linux-arm-gnueabihf': 12.2.5-canary.4 - '@next/swc-linux-arm64-gnu': 12.2.5-canary.4 - '@next/swc-linux-arm64-musl': 12.2.5-canary.4 - '@next/swc-linux-x64-gnu': 12.2.5-canary.4 - '@next/swc-linux-x64-musl': 12.2.5-canary.4 - '@next/swc-win32-arm64-msvc': 12.2.5-canary.4 - '@next/swc-win32-ia32-msvc': 12.2.5-canary.4 - '@next/swc-win32-x64-msvc': 12.2.5-canary.4 + '@next/swc-android-arm-eabi': 12.2.5 + '@next/swc-android-arm64': 12.2.5 + '@next/swc-darwin-arm64': 12.2.5 + '@next/swc-darwin-x64': 12.2.5 + '@next/swc-freebsd-x64': 12.2.5 + '@next/swc-linux-arm-gnueabihf': 12.2.5 + '@next/swc-linux-arm64-gnu': 12.2.5 + '@next/swc-linux-arm64-musl': 12.2.5 + '@next/swc-linux-x64-gnu': 12.2.5 + '@next/swc-linux-x64-musl': 12.2.5 + '@next/swc-win32-arm64-msvc': 12.2.5 + '@next/swc-win32-ia32-msvc': 12.2.5 + '@next/swc-win32-x64-msvc': 12.2.5 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + dev: true + + /next/12.2.5_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-tBdjqX5XC/oFs/6gxrZhjmiq90YWizUYU6qOWAfat7zJwrwapJ+BYgX2PmiacunXMaRpeVT4vz5MSPSLgNkrpA==} + engines: {node: '>=12.22.0'} + hasBin: true + peerDependencies: + fibers: '>= 3.1.0' + node-sass: ^6.0.0 || ^7.0.0 + react: ^17.0.2 || ^18.0.0-0 + react-dom: ^17.0.2 || ^18.0.0-0 + sass: ^1.3.0 + peerDependenciesMeta: + fibers: + optional: true + node-sass: + optional: true + sass: + optional: true + dependencies: + '@next/env': 12.2.5 + '@swc/helpers': 0.4.3 + caniuse-lite: 1.0.30001358 + postcss: 8.4.14 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + styled-jsx: 5.0.4_react@18.2.0 + use-sync-external-store: 1.2.0_react@18.2.0 + optionalDependencies: + '@next/swc-android-arm-eabi': 12.2.5 + '@next/swc-android-arm64': 12.2.5 + '@next/swc-darwin-arm64': 12.2.5 + '@next/swc-darwin-x64': 12.2.5 + '@next/swc-freebsd-x64': 12.2.5 + '@next/swc-linux-arm-gnueabihf': 12.2.5 + '@next/swc-linux-arm64-gnu': 12.2.5 + '@next/swc-linux-arm64-musl': 12.2.5 + '@next/swc-linux-x64-gnu': 12.2.5 + '@next/swc-linux-x64-musl': 12.2.5 + '@next/swc-win32-arm64-msvc': 12.2.5 + '@next/swc-win32-ia32-msvc': 12.2.5 + '@next/swc-win32-x64-msvc': 12.2.5 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + dev: false /nice-try/1.0.5: resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==} @@ -20209,6 +20259,39 @@ packages: supports-color: 5.5.0 dev: false + /styled-jsx/5.0.4_5wvcx74lvxq2lfpc5x4ihgp2jm: + resolution: {integrity: sha512-sDFWLbg4zR+UkNzfk5lPilyIgtpddfxXEULxhujorr5jtePTUqiPDc5BC0v1NRqTr/WaFBGQQUoYToGlF4B2KQ==} + engines: {node: '>= 12.0.0'} + peerDependencies: + '@babel/core': '*' + babel-plugin-macros: '*' + react: '>= 16.8.0 || 17.x.x || ^18.0.0-0' + peerDependenciesMeta: + '@babel/core': + optional: true + babel-plugin-macros: + optional: true + dependencies: + '@babel/core': 7.18.5 + react: 18.2.0 + dev: true + + /styled-jsx/5.0.4_react@18.2.0: + resolution: {integrity: sha512-sDFWLbg4zR+UkNzfk5lPilyIgtpddfxXEULxhujorr5jtePTUqiPDc5BC0v1NRqTr/WaFBGQQUoYToGlF4B2KQ==} + engines: {node: '>= 12.0.0'} + peerDependencies: + '@babel/core': '*' + babel-plugin-macros: '*' + react: '>= 16.8.0 || 17.x.x || ^18.0.0-0' + peerDependenciesMeta: + '@babel/core': + optional: true + babel-plugin-macros: + optional: true + dependencies: + react: 18.2.0 + dev: false + /stylehacks/5.1.0_postcss@8.4.14: resolution: {integrity: sha512-SzLmvHQTrIWfSgljkQCw2++C9+Ne91d/6Sp92I8c5uHTcy/PgeHamwITIbBW9wnFTY/3ZfSXR9HIL6Ikqmcu6Q==} engines: {node: ^10 || ^12 || >=14.0} From db6b1c9e9668e73042b8fd70c1290bb23a509afe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Fri, 12 Aug 2022 14:19:52 +0200 Subject: [PATCH 07/93] implement body reader --- packages/next-auth/src/core/index.ts | 14 ++++----- packages/next-auth/src/core/lib/spec.ts | 41 +++++++++++++++++-------- packages/next-auth/src/next/utils.ts | 15 +++++++++ 3 files changed, 51 insertions(+), 19 deletions(-) diff --git a/packages/next-auth/src/core/index.ts b/packages/next-auth/src/core/index.ts index 0bd482afe5..37ac95e07a 100644 --- a/packages/next-auth/src/core/index.ts +++ b/packages/next-auth/src/core/index.ts @@ -4,7 +4,7 @@ import renderPage from "./pages" import { init } from "./init" import { assertConfig } from "./lib/assert" import { SessionStore } from "./lib/cookie" -import { fromRequest, toResponse } from "./lib/spec" +import { toInternalRequest, toResponse } from "./lib/spec" import type { NextAuthAction, NextAuthOptions } from "./types" import type { Cookie } from "./lib/cookie" @@ -255,11 +255,11 @@ export async function AuthHandlerInternal< * 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(params: { - req: Request +export async function AuthHandler( + request: Request, options: NextAuthOptions - parsedBody?: any -}): Promise { - const req = await fromRequest(params.req, params.parsedBody) - return toResponse(await AuthHandlerInternal({ ...params, req })) +): Promise { + const req = await toInternalRequest(request) + const internalResponse = await AuthHandlerInternal({ req, options }) + return toResponse(internalResponse) } diff --git a/packages/next-auth/src/core/lib/spec.ts b/packages/next-auth/src/core/lib/spec.ts index a310a39865..bd956af348 100644 --- a/packages/next-auth/src/core/lib/spec.ts +++ b/packages/next-auth/src/core/lib/spec.ts @@ -3,23 +3,41 @@ import { detectHost } from "../../utils/detect-host" import type { InternalResponse, InternalRequest } from ".." import type { NextAuthAction } from "../types" -// TODO: Implement -async function readBody( - body: ReadableStream | null +const decoder = new TextDecoder() + +async function readJSONBody( + body: ReadableStream | Buffer ): Promise | undefined> { - throw new Error("Not implemented") + try { + if (body instanceof ReadableStream) { + const reader = body.getReader() + const bytes: number[] = [] + while (true) { + const { value, done } = await reader.read() + if (done) break + bytes.push(...value) + } + const b = new Uint8Array(bytes) + return JSON.parse(decoder.decode(b)) + } + + // Handle `node-fetch` implementation of `body` + // We expect it to be a JSON.stringify'd object in a `Buffer` + return JSON.parse(body.toString()) + } catch (e) { + console.error(e) + } } -// TODO: -export async function fromRequest(req: Request): Promise { - // TODO: handle custom url - const url = new URL(req.url, "http://localhost:3000") +export async function toInternalRequest( + req: Request +): Promise { + const url = new URL(req.url) const nextauth = url.pathname.split("/").slice(3) const headers = Object.fromEntries(req.headers.entries()) const query: Record = Object.fromEntries( url.searchParams.entries() ) - query.nextauth = nextauth const cookieHeader = req.headers.get("cookie") ?? "" const cookies = @@ -31,7 +49,7 @@ export async function fromRequest(req: Request): Promise { action: nextauth[0] as NextAuthAction, method: req.method, headers, - body: await readBody(req.body), + body: req.body ? await readJSONBody(req.body) : undefined, cookies: cookies, providerId: nextauth[1], error: url.searchParams.get("error") ?? undefined, @@ -59,8 +77,7 @@ export function toResponse(res: InternalResponse): Response { }) const body = - res.headers?.find(({ key }) => key === "Content-Type")?.value === - "application/json" + headers.get("content-type") === "application/json" ? JSON.stringify(res.body) : res.body diff --git a/packages/next-auth/src/next/utils.ts b/packages/next-auth/src/next/utils.ts index 9075bcaaf6..208506b8aa 100644 --- a/packages/next-auth/src/next/utils.ts +++ b/packages/next-auth/src/next/utils.ts @@ -1,3 +1,6 @@ +import type { GetServerSidePropsContext, NextApiRequest } from "next" +import type { NextRequest } from "next/server" + export function setCookie(res, value: string) { // Preserve any existing cookies that have already been set in the same session let setCookieHeader = res.getHeader("Set-Cookie") ?? [] @@ -8,3 +11,15 @@ export function setCookie(res, value: string) { setCookieHeader.push(value) res.setHeader("Set-Cookie", setCookieHeader) } + +export function getBody( + req: NextApiRequest | NextRequest | GetServerSidePropsContext["req"] +) { + if (!("body" in req) || !req.body || req.method !== "POST") { + return + } + return { + body: + req.body instanceof ReadableStream ? req.body : JSON.stringify(req.body), + } +} From fe9c6a5d3f33d2dd321cb0d98b10d4cfe165e61c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Fri, 12 Aug 2022 14:20:41 +0200 Subject: [PATCH 08/93] use `Request`/`Response` in `next-auth/next` --- packages/next-auth/src/next/index.ts | 74 +++++++++++----------------- 1 file changed, 30 insertions(+), 44 deletions(-) diff --git a/packages/next-auth/src/next/index.ts b/packages/next-auth/src/next/index.ts index 78b205e04c..09a557767e 100644 --- a/packages/next-auth/src/next/index.ts +++ b/packages/next-auth/src/next/index.ts @@ -1,7 +1,7 @@ import { serialize } from "cookie" -import { AuthHandlerInternal } from "../core" +import { AuthHandler, AuthHandlerInternal } from "../core" import { detectHost } from "../utils/detect-host" -import { setCookie } from "./utils" +import { setCookie, getBody } from "./utils" import type { GetServerSidePropsContext, @@ -9,60 +9,46 @@ import type { NextApiResponse, } from "next" import type { NextAuthOptions, Session } from ".." -import type { - NextAuthAction, - NextAuthRequest, - NextAuthResponse, -} from "../core/types" +import type { NextAuthRequest, NextAuthResponse } from "../core/types" async function NextAuthHandler( req: NextApiRequest, res: NextApiResponse, options: NextAuthOptions ) { - const { nextauth, ...query } = req.query + options.secret ??= options.jwt?.secret ?? process.env.NEXTAUTH_SECRET - options.secret = - options.secret ?? options.jwt?.secret ?? process.env.NEXTAUTH_SECRET + // TODO: verify (detect host), normalize (full url), fallback (localhost) + const url = new URL(req.url!, "http://localhost:3000") - const handler = await AuthHandlerInternal({ - req: { - host: detectHost(req.headers["x-forwarded-host"]), - body: req.body, - query, - cookies: req.cookies, - headers: req.headers, + const { status, headers, body } = await AuthHandler( + new Request(url, { + headers: new Headers(req.headers as any), method: req.method, - action: nextauth?.[0] as NextAuthAction, - providerId: nextauth?.[1], - error: (req.query.error as string | undefined) ?? nextauth?.[1], - }, - options, - }) - - res.status(handler.status ?? 200) - - handler.cookies?.forEach((cookie) => { - const { name, value, options } = cookie - const cookieHeader = serialize(name, value, options) - setCookie(res, cookieHeader) - }) - - handler.headers?.forEach((h) => res.setHeader(h.key, h.value)) - - if (handler.redirect) { - // If the request expects a return URL, send it as JSON - // instead of doing an actual redirect. - if (req.body?.json !== "true") { - // Could chain. .end() when lowest target is Node 14 - // https://github.com/nodejs/node/issues/33148 - res.status(302).setHeader("Location", handler.redirect) - return res.end() + ...getBody(req), + }), + options + ) + + res.status(status) + + for (const [key, value] of headers.entries()) { + if (key === "set-cookie") { + res.setHeader("set-cookie", value.split(",")) + } else { + res.setHeader(key, value) } - return res.json({ url: handler.redirect }) } - return res.send(handler.body) + // If the request expects a return URL, send it as JSON + // instead of doing an actual redirect. + const redirect = headers.get("Location") + if (req.body?.json === "true" && redirect) { + res.removeHeader("Location") + return res.json({ url: redirect }) + } + + return res.send(body) } export default function NextAuth(options: NextAuthOptions): any From bc9ddaeaa27b91f69cdfb8628aa55b6c7c4146e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Fri, 12 Aug 2022 14:49:28 +0200 Subject: [PATCH 09/93] make linter happy --- apps/dev/pages/api/auth/[...nextauth].ts | 44 ++++++++++++------------ packages/next-auth/src/next/index.ts | 15 ++++---- 2 files changed, 30 insertions(+), 29 deletions(-) diff --git a/apps/dev/pages/api/auth/[...nextauth].ts b/apps/dev/pages/api/auth/[...nextauth].ts index aab7e76f27..f601a39f96 100644 --- a/apps/dev/pages/api/auth/[...nextauth].ts +++ b/apps/dev/pages/api/auth/[...nextauth].ts @@ -39,9 +39,9 @@ import WorkOS from "next-auth/providers/workos" // Adapters import { PrismaClient } from "@prisma/client" import { PrismaAdapter } from "@next-auth/prisma-adapter" -import { Client as FaunaClient } from "faunadb" -import { FaunaAdapter } from "@next-auth/fauna-adapter" -import { TypeORMLegacyAdapter } from "@next-auth/typeorm-legacy-adapter" +// import { Client as FaunaClient } from "faunadb" +// import { FaunaAdapter } from "@next-auth/fauna-adapter" +// import { TypeORMLegacyAdapter } from "@next-auth/typeorm-legacy-adapter" // Add an adapter you want to test here. const adapters = { @@ -50,24 +50,24 @@ const adapters = { if (process.env.NODE_ENV !== "production") globalThis.prisma = client return PrismaAdapter(client) }, - typeorm() { - return TypeORMLegacyAdapter({ - type: "sqlite", - name: "next-auth-test-memory", - database: "./typeorm/dev.db", - synchronize: true, - }) - }, - fauna() { - const client = - globalThis.fauna || - new FaunaClient({ - secret: process.env.FAUNA_SECRET, - domain: process.env.FAUNA_DOMAIN, - }) - if (process.env.NODE_ENV !== "production") global.fauna = client - return FaunaAdapter(client) - }, + // typeorm() { + // return TypeORMLegacyAdapter({ + // type: "sqlite", + // name: "next-auth-test-memory", + // database: "./typeorm/dev.db", + // synchronize: true, + // }) + // }, + // fauna() { + // const client = + // globalThis.fauna || + // new FaunaClient({ + // secret: process.env.FAUNA_SECRET, + // domain: process.env.FAUNA_DOMAIN, + // }) + // if (process.env.NODE_ENV !== "production") global.fauna = client + // return FaunaAdapter(client) + // }, noop() { return undefined }, @@ -75,7 +75,7 @@ const adapters = { export const authOptions: NextAuthOptions = { adapter: adapters.noop(), - debug: true, + // debug: true, theme: { logo: "https://next-auth.js.org/img/logo/logo-sm.png", brandColor: "#1786fb", diff --git a/packages/next-auth/src/next/index.ts b/packages/next-auth/src/next/index.ts index 09a557767e..bc2607c959 100644 --- a/packages/next-auth/src/next/index.ts +++ b/packages/next-auth/src/next/index.ts @@ -19,7 +19,7 @@ async function NextAuthHandler( options.secret ??= options.jwt?.secret ?? process.env.NEXTAUTH_SECRET // TODO: verify (detect host), normalize (full url), fallback (localhost) - const url = new URL(req.url!, "http://localhost:3000") + const url = new URL(req.url ?? "", "http://localhost:3000") const { status, headers, body } = await AuthHandler( new Request(url, { @@ -51,28 +51,29 @@ async function NextAuthHandler( return res.send(body) } -export default function NextAuth(options: NextAuthOptions): any -export default function NextAuth( +function NextAuth(options: NextAuthOptions): any +function NextAuth( req: NextApiRequest, res: NextApiResponse, options: NextAuthOptions ): any /** The main entry point to next-auth */ -export default function NextAuth( +function NextAuth( ...args: | [NextAuthOptions] | [NextApiRequest, NextApiResponse, NextAuthOptions] ) { if (args.length === 1) { - return function handler(req: NextAuthRequest, res: NextAuthResponse) { - return NextAuthHandler(req, res, args[0]) - } + return async (req: NextAuthRequest, res: NextAuthResponse) => + await NextAuthHandler(req, res, args[0]) } return NextAuthHandler(args[0], args[1], args[2]) } +export default NextAuth + let experimentalWarningShown = false export async function unstable_getServerSession( ...args: From a5cd972a559f2b4bc3a99a329c35aa5a16b3dbf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Fri, 12 Aug 2022 14:50:31 +0200 Subject: [PATCH 10/93] revert --- apps/dev/pages/api/auth/[...nextauth].ts | 44 ++++++++++++------------ 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/apps/dev/pages/api/auth/[...nextauth].ts b/apps/dev/pages/api/auth/[...nextauth].ts index f601a39f96..aab7e76f27 100644 --- a/apps/dev/pages/api/auth/[...nextauth].ts +++ b/apps/dev/pages/api/auth/[...nextauth].ts @@ -39,9 +39,9 @@ import WorkOS from "next-auth/providers/workos" // Adapters import { PrismaClient } from "@prisma/client" import { PrismaAdapter } from "@next-auth/prisma-adapter" -// import { Client as FaunaClient } from "faunadb" -// import { FaunaAdapter } from "@next-auth/fauna-adapter" -// import { TypeORMLegacyAdapter } from "@next-auth/typeorm-legacy-adapter" +import { Client as FaunaClient } from "faunadb" +import { FaunaAdapter } from "@next-auth/fauna-adapter" +import { TypeORMLegacyAdapter } from "@next-auth/typeorm-legacy-adapter" // Add an adapter you want to test here. const adapters = { @@ -50,24 +50,24 @@ const adapters = { if (process.env.NODE_ENV !== "production") globalThis.prisma = client return PrismaAdapter(client) }, - // typeorm() { - // return TypeORMLegacyAdapter({ - // type: "sqlite", - // name: "next-auth-test-memory", - // database: "./typeorm/dev.db", - // synchronize: true, - // }) - // }, - // fauna() { - // const client = - // globalThis.fauna || - // new FaunaClient({ - // secret: process.env.FAUNA_SECRET, - // domain: process.env.FAUNA_DOMAIN, - // }) - // if (process.env.NODE_ENV !== "production") global.fauna = client - // return FaunaAdapter(client) - // }, + typeorm() { + return TypeORMLegacyAdapter({ + type: "sqlite", + name: "next-auth-test-memory", + database: "./typeorm/dev.db", + synchronize: true, + }) + }, + fauna() { + const client = + globalThis.fauna || + new FaunaClient({ + secret: process.env.FAUNA_SECRET, + domain: process.env.FAUNA_DOMAIN, + }) + if (process.env.NODE_ENV !== "production") global.fauna = client + return FaunaAdapter(client) + }, noop() { return undefined }, @@ -75,7 +75,7 @@ const adapters = { export const authOptions: NextAuthOptions = { adapter: adapters.noop(), - // debug: true, + debug: true, theme: { logo: "https://next-auth.js.org/img/logo/logo-sm.png", brandColor: "#1786fb", From 2cf11bbc71e84811594912d6df171e179880c6db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Fri, 12 Aug 2022 14:54:04 +0200 Subject: [PATCH 11/93] fix tests --- packages/next-auth/tests/lib.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/next-auth/tests/lib.ts b/packages/next-auth/tests/lib.ts index 47462de566..7f81a8cfcd 100644 --- a/packages/next-auth/tests/lib.ts +++ b/packages/next-auth/tests/lib.ts @@ -1,5 +1,5 @@ import { createHash } from "crypto" -import { NextAuthHandler } from "../src/core" +import { AuthHandler } from "../src/core" import type { LoggerInstance, NextAuthOptions } from "../src" import type { Adapter } from "../src/adapters" @@ -30,9 +30,10 @@ export async function handler( ) const req = new Request(url, { headers: { host: "" }, ...requestInit }) const logger = mockLogger() - const response = await NextAuthHandler({ - req, - options: { secret: "secret", ...options, logger }, + const response = await AuthHandler(req, { + secret: "secret", + ...options, + logger, }) // @ts-ignore if (prod) process.env.NODE_ENV = "test" @@ -62,5 +63,5 @@ export function mockAdapter(): Adapter { return { createVerificationToken: jest.fn(() => {}), getUserByEmail: jest.fn(() => {}), - } as Adapter + } as unknown as Adapter } From 63cdea45066e7d3a52cae4cc820b65057f3f347d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Fri, 12 Aug 2022 16:20:55 +0200 Subject: [PATCH 12/93] remove workaround for middleware return type --- packages/next-auth/src/next/middleware.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/next-auth/src/next/middleware.ts b/packages/next-auth/src/next/middleware.ts index a9efb3ec7e..e4b3481983 100644 --- a/packages/next-auth/src/next/middleware.ts +++ b/packages/next-auth/src/next/middleware.ts @@ -92,14 +92,10 @@ export interface NextAuthMiddlewareOptions { secret?: string } -// TODO: `NextMiddleware` should allow returning `void` -// Simplify when https://github.com/vercel/next.js/pull/38625 is merged. -type NextMiddlewareResult = ReturnType | void // eslint-disable-line @typescript-eslint/no-invalid-void-type - async function handleMiddleware( req: NextRequest, options: NextAuthMiddlewareOptions | undefined, - onSuccess?: (token: JWT | null) => Promise + onSuccess?: (token: JWT | null) => ReturnType ) { const { pathname, search, origin } = req.nextUrl @@ -156,7 +152,7 @@ export interface NextRequestWithAuth extends NextRequest { export type NextMiddlewareWithAuth = ( request: NextRequestWithAuth, event: NextFetchEvent -) => NextMiddlewareResult | Promise +) => ReturnType export type WithAuthArgs = | [NextRequestWithAuth] From 1903e3c616098826db0e64cf399f042ee5553ae8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Wed, 5 Oct 2022 04:02:51 +0200 Subject: [PATCH 13/93] return session in protected api route example --- apps/dev/pages/api/examples/protected.js | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/dev/pages/api/examples/protected.js b/apps/dev/pages/api/examples/protected.js index 5cd08f6190..a3f07930bb 100644 --- a/apps/dev/pages/api/examples/protected.js +++ b/apps/dev/pages/api/examples/protected.js @@ -9,6 +9,7 @@ export default async (req, res) => { res.send({ content: "This is protected content. You can access this content because you are signed in.", + session, }) } else { res.send({ From b63e84334e1ad59a8aaa2ca65591ea3b46283f33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Wed, 5 Oct 2022 04:03:03 +0200 Subject: [PATCH 14/93] don't export internal handler --- packages/next-auth/src/core/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/next-auth/src/core/index.ts b/packages/next-auth/src/core/index.ts index 6074f852ee..661021af03 100644 --- a/packages/next-auth/src/core/index.ts +++ b/packages/next-auth/src/core/index.ts @@ -38,7 +38,7 @@ export interface NextAuthHeader { value: string } -export async function AuthHandlerInternal< +async function AuthHandlerInternal< Body extends string | Record | any[] >(params: { req: InternalRequest @@ -117,6 +117,7 @@ export async function AuthHandlerInternal< 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": From 07ab4f8cd41781abc3fc25a3d4bf4aed7d4ae738 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Wed, 5 Oct 2022 04:03:19 +0200 Subject: [PATCH 15/93] fall back host to localhost --- packages/next-auth/src/utils/detect-host.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/next-auth/src/utils/detect-host.ts b/packages/next-auth/src/utils/detect-host.ts index 0f792f10f9..1dfd54e889 100644 --- a/packages/next-auth/src/utils/detect-host.ts +++ b/packages/next-auth/src/utils/detect-host.ts @@ -1,7 +1,9 @@ -/** Extract the host from the environment */ +const trustedHost = process.env.AUTH_TRUST_HOST ?? process.env.VERCEL + +/** Safely extract the host from the environment */ export function detectHost(forwardedHost: any) { // If we detect a Vercel environment, we can trust the host - if (process.env.VERCEL) return forwardedHost + if (trustedHost) return forwardedHost // If `NEXTAUTH_URL` is `undefined` we fall back to "http://localhost:3000" - return process.env.NEXTAUTH_URL + return process.env.NEXTAUTH_URL ?? "http://localhost:3000" } From 415d299698ceda67745836a007b4cc4292f40f0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Wed, 5 Oct 2022 04:03:32 +0200 Subject: [PATCH 16/93] refactor `getBody` --- packages/next-auth/src/next/utils.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/next-auth/src/next/utils.ts b/packages/next-auth/src/next/utils.ts index 208506b8aa..aad9601283 100644 --- a/packages/next-auth/src/next/utils.ts +++ b/packages/next-auth/src/next/utils.ts @@ -18,8 +18,9 @@ export function getBody( if (!("body" in req) || !req.body || req.method !== "POST") { return } - return { - body: - req.body instanceof ReadableStream ? req.body : JSON.stringify(req.body), + + if (req.body instanceof ReadableStream) { + return { body: req.body } } + return { body: JSON.stringify(req.body) } } From b41bdeb787ed404430c290d0010c023ec9ab5b3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Wed, 5 Oct 2022 04:10:42 +0200 Subject: [PATCH 17/93] refactor `next-auth/next` --- packages/next-auth/src/next/index.ts | 60 ++++++++++++---------------- 1 file changed, 26 insertions(+), 34 deletions(-) diff --git a/packages/next-auth/src/next/index.ts b/packages/next-auth/src/next/index.ts index 91d5d452ff..48936a2c2a 100644 --- a/packages/next-auth/src/next/index.ts +++ b/packages/next-auth/src/next/index.ts @@ -1,7 +1,6 @@ -import { serialize } from "cookie" -import { AuthHandler, AuthHandlerInternal } from "../core" +import { AuthHandler } from "../core" import { detectHost } from "../utils/detect-host" -import { setCookie, getBody } from "./utils" +import { getBody } from "./utils" import type { GetServerSidePropsContext, @@ -18,8 +17,8 @@ async function NextAuthHandler( ) { options.secret ??= options.jwt?.secret ?? process.env.NEXTAUTH_SECRET - // TODO: verify (detect host), normalize (full url), fallback (localhost) - const url = new URL(req.url ?? "", "http://localhost:3000") + const host = detectHost(req.headers["x-forwarded-host"]) + const url = new URL(req.url ?? "", host) const { status, headers, body } = await AuthHandler( new Request(url, { @@ -32,12 +31,9 @@ async function NextAuthHandler( res.status(status) - for (const [key, value] of headers.entries()) { - if (key === "set-cookie") { - res.setHeader("set-cookie", value.split(",")) - } else { - res.setHeader(key, value) - } + for (const [key, val] of headers.entries()) { + const value = key === "set-cookie" ? val.split(",") : val + res.setHeader(key, value) } // If the request expects a return URL, send it as JSON @@ -97,39 +93,35 @@ export async function unstable_getServerSession( const [req, res, options] = args options.secret = options.secret ?? process.env.NEXTAUTH_SECRET + const host = detectHost(req.headers["x-forwarded-host"]) + const url = new URL("/api/auth/session", host) + + const response = await AuthHandler( + new Request(url, { headers: req.headers as any }), + options + ) + + const { status = 200, headers, body } = response - const session = await AuthHandlerInternal({ - options, - req: { - host: detectHost(req.headers["x-forwarded-host"]), - action: "session", - method: "GET", - cookies: req.cookies, - headers: req.headers, - }, - }) - - const { body, cookies, status = 200 } = session - - cookies?.forEach((cookie) => { - const { name, value, options } = cookie - const cookieHeader = serialize(name, value, options) - setCookie(res, cookieHeader) - }) - - if (body && typeof body !== "string" && Object.keys(body).length) { - if (status === 200) return body as Session - throw new Error((body as any).message) + for (const [key, val] of headers.entries()) { + const value = key === "set-cookie" ? val.split(",") : val + res.setHeader(key, value) } - return null + const data = JSON.parse((body as unknown as Buffer).toString()) // REVIEW: response.json() should work (node-fetch?) + + if (!data || !Object.keys(data).length) return null + if (status === 200) return data as Session + throw new Error(data.message) } declare global { // eslint-disable-next-line @typescript-eslint/no-namespace namespace NodeJS { interface ProcessEnv { + AUTH_TRUST_HOST?: string NEXTAUTH_URL?: string + NEXTAUTH_SECRET?: string VERCEL?: "1" } } From bd4d78ba7796c5c142f669f8d08e8410d4c798d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Fri, 7 Oct 2022 00:08:20 +0200 Subject: [PATCH 18/93] chore: add `@edge-runtime/jest-environment` --- packages/next-auth/config/jest.config.js | 6 +- packages/next-auth/config/swc.config.js | 1 + packages/next-auth/package.json | 1 + pnpm-lock.yaml | 109 +++++++++++++++++++++++ 4 files changed, 114 insertions(+), 3 deletions(-) diff --git a/packages/next-auth/config/jest.config.js b/packages/next-auth/config/jest.config.js index 7d6eb84d1c..b1a04892d8 100644 --- a/packages/next-auth/config/jest.config.js +++ b/packages/next-auth/config/jest.config.js @@ -3,13 +3,13 @@ module.exports = { projects: [ { displayName: "core", - testMatch: ["**/*.test.ts"], + testMatch: ["/tests/**/*.test.ts"], rootDir: ".", - setupFilesAfterEnv: ["./config/jest-setup.js"], transform: { - "\\.(js|jsx|ts|tsx)$": ["@swc/jest", require("./swc.config")], + "\\.(ts|tsx)$": "@swc/jest", }, coveragePathIgnorePatterns: ["tests"], + testEnvironment: "@edge-runtime/jest-environment", }, { displayName: "client", diff --git a/packages/next-auth/config/swc.config.js b/packages/next-auth/config/swc.config.js index 1e0e3e9c27..5aeeac2dc1 100644 --- a/packages/next-auth/config/swc.config.js +++ b/packages/next-auth/config/swc.config.js @@ -1,3 +1,4 @@ +/** @type {import("@swc/core").Config} */ module.exports = { jsc: { parser: { diff --git a/packages/next-auth/package.json b/packages/next-auth/package.json index b22d9d4a0c..741277b640 100644 --- a/packages/next-auth/package.json +++ b/packages/next-auth/package.json @@ -96,6 +96,7 @@ "@babel/preset-env": "^7.18.2", "@babel/preset-react": "^7.17.12", "@babel/preset-typescript": "^7.17.12", + "@edge-runtime/jest-environment": "1.1.0-beta.31", "@next-auth/tsconfig": "workspace:*", "@swc/core": "^1.2.198", "@swc/jest": "^0.2.21", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 477444009d..72518dc600 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -409,6 +409,7 @@ importers: '@babel/preset-react': ^7.17.12 '@babel/preset-typescript': ^7.17.12 '@babel/runtime': ^7.16.3 + '@edge-runtime/jest-environment': 1.1.0-beta.31 '@next-auth/tsconfig': workspace:* '@panva/hkdf': ^1.0.1 '@swc/core': ^1.2.198 @@ -465,6 +466,7 @@ importers: '@babel/preset-env': 7.18.2_@babel+core@7.18.5 '@babel/preset-react': 7.17.12_@babel+core@7.18.5 '@babel/preset-typescript': 7.17.12_@babel+core@7.18.5 + '@edge-runtime/jest-environment': 1.1.0-beta.31 '@next-auth/tsconfig': link:../tsconfig '@swc/core': 1.2.204 '@swc/jest': 0.2.21_@swc+core@1.2.204 @@ -4409,6 +4411,27 @@ packages: - webpack-cli dev: false + /@edge-runtime/jest-environment/1.1.0-beta.31: + resolution: {integrity: sha512-a65LVlCMkwkMxg8RjhEY5o3OZFHzMnLZcGqL234h3HO7Ri1Vriorj330BcPSh2GUt4zFqTS/3+XmWE6ueJwAZg==} + dependencies: + '@edge-runtime/vm': 1.1.0-beta.31 + '@jest/environment': 28.1.3 + '@jest/fake-timers': 28.1.3 + '@jest/types': 28.1.3 + jest-mock: 28.1.3 + jest-util: 28.1.3 + dev: true + + /@edge-runtime/primitives/1.1.0-beta.31: + resolution: {integrity: sha512-OO1x32aJoxgME1k77RVxVNsazs5NY/SNwYEN8ptlZ6DKUXn0eesXftDsmlypX/OU0ZeJc61/xNVUuoeyDGJDVA==} + dev: true + + /@edge-runtime/vm/1.1.0-beta.31: + resolution: {integrity: sha512-D3JW32u7LSTt0KbphGWx2F41jId7BY8H0Awr25PTRFWroqohfWo1C2huOh7/Yyn4qeyJOVEuxWeTzvbSkTyyTg==} + dependencies: + '@edge-runtime/primitives': 1.1.0-beta.31 + dev: true + /@emotion/is-prop-valid/0.8.8: resolution: {integrity: sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==} dependencies: @@ -5245,6 +5268,16 @@ packages: jest-mock: 28.1.1 dev: true + /@jest/environment/28.1.3: + resolution: {integrity: sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dependencies: + '@jest/fake-timers': 28.1.3 + '@jest/types': 28.1.3 + '@types/node': 18.0.0 + jest-mock: 28.1.3 + dev: true + /@jest/environment/29.0.3: resolution: {integrity: sha512-iKl272NKxYNQNqXMQandAIwjhQaGw5uJfGXduu8dS9llHi8jV2ChWrtOAVPnMbaaoDhnI3wgUGNDvZgHeEJQCA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -5313,6 +5346,18 @@ packages: jest-util: 28.1.1 dev: true + /@jest/fake-timers/28.1.3: + resolution: {integrity: sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dependencies: + '@jest/types': 28.1.3 + '@sinonjs/fake-timers': 9.1.2 + '@types/node': 18.0.0 + jest-message-util: 28.1.3 + jest-mock: 28.1.3 + jest-util: 28.1.3 + dev: true + /@jest/fake-timers/29.0.3: resolution: {integrity: sha512-tmbUIo03x0TdtcZCESQ0oQSakPCpo7+s6+9mU19dd71MptkP4zCwoeZqna23//pgbhtT1Wq02VmA9Z9cNtvtCQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -5478,6 +5523,13 @@ packages: '@sinclair/typebox': 0.23.5 dev: true + /@jest/schemas/28.1.3: + resolution: {integrity: sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dependencies: + '@sinclair/typebox': 0.24.42 + dev: true + /@jest/schemas/29.0.0: resolution: {integrity: sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -5677,6 +5729,18 @@ packages: chalk: 4.1.2 dev: true + /@jest/types/28.1.3: + resolution: {integrity: sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dependencies: + '@jest/schemas': 28.1.3 + '@types/istanbul-lib-coverage': 2.0.4 + '@types/istanbul-reports': 3.0.1 + '@types/node': 18.0.0 + '@types/yargs': 17.0.10 + chalk: 4.1.2 + dev: true + /@jest/types/29.0.3: resolution: {integrity: sha512-coBJmOQvurXjN1Hh5PzF7cmsod0zLIOXpP8KD161mqNlroMhLcwpODiEzi7ZsRl5Z/AIuxpeNm8DCl43F4kz8A==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -14586,6 +14650,21 @@ packages: stack-utils: 2.0.5 dev: true + /jest-message-util/28.1.3: + resolution: {integrity: sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dependencies: + '@babel/code-frame': 7.16.7 + '@jest/types': 28.1.3 + '@types/stack-utils': 2.0.1 + chalk: 4.1.2 + graceful-fs: 4.2.10 + micromatch: 4.0.5 + pretty-format: 28.1.3 + slash: 3.0.0 + stack-utils: 2.0.5 + dev: true + /jest-message-util/29.0.3: resolution: {integrity: sha512-7T8JiUTtDfppojosORAflABfLsLKMLkBHSWkjNQrjIltGoDzNGn7wEPOSfjqYAGTYME65esQzMJxGDjuLBKdOg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -14617,6 +14696,14 @@ packages: '@types/node': 18.0.0 dev: true + /jest-mock/28.1.3: + resolution: {integrity: sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dependencies: + '@jest/types': 28.1.3 + '@types/node': 18.0.0 + dev: true + /jest-mock/29.0.3: resolution: {integrity: sha512-ort9pYowltbcrCVR43wdlqfAiFJXBx8l4uJDsD8U72LgBcetvEp+Qxj1W9ZYgMRoeAo+ov5cnAGF2B6+Oth+ww==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -15058,6 +15145,18 @@ packages: picomatch: 2.3.1 dev: true + /jest-util/28.1.3: + resolution: {integrity: sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dependencies: + '@jest/types': 28.1.3 + '@types/node': 18.0.0 + chalk: 4.1.2 + ci-info: 3.3.2 + graceful-fs: 4.2.10 + picomatch: 2.3.1 + dev: true + /jest-util/29.0.3: resolution: {integrity: sha512-Q0xaG3YRG8QiTC4R6fHjHQPaPpz9pJBEi0AeOE4mQh/FuWOijFjGXMMOfQEaU9i3z76cNR7FobZZUQnL6IyfdQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -18337,6 +18436,16 @@ packages: react-is: 18.2.0 dev: true + /pretty-format/28.1.3: + resolution: {integrity: sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dependencies: + '@jest/schemas': 28.1.3 + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 18.2.0 + dev: true + /pretty-format/29.0.3: resolution: {integrity: sha512-cHudsvQr1K5vNVLbvYF/nv3Qy/F/BcEKxGuIeMiVMRHxPOO1RxXooP8g/ZrwAp7Dx+KdMZoOc7NxLHhMrP2f9Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} From 1a2e752c13558f9ae7ee12e1ff58a029445807ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Fri, 7 Oct 2022 01:00:25 +0200 Subject: [PATCH 19/93] fix tests, using Node 18 as runtime --- packages/next-auth/config/jest.config.js | 9 ++++---- packages/next-auth/src/next/index.ts | 8 +++---- .../next-auth/tests/getServerSession.test.ts | 6 ++--- packages/next-auth/tests/lib.ts | 23 ++++++++++++------- 4 files changed, 27 insertions(+), 19 deletions(-) diff --git a/packages/next-auth/config/jest.config.js b/packages/next-auth/config/jest.config.js index b1a04892d8..6d0203436d 100644 --- a/packages/next-auth/config/jest.config.js +++ b/packages/next-auth/config/jest.config.js @@ -1,3 +1,4 @@ +const swcConfig = require("./swc.config") /** @type {import('jest').Config} */ module.exports = { projects: [ @@ -6,18 +7,18 @@ module.exports = { testMatch: ["/tests/**/*.test.ts"], rootDir: ".", transform: { - "\\.(ts|tsx)$": "@swc/jest", + "\\.(js|jsx|ts|tsx)$": ["@swc/jest", swcConfig], }, coveragePathIgnorePatterns: ["tests"], - testEnvironment: "@edge-runtime/jest-environment", + // testEnvironment: "@edge-runtime/jest-environment", }, { displayName: "client", - testMatch: ["**/*.test.js"], + testMatch: ["/src/client/**/*.test.js"], setupFilesAfterEnv: ["./config/jest-setup.js"], rootDir: ".", transform: { - "\\.(js|jsx|ts|tsx)$": ["@swc/jest", require("./swc.config")], + "\\.(js|jsx|ts|tsx)$": ["@swc/jest", swcConfig], }, testEnvironment: "jsdom", coveragePathIgnorePatterns: ["__tests__"], diff --git a/packages/next-auth/src/next/index.ts b/packages/next-auth/src/next/index.ts index 48936a2c2a..ff27179800 100644 --- a/packages/next-auth/src/next/index.ts +++ b/packages/next-auth/src/next/index.ts @@ -101,18 +101,18 @@ export async function unstable_getServerSession( options ) - const { status = 200, headers, body } = response + const { status = 200, headers } = response for (const [key, val] of headers.entries()) { const value = key === "set-cookie" ? val.split(",") : val res.setHeader(key, value) } - const data = JSON.parse((body as unknown as Buffer).toString()) // REVIEW: response.json() should work (node-fetch?) + const data = await response.json() if (!data || !Object.keys(data).length) return null - if (status === 200) return data as Session - throw new Error(data.message) + if (status === 200) return data as unknown as Session + throw new Error((data as unknown as Error).message) } declare global { diff --git a/packages/next-auth/tests/getServerSession.test.ts b/packages/next-auth/tests/getServerSession.test.ts index 64c30cdc45..e76966c9a9 100644 --- a/packages/next-auth/tests/getServerSession.test.ts +++ b/packages/next-auth/tests/getServerSession.test.ts @@ -46,7 +46,7 @@ describe("Treat secret correctly", () => { expect(logger.error).not.toBeCalledWith("NO_SECRET") }) - it("Error if missing NEXTAUTH_SECRET and secret", async () => { + it.only("Error if missing NEXTAUTH_SECRET and secret", async () => { const configError = new Error( "There is a problem with the server configuration. Check the server logs for more information." ) @@ -83,7 +83,7 @@ describe("Return correct data", () => { }) it("Should return null if there is no session", async () => { - const spy = jest.spyOn(core, "NextAuthHandler") + const spy = jest.spyOn(core, "AuthHandler") // @ts-expect-error spy.mockReturnValue({ body: {} }) @@ -109,7 +109,7 @@ describe("Return correct data", () => { }, } - const spy = jest.spyOn(core, "NextAuthHandler") + const spy = jest.spyOn(core, "AuthHandler") // @ts-expect-error spy.mockReturnValue(mockedResponse) diff --git a/packages/next-auth/tests/lib.ts b/packages/next-auth/tests/lib.ts index 13ce1bf34e..ec98874190 100644 --- a/packages/next-auth/tests/lib.ts +++ b/packages/next-auth/tests/lib.ts @@ -1,13 +1,15 @@ -import { createHash } from "crypto" +import { createHash } from "node:crypto" import { AuthHandler } from "../src/core" import type { LoggerInstance, NextAuthOptions } from "../src" import type { Adapter } from "../src/adapters" -export const mockLogger: () => LoggerInstance = () => ({ - error: jest.fn(() => {}), - warn: jest.fn(() => {}), - debug: jest.fn(() => {}), -}) +export function mockLogger(): Record { + return { + error: jest.fn(() => {}), + warn: jest.fn(() => {}), + debug: jest.fn(() => {}), + } +} interface HandlerOptions { prod?: boolean @@ -40,9 +42,14 @@ export async function handler( return { res: { - ...response, + status: response.status, + headers: response.headers, + body: response.body, + redirect: response.headers.get("location"), html: - response.headers?.[0].value === "text/html" ? response.body : undefined, + response.headers?.get("content-type") === "text/html" + ? await response.clone().text() + : undefined, }, log: logger, } From 195822ce3da0dd74b6c046a7aabc8615f75404f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Fri, 7 Oct 2022 01:32:23 +0200 Subject: [PATCH 20/93] fix test --- package.json | 7 ++++++- packages/next-auth/config/jest.config.js | 9 ++++++++- ...dge-runtime__jest-environment@1.1.0-beta.31.patch | 12 ++++++++++++ pnpm-lock.yaml | 10 ++++++++-- 4 files changed, 34 insertions(+), 4 deletions(-) create mode 100644 patches/@edge-runtime__jest-environment@1.1.0-beta.31.patch diff --git a/package.json b/package.json index 43a4184cb1..d69ce750c8 100644 --- a/package.json +++ b/package.json @@ -64,5 +64,10 @@ "type": "opencollective", "url": "https://opencollective.com/nextauth" } - ] + ], + "pnpm": { + "patchedDependencies": { + "@edge-runtime/jest-environment@1.1.0-beta.31": "patches/@edge-runtime__jest-environment@1.1.0-beta.31.patch" + } + } } diff --git a/packages/next-auth/config/jest.config.js b/packages/next-auth/config/jest.config.js index 6d0203436d..194f4c9c02 100644 --- a/packages/next-auth/config/jest.config.js +++ b/packages/next-auth/config/jest.config.js @@ -10,7 +10,14 @@ module.exports = { "\\.(js|jsx|ts|tsx)$": ["@swc/jest", swcConfig], }, coveragePathIgnorePatterns: ["tests"], - // testEnvironment: "@edge-runtime/jest-environment", + testEnvironment: "@edge-runtime/jest-environment", + transformIgnorePatterns: ["node_modules/(?!uuid)/"], + // This assumes patching `@edge-runtime/jest-environment`. See `patches` + testEnvironmentOptions: { + codeGeneration: { + strings: true, + }, + }, }, { displayName: "client", diff --git a/patches/@edge-runtime__jest-environment@1.1.0-beta.31.patch b/patches/@edge-runtime__jest-environment@1.1.0-beta.31.patch new file mode 100644 index 0000000000..9d3f689edb --- /dev/null +++ b/patches/@edge-runtime__jest-environment@1.1.0-beta.31.patch @@ -0,0 +1,12 @@ +diff --git a/dist/index.js b/dist/index.js +index 25ee9a86fa8766b172ac25b140d746c38098a59d..1e9ba944f8b573d82d21ddc067bdb738789db04f 100644 +--- a/dist/index.js ++++ b/dist/index.js +@@ -81,6 +81,7 @@ module.exports = class EdgeEnvironment { + context.Buffer = Buffer; + return context; + }, ++ ...(config.projectConfig ?? config)?.testEnvironmentOptions + }); + revealPrimitives(vm); + this.context = vm.context; \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 72518dc600..a7e3c336d5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,5 +1,10 @@ lockfileVersion: 5.4 +patchedDependencies: + '@edge-runtime/jest-environment@1.1.0-beta.31': + hash: jwqh4wrsxmftsmeqsjnafb2dzm + path: patches/@edge-runtime__jest-environment@1.1.0-beta.31.patch + importers: .: @@ -466,7 +471,7 @@ importers: '@babel/preset-env': 7.18.2_@babel+core@7.18.5 '@babel/preset-react': 7.17.12_@babel+core@7.18.5 '@babel/preset-typescript': 7.17.12_@babel+core@7.18.5 - '@edge-runtime/jest-environment': 1.1.0-beta.31 + '@edge-runtime/jest-environment': 1.1.0-beta.31_jwqh4wrsxmftsmeqsjnafb2dzm '@next-auth/tsconfig': link:../tsconfig '@swc/core': 1.2.204 '@swc/jest': 0.2.21_@swc+core@1.2.204 @@ -4411,7 +4416,7 @@ packages: - webpack-cli dev: false - /@edge-runtime/jest-environment/1.1.0-beta.31: + /@edge-runtime/jest-environment/1.1.0-beta.31_jwqh4wrsxmftsmeqsjnafb2dzm: resolution: {integrity: sha512-a65LVlCMkwkMxg8RjhEY5o3OZFHzMnLZcGqL234h3HO7Ri1Vriorj330BcPSh2GUt4zFqTS/3+XmWE6ueJwAZg==} dependencies: '@edge-runtime/vm': 1.1.0-beta.31 @@ -4421,6 +4426,7 @@ packages: jest-mock: 28.1.3 jest-util: 28.1.3 dev: true + patched: true /@edge-runtime/primitives/1.1.0-beta.31: resolution: {integrity: sha512-OO1x32aJoxgME1k77RVxVNsazs5NY/SNwYEN8ptlZ6DKUXn0eesXftDsmlypX/OU0ZeJc61/xNVUuoeyDGJDVA==} From 57a7d3f4f8b88996b8890a7cbe9eef438b099c91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Fri, 7 Oct 2022 13:32:17 +0200 Subject: [PATCH 21/93] remove patch --- package.json | 7 +- packages/next-auth/config/jest.config.js | 3 +- packages/next-auth/package.json | 3 +- ...time__jest-environment@1.1.0-beta.31.patch | 12 -- pnpm-lock.yaml | 187 ++++++++---------- 5 files changed, 89 insertions(+), 123 deletions(-) delete mode 100644 patches/@edge-runtime__jest-environment@1.1.0-beta.31.patch diff --git a/package.json b/package.json index d69ce750c8..43a4184cb1 100644 --- a/package.json +++ b/package.json @@ -64,10 +64,5 @@ "type": "opencollective", "url": "https://opencollective.com/nextauth" } - ], - "pnpm": { - "patchedDependencies": { - "@edge-runtime/jest-environment@1.1.0-beta.31": "patches/@edge-runtime__jest-environment@1.1.0-beta.31.patch" - } - } + ] } diff --git a/packages/next-auth/config/jest.config.js b/packages/next-auth/config/jest.config.js index 194f4c9c02..951c929dad 100644 --- a/packages/next-auth/config/jest.config.js +++ b/packages/next-auth/config/jest.config.js @@ -1,4 +1,5 @@ const swcConfig = require("./swc.config") + /** @type {import('jest').Config} */ module.exports = { projects: [ @@ -12,7 +13,7 @@ module.exports = { coveragePathIgnorePatterns: ["tests"], testEnvironment: "@edge-runtime/jest-environment", transformIgnorePatterns: ["node_modules/(?!uuid)/"], - // This assumes patching `@edge-runtime/jest-environment`. See `patches` + /** @type {import("@edge-runtime/vm").EdgeVMOptions} */ testEnvironmentOptions: { codeGeneration: { strings: true, diff --git a/packages/next-auth/package.json b/packages/next-auth/package.json index 741277b640..9116341b57 100644 --- a/packages/next-auth/package.json +++ b/packages/next-auth/package.json @@ -96,7 +96,8 @@ "@babel/preset-env": "^7.18.2", "@babel/preset-react": "^7.17.12", "@babel/preset-typescript": "^7.17.12", - "@edge-runtime/jest-environment": "1.1.0-beta.31", + "@edge-runtime/jest-environment": "1.1.0-beta.35", + "@edge-runtime/vm": "1.1.0-beta.36", "@next-auth/tsconfig": "workspace:*", "@swc/core": "^1.2.198", "@swc/jest": "^0.2.21", diff --git a/patches/@edge-runtime__jest-environment@1.1.0-beta.31.patch b/patches/@edge-runtime__jest-environment@1.1.0-beta.31.patch deleted file mode 100644 index 9d3f689edb..0000000000 --- a/patches/@edge-runtime__jest-environment@1.1.0-beta.31.patch +++ /dev/null @@ -1,12 +0,0 @@ -diff --git a/dist/index.js b/dist/index.js -index 25ee9a86fa8766b172ac25b140d746c38098a59d..1e9ba944f8b573d82d21ddc067bdb738789db04f 100644 ---- a/dist/index.js -+++ b/dist/index.js -@@ -81,6 +81,7 @@ module.exports = class EdgeEnvironment { - context.Buffer = Buffer; - return context; - }, -+ ...(config.projectConfig ?? config)?.testEnvironmentOptions - }); - revealPrimitives(vm); - this.context = vm.context; \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a7e3c336d5..33e8d81155 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,10 +1,5 @@ lockfileVersion: 5.4 -patchedDependencies: - '@edge-runtime/jest-environment@1.1.0-beta.31': - hash: jwqh4wrsxmftsmeqsjnafb2dzm - path: patches/@edge-runtime__jest-environment@1.1.0-beta.31.patch - importers: .: @@ -414,7 +409,8 @@ importers: '@babel/preset-react': ^7.17.12 '@babel/preset-typescript': ^7.17.12 '@babel/runtime': ^7.16.3 - '@edge-runtime/jest-environment': 1.1.0-beta.31 + '@edge-runtime/jest-environment': 1.1.0-beta.35 + '@edge-runtime/vm': 1.1.0-beta.36 '@next-auth/tsconfig': workspace:* '@panva/hkdf': ^1.0.1 '@swc/core': ^1.2.198 @@ -471,7 +467,8 @@ importers: '@babel/preset-env': 7.18.2_@babel+core@7.18.5 '@babel/preset-react': 7.17.12_@babel+core@7.18.5 '@babel/preset-typescript': 7.17.12_@babel+core@7.18.5 - '@edge-runtime/jest-environment': 1.1.0-beta.31_jwqh4wrsxmftsmeqsjnafb2dzm + '@edge-runtime/jest-environment': 1.1.0-beta.35 + '@edge-runtime/vm': 1.1.0-beta.36 '@next-auth/tsconfig': link:../tsconfig '@swc/core': 1.2.204 '@swc/jest': 0.2.21_@swc+core@1.2.204 @@ -4416,26 +4413,25 @@ packages: - webpack-cli dev: false - /@edge-runtime/jest-environment/1.1.0-beta.31_jwqh4wrsxmftsmeqsjnafb2dzm: - resolution: {integrity: sha512-a65LVlCMkwkMxg8RjhEY5o3OZFHzMnLZcGqL234h3HO7Ri1Vriorj330BcPSh2GUt4zFqTS/3+XmWE6ueJwAZg==} + /@edge-runtime/jest-environment/1.1.0-beta.35: + resolution: {integrity: sha512-CBMGtry4W/BLtmWpyHcbOwY+nn96YOEUVUWhob1SPQURGsxGnFU5r2fxrd99TDy/oTujw5jAbK4syOyUbna4KQ==} dependencies: - '@edge-runtime/vm': 1.1.0-beta.31 + '@edge-runtime/vm': 1.1.0-beta.36 '@jest/environment': 28.1.3 '@jest/fake-timers': 28.1.3 '@jest/types': 28.1.3 jest-mock: 28.1.3 jest-util: 28.1.3 dev: true - patched: true - /@edge-runtime/primitives/1.1.0-beta.31: - resolution: {integrity: sha512-OO1x32aJoxgME1k77RVxVNsazs5NY/SNwYEN8ptlZ6DKUXn0eesXftDsmlypX/OU0ZeJc61/xNVUuoeyDGJDVA==} + /@edge-runtime/primitives/1.1.0-beta.36: + resolution: {integrity: sha512-Tji7SGWmn1+JGSnzFtWUoS7+kODIFprTyIAw0EBOVWEQKWfs7r0aTEm1XkJR0+d1jP9f0GB5LBKG/Z7KFyhx7g==} dev: true - /@edge-runtime/vm/1.1.0-beta.31: - resolution: {integrity: sha512-D3JW32u7LSTt0KbphGWx2F41jId7BY8H0Awr25PTRFWroqohfWo1C2huOh7/Yyn4qeyJOVEuxWeTzvbSkTyyTg==} + /@edge-runtime/vm/1.1.0-beta.36: + resolution: {integrity: sha512-uPZmL7X+lKBFJsTg8nC0qPDBx4JGgpRqlgJi2s77g2NOtqitQOI90BfIKHZSSoMQEwTqfvAkpu2ui8nazOwHxA==} dependencies: - '@edge-runtime/primitives': 1.1.0-beta.31 + '@edge-runtime/primitives': 1.1.0-beta.36 dev: true /@emotion/is-prop-valid/0.8.8: @@ -5097,11 +5093,11 @@ packages: resolution: {integrity: sha512-0RiUocPVFEm3WRMOStIHbRWllG6iW6E3/gUPnf4lkrVFyXIIDeCe+vlKeYyFOMhB2EPE6FLFCNADSOOQMaqvyA==} engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: - '@jest/types': 28.1.1 + '@jest/types': 28.1.3 '@types/node': 18.0.0 chalk: 4.1.2 - jest-message-util: 28.1.1 - jest-util: 28.1.1 + jest-message-util: 28.1.3 + jest-util: 28.1.3 slash: 3.0.0 dev: true @@ -5175,7 +5171,7 @@ packages: '@jest/reporters': 28.1.1 '@jest/test-result': 28.1.1 '@jest/transform': 28.1.1 - '@jest/types': 28.1.1 + '@jest/types': 28.1.3 '@types/node': 18.0.0 ansi-escapes: 4.3.2 chalk: 4.1.2 @@ -5185,18 +5181,18 @@ packages: jest-changed-files: 28.0.2 jest-config: 28.1.1_@types+node@18.0.0 jest-haste-map: 28.1.1 - jest-message-util: 28.1.1 + jest-message-util: 28.1.3 jest-regex-util: 28.0.2 jest-resolve: 28.1.1 jest-resolve-dependencies: 28.1.1 jest-runner: 28.1.1 jest-runtime: 28.1.1 jest-snapshot: 28.1.1 - jest-util: 28.1.1 + jest-util: 28.1.3 jest-validate: 28.1.1 jest-watcher: 28.1.1 micromatch: 4.0.5 - pretty-format: 28.1.1 + pretty-format: 28.1.3 rimraf: 3.0.2 slash: 3.0.0 strip-ansi: 6.0.1 @@ -5268,10 +5264,10 @@ packages: resolution: {integrity: sha512-9auVQ2GzQ7nrU+lAr8KyY838YahElTX9HVjbQPPS2XjlxQ+na18G113OoBhyBGBtD6ZnO/SrUy5WR8EzOj1/Uw==} engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: - '@jest/fake-timers': 28.1.1 - '@jest/types': 28.1.1 + '@jest/fake-timers': 28.1.3 + '@jest/types': 28.1.3 '@types/node': 18.0.0 - jest-mock: 28.1.1 + jest-mock: 28.1.3 dev: true /@jest/environment/28.1.3: @@ -5344,12 +5340,12 @@ packages: resolution: {integrity: sha512-BY/3+TyLs5+q87rGWrGUY5f8e8uC3LsVHS9Diz8+FV3ARXL4sNnkLlIB8dvDvRrp+LUCGM+DLqlsYubizGUjIA==} engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: - '@jest/types': 28.1.1 + '@jest/types': 28.1.3 '@sinonjs/fake-timers': 9.1.2 '@types/node': 18.0.0 - jest-message-util: 28.1.1 - jest-mock: 28.1.1 - jest-util: 28.1.1 + jest-message-util: 28.1.3 + jest-mock: 28.1.3 + jest-util: 28.1.3 dev: true /@jest/fake-timers/28.1.3: @@ -5389,9 +5385,9 @@ packages: resolution: {integrity: sha512-dEgl/6v7ToB4vXItdvcltJBgny0xBE6xy6IYQrPJAJggdEinGxCDMivNv7sFzPcTITGquXD6UJwYxfJ/5ZwDSg==} engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: - '@jest/environment': 28.1.1 + '@jest/environment': 28.1.3 '@jest/expect': 28.1.1 - '@jest/types': 28.1.1 + '@jest/types': 28.1.3 transitivePeerDependencies: - supports-color dev: true @@ -5459,7 +5455,7 @@ packages: '@jest/console': 28.1.1 '@jest/test-result': 28.1.1 '@jest/transform': 28.1.1 - '@jest/types': 28.1.1 + '@jest/types': 28.1.3 '@jridgewell/trace-mapping': 0.3.15 '@types/node': 18.0.0 chalk: 4.1.2 @@ -5472,8 +5468,8 @@ packages: istanbul-lib-report: 3.0.0 istanbul-lib-source-maps: 4.0.1 istanbul-reports: 3.1.4 - jest-message-util: 28.1.1 - jest-util: 28.1.1 + jest-message-util: 28.1.3 + jest-util: 28.1.3 jest-worker: 28.1.1 slash: 3.0.0 string-length: 4.0.2 @@ -5585,7 +5581,7 @@ packages: engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: '@jest/console': 28.1.1 - '@jest/types': 28.1.1 + '@jest/types': 28.1.3 '@types/istanbul-lib-coverage': 2.0.4 collect-v8-coverage: 1.0.1 dev: true @@ -5660,7 +5656,7 @@ packages: engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: '@babel/core': 7.18.5 - '@jest/types': 28.1.1 + '@jest/types': 28.1.3 '@jridgewell/trace-mapping': 0.3.15 babel-plugin-istanbul: 6.1.1 chalk: 4.1.2 @@ -5669,7 +5665,7 @@ packages: graceful-fs: 4.2.10 jest-haste-map: 28.1.1 jest-regex-util: 28.0.2 - jest-util: 28.1.1 + jest-util: 28.1.3 micromatch: 4.0.5 pirates: 4.0.5 slash: 3.0.0 @@ -5727,7 +5723,7 @@ packages: resolution: {integrity: sha512-vRXVqSg1VhDnB8bWcmvLzmg0Bt9CRKVgHPXqYwvWMX3TvAjeO+nRuK6+VdTKCtWOvYlmkF/HqNAL/z+N3B53Kw==} engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: - '@jest/schemas': 28.0.2 + '@jest/schemas': 28.1.3 '@types/istanbul-lib-coverage': 2.0.4 '@types/istanbul-reports': 3.0.1 '@types/node': 18.0.0 @@ -11696,8 +11692,8 @@ packages: '@jest/expect-utils': 28.1.1 jest-get-type: 28.0.2 jest-matcher-utils: 28.1.1 - jest-message-util: 28.1.1 - jest-util: 28.1.1 + jest-message-util: 28.1.3 + jest-util: 28.1.3 dev: true /expect/29.0.3: @@ -13971,10 +13967,10 @@ packages: resolution: {integrity: sha512-75+BBVTsL4+p2w198DQpCeyh1RdaS2lhEG87HkaFX/UG0gJExVq2skG2pT7XZEGBubNj2CytcWSPan4QEPNosw==} engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: - '@jest/environment': 28.1.1 + '@jest/environment': 28.1.3 '@jest/expect': 28.1.1 '@jest/test-result': 28.1.1 - '@jest/types': 28.1.1 + '@jest/types': 28.1.3 '@types/node': 18.0.0 chalk: 4.1.2 co: 4.6.0 @@ -13982,11 +13978,11 @@ packages: is-generator-fn: 2.1.0 jest-each: 28.1.1 jest-matcher-utils: 28.1.1 - jest-message-util: 28.1.1 + jest-message-util: 28.1.3 jest-runtime: 28.1.1 jest-snapshot: 28.1.1 - jest-util: 28.1.1 - pretty-format: 28.1.1 + jest-util: 28.1.3 + pretty-format: 28.1.3 slash: 3.0.0 stack-utils: 2.0.5 throat: 6.0.1 @@ -14063,13 +14059,13 @@ packages: dependencies: '@jest/core': 28.1.1 '@jest/test-result': 28.1.1 - '@jest/types': 28.1.1 + '@jest/types': 28.1.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.10 import-local: 3.1.0 jest-config: 28.1.1_@types+node@17.0.45 - jest-util: 28.1.1 + jest-util: 28.1.3 jest-validate: 28.1.1 prompts: 2.4.2 yargs: 17.5.1 @@ -14161,7 +14157,7 @@ packages: dependencies: '@babel/core': 7.18.5 '@jest/test-sequencer': 28.1.1 - '@jest/types': 28.1.1 + '@jest/types': 28.1.3 '@types/node': 17.0.45 babel-jest: 28.1.1_@babel+core@7.18.5 chalk: 4.1.2 @@ -14175,11 +14171,11 @@ packages: jest-regex-util: 28.0.2 jest-resolve: 28.1.1 jest-runner: 28.1.1 - jest-util: 28.1.1 + jest-util: 28.1.3 jest-validate: 28.1.1 micromatch: 4.0.5 parse-json: 5.2.0 - pretty-format: 28.1.1 + pretty-format: 28.1.3 slash: 3.0.0 strip-json-comments: 3.1.1 transitivePeerDependencies: @@ -14200,7 +14196,7 @@ packages: dependencies: '@babel/core': 7.18.5 '@jest/test-sequencer': 28.1.1 - '@jest/types': 28.1.1 + '@jest/types': 28.1.3 '@types/node': 18.0.0 babel-jest: 28.1.1_@babel+core@7.18.5 chalk: 4.1.2 @@ -14214,11 +14210,11 @@ packages: jest-regex-util: 28.0.2 jest-resolve: 28.1.1 jest-runner: 28.1.1 - jest-util: 28.1.1 + jest-util: 28.1.3 jest-validate: 28.1.1 micromatch: 4.0.5 parse-json: 5.2.0 - pretty-format: 28.1.1 + pretty-format: 28.1.3 slash: 3.0.0 strip-json-comments: 3.1.1 transitivePeerDependencies: @@ -14378,11 +14374,11 @@ packages: resolution: {integrity: sha512-A042rqh17ZvEhRceDMi784ppoXR7MWGDEKTXEZXb4svt0eShMZvijGxzKsx+yIjeE8QYmHPrnHiTSQVhN4nqaw==} engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: - '@jest/types': 28.1.1 + '@jest/types': 28.1.3 chalk: 4.1.2 jest-get-type: 28.0.2 - jest-util: 28.1.1 - pretty-format: 28.1.1 + jest-util: 28.1.3 + pretty-format: 28.1.3 dev: true /jest-each/29.0.3: @@ -14449,12 +14445,12 @@ packages: resolution: {integrity: sha512-2aV/eeY/WNgUUJrrkDJ3cFEigjC5fqT1+fCclrY6paqJ5zVPoM//sHmfgUUp7WLYxIdbPwMiVIzejpN56MxnNA==} engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: - '@jest/environment': 28.1.1 - '@jest/fake-timers': 28.1.1 - '@jest/types': 28.1.1 + '@jest/environment': 28.1.3 + '@jest/fake-timers': 28.1.3 + '@jest/types': 28.1.3 '@types/node': 18.0.0 - jest-mock: 28.1.1 - jest-util: 28.1.1 + jest-mock: 28.1.3 + jest-util: 28.1.3 dev: true /jest-environment-node/29.0.3: @@ -14513,14 +14509,14 @@ packages: resolution: {integrity: sha512-ZrRSE2o3Ezh7sb1KmeLEZRZ4mgufbrMwolcFHNRSjKZhpLa8TdooXOOFlSwoUzlbVs1t0l7upVRW2K7RWGHzbQ==} engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: - '@jest/types': 28.1.1 + '@jest/types': 28.1.3 '@types/graceful-fs': 4.1.5 '@types/node': 18.0.0 anymatch: 3.1.2 fb-watchman: 2.0.1 graceful-fs: 4.2.10 jest-regex-util: 28.0.2 - jest-util: 28.1.1 + jest-util: 28.1.3 jest-worker: 28.1.1 micromatch: 4.0.5 walker: 1.0.8 @@ -14585,7 +14581,7 @@ packages: engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: jest-get-type: 28.0.2 - pretty-format: 28.1.1 + pretty-format: 28.1.3 dev: true /jest-leak-detector/29.0.3: @@ -14641,21 +14637,6 @@ packages: stack-utils: 2.0.5 dev: true - /jest-message-util/28.1.1: - resolution: {integrity: sha512-xoDOOT66fLfmTRiqkoLIU7v42mal/SqwDKvfmfiWAdJMSJiU+ozgluO7KbvoAgiwIrrGZsV7viETjc8GNrA/IQ==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} - dependencies: - '@babel/code-frame': 7.16.7 - '@jest/types': 28.1.1 - '@types/stack-utils': 2.0.1 - chalk: 4.1.2 - graceful-fs: 4.2.10 - micromatch: 4.0.5 - pretty-format: 28.1.1 - slash: 3.0.0 - stack-utils: 2.0.5 - dev: true - /jest-message-util/28.1.3: resolution: {integrity: sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==} engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} @@ -14698,7 +14679,7 @@ packages: resolution: {integrity: sha512-bDCb0FjfsmKweAvE09dZT59IMkzgN0fYBH6t5S45NoJfd2DHkS3ySG2K+hucortryhO3fVuXdlxWcbtIuV/Skw==} engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: - '@jest/types': 28.1.1 + '@jest/types': 28.1.3 '@types/node': 18.0.0 dev: true @@ -14824,7 +14805,7 @@ packages: graceful-fs: 4.2.10 jest-haste-map: 28.1.1 jest-pnp-resolver: 1.2.2_jest-resolve@28.1.1 - jest-util: 28.1.1 + jest-util: 28.1.3 jest-validate: 28.1.1 resolve: 1.22.1 resolve.exports: 1.1.0 @@ -14883,10 +14864,10 @@ packages: engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: '@jest/console': 28.1.1 - '@jest/environment': 28.1.1 + '@jest/environment': 28.1.3 '@jest/test-result': 28.1.1 '@jest/transform': 28.1.1 - '@jest/types': 28.1.1 + '@jest/types': 28.1.3 '@types/node': 18.0.0 chalk: 4.1.2 emittery: 0.10.2 @@ -14895,10 +14876,10 @@ packages: jest-environment-node: 28.1.1 jest-haste-map: 28.1.1 jest-leak-detector: 28.1.1 - jest-message-util: 28.1.1 + jest-message-util: 28.1.3 jest-resolve: 28.1.1 jest-runtime: 28.1.1 - jest-util: 28.1.1 + jest-util: 28.1.3 jest-watcher: 28.1.1 jest-worker: 28.1.1 source-map-support: 0.5.13 @@ -14970,13 +14951,13 @@ packages: resolution: {integrity: sha512-J89qEJWW0leOsqyi0D9zHpFEYHwwafFdS9xgvhFHtIdRghbadodI0eA+DrthK/1PebBv3Px8mFSMGKrtaVnleg==} engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: - '@jest/environment': 28.1.1 - '@jest/fake-timers': 28.1.1 + '@jest/environment': 28.1.3 + '@jest/fake-timers': 28.1.3 '@jest/globals': 28.1.1 '@jest/source-map': 28.0.2 '@jest/test-result': 28.1.1 '@jest/transform': 28.1.1 - '@jest/types': 28.1.1 + '@jest/types': 28.1.3 chalk: 4.1.2 cjs-module-lexer: 1.2.2 collect-v8-coverage: 1.0.1 @@ -14984,12 +14965,12 @@ packages: glob: 7.2.3 graceful-fs: 4.2.10 jest-haste-map: 28.1.1 - jest-message-util: 28.1.1 - jest-mock: 28.1.1 + jest-message-util: 28.1.3 + jest-mock: 28.1.3 jest-regex-util: 28.0.2 jest-resolve: 28.1.1 jest-snapshot: 28.1.1 - jest-util: 28.1.1 + jest-util: 28.1.3 slash: 3.0.0 strip-bom: 4.0.0 transitivePeerDependencies: @@ -15075,7 +15056,7 @@ packages: '@babel/types': 7.18.4 '@jest/expect-utils': 28.1.1 '@jest/transform': 28.1.1 - '@jest/types': 28.1.1 + '@jest/types': 28.1.3 '@types/babel__traverse': 7.17.1 '@types/prettier': 2.6.3 babel-preset-current-node-syntax: 1.0.1_@babel+core@7.18.5 @@ -15086,10 +15067,10 @@ packages: jest-get-type: 28.0.2 jest-haste-map: 28.1.1 jest-matcher-utils: 28.1.1 - jest-message-util: 28.1.1 - jest-util: 28.1.1 + jest-message-util: 28.1.3 + jest-util: 28.1.3 natural-compare: 1.4.0 - pretty-format: 28.1.1 + pretty-format: 28.1.3 semver: 7.3.7 transitivePeerDependencies: - supports-color @@ -15143,7 +15124,7 @@ packages: resolution: {integrity: sha512-FktOu7ca1DZSyhPAxgxB6hfh2+9zMoJ7aEQA759Z6p45NuO8mWcqujH+UdHlCm/V6JTWwDztM2ITCzU1ijJAfw==} engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: - '@jest/types': 28.1.1 + '@jest/types': 28.1.3 '@types/node': 18.0.0 chalk: 4.1.2 ci-info: 3.3.2 @@ -15191,12 +15172,12 @@ packages: resolution: {integrity: sha512-Kpf6gcClqFCIZ4ti5++XemYJWUPCFUW+N2gknn+KgnDf549iLul3cBuKVe1YcWRlaF8tZV8eJCap0eECOEE3Ug==} engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: - '@jest/types': 28.1.1 + '@jest/types': 28.1.3 camelcase: 6.3.0 chalk: 4.1.2 jest-get-type: 28.0.2 leven: 3.1.0 - pretty-format: 28.1.1 + pretty-format: 28.1.3 dev: true /jest-validate/29.0.3: @@ -15245,12 +15226,12 @@ packages: engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: '@jest/test-result': 28.1.1 - '@jest/types': 28.1.1 + '@jest/types': 28.1.3 '@types/node': 18.0.0 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.10.2 - jest-util: 28.1.1 + jest-util: 28.1.3 string-length: 4.0.2 dev: true @@ -15259,12 +15240,12 @@ packages: engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: '@jest/test-result': 28.1.1 - '@jest/types': 28.1.1 + '@jest/types': 28.1.3 '@types/node': 18.0.0 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.10.2 - jest-util: 28.1.1 + jest-util: 28.1.3 string-length: 4.0.2 dev: true From 3d1453682dcd9cfb47d6ecc0854148193f7558b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Sun, 9 Oct 2022 01:33:43 +0200 Subject: [PATCH 22/93] upgrade/add dependencies --- package.json | 4 +- packages/next-auth/package.json | 5 ++ pnpm-lock.yaml | 145 ++++++++++++++++++++++---------- 3 files changed, 108 insertions(+), 46 deletions(-) diff --git a/package.json b/package.json index 43a4184cb1..c7db6d371d 100644 --- a/package.json +++ b/package.json @@ -31,13 +31,13 @@ "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^6.0.0", "husky": "^7.0.4", - "prettier": "2.4.1", + "prettier": "2.7.1", "pretty-quick": "^3.1.2", "semver": "7.3.5", "stream-to-array": "2.3.0", "ts-node": "10.5.0", "turbo": "1.3.1", - "typescript": "4.7.4" + "typescript": "4.8.4" }, "engines": { "node": "^12.19.0 || ^14.15.0 || ^16.13.0" diff --git a/packages/next-auth/package.json b/packages/next-auth/package.json index 9116341b57..02a2431664 100644 --- a/packages/next-auth/package.json +++ b/packages/next-auth/package.json @@ -78,12 +78,16 @@ "uuid": "^8.3.2" }, "peerDependencies": { + "@panva/oauth4webapi": "1.2.0", "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 } @@ -99,6 +103,7 @@ "@edge-runtime/jest-environment": "1.1.0-beta.35", "@edge-runtime/vm": "1.1.0-beta.36", "@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", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 33e8d81155..168914d7bd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18,35 +18,35 @@ importers: eslint-plugin-node: ^11.1.0 eslint-plugin-promise: ^6.0.0 husky: ^7.0.4 - prettier: 2.4.1 + prettier: 2.7.1 pretty-quick: ^3.1.2 semver: 7.3.5 stream-to-array: 2.3.0 ts-node: 10.5.0 turbo: 1.3.1 - typescript: 4.7.4 + typescript: 4.8.4 devDependencies: '@actions/core': 1.9.0 '@balazsorban/monorepo-release': 0.0.5 '@types/jest': 28.1.3 '@types/node': 17.0.45 - '@typescript-eslint/eslint-plugin': 5.29.0_3ekaj7j3owlolnuhj3ykrb7u7i - '@typescript-eslint/parser': 4.33.0_hxadhbs2xogijvk7vq4t2azzbu + '@typescript-eslint/eslint-plugin': 5.29.0_k4l66av2tbo6kxzw52jzgbfzii + '@typescript-eslint/parser': 4.33.0_3rubbgt5ekhqrcgx4uwls3neim eslint: 7.32.0 eslint-config-prettier: 8.5.0_eslint@7.32.0 - eslint-config-standard-with-typescript: 21.0.1_wku6qe4ns7jbrowxtfd54afziq + eslint-config-standard-with-typescript: 21.0.1_lmp6wodjnmju6ozb2nvqbelp44 eslint-plugin-import: 2.26.0_ffi3uiz42rv3jyhs6cr7p7qqry - eslint-plugin-jest: 27.0.2_vibe533nrfhlkvcegtsn4treva + eslint-plugin-jest: 27.0.2_taf6alw3cl62tudcjy32ivuzqe eslint-plugin-node: 11.1.0_eslint@7.32.0 eslint-plugin-promise: 6.0.0_eslint@7.32.0 husky: 7.0.4 - prettier: 2.4.1 - pretty-quick: 3.1.3_prettier@2.4.1 + prettier: 2.7.1 + pretty-quick: 3.1.3_prettier@2.7.1 semver: 7.3.5 stream-to-array: 2.3.0 - ts-node: 10.5.0_x2utdhayajzrh747hktprshhby + ts-node: 10.5.0_ksn4eycaeggbrckn3ykh37hwf4 turbo: 1.3.1 - typescript: 4.7.4 + typescript: 4.8.4 apps/dev: specifiers: @@ -413,6 +413,7 @@ importers: '@edge-runtime/vm': 1.1.0-beta.36 '@next-auth/tsconfig': workspace:* '@panva/hkdf': ^1.0.1 + '@panva/oauth4webapi': 1.2.0 '@swc/core': ^1.2.198 '@swc/jest': ^0.2.21 '@testing-library/dom': ^8.13.0 @@ -470,6 +471,7 @@ importers: '@edge-runtime/jest-environment': 1.1.0-beta.35 '@edge-runtime/vm': 1.1.0-beta.36 '@next-auth/tsconfig': link:../tsconfig + '@panva/oauth4webapi': 1.2.0 '@swc/core': 1.2.204 '@swc/jest': 0.2.21_@swc+core@1.2.204 '@testing-library/dom': 8.14.0 @@ -6196,6 +6198,10 @@ packages: resolution: {integrity: sha512-MSAs9t3Go7GUkMhpKC44T58DJ5KGk2vBo+h1cqQeqlMfdGkxaVB78ZWpv9gYi/g2fa4sopag9gJsNvS8XGgWJA==} dev: false + /@panva/oauth4webapi/1.2.0: + resolution: {integrity: sha512-OmwAE3fzlSJsA0zzCWA/ob7Nwb7nwzku8vbAjgmnkkVYbpyokBsaVrrzog9cM0RMytXexpNNcbQbMF/UNa71mg==} + dev: true + /@polka/url/1.0.0-next.21: resolution: {integrity: sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==} dev: false @@ -7391,7 +7397,7 @@ packages: - supports-color dev: true - /@typescript-eslint/eslint-plugin/5.29.0_3ekaj7j3owlolnuhj3ykrb7u7i: + /@typescript-eslint/eslint-plugin/5.29.0_k4l66av2tbo6kxzw52jzgbfzii: resolution: {integrity: sha512-kgTsISt9pM53yRFQmLZ4npj99yGl3x3Pl7z4eA66OuTzAGC4bQB5H5fuLwPnqTKU3yyrrg4MIhjF17UYnL4c0w==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -7402,18 +7408,18 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/parser': 4.33.0_hxadhbs2xogijvk7vq4t2azzbu + '@typescript-eslint/parser': 4.33.0_3rubbgt5ekhqrcgx4uwls3neim '@typescript-eslint/scope-manager': 5.29.0 - '@typescript-eslint/type-utils': 5.29.0_hxadhbs2xogijvk7vq4t2azzbu - '@typescript-eslint/utils': 5.29.0_hxadhbs2xogijvk7vq4t2azzbu + '@typescript-eslint/type-utils': 5.29.0_3rubbgt5ekhqrcgx4uwls3neim + '@typescript-eslint/utils': 5.29.0_3rubbgt5ekhqrcgx4uwls3neim debug: 4.3.4 eslint: 7.32.0 functional-red-black-tree: 1.0.1 ignore: 5.2.0 regexpp: 3.2.0 semver: 7.3.7 - tsutils: 3.21.0_typescript@4.7.4 - typescript: 4.7.4 + tsutils: 3.21.0_typescript@4.8.4 + typescript: 4.8.4 transitivePeerDependencies: - supports-color dev: true @@ -7436,6 +7442,26 @@ packages: - typescript dev: true + /@typescript-eslint/parser/4.33.0_3rubbgt5ekhqrcgx4uwls3neim: + resolution: {integrity: sha512-ZohdsbXadjGBSK0/r+d87X0SBmKzOq4/S5nzK6SBgJspFo9/CUDJ7hjayuze+JK7CZQLDMroqytp7pOcFKTxZA==} + engines: {node: ^10.12.0 || >=12.0.0} + peerDependencies: + eslint: ^5.0.0 || ^6.0.0 || ^7.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/scope-manager': 4.33.0 + '@typescript-eslint/types': 4.33.0 + '@typescript-eslint/typescript-estree': 4.33.0_typescript@4.8.4 + debug: 4.3.4 + eslint: 7.32.0 + typescript: 4.8.4 + transitivePeerDependencies: + - supports-color + dev: true + /@typescript-eslint/parser/4.33.0_hxadhbs2xogijvk7vq4t2azzbu: resolution: {integrity: sha512-ZohdsbXadjGBSK0/r+d87X0SBmKzOq4/S5nzK6SBgJspFo9/CUDJ7hjayuze+JK7CZQLDMroqytp7pOcFKTxZA==} engines: {node: ^10.12.0 || >=12.0.0} @@ -7472,7 +7498,7 @@ packages: '@typescript-eslint/visitor-keys': 5.29.0 dev: true - /@typescript-eslint/type-utils/5.29.0_hxadhbs2xogijvk7vq4t2azzbu: + /@typescript-eslint/type-utils/5.29.0_3rubbgt5ekhqrcgx4uwls3neim: resolution: {integrity: sha512-JK6bAaaiJozbox3K220VRfCzLa9n0ib/J+FHIwnaV3Enw/TO267qe0pM1b1QrrEuy6xun374XEAsRlA86JJnyg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -7482,11 +7508,11 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/utils': 5.29.0_hxadhbs2xogijvk7vq4t2azzbu + '@typescript-eslint/utils': 5.29.0_3rubbgt5ekhqrcgx4uwls3neim debug: 4.3.4 eslint: 7.32.0 - tsutils: 3.21.0_typescript@4.7.4 - typescript: 4.7.4 + tsutils: 3.21.0_typescript@4.8.4 + typescript: 4.8.4 transitivePeerDependencies: - supports-color dev: true @@ -7522,7 +7548,28 @@ packages: - supports-color dev: true - /@typescript-eslint/typescript-estree/5.29.0_typescript@4.7.4: + /@typescript-eslint/typescript-estree/4.33.0_typescript@4.8.4: + resolution: {integrity: sha512-rkWRY1MPFzjwnEVHsxGemDzqqddw2QbTJlICPD9p9I9LfsO8fdmfQPOX3uKfUaGRDFJbfrtm/sXhVXN4E+bzCA==} + engines: {node: ^10.12.0 || >=12.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/types': 4.33.0 + '@typescript-eslint/visitor-keys': 4.33.0 + debug: 4.3.4 + globby: 11.1.0 + is-glob: 4.0.3 + semver: 7.3.7 + tsutils: 3.21.0_typescript@4.8.4 + typescript: 4.8.4 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/typescript-estree/5.29.0_typescript@4.8.4: resolution: {integrity: sha512-mQvSUJ/JjGBdvo+1LwC+GY2XmSYjK1nAaVw2emp/E61wEVYEyibRHCqm1I1vEKbXCpUKuW4G7u9ZCaZhJbLoNQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -7537,13 +7584,13 @@ packages: globby: 11.1.0 is-glob: 4.0.3 semver: 7.3.7 - tsutils: 3.21.0_typescript@4.7.4 - typescript: 4.7.4 + tsutils: 3.21.0_typescript@4.8.4 + typescript: 4.8.4 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/utils/5.29.0_hxadhbs2xogijvk7vq4t2azzbu: + /@typescript-eslint/utils/5.29.0_3rubbgt5ekhqrcgx4uwls3neim: resolution: {integrity: sha512-3Eos6uP1nyLOBayc/VUdKZikV90HahXE5Dx9L5YlSd/7ylQPXhLk1BYb29SDgnBnTp+jmSZUU0QxUiyHgW4p7A==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -7552,7 +7599,7 @@ packages: '@types/json-schema': 7.0.11 '@typescript-eslint/scope-manager': 5.29.0 '@typescript-eslint/types': 5.29.0 - '@typescript-eslint/typescript-estree': 5.29.0_typescript@4.7.4 + '@typescript-eslint/typescript-estree': 5.29.0_typescript@4.8.4 eslint: 7.32.0 eslint-scope: 5.1.1 eslint-utils: 3.0.0_eslint@7.32.0 @@ -11236,7 +11283,7 @@ packages: - supports-color dev: true - /eslint-config-standard-with-typescript/21.0.1_wku6qe4ns7jbrowxtfd54afziq: + /eslint-config-standard-with-typescript/21.0.1_lmp6wodjnmju6ozb2nvqbelp44: resolution: {integrity: sha512-FeiMHljEJ346Y0I/HpAymNKdrgKEpHpcg/D93FvPHWfCzbT4QyUJba/0FwntZeGLXfUiWDSeKmdJD597d9wwiw==} peerDependencies: '@typescript-eslint/eslint-plugin': ^4.0.1 @@ -11246,14 +11293,14 @@ packages: eslint-plugin-promise: ^4.2.1 || ^5.0.0 typescript: ^3.9 || ^4.0.0 dependencies: - '@typescript-eslint/eslint-plugin': 5.29.0_3ekaj7j3owlolnuhj3ykrb7u7i - '@typescript-eslint/parser': 4.33.0_hxadhbs2xogijvk7vq4t2azzbu + '@typescript-eslint/eslint-plugin': 5.29.0_k4l66av2tbo6kxzw52jzgbfzii + '@typescript-eslint/parser': 4.33.0_3rubbgt5ekhqrcgx4uwls3neim eslint: 7.32.0 eslint-config-standard: 16.0.3_lce4tli34gkpamqqqtkhffizou eslint-plugin-import: 2.26.0_ffi3uiz42rv3jyhs6cr7p7qqry eslint-plugin-node: 11.1.0_eslint@7.32.0 eslint-plugin-promise: 6.0.0_eslint@7.32.0 - typescript: 4.7.4 + typescript: 4.8.4 transitivePeerDependencies: - supports-color dev: true @@ -11313,7 +11360,7 @@ packages: eslint-import-resolver-webpack: optional: true dependencies: - '@typescript-eslint/parser': 4.33.0_hxadhbs2xogijvk7vq4t2azzbu + '@typescript-eslint/parser': 4.33.0_3rubbgt5ekhqrcgx4uwls3neim debug: 3.2.7 eslint-import-resolver-node: 0.3.6 find-up: 2.1.0 @@ -11342,7 +11389,7 @@ packages: '@typescript-eslint/parser': optional: true dependencies: - '@typescript-eslint/parser': 4.33.0_hxadhbs2xogijvk7vq4t2azzbu + '@typescript-eslint/parser': 4.33.0_3rubbgt5ekhqrcgx4uwls3neim array-includes: 3.1.5 array.prototype.flat: 1.3.0 debug: 2.6.9 @@ -11363,7 +11410,7 @@ packages: - supports-color dev: true - /eslint-plugin-jest/27.0.2_vibe533nrfhlkvcegtsn4treva: + /eslint-plugin-jest/27.0.2_taf6alw3cl62tudcjy32ivuzqe: resolution: {integrity: sha512-VEZaj19IMxqg/URcHNT4PhfoJJ1EsFurgq0SjMEYprDCq+et9fKkE4jIHnAsFh3mHLPBgAq04YQqkeW3slXs+Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: @@ -11376,8 +11423,8 @@ packages: jest: optional: true dependencies: - '@typescript-eslint/eslint-plugin': 5.29.0_3ekaj7j3owlolnuhj3ykrb7u7i - '@typescript-eslint/utils': 5.29.0_hxadhbs2xogijvk7vq4t2azzbu + '@typescript-eslint/eslint-plugin': 5.29.0_k4l66av2tbo6kxzw52jzgbfzii + '@typescript-eslint/utils': 5.29.0_3rubbgt5ekhqrcgx4uwls3neim eslint: 7.32.0 transitivePeerDependencies: - supports-color @@ -18375,12 +18422,6 @@ packages: resolution: {integrity: sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA==} engines: {node: '>=4'} - /prettier/2.4.1: - resolution: {integrity: sha512-9fbDAXSBcc6Bs1mZrDYb3XKzDLm4EXXL9sC1LqKP5rZkT6KRr/rf9amVUcODVXgguK/isJz0d0hP72WeaKWsvA==} - engines: {node: '>=10.13.0'} - hasBin: true - dev: true - /prettier/2.7.1: resolution: {integrity: sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==} engines: {node: '>=10.13.0'} @@ -18451,7 +18492,7 @@ packages: engines: {node: '>= 0.8'} dev: true - /pretty-quick/3.1.3_prettier@2.4.1: + /pretty-quick/3.1.3_prettier@2.7.1: resolution: {integrity: sha512-kOCi2FJabvuh1as9enxYmrnBC6tVMoVOenMaBqRfsvBHB0cbpYHjdQEpSglpASDFEXVwplpcGR4CLEaisYAFcA==} engines: {node: '>=10.13'} hasBin: true @@ -18464,7 +18505,7 @@ packages: ignore: 5.2.0 mri: 1.2.0 multimatch: 4.0.0 - prettier: 2.4.1 + prettier: 2.7.1 dev: true /pretty-time/1.1.0: @@ -20958,7 +20999,7 @@ packages: yargs-parser: 20.2.9 dev: true - /ts-node/10.5.0_x2utdhayajzrh747hktprshhby: + /ts-node/10.5.0_ksn4eycaeggbrckn3ykh37hwf4: resolution: {integrity: sha512-6kEJKwVxAJ35W4akuiysfKwKmjkbYxwQMTBaAxo9KKAx/Yd26mPUyhGz3ji+EsJoAgrLqVsYHNuuYwQe22lbtw==} hasBin: true peerDependencies: @@ -20984,7 +21025,7 @@ packages: create-require: 1.1.1 diff: 4.0.2 make-error: 1.3.6 - typescript: 4.7.4 + typescript: 4.8.4 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 dev: true @@ -21015,6 +21056,16 @@ packages: typescript: 4.7.4 dev: true + /tsutils/3.21.0_typescript@4.8.4: + resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} + engines: {node: '>= 6'} + peerDependencies: + typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' + dependencies: + tslib: 1.14.1 + typescript: 4.8.4 + dev: true + /tunnel-agent/0.6.0: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} dependencies: @@ -21425,6 +21476,12 @@ packages: hasBin: true dev: true + /typescript/4.8.4: + resolution: {integrity: sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==} + engines: {node: '>=4.2.0'} + hasBin: true + dev: true + /ua-parser-js/0.7.31: resolution: {integrity: sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ==} dev: false From bc314ef7e8dbca89ba74da7da8b05fe1c5a7b612 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Sun, 9 Oct 2022 01:36:05 +0200 Subject: [PATCH 23/93] type and default import on one line --- apps/dev/pages/api/auth/[...nextauth].ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/dev/pages/api/auth/[...nextauth].ts b/apps/dev/pages/api/auth/[...nextauth].ts index 02d46d2db7..5938a5bfad 100644 --- a/apps/dev/pages/api/auth/[...nextauth].ts +++ b/apps/dev/pages/api/auth/[...nextauth].ts @@ -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" From 0723747523873cfdbb64bd28075256adfce654d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Sun, 9 Oct 2022 01:37:26 +0200 Subject: [PATCH 24/93] don't import all adapters by default in dev --- apps/dev/pages/api/auth/[...nextauth].ts | 61 ++++++++++-------------- package.json | 6 +-- 2 files changed, 28 insertions(+), 39 deletions(-) diff --git a/apps/dev/pages/api/auth/[...nextauth].ts b/apps/dev/pages/api/auth/[...nextauth].ts index 5938a5bfad..2eaa619183 100644 --- a/apps/dev/pages/api/auth/[...nextauth].ts +++ b/apps/dev/pages/api/auth/[...nextauth].ts @@ -37,45 +37,34 @@ import WorkOS from "next-auth/providers/workos" import Zitadel from "next-auth/providers/zitadel" // Adapters -import { PrismaClient } from "@prisma/client" -import { PrismaAdapter } from "@next-auth/prisma-adapter" -import { Client as FaunaClient } from "faunadb" -import { FaunaAdapter } from "@next-auth/fauna-adapter" -import { TypeORMLegacyAdapter } from "@next-auth/typeorm-legacy-adapter" -// Add an adapter you want to test here. -const adapters = { - prisma() { - const client = globalThis.prisma || new PrismaClient() - if (process.env.NODE_ENV !== "production") globalThis.prisma = client - return PrismaAdapter(client) - }, - typeorm() { - return TypeORMLegacyAdapter({ - type: "sqlite", - name: "next-auth-test-memory", - database: "./typeorm/dev.db", - synchronize: true, - }) - }, - fauna() { - const client = - globalThis.fauna || - new FaunaClient({ - secret: process.env.FAUNA_SECRET, - domain: process.env.FAUNA_DOMAIN, - }) - if (process.env.NODE_ENV !== "production") global.fauna = client - return FaunaAdapter(client) - }, - noop() { - return undefined - }, -} +// // Prisma +// import { PrismaClient } from "@prisma/client" +// import { PrismaAdapter } from "@next-auth/prisma-adapter" +// const client = globalThis.prisma || new PrismaClient() +// if (process.env.NODE_ENV !== "production") globalThis.prisma = client +// const adapter = PrismaAdapter(client) + +// // Fauna +// import { Client as FaunaClient } from "faunadb" +// import { FaunaAdapter } from "@next-auth/fauna-adapter" +// const opts = { secret: process.env.FAUNA_SECRET, domain: process.env.FAUNA_DOMAIN } +// const client = globalThis.fauna || new FaunaClient(opts) +// if (process.env.NODE_ENV !== "production") globalThis.fauna = client +// const adapter = FaunaAdapter(client) + +// // TypeORM +// import { TypeORMLegacyAdapter } from "@next-auth/typeorm-legacy-adapter" +// const adapter = TypeORMLegacyAdapter({ +// type: "sqlite", +// name: "next-auth-test-memory", +// database: "./typeorm/dev.db", +// synchronize: true, +// }) export const authOptions: NextAuthOptions = { - adapter: adapters.noop(), - debug: true, + // adapter, + debug: process.env.NODE_ENV !== "production", theme: { logo: "https://next-auth.js.org/img/logo/logo-sm.png", brandColor: "#1786fb", diff --git a/package.json b/package.json index c7db6d371d..a9e84666da 100644 --- a/package.json +++ b/package.json @@ -4,14 +4,14 @@ "private": true, "repository": "https://github.com/nextauthjs/next-auth.git", "scripts": { - "build:app": "turbo run build --filter=next-auth-app --include-dependencies", + "build:app": "turbo run build --filter=next-auth-app", "build": "turbo run build --filter=next-auth --filter=@next-auth/* --no-deps", "lint": "turbo run lint --filter=!next-auth-docs --parallel", "test": "turbo run test --concurrency=1 --filter=!@next-auth/pouchdb-adapter --filter=!@next-auth/upstash-redis-adapter --filter=!next-auth-* --filter=[HEAD^1]", "clean": "turbo run clean --no-cache", - "dev:app": "turbo run dev --parallel --continue --filter=next-auth-app...", + "dev:db": "turbo run dev --parallel --continue --filter=next-auth-app...", + "dev": "turbo run dev --parallel --continue --filter=next-auth-app... --filter=!./packages/adapter-*", "dev:docs": "turbo run dev --filter=next-auth-docs", - "dev": "pnpm dev:app", "email": "cd apps/dev && pnpm email", "release": "release", "version:pr": "node ./config/version-pr" From b8ed7358c9606090220fe5b7e110e112b9891ae6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Sun, 9 Oct 2022 01:47:44 +0200 Subject: [PATCH 25/93] simplify internal endpoint config Instead of passing url and params around as a string and an object, we parse them into a `URL` instance. --- .../src/core/lib/oauth/authorization-url.ts | 22 ++++------ .../next-auth/src/core/lib/oauth/callback.ts | 16 +++---- .../next-auth/src/core/lib/oauth/client.ts | 15 ++++--- packages/next-auth/src/core/lib/providers.ts | 42 ++++++++++++------- packages/next-auth/src/core/types.ts | 4 +- packages/next-auth/src/providers/oauth.ts | 22 +++++++++- 6 files changed, 69 insertions(+), 52 deletions(-) diff --git a/packages/next-auth/src/core/lib/oauth/authorization-url.ts b/packages/next-auth/src/core/lib/oauth/authorization-url.ts index 7d29e26e75..66214de22f 100644 --- a/packages/next-auth/src/core/lib/oauth/authorization-url.ts +++ b/packages/next-auth/src/core/lib/oauth/authorization-url.ts @@ -23,26 +23,22 @@ export default async function getAuthorizationUrl({ query: InternalRequest["query"] }) { const { logger, provider } = options - let params: any = {} + let params - if (typeof provider.authorization === "string") { - const parsedUrl = new URL(provider.authorization) - const parsedParams = Object.fromEntries(parsedUrl.searchParams.entries()) - params = { ...params, ...parsedParams } + if (provider.authorization?.url) { + params = Object.assign( + Object.fromEntries(provider.authorization.url.searchParams.entries()), + query + ) } else { - params = { ...params, ...provider.authorization?.params } + params = query } - params = { ...params, ...query } - // Handle OAuth v1.x if (provider.version?.startsWith("1.")) { const client = oAuth1Client(options) const tokens = (await client.getOAuthRequestToken(params)) as any - const url = `${ - // @ts-expect-error - provider.authorization?.url ?? provider.authorization - }?${new URLSearchParams({ + const url = `${provider.authorization?.url}?${new URLSearchParams({ oauth_token: tokens.oauth_token, oauth_token_secret: tokens.oauth_token_secret, ...tokens.params, @@ -68,7 +64,7 @@ export default async function getAuthorizationUrl({ authorizationParams.nonce = nonce.value cookies.push(nonce.cookie) } - + const pkce = await createPKCE(options) if (pkce) { authorizationParams.code_challenge = pkce.code_challenge diff --git a/packages/next-auth/src/core/lib/oauth/callback.ts b/packages/next-auth/src/core/lib/oauth/callback.ts index 8aa6c4ef85..3997f6c5ea 100644 --- a/packages/next-auth/src/core/lib/oauth/callback.ts +++ b/packages/next-auth/src/core/lib/oauth/callback.ts @@ -8,7 +8,7 @@ import { OAuthCallbackError } from "../../errors" import type { CallbackParamsType, OpenIDCallbackChecks } from "openid-client" import type { Account, LoggerInstance, Profile } from "../../.." -import type { OAuthChecks, OAuthConfig } from "../../../providers" +import type { OAuthConfigInternal, ProfileCallback } from "../../../providers" import type { InternalOptions } from "../../types" import type { InternalRequest, InternalResponse } from "../.." import type { Cookie } from "../cookie" @@ -34,7 +34,6 @@ export default async function oAuthCallback(params: { logger.debug("OAUTH_CALLBACK_HANDLER_ERROR", { body }) throw error } - if (provider.version?.startsWith("1.")) { try { @@ -71,7 +70,7 @@ export default async function oAuthCallback(params: { let tokens: TokenSet - const checks: OAuthChecks = {} + const checks: OpenIDCallbackChecks = {} const resCookies: Cookie[] = [] const state = await useState(cookies?.[options.cookies.state.name], options) @@ -82,7 +81,7 @@ export default async function oAuthCallback(params: { const nonce = await useNonce(cookies?.[options.cookies.nonce.name], options) if (nonce && provider.idToken) { - (checks as OpenIDCallbackChecks).nonce = nonce.value + checks.nonce = nonce.value resCookies.push(nonce.cookie) } @@ -106,9 +105,7 @@ export default async function oAuthCallback(params: { ...provider.token?.params, } - // @ts-expect-error if (provider.token?.request) { - // @ts-expect-error const response = await provider.token.request({ provider, params, @@ -128,9 +125,7 @@ export default async function oAuthCallback(params: { } let profile: Profile - // @ts-expect-error if (provider.userinfo?.request) { - // @ts-expect-error profile = await provider.userinfo.request({ provider, tokens, @@ -160,13 +155,12 @@ export default async function oAuthCallback(params: { export interface GetProfileParams { profile: Profile tokens: TokenSet - provider: OAuthConfig + provider: OAuthConfigInternal logger: LoggerInstance } export interface GetProfileResult { - // @ts-expect-error - profile: ReturnType | null + profile: ReturnType> | null account: Omit | null OAuthProfile: Profile } diff --git a/packages/next-auth/src/core/lib/oauth/client.ts b/packages/next-auth/src/core/lib/oauth/client.ts index abbfba9bfe..5dee813f09 100644 --- a/packages/next-auth/src/core/lib/oauth/client.ts +++ b/packages/next-auth/src/core/lib/oauth/client.ts @@ -21,14 +21,13 @@ export async function openidClient( issuer = await Issuer.discover(provider.wellKnown) } else { issuer = new Issuer({ - issuer: provider.issuer as string, - authorization_endpoint: - // @ts-expect-error - provider.authorization?.url ?? provider.authorization, - // @ts-expect-error - token_endpoint: provider.token?.url ?? provider.token, - // @ts-expect-error - userinfo_endpoint: provider.userinfo?.url ?? provider.userinfo, + // NOTE: We verify that either `issuer` or the other endpoints + // are always defined in assert.ts + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + issuer: provider.issuer!, + authorization_endpoint: provider.authorization?.url.toString(), + token_endpoint: provider.token?.url.toString(), + userinfo_endpoint: provider.userinfo?.url.toString(), }) } diff --git a/packages/next-auth/src/core/lib/providers.ts b/packages/next-auth/src/core/lib/providers.ts index 9b2d085dc4..7448949af2 100644 --- a/packages/next-auth/src/core/lib/providers.ts +++ b/packages/next-auth/src/core/lib/providers.ts @@ -1,7 +1,7 @@ import { merge } from "../../utils/merge" import type { InternalProvider } from "../types" -import type { Provider } from "../../providers" +import type { EndpointHandler, Provider } from "../../providers" import type { InternalUrl } from "../../utils/parse-url" /** @@ -18,16 +18,18 @@ export default function parseProviders(params: { } { const { url, providerId } = params - const providers = params.providers.map(({ options, ...rest }) => { - const defaultOptions = normalizeProvider(rest as Provider) - const userOptions = normalizeProvider(options as Provider) + const providers: InternalProvider[] = params.providers.map( + ({ options, ...rest }) => { + const defaultOptions = normalizeProvider(rest as Provider) + const userOptions = normalizeProvider(options as Provider) - return merge(defaultOptions, { - ...userOptions, - signinUrl: `${url}/signin/${userOptions?.id ?? rest.id}`, - callbackUrl: `${url}/callback/${userOptions?.id ?? rest.id}`, - }) - }) + return merge(defaultOptions, { + ...userOptions, + signinUrl: `${url}/signin/${userOptions?.id ?? rest.id}`, + callbackUrl: `${url}/callback/${userOptions?.id ?? rest.id}`, + }) + } + ) const provider = providers.find(({ id }) => id === providerId) @@ -41,13 +43,21 @@ function normalizeProvider(provider?: Provider) { provider ).reduce((acc, [key, value]) => { if ( - ["authorization", "token", "userinfo"].includes(key) && - typeof value === "string" + provider.type === "oauth" && + ["authorization", "token", "userinfo"].includes(key) ) { - const url = new URL(value) - acc[key] = { - url: `${url.origin}${url.pathname}`, - params: Object.fromEntries(url.searchParams ?? []), + const v = value as EndpointHandler + if (typeof v === "string") { + acc[key] = { url: new URL(v) } + } else { + // NOTE: If v.url is undefined, it's because the provider config + // assumes that we will use the issuer endpoint. + // The existence of either v.url or provider.issuer is checked in + // assert.ts + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const url = new URL(v.url!) + for (const k in v.params) url.searchParams.set(k, v.params[k]) + acc[key] = { url } } } else { acc[key] = value diff --git a/packages/next-auth/src/core/types.ts b/packages/next-auth/src/core/types.ts index bfc57f452d..247add0cc5 100644 --- a/packages/next-auth/src/core/types.ts +++ b/packages/next-auth/src/core/types.ts @@ -3,7 +3,7 @@ import type { Provider, CredentialInput, ProviderType, - OAuthConfig, + OAuthConfigInternal, EmailConfig, CredentialsConfig, } from "../providers" @@ -500,7 +500,7 @@ export interface User extends Record, DefaultUser {} /** @internal */ export type InternalProvider = (T extends "oauth" - ? OAuthConfig + ? OAuthConfigInternal : T extends "email" ? EmailConfig : T extends "credentials" diff --git a/packages/next-auth/src/providers/oauth.ts b/packages/next-auth/src/providers/oauth.ts index 9bac59c986..1ec2bbb647 100644 --- a/packages/next-auth/src/providers/oauth.ts +++ b/packages/next-auth/src/providers/oauth.ts @@ -30,7 +30,7 @@ type EndpointRequest = ( /** `openid-client` Client */ client: Client /** Provider is passed for convenience, ans also contains the `callbackUrl`. */ - provider: OAuthConfig

& { + provider: (OAuthConfig

| OAuthConfigInternal

) & { signinUrl: string callbackUrl: string } @@ -89,6 +89,11 @@ export type UserinfoEndpointHandler = EndpointHandler< Profile > +export type ProfileCallback

= ( + profile: P, + tokens: TokenSet +) => Awaitable + export interface OAuthConfig

extends CommonProviderOptions, PartialIssuer { /** * OpenID Connect (OIDC) compliant providers can configure @@ -110,7 +115,7 @@ export interface OAuthConfig

extends CommonProviderOptions, PartialIssuer { userinfo?: string | UserinfoEndpointHandler type: "oauth" version?: string - profile?: (profile: P, tokens: TokenSet) => Awaitable + profile?: ProfileCallback

checks?: ChecksType | ChecksType[] client?: Partial jwks?: { keys: JWK[] } @@ -147,6 +152,19 @@ export interface OAuthConfig

extends CommonProviderOptions, PartialIssuer { encoding?: string } +/** + * We parsesd `authorization`, `token` and `userinfo` + * to always contain a valid `URL`, with the params + */ +export type OAuthConfigInternal

= Omit< + OAuthConfig

, + "authorization" | "token" | "userinfo" +> & { + authorization?: { url: URL } + token?: { url: URL; request: TokenEndpointHandler["request"] } + userinfo?: { url: URL; request: UserinfoEndpointHandler["request"] } +} + export type OAuthUserConfig

= Omit< Partial>, "options" | "type" From 9bea47381fbdf04630296ab40315997760f2305a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Sun, 9 Oct 2022 01:48:15 +0200 Subject: [PATCH 26/93] assert if both endpoint and issuer config is missing --- packages/next-auth/src/core/errors.ts | 7 ++++++- packages/next-auth/src/core/index.ts | 4 ++++ packages/next-auth/src/core/lib/assert.ts | 16 ++++++++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/packages/next-auth/src/core/errors.ts b/packages/next-auth/src/core/errors.ts index 1270da9f0b..91164ace7e 100644 --- a/packages/next-auth/src/core/errors.ts +++ b/packages/next-auth/src/core/errors.ts @@ -64,10 +64,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 export function upperSnake(s: string) { diff --git a/packages/next-auth/src/core/index.ts b/packages/next-auth/src/core/index.ts index 661021af03..4c07a4f310 100644 --- a/packages/next-auth/src/core/index.ts +++ b/packages/next-auth/src/core/index.ts @@ -66,6 +66,10 @@ async function AuthHandlerInternal< 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 = diff --git a/packages/next-auth/src/core/lib/assert.ts b/packages/next-auth/src/core/lib/assert.ts index ed09c39d6a..1eeff05ad7 100644 --- a/packages/next-auth/src/core/lib/assert.ts +++ b/packages/next-auth/src/core/lib/assert.ts @@ -5,6 +5,7 @@ import { MissingSecret, UnsupportedStrategy, InvalidCallbackUrl, + InvalidEndpoints, } from "../errors" import parseUrl from "../../utils/parse-url" import { defaultCookies } from "./cookie" @@ -19,6 +20,7 @@ type ConfigError = | UnsupportedStrategy | MissingAuthorize | MissingAdapter + | InvalidEndpoints let warned = false @@ -93,6 +95,20 @@ export function assertConfig(params: { let hasTwitterOAuth2 for (const provider of options.providers) { + if (provider.type === "oauth" && !provider.issuer) { + 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") From cbd7f18b3a40749722856c52aefa7977ed454373 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Sun, 9 Oct 2022 01:51:00 +0200 Subject: [PATCH 27/93] allow internal redirect to be `URL` --- packages/next-auth/src/core/index.ts | 2 +- packages/next-auth/src/core/lib/spec.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/next-auth/src/core/index.ts b/packages/next-auth/src/core/index.ts index 4c07a4f310..88455ea884 100644 --- a/packages/next-auth/src/core/index.ts +++ b/packages/next-auth/src/core/index.ts @@ -29,7 +29,7 @@ export interface InternalResponse< status?: number headers?: NextAuthHeader[] body?: Body - redirect?: string + redirect?: URL | string // TODO: refactor to only allow URL cookies?: Cookie[] } diff --git a/packages/next-auth/src/core/lib/spec.ts b/packages/next-auth/src/core/lib/spec.ts index bd956af348..86918dbdcc 100644 --- a/packages/next-auth/src/core/lib/spec.ts +++ b/packages/next-auth/src/core/lib/spec.ts @@ -87,7 +87,7 @@ export function toResponse(res: InternalResponse): Response { }) if (res.redirect) { - response.headers.set("Location", res.redirect) + response.headers.set("Location", res.redirect.toString()) } return response From 9466f6503291d5ae4d043cc0ecc6da5a79964985 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Sun, 9 Oct 2022 02:04:05 +0200 Subject: [PATCH 28/93] mark clientId as always internally, fix comments --- packages/next-auth/src/core/lib/oauth/client.ts | 13 ++++--------- packages/next-auth/src/core/lib/providers.ts | 2 +- packages/next-auth/src/core/types.ts | 2 +- packages/next-auth/src/providers/oauth.ts | 3 ++- 4 files changed, 8 insertions(+), 12 deletions(-) diff --git a/packages/next-auth/src/core/lib/oauth/client.ts b/packages/next-auth/src/core/lib/oauth/client.ts index 5dee813f09..82c2d075d8 100644 --- a/packages/next-auth/src/core/lib/oauth/client.ts +++ b/packages/next-auth/src/core/lib/oauth/client.ts @@ -2,13 +2,7 @@ import { Issuer, custom } from "openid-client" import type { Client } from "openid-client" import type { InternalOptions } from "../../types" -/** - * NOTE: We can add auto discovery of the provider's endpoint - * that requires only one endpoint to be specified by the user. - * Check out `Issuer.discover` - * - * Client supporting OAuth 2.x and OIDC - */ +/** Node.js reliant client supporting OAuth 2.x and OIDC */ export async function openidClient( options: InternalOptions<"oauth"> ): Promise { @@ -21,7 +15,7 @@ export async function openidClient( issuer = await Issuer.discover(provider.wellKnown) } else { issuer = new Issuer({ - // NOTE: We verify that either `issuer` or the other endpoints + // We verify that either `issuer` or the other endpoints // are always defined in assert.ts // eslint-disable-next-line @typescript-eslint/no-non-null-assertion issuer: provider.issuer!, @@ -33,7 +27,8 @@ export async function openidClient( const client = new issuer.Client( { - client_id: provider.clientId as string, + // clientId can technically be undefined, should we check this in assert.ts or rely on the Authorization Server to do it? + client_id: provider.clientId, client_secret: provider.clientSecret as string, redirect_uris: [provider.callbackUrl], ...provider.client, diff --git a/packages/next-auth/src/core/lib/providers.ts b/packages/next-auth/src/core/lib/providers.ts index 7448949af2..67139d6ce8 100644 --- a/packages/next-auth/src/core/lib/providers.ts +++ b/packages/next-auth/src/core/lib/providers.ts @@ -50,7 +50,7 @@ function normalizeProvider(provider?: Provider) { if (typeof v === "string") { acc[key] = { url: new URL(v) } } else { - // NOTE: If v.url is undefined, it's because the provider config + // If v.url is undefined, it's because the provider config // assumes that we will use the issuer endpoint. // The existence of either v.url or provider.issuer is checked in // assert.ts diff --git a/packages/next-auth/src/core/types.ts b/packages/next-auth/src/core/types.ts index 247add0cc5..f0c24a841e 100644 --- a/packages/next-auth/src/core/types.ts +++ b/packages/next-auth/src/core/types.ts @@ -41,7 +41,7 @@ export interface NextAuthOptions { * If not specified, it falls back to `jwt.secret` or `NEXTAUTH_SECRET` from environment vairables. * Otherwise it will use a hash of all configuration options, including Client ID / Secrets for entropy. * - * NOTE: The last behavior is extrmely volatile, and will throw an error in production. + * NOTE: The last behavior is extremely volatile, and will throw an error in production. * * **Default value**: `string` (SHA hash of the "options" object) * * **Required**: No - **but strongly recommended**! * diff --git a/packages/next-auth/src/providers/oauth.ts b/packages/next-auth/src/providers/oauth.ts index 1ec2bbb647..dab5a0c802 100644 --- a/packages/next-auth/src/providers/oauth.ts +++ b/packages/next-auth/src/providers/oauth.ts @@ -158,8 +158,9 @@ export interface OAuthConfig

extends CommonProviderOptions, PartialIssuer { */ export type OAuthConfigInternal

= Omit< OAuthConfig

, - "authorization" | "token" | "userinfo" + "authorization" | "token" | "userinfo" | "clientId" > & { + clientId: string authorization?: { url: URL } token?: { url: URL; request: TokenEndpointHandler["request"] } userinfo?: { url: URL; request: UserinfoEndpointHandler["request"] } From dbf42936e478671f279812690ff7949ac446fcdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Sun, 9 Oct 2022 02:06:14 +0200 Subject: [PATCH 29/93] add web-compatible authorization URL handling --- .../core/lib/oauth/web/authorization-url.ts | 139 ++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 packages/next-auth/src/core/lib/oauth/web/authorization-url.ts diff --git a/packages/next-auth/src/core/lib/oauth/web/authorization-url.ts b/packages/next-auth/src/core/lib/oauth/web/authorization-url.ts new file mode 100644 index 0000000000..b1e3e6f115 --- /dev/null +++ b/packages/next-auth/src/core/lib/oauth/web/authorization-url.ts @@ -0,0 +1,139 @@ +import * as o from "@panva/oauth4webapi" + +import type { CookiesOptions, InternalOptions } from "../../../types" +import type { InternalRequest, InternalResponse } from "../../.." +import type { Cookie } from "../../cookie" + +/** + * Generates an authorization/request token URL. + * + * [OAuth 2](https://www.oauth.com/oauth2-servers/authorization/the-authorization-request/) + */ +export default async function getAuthorizationUrl({ + options, + query, +}: { + options: InternalOptions<"oauth"> + query: InternalRequest["query"] +}): Promise { + const { logger, provider } = options + + if (provider.version?.startsWith("1.")) { + const error = new Error("OAuth v1.x is not supported in next-auth/web") + logger.error("GET_AUTHORIZATION_URL", error) + throw error + } + + let url = provider.authorization?.url + let as: o.AuthorizationServer | undefined + + if (!url) { + // If url is undefined, we assume that issuer is always defined + // We check this in assert.ts + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const issuer = new URL(provider.issuer!) + const discoveryResponse = await o.discoveryRequest(issuer) + const as = await o.processDiscoveryResponse(issuer, discoveryResponse) + + if (!as.authorization_endpoint) { + throw new TypeError( + "Authorization server did not provide an authorization endpoint." + ) + } + + url = new URL(as.authorization_endpoint) + } + + const authParams = url.searchParams + const params = Object.assign( + { + response_type: "code", + // clientId can technically be undefined, should we check this in assert.ts or rely on the Authorization Server to do it? + client_id: provider.clientId, + redirect_uri: provider.callbackUrl, + }, // Defaults + Object.fromEntries(authParams.entries()), // From provider config + query // From `signIn` call + ) + + for (const k in params) authParams.set(k, params[k]) + + const cookies: Cookie[] = [] + + if (provider.checks?.includes("state")) { + const state = await createState(options) + authParams.set("state", state.value) + cookies.push(state) + } + + if (provider.checks?.includes("pkce")) { + if (as && !as.code_challenge_methods_supported?.includes("S256")) { + // We assume S256 PKCE support, if the server does not advertise that, + // a random `nonce` must be used for CSRF protection. + provider.checks = "nonce" + } else { + const { code_challenge, pkce } = await createPKCE(options) + authParams.set("code_challenge", code_challenge) + authParams.set("code_challenge_method", "S256") + cookies.push(pkce) + } + } + + if (provider.checks?.includes("nonce")) { + const nonce = await createNonce(options) + authParams.set("nonce", nonce.value) + cookies.push(nonce) + } + + logger.debug("GET_AUTHORIZATION_URL", { url, cookies, provider }) + return { redirect: url, cookies } +} + +/** Returns a signed cookie. */ +export async function signCookie( + type: keyof CookiesOptions, + value: string, + maxAge: number, + options: InternalOptions<"oauth"> +): Promise { + const { cookies, jwt, logger } = options + + logger.debug(`CREATE_${type.toUpperCase()}`, { value, maxAge }) + + const expires = new Date() + expires.setTime(expires.getTime() + maxAge * 1000) + return { + name: cookies[type].name, + value: await jwt.encode({ ...jwt, maxAge, token: { value } }), + options: { ...cookies[type].options, expires }, + } +} + +const STATE_MAX_AGE = 60 * 15 // 15 minutes in seconds +async function createState(options: InternalOptions<"oauth">) { + const raw = o.generateRandomState() + const maxAge = STATE_MAX_AGE + const state = await signCookie("state", raw, maxAge, options) + return state +} + +const PKCE_MAX_AGE = 60 * 15 // 15 minutes in seconds +async function createPKCE(options: InternalOptions<"oauth">) { + const code_verifier = o.generateRandomCodeVerifier() + const code_challenge = await o.calculatePKCECodeChallenge(code_verifier) + const maxAge = PKCE_MAX_AGE + const pkce = await signCookie( + "pkceCodeVerifier", + code_verifier, + maxAge, + options + ) + return { code_challenge, pkce } +} + +const NONCE_MAX_AGE = 60 * 15 // 15 minutes in seconds +async function createNonce(options: InternalOptions<"oauth">) { + const raw = o.generateRandomNonce() + const maxAge = NONCE_MAX_AGE + return await signCookie("nonce", raw, maxAge, options) +} From 0c71f26ec1abe7d8505c879ffb3d5ab5283923ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Sun, 9 Oct 2022 02:14:28 +0200 Subject: [PATCH 30/93] fix type --- packages/next-auth/src/core/lib/oauth/callback.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/next-auth/src/core/lib/oauth/callback.ts b/packages/next-auth/src/core/lib/oauth/callback.ts index 3997f6c5ea..9417ed7004 100644 --- a/packages/next-auth/src/core/lib/oauth/callback.ts +++ b/packages/next-auth/src/core/lib/oauth/callback.ts @@ -160,7 +160,7 @@ export interface GetProfileParams { } export interface GetProfileResult { - profile: ReturnType> | null + profile: Awaited>> | null account: Omit | null OAuthProfile: Profile } From 1bed1182a17b8b61d987df5f34e2251377211fc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Mon, 10 Oct 2022 02:01:57 +0200 Subject: [PATCH 31/93] fix neo4j build --- packages/adapter-neo4j/src/utils.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/adapter-neo4j/src/utils.ts b/packages/adapter-neo4j/src/utils.ts index 83153cb598..df6f43976c 100644 --- a/packages/adapter-neo4j/src/utils.ts +++ b/packages/adapter-neo4j/src/utils.ts @@ -54,7 +54,10 @@ export function client(session: Session) { * Reads/writes values from/to the database. * Properties are available under `$data` */ - async write(statement: string, values: T): Promise { + async write>( + statement: string, + values: T + ): Promise { const result = await session.writeTransaction((tx) => tx.run(statement, { data: format.to(values) }) ) From b2de56c9e657bb7466224954bd55f8958f32a5bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Mon, 10 Oct 2022 02:30:06 +0200 Subject: [PATCH 32/93] remove new-line --- apps/example-nextjs/.gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/example-nextjs/.gitignore b/apps/example-nextjs/.gitignore index f8fe415c93..e6b58d6c40 100644 --- a/apps/example-nextjs/.gitignore +++ b/apps/example-nextjs/.gitignore @@ -17,4 +17,4 @@ next-env.d.ts .next .vercel -.env*.local +.env*.local \ No newline at end of file From 8041a594908c971a5a5d85e4ee028dbfbb5b44a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Mon, 10 Oct 2022 02:33:08 +0200 Subject: [PATCH 33/93] reduce file changes in the PR --- packages/next-auth/src/core/index.ts | 21 ++++++++++--------- packages/next-auth/src/core/init.ts | 4 ++-- packages/next-auth/src/core/lib/assert.ts | 4 ++-- .../src/core/lib/oauth/authorization-url.ts | 4 ++-- .../next-auth/src/core/lib/oauth/callback.ts | 10 ++++----- packages/next-auth/src/core/lib/spec.ts | 6 +++--- packages/next-auth/src/core/pages/index.ts | 6 +++--- .../next-auth/src/core/routes/callback.ts | 16 +++++++------- .../next-auth/src/core/routes/providers.ts | 4 ++-- packages/next-auth/src/core/routes/session.ts | 6 +++--- packages/next-auth/src/core/routes/signin.ts | 10 ++++----- packages/next-auth/src/core/routes/signout.ts | 4 ++-- .../next-auth/src/providers/credentials.ts | 4 ++-- .../next-auth/tests/getServerSession.test.ts | 2 +- packages/next-auth/tests/lib.ts | 2 +- 15 files changed, 52 insertions(+), 51 deletions(-) diff --git a/packages/next-auth/src/core/index.ts b/packages/next-auth/src/core/index.ts index 661021af03..e6bc907feb 100644 --- a/packages/next-auth/src/core/index.ts +++ b/packages/next-auth/src/core/index.ts @@ -1,16 +1,16 @@ import logger, { setLogger } from "../utils/logger" +import { toInternalRequest, toResponse } from "./lib/spec" import * as routes from "./routes" import renderPage from "./pages" import { init } from "./init" import { assertConfig } from "./lib/assert" import { SessionStore } from "./lib/cookie" -import { toInternalRequest, toResponse } from "./lib/spec" import type { NextAuthAction, NextAuthOptions } from "./types" import type { Cookie } from "./lib/cookie" import type { ErrorType } from "./pages/error" -export interface InternalRequest { +export interface RequestInternal { /** @default "http://localhost:3000" */ host?: string method?: string @@ -23,7 +23,13 @@ export interface InternalRequest { error?: string } -export interface InternalResponse< +export interface NextAuthHeader { + key: string + value: string +} + +// TODO: Rename to `ResponseInternal` +export interface OutgoingResponse< Body extends string | Record | any[] = any > { status?: number @@ -33,19 +39,14 @@ export interface InternalResponse< cookies?: Cookie[] } -export interface NextAuthHeader { - key: string - value: string -} - async function AuthHandlerInternal< Body extends string | Record | any[] >(params: { - req: InternalRequest + req: RequestInternal options: NextAuthOptions /** REVIEW: Is this the best way to skip parsing the body in Node.js? */ parsedBody?: any -}): Promise> { +}): Promise> { const { options: userOptions, req } = params setLogger(userOptions.logger, userOptions.debug) diff --git a/packages/next-auth/src/core/init.ts b/packages/next-auth/src/core/init.ts index 1dbe32267a..b5fa4a72e4 100644 --- a/packages/next-auth/src/core/init.ts +++ b/packages/next-auth/src/core/init.ts @@ -10,7 +10,7 @@ import * as jwt from "../jwt" import { defaultCallbacks } from "./lib/default-callbacks" import { createCSRFToken } from "./lib/csrf-token" import { createCallbackUrl } from "./lib/callback-url" -import { InternalRequest } from "." +import { RequestInternal } from "." import type { InternalOptions } from "./types" @@ -25,7 +25,7 @@ interface InitParams { csrfToken?: string /** Is the incoming request a POST request? */ isPost: boolean - cookies: InternalRequest["cookies"] + cookies: RequestInternal["cookies"] } /** Initialize all internal options and cookies. */ diff --git a/packages/next-auth/src/core/lib/assert.ts b/packages/next-auth/src/core/lib/assert.ts index 86d540e8f8..d1e5bb3533 100644 --- a/packages/next-auth/src/core/lib/assert.ts +++ b/packages/next-auth/src/core/lib/assert.ts @@ -10,7 +10,7 @@ import { import parseUrl from "../../utils/parse-url" import { defaultCookies } from "./cookie" -import type { InternalRequest } from ".." +import type { RequestInternal } from ".." import type { WarningCode } from "../../utils/logger" import type { NextAuthOptions } from "../types" @@ -41,7 +41,7 @@ function isValidHttpUrl(url: string, baseUrl: string) { */ export function assertConfig(params: { options: NextAuthOptions - req: InternalRequest + req: RequestInternal }): ConfigError | WarningCode[] { const { options, req } = params diff --git a/packages/next-auth/src/core/lib/oauth/authorization-url.ts b/packages/next-auth/src/core/lib/oauth/authorization-url.ts index 78c2d67d08..ebe43a067b 100644 --- a/packages/next-auth/src/core/lib/oauth/authorization-url.ts +++ b/packages/next-auth/src/core/lib/oauth/authorization-url.ts @@ -6,7 +6,7 @@ import { createPKCE } from "./pkce-handler" import type { AuthorizationParameters } from "openid-client" import type { InternalOptions } from "../../types" -import type { InternalRequest } from "../.." +import type { RequestInternal } from "../.." import type { Cookie } from "../cookie" /** @@ -20,7 +20,7 @@ export default async function getAuthorizationUrl({ query, }: { options: InternalOptions<"oauth"> - query: InternalRequest["query"] + query: RequestInternal["query"] }) { const { logger, provider } = options let params: any = {} diff --git a/packages/next-auth/src/core/lib/oauth/callback.ts b/packages/next-auth/src/core/lib/oauth/callback.ts index 195bc3ab40..3185bf4c1f 100644 --- a/packages/next-auth/src/core/lib/oauth/callback.ts +++ b/packages/next-auth/src/core/lib/oauth/callback.ts @@ -10,15 +10,15 @@ import type { CallbackParamsType, OpenIDCallbackChecks } from "openid-client" import type { LoggerInstance, Profile } from "../../.." import type { OAuthChecks, OAuthConfig } from "../../../providers" import type { InternalOptions } from "../../types" -import type { InternalRequest } from "../.." +import type { RequestInternal } from "../.." import type { Cookie } from "../cookie" export default async function oAuthCallback(params: { options: InternalOptions<"oauth"> - query: InternalRequest["query"] - body: InternalRequest["body"] - method: Required["method"] - cookies: InternalRequest["cookies"] + query: RequestInternal["query"] + body: RequestInternal["body"] + method: Required["method"] + cookies: RequestInternal["cookies"] }) { const { options, query, body, method, cookies } = params const { logger, provider } = options diff --git a/packages/next-auth/src/core/lib/spec.ts b/packages/next-auth/src/core/lib/spec.ts index bd956af348..aacfbb3527 100644 --- a/packages/next-auth/src/core/lib/spec.ts +++ b/packages/next-auth/src/core/lib/spec.ts @@ -1,6 +1,6 @@ import { serialize, parse as parseCookie } from "cookie" import { detectHost } from "../../utils/detect-host" -import type { InternalResponse, InternalRequest } from ".." +import type { OutgoingResponse, RequestInternal } from ".." import type { NextAuthAction } from "../types" const decoder = new TextDecoder() @@ -31,7 +31,7 @@ async function readJSONBody( export async function toInternalRequest( req: Request -): Promise { +): Promise { const url = new URL(req.url) const nextauth = url.pathname.split("/").slice(3) const headers = Object.fromEntries(req.headers.entries()) @@ -58,7 +58,7 @@ export async function toInternalRequest( } } -export function toResponse(res: InternalResponse): Response { +export function toResponse(res: OutgoingResponse): Response { const headers = new Headers( res.headers?.reduce((acc, { key, value }) => { acc[key] = value diff --git a/packages/next-auth/src/core/pages/index.ts b/packages/next-auth/src/core/pages/index.ts index b79dcf65c6..9812497aa0 100644 --- a/packages/next-auth/src/core/pages/index.ts +++ b/packages/next-auth/src/core/pages/index.ts @@ -6,12 +6,12 @@ import ErrorPage from "./error" import css from "../../css" import type { InternalOptions } from "../types" -import type { InternalRequest, InternalResponse } from ".." +import type { RequestInternal, OutgoingResponse } from ".." import type { Cookie } from "../lib/cookie" import type { ErrorType } from "./error" type RenderPageParams = { - query?: InternalRequest["query"] + query?: RequestInternal["query"] cookies?: Cookie[] } & Partial< Pick< @@ -27,7 +27,7 @@ type RenderPageParams = { export default function renderPage(params: RenderPageParams) { const { url, theme, query, cookies } = params - function send({ html, title, status }: any): InternalResponse { + function send({ html, title, status }: any): OutgoingResponse { return { cookies, status, diff --git a/packages/next-auth/src/core/routes/callback.ts b/packages/next-auth/src/core/routes/callback.ts index 91b27daed0..b7a07581fd 100644 --- a/packages/next-auth/src/core/routes/callback.ts +++ b/packages/next-auth/src/core/routes/callback.ts @@ -4,21 +4,21 @@ import { hashToken } from "../lib/utils" import getUserFromEmail from "../lib/email/getUserFromEmail" import type { InternalOptions } from "../types" -import type { InternalRequest, InternalResponse } from ".." +import type { RequestInternal, OutgoingResponse } from ".." import type { Cookie, SessionStore } from "../lib/cookie" import type { User } from "../.." import type { AdapterSession } from "../../adapters" /** Handle callbacks from login services */ export default async function callback(params: { - options: InternalOptions<"oauth" | "credentials" | "email"> - query: InternalRequest["query"] - method: Required["method"] - body: InternalRequest["body"] - headers: InternalRequest["headers"] - cookies: InternalRequest["cookies"] + options: InternalOptions + query: RequestInternal["query"] + method: Required["method"] + body: RequestInternal["body"] + headers: RequestInternal["headers"] + cookies: RequestInternal["cookies"] sessionStore: SessionStore -}): Promise { +}): Promise { const { options, query, body, method, headers, sessionStore } = params const { provider, diff --git a/packages/next-auth/src/core/routes/providers.ts b/packages/next-auth/src/core/routes/providers.ts index 5a2bb979a8..f946538c5f 100644 --- a/packages/next-auth/src/core/routes/providers.ts +++ b/packages/next-auth/src/core/routes/providers.ts @@ -1,4 +1,4 @@ -import type { InternalResponse } from ".." +import type { OutgoingResponse } from ".." import type { InternalProvider } from "../types" export interface PublicProvider { @@ -16,7 +16,7 @@ export interface PublicProvider { */ export default function providers( providers: InternalProvider[] -): InternalResponse> { +): OutgoingResponse> { return { headers: [{ key: "Content-Type", value: "application/json" }], body: providers.reduce>( diff --git a/packages/next-auth/src/core/routes/session.ts b/packages/next-auth/src/core/routes/session.ts index aadfd45e22..8989bc7138 100644 --- a/packages/next-auth/src/core/routes/session.ts +++ b/packages/next-auth/src/core/routes/session.ts @@ -2,7 +2,7 @@ import { fromDate } from "../lib/utils" import type { Adapter } from "../../adapters" import type { InternalOptions } from "../types" -import type { InternalResponse } from ".." +import type { OutgoingResponse } from ".." import type { Session } from "../.." import type { SessionStore } from "../lib/cookie" @@ -18,7 +18,7 @@ interface SessionParams { export default async function session( params: SessionParams -): Promise> { +): Promise> { const { options, sessionStore } = params const { adapter, @@ -29,7 +29,7 @@ export default async function session( session: { strategy: sessionStrategy, maxAge: sessionMaxAge }, } = options - const response: InternalResponse = { + const response: OutgoingResponse = { body: {}, headers: [{ key: "Content-Type", value: "application/json" }], cookies: [], diff --git a/packages/next-auth/src/core/routes/signin.ts b/packages/next-auth/src/core/routes/signin.ts index 3be460d860..a314099532 100644 --- a/packages/next-auth/src/core/routes/signin.ts +++ b/packages/next-auth/src/core/routes/signin.ts @@ -1,16 +1,16 @@ import getAuthorizationUrl from "../lib/oauth/authorization-url" -import getUserFromEmail from "../lib/email/getUserFromEmail" import emailSignin from "../lib/email/signin" -import type { InternalRequest, InternalResponse } from ".." +import getUserFromEmail from "../lib/email/getUserFromEmail" +import type { RequestInternal, OutgoingResponse } from ".." import type { InternalOptions } from "../types" import type { Account } from "../.." /** Handle requests to /api/auth/signin */ export default async function signin(params: { options: InternalOptions<"oauth" | "email"> - query: InternalRequest["query"] - body: InternalRequest["body"] -}): Promise { + query: RequestInternal["query"] + body: RequestInternal["body"] +}): Promise { const { options, query, body } = params const { url, callbacks, logger, provider } = options diff --git a/packages/next-auth/src/core/routes/signout.ts b/packages/next-auth/src/core/routes/signout.ts index 5009a27bb5..69648aabcd 100644 --- a/packages/next-auth/src/core/routes/signout.ts +++ b/packages/next-auth/src/core/routes/signout.ts @@ -1,13 +1,13 @@ import type { Adapter } from "../../adapters" import type { InternalOptions } from "../types" -import type { InternalResponse } from ".." +import type { OutgoingResponse } from ".." import type { SessionStore } from "../lib/cookie" /** Handle requests to /api/auth/signout */ export default async function signout(params: { options: InternalOptions sessionStore: SessionStore -}): Promise { +}): Promise { const { options, sessionStore } = params const { adapter, events, jwt, callbackUrl, logger, session } = options diff --git a/packages/next-auth/src/providers/credentials.ts b/packages/next-auth/src/providers/credentials.ts index 055c1a7b80..4c17b2f393 100644 --- a/packages/next-auth/src/providers/credentials.ts +++ b/packages/next-auth/src/providers/credentials.ts @@ -1,4 +1,4 @@ -import type { InternalRequest } from "../core" +import type { RequestInternal } from "../core" import type { CommonProviderOptions } from "." import type { User, Awaitable } from ".." @@ -16,7 +16,7 @@ export interface CredentialsConfig< credentials: C authorize: ( credentials: Record | undefined, - req: Pick + req: Pick ) => Awaitable<(Omit | { id?: string }) | null> } diff --git a/packages/next-auth/tests/getServerSession.test.ts b/packages/next-auth/tests/getServerSession.test.ts index e76966c9a9..6643fa9bdd 100644 --- a/packages/next-auth/tests/getServerSession.test.ts +++ b/packages/next-auth/tests/getServerSession.test.ts @@ -46,7 +46,7 @@ describe("Treat secret correctly", () => { expect(logger.error).not.toBeCalledWith("NO_SECRET") }) - it.only("Error if missing NEXTAUTH_SECRET and secret", async () => { + it("Error if missing NEXTAUTH_SECRET and secret", async () => { const configError = new Error( "There is a problem with the server configuration. Check the server logs for more information." ) diff --git a/packages/next-auth/tests/lib.ts b/packages/next-auth/tests/lib.ts index 11a2f5c6b5..b74b98b363 100644 --- a/packages/next-auth/tests/lib.ts +++ b/packages/next-auth/tests/lib.ts @@ -1,4 +1,4 @@ -import { createHash } from "node:crypto" +import { createHash } from "crypto" import { AuthHandler } from "../src/core" import type { LoggerInstance, NextAuthOptions } from "../src" import type { Adapter } from "../src/adapters" From 7b46d1a3a571092ebd7da98bf3edef4ebdf01083 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Mon, 10 Oct 2022 04:38:10 +0200 Subject: [PATCH 34/93] simplify types --- packages/next-auth/src/client/_utils.ts | 6 ++-- packages/next-auth/src/core/index.ts | 17 +++++----- packages/next-auth/src/core/init.ts | 4 +-- packages/next-auth/src/core/lib/assert.ts | 4 +-- .../next-auth/src/core/lib/oauth/callback.ts | 2 +- .../core/lib/oauth/web/authorization-url.ts | 6 ++-- packages/next-auth/src/core/lib/spec.ts | 34 +++---------------- packages/next-auth/src/core/pages/index.ts | 4 +-- .../next-auth/src/core/routes/callback.ts | 4 +-- .../next-auth/src/core/routes/providers.ts | 4 +-- packages/next-auth/src/core/routes/session.ts | 6 ++-- packages/next-auth/src/core/routes/signin.ts | 4 +-- packages/next-auth/src/core/routes/signout.ts | 4 +-- packages/next-auth/src/core/types.ts | 23 ++----------- packages/next-auth/src/index.ts | 2 +- packages/next-auth/src/next/index.ts | 19 +++++------ packages/next-auth/src/next/middleware.ts | 11 +++--- packages/next-auth/src/react/index.tsx | 10 +++--- 18 files changed, 60 insertions(+), 104 deletions(-) diff --git a/packages/next-auth/src/client/_utils.ts b/packages/next-auth/src/client/_utils.ts index 7ccc07761c..79527a2a25 100644 --- a/packages/next-auth/src/client/_utils.ts +++ b/packages/next-auth/src/client/_utils.ts @@ -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 @@ -31,7 +31,7 @@ export interface CtxOrReq { */ export async function fetchData( path: string, - __NEXTAUTH: NextAuthClientConfig, + __NEXTAUTH: AuthClientConfig, logger: LoggerInstance, { ctx, req = ctx?.req }: CtxOrReq = {} ): Promise { @@ -50,7 +50,7 @@ export async function fetchData( } } -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}` diff --git a/packages/next-auth/src/core/index.ts b/packages/next-auth/src/core/index.ts index c1254b541e..a62f5e41d3 100644 --- a/packages/next-auth/src/core/index.ts +++ b/packages/next-auth/src/core/index.ts @@ -6,7 +6,7 @@ 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" @@ -18,22 +18,21 @@ export interface RequestInternal { headers?: Record query?: Record body?: Record - action: NextAuthAction + action: AuthAction providerId?: string error?: string } -export interface NextAuthHeader { +interface AuthHeader { key: string value: string } -// TODO: Rename to `ResponseInternal` -export interface OutgoingResponse< +export interface ResponseInternal< Body extends string | Record | any[] = any > { status?: number - headers?: NextAuthHeader[] + headers?: AuthHeader[] body?: Body redirect?: URL | string // TODO: refactor to only allow URL cookies?: Cookie[] @@ -43,10 +42,10 @@ async function AuthHandlerInternal< Body extends string | Record | any[] >(params: { req: RequestInternal - options: NextAuthOptions + options: AuthOptions /** REVIEW: Is this the best way to skip parsing the body in Node.js? */ parsedBody?: any -}): Promise> { +}): Promise> { const { options: userOptions, req } = params setLogger(userOptions.logger, userOptions.debug) @@ -271,7 +270,7 @@ async function AuthHandlerInternal< */ export async function AuthHandler( request: Request, - options: NextAuthOptions + options: AuthOptions ): Promise { const req = await toInternalRequest(request) const internalResponse = await AuthHandlerInternal({ req, options }) diff --git a/packages/next-auth/src/core/init.ts b/packages/next-auth/src/core/init.ts index b5fa4a72e4..f3fd7d0d9f 100644 --- a/packages/next-auth/src/core/init.ts +++ b/packages/next-auth/src/core/init.ts @@ -1,5 +1,5 @@ import { randomBytes, randomUUID } from "crypto" -import { NextAuthOptions } from ".." +import { AuthOptions } from ".." import logger from "../utils/logger" import parseUrl from "../utils/parse-url" import { adapterErrorHandler, eventsErrorHandler } from "./errors" @@ -16,7 +16,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. */ diff --git a/packages/next-auth/src/core/lib/assert.ts b/packages/next-auth/src/core/lib/assert.ts index afba4fb685..23e348e0e6 100644 --- a/packages/next-auth/src/core/lib/assert.ts +++ b/packages/next-auth/src/core/lib/assert.ts @@ -13,7 +13,7 @@ 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 @@ -45,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 diff --git a/packages/next-auth/src/core/lib/oauth/callback.ts b/packages/next-auth/src/core/lib/oauth/callback.ts index 60dde8a206..8033658d8e 100644 --- a/packages/next-auth/src/core/lib/oauth/callback.ts +++ b/packages/next-auth/src/core/lib/oauth/callback.ts @@ -149,7 +149,7 @@ export default async function oAuthCallback(params: { } } -export interface GetProfileParams { +interface GetProfileParams { profile: Profile tokens: TokenSet provider: OAuthConfigInternal diff --git a/packages/next-auth/src/core/lib/oauth/web/authorization-url.ts b/packages/next-auth/src/core/lib/oauth/web/authorization-url.ts index b1e3e6f115..095d5b4766 100644 --- a/packages/next-auth/src/core/lib/oauth/web/authorization-url.ts +++ b/packages/next-auth/src/core/lib/oauth/web/authorization-url.ts @@ -1,7 +1,7 @@ import * as o from "@panva/oauth4webapi" import type { CookiesOptions, InternalOptions } from "../../../types" -import type { InternalRequest, InternalResponse } from "../../.." +import type { RequestInternal, ResponseInternal } from "../../.." import type { Cookie } from "../../cookie" /** @@ -14,8 +14,8 @@ export default async function getAuthorizationUrl({ query, }: { options: InternalOptions<"oauth"> - query: InternalRequest["query"] -}): Promise { + query: RequestInternal["query"] +}): Promise { const { logger, provider } = options if (provider.version?.startsWith("1.")) { diff --git a/packages/next-auth/src/core/lib/spec.ts b/packages/next-auth/src/core/lib/spec.ts index 641168f1b3..3653b30f86 100644 --- a/packages/next-auth/src/core/lib/spec.ts +++ b/packages/next-auth/src/core/lib/spec.ts @@ -1,33 +1,7 @@ import { serialize, parse as parseCookie } from "cookie" import { detectHost } from "../../utils/detect-host" -import type { OutgoingResponse, RequestInternal } from ".." -import type { NextAuthAction } from "../types" - -const decoder = new TextDecoder() - -async function readJSONBody( - body: ReadableStream | Buffer -): Promise | undefined> { - try { - if (body instanceof ReadableStream) { - const reader = body.getReader() - const bytes: number[] = [] - while (true) { - const { value, done } = await reader.read() - if (done) break - bytes.push(...value) - } - const b = new Uint8Array(bytes) - return JSON.parse(decoder.decode(b)) - } - - // Handle `node-fetch` implementation of `body` - // We expect it to be a JSON.stringify'd object in a `Buffer` - return JSON.parse(body.toString()) - } catch (e) { - console.error(e) - } -} +import type { ResponseInternal, RequestInternal } from ".." +import type { AuthAction } from "../types" export async function toInternalRequest( req: Request @@ -46,7 +20,7 @@ export async function toInternalRequest( ) ?? {} return { - action: nextauth[0] as NextAuthAction, + action: nextauth[0] as AuthAction, method: req.method, headers, body: req.body ? await readJSONBody(req.body) : undefined, @@ -58,7 +32,7 @@ export async function toInternalRequest( } } -export function toResponse(res: OutgoingResponse): Response { +export function toResponse(res: ResponseInternal): Response { const headers = new Headers( res.headers?.reduce((acc, { key, value }) => { acc[key] = value diff --git a/packages/next-auth/src/core/pages/index.ts b/packages/next-auth/src/core/pages/index.ts index 9812497aa0..6938a4e016 100644 --- a/packages/next-auth/src/core/pages/index.ts +++ b/packages/next-auth/src/core/pages/index.ts @@ -6,7 +6,7 @@ import ErrorPage from "./error" import css from "../../css" import type { InternalOptions } from "../types" -import type { RequestInternal, OutgoingResponse } from ".." +import type { RequestInternal, ResponseInternal } from ".." import type { Cookie } from "../lib/cookie" import type { ErrorType } from "./error" @@ -27,7 +27,7 @@ type RenderPageParams = { export default function renderPage(params: RenderPageParams) { const { url, theme, query, cookies } = params - function send({ html, title, status }: any): OutgoingResponse { + function send({ html, title, status }: any): ResponseInternal { return { cookies, status, diff --git a/packages/next-auth/src/core/routes/callback.ts b/packages/next-auth/src/core/routes/callback.ts index b7a07581fd..a167ccbf95 100644 --- a/packages/next-auth/src/core/routes/callback.ts +++ b/packages/next-auth/src/core/routes/callback.ts @@ -4,7 +4,7 @@ import { hashToken } from "../lib/utils" import getUserFromEmail from "../lib/email/getUserFromEmail" import type { InternalOptions } from "../types" -import type { RequestInternal, OutgoingResponse } from ".." +import type { RequestInternal, ResponseInternal } from ".." import type { Cookie, SessionStore } from "../lib/cookie" import type { User } from "../.." import type { AdapterSession } from "../../adapters" @@ -18,7 +18,7 @@ export default async function callback(params: { headers: RequestInternal["headers"] cookies: RequestInternal["cookies"] sessionStore: SessionStore -}): Promise { +}): Promise { const { options, query, body, method, headers, sessionStore } = params const { provider, diff --git a/packages/next-auth/src/core/routes/providers.ts b/packages/next-auth/src/core/routes/providers.ts index f946538c5f..9ce34acd44 100644 --- a/packages/next-auth/src/core/routes/providers.ts +++ b/packages/next-auth/src/core/routes/providers.ts @@ -1,4 +1,4 @@ -import type { OutgoingResponse } from ".." +import type { ResponseInternal } from ".." import type { InternalProvider } from "../types" export interface PublicProvider { @@ -16,7 +16,7 @@ export interface PublicProvider { */ export default function providers( providers: InternalProvider[] -): OutgoingResponse> { +): ResponseInternal> { return { headers: [{ key: "Content-Type", value: "application/json" }], body: providers.reduce>( diff --git a/packages/next-auth/src/core/routes/session.ts b/packages/next-auth/src/core/routes/session.ts index 8989bc7138..73caccdd9b 100644 --- a/packages/next-auth/src/core/routes/session.ts +++ b/packages/next-auth/src/core/routes/session.ts @@ -2,7 +2,7 @@ import { fromDate } from "../lib/utils" import type { Adapter } from "../../adapters" import type { InternalOptions } from "../types" -import type { OutgoingResponse } from ".." +import type { ResponseInternal } from ".." import type { Session } from "../.." import type { SessionStore } from "../lib/cookie" @@ -18,7 +18,7 @@ interface SessionParams { export default async function session( params: SessionParams -): Promise> { +): Promise> { const { options, sessionStore } = params const { adapter, @@ -29,7 +29,7 @@ export default async function session( session: { strategy: sessionStrategy, maxAge: sessionMaxAge }, } = options - const response: OutgoingResponse = { + const response: ResponseInternal = { body: {}, headers: [{ key: "Content-Type", value: "application/json" }], cookies: [], diff --git a/packages/next-auth/src/core/routes/signin.ts b/packages/next-auth/src/core/routes/signin.ts index a314099532..786c8b87fa 100644 --- a/packages/next-auth/src/core/routes/signin.ts +++ b/packages/next-auth/src/core/routes/signin.ts @@ -1,7 +1,7 @@ import getAuthorizationUrl from "../lib/oauth/authorization-url" import emailSignin from "../lib/email/signin" import getUserFromEmail from "../lib/email/getUserFromEmail" -import type { RequestInternal, OutgoingResponse } from ".." +import type { RequestInternal, ResponseInternal } from ".." import type { InternalOptions } from "../types" import type { Account } from "../.." @@ -10,7 +10,7 @@ export default async function signin(params: { options: InternalOptions<"oauth" | "email"> query: RequestInternal["query"] body: RequestInternal["body"] -}): Promise { +}): Promise { const { options, query, body } = params const { url, callbacks, logger, provider } = options diff --git a/packages/next-auth/src/core/routes/signout.ts b/packages/next-auth/src/core/routes/signout.ts index 69648aabcd..5078d53fb5 100644 --- a/packages/next-auth/src/core/routes/signout.ts +++ b/packages/next-auth/src/core/routes/signout.ts @@ -1,13 +1,13 @@ import type { Adapter } from "../../adapters" import type { InternalOptions } from "../types" -import type { OutgoingResponse } from ".." +import type { ResponseInternal } from ".." import type { SessionStore } from "../lib/cookie" /** Handle requests to /api/auth/signout */ export default async function signout(params: { options: InternalOptions sessionStore: SessionStore -}): Promise { +}): Promise { const { options, sessionStore } = params const { adapter, events, jwt, callbackUrl, logger, session } = options diff --git a/packages/next-auth/src/core/types.ts b/packages/next-auth/src/core/types.ts index 8235471de7..477eb5de15 100644 --- a/packages/next-auth/src/core/types.ts +++ b/packages/next-auth/src/core/types.ts @@ -12,8 +12,6 @@ import type { JWT, JWTOptions } from "../jwt" import type { LoggerInstance } from "../utils/logger" import type { CookieSerializeOptions } from "cookie" -import type { NextApiRequest, NextApiResponse } from "next" - import type { InternalUrl } from "../utils/parse-url" export type Awaitable = T | PromiseLike @@ -25,7 +23,7 @@ export type { LoggerInstance } * * [Documentation](https://next-auth.js.org/configuration/options#options) */ -export interface NextAuthOptions { +export interface AuthOptions { /** * An array of authentication providers for signing in * (e.g. Google, Facebook, Twitter, GitHub, Email, etc) in any order. @@ -503,7 +501,7 @@ export type InternalProvider = (T extends "oauth" callbackUrl: string } -export type NextAuthAction = +export type AuthAction = | "providers" | "session" | "csrf" @@ -525,7 +523,7 @@ export interface InternalOptions< * @default "http://localhost:3000/api/auth" */ url: InternalUrl - action: NextAuthAction + action: AuthAction provider: InternalProvider csrfToken?: string csrfTokenVerified?: boolean @@ -544,18 +542,3 @@ export interface InternalOptions< cookies: CookiesOptions callbackUrl: string } - -/** @internal */ -export interface NextAuthRequest extends NextApiRequest { - options: InternalOptions -} - -/** @internal */ -export type NextAuthResponse = NextApiResponse - -/** @internal */ -// eslint-disable-next-line @typescript-eslint/no-invalid-void-type -export type NextAuthApiHandler = ( - req: NextAuthRequest, - res: NextAuthResponse -) => Awaitable diff --git a/packages/next-auth/src/index.ts b/packages/next-auth/src/index.ts index f27a296aaf..1efe460d51 100644 --- a/packages/next-auth/src/index.ts +++ b/packages/next-auth/src/index.ts @@ -1,4 +1,4 @@ export * from "./core/types" - +export type { AuthOptions as NextAuthOptions } from "./core/types" export * from "./next" export { default } from "./next" diff --git a/packages/next-auth/src/next/index.ts b/packages/next-auth/src/next/index.ts index ff27179800..710bc6992d 100644 --- a/packages/next-auth/src/next/index.ts +++ b/packages/next-auth/src/next/index.ts @@ -7,13 +7,12 @@ import type { NextApiRequest, NextApiResponse, } from "next" -import type { NextAuthOptions, Session } from ".." -import type { NextAuthRequest, NextAuthResponse } from "../core/types" +import type { AuthOptions, Session } from ".." async function NextAuthHandler( req: NextApiRequest, res: NextApiResponse, - options: NextAuthOptions + options: AuthOptions ) { options.secret ??= options.jwt?.secret ?? process.env.NEXTAUTH_SECRET @@ -47,21 +46,19 @@ async function NextAuthHandler( return res.send(body) } -function NextAuth(options: NextAuthOptions): any +function NextAuth(options: AuthOptions): any function NextAuth( req: NextApiRequest, res: NextApiResponse, - options: NextAuthOptions + options: AuthOptions ): any /** The main entry point to next-auth */ function NextAuth( - ...args: - | [NextAuthOptions] - | [NextApiRequest, NextApiResponse, NextAuthOptions] + ...args: [AuthOptions] | [NextApiRequest, NextApiResponse, AuthOptions] ) { if (args.length === 1) { - return async (req: NextAuthRequest, res: NextAuthResponse) => + return async (req: NextApiRequest, res: NextApiResponse) => await NextAuthHandler(req, res, args[0]) } @@ -76,9 +73,9 @@ export async function unstable_getServerSession( | [ GetServerSidePropsContext["req"], GetServerSidePropsContext["res"], - NextAuthOptions + AuthOptions ] - | [NextApiRequest, NextApiResponse, NextAuthOptions] + | [NextApiRequest, NextApiResponse, AuthOptions] ): Promise { if (!experimentalWarningShown && process.env.NODE_ENV !== "production") { console.warn( diff --git a/packages/next-auth/src/next/middleware.ts b/packages/next-auth/src/next/middleware.ts index 7a949d10f7..00fd0579fa 100644 --- a/packages/next-auth/src/next/middleware.ts +++ b/packages/next-auth/src/next/middleware.ts @@ -1,5 +1,5 @@ import type { NextMiddleware, NextFetchEvent } from "next/server" -import type { Awaitable, CookieOption, NextAuthOptions } from ".." +import type { Awaitable, CookieOption, AuthOptions } from ".." import type { JWT, JWTOptions } from "../jwt" import { NextResponse, NextRequest } from "next/server" @@ -20,7 +20,7 @@ export interface NextAuthMiddlewareOptions { * --- * [Documentation](https://next-auth.js.org/configuration/pages) */ - pages?: NextAuthOptions["pages"] + pages?: AuthOptions["pages"] /** * You can override the default cookie names and options for any of the cookies @@ -38,7 +38,7 @@ export interface NextAuthMiddlewareOptions { */ cookies?: Partial< Record< - keyof Pick, + keyof Pick, Omit > > @@ -142,7 +142,10 @@ async function handleMiddleware( // the user is not logged in, redirect to the sign-in page const signInUrl = new URL(`${basePath}${signInPage}`, origin) - signInUrl.searchParams.append("callbackUrl", `${basePath}${pathname}${search}`) + signInUrl.searchParams.append( + "callbackUrl", + `${basePath}${pathname}${search}` + ) return NextResponse.redirect(signInUrl) } diff --git a/packages/next-auth/src/react/index.tsx b/packages/next-auth/src/react/index.tsx index 3964c70546..c3d98df04a 100644 --- a/packages/next-auth/src/react/index.tsx +++ b/packages/next-auth/src/react/index.tsx @@ -18,7 +18,7 @@ import { apiBaseUrl, fetchData, now, - NextAuthClientConfig, + AuthClientConfig, } from "../client/_utils" import type { @@ -46,7 +46,7 @@ export * from "./types" // relative URLs are valid in that context and so defaults to empty. // 2. When invoked server side the value is picked up from an environment // variable and defaults to 'http://localhost:3000'. -const __NEXTAUTH: NextAuthClientConfig = { +const __NEXTAUTH: AuthClientConfig = { baseUrl: parseUrl(process.env.NEXTAUTH_URL ?? process.env.VERCEL_URL).origin, basePath: parseUrl(process.env.NEXTAUTH_URL).path, baseUrlServer: parseUrl( @@ -74,9 +74,9 @@ export type SessionContextValue = R extends true | { data: Session; status: "authenticated" } | { data: null; status: "unauthenticated" | "loading" } -export const SessionContext = React.createContext( - undefined -) +export const SessionContext = React.createContext< + SessionContextValue | undefined +>(undefined) /** * React Hook that gives you access From 79d629947434779819f0b2b2d3f3979b48fba925 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Mon, 10 Oct 2022 04:46:08 +0200 Subject: [PATCH 35/93] refactor `crypto` usage In Node.js, inject `globalThis.crypto` instead of import --- packages/next-auth/src/core/init.ts | 22 +++++++----- packages/next-auth/src/core/lib/csrf-token.ts | 19 +++++----- .../next-auth/src/core/lib/email/signin.ts | 9 +++-- packages/next-auth/src/core/lib/utils.ts | 36 ------------------- .../next-auth/src/core/routes/callback.ts | 5 +-- packages/next-auth/src/next/index.ts | 1 + packages/next-auth/src/next/inject-globals.ts | 29 +++++++++++++++ 7 files changed, 60 insertions(+), 61 deletions(-) create mode 100644 packages/next-auth/src/next/inject-globals.ts diff --git a/packages/next-auth/src/core/init.ts b/packages/next-auth/src/core/init.ts index f3fd7d0d9f..122b6796d7 100644 --- a/packages/next-auth/src/core/init.ts +++ b/packages/next-auth/src/core/init.ts @@ -1,10 +1,9 @@ -import { randomBytes, randomUUID } from "crypto" +import { createHash, randomUUID } from "./lib/spec" 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" @@ -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, @@ -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 @@ -119,7 +125,7 @@ export async function init({ csrfToken, cookie: csrfCookie, csrfTokenVerified, - } = createCSRFToken({ + } = await createCSRFToken({ options, cookieValue: reqCookies?.[options.cookies.csrfToken.name], isPost, diff --git a/packages/next-auth/src/core/lib/csrf-token.ts b/packages/next-auth/src/core/lib/csrf-token.ts index cd61492218..120511d853 100644 --- a/packages/next-auth/src/core/lib/csrf-token.ts +++ b/packages/next-auth/src/core/lib/csrf-token.ts @@ -1,5 +1,4 @@ -import { createHash, randomBytes } from "crypto" - +import { createHash, randomString } from "./spec" import type { InternalOptions } from "../types" interface CreateCSRFTokenParams { @@ -23,7 +22,7 @@ interface CreateCSRFTokenParams { * https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#double-submit-cookie * https://owasp.org/www-chapter-london/assets/slides/David_Johansson-Double_Defeat_of_Double-Submit_Cookie.pdf */ -export function createCSRFToken({ +export async function createCSRFToken({ options, cookieValue, isPost, @@ -31,9 +30,11 @@ export function createCSRFToken({ }: CreateCSRFTokenParams) { if (cookieValue) { const [csrfToken, csrfTokenHash] = cookieValue.split("|") - const expectedCsrfTokenHash = createHash("sha256") - .update(`${csrfToken}${options.secret}`) - .digest("hex") + + const expectedCsrfTokenHash = await createHash( + `${csrfToken}${options.secret}` + ) + if (csrfTokenHash === expectedCsrfTokenHash) { // If hash matches then we trust the CSRF token value // If this is a POST request and the CSRF Token in the POST request matches @@ -45,10 +46,8 @@ export function createCSRFToken({ } // New CSRF token - const csrfToken = randomBytes(32).toString("hex") - const csrfTokenHash = createHash("sha256") - .update(`${csrfToken}${options.secret}`) - .digest("hex") + const csrfToken = randomString(32) + const csrfTokenHash = await createHash(`${csrfToken}${options.secret}`) const cookie = `${csrfToken}|${csrfTokenHash}` return { cookie, csrfToken } diff --git a/packages/next-auth/src/core/lib/email/signin.ts b/packages/next-auth/src/core/lib/email/signin.ts index fa4ba15143..b074d86bd8 100644 --- a/packages/next-auth/src/core/lib/email/signin.ts +++ b/packages/next-auth/src/core/lib/email/signin.ts @@ -1,5 +1,4 @@ -import { randomBytes } from "crypto" -import { hashToken } from "../utils" +import { randomString, createHash } from "../spec" import type { InternalOptions } from "../../types" /** @@ -13,8 +12,7 @@ export default async function email( const { url, adapter, provider, callbackUrl, theme } = options // Generate token const token = - (await provider.generateVerificationToken?.()) ?? - randomBytes(32).toString("hex") + (await provider.generateVerificationToken?.()) ?? randomString(32) const ONE_DAY_IN_SECONDS = 86400 const expires = new Date( @@ -25,6 +23,7 @@ export default async function email( const params = new URLSearchParams({ callbackUrl, token, email: identifier }) const _url = `${url}/callback/${provider.id}?${params}` + const secret = provider.secret ?? options.secret await Promise.all([ // Send to user provider.sendVerificationRequest({ @@ -38,7 +37,7 @@ export default async function email( // Save in database adapter.createVerificationToken({ identifier, - token: hashToken(token, options), + token: await createHash(`${token}${secret}`), expires, }), ]) diff --git a/packages/next-auth/src/core/lib/utils.ts b/packages/next-auth/src/core/lib/utils.ts index 9d61a7b7a8..79894224d9 100644 --- a/packages/next-auth/src/core/lib/utils.ts +++ b/packages/next-auth/src/core/lib/utils.ts @@ -1,9 +1,3 @@ -import { createHash } from "crypto" - -import type { NextAuthOptions } from "../.." -import type { InternalOptions } from "../types" -import type { InternalUrl } from "../../utils/parse-url" - /** * Takes a number in seconds and returns the date in the future. * Optionally takes a second date parameter. In that case @@ -12,33 +6,3 @@ import type { InternalUrl } from "../../utils/parse-url" export function fromDate(time: number, date = Date.now()) { return new Date(date + time * 1000) } - -export function hashToken(token: string, options: InternalOptions<"email">) { - const { provider, secret } = options - return ( - createHash("sha256") - // Prefer provider specific secret, but use default secret if none specified - .update(`${token}${provider.secret ?? secret}`) - .digest("hex") - ) -} - -/** - * Secret used 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. */ -export function createSecret(params: { - userOptions: NextAuthOptions - url: InternalUrl -}) { - const { userOptions, url } = params - - return ( - userOptions.secret ?? - // TODO: Remove falling back to default secret, and error in dev if one isn't provided - createHash("sha256") - .update(JSON.stringify({ ...url, ...userOptions })) - .digest("hex") - ) -} diff --git a/packages/next-auth/src/core/routes/callback.ts b/packages/next-auth/src/core/routes/callback.ts index a167ccbf95..1af8f50909 100644 --- a/packages/next-auth/src/core/routes/callback.ts +++ b/packages/next-auth/src/core/routes/callback.ts @@ -1,6 +1,6 @@ import oAuthCallback from "../lib/oauth/callback" import callbackHandler from "../lib/callback-handler" -import { hashToken } from "../lib/utils" +import { createHash } from "../lib/spec" import getUserFromEmail from "../lib/email/getUserFromEmail" import type { InternalOptions } from "../types" @@ -206,10 +206,11 @@ export default async function callback(params: { return { redirect: `${url}/error?error=configuration`, cookies } } + const secret = provider.secret ?? options.secret // @ts-expect-error -- Verified in `assertConfig`. adapter: Adapter const invite = await adapter.useVerificationToken({ identifier, - token: hashToken(token, options), + token: await createHash(`${token}${secret}`), }) const invalidInvite = !invite || invite.expires.valueOf() < Date.now() diff --git a/packages/next-auth/src/next/index.ts b/packages/next-auth/src/next/index.ts index 710bc6992d..f2fec5ab74 100644 --- a/packages/next-auth/src/next/index.ts +++ b/packages/next-auth/src/next/index.ts @@ -1,3 +1,4 @@ +import "./inject-globals" import { AuthHandler } from "../core" import { detectHost } from "../utils/detect-host" import { getBody } from "./utils" diff --git a/packages/next-auth/src/next/inject-globals.ts b/packages/next-auth/src/next/inject-globals.ts new file mode 100644 index 0000000000..9551fd4b5e --- /dev/null +++ b/packages/next-auth/src/next/inject-globals.ts @@ -0,0 +1,29 @@ +import * as crypto from "crypto" + +declare global { + interface Crypto { + createHash?: typeof crypto.createHash + randomBytes?: typeof crypto.randomBytes + // @ts-expect-error + subtle?: SubtleCrypto + // @ts-expect-error + getRandomValues?: < + T extends + | Int8Array + | Int16Array + | Int32Array + | Uint8Array + | Uint16Array + | Uint32Array + | Uint8ClampedArray + | Float32Array + | Float64Array + | DataView + | null + >( + array: T + ) => T + } +} + +if (!globalThis.crypto) globalThis.crypto = crypto From 68d53876eacb809d01247b2aaaee1a2a0648b0b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Mon, 10 Oct 2022 04:49:42 +0200 Subject: [PATCH 36/93] add `next-auth/web` --- .gitignore | 1 + packages/next-auth/package.json | 6 +- packages/next-auth/src/core/index.ts | 2 +- packages/next-auth/src/core/init.ts | 2 +- packages/next-auth/src/core/lib/csrf-token.ts | 2 +- .../next-auth/src/core/lib/email/signin.ts | 2 +- .../core/lib/oauth/web/authorization-url.ts | 2 +- packages/next-auth/src/core/lib/spec.ts | 68 ---------- packages/next-auth/src/core/lib/web.ts | 125 ++++++++++++++++++ .../next-auth/src/core/routes/callback.ts | 2 +- packages/next-auth/src/next/utils.ts | 13 +- packages/next-auth/src/web/index.ts | 41 ++++++ 12 files changed, 184 insertions(+), 82 deletions(-) delete mode 100644 packages/next-auth/src/core/lib/spec.ts create mode 100644 packages/next-auth/src/core/lib/web.ts create mode 100644 packages/next-auth/src/web/index.ts diff --git a/.gitignore b/.gitignore index 530cba5fdd..9e741d37f9 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/packages/next-auth/package.json b/packages/next-auth/package.json index c4a03e51cf..da5f7763de 100644 --- a/packages/next-auth/package.json +++ b/packages/next-auth/package.json @@ -32,6 +32,7 @@ "./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" @@ -39,7 +40,7 @@ "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", @@ -63,7 +64,8 @@ "adapters.d.ts", "middleware.d.ts", "middleware.js", - "utils" + "utils", + "web" ], "license": "ISC", "dependencies": { diff --git a/packages/next-auth/src/core/index.ts b/packages/next-auth/src/core/index.ts index a62f5e41d3..37934b0a57 100644 --- a/packages/next-auth/src/core/index.ts +++ b/packages/next-auth/src/core/index.ts @@ -1,5 +1,5 @@ import logger, { setLogger } from "../utils/logger" -import { toInternalRequest, toResponse } from "./lib/spec" +import { toInternalRequest, toResponse } from "./lib/web" import * as routes from "./routes" import renderPage from "./pages" import { init } from "./init" diff --git a/packages/next-auth/src/core/init.ts b/packages/next-auth/src/core/init.ts index 122b6796d7..8740e4d619 100644 --- a/packages/next-auth/src/core/init.ts +++ b/packages/next-auth/src/core/init.ts @@ -1,4 +1,4 @@ -import { createHash, randomUUID } from "./lib/spec" +import { createHash, randomUUID } from "./lib/web" import { AuthOptions } from ".." import logger from "../utils/logger" import parseUrl from "../utils/parse-url" diff --git a/packages/next-auth/src/core/lib/csrf-token.ts b/packages/next-auth/src/core/lib/csrf-token.ts index 120511d853..15f75726ca 100644 --- a/packages/next-auth/src/core/lib/csrf-token.ts +++ b/packages/next-auth/src/core/lib/csrf-token.ts @@ -1,4 +1,4 @@ -import { createHash, randomString } from "./spec" +import { createHash, randomString } from "./web" import type { InternalOptions } from "../types" interface CreateCSRFTokenParams { diff --git a/packages/next-auth/src/core/lib/email/signin.ts b/packages/next-auth/src/core/lib/email/signin.ts index b074d86bd8..41b86aa3a8 100644 --- a/packages/next-auth/src/core/lib/email/signin.ts +++ b/packages/next-auth/src/core/lib/email/signin.ts @@ -1,4 +1,4 @@ -import { randomString, createHash } from "../spec" +import { randomString, createHash } from "../web" import type { InternalOptions } from "../../types" /** diff --git a/packages/next-auth/src/core/lib/oauth/web/authorization-url.ts b/packages/next-auth/src/core/lib/oauth/web/authorization-url.ts index 095d5b4766..31a736bc72 100644 --- a/packages/next-auth/src/core/lib/oauth/web/authorization-url.ts +++ b/packages/next-auth/src/core/lib/oauth/web/authorization-url.ts @@ -70,7 +70,7 @@ export default async function getAuthorizationUrl({ if (as && !as.code_challenge_methods_supported?.includes("S256")) { // We assume S256 PKCE support, if the server does not advertise that, // a random `nonce` must be used for CSRF protection. - provider.checks = "nonce" + provider.checks = ["nonce"] } else { const { code_challenge, pkce } = await createPKCE(options) authParams.set("code_challenge", code_challenge) diff --git a/packages/next-auth/src/core/lib/spec.ts b/packages/next-auth/src/core/lib/spec.ts deleted file mode 100644 index 3653b30f86..0000000000 --- a/packages/next-auth/src/core/lib/spec.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { serialize, parse as parseCookie } from "cookie" -import { detectHost } from "../../utils/detect-host" -import type { ResponseInternal, RequestInternal } from ".." -import type { AuthAction } from "../types" - -export async function toInternalRequest( - req: Request -): Promise { - const url = new URL(req.url) - const nextauth = url.pathname.split("/").slice(3) - const headers = Object.fromEntries(req.headers.entries()) - const query: Record = Object.fromEntries( - url.searchParams.entries() - ) - - const cookieHeader = req.headers.get("cookie") ?? "" - const cookies = - parseCookie( - Array.isArray(cookieHeader) ? cookieHeader.join(";") : cookieHeader - ) ?? {} - - return { - action: nextauth[0] as AuthAction, - method: req.method, - headers, - body: req.body ? await readJSONBody(req.body) : undefined, - cookies: cookies, - providerId: nextauth[1], - error: url.searchParams.get("error") ?? undefined, - host: detectHost(headers["x-forwarded-host"] ?? headers.host), - query, - } -} - -export function toResponse(res: ResponseInternal): Response { - const headers = new Headers( - res.headers?.reduce((acc, { key, value }) => { - acc[key] = value - return acc - }, {}) - ) - - res.cookies?.forEach((cookie) => { - const { name, value, options } = cookie - const cookieHeader = serialize(name, value, options) - if (headers.has("Set-Cookie")) { - headers.append("Set-Cookie", cookieHeader) - } else { - headers.set("Set-Cookie", cookieHeader) - } - }) - - const body = - headers.get("content-type") === "application/json" - ? JSON.stringify(res.body) - : res.body - - const response = new Response(body, { - headers, - status: res.redirect ? 301 : res.status ?? 200, - }) - - if (res.redirect) { - response.headers.set("Location", res.redirect.toString()) - } - - return response -} diff --git a/packages/next-auth/src/core/lib/web.ts b/packages/next-auth/src/core/lib/web.ts new file mode 100644 index 0000000000..97f0a3066e --- /dev/null +++ b/packages/next-auth/src/core/lib/web.ts @@ -0,0 +1,125 @@ +import { serialize, parse as parseCookie } from "cookie" +import { detectHost } from "../../utils/detect-host" +import type { ResponseInternal, RequestInternal } from ".." +import type { AuthAction } from "../types" + +export async function toInternalRequest( + req: Request +): Promise { + const url = new URL(req.url) + const nextauth = url.pathname.split("/").slice(3) + const headers = Object.fromEntries(req.headers.entries()) + const query: Record = Object.fromEntries( + url.searchParams.entries() + ) + + const cookieHeader = req.headers.get("cookie") ?? "" + const cookies = + parseCookie( + Array.isArray(cookieHeader) ? cookieHeader.join(";") : cookieHeader + ) ?? {} + + return { + action: nextauth[0] as AuthAction, + method: req.method, + headers, + body: await getBody(req), + cookies: cookies, + providerId: nextauth[1], + error: url.searchParams.get("error") ?? undefined, + host: detectHost(headers["x-forwarded-host"] ?? headers.host), + query, + } +} + +export function toResponse(res: ResponseInternal): Response { + const headers = new Headers( + res.headers?.reduce((acc, { key, value }) => { + acc[key] = value + return acc + }, {}) + ) + + res.cookies?.forEach((cookie) => { + const { name, value, options } = cookie + const cookieHeader = serialize(name, value, options) + if (headers.has("Set-Cookie")) { + headers.append("Set-Cookie", cookieHeader) + } else { + headers.set("Set-Cookie", cookieHeader) + } + }) + + const body = + headers.get("content-type") === "application/json" + ? JSON.stringify(res.body) + : res.body + + const response = new Response(body, { + headers, + status: res.redirect ? 301 : res.status ?? 200, + }) + + if (res.redirect) { + response.headers.set("Location", res.redirect.toString()) + } + + return response +} + +/** Web/Node.js compatible method to create a hash, using SHA256 */ +export async function createHash(message) { + if (crypto.createHash) { + return crypto.createHash("sha256").update(message).digest("hex") + } + if (crypto.subtle) { + const data = new TextEncoder().encode(message) + const hash = await crypto.subtle.digest("SHA-256", data) + return Array.from(new Uint8Array(hash)) + .map((b) => b.toString(16).padStart(2, "0")) + .join("") + .toString() + } + + throw new TypeError("`crypto` module is missing method(s) to create hash") +} + +/** Web/Node.js compatible method to create a random string of a given length */ +export function randomString(size: number) { + if (crypto.getRandomValues) { + const i2hex = (i: number) => ("0" + i.toString(16)).slice(-2) + const r = (a: string, i: number): string => a + i2hex(i) + const bytes = crypto.getRandomValues(new Uint8Array(size)) + return Array.from(bytes).reduce(r, "") + } + if (crypto.randomBytes) { + return crypto.randomBytes(size).toString("hex") + } + + throw new TypeError( + "`crypto` module is missing method(s) to create random bytes" + ) +} + +/** Web/Node.js compatible method to create a random UUID */ +export function randomUUID() { + if (crypto.randomUUID) return crypto.randomUUID() + else if (crypto.randomBytes) return crypto.randomBytes(32).toString("hex") + throw new TypeError( + "`crypto` module is missing method(s) to create random UUID" + ) +} + +async function getBody(req: Request): Promise | undefined> { + if (!("body" in req) || !req.body || req.method !== "POST") { + return + } + + const contentType = req.headers.get("content-type") + if (contentType?.includes("application/json")) { + return await req.json() + } else if (contentType?.includes("application/x-www-form-urlencoded")) { + const params = new URLSearchParams(await req.text()) + return Object.fromEntries(params.entries()) + } +} diff --git a/packages/next-auth/src/core/routes/callback.ts b/packages/next-auth/src/core/routes/callback.ts index 1af8f50909..d6b85f6e61 100644 --- a/packages/next-auth/src/core/routes/callback.ts +++ b/packages/next-auth/src/core/routes/callback.ts @@ -1,6 +1,6 @@ import oAuthCallback from "../lib/oauth/callback" import callbackHandler from "../lib/callback-handler" -import { createHash } from "../lib/spec" +import { createHash } from "../lib/web" import getUserFromEmail from "../lib/email/getUserFromEmail" import type { InternalOptions } from "../types" diff --git a/packages/next-auth/src/next/utils.ts b/packages/next-auth/src/next/utils.ts index aad9601283..3faa5b2180 100644 --- a/packages/next-auth/src/next/utils.ts +++ b/packages/next-auth/src/next/utils.ts @@ -1,5 +1,4 @@ import type { GetServerSidePropsContext, NextApiRequest } from "next" -import type { NextRequest } from "next/server" export function setCookie(res, value: string) { // Preserve any existing cookies that have already been set in the same session @@ -13,14 +12,16 @@ export function setCookie(res, value: string) { } export function getBody( - req: NextApiRequest | NextRequest | GetServerSidePropsContext["req"] -) { + req: NextApiRequest | GetServerSidePropsContext["req"] +): { body: RequestInit["body"] } | undefined { if (!("body" in req) || !req.body || req.method !== "POST") { return } - if (req.body instanceof ReadableStream) { - return { body: req.body } + const contentType = req.headers?.["content-type"] + if (contentType?.includes("application/json")) { + return { body: JSON.stringify(req.body) } + } else if (contentType?.includes("application/x-www-form-urlencoded")) { + return { body: new URLSearchParams(req.body) } } - return { body: JSON.stringify(req.body) } } diff --git a/packages/next-auth/src/web/index.ts b/packages/next-auth/src/web/index.ts new file mode 100644 index 0000000000..9ed33e2dd5 --- /dev/null +++ b/packages/next-auth/src/web/index.ts @@ -0,0 +1,41 @@ +import { AuthHandler } from "../core" +import { detectHost } from "../utils/detect-host" + +import type { AuthOptions } from ".." +export * from "../core/types" + +async function WebAuthHandler(req: Request, options: AuthOptions) { + options.secret ??= options.jwt?.secret ?? process.env.NEXTAUTH_SECRET + + const host = detectHost(req.headers["x-forwarded-host"]) + const url = new URL(req.url ?? "", host) + + const res = await AuthHandler(new Request(url, req), options) + + // If the request expects a return URL, send it as JSON + // instead of doing an actual redirect. + const redirect = res.headers.get("Location") + if (req.headers.get("X-Auth-Return-Redirect") && redirect) { + res.headers.delete("Location") + return new Response(JSON.stringify({ url: redirect }), res) + } + + return res +} + +function Auth(options: AuthOptions): any +function Auth(req: Request, options: AuthOptions): any + +/** The main entry point to next-auth/web */ +function Auth(...args: [AuthOptions] | [Request, AuthOptions]) { + if (!args.length || args[0] instanceof Request) { + // @ts-expect-error + return WebAuthHandler(...args) + } + if (args.length === 1) + return async (req: Request) => await WebAuthHandler(req, args[0]) + + return WebAuthHandler(args[0], args[1]) +} + +export default Auth From a063349533413c5c662f9fd6e10b2c4efda951ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Mon, 10 Oct 2022 04:50:34 +0200 Subject: [PATCH 37/93] refactor --- packages/next-auth/src/core/lib/providers.ts | 33 ++++++++------------ 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/packages/next-auth/src/core/lib/providers.ts b/packages/next-auth/src/core/lib/providers.ts index 51dacef003..fbcfe77734 100644 --- a/packages/next-auth/src/core/lib/providers.ts +++ b/packages/next-auth/src/core/lib/providers.ts @@ -45,38 +45,31 @@ export default function parseProviders(params: { } } -/** - * Transform OAuth options `authorization`, `token` and `profile` to `{ url: URL }` - */ function normalizeOAuth( - config?: OAuthConfig | OAuthUserConfig + c?: OAuthConfig | OAuthUserConfig ): OAuthConfigInternal | {} { - if (!config) return {} + if (!c) return {} - const authorization = normalizeEndpoint(config.authorization) - const token = normalizeEndpoint(config.token) - const userinfo = normalizeEndpoint(config.userinfo) + const authorization = normalizeEndpoint(c.authorization) - if (!config.version?.startsWith("1.")) { - config.idToken = - config.idToken ?? + if (!c.version?.startsWith("1.")) { + c.idToken ??= // If a provider has as an "openid-configuration" well-known endpoint - config.wellKnown?.includes("openid-configuration") ?? + c.wellKnown?.includes("openid-configuration") ?? // or an "openid" scope request, it will also likely be return an `id_token` authorization?.url.searchParams.get("scope")?.includes("openid") // Set default check to state - config.checks = Array.isArray(config.checks) - ? config.checks - : [config.checks ?? "state"] + c.checks ??= ["state"] + if (!Array.isArray(c.checks)) c.checks = [c.checks] } return { - ...config, - authorization, - token, - userinfo, - } as unknown as OAuthConfigInternal + ...c, + ...(authorization ? { authorization } : undefined), + ...(c.token ? { token: normalizeEndpoint(c.token) } : undefined), + ...(c.userinfo ? { userinfo: normalizeEndpoint(c.userinfo) } : undefined), + } } function normalizeEndpoint( From 697443df7a782649f759c3bfbb2adc08922be62e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Mon, 10 Oct 2022 04:51:18 +0200 Subject: [PATCH 38/93] send header instead of body to indicate redirect response --- packages/next-auth/src/next/index.ts | 2 +- packages/next-auth/src/react/index.tsx | 15 +++++++-------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/packages/next-auth/src/next/index.ts b/packages/next-auth/src/next/index.ts index f2fec5ab74..c73c37643a 100644 --- a/packages/next-auth/src/next/index.ts +++ b/packages/next-auth/src/next/index.ts @@ -39,7 +39,7 @@ async function NextAuthHandler( // If the request expects a return URL, send it as JSON // instead of doing an actual redirect. const redirect = headers.get("Location") - if (req.body?.json === "true" && redirect) { + if (req.headers["x-auth-return-redirect"] && redirect) { res.removeHeader("Location") return res.json({ url: redirect }) } diff --git a/packages/next-auth/src/react/index.tsx b/packages/next-auth/src/react/index.tsx index c3d98df04a..c2649b9dc9 100644 --- a/packages/next-auth/src/react/index.tsx +++ b/packages/next-auth/src/react/index.tsx @@ -1,11 +1,10 @@ // Note about signIn() and signOut() methods: // -// On signIn() and signOut() we pass 'json: true' to request a response in JSON -// instead of HTTP as redirect URLs on other domains are not returned to -// requests made using the fetch API in the browser, and we need to ask the API -// to return the response as a JSON object (the end point still defaults to -// returning an HTTP response with a redirect for non-JavaScript clients). -// +// On signIn() and signOut() we pass a "X-Auth-Return-Redirect" header +// to request a response in JSON instead of HTTP as redirect URLs on other domains +// are not returned to requests made using the fetch API in the browser, +// and we need to ask the API to return the response as a JSON object +// (the endpoint still defaults to returning an HTTP response with a redirect for non-JavaScript clients). // We use HTTP POST requests with CSRF Tokens to protect against CSRF attacks. import * as React from "react" @@ -216,13 +215,13 @@ export async function signIn< method: "post", headers: { "Content-Type": "application/x-www-form-urlencoded", + "X-Auth-Return-Redirect": "1", }, // @ts-expect-error body: new URLSearchParams({ ...options, csrfToken: await getCsrfToken(), callbackUrl, - json: true, }), }) @@ -266,12 +265,12 @@ export async function signOut( method: "post", headers: { "Content-Type": "application/x-www-form-urlencoded", + "X-Auth-Return-Redirect": "1", }, // @ts-expect-error body: new URLSearchParams({ csrfToken: await getCsrfToken(), callbackUrl, - json: true, }), } const res = await fetch(`${baseUrl}/signout`, fetchOptions) From 119de7eeb279bf50bfdf3ccdae95c055ebd82a04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Mon, 10 Oct 2022 05:15:36 +0200 Subject: [PATCH 39/93] fix eslint --- packages/next-auth/src/core/lib/oauth/client-legacy.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/next-auth/src/core/lib/oauth/client-legacy.ts b/packages/next-auth/src/core/lib/oauth/client-legacy.ts index b6eb1f71c8..a809012f6d 100644 --- a/packages/next-auth/src/core/lib/oauth/client-legacy.ts +++ b/packages/next-auth/src/core/lib/oauth/client-legacy.ts @@ -13,7 +13,7 @@ export function oAuth1Client(options: InternalOptions<"oauth">) { const oauth1Client = new OAuth( provider.requestTokenUrl as string, provider.accessTokenUrl as string, - provider.clientId as string, + provider.clientId, provider.clientSecret as string, provider.version ?? "1.0", provider.callbackUrl, From 7a47d68f5ed487826eecc439deb13b072d3d5ab7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Mon, 10 Oct 2022 05:35:43 +0200 Subject: [PATCH 40/93] fix tests --- packages/next-auth/tests/email.test.ts | 10 +++---- .../next-auth/tests/getServerSession.test.ts | 26 +++++++++---------- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/packages/next-auth/tests/email.test.ts b/packages/next-auth/tests/email.test.ts index 6c7e4a2dd9..3785770643 100644 --- a/packages/next-auth/tests/email.test.ts +++ b/packages/next-auth/tests/email.test.ts @@ -3,6 +3,7 @@ import EmailProvider from "../src/providers/email" it("Send e-mail to the only address correctly", async () => { const { secret, csrf } = await createCSRF() + const sendVerificationRequest = jest.fn() const signIn = jest.fn(() => true) @@ -18,7 +19,7 @@ it("Send e-mail to the only address correctly", async () => { path: "signin/email", requestInit: { method: "POST", - headers: { cookie: csrf.cookie }, + headers: { cookie: csrf.cookie, "content-type": "application/json" }, body: JSON.stringify({ email: email, csrfToken: csrf.value }), }, } @@ -58,7 +59,7 @@ it("Send e-mail to first address only", async () => { path: "signin/email", requestInit: { method: "POST", - headers: { cookie: csrf.cookie }, + headers: { cookie: csrf.cookie, "content-type": "application/json" }, body: JSON.stringify({ email: email, csrfToken: csrf.value }), }, } @@ -98,7 +99,7 @@ it("Send e-mail to address with first domain", async () => { path: "signin/email", requestInit: { method: "POST", - headers: { cookie: csrf.cookie }, + headers: { cookie: csrf.cookie, "content-type": "application/json" }, body: JSON.stringify({ email: email, csrfToken: csrf.value }), }, } @@ -144,7 +145,7 @@ it("Redirect to error page if multiple addresses aren't allowed", async () => { path: "signin/email", requestInit: { method: "POST", - headers: { cookie: csrf.cookie }, + headers: { cookie: csrf.cookie, "content-type": "application/json" }, body: JSON.stringify({ email: "email@email.com,email@email2.com", csrfToken: csrf.value, @@ -156,7 +157,6 @@ it("Redirect to error page if multiple addresses aren't allowed", async () => { expect(signIn).toBeCalledTimes(0) expect(sendVerificationRequest).toBeCalledTimes(0) - // @ts-expect-error expect(log.error.mock.calls[0]).toEqual([ "SIGNIN_EMAIL_ERROR", { error, providerId: "email" }, diff --git a/packages/next-auth/tests/getServerSession.test.ts b/packages/next-auth/tests/getServerSession.test.ts index 6643fa9bdd..686a0a86a2 100644 --- a/packages/next-auth/tests/getServerSession.test.ts +++ b/packages/next-auth/tests/getServerSession.test.ts @@ -84,8 +84,8 @@ describe("Return correct data", () => { it("Should return null if there is no session", async () => { const spy = jest.spyOn(core, "AuthHandler") - // @ts-expect-error - spy.mockReturnValue({ body: {} }) + // @ts-expect-error [Response.json](https://developer.mozilla.org/en-US/docs/Web/API/Response/json) + spy.mockReturnValue(Promise.resolve(Response.json(null))) const session = await unstable_getServerSession(req, res, { providers: [], @@ -97,21 +97,19 @@ describe("Return correct data", () => { }) it("Should return the session if one is found", async () => { - const mockedResponse = { - body: { - user: { - name: "John Doe", - email: "test@example.com", - image: "", - id: "1234", - }, - expires: "", + const mockedBody = { + user: { + name: "John Doe", + email: "test@example.com", + image: "", + id: "1234", }, + expires: "", } const spy = jest.spyOn(core, "AuthHandler") - // @ts-expect-error - spy.mockReturnValue(mockedResponse) + // @ts-expect-error [Response.json](https://developer.mozilla.org/en-US/docs/Web/API/Response/json) + spy.mockReturnValue(Promise.resolve(Response.json(mockedBody))) const session = await unstable_getServerSession(req, res, { providers: [], @@ -119,6 +117,6 @@ describe("Return correct data", () => { secret: "secret", }) - expect(session).toEqual(mockedResponse.body) + expect(session).toEqual(mockedBody) }) }) From 6b46f373bd5575a2c13a8a13e275e9efe3d1858c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Sat, 3 Dec 2022 15:36:47 +0100 Subject: [PATCH 41/93] chore: upgrade dep --- packages/next-auth/package.json | 8 +-- packages/next-auth/src/core/index.ts | 12 +--- packages/next-auth/src/core/init.ts | 1 + .../src/core/lib/oauth/authorization-url.ts | 2 +- .../next-auth/src/core/lib/oauth/callback.ts | 2 +- .../core/lib/oauth/web/authorization-url.ts | 12 ++-- packages/next-auth/src/core/lib/providers.ts | 55 ++++++++++++------- packages/next-auth/src/core/lib/web.ts | 15 +---- packages/next-auth/src/core/pages/index.ts | 2 +- .../next-auth/src/core/routes/callback.ts | 8 ++- packages/next-auth/src/core/routes/index.ts | 10 ++-- .../next-auth/src/core/routes/providers.ts | 4 +- packages/next-auth/src/core/routes/session.ts | 4 +- packages/next-auth/src/core/routes/signin.ts | 9 +-- packages/next-auth/src/core/routes/signout.ts | 2 +- packages/next-auth/src/core/types.ts | 4 ++ packages/next-auth/src/css/index.ts | 16 ++++-- packages/next-auth/src/providers/github.ts | 1 + packages/next-auth/src/providers/oauth.ts | 2 +- packages/next-auth/src/providers/twitch.ts | 2 +- packages/next-auth/src/utils/node.ts | 5 ++ packages/next-auth/src/utils/web.ts | 7 +-- packages/next-auth/src/web/index.ts | 1 + pnpm-lock.yaml | 12 ++-- 24 files changed, 108 insertions(+), 88 deletions(-) diff --git a/packages/next-auth/package.json b/packages/next-auth/package.json index cfe1a72f45..0537c9ad96 100644 --- a/packages/next-auth/package.json +++ b/packages/next-auth/package.json @@ -80,14 +80,14 @@ "uuid": "^8.3.2" }, "peerDependencies": { - "@panva/oauth4webapi": "1.2.0", + "oauth4webapi": "2.0.4", "next": "^12.2.5 || ^13", "nodemailer": "^6.6.5", "react": "^17.0.2 || ^18", "react-dom": "^17.0.2 || ^18" }, "peerDependenciesMeta": { - "@panva/oauth4webapi": { + "oauth4webapi": { "optional": true }, "nodemailer": { @@ -104,7 +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", + "oauth4webapi": "2.0.4", "@swc/core": "^1.2.198", "@swc/jest": "^0.2.21", "@testing-library/dom": "^8.13.0", @@ -138,4 +138,4 @@ "engines": { "node": "^12.19.0 || ^14.15.0 || ^16.13.0 || ^18.12.0" } -} \ No newline at end of file +} diff --git a/packages/next-auth/src/core/index.ts b/packages/next-auth/src/core/index.ts index 37934b0a57..2008fd0411 100644 --- a/packages/next-auth/src/core/index.ts +++ b/packages/next-auth/src/core/index.ts @@ -22,17 +22,11 @@ export interface RequestInternal { providerId?: string error?: string } - -interface AuthHeader { - key: string - value: string -} - export interface ResponseInternal< Body extends string | Record | any[] = any > { status?: number - headers?: AuthHeader[] + headers?: Headers | HeadersInit body?: Body redirect?: URL | string // TODO: refactor to only allow URL cookies?: Cookie[] @@ -62,7 +56,7 @@ async function AuthHandlerInternal< const message = `There is a problem with the server configuration. Check the server logs for more information.` return { status: 500, - headers: [{ key: "Content-Type", value: "application/json" }], + headers: { "Content-Type": "application/json" }, body: { message } as any, } } @@ -126,7 +120,7 @@ async function AuthHandlerInternal< } case "csrf": return { - headers: [{ key: "Content-Type", value: "application/json" }], + headers: { "Content-Type": "application/json" }, body: { csrfToken: options.csrfToken } as any, cookies, } diff --git a/packages/next-auth/src/core/init.ts b/packages/next-auth/src/core/init.ts index 8740e4d619..2ec8b8c7b0 100644 --- a/packages/next-auth/src/core/init.ts +++ b/packages/next-auth/src/core/init.ts @@ -59,6 +59,7 @@ export async function init({ providers: userOptions.providers, url, providerId, + runtime: userOptions.__internal__?.runtime, }) const maxAge = 30 * 24 * 60 * 60 // Sessions expire after 30 days of being idle by default diff --git a/packages/next-auth/src/core/lib/oauth/authorization-url.ts b/packages/next-auth/src/core/lib/oauth/authorization-url.ts index 1ba65e7ad8..4d1a51f468 100644 --- a/packages/next-auth/src/core/lib/oauth/authorization-url.ts +++ b/packages/next-auth/src/core/lib/oauth/authorization-url.ts @@ -15,7 +15,7 @@ import type { Cookie } from "../cookie" * * [OAuth 2](https://www.oauth.com/oauth2-servers/authorization/the-authorization-request/) | [OAuth 1](https://oauth.net/core/1.0a/#auth_step2) */ -export default async function getAuthorizationUrl({ +export async function getAuthorizationUrl({ options, query, }: { diff --git a/packages/next-auth/src/core/lib/oauth/callback.ts b/packages/next-auth/src/core/lib/oauth/callback.ts index 8033658d8e..5661bb5565 100644 --- a/packages/next-auth/src/core/lib/oauth/callback.ts +++ b/packages/next-auth/src/core/lib/oauth/callback.ts @@ -13,7 +13,7 @@ import type { InternalOptions } from "../../types" import type { RequestInternal } from "../.." import type { Cookie } from "../cookie" -export default async function oAuthCallback(params: { +export async function handleOAuthCallback(params: { options: InternalOptions<"oauth"> query: RequestInternal["query"] body: RequestInternal["body"] diff --git a/packages/next-auth/src/core/lib/oauth/web/authorization-url.ts b/packages/next-auth/src/core/lib/oauth/web/authorization-url.ts index 31a736bc72..4982d528d2 100644 --- a/packages/next-auth/src/core/lib/oauth/web/authorization-url.ts +++ b/packages/next-auth/src/core/lib/oauth/web/authorization-url.ts @@ -9,7 +9,7 @@ import type { Cookie } from "../../cookie" * * [OAuth 2](https://www.oauth.com/oauth2-servers/authorization/the-authorization-request/) */ -export default async function getAuthorizationUrl({ +export async function getAuthorizationUrl({ options, query, }: { @@ -61,9 +61,9 @@ export default async function getAuthorizationUrl({ const cookies: Cookie[] = [] if (provider.checks?.includes("state")) { - const state = await createState(options) - authParams.set("state", state.value) - cookies.push(state) + const { value, raw } = await createState(options) + authParams.set("state", raw) + cookies.push(value) } if (provider.checks?.includes("pkce")) { @@ -113,8 +113,8 @@ const STATE_MAX_AGE = 60 * 15 // 15 minutes in seconds async function createState(options: InternalOptions<"oauth">) { const raw = o.generateRandomState() const maxAge = STATE_MAX_AGE - const state = await signCookie("state", raw, maxAge, options) - return state + const value = await signCookie("state", raw, maxAge, options) + return { value, raw } } const PKCE_MAX_AGE = 60 * 15 // 15 minutes in seconds diff --git a/packages/next-auth/src/core/lib/providers.ts b/packages/next-auth/src/core/lib/providers.ts index fbcfe77734..6cbf0a45d4 100644 --- a/packages/next-auth/src/core/lib/providers.ts +++ b/packages/next-auth/src/core/lib/providers.ts @@ -18,6 +18,7 @@ export default function parseProviders(params: { providers: Provider[] url: InternalUrl providerId?: string + runtime?: "web" | "nodejs" }): { providers: InternalProvider[] provider?: InternalProvider @@ -25,18 +26,21 @@ export default function parseProviders(params: { const { url, providerId } = params const providers = params.providers.map((provider) => { - let { options: userOptions, ...rest } = provider - if (provider.type === "oauth") { - // @ts-expect-error after normalization, we don't match the initial type of rest - rest = normalizeOAuth(rest as OAuthConfig) - userOptions = normalizeOAuth(userOptions as OAuthUserConfig) - } - const id = (userOptions?.id as string | undefined) ?? rest.id - return merge(rest, { - ...userOptions, + const { options: userOptions, ...defaults } = provider + + const id = (userOptions?.id ?? defaults.id) as string + const merged = merge(defaults, userOptions, { signinUrl: `${url}/signin/${id}`, callbackUrl: `${url}/callback/${id}`, }) + + if (provider.type === "oauth") { + const p = normalizeOAuth(merged) + + return p + } + + return merged }) return { @@ -46,36 +50,49 @@ export default function parseProviders(params: { } function normalizeOAuth( - c?: OAuthConfig | OAuthUserConfig + c?: OAuthConfig | OAuthUserConfig, + runtime?: "web" | "nodejs" ): OAuthConfigInternal | {} { if (!c) return {} - const authorization = normalizeEndpoint(c.authorization) + const hasIssuer = !!c.issuer + const authorization = normalizeEndpoint(c.authorization, hasIssuer) if (!c.version?.startsWith("1.")) { + // Set default check to state + c.checks ??= ["pkce"] + c.checks = Array.isArray(c.checks) ? c.checks : [c.checks] + if (runtime === "web" && !c.checks.includes("pkce")) c.checks.push("pkce") + + if (!Array.isArray(c.checks)) c.checks = [c.checks] + + // REVIEW: Deprecate `idToken` in favor of `type: "oidc"`? c.idToken ??= // If a provider has as an "openid-configuration" well-known endpoint c.wellKnown?.includes("openid-configuration") ?? - // or an "openid" scope request, it will also likely be return an `id_token` + // or an "openid" scope request, it must also return an `id_token` authorization?.url.searchParams.get("scope")?.includes("openid") - // Set default check to state - c.checks ??= ["state"] - if (!Array.isArray(c.checks)) c.checks = [c.checks] + if (c.issuer && c.idToken) { + c.wellKnown ??= `${c.issuer}/.well-known/openid-configuration` + } } return { ...c, ...(authorization ? { authorization } : undefined), - ...(c.token ? { token: normalizeEndpoint(c.token) } : undefined), - ...(c.userinfo ? { userinfo: normalizeEndpoint(c.userinfo) } : undefined), + ...(c.token ? { token: normalizeEndpoint(c.token, hasIssuer) } : undefined), + ...(c.userinfo + ? { userinfo: normalizeEndpoint(c.userinfo, hasIssuer) } + : undefined), } } function normalizeEndpoint( - e?: OAuthConfig[OAuthEndpointType] + e?: OAuthConfig[OAuthEndpointType], + hasIssuer?: boolean ): OAuthConfigInternal[OAuthEndpointType] { - if (!e) return + if (!e || hasIssuer) return if (typeof e === "string") { return { url: new URL(e) } } diff --git a/packages/next-auth/src/core/lib/web.ts b/packages/next-auth/src/core/lib/web.ts index 3b5d03c147..f66b47bd33 100644 --- a/packages/next-auth/src/core/lib/web.ts +++ b/packages/next-auth/src/core/lib/web.ts @@ -32,21 +32,12 @@ export async function toInternalRequest( } export function toResponse(res: ResponseInternal): Response { - const headers = new Headers( - res.headers?.reduce((acc, { key, value }) => { - acc[key] = value - return acc - }, {}) - ) + const headers = new Headers(res.headers) res.cookies?.forEach((cookie) => { const { name, value, options } = cookie const cookieHeader = serialize(name, value, options) - if (headers.has("Set-Cookie")) { - headers.append("Set-Cookie", cookieHeader) - } else { - headers.set("Set-Cookie", cookieHeader) - } + headers.append("Set-Cookie", cookieHeader) }) const body = @@ -119,6 +110,6 @@ async function getBody(req: Request): Promise | undefined> { return await req.json() } else if (contentType?.includes("application/x-www-form-urlencoded")) { const params = new URLSearchParams(await req.text()) - return Object.fromEntries(params.entries()) + return Object.fromEntries(params) } } diff --git a/packages/next-auth/src/core/pages/index.ts b/packages/next-auth/src/core/pages/index.ts index 6938a4e016..8eed148e73 100644 --- a/packages/next-auth/src/core/pages/index.ts +++ b/packages/next-auth/src/core/pages/index.ts @@ -31,7 +31,7 @@ export default function renderPage(params: RenderPageParams) { return { cookies, status, - headers: [{ key: "Content-Type", value: "text/html" }], + headers: { "Content-Type": "text/html" }, body: `${title}

${renderToString(html)}
`, diff --git a/packages/next-auth/src/core/routes/callback.ts b/packages/next-auth/src/core/routes/callback.ts index 4a3842d145..67090e3bb4 100644 --- a/packages/next-auth/src/core/routes/callback.ts +++ b/packages/next-auth/src/core/routes/callback.ts @@ -1,4 +1,6 @@ -import oAuthCallback from "../lib/oauth/callback" +// TODO: Make this interchangeable with the Node.js import +// based on import of `next-auth` or `next-auth/web` +import { handleOAuthCallback } from "../lib/oauth/callback" import callbackHandler from "../lib/callback-handler" import { createHash } from "../lib/web" import getAdapterUserFromEmail from "../lib/email/getUserFromEmail" @@ -10,7 +12,7 @@ import type { User } from "../.." import type { AdapterSession } from "../../adapters" /** Handle callbacks from login services */ -export default async function callback(params: { +export async function callback(params: { options: InternalOptions query: RequestInternal["query"] method: Required["method"] @@ -44,7 +46,7 @@ export default async function callback(params: { account, OAuthProfile, cookies: oauthCookies, - } = await oAuthCallback({ + } = await handleOAuthCallback({ query, body, method, diff --git a/packages/next-auth/src/core/routes/index.ts b/packages/next-auth/src/core/routes/index.ts index 4cf7daa8f3..a1c28f4cc3 100644 --- a/packages/next-auth/src/core/routes/index.ts +++ b/packages/next-auth/src/core/routes/index.ts @@ -1,5 +1,5 @@ -export { default as callback } from './callback' -export { default as signin } from './signin' -export { default as signout } from './signout' -export { default as session } from './session' -export { default as providers } from './providers' +export { callback } from "./callback" +export { signin } from "./signin" +export { signout } from "./signout" +export { session } from "./session" +export { providers } from "./providers" diff --git a/packages/next-auth/src/core/routes/providers.ts b/packages/next-auth/src/core/routes/providers.ts index 9ce34acd44..3a9f978c04 100644 --- a/packages/next-auth/src/core/routes/providers.ts +++ b/packages/next-auth/src/core/routes/providers.ts @@ -14,11 +14,11 @@ export interface PublicProvider { * and their signin and callback URLs. This makes it possible to automatically * generate buttons for all providers when rendering client side. */ -export default function providers( +export function providers( providers: InternalProvider[] ): ResponseInternal> { return { - headers: [{ key: "Content-Type", value: "application/json" }], + headers: { "Content-Type": "application/json" }, body: providers.reduce>( (acc, { id, name, type, signinUrl, callbackUrl }) => { acc[id] = { id, name, type, signinUrl, callbackUrl } diff --git a/packages/next-auth/src/core/routes/session.ts b/packages/next-auth/src/core/routes/session.ts index 73caccdd9b..d1d2bf58cd 100644 --- a/packages/next-auth/src/core/routes/session.ts +++ b/packages/next-auth/src/core/routes/session.ts @@ -16,7 +16,7 @@ interface SessionParams { * for Single Page App clients */ -export default async function session( +export async function session( params: SessionParams ): Promise> { const { options, sessionStore } = params @@ -31,7 +31,7 @@ export default async function session( const response: ResponseInternal = { body: {}, - headers: [{ key: "Content-Type", value: "application/json" }], + headers: { "Content-Type": "application/json" }, cookies: [], } diff --git a/packages/next-auth/src/core/routes/signin.ts b/packages/next-auth/src/core/routes/signin.ts index 201f279101..d239fe7d72 100644 --- a/packages/next-auth/src/core/routes/signin.ts +++ b/packages/next-auth/src/core/routes/signin.ts @@ -1,4 +1,6 @@ -import getAuthorizationUrl from "../lib/oauth/authorization-url" +// TODO: Make this interchangeable with the Node.js import +// based on import of `next-auth` or `next-auth/web` +import { getAuthorizationUrl } from "../lib/oauth/authorization-url" import emailSignin from "../lib/email/signin" import getAdapterUserFromEmail from "../lib/email/getUserFromEmail" import type { RequestInternal, ResponseInternal } from ".." @@ -6,7 +8,7 @@ import type { InternalOptions } from "../types" import type { Account } from "../.." /** Handle requests to /api/auth/signin */ -export default async function signin(params: { +export async function signin(params: { options: InternalOptions<"oauth" | "email"> query: RequestInternal["query"] body: RequestInternal["body"] @@ -24,8 +26,7 @@ export default async function signin(params: { if (provider.type === "oauth") { try { - const response = await getAuthorizationUrl({ options, query }) - return response + return await getAuthorizationUrl({ options, query }) } catch (error) { logger.error("SIGNIN_OAUTH_ERROR", { error: error as Error, diff --git a/packages/next-auth/src/core/routes/signout.ts b/packages/next-auth/src/core/routes/signout.ts index 5078d53fb5..f87abe3b59 100644 --- a/packages/next-auth/src/core/routes/signout.ts +++ b/packages/next-auth/src/core/routes/signout.ts @@ -4,7 +4,7 @@ import type { ResponseInternal } from ".." import type { SessionStore } from "../lib/cookie" /** Handle requests to /api/auth/signout */ -export default async function signout(params: { +export async function signout(params: { options: InternalOptions sessionStore: SessionStore }): Promise { diff --git a/packages/next-auth/src/core/types.ts b/packages/next-auth/src/core/types.ts index 722d151ba1..0b6650624a 100644 --- a/packages/next-auth/src/core/types.ts +++ b/packages/next-auth/src/core/types.ts @@ -211,6 +211,10 @@ export interface AuthOptions { * @default Boolean(process.env.AUTH_TRUST_HOST ?? process.env.VERCEL) */ trustHost?: boolean + /** @internal */ + __internal__?: { + runtime?: "web" | "nodejs" + } } /** diff --git a/packages/next-auth/src/css/index.ts b/packages/next-auth/src/css/index.ts index a62e822f77..1774f34040 100644 --- a/packages/next-auth/src/css/index.ts +++ b/packages/next-auth/src/css/index.ts @@ -1,11 +1,19 @@ // To support serverless targets (which don"t work if you try to read in things // like CSS files at run time) this file is replaced in production builds with // a function that returns compiled CSS (embedded as a string in the function). -import fs from "fs" -import path from "path" - -const pathToCss = path.join(process.cwd(), process.env.NODE_ENV === "development" ? "node_modules/next-auth/css/index.css" : "/src/css/index.css") export default function css() { + if (globalThis.EdgeRuntime) return "TODO" + // eslint-disable-next-line @typescript-eslint/no-var-requires + const fs = require("fs") + // eslint-disable-next-line @typescript-eslint/no-var-requires + const path = require("path") + + const pathToCss = path.join( + process.cwd(), + process.env.NODE_ENV === "development" + ? "node_modules/next-auth/css/index.css" + : "/src/css/index.css" + ) return fs.readFileSync(pathToCss, "utf8") } diff --git a/packages/next-auth/src/providers/github.ts b/packages/next-auth/src/providers/github.ts index 87ff9bef84..b2063fd153 100644 --- a/packages/next-auth/src/providers/github.ts +++ b/packages/next-auth/src/providers/github.ts @@ -107,6 +107,7 @@ export default function Github

( text: "#000", textDark: "#fff", }, + checks: ["pkce"], options, } } diff --git a/packages/next-auth/src/providers/oauth.ts b/packages/next-auth/src/providers/oauth.ts index 5dd3f9f6bc..4a46ee99d6 100644 --- a/packages/next-auth/src/providers/oauth.ts +++ b/packages/next-auth/src/providers/oauth.ts @@ -186,7 +186,7 @@ export type OAuthUserConfig

= Omit< Partial>, "options" | "type" > & - Required, "clientId" | "clientSecret" | "profile">> + Required, "clientId" | "clientSecret">> export type OAuthProvider = ( options: Partial> diff --git a/packages/next-auth/src/providers/twitch.ts b/packages/next-auth/src/providers/twitch.ts index c22bc90d48..71aef987ff 100644 --- a/packages/next-auth/src/providers/twitch.ts +++ b/packages/next-auth/src/providers/twitch.ts @@ -11,7 +11,7 @@ export default function Twitch

( options: OAuthUserConfig

): OAuthConfig

{ return { - wellKnown: "https://id.twitch.tv/oauth2/.well-known/openid-configuration", + issuer: "https://id.twitch.tv/oauth2", id: "twitch", name: "Twitch", type: "oauth", diff --git a/packages/next-auth/src/utils/node.ts b/packages/next-auth/src/utils/node.ts index 7cec0624d5..92a5796362 100644 --- a/packages/next-auth/src/utils/node.ts +++ b/packages/next-auth/src/utils/node.ts @@ -22,6 +22,11 @@ export function getBody( if (req.body instanceof ReadableStream) { return { body: req.body } } + + if (req.headers["content-type"] === "application/x-www-form-urlencoded") { + return { body: new URLSearchParams(req.body) } + } + return { body: JSON.stringify(req.body) } } diff --git a/packages/next-auth/src/utils/web.ts b/packages/next-auth/src/utils/web.ts index 85f9fd82e1..b57644f5f1 100644 --- a/packages/next-auth/src/utils/web.ts +++ b/packages/next-auth/src/utils/web.ts @@ -69,12 +69,7 @@ export async function toInternalRequest( } export function toResponse(res: ResponseInternal): Response { - const headers = new Headers( - res.headers?.reduce((acc, { key, value }) => { - acc[key] = value - return acc - }, {}) - ) + const headers = new Headers(res.headers) res.cookies?.forEach((cookie) => { const { name, value, options } = cookie diff --git a/packages/next-auth/src/web/index.ts b/packages/next-auth/src/web/index.ts index 301debc3f2..367f0aff29 100644 --- a/packages/next-auth/src/web/index.ts +++ b/packages/next-auth/src/web/index.ts @@ -5,6 +5,7 @@ export * from "../core/types" async function WebAuthHandler(req: Request, options: AuthOptions) { options.secret ??= options.jwt?.secret ?? process.env.NEXTAUTH_SECRET + options.__internal__ = { runtime: "web" } const url = new URL(req.url ?? "") diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ce923abefa..02edf2761c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -448,7 +448,6 @@ importers: '@edge-runtime/jest-environment': 1.1.0-beta.35 '@next-auth/tsconfig': workspace:* '@panva/hkdf': ^1.0.1 - '@panva/oauth4webapi': 1.2.0 '@swc/core': ^1.2.198 '@swc/jest': ^0.2.21 '@testing-library/dom': ^8.13.0 @@ -475,6 +474,7 @@ importers: msw: ^0.42.3 next: 13.0.6 oauth: ^0.9.15 + oauth4webapi: 2.0.4 openid-client: ^5.1.0 postcss: ^8.4.14 postcss-cli: ^9.1.0 @@ -505,7 +505,6 @@ importers: '@babel/preset-typescript': 7.17.12_@babel+core@7.18.5 '@edge-runtime/jest-environment': 1.1.0-beta.35 '@next-auth/tsconfig': link:../tsconfig - '@panva/oauth4webapi': 1.2.0 '@swc/core': 1.2.204 '@swc/jest': 0.2.21_@swc+core@1.2.204 '@testing-library/dom': 8.14.0 @@ -529,6 +528,7 @@ importers: jest-watch-typeahead: 1.1.0_jest@28.1.1 msw: 0.42.3 next: 13.0.6_4cc5zw5azim2bix77d63le72su + oauth4webapi: 2.0.4 postcss: 8.4.14 postcss-cli: 9.1.0_postcss@8.4.14 postcss-nested: 5.0.6_postcss@8.4.14 @@ -7726,10 +7726,6 @@ packages: resolution: {integrity: sha512-MSAs9t3Go7GUkMhpKC44T58DJ5KGk2vBo+h1cqQeqlMfdGkxaVB78ZWpv9gYi/g2fa4sopag9gJsNvS8XGgWJA==} dev: false - /@panva/oauth4webapi/1.2.0: - resolution: {integrity: sha512-OmwAE3fzlSJsA0zzCWA/ob7Nwb7nwzku8vbAjgmnkkVYbpyokBsaVrrzog9cM0RMytXexpNNcbQbMF/UNa71mg==} - dev: true - /@polka/url/1.0.0-next.21: resolution: {integrity: sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==} dev: true @@ -19451,6 +19447,10 @@ packages: resolution: {integrity: sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==} dev: false + /oauth4webapi/2.0.4: + resolution: {integrity: sha512-d6NmQuOlCo6+HzNPG70Pl8T4WnHo/XPvQ3Dxus3fRvRjFmt9H+BggI/APyzQ4/jlcdIjPaOw81wIO6WkRGKfkg==} + dev: true + /object-assign/4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} From 5a22790673e1fb51660c3adbcd9d557831fbe91a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Sat, 3 Dec 2022 16:05:16 +0100 Subject: [PATCH 42/93] fix import --- packages/next-auth/src/core/lib/oauth/web/authorization-url.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/next-auth/src/core/lib/oauth/web/authorization-url.ts b/packages/next-auth/src/core/lib/oauth/web/authorization-url.ts index 4982d528d2..808f738899 100644 --- a/packages/next-auth/src/core/lib/oauth/web/authorization-url.ts +++ b/packages/next-auth/src/core/lib/oauth/web/authorization-url.ts @@ -1,4 +1,4 @@ -import * as o from "@panva/oauth4webapi" +import * as o from "oauth4webapi" import type { CookiesOptions, InternalOptions } from "../../../types" import type { RequestInternal, ResponseInternal } from "../../.." From 9f3e33eef22e9c7111684088ad5c2965993e23e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Mon, 5 Dec 2022 13:32:30 +0100 Subject: [PATCH 43/93] refactor: more renames --- packages/next-auth/src/client/_utils.ts | 6 +++--- packages/next-auth/src/core/lib/providers.ts | 4 ++-- packages/next-auth/src/core/types.ts | 4 ++-- packages/next-auth/src/next/index.ts | 2 +- packages/next-auth/src/providers/oauth.ts | 2 +- packages/next-auth/src/react/index.tsx | 4 ++-- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/next-auth/src/client/_utils.ts b/packages/next-auth/src/client/_utils.ts index 7ccc07761c..79527a2a25 100644 --- a/packages/next-auth/src/client/_utils.ts +++ b/packages/next-auth/src/client/_utils.ts @@ -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 @@ -31,7 +31,7 @@ export interface CtxOrReq { */ export async function fetchData( path: string, - __NEXTAUTH: NextAuthClientConfig, + __NEXTAUTH: AuthClientConfig, logger: LoggerInstance, { ctx, req = ctx?.req }: CtxOrReq = {} ): Promise { @@ -50,7 +50,7 @@ export async function fetchData( } } -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}` diff --git a/packages/next-auth/src/core/lib/providers.ts b/packages/next-auth/src/core/lib/providers.ts index 423969990f..892bd374f8 100644 --- a/packages/next-auth/src/core/lib/providers.ts +++ b/packages/next-auth/src/core/lib/providers.ts @@ -2,7 +2,7 @@ import { merge } from "../../utils/merge" import type { InternalProvider } from "../types" import type { - InternalOAuthConfig, + OAuthConfigInternal, OAuthConfig, Provider, } from "../../providers" @@ -59,7 +59,7 @@ function normalizeOAuthOptions( if (!oauthOptions) return const normalized = Object.entries(oauthOptions).reduce< - InternalOAuthConfig> + OAuthConfigInternal> >( (acc, [key, value]) => { if ( diff --git a/packages/next-auth/src/core/types.ts b/packages/next-auth/src/core/types.ts index c7414c924a..4b793cda2c 100644 --- a/packages/next-auth/src/core/types.ts +++ b/packages/next-auth/src/core/types.ts @@ -5,7 +5,7 @@ import type { ProviderType, EmailConfig, CredentialsConfig, - InternalOAuthConfig, + OAuthConfigInternal, } from "../providers" import type { TokenSetParameters } from "openid-client" import type { JWT, JWTOptions } from "../jwt" @@ -501,7 +501,7 @@ export interface User extends DefaultUser {} /** @internal */ export type InternalProvider = (T extends "oauth" - ? InternalOAuthConfig + ? OAuthConfigInternal : T extends "email" ? EmailConfig : T extends "credentials" diff --git a/packages/next-auth/src/next/index.ts b/packages/next-auth/src/next/index.ts index e2b4321c28..d6fcabbc90 100644 --- a/packages/next-auth/src/next/index.ts +++ b/packages/next-auth/src/next/index.ts @@ -54,7 +54,7 @@ async function NextAuthHandler( return res.send(await response.text()) } -function NextAuth(options: NextAuthOptions): any +function NextAuth(options: AuthOptions): any function NextAuth( req: NextApiRequest, res: NextApiResponse, diff --git a/packages/next-auth/src/providers/oauth.ts b/packages/next-auth/src/providers/oauth.ts index c0ae9fe3b0..c476279b12 100644 --- a/packages/next-auth/src/providers/oauth.ts +++ b/packages/next-auth/src/providers/oauth.ts @@ -160,7 +160,7 @@ export interface OAuthConfig

extends CommonProviderOptions, PartialIssuer { } /** @internal */ -export interface InternalOAuthConfig

+export interface OAuthConfigInternal

extends Omit, "authorization" | "token" | "userinfo"> { authorization?: AuthorizationEndpointHandler token?: TokenEndpointHandler diff --git a/packages/next-auth/src/react/index.tsx b/packages/next-auth/src/react/index.tsx index 07f1f199c9..bab02ec8a4 100644 --- a/packages/next-auth/src/react/index.tsx +++ b/packages/next-auth/src/react/index.tsx @@ -18,7 +18,7 @@ import { apiBaseUrl, fetchData, now, - NextAuthClientConfig, + AuthClientConfig, } from "../client/_utils" import type { @@ -46,7 +46,7 @@ export * from "./types" // relative URLs are valid in that context and so defaults to empty. // 2. When invoked server side the value is picked up from an environment // variable and defaults to 'http://localhost:3000'. -const __NEXTAUTH: NextAuthClientConfig = { +const __NEXTAUTH: AuthClientConfig = { baseUrl: parseUrl(process.env.NEXTAUTH_URL ?? process.env.VERCEL_URL).origin, basePath: parseUrl(process.env.NEXTAUTH_URL).path, baseUrlServer: parseUrl( From 9d079a5fcb662592de77f34753972c4911c83c6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Tue, 6 Dec 2022 15:46:51 +0100 Subject: [PATCH 44/93] wip core --- apps/dev/package.json | 1 + apps/dev/pages/api/auth/[...nextauth].ts | 166 ++-- packages/core/package.json | 53 ++ packages/core/src/adapters.ts | 130 +++ packages/core/src/css/index.css | 290 ++++++ packages/core/src/css/index.ts | 19 + packages/core/src/errors.ts | 132 +++ packages/core/src/index.ts | 251 +++++ packages/core/src/init.ts | 160 ++++ packages/core/src/jwt/index.ts | 128 +++ packages/core/src/jwt/types.ts | 54 ++ packages/core/src/lib/assert.ts | 171 ++++ packages/core/src/lib/callback-handler.ts | 228 +++++ packages/core/src/lib/callback-url.ts | 42 + packages/core/src/lib/cookie.ts | 236 +++++ packages/core/src/lib/csrf-token.ts | 54 ++ packages/core/src/lib/default-callbacks.ts | 18 + .../core/src/lib/email/getUserFromEmail.ts | 20 + packages/core/src/lib/email/signin.ts | 49 + .../core/src/lib/oauth/authorization-url.ts | 143 +++ packages/core/src/lib/oauth/callback.ts | 204 ++++ packages/core/src/lib/oauth/client.ts | 45 + packages/core/src/lib/oauth/nonce-handler.ts | 76 ++ packages/core/src/lib/oauth/pkce-handler.ts | 87 ++ packages/core/src/lib/oauth/state-handler.ts | 63 ++ packages/core/src/lib/providers.ts | 109 +++ packages/core/src/lib/web.ts | 86 ++ packages/core/src/pages/error.tsx | 114 +++ packages/core/src/pages/index.ts | 78 ++ packages/core/src/pages/signin.tsx | 193 ++++ packages/core/src/pages/signout.tsx | 37 + packages/core/src/pages/verify-request.tsx | 37 + packages/core/src/providers/42-school.ts | 178 ++++ packages/core/src/providers/apple.ts | 130 +++ packages/core/src/providers/atlassian.ts | 44 + packages/core/src/providers/auth0.ts | 39 + packages/core/src/providers/authentik.ts | 44 + packages/core/src/providers/azure-ad-b2c.ts | 55 ++ packages/core/src/providers/azure-ad.ts | 73 ++ packages/core/src/providers/battlenet.ts | 39 + packages/core/src/providers/box.js | 28 + packages/core/src/providers/boxyhq-saml.ts | 37 + packages/core/src/providers/bungie.js | 25 + packages/core/src/providers/cognito.ts | 37 + packages/core/src/providers/coinbase.js | 21 + packages/core/src/providers/credentials.ts | 44 + packages/core/src/providers/discord.ts | 57 ++ packages/core/src/providers/dropbox.js | 51 + .../src/providers/duende-identity-server6.ts | 31 + packages/core/src/providers/email.ts | 166 ++++ packages/core/src/providers/eveonline.ts | 33 + packages/core/src/providers/facebook.ts | 54 ++ packages/core/src/providers/faceit.js | 25 + packages/core/src/providers/foursquare.js | 63 ++ packages/core/src/providers/freshbooks.js | 30 + packages/core/src/providers/fusionauth.ts | 50 + packages/core/src/providers/github.ts | 112 +++ packages/core/src/providers/gitlab.ts | 80 ++ packages/core/src/providers/google.ts | 50 + packages/core/src/providers/hubspot.ts | 77 ++ .../core/src/providers/identity-server4.js | 21 + packages/core/src/providers/index.ts | 41 + packages/core/src/providers/instagram.js | 59 ++ packages/core/src/providers/kakao.ts | 92 ++ packages/core/src/providers/keycloak.ts | 56 ++ packages/core/src/providers/line.ts | 46 + packages/core/src/providers/linkedin.ts | 68 ++ .../core/src/providers/logos/apple-dark.svg | 4 + packages/core/src/providers/logos/apple.svg | 4 + .../src/providers/logos/atlassian-dark.svg | 8 + .../core/src/providers/logos/atlassian.svg | 4 + .../core/src/providers/logos/auth0-dark.svg | 12 + packages/core/src/providers/logos/auth0.svg | 3 + .../core/src/providers/logos/azure-dark.svg | 3 + packages/core/src/providers/logos/azure.svg | 3 + .../src/providers/logos/battlenet-dark.svg | 3 + .../core/src/providers/logos/battlenet.svg | 3 + .../core/src/providers/logos/box-dark.svg | 6 + packages/core/src/providers/logos/box.svg | 6 + packages/core/src/providers/logos/cognito.svg | 10 + .../core/src/providers/logos/discord-dark.svg | 3 + packages/core/src/providers/logos/discord.svg | 3 + .../src/providers/logos/facebook-dark.svg | 4 + .../core/src/providers/logos/facebook.svg | 8 + .../src/providers/logos/foursquare-dark.svg | 18 + .../core/src/providers/logos/foursquare.svg | 18 + .../src/providers/logos/freshbooks-dark.svg | 3 + .../core/src/providers/logos/freshbooks.svg | 3 + .../core/src/providers/logos/github-dark.svg | 4 + packages/core/src/providers/logos/github.svg | 4 + .../core/src/providers/logos/gitlab-dark.svg | 8 + packages/core/src/providers/logos/gitlab.svg | 11 + packages/core/src/providers/logos/google.svg | 7 + .../core/src/providers/logos/hubspot-dark.svg | 3 + packages/core/src/providers/logos/hubspot.svg | 3 + .../core/src/providers/logos/instagram.svg | 15 + .../core/src/providers/logos/keycloak.svg | 260 ++++++ packages/core/src/providers/logos/line.svg | 6 + .../src/providers/logos/linkedin-dark.svg | 6 + .../core/src/providers/logos/linkedin.svg | 6 + .../src/providers/logos/mailchimp-dark.svg | 3 + .../core/src/providers/logos/mailchimp.svg | 3 + .../core/src/providers/logos/okta-dark.svg | 3 + packages/core/src/providers/logos/okta.svg | 3 + packages/core/src/providers/logos/patreon.svg | 4 + packages/core/src/providers/logos/slack.svg | 8 + packages/core/src/providers/logos/spotify.svg | 4 + packages/core/src/providers/logos/todoist.svg | 8 + .../core/src/providers/logos/trakt-dark.svg | 17 + packages/core/src/providers/logos/trakt.svg | 18 + .../core/src/providers/logos/twitch-dark.svg | 4 + packages/core/src/providers/logos/twitch.svg | 4 + .../core/src/providers/logos/twitter-dark.svg | 3 + packages/core/src/providers/logos/twitter.svg | 3 + packages/core/src/providers/logos/vk-dark.svg | 4 + packages/core/src/providers/logos/vk.svg | 4 + .../src/providers/logos/wikimedia-dark.svg | 10 + .../core/src/providers/logos/wikimedia.svg | 10 + .../core/src/providers/logos/workos-dark.svg | 4 + packages/core/src/providers/logos/workos.svg | 4 + packages/core/src/providers/mailchimp.js | 28 + packages/core/src/providers/mailru.js | 20 + packages/core/src/providers/medium.js | 20 + packages/core/src/providers/naver.ts | 42 + packages/core/src/providers/netlify.js | 20 + packages/core/src/providers/oauth-types.ts | 70 ++ packages/core/src/providers/oauth.ts | 193 ++++ packages/core/src/providers/okta.ts | 65 ++ packages/core/src/providers/onelogin.js | 20 + packages/core/src/providers/osso.js | 20 + packages/core/src/providers/osu.ts | 77 ++ packages/core/src/providers/patreon.ts | 42 + packages/core/src/providers/pinterest.ts | 34 + packages/core/src/providers/pipedrive.ts | 59 ++ packages/core/src/providers/reddit.js | 20 + packages/core/src/providers/salesforce.ts | 32 + packages/core/src/providers/slack.ts | 62 ++ packages/core/src/providers/spotify.ts | 42 + packages/core/src/providers/strava.ts | 42 + packages/core/src/providers/todoist.ts | 66 ++ packages/core/src/providers/trakt.ts | 64 ++ packages/core/src/providers/twitch.ts | 49 + packages/core/src/providers/twitter.ts | 230 +++++ packages/core/src/providers/united-effects.ts | 31 + packages/core/src/providers/vk.ts | 318 +++++++ packages/core/src/providers/wikimedia.ts | 193 ++++ packages/core/src/providers/wordpress.js | 21 + packages/core/src/providers/workos.ts | 57 ++ packages/core/src/providers/yandex.js | 23 + packages/core/src/providers/zitadel.ts | 51 + packages/core/src/providers/zoho.js | 21 + packages/core/src/providers/zoom.ts | 52 ++ packages/core/src/routes/callback.ts | 420 +++++++++ packages/core/src/routes/index.ts | 5 + packages/core/src/routes/providers.ts | 29 + packages/core/src/routes/session.ts | 165 ++++ packages/core/src/routes/signin.ts | 103 +++ packages/core/src/routes/signout.ts | 44 + packages/core/src/types.ts | 586 ++++++++++++ packages/core/src/utils/date.ts | 8 + packages/core/src/utils/logger.ts | 117 +++ packages/core/src/utils/merge.ts | 25 + packages/core/src/utils/node.ts | 35 + packages/core/src/utils/parse-url.ts | 36 + packages/core/tsconfig.dev.json | 21 + packages/core/tsconfig.eslint.json | 8 + packages/core/tsconfig.json | 31 + pnpm-lock.yaml | 868 ++++++++++++++---- 168 files changed, 10462 insertions(+), 236 deletions(-) create mode 100644 packages/core/package.json create mode 100644 packages/core/src/adapters.ts create mode 100644 packages/core/src/css/index.css create mode 100644 packages/core/src/css/index.ts create mode 100644 packages/core/src/errors.ts create mode 100644 packages/core/src/index.ts create mode 100644 packages/core/src/init.ts create mode 100644 packages/core/src/jwt/index.ts create mode 100644 packages/core/src/jwt/types.ts create mode 100644 packages/core/src/lib/assert.ts create mode 100644 packages/core/src/lib/callback-handler.ts create mode 100644 packages/core/src/lib/callback-url.ts create mode 100644 packages/core/src/lib/cookie.ts create mode 100644 packages/core/src/lib/csrf-token.ts create mode 100644 packages/core/src/lib/default-callbacks.ts create mode 100644 packages/core/src/lib/email/getUserFromEmail.ts create mode 100644 packages/core/src/lib/email/signin.ts create mode 100644 packages/core/src/lib/oauth/authorization-url.ts create mode 100644 packages/core/src/lib/oauth/callback.ts create mode 100644 packages/core/src/lib/oauth/client.ts create mode 100644 packages/core/src/lib/oauth/nonce-handler.ts create mode 100644 packages/core/src/lib/oauth/pkce-handler.ts create mode 100644 packages/core/src/lib/oauth/state-handler.ts create mode 100644 packages/core/src/lib/providers.ts create mode 100644 packages/core/src/lib/web.ts create mode 100644 packages/core/src/pages/error.tsx create mode 100644 packages/core/src/pages/index.ts create mode 100644 packages/core/src/pages/signin.tsx create mode 100644 packages/core/src/pages/signout.tsx create mode 100644 packages/core/src/pages/verify-request.tsx create mode 100644 packages/core/src/providers/42-school.ts create mode 100644 packages/core/src/providers/apple.ts create mode 100644 packages/core/src/providers/atlassian.ts create mode 100644 packages/core/src/providers/auth0.ts create mode 100644 packages/core/src/providers/authentik.ts create mode 100644 packages/core/src/providers/azure-ad-b2c.ts create mode 100644 packages/core/src/providers/azure-ad.ts create mode 100644 packages/core/src/providers/battlenet.ts create mode 100644 packages/core/src/providers/box.js create mode 100644 packages/core/src/providers/boxyhq-saml.ts create mode 100644 packages/core/src/providers/bungie.js create mode 100644 packages/core/src/providers/cognito.ts create mode 100644 packages/core/src/providers/coinbase.js create mode 100644 packages/core/src/providers/credentials.ts create mode 100644 packages/core/src/providers/discord.ts create mode 100644 packages/core/src/providers/dropbox.js create mode 100644 packages/core/src/providers/duende-identity-server6.ts create mode 100644 packages/core/src/providers/email.ts create mode 100644 packages/core/src/providers/eveonline.ts create mode 100644 packages/core/src/providers/facebook.ts create mode 100644 packages/core/src/providers/faceit.js create mode 100644 packages/core/src/providers/foursquare.js create mode 100644 packages/core/src/providers/freshbooks.js create mode 100644 packages/core/src/providers/fusionauth.ts create mode 100644 packages/core/src/providers/github.ts create mode 100644 packages/core/src/providers/gitlab.ts create mode 100644 packages/core/src/providers/google.ts create mode 100644 packages/core/src/providers/hubspot.ts create mode 100644 packages/core/src/providers/identity-server4.js create mode 100644 packages/core/src/providers/index.ts create mode 100644 packages/core/src/providers/instagram.js create mode 100644 packages/core/src/providers/kakao.ts create mode 100644 packages/core/src/providers/keycloak.ts create mode 100644 packages/core/src/providers/line.ts create mode 100644 packages/core/src/providers/linkedin.ts create mode 100644 packages/core/src/providers/logos/apple-dark.svg create mode 100644 packages/core/src/providers/logos/apple.svg create mode 100644 packages/core/src/providers/logos/atlassian-dark.svg create mode 100644 packages/core/src/providers/logos/atlassian.svg create mode 100644 packages/core/src/providers/logos/auth0-dark.svg create mode 100644 packages/core/src/providers/logos/auth0.svg create mode 100644 packages/core/src/providers/logos/azure-dark.svg create mode 100644 packages/core/src/providers/logos/azure.svg create mode 100644 packages/core/src/providers/logos/battlenet-dark.svg create mode 100644 packages/core/src/providers/logos/battlenet.svg create mode 100644 packages/core/src/providers/logos/box-dark.svg create mode 100644 packages/core/src/providers/logos/box.svg create mode 100644 packages/core/src/providers/logos/cognito.svg create mode 100644 packages/core/src/providers/logos/discord-dark.svg create mode 100644 packages/core/src/providers/logos/discord.svg create mode 100644 packages/core/src/providers/logos/facebook-dark.svg create mode 100644 packages/core/src/providers/logos/facebook.svg create mode 100644 packages/core/src/providers/logos/foursquare-dark.svg create mode 100644 packages/core/src/providers/logos/foursquare.svg create mode 100644 packages/core/src/providers/logos/freshbooks-dark.svg create mode 100644 packages/core/src/providers/logos/freshbooks.svg create mode 100644 packages/core/src/providers/logos/github-dark.svg create mode 100644 packages/core/src/providers/logos/github.svg create mode 100644 packages/core/src/providers/logos/gitlab-dark.svg create mode 100644 packages/core/src/providers/logos/gitlab.svg create mode 100644 packages/core/src/providers/logos/google.svg create mode 100644 packages/core/src/providers/logos/hubspot-dark.svg create mode 100644 packages/core/src/providers/logos/hubspot.svg create mode 100644 packages/core/src/providers/logos/instagram.svg create mode 100644 packages/core/src/providers/logos/keycloak.svg create mode 100644 packages/core/src/providers/logos/line.svg create mode 100644 packages/core/src/providers/logos/linkedin-dark.svg create mode 100644 packages/core/src/providers/logos/linkedin.svg create mode 100644 packages/core/src/providers/logos/mailchimp-dark.svg create mode 100644 packages/core/src/providers/logos/mailchimp.svg create mode 100644 packages/core/src/providers/logos/okta-dark.svg create mode 100644 packages/core/src/providers/logos/okta.svg create mode 100644 packages/core/src/providers/logos/patreon.svg create mode 100644 packages/core/src/providers/logos/slack.svg create mode 100644 packages/core/src/providers/logos/spotify.svg create mode 100644 packages/core/src/providers/logos/todoist.svg create mode 100644 packages/core/src/providers/logos/trakt-dark.svg create mode 100644 packages/core/src/providers/logos/trakt.svg create mode 100644 packages/core/src/providers/logos/twitch-dark.svg create mode 100644 packages/core/src/providers/logos/twitch.svg create mode 100644 packages/core/src/providers/logos/twitter-dark.svg create mode 100644 packages/core/src/providers/logos/twitter.svg create mode 100644 packages/core/src/providers/logos/vk-dark.svg create mode 100644 packages/core/src/providers/logos/vk.svg create mode 100644 packages/core/src/providers/logos/wikimedia-dark.svg create mode 100644 packages/core/src/providers/logos/wikimedia.svg create mode 100644 packages/core/src/providers/logos/workos-dark.svg create mode 100644 packages/core/src/providers/logos/workos.svg create mode 100644 packages/core/src/providers/mailchimp.js create mode 100644 packages/core/src/providers/mailru.js create mode 100644 packages/core/src/providers/medium.js create mode 100644 packages/core/src/providers/naver.ts create mode 100644 packages/core/src/providers/netlify.js create mode 100644 packages/core/src/providers/oauth-types.ts create mode 100644 packages/core/src/providers/oauth.ts create mode 100644 packages/core/src/providers/okta.ts create mode 100644 packages/core/src/providers/onelogin.js create mode 100644 packages/core/src/providers/osso.js create mode 100644 packages/core/src/providers/osu.ts create mode 100644 packages/core/src/providers/patreon.ts create mode 100644 packages/core/src/providers/pinterest.ts create mode 100644 packages/core/src/providers/pipedrive.ts create mode 100644 packages/core/src/providers/reddit.js create mode 100644 packages/core/src/providers/salesforce.ts create mode 100644 packages/core/src/providers/slack.ts create mode 100644 packages/core/src/providers/spotify.ts create mode 100644 packages/core/src/providers/strava.ts create mode 100644 packages/core/src/providers/todoist.ts create mode 100644 packages/core/src/providers/trakt.ts create mode 100644 packages/core/src/providers/twitch.ts create mode 100644 packages/core/src/providers/twitter.ts create mode 100644 packages/core/src/providers/united-effects.ts create mode 100644 packages/core/src/providers/vk.ts create mode 100644 packages/core/src/providers/wikimedia.ts create mode 100644 packages/core/src/providers/wordpress.js create mode 100644 packages/core/src/providers/workos.ts create mode 100644 packages/core/src/providers/yandex.js create mode 100644 packages/core/src/providers/zitadel.ts create mode 100644 packages/core/src/providers/zoho.js create mode 100644 packages/core/src/providers/zoom.ts create mode 100644 packages/core/src/routes/callback.ts create mode 100644 packages/core/src/routes/index.ts create mode 100644 packages/core/src/routes/providers.ts create mode 100644 packages/core/src/routes/session.ts create mode 100644 packages/core/src/routes/signin.ts create mode 100644 packages/core/src/routes/signout.ts create mode 100644 packages/core/src/types.ts create mode 100644 packages/core/src/utils/date.ts create mode 100644 packages/core/src/utils/logger.ts create mode 100644 packages/core/src/utils/merge.ts create mode 100644 packages/core/src/utils/node.ts create mode 100644 packages/core/src/utils/parse-url.ts create mode 100644 packages/core/tsconfig.dev.json create mode 100644 packages/core/tsconfig.eslint.json create mode 100644 packages/core/tsconfig.json diff --git a/apps/dev/package.json b/apps/dev/package.json index 5b5cf51ed5..89d53f861f 100644 --- a/apps/dev/package.json +++ b/apps/dev/package.json @@ -23,6 +23,7 @@ "faunadb": "^4", "next": "13.0.6", "next-auth": "workspace:*", + "next-auth-core": "workspace:*", "nodemailer": "^6", "react": "^18", "react-dom": "^18" diff --git a/apps/dev/pages/api/auth/[...nextauth].ts b/apps/dev/pages/api/auth/[...nextauth].ts index 10533891a6..4dc82b6e10 100644 --- a/apps/dev/pages/api/auth/[...nextauth].ts +++ b/apps/dev/pages/api/auth/[...nextauth].ts @@ -1,39 +1,40 @@ -import NextAuth, { type NextAuthOptions } from "next-auth" +// @ts-nocheck +import { AuthHandler, AuthOptions } from "next-auth-core" // Providers -import Apple from "next-auth/providers/apple" +// import Apple from "next-auth/providers/apple" import Auth0 from "next-auth/providers/auth0" -import AzureAD from "next-auth/providers/azure-ad" -import AzureB2C from "next-auth/providers/azure-ad-b2c" -import BoxyHQSAML from "next-auth/providers/boxyhq-saml" -import Cognito from "next-auth/providers/cognito" +// import AzureAD from "next-auth/providers/azure-ad" +// import AzureB2C from "next-auth/providers/azure-ad-b2c" +// import BoxyHQSAML from "next-auth/providers/boxyhq-saml" +// import Cognito from "next-auth/providers/cognito" import Credentials from "next-auth/providers/credentials" -import Discord from "next-auth/providers/discord" -import DuendeIDS6 from "next-auth/providers/duende-identity-server6" -import Email from "next-auth/providers/email" -import Facebook from "next-auth/providers/facebook" -import Foursquare from "next-auth/providers/foursquare" -import Freshbooks from "next-auth/providers/freshbooks" +// import Discord from "next-auth/providers/discord" +// import DuendeIDS6 from "next-auth/providers/duende-identity-server6" +// import Email from "next-auth/providers/email" +// import Facebook from "next-auth/providers/facebook" +// import Foursquare from "next-auth/providers/foursquare" +// import Freshbooks from "next-auth/providers/freshbooks" import GitHub from "next-auth/providers/github" -import Gitlab from "next-auth/providers/gitlab" -import Google from "next-auth/providers/google" -import IDS4 from "next-auth/providers/identity-server4" -import Instagram from "next-auth/providers/instagram" -import Keycloak from "next-auth/providers/keycloak" -import Line from "next-auth/providers/line" -import LinkedIn from "next-auth/providers/linkedin" -import Mailchimp from "next-auth/providers/mailchimp" -import Okta from "next-auth/providers/okta" -import Osu from "next-auth/providers/osu" -import Patreon from "next-auth/providers/patreon" -import Slack from "next-auth/providers/slack" -import Spotify from "next-auth/providers/spotify" -import Trakt from "next-auth/providers/trakt" -import Twitch from "next-auth/providers/twitch" -import Twitter, { TwitterLegacy } from "next-auth/providers/twitter" -import Vk from "next-auth/providers/vk" -import Wikimedia from "next-auth/providers/wikimedia" -import WorkOS from "next-auth/providers/workos" +// import Gitlab from "next-auth/providers/gitlab" +// import Google from "next-auth/providers/google" +// import IDS4 from "next-auth/providers/identity-server4" +// import Instagram from "next-auth/providers/instagram" +// import Keycloak from "next-auth/providers/keycloak" +// import Line from "next-auth/providers/line" +// import LinkedIn from "next-auth/providers/linkedin" +// import Mailchimp from "next-auth/providers/mailchimp" +// import Okta from "next-auth/providers/okta" +// import Osu from "next-auth/providers/osu" +// import Patreon from "next-auth/providers/patreon" +// import Slack from "next-auth/providers/slack" +// import Spotify from "next-auth/providers/spotify" +// import Trakt from "next-auth/providers/trakt" +// import Twitch from "next-auth/providers/twitch" +// import Twitter, { TwitterLegacy } from "next-auth/providers/twitter" +// import Vk from "next-auth/providers/vk" +// import Wikimedia from "next-auth/providers/wikimedia" +// import WorkOS from "next-auth/providers/workos" // // Prisma // import { PrismaClient } from "@prisma/client" @@ -66,7 +67,7 @@ import WorkOS from "next-auth/providers/workos" // secret: process.env.SUPABASE_SERVICE_ROLE_KEY, // }) -export const authOptions: NextAuthOptions = { +export const authOptions: AuthOptions = { // adapter, debug: process.env.NODE_ENV !== "production", theme: { @@ -78,50 +79,77 @@ export const authOptions: NextAuthOptions = { credentials: { password: { label: "Password", type: "password" } }, async authorize(credentials) { if (credentials.password !== "pw") return null - return { name: "Fill Murray", email: "bill@fillmurray.com", image: "https://www.fillmurray.com/64/64" } + return { name: "Fill Murray", email: "bill@fillmurray.com", image: "https://www.fillmurray.com/64/64", id: "1", foo: "" } }, }), - Apple({ clientId: process.env.APPLE_ID, clientSecret: process.env.APPLE_SECRET }), + // Apple({ clientId: process.env.APPLE_ID, clientSecret: process.env.APPLE_SECRET }), Auth0({ clientId: process.env.AUTH0_ID, clientSecret: process.env.AUTH0_SECRET, issuer: process.env.AUTH0_ISSUER }), - AzureAD({ clientId: process.env.AZURE_AD_CLIENT_ID, clientSecret: process.env.AZURE_AD_CLIENT_SECRET, tenantId: process.env.AZURE_AD_TENANT_ID }), - AzureB2C({ clientId: process.env.AZURE_B2C_ID, clientSecret: process.env.AZURE_B2C_SECRET, issuer: process.env.AZURE_B2C_ISSUER }), - BoxyHQSAML({ issuer: "https://jackson-demo.boxyhq.com", clientId: "tenant=boxyhq.com&product=saml-demo.boxyhq.com", clientSecret: "dummy" }), - Cognito({ clientId: process.env.COGNITO_ID, clientSecret: process.env.COGNITO_SECRET, issuer: process.env.COGNITO_ISSUER }), - Discord({ clientId: process.env.DISCORD_ID, clientSecret: process.env.DISCORD_SECRET }), - DuendeIDS6({ clientId: "interactive.confidential", clientSecret: "secret", issuer: "https://demo.duendesoftware.com" }), - Facebook({ clientId: process.env.FACEBOOK_ID, clientSecret: process.env.FACEBOOK_SECRET }), - Foursquare({ clientId: process.env.FOURSQUARE_ID, clientSecret: process.env.FOURSQUARE_SECRET }), - Freshbooks({ clientId: process.env.FRESHBOOKS_ID, clientSecret: process.env.FRESHBOOKS_SECRET }), + // AzureAD({ + // clientId: process.env.AZURE_AD_CLIENT_ID, + // clientSecret: process.env.AZURE_AD_CLIENT_SECRET, + // tenantId: process.env.AZURE_AD_TENANT_ID, + // }), + // AzureB2C({ clientId: process.env.AZURE_B2C_ID, clientSecret: process.env.AZURE_B2C_SECRET, issuer: process.env.AZURE_B2C_ISSUER }), + // BoxyHQSAML({ issuer: "https://jackson-demo.boxyhq.com", clientId: "tenant=boxyhq.com&product=saml-demo.boxyhq.com", clientSecret: "dummy" }), + // Cognito({ clientId: process.env.COGNITO_ID, clientSecret: process.env.COGNITO_SECRET, issuer: process.env.COGNITO_ISSUER }), + // Discord({ clientId: process.env.DISCORD_ID, clientSecret: process.env.DISCORD_SECRET }), + // DuendeIDS6({ clientId: "interactive.confidential", clientSecret: "secret", issuer: "https://demo.duendesoftware.com" }), + // Facebook({ clientId: process.env.FACEBOOK_ID, clientSecret: process.env.FACEBOOK_SECRET }), + // Foursquare({ clientId: process.env.FOURSQUARE_ID, clientSecret: process.env.FOURSQUARE_SECRET }), + // Freshbooks({ clientId: process.env.FRESHBOOKS_ID, clientSecret: process.env.FRESHBOOKS_SECRET }), + GitHub({ clientId: process.env.GITHUB_ID, clientSecret: process.env.GITHUB_SECRET }), - Gitlab({ clientId: process.env.GITLAB_ID, clientSecret: process.env.GITLAB_SECRET }), - Google({ clientId: process.env.GOOGLE_ID, clientSecret: process.env.GOOGLE_SECRET }), - IDS4({ clientId: process.env.IDS4_ID, clientSecret: process.env.IDS4_SECRET, issuer: process.env.IDS4_ISSUER }), - Instagram({ clientId: process.env.INSTAGRAM_ID, clientSecret: process.env.INSTAGRAM_SECRET }), - Keycloak({ clientId: process.env.KEYCLOAK_ID, clientSecret: process.env.KEYCLOAK_SECRET, issuer: process.env.KEYCLOAK_ISSUER }), - Line({ clientId: process.env.LINE_ID, clientSecret: process.env.LINE_SECRET }), - LinkedIn({ clientId: process.env.LINKEDIN_ID, clientSecret: process.env.LINKEDIN_SECRET }), - Mailchimp({ clientId: process.env.MAILCHIMP_ID, clientSecret: process.env.MAILCHIMP_SECRET }), - Okta({ clientId: process.env.OKTA_ID, clientSecret: process.env.OKTA_SECRET, issuer: process.env.OKTA_ISSUER }), - Osu({ clientId: process.env.OSU_CLIENT_ID, clientSecret: process.env.OSU_CLIENT_SECRET }), - Patreon({ clientId: process.env.PATREON_ID, clientSecret: process.env.PATREON_SECRET }), - Slack({ clientId: process.env.SLACK_ID, clientSecret: process.env.SLACK_SECRET }), - Spotify({ clientId: process.env.SPOTIFY_ID, clientSecret: process.env.SPOTIFY_SECRET }), - Trakt({ clientId: process.env.TRAKT_ID, clientSecret: process.env.TRAKT_SECRET }), - Twitch({ clientId: process.env.TWITCH_ID, clientSecret: process.env.TWITCH_SECRET }), - Twitter({ version: "2.0", clientId: process.env.TWITTER_ID, clientSecret: process.env.TWITTER_SECRET }), - TwitterLegacy({ clientId: process.env.TWITTER_LEGACY_ID, clientSecret: process.env.TWITTER_LEGACY_SECRET }), - Vk({ clientId: process.env.VK_ID, clientSecret: process.env.VK_SECRET }), - Wikimedia({ clientId: process.env.WIKIMEDIA_ID, clientSecret: process.env.WIKIMEDIA_SECRET }), - WorkOS({ clientId: process.env.WORKOS_ID, clientSecret: process.env.WORKOS_SECRET }), + // Gitlab({ clientId: process.env.GITLAB_ID, clientSecret: process.env.GITLAB_SECRET }), + // Google({ clientId: process.env.GOOGLE_ID, clientSecret: process.env.GOOGLE_SECRET }), + // IDS4({ clientId: process.env.IDS4_ID, clientSecret: process.env.IDS4_SECRET, issuer: process.env.IDS4_ISSUER }), + // Instagram({ clientId: process.env.INSTAGRAM_ID, clientSecret: process.env.INSTAGRAM_SECRET }), + // Keycloak({ clientId: process.env.KEYCLOAK_ID, clientSecret: process.env.KEYCLOAK_SECRET, issuer: process.env.KEYCLOAK_ISSUER }), + // Line({ clientId: process.env.LINE_ID, clientSecret: process.env.LINE_SECRET }), + // LinkedIn({ clientId: process.env.LINKEDIN_ID, clientSecret: process.env.LINKEDIN_SECRET }), + // Mailchimp({ clientId: process.env.MAILCHIMP_ID, clientSecret: process.env.MAILCHIMP_SECRET }), + // Okta({ clientId: process.env.OKTA_ID, clientSecret: process.env.OKTA_SECRET, issuer: process.env.OKTA_ISSUER }), + // Osu({ clientId: process.env.OSU_CLIENT_ID, clientSecret: process.env.OSU_CLIENT_SECRET }), + // Patreon({ clientId: process.env.PATREON_ID, clientSecret: process.env.PATREON_SECRET }), + // Slack({ clientId: process.env.SLACK_ID, clientSecret: process.env.SLACK_SECRET }), + // Spotify({ clientId: process.env.SPOTIFY_ID, clientSecret: process.env.SPOTIFY_SECRET }), + // Trakt({ clientId: process.env.TRAKT_ID, clientSecret: process.env.TRAKT_SECRET }), + // Twitch({ clientId: process.env.TWITCH_ID, clientSecret: process.env.TWITCH_SECRET }), + // Twitter({ version: "2.0", clientId: process.env.TWITTER_ID, clientSecret: process.env.TWITTER_SECRET }), + // TwitterLegacy({ clientId: process.env.TWITTER_LEGACY_ID, clientSecret: process.env.TWITTER_LEGACY_SECRET }), + // Vk({ clientId: process.env.VK_ID, clientSecret: process.env.VK_SECRET }), + // Wikimedia({ clientId: process.env.WIKIMEDIA_ID, clientSecret: process.env.WIKIMEDIA_SECRET }), + // WorkOS({ clientId: process.env.WORKOS_ID, clientSecret: process.env.WORKOS_SECRET }), ], } if (authOptions.adapter) { - authOptions.providers.unshift( - // NOTE: You can start a fake e-mail server with `pnpm email` - // and then go to `http://localhost:1080` in the browser - Email({ server: "smtp://127.0.0.1:1025?tls.rejectUnauthorized=false" }) - ) + // TODO: + // authOptions.providers.unshift( + // // NOTE: You can start a fake e-mail server with `pnpm email` + // // and then go to `http://localhost:1080` in the browser + // Email({ server: "smtp://127.0.0.1:1025?tls.rejectUnauthorized=false" }) + // ) } -export default NextAuth(authOptions) +// TODO: move +function Auth(...args: any[]) { + if (args.length === 1) + return async (req: Request) => { + args[0].secret ??= process.env.NEXTAUTH_SECRET + const res = await AuthHandler(req, args[0]) + + // If the request expects a return URL, send it as JSON + // instead of doing an actual redirect. + const redirect = res.headers.get("Location") || /* TODO: remove */ (await req.json().json) + if (req.headers.get("X-Auth-Return-Redirect") && redirect) { + res.headers.delete("Location") + return new Response(JSON.stringify({ url: redirect }), res) + } + return res + } + return AuthHandler(args[0], args[1]) +} + +export default Auth(authOptions) + +export const config = { runtime: "experimental-edge" } diff --git a/packages/core/package.json b/packages/core/package.json new file mode 100644 index 0000000000..3ae629298e --- /dev/null +++ b/packages/core/package.json @@ -0,0 +1,53 @@ +{ + "name": "next-auth-core", + "private": true, + "version": "0.0.1", + "description": "Authentication for the web.", + "homepage": "https://next-auth.js.org", + "repository": "https://github.com/nextauthjs/next-auth.git", + "author": "Balázs Orbán ", + "contributors": [ + "Balázs Orbán ", + "Nico Domino ", + "Lluis Agusti ", + "Thang Huu Vu ", + "Iain Collins = DefaultAdapter & + (WithVerificationToken extends true + ? { + createVerificationToken: ( + verificationToken: VerificationToken + ) => Awaitable + /** + * Return verification token from the database + * and delete it so it cannot be used again. + */ + useVerificationToken: (params: { + identifier: string + token: string + }) => Awaitable + } + : {}) + +export interface DefaultAdapter { + createUser: (user: Omit) => Awaitable + getUser: (id: string) => Awaitable + getUserByEmail: (email: string) => Awaitable + /** Using the provider id and the id of the user for a specific account, get the user. */ + getUserByAccount: ( + providerAccountId: Pick + ) => Awaitable + updateUser: (user: Partial) => Awaitable + /** @todo Implement */ + deleteUser?: ( + userId: string + ) => Promise | Awaitable + linkAccount: ( + account: AdapterAccount + ) => Promise | Awaitable + /** @todo Implement */ + unlinkAccount?: ( + providerAccountId: Pick + ) => Promise | Awaitable + /** Creates a session for the user and returns it. */ + createSession: (session: { + sessionToken: string + userId: string + expires: Date + }) => Awaitable + getSessionAndUser: ( + sessionToken: string + ) => Awaitable<{ session: AdapterSession; user: AdapterUser } | null> + updateSession: ( + session: Partial & Pick + ) => Awaitable + /** + * Deletes a session from the database. + * It is preferred that this method also returns the session + * that is being deleted for logging purposes. + */ + deleteSession: ( + sessionToken: string + ) => Promise | Awaitable + createVerificationToken?: ( + verificationToken: VerificationToken + ) => Awaitable + /** + * Return verification token from the database + * and delete it so it cannot be used again. + */ + useVerificationToken?: (params: { + identifier: string + token: string + }) => Awaitable +} diff --git a/packages/core/src/css/index.css b/packages/core/src/css/index.css new file mode 100644 index 0000000000..7e74f04e52 --- /dev/null +++ b/packages/core/src/css/index.css @@ -0,0 +1,290 @@ +:root { + --border-width: 1px; + --border-radius: 0.5rem; + --color-error: #c94b4b; + --color-info: #157efb; + --color-info-text: #fff; +} + +.__next-auth-theme-auto, +.__next-auth-theme-light { + --color-background: #fff; + --color-text: #000; + --color-primary: #444; + --color-control-border: #bbb; + --color-button-active-background: #f9f9f9; + --color-button-active-border: #aaa; + --color-seperator: #ccc; +} + +.__next-auth-theme-dark { + --color-background: #000; + --color-text: #fff; + --color-primary: #ccc; + --color-control-border: #555; + --color-button-active-background: #060606; + --color-button-active-border: #666; + --color-seperator: #444; +} + +@media (prefers-color-scheme: dark) { + .__next-auth-theme-auto { + --color-background: #000; + --color-text: #fff; + --color-primary: #ccc; + --color-control-border: #555; + --color-button-active-background: #060606; + --color-button-active-border: #666; + --color-seperator: #444; + } +} + +body { + background-color: var(--color-background); + margin: 0; + padding: 0; + font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, + "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, + "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; +} + +h1 { + font-weight: 400; + margin-bottom: 1.5rem; + padding: 0 1rem; + color: var(--color-text); +} + +p { + color: var(--color-text); +} + +form { + margin: 0; + padding: 0; +} + +label { + font-weight: 500; + text-align: left; + margin-bottom: 0.25rem; + display: block; + color: var(--color-text); +} + +input[type] { + box-sizing: border-box; + display: block; + width: 100%; + padding: 0.5rem 1rem; + border: var(--border-width) solid var(--color-control-border); + background: var(--color-background); + font-size: 1rem; + border-radius: var(--border-radius); + box-shadow: inset 0 0.1rem 0.2rem rgba(0, 0, 0, 0.2); + color: var(--color-text); + + &:focus { + box-shadow: none; + } +} + +p { + margin: 0 0 1.5rem 0; + padding: 0 1rem; + font-size: 1.1rem; + line-height: 2rem; +} + +a.button { + text-decoration: none; + line-height: 1rem; + + &:link, + &:visited { + background-color: var(--color-background); + color: var(--color-primary); + } +} + +button, +a.button { + margin: 0 0 0.75rem 0; + padding: 0.75rem 1rem; + color: var(--provider-color, var(--color-primary)); + background-color: var(--provider-bg, var(--color-background)); + font-size: 1.1rem; + min-height: 62px; + border-color: rgba(0, 0, 0, 0.1); + border-radius: var(--border-radius); + transition: all 0.1s ease-in-out; + box-shadow: #000 0px 0px 0px 0px, #000 0px 0px 0px 0px, + rgba(0, 0, 0, 0.2) 0px 10px 15px -3px, rgba(0, 0, 0, 0.1) 0px 4px 6px -4px; + font-weight: 500; + position: relative; + display: flex; + align-items: center; + justify-content: center; + + &:has(img) { + justify-content: unset; + span { + flex-grow: 1; + } + } + &:hover { + cursor: pointer; + } + &:active { + box-shadow: 0 0.15rem 0.3rem rgba(0, 0, 0, 0.15), + inset 0 0.1rem 0.2rem var(--color-background), + inset 0 -0.1rem 0.1rem rgba(0, 0, 0, 0.1); + cursor: pointer; + } + #provider-logo { + display: block; + } + #provider-logo-dark { + display: none; + } +} + +@media (prefers-color-scheme: dark) { + button, + a.button { + color: var(--provider-dark-color, var(--color-primary)); + background-color: var(--provider-dark-bg, var(--color-background)); + border: 1px solid #0d0d0d; + box-shadow: #000 0px 0px 0px 0px, #ccc 0px 0px 0px 0px, + rgba(255, 255, 255, 0.01) 0px 5px 5px -3px, + rgba(255, 255, 255, 0.05) 0px 4px 6px -4px; + } + #provider-logo { + display: none !important; + } + #provider-logo-dark { + display: block !important; + } +} + +a.site { + color: var(--color-primary); + text-decoration: none; + font-size: 1rem; + line-height: 2rem; + + &:hover { + text-decoration: underline; + } +} + +.page { + position: absolute; + width: 100%; + height: 100%; + display: grid; + place-items: center; + margin: 0; + padding: 0; + + > div { + text-align: center; + padding: 0.5rem; + } +} + +.error { + a.button { + display: inline-block; + padding-left: 2rem; + padding-right: 2rem; + margin-top: 0.5rem; + } + + .message { + margin-bottom: 1.5rem; + } +} + +.signin { + input[type="text"] { + margin-left: auto; + margin-right: auto; + display: block; + } + + hr { + display: block; + border: 0; + border-top: 1px solid var(--color-seperator); + margin: 1.5em auto 0 auto; + overflow: visible; + + &::before { + content: "or"; + background: var(--color-background); + color: #888; + padding: 0 0.4rem; + position: relative; + top: -0.6rem; + } + } + + .error { + background: #f5f5f5; + font-weight: 500; + border-radius: 0.3rem; + background: var(--color-info); + + p { + text-align: left; + padding: 0.5rem 1rem; + font-size: 0.9rem; + line-height: 1.2rem; + color: var(--color-info-text); + } + } + + > div, + form { + display: block; + + input[type] { + margin-bottom: 0.5rem; + } + + button { + width: 100%; + } + + max-width: 300px; + } +} +.signout { + .message { + margin-bottom: 1.5rem; + } +} + +.logo { + display: inline-block; + margin-top: 100px; + max-width: 300px; + max-height: 150px; +} + +.card { + max-width: max-content; + border: 1px solid var(--color-control-border); + border-radius: 5px; + padding: 20px 50px; + margin: 50px auto; + + .header { + color: var(--color-primary); + } +} + +.section-header { + color: var(--brand-color, var(--color-text)); +} diff --git a/packages/core/src/css/index.ts b/packages/core/src/css/index.ts new file mode 100644 index 0000000000..d6ebc4c630 --- /dev/null +++ b/packages/core/src/css/index.ts @@ -0,0 +1,19 @@ +// To support serverless targets (which don't work if you try to read in things +// like CSS files at run time) this file is replaced in production builds with +// a function that returns compiled CSS (embedded as a string in the function). + +export default function css() { + if (globalThis.EdgeRuntime) return "TODO" + // eslint-disable-next-line @typescript-eslint/no-var-requires + const fs = require("fs") + // eslint-disable-next-line @typescript-eslint/no-var-requires + const path = require("path") + + const pathToCss = path.join( + process.cwd(), + process.env.NODE_ENV === "development" + ? "node_modules/next-auth/css/index.css" + : "/src/css/index.css" + ) + return fs.readFileSync(pathToCss, "utf8") +} diff --git a/packages/core/src/errors.ts b/packages/core/src/errors.ts new file mode 100644 index 0000000000..2e3df56f62 --- /dev/null +++ b/packages/core/src/errors.ts @@ -0,0 +1,132 @@ +import type { EventCallbacks, LoggerInstance } from "." + +/** + * Same as the default `Error`, but it is JSON serializable. + * @source https://iaincollins.medium.com/error-handling-in-javascript-a6172ccdf9af + */ +export class UnknownError extends Error { + code: string + constructor(error: Error | string) { + // Support passing error or string + super((error as Error)?.message ?? error) + this.name = "UnknownError" + this.code = (error as any).code + if (error instanceof Error) { + this.stack = error.stack + } + } + + toJSON() { + return { + name: this.name, + message: this.message, + stack: this.stack, + } + } +} + +export class OAuthCallbackError extends UnknownError { + name = "OAuthCallbackError" +} + +/** + * Thrown when an Email address is already associated with an account + * but the user is trying an OAuth account that is not linked to it. + */ +export class AccountNotLinkedError extends UnknownError { + name = "AccountNotLinkedError" +} + +export class MissingAPIRoute extends UnknownError { + name = "MissingAPIRouteError" + code = "MISSING_NEXTAUTH_API_ROUTE_ERROR" +} + +export class MissingSecret extends UnknownError { + name = "MissingSecretError" + code = "NO_SECRET" +} + +export class MissingAuthorize extends UnknownError { + name = "MissingAuthorizeError" + code = "CALLBACK_CREDENTIALS_HANDLER_ERROR" +} + +export class MissingAdapter extends UnknownError { + name = "MissingAdapterError" + code = "EMAIL_REQUIRES_ADAPTER_ERROR" +} + +export class MissingAdapterMethods extends UnknownError { + name = "MissingAdapterMethodsError" + code = "MISSING_ADAPTER_METHODS_ERROR" +} + +export class UnsupportedStrategy extends UnknownError { + name = "UnsupportedStrategyError" + code = "CALLBACK_CREDENTIALS_JWT_ERROR" +} + +export class InvalidCallbackUrl extends UnknownError { + name = "InvalidCallbackUrlError" + code = "INVALID_CALLBACK_URL_ERROR" +} + +export class InvalidEndpoints extends UnknownError { + name = "InvalidEndpoints" + code = "INVALID_ENDPOINTS_ERROR" +} + +type Method = (...args: any[]) => Promise + +export function upperSnake(s: string) { + return s.replace(/([A-Z])/g, "_$1").toUpperCase() +} + +export function capitalize(s: string) { + return `${s[0].toUpperCase()}${s.slice(1)}` +} + +/** + * Wraps an object of methods and adds error handling. + */ +export function eventsErrorHandler( + methods: Partial, + logger: LoggerInstance +): Partial { + return Object.keys(methods).reduce((acc, name) => { + acc[name] = async (...args: any[]) => { + try { + const method: Method = methods[name as keyof Method] + return await method(...args) + } catch (e) { + logger.error(`${upperSnake(name)}_EVENT_ERROR`, e as Error) + } + } + return acc + }, {}) +} + +/** Handles adapter induced errors. */ +export function adapterErrorHandler( + adapter: TAdapter | undefined, + logger: LoggerInstance +): TAdapter | undefined { + if (!adapter) return + + return Object.keys(adapter).reduce((acc, name) => { + acc[name] = async (...args: any[]) => { + try { + logger.debug(`adapter_${name}`, { args }) + const method: Method = adapter[name as keyof Method] + return await method(...args) + } catch (error) { + logger.error(`adapter_error_${name}`, error as Error) + const e = new UnknownError(error as Error) + e.name = `${capitalize(name)}Error` + throw e + } + } + return acc + }, {}) +} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts new file mode 100644 index 0000000000..6341f179f1 --- /dev/null +++ b/packages/core/src/index.ts @@ -0,0 +1,251 @@ +import { init } from "./init" +import { assertConfig } from "./lib/assert" +import { SessionStore } from "./lib/cookie" +import { toInternalRequest, toResponse } from "./lib/web" +import renderPage from "./pages" +import * as routes from "./routes" +import logger, { setLogger } from "./utils/logger" + +import type { ErrorType } from "./pages/error" +import type { AuthOptions, RequestInternal, ResponseInternal } from "./types" + +export * from "./types" + +async function AuthHandlerInternal< + Body extends string | Record | any[] +>(params: { + req: RequestInternal + options: AuthOptions + /** REVIEW: Is this the best way to skip parsing the body in Node.js? */ + parsedBody?: any +}): Promise> { + const { options: userOptions, req } = params + setLogger(userOptions.logger, userOptions.debug) + + const assertionResult = assertConfig({ options: userOptions, req }) + + if (Array.isArray(assertionResult)) { + assertionResult.forEach(logger.warn) + } else if (assertionResult instanceof Error) { + // Bail out early if there's an error in the user config + logger.error(assertionResult.code, assertionResult) + + const htmlPages = ["signin", "signout", "error", "verify-request"] + if (!htmlPages.includes(req.action) || req.method !== "GET") { + const message = `There is a problem with the server configuration. Check the server logs for more information.` + return { + status: 500, + headers: { "Content-Type": "application/json" }, + 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 = + pages?.error && req.query?.callbackUrl?.startsWith(pages.error) + + if (!pages?.error || authOnErrorPage) { + if (authOnErrorPage) { + logger.error( + "AUTH_ON_ERROR_PAGE_ERROR", + new Error( + `The error page ${pages?.error} should not require authentication` + ) + ) + } + const render = renderPage({ theme }) + return render.error({ error: "configuration" }) + } + + return { + redirect: `${pages.error}?error=Configuration`, + } + } + + const { action, providerId, error, method = "GET" } = 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: method === "POST", + }) + + const sessionStore = new SessionStore( + options.cookies.sessionToken, + req, + options.logger + ) + + if (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": { + 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": + return { + headers: { "Content-Type": "application/json" }, + body: { csrfToken: options.csrfToken } as any, + cookies, + } + case "signin": + if (pages.signIn) { + let signinUrl = `${pages.signIn}${ + pages.signIn.includes("?") ? "&" : "?" + }callbackUrl=${encodeURIComponent(options.callbackUrl)}` + if (error) + signinUrl = `${signinUrl}&error=${encodeURIComponent(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, + headers: req.headers, + cookies: req.cookies, + method, + options, + sessionStore, + }) + 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": + // 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 } + } + + if (pages.error) { + return { + redirect: `${pages.error}${ + pages.error.includes("?") ? "&" : "?" + }error=${error}`, + cookies, + } + } + + return render.error({ error: error as ErrorType }) + default: + } + } else if (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, sessionStore }) + 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, + headers: req.headers, + cookies: req.cookies, + method, + options, + sessionStore, + }) + 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 as Error) + } + } + return {} + default: + } + } + + return { + status: 400, + 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 { + const req = await toInternalRequest(request) + const internalResponse = await AuthHandlerInternal({ req, options }) + return toResponse(internalResponse) +} diff --git a/packages/core/src/init.ts b/packages/core/src/init.ts new file mode 100644 index 0000000000..9f6b804900 --- /dev/null +++ b/packages/core/src/init.ts @@ -0,0 +1,160 @@ +import { adapterErrorHandler, eventsErrorHandler } from "./errors" +import * as jwt from "./jwt" +import { createCallbackUrl } from "./lib/callback-url" +import * as cookie from "./lib/cookie" +import { createCSRFToken } from "./lib/csrf-token" +import { defaultCallbacks } from "./lib/default-callbacks" +import parseProviders from "./lib/providers" +import { createHash } from "./lib/web" +import logger from "./utils/logger" +import parseUrl from "./utils/parse-url" + +import type { AuthOptions, InternalOptions, RequestInternal } from "." + +interface InitParams { + host?: string + userOptions: AuthOptions + providerId?: string + action: InternalOptions["action"] + /** Callback URL value extracted from the incoming request. */ + callbackUrl?: string + /** CSRF token value extracted from the incoming request. From body if POST, from query if GET */ + csrfToken?: string + /** Is the incoming request a POST request? */ + isPost: boolean + cookies: RequestInternal["cookies"] +} + +/** Initialize all internal options and cookies. */ +export async function init({ + userOptions, + providerId, + action, + host, + cookies: reqCookies, + callbackUrl: reqCallbackUrl, + csrfToken: reqCsrfToken, + isPost, +}: InitParams): Promise<{ + options: InternalOptions + cookies: cookie.Cookie[] +}> { + const url = parseUrl(host) + + /** + * 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, + url, + providerId, + runtime: userOptions.__internal__?.runtime, + }) + + const maxAge = 30 * 24 * 60 * 60 // Sessions expire after 30 days of being idle by default + + // User provided options are overriden by other options, + // except for the options with special handling above + const options: InternalOptions = { + debug: false, + pages: {}, + theme: { + colorScheme: "auto", + logo: "", + brandColor: "", + buttonText: "", + }, + // Custom options override defaults + ...userOptions, + // These computed settings can have values in userOptions but we override them + // and are request-specific. + url, + action, + // @ts-expect-errors + provider, + cookies: { + ...cookie.defaultCookies( + userOptions.useSecureCookies ?? url.base.startsWith("https://") + ), + // Allow user cookie options to override any cookie settings above + ...userOptions.cookies, + }, + secret, + providers, + // Session options + session: { + // If no adapter specified, force use of JSON Web Tokens (stateless) + strategy: userOptions.adapter ? "database" : "jwt", + maxAge, + updateAge: 24 * 60 * 60, + generateSessionToken: crypto.randomUUID, + ...userOptions.session, + }, + // JWT options + jwt: { + secret, // Use application secret if no keys specified + maxAge, // same as session maxAge, + encode: jwt.encode, + decode: jwt.decode, + ...userOptions.jwt, + }, + // Event messages + events: eventsErrorHandler(userOptions.events ?? {}, logger), + adapter: adapterErrorHandler(userOptions.adapter, logger), + // Callback functions + callbacks: { ...defaultCallbacks, ...userOptions.callbacks }, + logger, + callbackUrl: url.origin, + } + + // Init cookies + + const cookies: cookie.Cookie[] = [] + + const { + csrfToken, + cookie: csrfCookie, + csrfTokenVerified, + } = await createCSRFToken({ + options, + cookieValue: reqCookies?.[options.cookies.csrfToken.name], + isPost, + bodyValue: reqCsrfToken, + }) + + options.csrfToken = csrfToken + options.csrfTokenVerified = csrfTokenVerified + + if (csrfCookie) { + cookies.push({ + name: options.cookies.csrfToken.name, + value: csrfCookie, + options: options.cookies.csrfToken.options, + }) + } + + const { callbackUrl, callbackUrlCookie } = await createCallbackUrl({ + options, + cookieValue: reqCookies?.[options.cookies.callbackUrl.name], + paramValue: reqCallbackUrl, + }) + options.callbackUrl = callbackUrl + if (callbackUrlCookie) { + cookies.push({ + name: options.cookies.callbackUrl.name, + value: callbackUrlCookie, + options: options.cookies.callbackUrl.options, + }) + } + + return { options, cookies } +} diff --git a/packages/core/src/jwt/index.ts b/packages/core/src/jwt/index.ts new file mode 100644 index 0000000000..dcaeef4d02 --- /dev/null +++ b/packages/core/src/jwt/index.ts @@ -0,0 +1,128 @@ +import { EncryptJWT, jwtDecrypt } from "jose" +import hkdf from "@panva/hkdf" +import { v4 as uuid } from "uuid" +import { SessionStore } from "../lib/cookie" +import type { JWT, JWTDecodeParams, JWTEncodeParams, JWTOptions } from "./types" +import type { LoggerInstance } from ".." + +export * from "./types" + +const DEFAULT_MAX_AGE = 30 * 24 * 60 * 60 // 30 days + +const now = () => (Date.now() / 1000) | 0 + +/** Issues a JWT. By default, the JWT is encrypted using "A256GCM". */ +export async function encode(params: JWTEncodeParams) { + const { token = {}, secret, maxAge = DEFAULT_MAX_AGE } = params + const encryptionSecret = await getDerivedEncryptionKey(secret) + return await new EncryptJWT(token) + .setProtectedHeader({ alg: "dir", enc: "A256GCM" }) + .setIssuedAt() + .setExpirationTime(now() + maxAge) + .setJti(uuid()) + .encrypt(encryptionSecret) +} + +/** Decodes a NextAuth.js issued JWT. */ +export async function decode(params: JWTDecodeParams): Promise { + const { token, secret } = params + if (!token) return null + const encryptionSecret = await getDerivedEncryptionKey(secret) + const { payload } = await jwtDecrypt(token, encryptionSecret, { + clockTolerance: 15, + }) + return payload +} + +export interface GetTokenParams { + /** The request containing the JWT either in the cookies or in the `Authorization` header. */ + req: + | Request + | { cookies: Record; headers: Record } + /** + * Use secure prefix for cookie name, unless URL in `NEXTAUTH_URL` is http:// + * or not set (e.g. development or test instance) case use unprefixed name + */ + secureCookie?: boolean + /** If the JWT is in the cookie, what name `getToken()` should look for. */ + cookieName?: string + /** + * `getToken()` will return the raw JWT if this is set to `true` + * @default false + */ + raw?: R + /** + * The same `secret` used in the `NextAuth` configuration. + * Defaults to the `NEXTAUTH_SECRET` environment variable. + */ + secret?: string + decode?: JWTOptions["decode"] + logger?: LoggerInstance | Console +} + +/** + * Takes a NextAuth.js request (`req`) and returns either the NextAuth.js issued JWT's payload, + * or the raw JWT string. We look for the JWT in the either the cookies, or the `Authorization` header. + * [Documentation](https://next-auth.js.org/tutorials/securing-pages-and-api-routes#using-gettoken) + */ +export async function getToken( + params: GetTokenParams +): Promise { + const { + req, + secureCookie = process.env.NEXTAUTH_URL?.startsWith("https://") ?? + !!process.env.VERCEL, + cookieName = secureCookie + ? "__Secure-next-auth.session-token" + : "next-auth.session-token", + raw, + decode: _decode = decode, + logger = console, + secret = process.env.NEXTAUTH_SECRET, + } = params + + if (!req) throw new Error("Must pass `req` to JWT getToken()") + + const sessionStore = new SessionStore( + { name: cookieName, options: { secure: secureCookie } }, + // @ts-expect-error + { cookies: req.cookies, headers: req.headers }, + logger + ) + + let token = sessionStore.value + + const authorizationHeader = + req.headers instanceof Headers + ? req.headers.get("authorization") + : req.headers.authorization + + if (!token && authorizationHeader?.split(" ")[0] === "Bearer") { + const urlEncodedToken = authorizationHeader.split(" ")[1] + token = decodeURIComponent(urlEncodedToken) + } + + // @ts-expect-error + if (!token) return null + + // @ts-expect-error + if (raw) return token + + try { + // @ts-expect-error + return await _decode({ token, secret }) + } catch { + // @ts-expect-error + return null + } +} + +async function getDerivedEncryptionKey(secret: string | Buffer) { + return await hkdf( + "sha256", + secret, + "", + "NextAuth.js Generated Encryption Key", + 32 + ) +} diff --git a/packages/core/src/jwt/types.ts b/packages/core/src/jwt/types.ts new file mode 100644 index 0000000000..ecd0e2a9b0 --- /dev/null +++ b/packages/core/src/jwt/types.ts @@ -0,0 +1,54 @@ +import type { Awaitable } from ".." + +export interface DefaultJWT extends Record { + name?: string | null + email?: string | null + picture?: string | null + sub?: string +} + +/** + * Returned by the `jwt` callback and `getToken`, when using JWT sessions + * + * [`jwt` callback](https://next-auth.js.org/configuration/callbacks#jwt-callback) | [`getToken`](https://next-auth.js.org/tutorials/securing-pages-and-api-routes#using-gettoken) + */ +export interface JWT extends Record, DefaultJWT {} + +export interface JWTEncodeParams { + /** The JWT payload. */ + token?: JWT + /** The secret used to encode the NextAuth.js issued JWT. */ + secret: string | Buffer + /** + * The maximum age of the NextAuth.js issued JWT in seconds. + * @default 30 * 24 * 30 * 60 // 30 days + */ + maxAge?: number +} + +export interface JWTDecodeParams { + /** The NextAuth.js issued JWT to be decoded */ + token?: string + /** The secret used to decode the NextAuth.js issued JWT. */ + secret: string | Buffer +} + +export interface JWTOptions { + /** + * The secret used to encode/decode the NextAuth.js issued JWT. + * @deprecated Set the `NEXTAUTH_SECRET` environment vairable or + * use the top-level `secret` option instead + */ + secret: string + /** + * The maximum age of the NextAuth.js issued JWT in seconds. + * @default 30 * 24 * 30 * 60 // 30 days + */ + maxAge: number + /** Override this method to control the NextAuth.js issued JWT encoding. */ + encode: (params: JWTEncodeParams) => Awaitable + /** Override this method to control the NextAuth.js issued JWT decoding. */ + decode: (params: JWTDecodeParams) => Awaitable +} + +export type Secret = string | Buffer diff --git a/packages/core/src/lib/assert.ts b/packages/core/src/lib/assert.ts new file mode 100644 index 0000000000..34ff7b89bc --- /dev/null +++ b/packages/core/src/lib/assert.ts @@ -0,0 +1,171 @@ +import { + InvalidCallbackUrl, + InvalidEndpoints, + MissingAdapter, + MissingAdapterMethods, + MissingAPIRoute, + MissingAuthorize, + MissingSecret, + UnsupportedStrategy, +} from "../errors" +import parseUrl from "../utils/parse-url" +import { defaultCookies } from "./cookie" + +import type { AuthOptions, RequestInternal } from ".." +import type { WarningCode } from "../utils/logger" + +type ConfigError = + | MissingAdapter + | MissingAdapterMethods + | MissingAPIRoute + | MissingAuthorize + | MissingSecret + | InvalidCallbackUrl + | UnsupportedStrategy + | InvalidEndpoints + | UnsupportedStrategy + +let warned = false + +function isValidHttpUrl(url: string, baseUrl: string) { + try { + return /^https?:/.test( + new URL(url, url.startsWith("/") ? baseUrl : undefined).protocol + ) + } catch { + return false + } +} + +/** + * Verify that the user configured `next-auth` correctly. + * Good place to mention deprecations as well. + * + * REVIEW: Make some of these and corresponding docs less Next.js specific? + */ +export function assertConfig(params: { + options: AuthOptions + req: RequestInternal +}): ConfigError | WarningCode[] { + const { options, req } = params + + const warnings: WarningCode[] = [] + + if (!warned) { + if (!req.host) warnings.push("NEXTAUTH_URL") + + // TODO: Make this throw an error in next major. This will also get rid of `NODE_ENV` + if (!options.secret && process.env.NODE_ENV !== "production") + warnings.push("NO_SECRET") + + if (options.debug) warnings.push("DEBUG_ENABLED") + } + + if (!options.secret && process.env.NODE_ENV === "production") { + return new MissingSecret("Please define a `secret` in production.") + } + + // req.query isn't defined when asserting `unstable_getServerSession` for example + if (!req.query?.nextauth && !req.action) { + return new MissingAPIRoute( + "Cannot find [...nextauth].{js,ts} in `/pages/api/auth`. Make sure the filename is written correctly." + ) + } + + const callbackUrlParam = req.query?.callbackUrl as string | undefined + + const url = parseUrl(req.host) + + if (callbackUrlParam && !isValidHttpUrl(callbackUrlParam, url.base)) { + return new InvalidCallbackUrl( + `Invalid callback URL. Received: ${callbackUrlParam}` + ) + } + + const { callbackUrl: defaultCallbackUrl } = defaultCookies( + options.useSecureCookies ?? url.base.startsWith("https://") + ) + const callbackUrlCookie = + req.cookies?.[options.cookies?.callbackUrl?.name ?? defaultCallbackUrl.name] + + if (callbackUrlCookie && !isValidHttpUrl(callbackUrlCookie, url.base)) { + return new InvalidCallbackUrl( + `Invalid callback URL. Received: ${callbackUrlCookie}` + ) + } + + let hasCredentials, hasEmail + let hasTwitterOAuth2 + + for (const provider of options.providers) { + if ( + provider.type === "oauth" && + !(provider.issuer ?? provider.options?.issuer) + ) { + 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") + hasTwitterOAuth2 = true + } + + if (hasCredentials) { + const dbStrategy = options.session?.strategy === "database" + const onlyCredentials = !options.providers.some( + (p) => p.type !== "credentials" + ) + if (dbStrategy && onlyCredentials) { + return new UnsupportedStrategy( + "Signin in with credentials only supported if JWT strategy is enabled" + ) + } + + const credentialsNoAuthorize = options.providers.some( + (p) => p.type === "credentials" && !p.authorize + ) + if (credentialsNoAuthorize) { + return new MissingAuthorize( + "Must define an authorize() handler to use credentials authentication provider" + ) + } + } + + if (hasEmail) { + const { adapter } = options + if (!adapter) { + return new MissingAdapter("E-mail login requires an adapter.") + } + + const missingMethods = [ + "createVerificationToken", + "useVerificationToken", + "getUserByEmail", + ].filter((method) => !adapter[method]) + + if (missingMethods.length) { + return new MissingAdapterMethods( + `Required adapter methods were missing: ${missingMethods.join(", ")}` + ) + } + } + + if (!warned) { + if (hasTwitterOAuth2) warnings.push("TWITTER_OAUTH_2_BETA") + warned = true + } + + return warnings +} diff --git a/packages/core/src/lib/callback-handler.ts b/packages/core/src/lib/callback-handler.ts new file mode 100644 index 0000000000..9b3ae760a9 --- /dev/null +++ b/packages/core/src/lib/callback-handler.ts @@ -0,0 +1,228 @@ +import { AccountNotLinkedError } from "../errors" +import { fromDate } from "../utils/date" + +import type { Account, InternalOptions, User } from ".." +import type { AdapterSession, AdapterUser } from "../adapters" +import type { JWT } from "../jwt" +import type { OAuthConfig } from "../providers" +import type { SessionToken } from "./cookie" + +/** + * This function handles the complex flow of signing users in, and either creating, + * linking (or not linking) accounts depending on if the user is currently logged + * in, if they have account already and the authentication mechanism they are using. + * + * It prevents insecure behaviour, such as linking OAuth accounts unless a user is + * signed in and authenticated with an existing valid account. + * + * 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. + */ +export default async function callbackHandler(params: { + sessionToken?: SessionToken + profile: User | AdapterUser | { email: string } + account: Account | null + options: InternalOptions +}) { + const { sessionToken, profile: _profile, account, options } = params + // Input validation + if (!account?.providerAccountId || !account.type) + throw new Error("Missing or invalid provider account") + if (!["email", "oauth"].includes(account.type)) + throw new Error("Provider not supported") + + const { + adapter, + jwt, + events, + session: { strategy: sessionStrategy, generateSessionToken }, + } = options + + // If no adapter is configured then we don't have a database and cannot + // persist data; in this mode we just return a dummy session object. + if (!adapter) { + return { user: _profile as User, account } + } + + const profile = _profile as AdapterUser + + const { + createUser, + updateUser, + getUser, + getUserByAccount, + getUserByEmail, + linkAccount, + createSession, + getSessionAndUser, + deleteSession, + } = adapter + + let session: AdapterSession | JWT | null = null + let user: AdapterUser | null = null + let isNewUser = false + + const useJwtSession = sessionStrategy === "jwt" + + if (sessionToken) { + if (useJwtSession) { + try { + session = await jwt.decode({ ...jwt, token: sessionToken }) + if (session && "sub" in session && session.sub) { + user = await getUser(session.sub) + } + } catch { + // If session can't be verified, treat as no session + } + } else { + const userAndSession = await getSessionAndUser(sessionToken) + if (userAndSession) { + session = userAndSession.session + user = userAndSession.user + } + } + } + + if (account.type === "email") { + // If signing in with an email, check if an account with the same email address exists already + const userByEmail = await getUserByEmail(profile.email) + 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 + if (user?.id !== userByEmail.id && !useJwtSession && sessionToken) { + // Delete existing session if they are currently signed in as another user. + // This will switch user accounts for the session in cases where the user was + // already logged in with a different account. + await deleteSession(sessionToken) + } + + // Update emailVerified property on the user object + user = await updateUser({ id: userByEmail.id, emailVerified: new Date() }) + await events.updateUser?.({ user }) + } else { + const { id: _, ...newUser } = { ...profile, emailVerified: new Date() } + // Create user account if there isn't one for the email address already + user = await createUser(newUser) + await events.createUser?.({ user }) + isNewUser = true + } + + // Create new session + session = useJwtSession + ? {} + : await createSession({ + sessionToken: generateSessionToken(), + userId: user.id, + expires: fromDate(options.session.maxAge), + }) + + return { session, user, isNewUser } + } else if (account.type === "oauth") { + // If signing in with OAuth account, check to see if the account exists already + const userByAccount = await getUserByAccount({ + providerAccountId: account.providerAccountId, + provider: account.provider, + }) + if (userByAccount) { + if (user) { + // If the user is already signed in with this account, we don't need to do anything + if (userByAccount.id === user.id) { + return { session, user, isNewUser } + } + // If the user is currently signed in, but the new account they are signing in + // with is already associated with another user, then we cannot link them + // and need to return an error. + throw new AccountNotLinkedError( + "The account is already associated with another user" + ) + } + // 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({ + sessionToken: generateSessionToken(), + userId: userByAccount.id, + expires: fromDate(options.session.maxAge), + }) + + return { session, user: userByAccount, isNewUser } + } else { + if (user) { + // If the user is already signed in and the OAuth account isn't already associated + // with another user account then we can go ahead and link the accounts safely. + await linkAccount({ ...account, userId: user.id }) + await events.linkAccount?.({ user, account, profile }) + + // As they are already signed in, we don't need to do anything after linking them + return { session, user, isNewUser } + } + + // If the user is not signed in and it looks like a new OAuth account then we + // check there also isn't an user account already associated with the same + // email address as the one in the OAuth profile. + // + // This step is often overlooked in OAuth implementations, but covers the following cases: + // + // 1. It makes it harder for someone to accidentally create two accounts. + // e.g. by signin in with email, then again with an oauth account connected to the same email. + // 2. It makes it harder to hijack a user account using a 3rd party OAuth account. + // e.g. by creating an oauth account then changing the email address associated with it. + // + // It's quite common for services to automatically link accounts in this case, but it's + // better practice to require the user to sign in *then* link accounts to be sure + // someone is not exploiting a problem with a third party OAuth service. + // + // 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 + if (userByEmail) { + const provider = options.provider as OAuthConfig + if (provider?.allowDangerousEmailAccountLinking) { + // If you trust the oauth provider to correctly verify email addresses, you can opt-in to + // account linking even when the user is not signed-in. + user = userByEmail + } else { + // 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 + // OAuth profile the user has just tried to sign in with. + // + // We don't want to have two accounts with the same email address, and we don't + // want to link them in case it's not safe to do so, so instead we prompt the user + // to sign in via email to verify their identity and then link the accounts. + throw new AccountNotLinkedError( + "Another account already exists with the same e-mail address" + ) + } + } else { + // If the current user is not logged in and the profile isn't linked to any user + // accounts (by email or provider account id)... + // + // If no account matching the same [provider].id or .email exists, we can + // create a new account for the user, link it to the OAuth acccount and + // create a new session for them so they are signed in with it. + const { id: _, ...newUser } = { ...profile, emailVerified: null } + user = await createUser(newUser) + } + await events.createUser?.({ user }) + + await linkAccount({ ...account, userId: user.id }) + await events.linkAccount?.({ user, account, profile }) + + session = useJwtSession + ? {} + : await createSession({ + sessionToken: generateSessionToken(), + userId: user.id, + expires: fromDate(options.session.maxAge), + }) + + return { session, user, isNewUser: true } + } + } + + throw new Error("Unsupported account type") +} diff --git a/packages/core/src/lib/callback-url.ts b/packages/core/src/lib/callback-url.ts new file mode 100644 index 0000000000..39c0692de6 --- /dev/null +++ b/packages/core/src/lib/callback-url.ts @@ -0,0 +1,42 @@ +import type { InternalOptions } from ".." + +interface CreateCallbackUrlParams { + options: InternalOptions + /** Try reading value from request body (POST) then from query param (GET) */ + paramValue?: string + cookieValue?: string +} + +/** + * Get callback URL based on query param / cookie + validation, + * and add it to `req.options.callbackUrl`. + */ +export async function createCallbackUrl({ + options, + paramValue, + cookieValue, +}: CreateCallbackUrlParams) { + const { url, callbacks } = options + + let callbackUrl = url.origin + + if (paramValue) { + // If callbackUrl form field or query parameter is passed try to use it if allowed + callbackUrl = await callbacks.redirect({ + url: paramValue, + baseUrl: url.origin, + }) + } else if (cookieValue) { + // If no callbackUrl specified, try using the value from the cookie if allowed + callbackUrl = await callbacks.redirect({ + url: cookieValue, + baseUrl: url.origin, + }) + } + + return { + callbackUrl, + // Save callback URL in a cookie so that it can be used for subsequent requests in signin/signout/callback flow + callbackUrlCookie: callbackUrl !== cookieValue ? callbackUrl : undefined, + } +} diff --git a/packages/core/src/lib/cookie.ts b/packages/core/src/lib/cookie.ts new file mode 100644 index 0000000000..e038a9e3f7 --- /dev/null +++ b/packages/core/src/lib/cookie.ts @@ -0,0 +1,236 @@ +import type { + CookieOption, + CookiesOptions, + LoggerInstance, + SessionStrategy, +} from ".." + +// Uncomment to recalculate the estimated size +// of an empty session cookie +// import { serialize } from "cookie" +// console.log( +// "Cookie estimated to be ", +// serialize(`__Secure.next-auth.session-token.0`, "", { +// expires: new Date(), +// httpOnly: true, +// maxAge: Number.MAX_SAFE_INTEGER, +// path: "/", +// sameSite: "strict", +// secure: true, +// domain: "example.com", +// }).length, +// " bytes" +// ) + +const ALLOWED_COOKIE_SIZE = 4096 +// Based on commented out section above +const ESTIMATED_EMPTY_COOKIE_SIZE = 163 +const CHUNK_SIZE = ALLOWED_COOKIE_SIZE - ESTIMATED_EMPTY_COOKIE_SIZE + +// REVIEW: Is there any way to defer two types of strings? + +/** Stringified form of `JWT`. Extract the content with `jwt.decode` */ +export type JWTString = string + +export type SetCookieOptions = Partial & { + expires?: Date | string + encode?: (val: unknown) => string +} + +/** + * If `options.session.strategy` is set to `jwt`, this is a stringified `JWT`. + * In case of `strategy: "database"`, this is the `sessionToken` of the session in the database. + */ +export type SessionToken = T extends "jwt" + ? JWTString + : string + +/** + * Use secure cookies if the site uses HTTPS + * This being conditional allows cookies to work non-HTTPS development URLs + * Honour secure cookie option, which sets 'secure' and also adds '__Secure-' + * prefix, but enable them by default if the site URL is HTTPS; but not for + * non-HTTPS URLs like http://localhost which are used in development). + * For more on prefixes see https://googlechrome.github.io/samples/cookie-prefixes/ + * + * @TODO Review cookie settings (names, options) + */ +export function defaultCookies(useSecureCookies: boolean): CookiesOptions { + const cookiePrefix = useSecureCookies ? "__Secure-" : "" + return { + // default cookie options + sessionToken: { + name: `${cookiePrefix}next-auth.session-token`, + options: { + httpOnly: true, + sameSite: "lax", + path: "/", + secure: useSecureCookies, + }, + }, + callbackUrl: { + name: `${cookiePrefix}next-auth.callback-url`, + options: { + httpOnly: true, + sameSite: "lax", + path: "/", + secure: useSecureCookies, + }, + }, + csrfToken: { + // Default to __Host- for CSRF token for additional protection if using useSecureCookies + // NB: The `__Host-` prefix is stricter than the `__Secure-` prefix. + name: `${useSecureCookies ? "__Host-" : ""}next-auth.csrf-token`, + options: { + httpOnly: true, + sameSite: "lax", + path: "/", + secure: useSecureCookies, + }, + }, + pkceCodeVerifier: { + name: `${cookiePrefix}next-auth.pkce.code_verifier`, + options: { + httpOnly: true, + sameSite: "lax", + path: "/", + secure: useSecureCookies, + maxAge: 60 * 15, // 15 minutes in seconds + }, + }, + state: { + name: `${cookiePrefix}next-auth.state`, + options: { + httpOnly: true, + sameSite: "lax", + path: "/", + secure: useSecureCookies, + maxAge: 60 * 15, // 15 minutes in seconds + }, + }, + nonce: { + name: `${cookiePrefix}next-auth.nonce`, + options: { + httpOnly: true, + sameSite: "lax", + path: "/", + secure: useSecureCookies, + }, + }, + } +} + +export interface Cookie extends CookieOption { + value: string +} + +type Chunks = Record + +export class SessionStore { + #chunks: Chunks = {} + #option: CookieOption + #logger: LoggerInstance | Console + + constructor( + option: CookieOption, + req: Partial<{ cookies: any; headers: any }>, + logger: LoggerInstance | Console + ) { + this.#logger = logger + this.#option = option + + const { cookies } = req + const { name: cookieName } = option + + if (typeof cookies?.getAll === "function") { + // Next.js ^v13.0.1 (Edge Env) + for (const { name, value } of cookies.getAll()) { + if (name.startsWith(cookieName)) { + this.#chunks[name] = value + } + } + } else if (cookies instanceof Map) { + for (const name of cookies.keys()) { + if (name.startsWith(cookieName)) this.#chunks[name] = cookies.get(name) + } + } else { + for (const name in cookies) { + if (name.startsWith(cookieName)) this.#chunks[name] = cookies[name] + } + } + } + + get value() { + return Object.values(this.#chunks)?.join("") + } + + /** Given a cookie, return a list of cookies, chunked to fit the allowed cookie size. */ + #chunk(cookie: Cookie): Cookie[] { + const chunkCount = Math.ceil(cookie.value.length / CHUNK_SIZE) + + if (chunkCount === 1) { + this.#chunks[cookie.name] = cookie.value + return [cookie] + } + + const cookies: Cookie[] = [] + for (let i = 0; i < chunkCount; i++) { + const name = `${cookie.name}.${i}` + const value = cookie.value.substr(i * CHUNK_SIZE, CHUNK_SIZE) + cookies.push({ ...cookie, name, value }) + this.#chunks[name] = value + } + + this.#logger.debug("CHUNKING_SESSION_COOKIE", { + message: `Session cookie exceeds allowed ${ALLOWED_COOKIE_SIZE} bytes.`, + emptyCookieSize: ESTIMATED_EMPTY_COOKIE_SIZE, + valueSize: cookie.value.length, + chunks: cookies.map((c) => c.value.length + ESTIMATED_EMPTY_COOKIE_SIZE), + }) + + return cookies + } + + /** Returns cleaned cookie chunks. */ + #clean(): Record { + const cleanedChunks: Record = {} + for (const name in this.#chunks) { + delete this.#chunks?.[name] + cleanedChunks[name] = { + name, + value: "", + options: { ...this.#option.options, maxAge: 0 }, + } + } + return cleanedChunks + } + + /** + * Given a cookie value, return new cookies, chunked, to fit the allowed cookie size. + * If the cookie has changed from chunked to unchunked or vice versa, + * it deletes the old cookies as well. + */ + chunk(value: string, options: Partial): Cookie[] { + // Assume all cookies should be cleaned by default + const cookies: Record = this.#clean() + + // Calculate new chunks + const chunked = this.#chunk({ + name: this.#option.name, + value, + options: { ...this.#option.options, ...options }, + }) + + // Update stored chunks / cookies + for (const chunk of chunked) { + cookies[chunk.name] = chunk + } + + return Object.values(cookies) + } + + /** Returns a list of cookies that should be cleaned. */ + clean(): Cookie[] { + return Object.values(this.#clean()) + } +} diff --git a/packages/core/src/lib/csrf-token.ts b/packages/core/src/lib/csrf-token.ts new file mode 100644 index 0000000000..15f75726ca --- /dev/null +++ b/packages/core/src/lib/csrf-token.ts @@ -0,0 +1,54 @@ +import { createHash, randomString } from "./web" +import type { InternalOptions } from "../types" + +interface CreateCSRFTokenParams { + options: InternalOptions + cookieValue?: string + isPost: boolean + bodyValue?: string +} + +/** + * Ensure CSRF Token cookie is set for any subsequent requests. + * Used as part of the strategy for mitigation for CSRF tokens. + * + * Creates a cookie like 'next-auth.csrf-token' with the value 'token|hash', + * where 'token' is the CSRF token and 'hash' is a hash made of the token and + * the secret, and the two values are joined by a pipe '|'. By storing the + * value and the hash of the value (with the secret used as a salt) we can + * verify the cookie was set by the server and not by a malicous attacker. + * + * For more details, see the following OWASP links: + * https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#double-submit-cookie + * https://owasp.org/www-chapter-london/assets/slides/David_Johansson-Double_Defeat_of_Double-Submit_Cookie.pdf + */ +export async function createCSRFToken({ + options, + cookieValue, + isPost, + bodyValue, +}: CreateCSRFTokenParams) { + if (cookieValue) { + const [csrfToken, csrfTokenHash] = cookieValue.split("|") + + const expectedCsrfTokenHash = await createHash( + `${csrfToken}${options.secret}` + ) + + if (csrfTokenHash === expectedCsrfTokenHash) { + // If hash matches then we trust the CSRF token value + // If this is a POST request and the CSRF Token in the POST request matches + // the cookie we have already verified is the one we have set, then the token is verified! + const csrfTokenVerified = isPost && csrfToken === bodyValue + + return { csrfTokenVerified, csrfToken } + } + } + + // New CSRF token + const csrfToken = randomString(32) + const csrfTokenHash = await createHash(`${csrfToken}${options.secret}`) + const cookie = `${csrfToken}|${csrfTokenHash}` + + return { cookie, csrfToken } +} diff --git a/packages/core/src/lib/default-callbacks.ts b/packages/core/src/lib/default-callbacks.ts new file mode 100644 index 0000000000..ed7e3d5152 --- /dev/null +++ b/packages/core/src/lib/default-callbacks.ts @@ -0,0 +1,18 @@ +import type { CallbacksOptions } from ".." + +export const defaultCallbacks: CallbacksOptions = { + signIn() { + return true + }, + redirect({ url, baseUrl }) { + if (url.startsWith("/")) return `${baseUrl}${url}` + else if (new URL(url).origin === baseUrl) return url + return baseUrl + }, + session({ session }) { + return session + }, + jwt({ token }) { + return token + }, +} diff --git a/packages/core/src/lib/email/getUserFromEmail.ts b/packages/core/src/lib/email/getUserFromEmail.ts new file mode 100644 index 0000000000..c0b0b56db2 --- /dev/null +++ b/packages/core/src/lib/email/getUserFromEmail.ts @@ -0,0 +1,20 @@ +import type { AdapterUser } from "../../adapters" +import type { InternalOptions } from "../.." + +/** + * Query the database for a user by email address. + * If is an existing user return a user object (otherwise use placeholder). + */ +export default async function getAdapterUserFromEmail({ + email, + adapter, +}: { + email: string + adapter: InternalOptions<"email">["adapter"] +}): Promise { + const { getUserByEmail } = adapter + const adapterUser = email ? await getUserByEmail(email) : null + if (adapterUser) return adapterUser + + return { id: email, email, emailVerified: null } +} diff --git a/packages/core/src/lib/email/signin.ts b/packages/core/src/lib/email/signin.ts new file mode 100644 index 0000000000..5ba00043b7 --- /dev/null +++ b/packages/core/src/lib/email/signin.ts @@ -0,0 +1,49 @@ +import { randomString, createHash } from "../web" +import type { InternalOptions } from "../.." + +/** + * Starts an e-mail login flow, by generating a token, + * and sending it to the user's e-mail (with the help of a DB adapter) + */ +export default async function email( + identifier: string, + options: InternalOptions<"email"> +): Promise { + const { url, adapter, provider, callbackUrl, theme } = options + // Generate token + const token = + (await provider.generateVerificationToken?.()) ?? randomString(32) + + const ONE_DAY_IN_SECONDS = 86400 + const expires = new Date( + Date.now() + (provider.maxAge ?? ONE_DAY_IN_SECONDS) * 1000 + ) + + // Generate a link with email, unhashed token and callback url + const params = new URLSearchParams({ callbackUrl, token, email: identifier }) + const _url = `${url}/callback/${provider.id}?${params}` + + const secret = provider.secret ?? options.secret + await Promise.all([ + // Send to user + provider.sendVerificationRequest({ + identifier, + token, + expires, + url: _url, + provider, + theme, + }), + // Save in database + adapter.createVerificationToken({ + identifier, + token: await createHash(`${token}${secret}`), + expires, + }), + ]) + + return `${url}/verify-request?${new URLSearchParams({ + provider: provider.id, + type: provider.type, + })}` +} diff --git a/packages/core/src/lib/oauth/authorization-url.ts b/packages/core/src/lib/oauth/authorization-url.ts new file mode 100644 index 0000000000..9cac65cfc4 --- /dev/null +++ b/packages/core/src/lib/oauth/authorization-url.ts @@ -0,0 +1,143 @@ +import * as o from "oauth4webapi" + +import type { + CookiesOptions, + InternalOptions, + RequestInternal, + ResponseInternal, +} from "../.." +import type { Cookie } from "../cookie" + +/** + * Generates an authorization/request token URL. + * + * [OAuth 2](https://www.oauth.com/oauth2-servers/authorization/the-authorization-request/) + */ +export async function getAuthorizationUrl({ + options, + query, +}: { + options: InternalOptions<"oauth"> + query: RequestInternal["query"] +}): Promise { + const { logger, provider } = options + + if (provider.version?.startsWith("1.")) { + const error = new Error("OAuth v1.x is not supported in next-auth/web") + logger.error("GET_AUTHORIZATION_URL", error) + throw error + } + + let url = provider.authorization?.url + let as: o.AuthorizationServer | undefined + + if (!url) { + // If url is undefined, we assume that issuer is always defined + // We check this in assert.ts + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const issuer = new URL(provider.issuer!) + const discoveryResponse = await o.discoveryRequest(issuer) + const as = await o.processDiscoveryResponse(issuer, discoveryResponse) + + if (!as.authorization_endpoint) { + throw new TypeError( + "Authorization server did not provide an authorization endpoint." + ) + } + + url = new URL(as.authorization_endpoint) + } + + const authParams = url.searchParams + const params = Object.assign( + { + response_type: "code", + // clientId can technically be undefined, should we check this in assert.ts or rely on the Authorization Server to do it? + client_id: provider.clientId, + redirect_uri: provider.callbackUrl, + }, // Defaults + Object.fromEntries(authParams.entries()), // From provider config + query // From `signIn` call + ) + + for (const k in params) authParams.set(k, params[k]) + + const cookies: Cookie[] = [] + + if (provider.checks?.includes("state")) { + const { value, raw } = await createState(options) + authParams.set("state", raw) + cookies.push(value) + } + + if (provider.checks?.includes("pkce")) { + if (as && !as.code_challenge_methods_supported?.includes("S256")) { + // We assume S256 PKCE support, if the server does not advertise that, + // a random `nonce` must be used for CSRF protection. + provider.checks = ["nonce"] + } else { + const { code_challenge, pkce } = await createPKCE(options) + authParams.set("code_challenge", code_challenge) + authParams.set("code_challenge_method", "S256") + cookies.push(pkce) + } + } + + if (provider.checks?.includes("nonce")) { + const nonce = await createNonce(options) + authParams.set("nonce", nonce.value) + cookies.push(nonce) + } + + logger.debug("GET_AUTHORIZATION_URL", { url, cookies, provider }) + return { redirect: url, cookies } +} + +/** Returns a signed cookie. */ +export async function signCookie( + type: keyof CookiesOptions, + value: string, + maxAge: number, + options: InternalOptions<"oauth"> +): Promise { + const { cookies, jwt, logger } = options + + logger.debug(`CREATE_${type.toUpperCase()}`, { value, maxAge }) + + const expires = new Date() + expires.setTime(expires.getTime() + maxAge * 1000) + return { + name: cookies[type].name, + value: await jwt.encode({ ...jwt, maxAge, token: { value } }), + options: { ...cookies[type].options, expires }, + } +} + +const STATE_MAX_AGE = 60 * 15 // 15 minutes in seconds +async function createState(options: InternalOptions<"oauth">) { + const raw = o.generateRandomState() + const maxAge = STATE_MAX_AGE + const value = await signCookie("state", raw, maxAge, options) + return { value, raw } +} + +const PKCE_MAX_AGE = 60 * 15 // 15 minutes in seconds +async function createPKCE(options: InternalOptions<"oauth">) { + const code_verifier = o.generateRandomCodeVerifier() + const code_challenge = await o.calculatePKCECodeChallenge(code_verifier) + const maxAge = PKCE_MAX_AGE + const pkce = await signCookie( + "pkceCodeVerifier", + code_verifier, + maxAge, + options + ) + return { code_challenge, pkce } +} + +const NONCE_MAX_AGE = 60 * 15 // 15 minutes in seconds +async function createNonce(options: InternalOptions<"oauth">) { + const raw = o.generateRandomNonce() + const maxAge = NONCE_MAX_AGE + return await signCookie("nonce", raw, maxAge, options) +} diff --git a/packages/core/src/lib/oauth/callback.ts b/packages/core/src/lib/oauth/callback.ts new file mode 100644 index 0000000000..4dccc4123c --- /dev/null +++ b/packages/core/src/lib/oauth/callback.ts @@ -0,0 +1,204 @@ +import { OAuthCallbackError } from "../../errors" +import { useNonce } from "./nonce-handler" +import { usePKCECodeVerifier } from "./pkce-handler" +import { useState } from "./state-handler" +import * as o from "oauth4webapi" + +import type { + InternalOptions, + LoggerInstance, + Profile, + RequestInternal, + TokenSet, +} from "../.." +import type { OAuthConfigInternal } from "../../providers" +import type { Cookie } from "../cookie" + +export async function handleOAuthCallback(params: { + options: InternalOptions<"oauth"> + query: RequestInternal["query"] + body: RequestInternal["body"] + cookies: RequestInternal["cookies"] +}) { + const { options, query, body, cookies } = params + const { logger, provider } = options + + const errorMessage = body?.error ?? query?.error + if (errorMessage) { + const error = new Error(errorMessage) + logger.error("OAUTH_CALLBACK_HANDLER_ERROR", { + error, + error_description: query?.error_description, + providerId: provider.id, + }) + logger.debug("OAUTH_CALLBACK_HANDLER_ERROR", { body }) + throw error + } + + try { + let as: o.AuthorizationServer + + if (!provider.token?.url && !provider.userinfo?.url) { + // We assume that issuer is always defined as this has been asserted earlier + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const issuer = new URL(provider.issuer!) + const discoveryResponse = await o.discoveryRequest(issuer) + const discoveredAs = await o.processDiscoveryResponse( + issuer, + discoveryResponse + ) + + if (!discoveredAs.token_endpoint) + throw new TypeError( + "TODO: Authorization server did not provide a token endpoint." + ) + + if (!discoveredAs.userinfo_endpoint) + throw new TypeError( + "TODO: Authorization server did not provide a userinfo endpoint." + ) + + as = discoveredAs + } else { + as = { + issuer: provider.issuer ?? "https://a", // TODO: review fallback issuer + token_endpoint: provider.token?.url.toString(), + userinfo_endpoint: provider.userinfo?.url.toString(), + } + } + + const client: o.Client = { + client_id: provider.clientId, + client_secret: provider.clientSecret, + token_endpoint_auth_method: + "client_secret_basic" ?? provider.client?.token_endpoint_auth_method, + // TODO: support other client options + } + + const resCookies: Cookie[] = [] + + const state = await useState(cookies?.[options.cookies.state.name], options) + if (state) resCookies.push(state.cookie) + + const codeVerifier = await usePKCECodeVerifier( + cookies?.[options.cookies.pkceCodeVerifier.name], + options + ) + if (codeVerifier) resCookies.push(codeVerifier.cookie) + + // TODO: + const nonce = await useNonce(cookies?.[options.cookies.nonce.name], options) + if (nonce && provider.idToken) { + resCookies.push(nonce.cookie) + } + + const parameters = o.validateAuthResponse( + as, + client, + new URLSearchParams(query), + provider.checks.includes("state") ? state?.value : o.skipStateCheck + ) + + if (o.isOAuth2Error(parameters)) { + console.log("error", parameters) + throw new Error("TODO: Handle OAuth 2.0 redirect error") + } + + const response = await o.authorizationCodeGrantRequest( + as, + client, + parameters, + provider.callbackUrl, + codeVerifier?.codeVerifier ?? "yooo" // TODO: review fallback code verifier + ) + + let challenges: o.WWWAuthenticateChallenge[] | undefined + if ((challenges = o.parseWwwAuthenticateChallenges(response))) { + for (const challenge of challenges) { + console.log("challenge", challenge) + } + throw new Error("TODO: Handle www-authenticate challenges as needed") + } + + const tokens = await o.processAuthorizationCodeOAuth2Response( + as, + client, + response + ) + if (o.isOAuth2Error(tokens)) { + console.log("error", tokens) + throw new Error("TODO: Handle OAuth 2.0 response body error") + } + + let profile: Profile = {} + + if (provider.userinfo?.url) { + const userinfoResponse = await o.userInfoRequest( + as, + client, + tokens.access_token + ) + profile = await userinfoResponse.json() + } + + const profileResult = await getProfile({ + profile, + provider, + tokens, + logger, + }) + + return { ...profileResult, cookies: resCookies } + } catch (error) { + throw new OAuthCallbackError(error as Error) + } +} + +interface GetProfileParams { + profile: Profile + tokens: TokenSet + provider: OAuthConfigInternal + logger: LoggerInstance +} + +/** Returns profile, raw profile and auth provider details */ +async function getProfile({ + profile: OAuthProfile, + tokens, + provider, + logger, +}: GetProfileParams) { + try { + logger.debug("PROFILE_DATA", { OAuthProfile }) + const profile = await provider.profile(OAuthProfile, tokens) + profile.email = profile.email?.toLowerCase() + if (!profile.id) + throw new TypeError( + `Profile id is missing in ${provider.name} OAuth profile response` + ) + + // Return profile, raw profile and auth provider details + return { + profile, + account: { + provider: provider.id, + type: provider.type, + providerAccountId: profile.id.toString(), + ...tokens, + }, + OAuthProfile, + } + } catch (error) { + // If we didn't get a response either there was a problem with the provider + // response *or* the user cancelled the action with the provider. + // + // Unfortuately, we can't tell which - at least not in a way that works for + // all providers, so we return an empty object; the user should then be + // redirected back to the sign up page. We log the error to help developers + // who might be trying to debug this when configuring a new provider. + logger.error("OAUTH_PARSE_PROFILE_ERROR", { + error: error as Error, + OAuthProfile, + }) + } +} diff --git a/packages/core/src/lib/oauth/client.ts b/packages/core/src/lib/oauth/client.ts new file mode 100644 index 0000000000..3f93f66b72 --- /dev/null +++ b/packages/core/src/lib/oauth/client.ts @@ -0,0 +1,45 @@ +import { custom, Issuer, type Client } from "openid-client" + +import type { InternalOptions } from "../.." + +/** Node.js reliant client supporting OAuth 2.x and OIDC */ +export async function openidClient( + options: InternalOptions<"oauth"> +): Promise { + const provider = options.provider + + if (provider.httpOptions) custom.setHttpOptionsDefaults(provider.httpOptions) + + let issuer: Issuer + if (provider.wellKnown) { + issuer = await Issuer.discover(provider.wellKnown) + } else { + issuer = new Issuer({ + // We verify that either `issuer` or the other endpoints + // are always defined in assert.ts + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + issuer: provider.issuer!, + authorization_endpoint: provider.authorization?.url.toString(), + token_endpoint: provider.token?.url.toString(), + userinfo_endpoint: provider.userinfo?.url.toString(), + }) + } + + const client = new issuer.Client( + { + // clientId can technically be undefined, should we check this in assert.ts or rely on the Authorization Server to do it? + client_id: provider.clientId, + client_secret: provider.clientSecret as string, + redirect_uris: [provider.callbackUrl], + ...provider.client, + }, + provider.jwks + ) + + // allow a 10 second skew + // See https://github.com/nextauthjs/next-auth/issues/3032 + // and https://github.com/nextauthjs/next-auth/issues/3067 + client[custom.clock_tolerance] = 10 + + return client +} diff --git a/packages/core/src/lib/oauth/nonce-handler.ts b/packages/core/src/lib/oauth/nonce-handler.ts new file mode 100644 index 0000000000..32e17233ac --- /dev/null +++ b/packages/core/src/lib/oauth/nonce-handler.ts @@ -0,0 +1,76 @@ +import * as o from "oauth4webapi" +import * as jwt from "../../jwt" + +import type { InternalOptions } from "../.." +import type { Cookie } from "../cookie" + +const NONCE_MAX_AGE = 60 * 15 // 15 minutes in seconds + +/** + * Returns nonce if the provider supports it + * and saves it in a cookie */ +export async function createNonce(options: InternalOptions<"oauth">): Promise< + | undefined + | { + value: string + cookie: Cookie + } +> { + const { cookies, logger, provider } = options + if (!provider.checks?.includes("nonce")) { + // Provider does not support nonce, return nothing. + return + } + + const nonce = o.generateRandomNonce() + + const expires = new Date() + expires.setTime(expires.getTime() + NONCE_MAX_AGE * 1000) + + // Encrypt nonce and save it to an encrypted cookie + const encryptedNonce = await jwt.encode({ + ...options.jwt, + maxAge: NONCE_MAX_AGE, + token: { nonce }, + }) + + logger.debug("CREATE_ENCRYPTED_NONCE", { + nonce, + maxAge: NONCE_MAX_AGE, + }) + + return { + cookie: { + name: cookies.nonce.name, + value: encryptedNonce, + options: { ...cookies.nonce.options, expires }, + }, + value: nonce, + } +} + +/** + * Returns nonce from if the provider supports nonce, + * and clears the container cookie afterwards. + */ +export async function useNonce( + nonce: string | undefined, + options: InternalOptions<"oauth"> +): Promise<{ value: string; cookie: Cookie } | undefined> { + const { cookies, provider } = options + + if (!provider?.checks?.includes("nonce") || !nonce) { + return + } + + const value = (await jwt.decode({ ...options.jwt, token: nonce })) as any + + return { + value: value?.nonce ?? undefined, + cookie: { + name: cookies.nonce.name, + value: "", + options: { ...cookies.nonce.options, maxAge: 0 }, + }, + } +} diff --git a/packages/core/src/lib/oauth/pkce-handler.ts b/packages/core/src/lib/oauth/pkce-handler.ts new file mode 100644 index 0000000000..24c7f70931 --- /dev/null +++ b/packages/core/src/lib/oauth/pkce-handler.ts @@ -0,0 +1,87 @@ +import * as o from "oauth4webapi" +import * as jwt from "../../jwt" + +import type { InternalOptions } from "../.." +import type { Cookie } from "../cookie" + +const PKCE_CODE_CHALLENGE_METHOD = "S256" +const PKCE_MAX_AGE = 60 * 15 // 15 minutes in seconds + +/** + * Returns `code_challenge` and `code_challenge_method` + * and saves them in a cookie. + */ +export async function createPKCE(options: InternalOptions<"oauth">): Promise< + | undefined + | { + code_challenge: string + code_challenge_method: "S256" + cookie: Cookie + } +> { + const { cookies, logger, provider } = options + if (!provider.checks?.includes("pkce")) { + // Provider does not support PKCE, return nothing. + return + } + const code_verifier = o.generateRandomCodeVerifier() + const code_challenge = await o.calculatePKCECodeChallenge(code_verifier) + + const maxAge = cookies.pkceCodeVerifier.options.maxAge ?? PKCE_MAX_AGE + + const expires = new Date() + expires.setTime(expires.getTime() + maxAge * 1000) + + // Encrypt code_verifier and save it to an encrypted cookie + const encryptedCodeVerifier = await jwt.encode({ + ...options.jwt, + maxAge, + token: { code_verifier }, + }) + + logger.debug("CREATE_PKCE_CHALLENGE_VERIFIER", { + code_challenge, + code_challenge_method: PKCE_CODE_CHALLENGE_METHOD, + code_verifier, + maxAge, + }) + + return { + code_challenge, + code_challenge_method: PKCE_CODE_CHALLENGE_METHOD, + cookie: { + name: cookies.pkceCodeVerifier.name, + value: encryptedCodeVerifier, + options: { ...cookies.pkceCodeVerifier.options, expires }, + }, + } +} + +/** + * Returns code_verifier if provider uses PKCE, + * and clears the container cookie afterwards. + */ +export async function usePKCECodeVerifier( + codeVerifier: string | undefined, + options: InternalOptions<"oauth"> +): Promise<{ codeVerifier: string; cookie: Cookie } | undefined> { + const { cookies, provider } = options + + if (!provider?.checks?.includes("pkce") || !codeVerifier) { + return + } + + const pkce = (await jwt.decode({ + ...options.jwt, + token: codeVerifier, + })) as any + + return { + codeVerifier: pkce?.code_verifier ?? undefined, + cookie: { + name: cookies.pkceCodeVerifier.name, + value: "", + options: { ...cookies.pkceCodeVerifier.options, maxAge: 0 }, + }, + } +} diff --git a/packages/core/src/lib/oauth/state-handler.ts b/packages/core/src/lib/oauth/state-handler.ts new file mode 100644 index 0000000000..e0fbb1cd1a --- /dev/null +++ b/packages/core/src/lib/oauth/state-handler.ts @@ -0,0 +1,63 @@ +import type { InternalOptions } from "../.." +import type { Cookie } from "../cookie" +import * as o from "oauth4webapi" + +const STATE_MAX_AGE = 60 * 15 // 15 minutes in seconds + +/** Returns state if the provider supports it */ +export async function createState( + options: InternalOptions<"oauth"> +): Promise<{ cookie: Cookie; value: string } | undefined> { + const { logger, provider, jwt, cookies } = options + + if (!provider.checks?.includes("state")) { + // Provider does not support state, return nothing + return + } + + const state = o.generateRandomState() + const maxAge = cookies.state.options.maxAge ?? STATE_MAX_AGE + + const encodedState = await jwt.encode({ + ...jwt, + maxAge, + token: { state }, + }) + + logger.debug("CREATE_STATE", { state, maxAge }) + + const expires = new Date() + expires.setTime(expires.getTime() + maxAge * 1000) + return { + value: state, + cookie: { + name: cookies.state.name, + value: encodedState, + options: { ...cookies.state.options, expires }, + }, + } +} + +/** + * Returns state from if the provider supports states, + * and clears the container cookie afterwards. + */ +export async function useState( + state: string | undefined, + options: InternalOptions<"oauth"> +): Promise<{ value: string; cookie: Cookie } | undefined> { + const { cookies, provider, jwt } = options + + if (!provider.checks?.includes("state") || !state) return + + const value = (await jwt.decode({ ...options.jwt, token: state })) as any + + return { + value: value?.state ?? undefined, + cookie: { + name: cookies.state.name, + value: "", + options: { ...cookies.pkceCodeVerifier.options, maxAge: 0 }, + }, + } +} diff --git a/packages/core/src/lib/providers.ts b/packages/core/src/lib/providers.ts new file mode 100644 index 0000000000..f1590b0082 --- /dev/null +++ b/packages/core/src/lib/providers.ts @@ -0,0 +1,109 @@ +import { merge } from "../utils/merge" + +import type { InternalProvider } from ".." +import type { + OAuthConfig, + OAuthConfigInternal, + OAuthEndpointType, + OAuthUserConfig, + Provider, +} from "../providers" +import type { InternalUrl } from "../utils/parse-url" + +/** + * Adds `signinUrl` and `callbackUrl` to each provider + * and deep merge user-defined options. + */ +export default function parseProviders(params: { + providers: Provider[] + url: InternalUrl + providerId?: string + runtime?: "web" | "nodejs" +}): { + providers: InternalProvider[] + provider?: InternalProvider +} { + const { url, providerId } = params + + const providers = params.providers.map((provider) => { + const { options: userOptions, ...defaults } = provider + + const id = (userOptions?.id ?? defaults.id) as string + const merged = merge(defaults, userOptions, { + signinUrl: `${url}/signin/${id}`, + callbackUrl: `${url}/callback/${id}`, + }) + + if (provider.type === "oauth") { + const p = normalizeOAuth(merged) + + return p + } + + return merged + }) + + return { + providers, + provider: providers.find(({ id }) => id === providerId), + } +} + +function normalizeOAuth( + c?: OAuthConfig | OAuthUserConfig, + runtime?: "web" | "nodejs" +): OAuthConfigInternal | {} { + if (!c) return {} + + const hasIssuer = !!c.issuer + const authorization = normalizeEndpoint(c.authorization, hasIssuer) + + if (!c.version?.startsWith("1.")) { + // Set default check to state + c.checks ??= ["pkce"] + c.checks = Array.isArray(c.checks) ? c.checks : [c.checks] + if (runtime === "web" && !c.checks.includes("pkce")) c.checks.push("pkce") + + if (!Array.isArray(c.checks)) c.checks = [c.checks] + + // REVIEW: Deprecate `idToken` in favor of `type: "oidc"`? + c.idToken ??= + // If a provider has as an "openid-configuration" well-known endpoint + c.wellKnown?.includes("openid-configuration") ?? + // or an "openid" scope request, it must also return an `id_token` + authorization?.url.searchParams.get("scope")?.includes("openid") + + if (c.issuer && c.idToken) { + c.wellKnown ??= `${c.issuer}/.well-known/openid-configuration` + } + } + + return { + ...c, + ...(authorization ? { authorization } : undefined), + ...(c.token ? { token: normalizeEndpoint(c.token, hasIssuer) } : undefined), + ...(c.userinfo + ? { userinfo: normalizeEndpoint(c.userinfo, hasIssuer) } + : undefined), + } +} + +function normalizeEndpoint( + e?: OAuthConfig[OAuthEndpointType], + hasIssuer?: boolean +): OAuthConfigInternal[OAuthEndpointType] { + if (!e || hasIssuer) return + if (typeof e === "string") { + return { url: new URL(e) } + } + // If v.url is undefined, it's because the provider config + // assumes that we will use the issuer endpoint. + // The existence of either v.url or provider.issuer is checked in + // assert.ts + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const url = new URL(e.url!) + // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion + for (const k in e.params) url.searchParams.set(k, e.params[k] as any) + + return { url } +} diff --git a/packages/core/src/lib/web.ts b/packages/core/src/lib/web.ts new file mode 100644 index 0000000000..1ffbb32ad7 --- /dev/null +++ b/packages/core/src/lib/web.ts @@ -0,0 +1,86 @@ +import { parse as parseCookie, serialize } from "cookie" +import type { AuthAction, RequestInternal, ResponseInternal } from ".." + +async function getBody(req: Request): Promise | undefined> { + if (!("body" in req) || !req.body || req.method !== "POST") return + + const contentType = req.headers.get("content-type") + if (contentType?.includes("application/json")) { + return await req.json() + } else if (contentType?.includes("application/x-www-form-urlencoded")) { + const params = new URLSearchParams(await req.text()) + return Object.fromEntries(params) + } +} + +export async function toInternalRequest( + req: Request +): Promise { + const url = new URL(req.url) + const nextauth = url.pathname.split("/").slice(3) + const headers = Object.fromEntries(req.headers) + const query: Record = Object.fromEntries(url.searchParams) + + const cookieHeader = req.headers.get("cookie") ?? "" + const cookies = + parseCookie( + Array.isArray(cookieHeader) ? cookieHeader.join(";") : cookieHeader + ) ?? {} + + return { + action: nextauth[0] as AuthAction, + method: req.method, + headers, + body: req.body ? await getBody(req) : undefined, + cookies: cookies, + providerId: nextauth[1], + error: url.searchParams.get("error") ?? undefined, + host: new URL(req.url).origin, + query, + } +} + +export function toResponse(res: ResponseInternal): Response { + const headers = new Headers(res.headers) + + res.cookies?.forEach((cookie) => { + const { name, value, options } = cookie + const cookieHeader = serialize(name, value, options) + // FIXME: Should be .append. Cannot set multiple cookies right now. + headers.set("Set-Cookie", cookieHeader) + }) + + const body = + headers.get("content-type") === "application/json" + ? JSON.stringify(res.body) + : res.body + + const response = new Response(body, { + headers, + status: res.redirect ? 302 : res.status ?? 200, + }) + + if (res.redirect) { + response.headers.set("Location", res.redirect.toString()) + } + + return response +} + +/** Web compatible method to create a hash, using SHA256 */ +export async function createHash(message) { + const data = new TextEncoder().encode(message) + const hash = await crypto.subtle.digest("SHA-256", data) + return Array.from(new Uint8Array(hash)) + .map((b) => b.toString(16).padStart(2, "0")) + .join("") + .toString() +} + +/** Web compatible method to create a random string of a given length */ +export function randomString(size: number) { + const i2hex = (i: number) => ("0" + i.toString(16)).slice(-2) + const r = (a: string, i: number): string => a + i2hex(i) + const bytes = crypto.getRandomValues(new Uint8Array(size)) + return Array.from(bytes).reduce(r, "") +} diff --git a/packages/core/src/pages/error.tsx b/packages/core/src/pages/error.tsx new file mode 100644 index 0000000000..a119bd98bb --- /dev/null +++ b/packages/core/src/pages/error.tsx @@ -0,0 +1,114 @@ +import type { Theme } from ".." +import type { InternalUrl } from "../utils/parse-url" + +/** + * The following errors are passed as error query parameters to the default or overridden error page. + * + * [Documentation](https://next-auth.js.org/configuration/pages#error-page) */ +export type ErrorType = + | "default" + | "configuration" + | "accessdenied" + | "verification" + +export interface ErrorProps { + url?: InternalUrl + theme?: Theme + error?: ErrorType +} + +interface ErrorView { + status: number + heading: string + message: JSX.Element + signin?: JSX.Element +} + +/** Renders an error page. */ +export default function ErrorPage(props: ErrorProps) { + const { url, error = "default", theme } = props + const signinPageUrl = `${url}/signin` + + const errors: Record = { + default: { + status: 200, + heading: "Error", + message: ( +

+ + {url?.host} + +

+ ), + }, + configuration: { + status: 500, + heading: "Server error", + message: ( +
+

There is a problem with the server configuration.

+

Check the server logs for more information.

+
+ ), + }, + accessdenied: { + status: 403, + heading: "Access Denied", + message: ( +
+

You do not have permission to sign in.

+

+ + Sign in + +

+
+ ), + }, + verification: { + status: 403, + heading: "Unable to sign in", + message: ( +
+

The sign in link is no longer valid.

+

It may have been used already or it may have expired.

+
+ ), + signin: ( +

+ + Sign in + +

+ ), + }, + } + + const { status, heading, message, signin } = + errors[error.toLowerCase()] ?? errors.default + + return { + status, + html: ( +
+ {theme?.brandColor && ( + ${title}
${renderToString(html)}
`, + } + } + + return { + signin(props?: any) { + return send({ + html: SigninPage({ + csrfToken: params.csrfToken, + providers: params.providers, + callbackUrl: params.callbackUrl, + theme, + ...query, + ...props, + }), + title: "Sign In", + }) + }, + signout(props?: any) { + return send({ + html: SignoutPage({ + csrfToken: params.csrfToken, + url, + theme, + ...props, + }), + title: "Sign Out", + }) + }, + verifyRequest(props?: any) { + return send({ + html: VerifyRequestPage({ url, theme, ...props }), + title: "Verify Request", + }) + }, + error(props?: { error?: ErrorType }) { + return send({ + ...ErrorPage({ url, theme, ...props }), + title: "Error", + }) + }, + } +} diff --git a/packages/core/src/pages/signin.tsx b/packages/core/src/pages/signin.tsx new file mode 100644 index 0000000000..5a8f1ae9f7 --- /dev/null +++ b/packages/core/src/pages/signin.tsx @@ -0,0 +1,193 @@ +import type { InternalProvider, Theme } from ".." +import type { CSSProperties } from "react" + +/** + * The following errors are passed as error query parameters to the default or overridden sign-in page. + * + * [Documentation](https://next-auth.js.org/configuration/pages#sign-in-page) */ +export type SignInErrorTypes = + | "Signin" + | "OAuthSignin" + | "OAuthCallback" + | "OAuthCreateAccount" + | "EmailCreateAccount" + | "Callback" + | "OAuthAccountNotLinked" + | "EmailSignin" + | "CredentialsSignin" + | "SessionRequired" + | "default" + +export interface SignInServerPageParams { + csrfToken: string + providers: InternalProvider[] + callbackUrl: string + email: string + error: SignInErrorTypes + theme: Theme +} + +export default function SigninPage(props: SignInServerPageParams) { + const { + csrfToken, + providers, + callbackUrl, + theme, + email, + error: errorType, + } = props + // We only want to render providers + const providersToRender = providers.filter((provider) => { + if (provider.type === "oauth" || provider.type === "email") { + // Always render oauth and email type providers + return true + } else if (provider.type === "credentials" && provider.credentials) { + // Only render credentials type provider if credentials are defined + return true + } + // Don't render other provider types + return false + }) + + if (typeof document !== "undefined" && theme.brandColor) { + document.documentElement.style.setProperty( + "--brand-color", + theme.brandColor + ) + } + + const errors: Record = { + Signin: "Try signing in with a different account.", + OAuthSignin: "Try signing in with a different account.", + OAuthCallback: "Try signing in with a different account.", + OAuthCreateAccount: "Try signing in with a different account.", + EmailCreateAccount: "Try signing in with a different account.", + Callback: "Try signing in with a different account.", + OAuthAccountNotLinked: + "To confirm your identity, sign in with the same account you used originally.", + EmailSignin: "The e-mail could not be sent.", + CredentialsSignin: + "Sign in failed. Check the details you provided are correct.", + SessionRequired: "Please sign in to access this page.", + default: "Unable to sign in.", + } + + const error = errorType && (errors[errorType] ?? errors.default) + + return ( +
+ {theme.brandColor && ( + + + + + + + + diff --git a/packages/core/src/providers/logos/foursquare.svg b/packages/core/src/providers/logos/foursquare.svg new file mode 100644 index 0000000000..5f63b4521b --- /dev/null +++ b/packages/core/src/providers/logos/foursquare.svg @@ -0,0 +1,18 @@ + + + + + + + + + diff --git a/packages/core/src/providers/logos/freshbooks-dark.svg b/packages/core/src/providers/logos/freshbooks-dark.svg new file mode 100644 index 0000000000..c673c4d219 --- /dev/null +++ b/packages/core/src/providers/logos/freshbooks-dark.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/core/src/providers/logos/freshbooks.svg b/packages/core/src/providers/logos/freshbooks.svg new file mode 100644 index 0000000000..ff80db2830 --- /dev/null +++ b/packages/core/src/providers/logos/freshbooks.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/core/src/providers/logos/github-dark.svg b/packages/core/src/providers/logos/github-dark.svg new file mode 100644 index 0000000000..41128ce9ed --- /dev/null +++ b/packages/core/src/providers/logos/github-dark.svg @@ -0,0 +1,4 @@ + + GitHub icon + + diff --git a/packages/core/src/providers/logos/github.svg b/packages/core/src/providers/logos/github.svg new file mode 100644 index 0000000000..a6f58d977d --- /dev/null +++ b/packages/core/src/providers/logos/github.svg @@ -0,0 +1,4 @@ + + GitHub dark icon + + diff --git a/packages/core/src/providers/logos/gitlab-dark.svg b/packages/core/src/providers/logos/gitlab-dark.svg new file mode 100644 index 0000000000..a9d455410d --- /dev/null +++ b/packages/core/src/providers/logos/gitlab-dark.svg @@ -0,0 +1,8 @@ + + + + + + diff --git a/packages/core/src/providers/logos/gitlab.svg b/packages/core/src/providers/logos/gitlab.svg new file mode 100644 index 0000000000..3b6849074a --- /dev/null +++ b/packages/core/src/providers/logos/gitlab.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/packages/core/src/providers/logos/google.svg b/packages/core/src/providers/logos/google.svg new file mode 100644 index 0000000000..60d0ec131a --- /dev/null +++ b/packages/core/src/providers/logos/google.svg @@ -0,0 +1,7 @@ + + Google icon + + + + + diff --git a/packages/core/src/providers/logos/hubspot-dark.svg b/packages/core/src/providers/logos/hubspot-dark.svg new file mode 100644 index 0000000000..e8ef5e7f45 --- /dev/null +++ b/packages/core/src/providers/logos/hubspot-dark.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/core/src/providers/logos/hubspot.svg b/packages/core/src/providers/logos/hubspot.svg new file mode 100644 index 0000000000..3ab02c3bdc --- /dev/null +++ b/packages/core/src/providers/logos/hubspot.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/core/src/providers/logos/instagram.svg b/packages/core/src/providers/logos/instagram.svg new file mode 100644 index 0000000000..9801b04bef --- /dev/null +++ b/packages/core/src/providers/logos/instagram.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packages/core/src/providers/logos/keycloak.svg b/packages/core/src/providers/logos/keycloak.svg new file mode 100644 index 0000000000..4a558aef01 --- /dev/null +++ b/packages/core/src/providers/logos/keycloak.svg @@ -0,0 +1,260 @@ + + + + + + + + + + + + keycloak_deliverables + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/core/src/providers/logos/line.svg b/packages/core/src/providers/logos/line.svg new file mode 100644 index 0000000000..afbd2758a3 --- /dev/null +++ b/packages/core/src/providers/logos/line.svg @@ -0,0 +1,6 @@ + + Line icon + + + + diff --git a/packages/core/src/providers/logos/linkedin-dark.svg b/packages/core/src/providers/logos/linkedin-dark.svg new file mode 100644 index 0000000000..3240302d5d --- /dev/null +++ b/packages/core/src/providers/logos/linkedin-dark.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/core/src/providers/logos/linkedin.svg b/packages/core/src/providers/logos/linkedin.svg new file mode 100644 index 0000000000..1bc626bcbf --- /dev/null +++ b/packages/core/src/providers/logos/linkedin.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/core/src/providers/logos/mailchimp-dark.svg b/packages/core/src/providers/logos/mailchimp-dark.svg new file mode 100644 index 0000000000..adf1fa2b4d --- /dev/null +++ b/packages/core/src/providers/logos/mailchimp-dark.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/core/src/providers/logos/mailchimp.svg b/packages/core/src/providers/logos/mailchimp.svg new file mode 100644 index 0000000000..27a2f9bcfa --- /dev/null +++ b/packages/core/src/providers/logos/mailchimp.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/core/src/providers/logos/okta-dark.svg b/packages/core/src/providers/logos/okta-dark.svg new file mode 100644 index 0000000000..a976f88b19 --- /dev/null +++ b/packages/core/src/providers/logos/okta-dark.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/core/src/providers/logos/okta.svg b/packages/core/src/providers/logos/okta.svg new file mode 100644 index 0000000000..2321a15303 --- /dev/null +++ b/packages/core/src/providers/logos/okta.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/core/src/providers/logos/patreon.svg b/packages/core/src/providers/logos/patreon.svg new file mode 100644 index 0000000000..4fa72b5231 --- /dev/null +++ b/packages/core/src/providers/logos/patreon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/core/src/providers/logos/slack.svg b/packages/core/src/providers/logos/slack.svg new file mode 100644 index 0000000000..b80883e74d --- /dev/null +++ b/packages/core/src/providers/logos/slack.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/packages/core/src/providers/logos/spotify.svg b/packages/core/src/providers/logos/spotify.svg new file mode 100644 index 0000000000..2421491e16 --- /dev/null +++ b/packages/core/src/providers/logos/spotify.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/core/src/providers/logos/todoist.svg b/packages/core/src/providers/logos/todoist.svg new file mode 100644 index 0000000000..e229dc2c6f --- /dev/null +++ b/packages/core/src/providers/logos/todoist.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/packages/core/src/providers/logos/trakt-dark.svg b/packages/core/src/providers/logos/trakt-dark.svg new file mode 100644 index 0000000000..9722816d7f --- /dev/null +++ b/packages/core/src/providers/logos/trakt-dark.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + diff --git a/packages/core/src/providers/logos/trakt.svg b/packages/core/src/providers/logos/trakt.svg new file mode 100644 index 0000000000..5cb7e1fe56 --- /dev/null +++ b/packages/core/src/providers/logos/trakt.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + diff --git a/packages/core/src/providers/logos/twitch-dark.svg b/packages/core/src/providers/logos/twitch-dark.svg new file mode 100644 index 0000000000..41488e9df3 --- /dev/null +++ b/packages/core/src/providers/logos/twitch-dark.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/core/src/providers/logos/twitch.svg b/packages/core/src/providers/logos/twitch.svg new file mode 100644 index 0000000000..8c08a26001 --- /dev/null +++ b/packages/core/src/providers/logos/twitch.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/core/src/providers/logos/twitter-dark.svg b/packages/core/src/providers/logos/twitter-dark.svg new file mode 100644 index 0000000000..07f05a8634 --- /dev/null +++ b/packages/core/src/providers/logos/twitter-dark.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/core/src/providers/logos/twitter.svg b/packages/core/src/providers/logos/twitter.svg new file mode 100644 index 0000000000..35e715f2ea --- /dev/null +++ b/packages/core/src/providers/logos/twitter.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/core/src/providers/logos/vk-dark.svg b/packages/core/src/providers/logos/vk-dark.svg new file mode 100644 index 0000000000..6ef4ef9ea5 --- /dev/null +++ b/packages/core/src/providers/logos/vk-dark.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/core/src/providers/logos/vk.svg b/packages/core/src/providers/logos/vk.svg new file mode 100644 index 0000000000..f567c75cf7 --- /dev/null +++ b/packages/core/src/providers/logos/vk.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/core/src/providers/logos/wikimedia-dark.svg b/packages/core/src/providers/logos/wikimedia-dark.svg new file mode 100644 index 0000000000..55de1b6361 --- /dev/null +++ b/packages/core/src/providers/logos/wikimedia-dark.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/packages/core/src/providers/logos/wikimedia.svg b/packages/core/src/providers/logos/wikimedia.svg new file mode 100644 index 0000000000..3ae4b1ba4e --- /dev/null +++ b/packages/core/src/providers/logos/wikimedia.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/packages/core/src/providers/logos/workos-dark.svg b/packages/core/src/providers/logos/workos-dark.svg new file mode 100644 index 0000000000..b9047adca4 --- /dev/null +++ b/packages/core/src/providers/logos/workos-dark.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/core/src/providers/logos/workos.svg b/packages/core/src/providers/logos/workos.svg new file mode 100644 index 0000000000..42f799f2e8 --- /dev/null +++ b/packages/core/src/providers/logos/workos.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/core/src/providers/mailchimp.js b/packages/core/src/providers/mailchimp.js new file mode 100644 index 0000000000..4e4fac041d --- /dev/null +++ b/packages/core/src/providers/mailchimp.js @@ -0,0 +1,28 @@ +/** @type {import(".").OAuthProvider} */ +export default function Mailchimp(options) { + return { + id: "mailchimp", + name: "Mailchimp", + type: "oauth", + authorization: "https://login.mailchimp.com/oauth2/authorize", + token: "https://login.mailchimp.com/oauth2/token", + userinfo: "https://login.mailchimp.com/oauth2/metadata", + profile(profile) { + return { + id: profile.login.login_id, + name: profile.accountname, + email: profile.login.email, + image: null, + } + }, + style: { + logo: "/mailchimp.svg", + logoDark: "/mailchimp-dark.svg", + bg: "#fff", + text: "#000", + bgDark: "#000", + textDark: "#fff", + }, + options, + } +} diff --git a/packages/core/src/providers/mailru.js b/packages/core/src/providers/mailru.js new file mode 100644 index 0000000000..eee3cfd5fe --- /dev/null +++ b/packages/core/src/providers/mailru.js @@ -0,0 +1,20 @@ +/** @type {import(".").OAuthProvider} */ +export default function MailRu(options) { + return { + id: "mailru", + name: "Mail.ru", + type: "oauth", + authorization: "https://oauth.mail.ru/login?scope=userinfo", + token: "https://oauth.mail.ru/token", + userinfo: "https://oauth.mail.ru/userinfo", + profile(profile) { + return { + id: profile.id, + name: profile.name, + email: profile.email, + image: profile.image, + } + }, + options, + } +} diff --git a/packages/core/src/providers/medium.js b/packages/core/src/providers/medium.js new file mode 100644 index 0000000000..4ec8792ff0 --- /dev/null +++ b/packages/core/src/providers/medium.js @@ -0,0 +1,20 @@ +/** @type {import(".").OAuthProvider} */ +export default function Medium(options) { + return { + id: "medium", + name: "Medium", + type: "oauth", + authorization: "https://medium.com/m/oauth/authorize?scope=basicProfile", + token: "https://api.medium.com/v1/tokens", + userinfo: "https://api.medium.com/v1/me", + profile(profile) { + return { + id: profile.data.id, + name: profile.data.name, + email: null, + image: profile.data.imageUrl, + } + }, + options, + } +} diff --git a/packages/core/src/providers/naver.ts b/packages/core/src/providers/naver.ts new file mode 100644 index 0000000000..301eff2847 --- /dev/null +++ b/packages/core/src/providers/naver.ts @@ -0,0 +1,42 @@ +import type { OAuthConfig, OAuthUserConfig } from "." + +/** https://developers.naver.com/docs/login/profile/profile.md */ +export interface NaverProfile extends Record { + resultcode: string + message: string + response: { + id: string + nickname?: string + name?: string + email?: string + gender?: "F" | "M" | "U" + age?: string + birthday?: string + profile_image?: string + birthyear?: string + mobile?: string + } +} + +export default function Naver

( + options: OAuthUserConfig

+): OAuthConfig

{ + return { + id: "naver", + name: "Naver", + type: "oauth", + authorization: "https://nid.naver.com/oauth2.0/authorize", + token: "https://nid.naver.com/oauth2.0/token", + userinfo: "https://openapi.naver.com/v1/nid/me", + profile(profile) { + return { + id: profile.response.id, + name: profile.response.nickname, + email: profile.response.email, + image: profile.response.profile_image, + } + }, + checks: ["state"], + options, + } +} diff --git a/packages/core/src/providers/netlify.js b/packages/core/src/providers/netlify.js new file mode 100644 index 0000000000..7c31aed0bb --- /dev/null +++ b/packages/core/src/providers/netlify.js @@ -0,0 +1,20 @@ +/** @type {import(".").OAuthProvider} */ +export default function Netlify(options) { + return { + id: "netlify", + name: "Netlify", + type: "oauth", + authorization: "https://app.netlify.com/authorize", + token: "https://api.netlify.com/oauth/token", + userinfo: "https://api.netlify.com/api/v1/user", + profile(profile) { + return { + id: profile.id, + name: profile.full_name, + email: profile.email, + image: profile.avatar_url, + } + }, + options, + } +} diff --git a/packages/core/src/providers/oauth-types.ts b/packages/core/src/providers/oauth-types.ts new file mode 100644 index 0000000000..df51525a4f --- /dev/null +++ b/packages/core/src/providers/oauth-types.ts @@ -0,0 +1,70 @@ + +// THIS FILE IS AUTOGENERATED. DO NOT EDIT. +export type OAuthProviderType = + | "42-school" + | "apple" + | "atlassian" + | "auth0" + | "authentik" + | "azure-ad-b2c" + | "azure-ad" + | "battlenet" + | "box" + | "boxyhq-saml" + | "bungie" + | "cognito" + | "coinbase" + | "credentials" + | "discord" + | "dropbox" + | "duende-identity-server6" + | "email" + | "eveonline" + | "facebook" + | "faceit" + | "foursquare" + | "freshbooks" + | "fusionauth" + | "github" + | "gitlab" + | "google" + | "hubspot" + | "identity-server4" + | "index" + | "instagram" + | "kakao" + | "keycloak" + | "line" + | "linkedin" + | "mailchimp" + | "mailru" + | "medium" + | "naver" + | "netlify" + | "oauth-types" + | "oauth" + | "okta" + | "onelogin" + | "osso" + | "osu" + | "patreon" + | "pinterest" + | "pipedrive" + | "reddit" + | "salesforce" + | "slack" + | "spotify" + | "strava" + | "todoist" + | "trakt" + | "twitch" + | "twitter" + | "united-effects" + | "vk" + | "wikimedia" + | "wordpress" + | "workos" + | "yandex" + | "zitadel" + | "zoho" + | "zoom" \ No newline at end of file diff --git a/packages/core/src/providers/oauth.ts b/packages/core/src/providers/oauth.ts new file mode 100644 index 0000000000..4a46ee99d6 --- /dev/null +++ b/packages/core/src/providers/oauth.ts @@ -0,0 +1,193 @@ +import type { CommonProviderOptions } from "../providers" +import type { Profile, TokenSet, User, Awaitable } from ".." + +import type { + AuthorizationParameters, + CallbackParamsType, + Issuer, + ClientMetadata, + IssuerMetadata, + OAuthCallbackChecks, + OpenIDCallbackChecks, + HttpOptions, +} from "openid-client" +import type { JWK } from "jose" + +type Client = InstanceType + +export type { OAuthProviderType } from "./oauth-types" + +type ChecksType = "pkce" | "state" | "none" | "nonce" + +export type OAuthChecks = OpenIDCallbackChecks | OAuthCallbackChecks + +type PartialIssuer = Partial> + +type UrlParams = Record + +type EndpointRequest = ( + context: C & { + /** `openid-client` Client */ + client: Client + /** Provider is passed for convenience, ans also contains the `callbackUrl`. */ + provider: (OAuthConfig

| OAuthConfigInternal

) & { + signinUrl: string + callbackUrl: string + } + } +) => Awaitable + +/** Gives granular control of the request to the given endpoint */ +interface AdvancedEndpointHandler

{ + /** Endpoint URL. Can contain parameters. Optionally, you can use `params` */ + url?: string + /** These will be prepended to the `url` */ + params?: P + /** + * Control the corresponding OAuth endpoint request completely. + * Useful if your provider relies on some custom behaviour + * or it diverges from the OAuth spec. + * + * - ⚠ **This is an advanced option.** + * You should **try to avoid using advanced options** unless you are very comfortable using them. + */ + request?: EndpointRequest +} + +/** Either an URL (containing all the parameters) or an object with more granular control. */ +export type EndpointHandler< + P extends UrlParams, + C = any, + R = any +> = AdvancedEndpointHandler + +export type AuthorizationEndpointHandler = + EndpointHandler + +export type TokenEndpointHandler = EndpointHandler< + UrlParams, + { + /** + * Parameters extracted from the request to the `/api/auth/callback/:providerId` endpoint. + * Contains params like `state`. + */ + params: CallbackParamsType + /** + * When using this custom flow, make sure to do all the necessary security checks. + * Thist object contains parameters you have to match against the request to make sure it is valid. + */ + checks: OAuthChecks + }, + { + tokens: TokenSet + } +> + +export type UserinfoEndpointHandler = EndpointHandler< + UrlParams, + { tokens: TokenSet }, + Profile +> + +export type ProfileCallback

= ( + profile: P, + tokens: TokenSet +) => Awaitable + +export interface OAuthProviderButtonStyles { + logo: string + logoDark: string + bg: string + bgDark: string + text: string + textDark: string +} + +export interface OAuthConfig

extends CommonProviderOptions, PartialIssuer { + /** + * OpenID Connect (OIDC) compliant providers can configure + * this instead of `authorize`/`token`/`userinfo` options + * without further configuration needed in most cases. + * You can still use the `authorize`/`token`/`userinfo` + * options for advanced control. + * + * [Authorization Server Metadata](https://datatracker.ietf.org/doc/html/rfc8414#section-3) + */ + wellKnown?: string + /** + * The login process will be initiated by sending the user to this URL. + * + * [Authorization endpoint](https://datatracker.ietf.org/doc/html/rfc6749#section-3.1) + */ + authorization?: string | AuthorizationEndpointHandler + token?: string | TokenEndpointHandler + userinfo?: string | UserinfoEndpointHandler + type: "oauth" + version?: string + profile?: ProfileCallback

+ checks?: ChecksType | ChecksType[] + client?: Partial + jwks?: { keys: JWK[] } + clientId?: string + clientSecret?: string + /** + * If set to `true`, the user information will be extracted + * from the `id_token` claims, instead of + * making a request to the `userinfo` endpoint. + * + * `id_token` is usually present in OpenID Connect (OIDC) compliant providers. + * + * [`id_token` explanation](https://www.oauth.com/oauth2-servers/openid-connect/id-tokens) + */ + idToken?: boolean + // TODO: only allow for BattleNet + region?: string + // TODO: only allow for some + issuer?: string + /** Read more at: https://github.com/panva/node-openid-client/tree/main/docs#customizing-http-requests */ + httpOptions?: HttpOptions + + style?: OAuthProviderButtonStyles + + /** + * The options provided by the user. + * We will perform a deep-merge of these values + * with the default configuration. + */ + options?: OAuthUserConfig

+ + // These are kept around for backwards compatibility with OAuth 1.x + accessTokenUrl?: string + requestTokenUrl?: string + profileUrl?: string + encoding?: string + allowDangerousEmailAccountLinking?: boolean +} + +export type OAuthEndpointType = "authorization" | "token" | "userinfo" + +/** + * We parsesd `authorization`, `token` and `userinfo` + * to always contain a valid `URL`, with the params + */ +export type OAuthConfigInternal

= Omit< + OAuthConfig

, + OAuthEndpointType | "clientId" | "checks" | "profile" +> & { + clientId: string + authorization?: { url: URL } + token?: { url: URL; request?: TokenEndpointHandler["request"] } + userinfo?: { url: URL; request?: UserinfoEndpointHandler["request"] } + checks: ChecksType[] + profile: ProfileCallback

+} + +export type OAuthUserConfig

= Omit< + Partial>, + "options" | "type" +> & + Required, "clientId" | "clientSecret">> + +export type OAuthProvider = ( + options: Partial> +) => OAuthConfig diff --git a/packages/core/src/providers/okta.ts b/packages/core/src/providers/okta.ts new file mode 100644 index 0000000000..de2f855c7e --- /dev/null +++ b/packages/core/src/providers/okta.ts @@ -0,0 +1,65 @@ +import type { OAuthConfig, OAuthUserConfig } from "." + +export interface OktaProfile extends Record { + iss: string + ver: string + sub: string + aud: string + iat: string + exp: string + jti: string + auth_time: string + amr: string + idp: string + nonce: string + name: string + nickname: string + preferred_username: string + given_name: string + middle_name: string + family_name: string + email: string + email_verified: string + profile: string + zoneinfo: string + locale: string + address: string + phone_number: string + picture: string + website: string + gender: string + birthdate: string + updated_at: string + at_hash: string + c_hash: string +} + +export default function Okta

( + options: OAuthUserConfig

+): OAuthConfig

{ + return { + id: "okta", + name: "Okta", + type: "oauth", + wellKnown: `${options.issuer}/.well-known/openid-configuration`, + authorization: { params: { scope: "openid email profile" } }, + idToken: true, + profile(profile) { + return { + id: profile.sub, + name: profile.name ?? profile.preferred_username, + email: profile.email, + image: profile.picture, + } + }, + style: { + logo: "/okta.svg", + logoDark: "/okta-dark.svg", + bg: "#fff", + text: "#000", + bgDark: "#000", + textDark: "#fff", + }, + options, + } +} diff --git a/packages/core/src/providers/onelogin.js b/packages/core/src/providers/onelogin.js new file mode 100644 index 0000000000..c8593ae661 --- /dev/null +++ b/packages/core/src/providers/onelogin.js @@ -0,0 +1,20 @@ +/** @type {import(".").OAuthProvider} */ +export default function OneLogin(options) { + return { + id: "onelogin", + name: "OneLogin", + type: "oauth", + wellKnown: `${options.issuer}/oidc/2/.well-known/openid-configuration`, + authorization: { params: { scope: "openid profile email" } }, + idToken: true, + profile(profile) { + return { + id: profile.sub, + name: profile.nickname, + email: profile.email, + image: profile.picture, + } + }, + options, + } +} diff --git a/packages/core/src/providers/osso.js b/packages/core/src/providers/osso.js new file mode 100644 index 0000000000..6a1e001b5e --- /dev/null +++ b/packages/core/src/providers/osso.js @@ -0,0 +1,20 @@ +/** @type {import(".").OAuthProvider} */ +export default function Osso(options) { + return { + id: "osso", + name: "Osso", + type: "oauth", + authorization: `${options.issuer}oauth/authorize`, + token: `${options.issuer}oauth/token`, + userinfo: `${options.issuer}oauth/me`, + profile(profile) { + return { + id: profile.id, + name: profile.name, + email: profile.email, + image: null, + } + }, + options, + } +} diff --git a/packages/core/src/providers/osu.ts b/packages/core/src/providers/osu.ts new file mode 100644 index 0000000000..c2790008c0 --- /dev/null +++ b/packages/core/src/providers/osu.ts @@ -0,0 +1,77 @@ +import type { OAuthConfig, OAuthUserConfig } from "." + +export interface OsuUserCompact { + avatar_url: string + country_code: string + default_group: string + id: string + is_active: boolean + is_bot: boolean + is_deleted: boolean + is_online: boolean + is_supporter: boolean + last_visit: Date | null + pm_friends_only: boolean + profile_colour: string | null + username: string +} + +export interface OsuProfile extends OsuUserCompact, Record { + discord: string | null + has_supported: boolean + interests: string | null + join_date: Date + kudosu: { + available: number + total: number + } + location: string | null + max_blocks: number + max_friends: number + occupation: string | null + playmode: string + playstyle: string[] + post_count: number + profile_order: string[] + title: string | null + title_url: string | null + twitter: string | null + website: string | null + country: { + code: string + name: string + } + cover: { + custom_url: string | null + url: string + id: number | null + } + is_restricted: boolean +} + +export default function Osu

( + options: OAuthUserConfig

+): OAuthConfig

{ + return { + id: "osu", + name: "Osu!", + type: "oauth", + token: "https://osu.ppy.sh/oauth/token", + authorization: { + url: "https://osu.ppy.sh/oauth/authorize", + params: { + scope: "identify", + }, + }, + userinfo: "https://osu.ppy.sh/api/v2/me", + profile(profile) { + return { + id: profile.id, + email: null, + name: profile.username, + image: profile.avatar_url, + } + }, + options, + } +} diff --git a/packages/core/src/providers/patreon.ts b/packages/core/src/providers/patreon.ts new file mode 100644 index 0000000000..63079f5e78 --- /dev/null +++ b/packages/core/src/providers/patreon.ts @@ -0,0 +1,42 @@ +import type { OAuthConfig, OAuthUserConfig } from "." + +export interface PatreonProfile extends Record { + sub: string + nickname: string + email: string + picture: string +} + +export default function Patreon

( + options: OAuthUserConfig

+): OAuthConfig

{ + return { + id: "patreon", + name: "Patreon", + type: "oauth", + version: "2.0", + authorization: { + url: "https://www.patreon.com/oauth2/authorize", + params: { scope: "identity identity[email]" }, + }, + token: "https://www.patreon.com/api/oauth2/token", + userinfo: "https://www.patreon.com/api/oauth2/api/current_user", + profile(profile) { + return { + id: profile.data.id, + name: profile.data.attributes.full_name, + email: profile.data.attributes.email, + image: profile.data.attributes.image_url, + } + }, + style: { + logo: "/patreon.svg", + logoDark: "/patreon.svg", + bg: "#fff", + text: "#e85b46", + bgDark: "#000", + textDark: "#e85b46", + }, + options, + } +} diff --git a/packages/core/src/providers/pinterest.ts b/packages/core/src/providers/pinterest.ts new file mode 100644 index 0000000000..891dbd17fc --- /dev/null +++ b/packages/core/src/providers/pinterest.ts @@ -0,0 +1,34 @@ +import { OAuthConfig, OAuthUserConfig } from "." + +export interface PinterestProfile extends Record { + account_type: "BUSINESS" | "PINNER" + profile_image: string + website_url: string + username: string +} + +export default function PinterestProvider

( + options: OAuthUserConfig

+): OAuthConfig

{ + return { + id: "pinterest", + name: "Pinterest", + type: "oauth", + authorization: { + url: "https://www.pinterest.com/oauth", + params: { scope: "user_accounts:read" }, + }, + checks: ["state"], + token: "https://api.pinterest.com/v5/oauth/token", + userinfo: "https://api.pinterest.com/v5/user_account", + profile({ username, profile_image }) { + return { + id: username, + name: username, + image: profile_image, + email: null, + } + }, + options, + } +} diff --git a/packages/core/src/providers/pipedrive.ts b/packages/core/src/providers/pipedrive.ts new file mode 100644 index 0000000000..5cdab2a2d2 --- /dev/null +++ b/packages/core/src/providers/pipedrive.ts @@ -0,0 +1,59 @@ +import type { OAuthConfig, OAuthUserConfig } from "." + +export interface PipedriveProfile extends Record { + success: boolean + data: { + id: number + name: string + default_currency?: string + locale?: string + lang?: number + email: string + phone?: string + activated?: boolean + last_login?: Date + created?: Date + modified?: Date + signup_flow_variation?: string + has_created_company?: boolean + is_admin?: number + active_flag?: boolean + timezone_name?: string + timezone_offset?: string + role_id?: number + icon_url?: string + is_you?: boolean + company_id?: number + company_name?: string + company_domain?: string + company_country?: string + company_industry?: string + language?: { + language_code?: string + country_code?: string + } + } +} + +export default function Pipedrive

( + options: OAuthUserConfig

+): OAuthConfig

{ + return { + id: "pipedrive", + name: "Pipedrive", + type: "oauth", + version: "2.0", + authorization: "https://oauth.pipedrive.com/oauth/authorize", + token: "https://oauth.pipedrive.com/oauth/token", + userinfo: "https://api.pipedrive.com/users/me", + profile: ({ data: profile }) => { + return { + id: String(profile.id), + name: profile.name, + email: profile.email, + image: profile.icon_url, + } + }, + options, + } +} diff --git a/packages/core/src/providers/reddit.js b/packages/core/src/providers/reddit.js new file mode 100644 index 0000000000..814b32e005 --- /dev/null +++ b/packages/core/src/providers/reddit.js @@ -0,0 +1,20 @@ +/** @type {import(".").OAuthProvider} */ +export default function Reddit(options) { + return { + id: "reddit", + name: "Reddit", + type: "oauth", + authorization: "https://www.reddit.com/api/v1/authorize?scope=identity", + token: " https://www.reddit.com/api/v1/access_token", + userinfo: "https://oauth.reddit.com/api/v1/me", + profile(profile) { + return { + id: profile.id, + name: profile.name, + email: null, + image: null, + } + }, + options, + } +} diff --git a/packages/core/src/providers/salesforce.ts b/packages/core/src/providers/salesforce.ts new file mode 100644 index 0000000000..d4cd39c2e4 --- /dev/null +++ b/packages/core/src/providers/salesforce.ts @@ -0,0 +1,32 @@ +import type { OAuthConfig, OAuthUserConfig } from "." + +export interface SalesforceProfile extends Record { + sub: string + nickname: string + email: string + picture: string +} + +export default function Salesforce

( + options: OAuthUserConfig

+): OAuthConfig

{ + const { issuer = "https://login.salesforce.com" } = options + return { + id: "salesforce", + name: "Salesforce", + type: "oauth", + authorization: `${issuer}/services/oauth2/authorize?display=page`, + token: `${issuer}/services/oauth2/token`, + userinfo: `${issuer}/services/oauth2/userinfo`, + profile(profile) { + return { + id: profile.user_id, + name: null, + email: null, + image: profile.picture, + } + }, + checks: ["none"], + options, + } +} diff --git a/packages/core/src/providers/slack.ts b/packages/core/src/providers/slack.ts new file mode 100644 index 0000000000..c1378da538 --- /dev/null +++ b/packages/core/src/providers/slack.ts @@ -0,0 +1,62 @@ +import type { OAuthConfig, OAuthUserConfig } from "." + +export interface SlackProfile extends Record { + ok: boolean + sub: string + "https://slack.com/user_id": string + "https://slack.com/team_id": string + email: string + email_verified: boolean + date_email_verified: number + name: string + picture: string + given_name: string + family_name: string + locale: string + "https://slack.com/team_name": string + "https://slack.com/team_domain": string + "https://slack.com/user_image_24": string + "https://slack.com/user_image_32": string + "https://slack.com/user_image_48": string + "https://slack.com/user_image_72": string + "https://slack.com/user_image_192": string + "https://slack.com/user_image_512": string + "https://slack.com/user_image_1024": string + "https://slack.com/team_image_34": string + "https://slack.com/team_image_44": string + "https://slack.com/team_image_68": string + "https://slack.com/team_image_88": string + "https://slack.com/team_image_102": string + "https://slack.com/team_image_132": string + "https://slack.com/team_image_230": string + "https://slack.com/team_image_default": boolean +} + +export default function Slack

( + options: OAuthUserConfig

+): OAuthConfig

{ + return { + id: "slack", + name: "Slack", + type: "oauth", + wellKnown: "https://slack.com/.well-known/openid-configuration", + authorization: { params: { scope: "openid profile email" } }, + profile(profile) { + return { + id: profile.sub, + name: profile.name, + email: profile.email, + image: profile.picture, + } + }, + style: { + logo: "/slack.svg", + logoDark: "/slack.svg", + bg: "#fff", + text: "#000", + bgDark: "#000", + textDark: "#fff", + }, + options, + } +} diff --git a/packages/core/src/providers/spotify.ts b/packages/core/src/providers/spotify.ts new file mode 100644 index 0000000000..6a535acbdb --- /dev/null +++ b/packages/core/src/providers/spotify.ts @@ -0,0 +1,42 @@ +import type { OAuthConfig, OAuthUserConfig } from "." + +export interface SpotifyImage { + url: string +} + +export interface SpotifyProfile extends Record { + id: string + display_name: string + email: string + images: SpotifyImage[] +} +export default function Spotify

( + options: OAuthUserConfig

+): OAuthConfig

{ + return { + id: "spotify", + name: "Spotify", + type: "oauth", + authorization: + "https://accounts.spotify.com/authorize?scope=user-read-email", + token: "https://accounts.spotify.com/api/token", + userinfo: "https://api.spotify.com/v1/me", + profile(profile) { + return { + id: profile.id, + name: profile.display_name, + email: profile.email, + image: profile.images?.[0]?.url, + } + }, + style: { + logo: "/spotify.svg", + logoDark: "/spotify.svg", + bg: "#fff", + text: "#2ebd59", + bgDark: "#fff", + textDark: "#2ebd59", + }, + options, + } +} diff --git a/packages/core/src/providers/strava.ts b/packages/core/src/providers/strava.ts new file mode 100644 index 0000000000..ce20b33c7a --- /dev/null +++ b/packages/core/src/providers/strava.ts @@ -0,0 +1,42 @@ +import type { OAuthConfig, OAuthUserConfig } from "." + +export interface StravaProfile extends Record { + id: string // this is really a number + firstname: string + lastname: string + profile: string +} + +export default function Strava

( + options: OAuthUserConfig

+): OAuthConfig

{ + return { + id: "strava", + name: "Strava", + type: "oauth", + authorization: { + url: "https://www.strava.com/api/v3/oauth/authorize", + params: { + scope: "read", + approval_prompt: "auto", + response_type: "code", + }, + }, + token: { + url: "https://www.strava.com/api/v3/oauth/token", + }, + userinfo: "https://www.strava.com/api/v3/athlete", + client: { + token_endpoint_auth_method: "client_secret_post", + }, + profile(profile) { + return { + id: profile.id, + name: `${profile.firstname} ${profile.lastname}`, + email: null, + image: profile.profile, + } + }, + options, + } +} diff --git a/packages/core/src/providers/todoist.ts b/packages/core/src/providers/todoist.ts new file mode 100644 index 0000000000..576bca392e --- /dev/null +++ b/packages/core/src/providers/todoist.ts @@ -0,0 +1,66 @@ +import type { OAuthConfig, OAuthUserConfig } from "." + +/** + * @see https://developer.todoist.com/sync/v9/#user + */ +interface TodoistProfile extends Record { + avatar_big: string + email: string + full_name: string + id: string +} + +export default function TodoistProvider

( + options: OAuthUserConfig

+): OAuthConfig

{ + return { + id: "todoist", + name: "Todoist", + type: "oauth", + authorization: { + url: "https://todoist.com/oauth/authorize", + params: { scope: "data:read" }, + }, + token: "https://todoist.com/oauth/access_token", + client: { + token_endpoint_auth_method: "client_secret_post", + }, + userinfo: { + request: async ({ tokens }) => { + // To obtain Todoist user info, we need to call the Sync API + // See https://developer.todoist.com/sync/v9 + const res = await fetch("https://api.todoist.com/sync/v9/sync", { + method: "POST", + headers: { + Authorization: `Bearer ${tokens.access_token}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + sync_token: "*", + resource_types: '["user"]', + }), + }) + + const { user: profile } = await res.json() + return profile + }, + }, + profile: async (profile) => { + return { + id: profile.id, + email: profile.email, + name: profile.full_name, + image: profile.avatar_big, + } + }, + style: { + logo: "/todoist.svg", + logoDark: "/todoist.svg", + bg: "#fff", + text: "#E44332", + bgDark: "#000", + textDark: "#E44332", + }, + ...options, + } +} diff --git a/packages/core/src/providers/trakt.ts b/packages/core/src/providers/trakt.ts new file mode 100644 index 0000000000..e3b720a7c4 --- /dev/null +++ b/packages/core/src/providers/trakt.ts @@ -0,0 +1,64 @@ +import type { OAuthConfig, OAuthUserConfig } from "." + +export interface TraktUser extends Record { + username: string + private: boolean + name: string + vip: boolean + vip_ep: boolean + ids: { slug: string } + joined_at: string + location: string | null + about: string | null + gender: string | null + age: number | null + images: { avatar: { full: string } } +} + +export default function Trakt

( + options: OAuthUserConfig

+): OAuthConfig

{ + return { + id: "trakt", + name: "Trakt", + type: "oauth", + authorization: { + url: "https://trakt.tv/oauth/authorize", + params: { scope: "" }, // when default, trakt returns auth error + }, + token: "https://api.trakt.tv/oauth/token", + + userinfo: { + async request(context) { + const res = await fetch("https://api.trakt.tv/users/me?extended=full", { + headers: { + Authorization: `Bearer ${context.tokens.access_token}`, + "trakt-api-version": "2", + "trakt-api-key": context.provider.clientId as string, + }, + }) + + if (res.ok) return await res.json() + + throw new Error("Expected 200 OK from the userinfo endpoint") + }, + }, + profile(profile) { + return { + id: profile.ids.slug, + name: profile.name, + email: null, // trakt does not provide user emails + image: profile.images.avatar.full, // trakt does not allow hotlinking + } + }, + style: { + logo: "/trakt.svg", + logoDark: "/trakt-dark.svg", + bg: "#fff", + text: "#ED2224", + bgDark: "#ED2224", + textDark: "#fff", + }, + options, + } +} diff --git a/packages/core/src/providers/twitch.ts b/packages/core/src/providers/twitch.ts new file mode 100644 index 0000000000..122aa91d81 --- /dev/null +++ b/packages/core/src/providers/twitch.ts @@ -0,0 +1,49 @@ +import type { OAuthConfig, OAuthUserConfig } from "." + +export interface TwitchProfile extends Record { + sub: string + preferred_username: string + email: string + picture: string +} + +export default function Twitch

( + options: OAuthUserConfig

+): OAuthConfig

{ + return { + issuer: "https://id.twitch.tv/oauth2", + id: "twitch", + name: "Twitch", + type: "oauth", + authorization: { + params: { + scope: "openid user:read:email", + claims: { + id_token: { + email: null, + picture: null, + preferred_username: null, + }, + }, + }, + }, + idToken: true, + profile(profile) { + return { + id: profile.sub, + name: profile.preferred_username, + email: profile.email, + image: profile.picture, + } + }, + style: { + logo: "/twitch.svg", + logoDark: "/twitch-dark.svg", + bg: "#fff", + text: "#65459B", + bgDark: "#65459B", + textDark: "#fff", + }, + options, + } +} diff --git a/packages/core/src/providers/twitter.ts b/packages/core/src/providers/twitter.ts new file mode 100644 index 0000000000..ce55c238a8 --- /dev/null +++ b/packages/core/src/providers/twitter.ts @@ -0,0 +1,230 @@ +import type { OAuthConfig, OAuthUserConfig } from "." + +export interface TwitterLegacyProfile { + id: number + id_str: string + name: string + screen_name: string + location: string + description: string + url: string + entities: { + url: { + urls: Array<{ + url: string + expanded_url: string + display_url: string + indices: number[] + }> + } + description: { + urls: any[] + } + } + protected: boolean + followers_count: number + friends_count: number + listed_count: number + created_at: string + favourites_count: number + utc_offset?: any + time_zone?: any + geo_enabled: boolean + verified: boolean + statuses_count: number + lang?: any + status: { + created_at: string + id: number + id_str: string + text: string + truncated: boolean + entities: { + hashtags: any[] + symbols: any[] + user_mentions: Array<{ + screen_name: string + name: string + id: number + id_str: string + indices: number[] + }> + urls: any[] + } + source: string + in_reply_to_status_id: number + in_reply_to_status_id_str: string + in_reply_to_user_id: number + in_reply_to_user_id_str: string + in_reply_to_screen_name: string + geo?: any + coordinates?: any + place?: any + contributors?: any + is_quote_status: boolean + retweet_count: number + favorite_count: number + favorited: boolean + retweeted: boolean + lang: string + } + contributors_enabled: boolean + is_translator: boolean + is_translation_enabled: boolean + profile_background_color: string + profile_background_image_url: string + profile_background_image_url_https: string + profile_background_tile: boolean + profile_image_url: string + profile_image_url_https: string + profile_banner_url: string + profile_link_color: string + profile_sidebar_border_color: string + profile_sidebar_fill_color: string + profile_text_color: string + profile_use_background_image: boolean + has_extended_profile: boolean + default_profile: boolean + default_profile_image: boolean + following: boolean + follow_request_sent: boolean + notifications: boolean + translator_type: string + withheld_in_countries: any[] + suspended: boolean + needs_phone_verification: boolean +} + +export function TwitterLegacy< + P extends Record = TwitterLegacyProfile +>(options: OAuthUserConfig

): OAuthConfig

{ + return { + id: "twitter", + name: "Twitter (Legacy)", + type: "oauth", + version: "1.0A", + authorization: "https://api.twitter.com/oauth/authenticate", + accessTokenUrl: "https://api.twitter.com/oauth/access_token", + requestTokenUrl: "https://api.twitter.com/oauth/request_token", + profileUrl: + "https://api.twitter.com/1.1/account/verify_credentials.json?include_email=true", + profile(profile) { + return { + id: profile.id_str, + name: profile.name, + email: profile.email, + image: profile.profile_image_url_https.replace( + /_normal\.(jpg|png|gif)$/, + ".$1" + ), + } + }, + style: { + logo: "/twitter.svg", + logoDark: "/twitter-dark.svg", + bg: "#fff", + text: "#1da1f2", + bgDark: "#1da1f2", + textDark: "#fff", + }, + options, + } +} + +/** + * [Documentation](https://developer.twitter.com/en/docs/twitter-api/users/lookup/api-reference/get-users-me) + */ +export interface TwitterProfile { + data: { + id: string + name: string + username: string + location?: string + entities?: { + url: { + urls: Array<{ + start: number + end: number + url: string + expanded_url: string + display_url: string + }> + } + description: { + hashtags: Array<{ + start: number + end: number + tag: string + }> + } + } + verified?: boolean + description?: string + url?: string + profile_image_url?: string + protected?: boolean + pinned_tweet_id?: string + created_at?: string + } + includes?: { + tweets?: Array<{ + id: string + text: string + }> + } +} + +export default function Twitter< + P extends Record = TwitterLegacyProfile | TwitterProfile +>(options: OAuthUserConfig

): OAuthConfig

{ + if (options.version === "2.0") { + return { + id: "twitter", + name: "Twitter", + version: "2.0", + type: "oauth", + authorization: { + url: "https://twitter.com/i/oauth2/authorize", + params: { scope: "users.read tweet.read offline.access" }, + }, + token: { + url: "https://api.twitter.com/2/oauth2/token", + // TODO: Remove this + async request({ client, params, checks, provider }) { + const response = await client.oauthCallback( + provider.callbackUrl, + params, + checks, + { exchangeBody: { client_id: options.clientId } } + ) + return { tokens: response } + }, + }, + userinfo: { + url: "https://api.twitter.com/2/users/me", + params: { "user.fields": "profile_image_url" }, + }, + profile({ data }) { + return { + id: data.id, + name: data.name, + // NOTE: E-mail is currently unsupported by OAuth 2 Twitter. + email: null, + image: data.profile_image_url, + } + }, + checks: ["pkce", "state"], + style: { + logo: "/twitter.svg", + logoDark: "/twitter-dark.svg", + bg: "#fff", + text: "#1da1f2", + bgDark: "#1da1f2", + textDark: "#fff", + }, + options, + } + } + + return TwitterLegacy(options) +} diff --git a/packages/core/src/providers/united-effects.ts b/packages/core/src/providers/united-effects.ts new file mode 100644 index 0000000000..61315f35e7 --- /dev/null +++ b/packages/core/src/providers/united-effects.ts @@ -0,0 +1,31 @@ +import type { OAuthConfig, OAuthUserConfig } from "." + +export interface UnitedEffectsProfile extends Record { + sub: string + email: string +} + +export default function UnitedEffects

( + options: OAuthUserConfig

& { issuer: string } +): OAuthConfig

{ + return { + id: "united-effects", + name: "United Effects", + wellKnown: `${options.issuer}/.well-known/openid-configuration`, + type: "oauth", + authorization: { + params: { scope: "openid email profile", resource: options.issuer }, + }, + checks: ["pkce", "state"], + idToken: true, + profile(profile) { + return { + id: profile.sub, + name: null, + email: profile.email, + image: null, + } + }, + options, + } +} \ No newline at end of file diff --git a/packages/core/src/providers/vk.ts b/packages/core/src/providers/vk.ts new file mode 100644 index 0000000000..ee6aee9d49 --- /dev/null +++ b/packages/core/src/providers/vk.ts @@ -0,0 +1,318 @@ +import type { OAuthConfig, OAuthUserConfig } from "." + +export interface VkProfile { + // https://dev.vk.com/reference/objects/user + response: Array<{ + id: number + first_name: string + last_name: string + photo_100: string + can_access_closed: boolean + is_closed: boolean + deactivated?: string + sex?: 0 | 1 | 2 + screen_name?: string + photo_50?: string + online?: 0 | 1 + online_mobile?: 0 | 1 + online_app?: number + verified?: 0 | 1 + trending?: 0 | 1 + friend_status?: 0 | 1 | 2 | 3 + first_name_nom?: string + first_name_gen?: string + first_name_dat?: string + first_name_acc?: string + first_name_ins?: string + first_name_abl?: string + last_name_nom?: string + last_name_gen?: string + last_name_dat?: string + last_name_acc?: string + last_name_ins?: string + last_name_abl?: string + nickname?: string + maiden_name?: string + domain?: string + bdate?: string + city?: { + id: number + title: string + } + country?: { + id: number + title: string + } + timezone?: number + photo_200?: string + photo_max?: string + photo_200_orig?: string + photo_400_orig?: string + photo_max_orig?: string + photo_id?: string + has_photo?: 0 | 1 + has_mobile?: 0 | 1 + is_friend?: 0 | 1 + can_post?: 0 | 1 + can_see_all_posts?: 0 | 1 + can_see_audio?: 0 | 1 + connections?: { + facebook?: string + skype?: string + twitter?: string + livejournal?: string + instagram?: string + } + photo_400?: string + wall_default?: "owner" | "all" + interests?: string + books?: string + tv?: string + quotes?: string + about?: string + games?: string + movies?: string + activities?: string + music?: string + can_write_private_message?: 0 | 1 + can_send_friend_request?: 0 | 1 + contacts?: { + mobile_phone?: string + home_phone?: string + } + site?: string + status_audio?: { + access_key?: string + artist: string + id: number + owner_id: number + title: string + url?: string + duration: number + date?: number + album_id?: number + genre_id?: number + performer?: string + } + status?: string + last_seen?: { + platform?: 1 | 2 | 3 | 4 | 5 | 6 | 7 + time?: number + } + exports?: { + facebook?: number + livejournal?: number + twitter?: number + instagram?: number + } + crop_photo?: { + photo: { + access_key?: string + album_id: number + date: number + height?: number + id: number + images?: Array<{ + height?: number + type?: "s" | "m" | "x" | "l" | "o" | "p" | "q" | "r" | "y" | "z" | "w" + url?: string + width?: number + }> + lat?: number + long?: number + owner_id: number + photo_256?: string + can_comment?: 0 | 1 + place?: string + post_id?: number + sizes?: Array<{ + height: number + url: string + src?: string + type: + | "s" + | "m" + | "x" + | "o" + | "p" + | "q" + | "r" + | "k" + | "l" + | "y" + | "z" + | "c" + | "w" + | "a" + | "b" + | "e" + | "i" + | "d" + | "j" + | "temp" + | "h" + | "g" + | "n" + | "f" + | "max" + width: number + }> + text?: string + user_id?: number + width?: number + has_tags: boolean + } + crop: { + x: number + y: number + x2: number + y2: number + } + rect: { + x: number + y: number + x2: number + y2: number + } + } + followers_count?: number + blacklisted?: 0 | 1 + blacklisted_by_me?: 0 | 1 + is_favorite?: 0 | 1 + is_hidden_from_feed?: 0 | 1 + common_count?: number + occupation?: { + id?: number + name?: string + type?: "work" | "school" | "university" + } + career?: { + group_id?: number + company?: string + country_id?: number + city_id?: number + city_name?: string + from?: number + until?: number + position?: string + } + military?: { + country_id: number + from?: number + unit: string + unit_id: number + until?: number + } + education?: { + university?: number + university_name?: string + faculty?: number + faculty_name?: string + graduation?: number + } + home_town?: string + relation?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 + relation_partner?: { + deactivated?: string + first_name: string + hidden?: number + id: number + last_name: string + can_access_closed?: boolean + is_closed?: boolean + } + personal?: { + alcohol?: 1 | 2 | 3 | 4 | 5 + inspired_by?: string + langs?: string[] + life_main?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 + people_main?: 1 | 2 | 3 | 4 | 5 | 6 + political?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 + religion?: string + smoking?: 1 | 2 | 3 | 4 | 5 + } + universities?: Array<{ + chair?: number + chair_name?: string + city?: number + country?: number + education_form?: string + education_status?: string + faculty?: number + faculty_name?: string + graduation?: number + id?: number + name?: string + university_group_id?: number + }> + schools?: Array<{ + city?: number + class?: string + country?: number + id?: string + name?: string + type?: number + type_str?: string + year_from?: number + year_graduated?: number + year_to?: number + speciality?: string + }> + relatives?: Array<{ + id?: number + name?: string + type: "parent" | "child" | "grandparent" | "grandchild" | "sibling" + }> + counters?: { + albums?: number + videos?: number + audios?: number + photos?: number + notes?: number + friends?: number + groups?: number + online_friends?: number + mutual_friends?: number + user_videos?: number + followers?: number + pages?: number + } + is_no_index?: 0 | 1 + }> +} + +export default function VK

= VkProfile>( + options: OAuthUserConfig

+): OAuthConfig

{ + const apiVersion = "5.131" // https://vk.com/dev/versions + + return { + id: "vk", + name: "VK", + type: "oauth", + authorization: `https://oauth.vk.com/authorize?scope=email&v=${apiVersion}`, + client: { + token_endpoint_auth_method: "client_secret_post", + }, + token: `https://oauth.vk.com/access_token?v=${apiVersion}`, + userinfo: `https://api.vk.com/method/users.get?fields=photo_100&v=${apiVersion}`, + profile(result: P) { + const profile = result.response?.[0] ?? {} + return { + id: profile.id, + name: [profile.first_name, profile.last_name].filter(Boolean).join(" "), + email: null, + image: profile.photo_100, + } + }, + style: { + logo: "/vk.svg", + logoDark: "/vk-dark.svg", + bg: "#fff", + text: "#07F", + bgDark: "#07F", + textDark: "#fff", + }, + options, + } +} diff --git a/packages/core/src/providers/wikimedia.ts b/packages/core/src/providers/wikimedia.ts new file mode 100644 index 0000000000..a3666eab88 --- /dev/null +++ b/packages/core/src/providers/wikimedia.ts @@ -0,0 +1,193 @@ +import type { OAuthConfig, OAuthUserConfig } from "." + +export type WikimediaGroup = + | "*" + | "user" + | "autoconfirmed" + | "extendedconfirmed" + | "bot" + | "sysop" + | "bureaucrat" + | "steward" + | "accountcreator" + | "import" + | "transwiki" + | "ipblock-exempt" + | "oversight" + | "rollbacker" + | "propertycreator" + | "wikidata-staff" + | "flood" + | "translationadmin" + | "confirmed" + | "flow-bot" + | "checkuser" + +export type WikimediaGrant = + | "basic" + | "blockusers" + | "checkuser" + | "createaccount" + | "delete" + | "editinterface" + | "editmycssjs" + | "editmyoptions" + | "editmywatchlist" + | "editpage" + | "editprotected" + | "editsiteconfig" + | "globalblock" + | "highvolume" + | "import" + | "mergehistory" + | "oath" + | "oversight" + | "patrol" + | "privateinfo" + | "protect" + | "rollback" + | "sendemail" + | "shortenurls" + | "uploadfile" + | "viewdeleted" + | "viewmywatchlist" + +export type WikimediaRight = + | "abusefilter-log" + | "apihighlimits" + | "applychangetags" + | "autoconfirmed" + | "autopatrol" + | "autoreview" + | "bigdelete" + | "block" + | "blockemail" + | "bot" + | "browsearchive" + | "changetags" + | "checkuser" + | "checkuser-log" + | "createaccount" + | "createpage" + | "createpagemainns" + | "createtalk" + | "delete" + | "delete-redirect" + | "deletedhistory" + | "deletedtext" + | "deletelogentry" + | "deleterevision" + | "edit" + | "edit-legal" + | "editinterface" + | "editmyoptions" + | "editmyusercss" + | "editmyuserjs" + | "editmyuserjson" + | "editmywatchlist" + | "editprotected" + | "editsemiprotected" + | "editsitecss" + | "editsitejs" + | "editsitejson" + | "editusercss" + | "edituserjs" + | "edituserjson" + | "globalblock" + | "import" + | "importupload" + | "ipblock-exempt" + | "item-merge" + | "item-redirect" + | "item-term" + | "markbotedits" + | "massmessage" + | "mergehistory" + | "minoredit" + | "move" + | "move-subpages" + | "movefile" + | "movestable" + | "mwoauth-authonlyprivate" + | "nominornewtalk" + | "noratelimit" + | "nuke" + | "patrol" + | "patrolmarks" + | "property-create" + | "property-term" + | "protect" + | "purge" + | "read" + | "reupload" + | "reupload-own" + | "reupload-shared" + | "rollback" + | "sendemail" + | "skipcaptcha" + | "suppressionlog" + | "tboverride" + | "templateeditor" + | "torunblocked" + | "transcode-reset" + | "translate" + | "undelete" + | "unwatchedpages" + | "upload" + | "upload_by_url" + | "viewmywatchlist" + | "viewsuppressed" + | "writeapi" + +export interface WikimediaProfile extends Record { + sub: string + username: string + editcount: number + confirmed_email: boolean + blocked: boolean + registered: string + groups: WikimediaGroup[] + rights: WikimediaRight[] + grants: WikimediaGrant[] + realname: string + email: string +} + +/** + * Wikimedia OAuth2 provider. + * All Wikimedia wikis are supported. Wikipedia, Wikidata, etc... + * + * (Register)[https://meta.wikimedia.org/wiki/Special:OAuthConsumerRegistration] + * (Documentation)[https://www.mediawiki.org/wiki/Extension:OAuth] + */ +export default function Wikimedia

( + options: OAuthUserConfig

+): OAuthConfig

{ + return { + id: "wikimedia", + name: "Wikimedia", + type: "oauth", + token: "https://meta.wikimedia.org/w/rest.php/oauth2/access_token", + userinfo: "https://meta.wikimedia.org/w/rest.php/oauth2/resource/profile", + authorization: { + url: "https://meta.wikimedia.org/w/rest.php/oauth2/authorize", + params: { scope: "" }, + }, + profile(profile) { + return { + id: profile.sub, + name: profile.username, + email: profile.email, + } + }, + style: { + logo: "/wikimedia.svg", + logoDark: "/wikimedia-dark.svg", + bg: "#fff", + text: "#000", + bgDark: "#000", + textDark: "#fff", + }, + options, + } +} diff --git a/packages/core/src/providers/wordpress.js b/packages/core/src/providers/wordpress.js new file mode 100644 index 0000000000..22a7288554 --- /dev/null +++ b/packages/core/src/providers/wordpress.js @@ -0,0 +1,21 @@ +/** @type {import(".").OAuthProvider} */ +export default function WordPress(options) { + return { + id: "wordpress", + name: "WordPress.com", + type: "oauth", + authorization: + "https://public-api.wordpress.com/oauth2/authorize?scope=auth", + token: "https://public-api.wordpress.com/oauth2/token", + userinfo: "https://public-api.wordpress.com/rest/v1/me", + profile(profile) { + return { + id: profile.ID, + name: profile.display_name, + email: profile.email, + image: profile.avatar_URL, + } + }, + options, + } +} diff --git a/packages/core/src/providers/workos.ts b/packages/core/src/providers/workos.ts new file mode 100644 index 0000000000..69e070b5e9 --- /dev/null +++ b/packages/core/src/providers/workos.ts @@ -0,0 +1,57 @@ +import type { OAuthConfig, OAuthUserConfig } from "." + +export interface WorkOSProfile extends Record { + object: string + id: string + organization_id: string + connection_id: string + connection_type: string + idp_id: string + email: string + first_name: string + last_name: string + raw_attributes: { + id: string + email: string + lastName: string + firstName: string + picture: string + } +} + +export default function WorkOS

( + options: OAuthUserConfig

+): OAuthConfig

{ + const { issuer = "https://api.workos.com/" } = options + + return { + id: "workos", + name: "WorkOS", + type: "oauth", + authorization: `${issuer}sso/authorize`, + token: { + url: `${issuer}sso/token`, + }, + client: { + token_endpoint_auth_method: "client_secret_post", + }, + userinfo: `${issuer}sso/profile`, + profile(profile) { + return { + id: profile.id, + name: `${profile.first_name} ${profile.last_name}`, + email: profile.email, + image: profile.raw_attributes.picture ?? null, + } + }, + style: { + logo: "/workos.svg", + logoDark: "/workos-dark.svg", + bg: "#fff", + text: "#6363f1", + bgDark: "#6363f1", + textDark: "#fff", + }, + options, + } +} diff --git a/packages/core/src/providers/yandex.js b/packages/core/src/providers/yandex.js new file mode 100644 index 0000000000..86a02193a8 --- /dev/null +++ b/packages/core/src/providers/yandex.js @@ -0,0 +1,23 @@ +/** @type {import(".").OAuthProvider} */ +export default function Yandex(options) { + return { + id: "yandex", + name: "Yandex", + type: "oauth", + authorization: + "https://oauth.yandex.ru/authorize?scope=login:email+login:info", + token: "https://oauth.yandex.ru/token", + userinfo: "https://login.yandex.ru/info?format=json", + profile(profile) { + return { + id: profile.id, + name: profile.real_name, + email: profile.default_email, + image: profile.is_avatar_empty + ? null + : `https://avatars.yandex.net/get-yapic/${profile.default_avatar_id}/islands-200`, + } + }, + options, + } +} diff --git a/packages/core/src/providers/zitadel.ts b/packages/core/src/providers/zitadel.ts new file mode 100644 index 0000000000..57ecbd25d5 --- /dev/null +++ b/packages/core/src/providers/zitadel.ts @@ -0,0 +1,51 @@ +import type { OAuthConfig, OAuthUserConfig } from "." + +export interface ZitadelProfile extends Record { + amr: string // Authentication Method References as defined in RFC8176 + aud: string // The audience of the token, by default all client id's and the project id are included + auth_time: number // Unix time of the authentication + azp: string // Client id of the client who requested the token + email: string // Email Address of the subject + email_verified: boolean // if the email was verified by ZITADEL + exp: number // Time the token expires (as unix time) + family_name: string // The subjects family name + given_name: string // Given name of the subject + gender: string // Gender of the subject + iat: number // Time of the token was issued at (as unix time) + iss: string // Issuing domain of a token + jti: string // Unique id of the token + locale: string // Language from the subject + name: string // The subjects full name + nbf: number // Time the token must not be used before (as unix time) + picture: string // The subjects profile picture + phone: string // Phone number provided by the user + phone_verified: boolean // if the phonenumber was verified by ZITADEL + preferred_username: string // ZITADEL's login name of the user. Consist of username@primarydomain + sub: string // Subject ID of the user +} + +export default function Zitadel

( + options: OAuthUserConfig

+): OAuthConfig

{ + const { issuer } = options + + return { + id: "zitadel", + name: "ZITADEL", + type: "oauth", + version: "2", + wellKnown: `${issuer}/.well-known/openid-configuration`, + authorization: { params: { scope: "openid email profile" } }, + idToken: true, + checks: ["pkce", "state"], + async profile(profile) { + return { + id: profile.sub, + name: profile.name, + email: profile.email, + image: profile.picture, + } + }, + options, + } +} diff --git a/packages/core/src/providers/zoho.js b/packages/core/src/providers/zoho.js new file mode 100644 index 0000000000..27c3eca835 --- /dev/null +++ b/packages/core/src/providers/zoho.js @@ -0,0 +1,21 @@ +/** @type {import(".").OAuthProvider} */ +export default function Zoho(options) { + return { + id: "zoho", + name: "Zoho", + type: "oauth", + authorization: + "https://accounts.zoho.com/oauth/v2/auth?scope=AaaServer.profile.Read", + token: "https://accounts.zoho.com/oauth/v2/token", + userinfo: "https://accounts.zoho.com/oauth/user/info", + profile(profile) { + return { + id: profile.ZUID, + name: `${profile.First_Name} ${profile.Last_Name}`, + email: profile.Email, + image: null, + } + }, + options, + } +} diff --git a/packages/core/src/providers/zoom.ts b/packages/core/src/providers/zoom.ts new file mode 100644 index 0000000000..6c9b1a5750 --- /dev/null +++ b/packages/core/src/providers/zoom.ts @@ -0,0 +1,52 @@ +import type { OAuthConfig, OAuthUserConfig } from "." + +export interface ZoomProfile extends Record { + id: string + first_name: string + last_name: string + email: string + type: number + role_name: string + pmi: number + use_pmi: boolean + vanity_url: string + personal_meeting_url: string + timezone: string + verified: number + dept: string + created_at: string + last_login_time: string + last_client_version: string + pic_url: string + host_key: string + jid: string + group_ids: string[] + im_group_ids: string[] + account_id: string + language: string + phone_country: string + phone_number: string + status: string +} + +export default function Zoom

( + options: OAuthUserConfig

+): OAuthConfig

{ + return { + id: "zoom", + name: "Zoom", + type: "oauth", + authorization: "https://zoom.us/oauth/authorize?scope", + token: "https://zoom.us/oauth/token", + userinfo: "https://api.zoom.us/v2/users/me", + profile(profile) { + return { + id: profile.id, + name: `${profile.first_name} ${profile.last_name}`, + email: profile.email, + image: profile.pic_url, + } + }, + options, + } +} diff --git a/packages/core/src/routes/callback.ts b/packages/core/src/routes/callback.ts new file mode 100644 index 0000000000..03c56be919 --- /dev/null +++ b/packages/core/src/routes/callback.ts @@ -0,0 +1,420 @@ +import callbackHandler from "../lib/callback-handler" +import getAdapterUserFromEmail from "../lib/email/getUserFromEmail" +import { handleOAuthCallback } from "../lib/oauth/callback" +import { createHash } from "../lib/web" + +import type { RequestInternal, ResponseInternal, User } from ".." +import type { AdapterSession } from "../adapters" +import type { Cookie, SessionStore } from "../lib/cookie" +import type { InternalOptions } from "../types" + +/** Handle callbacks from login services */ +export async function callback(params: { + options: InternalOptions + query: RequestInternal["query"] + method: Required["method"] + body: RequestInternal["body"] + headers: RequestInternal["headers"] + cookies: RequestInternal["cookies"] + sessionStore: SessionStore +}): Promise { + const { options, query, body, method, headers, sessionStore } = params + const { + provider, + adapter, + url, + callbackUrl, + pages, + jwt, + events, + callbacks, + session: { strategy: sessionStrategy, maxAge: sessionMaxAge }, + logger, + } = options + + const cookies: Cookie[] = [] + + const useJwtSession = sessionStrategy === "jwt" + + if (provider.type === "oauth") { + try { + const { + profile, + account, + OAuthProfile, + cookies: oauthCookies, + } = await handleOAuthCallback({ + query, + body, + options, + cookies: params.cookies, + }) + + if (oauthCookies.length) cookies.push(...oauthCookies) + + try { + // Make it easier to debug when adding a new provider + 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 + // direct the user to the signin page for now. We could do something + // else in future. + // + // Note: In oAuthCallback an error is logged with debug info, so it + // should at least be visible to developers what happened if it is an + // error with the provider. + if (!profile || !account || !OAuthProfile) { + return { redirect: `${url}/signin`, cookies } + } + + // Check if user is allowed to sign in + // Attempt to get Profile from OAuth provider details before invoking + // signIn callback - but if no user object is returned, that is fine + // (that just means it's a new user signing in for the first time). + let userOrProfile = profile + if (adapter) { + const { getUserByAccount } = adapter + const userByAccount = await getUserByAccount({ + providerAccountId: account.providerAccountId, + provider: provider.id, + }) + + if (userByAccount) userOrProfile = userByAccount + } + + try { + const isAllowed = await callbacks.signIn({ + user: userOrProfile, + account, + profile: OAuthProfile, + }) + if (!isAllowed) { + return { redirect: `${url}/error?error=AccessDenied`, cookies } + } else if (typeof isAllowed === "string") { + return { redirect: isAllowed, cookies } + } + } catch (error) { + return { + redirect: `${url}/error?error=${encodeURIComponent( + (error as Error).message + )}`, + cookies, + } + } + + // Sign user in + const { user, session, isNewUser } = await callbackHandler({ + sessionToken: sessionStore.value, + profile, + account, + options, + }) + + if (useJwtSession) { + const defaultToken = { + name: user.name, + email: user.email, + picture: user.image, + sub: user.id?.toString(), + } + const token = await callbacks.jwt({ + token: defaultToken, + user, + account, + profile: OAuthProfile, + isNewUser, + }) + + // Encode token + const newToken = await jwt.encode({ ...jwt, token }) + + // Set cookie expiry date + const cookieExpires = new Date() + cookieExpires.setTime(cookieExpires.getTime() + sessionMaxAge * 1000) + + const sessionCookies = sessionStore.chunk(newToken, { + expires: cookieExpires, + }) + cookies.push(...sessionCookies) + } else { + // Save Session Token in cookie + cookies.push({ + name: options.cookies.sessionToken.name, + value: (session as AdapterSession).sessionToken, + options: { + ...options.cookies.sessionToken.options, + expires: (session as AdapterSession).expires, + }, + }) + } + + // @ts-expect-error + await events.signIn?.({ user, account, profile, isNewUser }) + + // Handle first logins on new accounts + // 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 { + redirect: `${pages.newUser}${ + pages.newUser.includes("?") ? "&" : "?" + }callbackUrl=${encodeURIComponent(callbackUrl)}`, + cookies, + } + } + + // Callback URL is already verified at this point, so safe to use if specified + return { redirect: callbackUrl, cookies } + } catch (error) { + if ((error as Error).name === "AccountNotLinkedError") { + // If the email on the account is already linked, but not with this OAuth account + return { + redirect: `${url}/error?error=OAuthAccountNotLinked`, + cookies, + } + } else if ((error as Error).name === "CreateUserError") { + return { redirect: `${url}/error?error=OAuthCreateAccount`, cookies } + } + logger.error("OAUTH_CALLBACK_HANDLER_ERROR", error as Error) + return { redirect: `${url}/error?error=Callback`, cookies } + } + } catch (error) { + if ((error as Error).name === "OAuthCallbackError") { + logger.error("OAUTH_CALLBACK_ERROR", { + error: error as Error, + providerId: provider.id, + }) + return { redirect: `${url}/error?error=OAuthCallback`, cookies } + } + logger.error("OAUTH_CALLBACK_ERROR", error as Error) + return { redirect: `${url}/error?error=Callback`, cookies } + } + } else if (provider.type === "email") { + try { + const token = query?.token as string | undefined + const identifier = query?.email as string | undefined + + // If these are missing, the sign-in URL was manually opened without these params or the `sendVerificationRequest` method did not send the link correctly in the email. + if (!token || !identifier) { + return { redirect: `${url}/error?error=configuration`, cookies } + } + + const secret = provider.secret ?? options.secret + // @ts-expect-error -- Verified in `assertConfig`. adapter: Adapter + const invite = await adapter.useVerificationToken({ + identifier, + token: await createHash(`${token}${secret}`), + }) + + const invalidInvite = !invite || invite.expires.valueOf() < Date.now() + if (invalidInvite) { + return { redirect: `${url}/error?error=Verification`, cookies } + } + + const profile = await getAdapterUserFromEmail({ + email: identifier, + // @ts-expect-error -- Verified in `assertConfig`. adapter: Adapter + adapter, + }) + + const account = { + providerAccountId: profile.email, + type: "email" as const, + provider: provider.id, + } + + // Check if user is allowed to sign in + try { + const signInCallbackResponse = await callbacks.signIn({ + user: profile, + account, + }) + if (!signInCallbackResponse) { + return { redirect: `${url}/error?error=AccessDenied`, cookies } + } else if (typeof signInCallbackResponse === "string") { + return { redirect: signInCallbackResponse, cookies } + } + } catch (error) { + return { + redirect: `${url}/error?error=${encodeURIComponent( + (error as Error).message + )}`, + cookies, + } + } + + // Sign user in + const { user, session, isNewUser } = await callbackHandler({ + sessionToken: sessionStore.value, + profile, + account, + options, + }) + + if (useJwtSession) { + const defaultToken = { + name: user.name, + email: user.email, + picture: user.image, + sub: user.id?.toString(), + } + const token = await callbacks.jwt({ + token: defaultToken, + user, + account, + isNewUser, + }) + + // Encode token + const newToken = await jwt.encode({ ...jwt, token }) + + // Set cookie expiry date + const cookieExpires = new Date() + cookieExpires.setTime(cookieExpires.getTime() + sessionMaxAge * 1000) + + const sessionCookies = sessionStore.chunk(newToken, { + expires: cookieExpires, + }) + cookies.push(...sessionCookies) + } else { + // Save Session Token in cookie + cookies.push({ + name: options.cookies.sessionToken.name, + value: (session as AdapterSession).sessionToken, + options: { + ...options.cookies.sessionToken.options, + expires: (session as AdapterSession).expires, + }, + }) + } + + await events.signIn?.({ user, account, isNewUser }) + + // Handle first logins on new accounts + // 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 { + redirect: `${pages.newUser}${ + pages.newUser.includes("?") ? "&" : "?" + }callbackUrl=${encodeURIComponent(callbackUrl)}`, + cookies, + } + } + + // Callback URL is already verified at this point, so safe to use if specified + return { redirect: callbackUrl, cookies } + } catch (error) { + if ((error as Error).name === "CreateUserError") { + return { redirect: `${url}/error?error=EmailCreateAccount`, cookies } + } + logger.error("CALLBACK_EMAIL_ERROR", error as Error) + return { redirect: `${url}/error?error=Callback`, cookies } + } + } else if (provider.type === "credentials" && method === "POST") { + const credentials = body + + let user: User | null + try { + user = await provider.authorize(credentials, { + query, + body, + headers, + method, + }) + if (!user) { + return { + status: 401, + redirect: `${url}/error?${new URLSearchParams({ + error: "CredentialsSignin", + provider: provider.id, + })}`, + cookies, + } + } + } catch (error) { + return { + status: 401, + redirect: `${url}/error?error=${encodeURIComponent( + (error as Error).message + )}`, + cookies, + } + } + + /** @type {import("src").Account} */ + const account = { + providerAccountId: user.id, + type: "credentials", + provider: provider.id, + } + + try { + const isAllowed = await callbacks.signIn({ + user, + // @ts-expect-error + account, + credentials, + }) + if (!isAllowed) { + return { + status: 403, + redirect: `${url}/error?error=AccessDenied`, + cookies, + } + } else if (typeof isAllowed === "string") { + return { redirect: isAllowed, cookies } + } + } catch (error) { + return { + redirect: `${url}/error?error=${encodeURIComponent( + (error as Error).message + )}`, + cookies, + } + } + + const defaultToken = { + name: user.name, + email: user.email, + picture: user.image, + sub: user.id?.toString(), + } + + const token = await callbacks.jwt({ + token: defaultToken, + user, + // @ts-expect-error + account, + isNewUser: false, + }) + + // Encode token + const newToken = await jwt.encode({ ...jwt, token }) + + // Set cookie expiry date + const cookieExpires = new Date() + cookieExpires.setTime(cookieExpires.getTime() + sessionMaxAge * 1000) + + const sessionCookies = sessionStore.chunk(newToken, { + expires: cookieExpires, + }) + + cookies.push(...sessionCookies) + + // @ts-expect-error + await events.signIn?.({ user, account }) + + return { redirect: callbackUrl, cookies } + } + return { + status: 500, + body: `Error: Callback for provider type ${provider.type} not supported`, + cookies, + } +} diff --git a/packages/core/src/routes/index.ts b/packages/core/src/routes/index.ts new file mode 100644 index 0000000000..320fe2de7f --- /dev/null +++ b/packages/core/src/routes/index.ts @@ -0,0 +1,5 @@ +export { callback } from "./callback" +export { providers } from "./providers" +export { session } from "./session" +export { signin } from "./signin" +export { signout } from "./signout" diff --git a/packages/core/src/routes/providers.ts b/packages/core/src/routes/providers.ts new file mode 100644 index 0000000000..18ef2696e7 --- /dev/null +++ b/packages/core/src/routes/providers.ts @@ -0,0 +1,29 @@ +import type { InternalProvider, ResponseInternal } from ".." + +export interface PublicProvider { + id: string + name: string + type: string + signinUrl: string + callbackUrl: string +} + +/** + * Return a JSON object with a list of all OAuth providers currently configured + * and their signin and callback URLs. This makes it possible to automatically + * generate buttons for all providers when rendering client side. + */ +export function providers( + providers: InternalProvider[] +): ResponseInternal> { + return { + headers: { "Content-Type": "application/json" }, + body: providers.reduce>( + (acc, { id, name, type, signinUrl, callbackUrl }) => { + acc[id] = { id, name, type, signinUrl, callbackUrl } + return acc + }, + {} + ), + } +} diff --git a/packages/core/src/routes/session.ts b/packages/core/src/routes/session.ts new file mode 100644 index 0000000000..8ad3f0c931 --- /dev/null +++ b/packages/core/src/routes/session.ts @@ -0,0 +1,165 @@ +import { fromDate } from "../utils/date" + +import type { InternalOptions, ResponseInternal, Session } from ".." +import type { Adapter } from "../adapters" +import type { SessionStore } from "../lib/cookie" + +interface SessionParams { + options: InternalOptions + sessionStore: SessionStore +} + +/** + * Return a session object (without any private fields) + * for Single Page App clients + */ + +export async function session( + params: SessionParams +): Promise> { + const { options, sessionStore } = params + const { + adapter, + jwt, + events, + callbacks, + logger, + session: { strategy: sessionStrategy, maxAge: sessionMaxAge }, + } = options + + const response: ResponseInternal = { + body: {}, + headers: { "Content-Type": "application/json" }, + cookies: [], + } + + const sessionToken = sessionStore.value + + if (!sessionToken) return response + + if (sessionStrategy === "jwt") { + try { + const decodedToken = await jwt.decode({ + ...jwt, + token: sessionToken, + }) + + const newExpires = fromDate(sessionMaxAge) + + // By default, only exposes a limited subset of information to the client + // as needed for presentation purposes (e.g. "you are logged in as..."). + const session = { + user: { + name: decodedToken?.name, + email: decodedToken?.email, + image: decodedToken?.picture, + }, + expires: newExpires.toISOString(), + } + + // @ts-expect-error + const token = await callbacks.jwt({ token: decodedToken }) + // @ts-expect-error + const newSession = await callbacks.session({ session, token }) + + // Return session payload as response + response.body = newSession + + // Refresh JWT expiry by re-signing it, with an updated expiry date + const newToken = await jwt.encode({ + ...jwt, + token, + maxAge: options.session.maxAge, + }) + + // Set cookie, to also update expiry date on cookie + const sessionCookies = sessionStore.chunk(newToken, { + expires: newExpires, + }) + + response.cookies?.push(...sessionCookies) + + await events.session?.({ session: newSession, token }) + } catch (error) { + // If JWT not verifiable, make sure the cookie for it is removed and return empty object + logger.error("JWT_SESSION_ERROR", error as Error) + + response.cookies?.push(...sessionStore.clean()) + } + } else { + try { + const { getSessionAndUser, deleteSession, updateSession } = + adapter as Adapter + let userAndSession = await getSessionAndUser(sessionToken) + + // If session has expired, clean up the database + if ( + userAndSession && + userAndSession.session.expires.valueOf() < Date.now() + ) { + await deleteSession(sessionToken) + userAndSession = null + } + + if (userAndSession) { + const { user, session } = userAndSession + + const sessionUpdateAge = options.session.updateAge + // Calculate last updated date to throttle write updates to database + // Formula: ({expiry date} - sessionMaxAge) + sessionUpdateAge + // e.g. ({expiry date} - 30 days) + 1 hour + const sessionIsDueToBeUpdatedDate = + session.expires.valueOf() - + sessionMaxAge * 1000 + + sessionUpdateAge * 1000 + + const newExpires = fromDate(sessionMaxAge) + // Trigger update of session expiry date and write to database, only + // if the session was last updated more than {sessionUpdateAge} ago + if (sessionIsDueToBeUpdatedDate <= Date.now()) { + await updateSession({ sessionToken, expires: newExpires }) + } + + // Pass Session through to the session callback + // @ts-expect-error + const sessionPayload = await callbacks.session({ + // By default, only exposes a limited subset of information to the client + // as needed for presentation purposes (e.g. "you are logged in as..."). + session: { + user: { + name: user.name, + email: user.email, + image: user.image, + }, + expires: session.expires.toISOString(), + }, + user, + }) + + // Return session payload as response + response.body = sessionPayload + + // Set cookie again to update expiry + response.cookies?.push({ + name: options.cookies.sessionToken.name, + value: sessionToken, + options: { + ...options.cookies.sessionToken.options, + expires: newExpires, + }, + }) + + // @ts-expect-error + await 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. + response.cookies?.push(...sessionStore.clean()) + } + } catch (error) { + logger.error("SESSION_ERROR", error as Error) + } + } + + return response +} diff --git a/packages/core/src/routes/signin.ts b/packages/core/src/routes/signin.ts new file mode 100644 index 0000000000..35a6e54a18 --- /dev/null +++ b/packages/core/src/routes/signin.ts @@ -0,0 +1,103 @@ +import getAdapterUserFromEmail from "../lib/email/getUserFromEmail" +import emailSignin from "../lib/email/signin" +import { getAuthorizationUrl } from "../lib/oauth/authorization-url" + +import type { + Account, + InternalOptions, + RequestInternal, + ResponseInternal, +} from ".." + +/** Handle requests to /api/auth/signin */ +export async function signin(params: { + options: InternalOptions<"oauth" | "email"> + query: RequestInternal["query"] + body: RequestInternal["body"] +}): Promise { + const { options, query, body } = params + const { url, callbacks, logger, provider } = options + + if (!provider.type) { + return { + status: 500, + // @ts-expect-error + text: `Error: Type not specified for ${provider.name}`, + } + } + + if (provider.type === "oauth") { + try { + return await getAuthorizationUrl({ options, query }) + } catch (error) { + logger.error("SIGNIN_OAUTH_ERROR", { + error: error as Error, + providerId: provider.id, + }) + return { redirect: `${url}/error?error=OAuthSignin` } + } + } else if (provider.type === "email") { + let email: string = body?.email + if (!email) return { redirect: `${url}/error?error=EmailSignin` } + const normalizer: (identifier: string) => string = + provider.normalizeIdentifier ?? + ((identifier) => { + // Get the first two elements only, + // separated by `@` from user input. + let [local, domain] = identifier.toLowerCase().trim().split("@") + // The part before "@" can contain a "," + // but we remove it on the domain part + domain = domain.split(",")[0] + return `${local}@${domain}` + }) + + try { + email = normalizer(body?.email) + } catch (error) { + logger.error("SIGNIN_EMAIL_ERROR", { error, providerId: provider.id }) + return { redirect: `${url}/error?error=EmailSignin` } + } + + const user = await getAdapterUserFromEmail({ + email, + // @ts-expect-error -- Verified in `assertConfig`. adapter: Adapter + adapter: options.adapter, + }) + + const account: Account = { + providerAccountId: email, + userId: email, + type: "email", + provider: provider.id, + } + + // Check if user is allowed to sign in + try { + const signInCallbackResponse = await callbacks.signIn({ + user, + account, + email: { verificationRequest: true }, + }) + if (!signInCallbackResponse) { + return { redirect: `${url}/error?error=AccessDenied` } + } else if (typeof signInCallbackResponse === "string") { + return { redirect: signInCallbackResponse } + } + } catch (error) { + return { + redirect: `${url}/error?${new URLSearchParams({ + error: error as string, + })}`, + } + } + + try { + const redirect = await emailSignin(email, options) + return { redirect } + } catch (error) { + logger.error("SIGNIN_EMAIL_ERROR", { error, providerId: provider.id }) + return { redirect: `${url}/error?error=EmailSignin` } + } + } + return { redirect: `${url}/signin` } +} diff --git a/packages/core/src/routes/signout.ts b/packages/core/src/routes/signout.ts new file mode 100644 index 0000000000..77427b368c --- /dev/null +++ b/packages/core/src/routes/signout.ts @@ -0,0 +1,44 @@ +import type { InternalOptions, ResponseInternal } from ".." +import type { Adapter } from "../adapters" +import type { SessionStore } from "../lib/cookie" + +/** Handle requests to /api/auth/signout */ +export async function signout(params: { + options: InternalOptions + sessionStore: SessionStore +}): Promise { + const { options, sessionStore } = params + const { adapter, events, jwt, callbackUrl, logger, session } = options + + const sessionToken = sessionStore?.value + if (!sessionToken) { + return { redirect: callbackUrl } + } + + if (session.strategy === "jwt") { + // Dispatch signout event + try { + const decodedJwt = await jwt.decode({ ...jwt, token: sessionToken }) + // @ts-expect-error + await events.signOut?.({ token: decodedJwt }) + } catch (error) { + // Do nothing if decoding the JWT fails + logger.error("SIGNOUT_ERROR", error) + } + } else { + try { + const session = await (adapter as Adapter).deleteSession(sessionToken) + // Dispatch signout event + // @ts-expect-error + await events.signOut?.({ session }) + } catch (error) { + // If error, log it but continue + logger.error("SIGNOUT_ERROR", error as Error) + } + } + + // Remove Session Token + const sessionCookies = sessionStore.clean() + + return { redirect: callbackUrl, cookies: sessionCookies } +} diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts new file mode 100644 index 0000000000..dab8e8e757 --- /dev/null +++ b/packages/core/src/types.ts @@ -0,0 +1,586 @@ +import type { CookieSerializeOptions } from "cookie" +import type { Adapter, AdapterUser } from "./adapters" +import type { + CredentialInput, + CredentialsConfig, + EmailConfig, + OAuthConfigInternal, + Provider, + ProviderType, +} from "./providers" +import type { + OAuth2TokenEndpointResponse, + OpenIDTokenEndpointResponse, +} from "oauth4webapi" +import type { JWT, JWTOptions } from "./jwt" +import type { Cookie } from "./lib/cookie" +import type { LoggerInstance } from "./utils/logger" +import type { InternalUrl } from "./utils/parse-url" + +export type Awaitable = T | PromiseLike + +export type { LoggerInstance } + +/** + * Configure your NextAuth instance + * + * [Documentation](https://next-auth.js.org/configuration/options#options) + */ +export interface AuthOptions { + /** + * An array of authentication providers for signing in + * (e.g. Google, Facebook, Twitter, GitHub, Email, etc) in any order. + * This can be one of the built-in providers or an object with a custom provider. + * * **Default value**: `[]` + * * **Required**: *Yes* + * + * [Documentation](https://next-auth.js.org/configuration/options#providers) | [Providers documentation](https://next-auth.js.org/configuration/providers) + */ + providers: Provider[] + /** + * A random string used to hash tokens, sign cookies and generate cryptographic keys. + * If not specified, it falls back to `jwt.secret` or `NEXTAUTH_SECRET` from environment variables. + * Otherwise, it will use a hash of all configuration options, including Client ID / Secrets for entropy. + * + * NOTE: The last behavior is extremely volatile, and will throw an error in production. + * * **Default value**: `string` (SHA hash of the "options" object) + * * **Required**: No - **but strongly recommended**! + * + * [Documentation](https://next-auth.js.org/configuration/options#secret) + */ + secret?: string + /** + * Configure your session like if you want to use JWT or a database, + * how long until an idle session expires, or to throttle write operations in case you are using a database. + * * **Default value**: See the documentation page + * * **Required**: No + * + * [Documentation](https://next-auth.js.org/configuration/options#session) + */ + session?: Partial + /** + * JSON Web Tokens are enabled by default if you have not specified an adapter. + * JSON Web Tokens are encrypted (JWE) by default. We recommend you keep this behaviour. + * * **Default value**: See the documentation page + * * **Required**: *No* + * + * [Documentation](https://next-auth.js.org/configuration/options#jwt) + */ + jwt?: Partial + /** + * Specify URLs to be used if you want to create custom sign in, sign out and error pages. + * Pages specified will override the corresponding built-in page. + * * **Default value**: `{}` + * * **Required**: *No* + * @example + * + * ```js + * pages: { + * signIn: '/auth/signin', + * signOut: '/auth/signout', + * error: '/auth/error', + * verifyRequest: '/auth/verify-request', + * newUser: '/auth/new-user' + * } + * ``` + * + * [Documentation](https://next-auth.js.org/configuration/options#pages) | [Pages documentation](https://next-auth.js.org/configuration/pages) + */ + pages?: Partial + /** + * Callbacks are asynchronous functions you can use to control what happens when an action is performed. + * Callbacks are *extremely powerful*, especially in scenarios involving JSON Web Tokens + * as they **allow you to implement access controls without a database** and to **integrate with external databases or APIs**. + * * **Default value**: See the Callbacks documentation + * * **Required**: *No* + * + * [Documentation](https://next-auth.js.org/configuration/options#callbacks) | [Callbacks documentation](https://next-auth.js.org/configuration/callbacks) + */ + callbacks?: Partial + /** + * Events are asynchronous functions that do not return a response, they are useful for audit logging. + * You can specify a handler for any of these events below - e.g. for debugging or to create an audit log. + * The content of the message object varies depending on the flow + * (e.g. OAuth or Email authentication flow, JWT or database sessions, etc), + * but typically contains a user object and/or contents of the JSON Web Token + * and other information relevant to the event. + * * **Default value**: `{}` + * * **Required**: *No* + * + * [Documentation](https://next-auth.js.org/configuration/options#events) | [Events documentation](https://next-auth.js.org/configuration/events) + */ + events?: Partial + /** + * You can use the adapter option to pass in your database adapter. + * + * * **Required**: *No* + * + * [Documentation](https://next-auth.js.org/configuration/options#adapter) | + * [Adapters Overview](https://next-auth.js.org/adapters/overview) + */ + adapter?: Adapter + /** + * Set debug to true to enable debug messages for authentication and database operations. + * * **Default value**: `false` + * * **Required**: *No* + * + * - ⚠ If you added a custom `logger`, this setting is ignored. + * + * [Documentation](https://next-auth.js.org/configuration/options#debug) | [Logger documentation](https://next-auth.js.org/configuration/options#logger) + */ + debug?: boolean + /** + * Override any of the logger levels (`undefined` levels will use the built-in logger), + * and intercept logs in NextAuth. You can use this option to send NextAuth logs to a third-party logging service. + * * **Default value**: `console` + * * **Required**: *No* + * + * @example + * + * ```js + * // /pages/api/auth/[...nextauth].js + * import log from "logging-service" + * export default NextAuth({ + * logger: { + * error(code, ...message) { + * log.error(code, message) + * }, + * warn(code, ...message) { + * log.warn(code, message) + * }, + * debug(code, ...message) { + * log.debug(code, message) + * } + * } + * }) + * ``` + * + * - ⚠ When set, the `debug` option is ignored + * + * [Documentation](https://next-auth.js.org/configuration/options#logger) | + * [Debug documentation](https://next-auth.js.org/configuration/options#debug) + */ + logger?: Partial + /** + * Changes the theme of pages. + * Set to `"light"` if you want to force pages to always be light. + * Set to `"dark"` if you want to force pages to always be dark. + * Set to `"auto"`, (or leave this option out)if you want the pages to follow the preferred system theme. + * * **Default value**: `"auto"` + * * **Required**: *No* + * + * [Documentation](https://next-auth.js.org/configuration/options#theme) | [Pages documentation]("https://next-auth.js.org/configuration/pages") + */ + theme?: Theme + /** + * When set to `true` then all cookies set by NextAuth.js will only be accessible from HTTPS URLs. + * This option defaults to `false` on URLs that start with `http://` (e.g. http://localhost:3000) for developer convenience. + * You can manually set this option to `false` to disable this security feature and allow cookies + * to be accessible from non-secured URLs (this is not recommended). + * * **Default value**: `true` for HTTPS and `false` for HTTP sites + * * **Required**: No + * + * [Documentation](https://next-auth.js.org/configuration/options#usesecurecookies) + * + * - ⚠ **This is an advanced option.** Advanced options are passed the same way as basic options, + * but **may have complex implications** or side effects. + * You should **try to avoid using advanced options** unless you are very comfortable using them. + */ + useSecureCookies?: boolean + /** + * You can override the default cookie names and options for any of the cookies used by NextAuth.js. + * You can specify one or more cookies with custom properties, + * but if you specify custom options for a cookie you must provide all the options for that cookie. + * If you use this feature, you will likely want to create conditional behavior + * to support setting different cookies policies in development and production builds, + * as you will be opting out of the built-in dynamic policy. + * * **Default value**: `{}` + * * **Required**: No + * + * - ⚠ **This is an advanced option.** Advanced options are passed the same way as basic options, + * but **may have complex implications** or side effects. + * You should **try to avoid using advanced options** unless you are very comfortable using them. + * + * [Documentation](https://next-auth.js.org/configuration/options#cookies) | [Usage example](https://next-auth.js.org/configuration/options#example) + */ + cookies?: Partial + /** + * If set to `true`, NextAuth.js will use either the `x-forwarded-host` or `host` headers, + * instead of `NEXTAUTH_URL` + * Make sure that reading `x-forwarded-host` on your hosting platform can be trusted. + * - ⚠ **This is an advanced option.** Advanced options are passed the same way as basic options, + * but **may have complex implications** or side effects. + * You should **try to avoid using advanced options** unless you are very comfortable using them. + * @default Boolean(process.env.AUTH_TRUST_HOST ?? process.env.VERCEL) + */ + trustHost?: boolean + /** @internal */ + __internal__?: { + runtime?: "web" | "nodejs" + } +} + +/** + * Change the theme of the built-in pages. + * + * [Documentation](https://next-auth.js.org/configuration/options#theme) | + * [Pages](https://next-auth.js.org/configuration/pages) + */ +export interface Theme { + colorScheme?: "auto" | "dark" | "light" + logo?: string + brandColor?: string + buttonText?: string +} + +/** + * Different tokens returned by OAuth Providers. + * Some of them are available with different casing, + * but they refer to the same value. + */ +export type TokenSet = Partial & + Partial + +/** + * Usually contains information about the provider being used + * and also extends `TokenSet`, which is different tokens returned by OAuth Providers. + */ +export interface Account extends TokenSet { + /** + * This value depends on the type of the provider being used to create the account. + * - oauth: The OAuth account's id, returned from the `profile()` callback. + * - email: The user's email address. + * - credentials: `id` returned from the `authorize()` callback + */ + providerAccountId: string + /** id of the user this account belongs to. */ + userId?: string + /** id of the provider used for this account */ + provider: string + /** Provider's type for this account */ + type: ProviderType +} + +/** The OAuth profile returned from your provider */ +export interface Profile { + sub?: string + name?: string + email?: string + image?: string +} + +/** [Documentation](https://next-auth.js.org/configuration/callbacks) */ +export interface CallbacksOptions

{ + /** + * Use this callback to control if a user is allowed to sign in. + * Returning true will continue the sign-in flow. + * Throwing an error or returning a string will stop the flow, and redirect the user. + * + * [Documentation](https://next-auth.js.org/configuration/callbacks#sign-in-callback) + */ + signIn: (params: { + user: User | AdapterUser + account: A | null + /** + * If OAuth provider is used, it contains the full + * OAuth profile returned by your provider. + */ + profile?: P + /** + * If Email provider is used, on the first call, it contains a + * `verificationRequest: true` property to indicate it is being triggered in the verification request flow. + * When the callback is invoked after a user has clicked on a sign in link, + * this property will not be present. You can check for the `verificationRequest` property + * to avoid sending emails to addresses or domains on a blocklist or to only explicitly generate them + * for email address in an allow list. + */ + email?: { + verificationRequest?: boolean + } + /** If Credentials provider is used, it contains the user credentials */ + credentials?: Record + }) => Awaitable + /** + * This callback is called anytime the user is redirected to a callback URL (e.g. on signin or signout). + * By default only URLs on the same URL as the site are allowed, + * you can use this callback to customise that behaviour. + * + * [Documentation](https://next-auth.js.org/configuration/callbacks#redirect-callback) + */ + redirect: (params: { + /** URL provided as callback URL by the client */ + url: string + /** Default base URL of site (can be used as fallback) */ + baseUrl: string + }) => Awaitable + /** + * This callback is called whenever a session is checked. + * (Eg.: invoking the `/api/session` endpoint, using `useSession` or `getSession`) + * + * ⚠ By default, only a subset (email, name, image) + * of the token is returned for increased security. + * + * If you want to make something available you added to the token through the `jwt` callback, + * you have to explicitly forward it here to make it available to the client. + * + * [Documentation](https://next-auth.js.org/configuration/callbacks#session-callback) | + * [`jwt` callback](https://next-auth.js.org/configuration/callbacks#jwt-callback) | + * [`useSession`](https://next-auth.js.org/getting-started/client#usesession) | + * [`getSession`](https://next-auth.js.org/getting-started/client#getsession) | + * + */ + session: (params: { + session: Session + user: User | AdapterUser + token: JWT + }) => Awaitable + /** + * This callback is called whenever a JSON Web Token is created (i.e. at sign in) + * or updated (i.e whenever a session is accessed in the client). + * Its content is forwarded to the `session` callback, + * where you can control what should be returned to the client. + * Anything else will be kept from your front-end. + * + * ⚠ By default the JWT is signed, but not encrypted. + * + * [Documentation](https://next-auth.js.org/configuration/callbacks#jwt-callback) | + * [`session` callback](https://next-auth.js.org/configuration/callbacks#session-callback) + */ + jwt: (params: { + token: JWT + user?: User | AdapterUser + account?: A | null + profile?: P + isNewUser?: boolean + }) => Awaitable +} + +/** [Documentation](https://next-auth.js.org/configuration/options#cookies) */ +export interface CookieOption { + name: string + options: CookieSerializeOptions +} + +/** [Documentation](https://next-auth.js.org/configuration/options#cookies) */ +export interface CookiesOptions { + sessionToken: CookieOption + callbackUrl: CookieOption + csrfToken: CookieOption + pkceCodeVerifier: CookieOption + state: CookieOption + nonce: CookieOption +} + +/** + * The various event callbacks you can register for from next-auth + * + * [Documentation](https://next-auth.js.org/configuration/events) + */ +export interface EventCallbacks { + /** + * If using a `credentials` type auth, the user is the raw response from your + * credential provider. + * For other providers, you'll get the User object from your adapter, the account, + * and an indicator if the user was new to your Adapter. + */ + signIn: (message: { + user: User + account: Account | null + profile?: Profile + isNewUser?: boolean + }) => Awaitable + /** + * The message object will contain one of these depending on + * if you use JWT or database persisted sessions: + * - `token`: The JWT token for this session. + * - `session`: The session object from your adapter that is being ended. + */ + signOut: (message: { session: Session; token: JWT }) => Awaitable + createUser: (message: { user: User }) => Awaitable + updateUser: (message: { user: User }) => Awaitable + linkAccount: (message: { + user: User | AdapterUser + account: Account + profile: User | AdapterUser + }) => Awaitable + /** + * The message object will contain one of these depending on + * if you use JWT or database persisted sessions: + * - `token`: The JWT token for this session. + * - `session`: The session object from your adapter. + */ + session: (message: { session: Session; token: JWT }) => Awaitable +} + +export type EventType = keyof EventCallbacks + +/** [Documentation](https://next-auth.js.org/configuration/pages) */ +export interface PagesOptions { + signIn: string + signOut: string + /** Error code passed in query string as ?error= */ + error: string + verifyRequest: string + /** If set, new users will be directed here on first sign in */ + newUser: string +} + +export type ISODateString = string + +export interface DefaultSession { + user?: { + name?: string | null + email?: string | null + image?: string | null + } + expires: ISODateString +} + +/** + * Returned by `useSession`, `getSession`, returned by the `session` callback + * and also the shape received as a prop on the `SessionProvider` React Context + * + * [`useSession`](https://next-auth.js.org/getting-started/client#usesession) | + * [`getSession`](https://next-auth.js.org/getting-started/client#getsession) | + * [`SessionProvider`](https://next-auth.js.org/getting-started/client#sessionprovider) | + * [`session` callback](https://next-auth.js.org/configuration/callbacks#jwt-callback) + */ +export interface Session extends DefaultSession {} + +export type SessionStrategy = "jwt" | "database" + +/** [Documentation](https://next-auth.js.org/configuration/options#session) */ +export interface SessionOptions { + /** + * Choose how you want to save the user session. + * The default is `"jwt"`, an encrypted JWT (JWE) in the session cookie. + * + * If you use an `adapter` however, we default it to `"database"` instead. + * You can still force a JWT session by explicitly defining `"jwt"`. + * + * When using `"database"`, the session cookie will only contain a `sessionToken` value, + * which is used to look up the session in the database. + * + * [Documentation](https://next-auth.js.org/configuration/options#session) | [Adapter](https://next-auth.js.org/configuration/options#adapter) | [About JSON Web Tokens](https://next-auth.js.org/faq#json-web-tokens) + */ + strategy: SessionStrategy + /** + * Relative time from now in seconds when to expire the session + * @default 2592000 // 30 days + */ + maxAge: number + /** + * How often the session should be updated in seconds. + * If set to `0`, session is updated every time. + * @default 86400 // 1 day + */ + updateAge: number + /** + * Generate a custom session token for database-based sessions. + * By default, a random UUID or string is generated depending on the Node.js version. + * However, you can specify your own custom string (such as CUID) to be used. + * @default `randomUUID` or `randomBytes.toHex` depending on the Node.js version + */ + generateSessionToken: () => string +} + +export interface DefaultUser { + id: string + name?: string | null + email?: string | null + image?: string | null +} + +/** + * The shape of the returned object in the OAuth providers' `profile` callback, + * available in the `jwt` and `session` callbacks, + * or the second parameter of the `session` callback, when using a database. + * + * [`signIn` callback](https://next-auth.js.org/configuration/callbacks#sign-in-callback) | + * [`session` callback](https://next-auth.js.org/configuration/callbacks#jwt-callback) | + * [`jwt` callback](https://next-auth.js.org/configuration/callbacks#jwt-callback) | + * [`profile` OAuth provider callback](https://next-auth.js.org/configuration/providers#using-a-custom-provider) + */ +export interface User extends DefaultUser {} + +// Below are types that are only supposed be used by next-auth internally + +/** @internal */ +export type InternalProvider = (T extends "oauth" + ? OAuthConfigInternal + : T extends "email" + ? EmailConfig + : T extends "credentials" + ? CredentialsConfig + : never) & { + signinUrl: string + callbackUrl: string +} + +/** @internal */ +export type AuthAction = + | "providers" + | "session" + | "csrf" + | "signin" + | "signout" + | "callback" + | "verify-request" + | "error" + | "_log" + +/** @internal */ +export interface RequestInternal { + /** @default "http://localhost:3000" */ + host?: string + method?: string + cookies?: Partial> + headers?: Record + query?: Record + body?: Record + action: AuthAction + providerId?: string + error?: string +} + +/** @internal */ +export interface ResponseInternal< + Body extends string | Record | any[] = any +> { + status?: number + headers?: Headers | HeadersInit + body?: Body + redirect?: URL | string // TODO: refactor to only allow URL + cookies?: Cookie[] +} + +/** @internal */ +export interface InternalOptions< + TProviderType = ProviderType, + WithVerificationToken = TProviderType extends "email" ? true : false +> { + providers: InternalProvider[] + /** + * Parsed from `NEXTAUTH_URL` or `x-forwarded-host` on Vercel. + * @default "http://localhost:3000/api/auth" + */ + url: InternalUrl + action: AuthAction + provider: InternalProvider + csrfToken?: string + csrfTokenVerified?: boolean + secret: string + theme: Theme + debug: boolean + logger: LoggerInstance + session: Required + pages: Partial + jwt: JWTOptions + events: Partial + adapter: WithVerificationToken extends true + ? Adapter + : Adapter | undefined + callbacks: CallbacksOptions + cookies: CookiesOptions + callbackUrl: string +} diff --git a/packages/core/src/utils/date.ts b/packages/core/src/utils/date.ts new file mode 100644 index 0000000000..79894224d9 --- /dev/null +++ b/packages/core/src/utils/date.ts @@ -0,0 +1,8 @@ +/** + * Takes a number in seconds and returns the date in the future. + * Optionally takes a second date parameter. In that case + * the date in the future will be calculated from that date instead of now. + */ +export function fromDate(time: number, date = Date.now()) { + return new Date(date + time * 1000) +} diff --git a/packages/core/src/utils/logger.ts b/packages/core/src/utils/logger.ts new file mode 100644 index 0000000000..70c766a466 --- /dev/null +++ b/packages/core/src/utils/logger.ts @@ -0,0 +1,117 @@ +import { UnknownError } from "../errors" + +// TODO: better typing +/** Makes sure that error is always serializable */ +function formatError(o: unknown): unknown { + if (o instanceof Error && !(o instanceof UnknownError)) { + return { message: o.message, stack: o.stack, name: o.name } + } + if (hasErrorProperty(o)) { + o.error = formatError(o.error) as Error + o.message = o.message ?? o.error.message + } + return o +} + +function hasErrorProperty( + x: unknown +): x is { error: Error; [key: string]: unknown } { + return !!(x as any)?.error +} + +export type WarningCode = + | "NEXTAUTH_URL" + | "NO_SECRET" + | "TWITTER_OAUTH_2_BETA" + | "DEBUG_ENABLED" + +/** + * Override any of the methods, and the rest will use the default logger. + * + * [Documentation](https://next-auth.js.org/configuration/options#logger) + */ +export interface LoggerInstance extends Record { + warn: (code: WarningCode) => void + error: ( + code: string, + /** + * Either an instance of (JSON serializable) Error + * or an object that contains some debug information. + * (Error is still available through `metadata.error`) + */ + metadata: Error | { error: Error; [key: string]: unknown } + ) => void + debug: (code: string, metadata: unknown) => void +} + +const _logger: LoggerInstance = { + error(code, metadata) { + metadata = formatError(metadata) as Error + console.error( + `[next-auth][error][${code}]`, + `\nhttps://next-auth.js.org/errors#${code.toLowerCase()}`, + metadata.message, + metadata + ) + }, + warn(code) { + console.warn( + `[next-auth][warn][${code}]`, + `\nhttps://next-auth.js.org/warnings#${code.toLowerCase()}` + ) + }, + debug(code, metadata) { + console.log(`[next-auth][debug][${code}]`, metadata) + }, +} + +/** + * Override the built-in logger with user's implementation. + * Any `undefined` level will use the default logger. + */ +export function setLogger( + newLogger: Partial = {}, + debug?: boolean +) { + // Turn off debug logging if `debug` isn't set to `true` + if (!debug) _logger.debug = () => {} + + if (newLogger.error) _logger.error = newLogger.error + if (newLogger.warn) _logger.warn = newLogger.warn + if (newLogger.debug) _logger.debug = newLogger.debug +} + +export default _logger + +/** Serializes client-side log messages and sends them to the server */ +export function proxyLogger( + logger: LoggerInstance = _logger, + basePath?: string +): LoggerInstance { + try { + if (typeof window === "undefined") { + return logger + } + + const clientLogger: Record = {} + for (const level in logger) { + clientLogger[level] = (code: string, metadata: Error) => { + _logger[level](code, metadata) // Logs to console + + if (level === "error") { + metadata = formatError(metadata) as Error + } + ;(metadata as any).client = true + const url = `${basePath}/_log` + const body = new URLSearchParams({ level, code, ...(metadata as any) }) + if (navigator.sendBeacon) { + return navigator.sendBeacon(url, body) + } + return fetch(url, { method: "POST", body, keepalive: true }) + } + } + return clientLogger as unknown as LoggerInstance + } catch { + return _logger + } +} diff --git a/packages/core/src/utils/merge.ts b/packages/core/src/utils/merge.ts new file mode 100644 index 0000000000..ad75850177 --- /dev/null +++ b/packages/core/src/utils/merge.ts @@ -0,0 +1,25 @@ +// Source: https://stackoverflow.com/a/34749873/5364135 + +/** Simple object check */ +function isObject(item: any): boolean { + return item && typeof item === "object" && !Array.isArray(item) +} + +/** Deep merge two objects */ +export function merge(target: any, ...sources: any[]): any { + if (!sources.length) return target + const source = sources.shift() + + if (isObject(target) && isObject(source)) { + for (const key in source) { + if (isObject(source[key])) { + if (!target[key]) Object.assign(target, { [key]: {} }) + merge(target[key], source[key]) + } else { + Object.assign(target, { [key]: source[key] }) + } + } + } + + return merge(target, ...sources) +} diff --git a/packages/core/src/utils/node.ts b/packages/core/src/utils/node.ts new file mode 100644 index 0000000000..48a4996744 --- /dev/null +++ b/packages/core/src/utils/node.ts @@ -0,0 +1,35 @@ +export function setCookie(res, value: string) { + // Preserve any existing cookies that have already been set in the same session + let setCookieHeader = res.getHeader("Set-Cookie") ?? [] + // If not an array (i.e. a string with a single cookie) convert it into an array + if (!Array.isArray(setCookieHeader)) { + setCookieHeader = [setCookieHeader] + } + setCookieHeader.push(value) + res.setHeader("Set-Cookie", setCookieHeader) +} + +/** Extract the host from the environment */ +export function getURL( + url: string | undefined | null, + trusted: boolean | undefined = !!( + process.env.AUTH_TRUST_HOST ?? process.env.VERCEL + ), + forwardedValue: string | string[] | undefined | null +): URL | Error { + try { + let host = + process.env.NEXTAUTH_URL ?? + (process.env.NODE_ENV !== "production" && "http://localhost:3000") + + if (trusted && forwardedValue) { + host = Array.isArray(forwardedValue) ? forwardedValue[0] : forwardedValue + } + + if (!host) throw new TypeError("Invalid host") + + return new URL(url ?? "", new URL(host)) + } catch (error) { + return error as Error + } +} diff --git a/packages/core/src/utils/parse-url.ts b/packages/core/src/utils/parse-url.ts new file mode 100644 index 0000000000..7f63b0ada2 --- /dev/null +++ b/packages/core/src/utils/parse-url.ts @@ -0,0 +1,36 @@ +export interface InternalUrl { + /** @default "http://localhost:3000" */ + origin: string + /** @default "localhost:3000" */ + host: string + /** @default "/api/auth" */ + path: string + /** @default "http://localhost:3000/api/auth" */ + base: string + /** @default "http://localhost:3000/api/auth" */ + toString: () => string +} + +/** Returns an `URL` like object to make requests/redirects from server-side */ +export default function parseUrl(url?: string | URL): InternalUrl { + const defaultUrl = new URL("http://localhost:3000/api/auth") + + if (url && !url.toString().startsWith("http")) { + url = `https://${url}` + } + + const _url = new URL(url ?? defaultUrl) + const path = (_url.pathname === "/" ? defaultUrl.pathname : _url.pathname) + // Remove trailing slash + .replace(/\/$/, "") + + const base = `${_url.origin}${path}` + + return { + origin: _url.origin, + host: _url.host, + path, + base, + toString: () => base, + } +} diff --git a/packages/core/tsconfig.dev.json b/packages/core/tsconfig.dev.json new file mode 100644 index 0000000000..2c634905f7 --- /dev/null +++ b/packages/core/tsconfig.dev.json @@ -0,0 +1,21 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "watch": true, + "emitDeclarationOnly": false + }, + "watchOptions": { + "excludeDirectories": [ + "lib", + "css", + "jwt", + "providers", + "core", + "utils" + ], + "excludeFiles": [ + "index.d.ts", + "index.js", + ] + } +} \ No newline at end of file diff --git a/packages/core/tsconfig.eslint.json b/packages/core/tsconfig.eslint.json new file mode 100644 index 0000000000..80c95afec5 --- /dev/null +++ b/packages/core/tsconfig.eslint.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "types": ["@types/jest"], + "typeRoots": ["./node_modules/@types"] + }, + "exclude": ["./coverage", "./*.js", "./*.d.ts"] +} diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json new file mode 100644 index 0000000000..752520dac5 --- /dev/null +++ b/packages/core/tsconfig.json @@ -0,0 +1,31 @@ +{ + "extends": "@next-auth/tsconfig/tsconfig.base.json", + "compilerOptions": { + "allowJs": true, + "baseUrl": ".", + "isolatedModules": true, + "jsx": "react-jsx", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "module": "esnext", + "moduleResolution": "node", + "outDir": "dist", + "resolveJsonModule": true, + "rootDir": "src", + "skipDefaultLibCheck": true, + "strict": false, + "strictNullChecks": true, + "stripInternal": true, + }, + "include": [ + "src/**/*", + ], + "exclude": [ + "./*.js", + "./*.d.ts", + "./config", + ] +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 02edf2761c..2889dd3906 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -63,6 +63,7 @@ importers: faunadb: ^4 next: 13.0.6 next-auth: workspace:* + next-auth-core: workspace:* nodemailer: ^6 pg: ^8.7.3 prisma: ^3 @@ -80,6 +81,7 @@ importers: faunadb: 4.6.0 next: 13.0.6_biqbaboplfbrettd7655fr4n2y next-auth: link:../../packages/next-auth + next-auth-core: link:../../packages/core nodemailer: 6.7.5 react: 18.2.0 react-dom: 18.2.0_react@18.2.0 @@ -435,6 +437,45 @@ importers: next-auth: link:../next-auth typescript: 4.7.4 + packages/core: + specifiers: + '@next-auth/tsconfig': workspace:* + '@panva/hkdf': 1.0.2 + '@types/node': 18.11.10 + '@types/nodemailer': 6.4.6 + '@types/react': 18.0.26 + autoprefixer: 10.4.13 + cookie: 0.5.0 + cssnano: 5.1.14 + jose: 4.11.1 + oauth4webapi: 2.0.4 + openid-client: ^5.1.0 + postcss: 8.4.19 + postcss-cli: 10.1.0 + postcss-nested: 6.0.0 + preact: 10.11.3 + preact-render-to-string: 5.2.3 + uuid: 9.0.0 + dependencies: + '@panva/hkdf': 1.0.2 + cookie: 0.5.0 + jose: 4.11.1 + oauth4webapi: 2.0.4 + preact: 10.11.3 + preact-render-to-string: 5.2.3_preact@10.11.3 + uuid: 9.0.0 + devDependencies: + '@next-auth/tsconfig': link:../tsconfig + '@types/node': 18.11.10 + '@types/nodemailer': 6.4.6 + '@types/react': 18.0.26 + autoprefixer: 10.4.13_postcss@8.4.19 + cssnano: 5.1.14_postcss@8.4.19 + openid-client: 5.1.6 + postcss: 8.4.19 + postcss-cli: 10.1.0_postcss@8.4.19 + postcss-nested: 6.0.0_postcss@8.4.19 + packages/next-auth: specifiers: '@babel/cli': ^7.17.10 @@ -5289,9 +5330,9 @@ packages: resolution: {integrity: sha512-pRLewcgGhOies6pzsUROfmPStDRdFw+FgV5sMtLr5+4Luv2rty5+b/eSIMMetqUsmg3A9r9bcxHk9bKAKvx3zQ==} engines: {node: '>=16.14'} dependencies: - cssnano-preset-advanced: 5.3.8_postcss@8.4.14 - postcss: 8.4.14 - postcss-sort-media-queries: 4.2.1_postcss@8.4.14 + cssnano-preset-advanced: 5.3.8_postcss@8.4.19 + postcss: 8.4.19 + postcss-sort-media-queries: 4.2.1_postcss@8.4.19 tslib: 2.4.0 dev: true @@ -5646,7 +5687,7 @@ packages: peerDependencies: react: '*' dependencies: - '@types/react': 18.0.15 + '@types/react': 18.0.26 prop-types: 15.8.1 react: 18.2.0 dev: true @@ -5676,7 +5717,7 @@ packages: infima: 0.2.0-alpha.42 lodash: 4.17.21 nprogress: 0.2.0 - postcss: 8.4.14 + postcss: 8.4.19 prism-react-renderer: 1.3.5_react@18.2.0 prismjs: 1.28.0 react: 18.2.0 @@ -6513,7 +6554,7 @@ packages: engines: {node: ^8.13.0 || >=10.10.0} dependencies: '@grpc/proto-loader': 0.6.13 - '@types/node': 18.0.0 + '@types/node': 18.11.10 dev: true /@grpc/grpc-js/1.7.3: @@ -6521,7 +6562,7 @@ packages: engines: {node: ^8.13.0 || >=10.10.0} dependencies: '@grpc/proto-loader': 0.7.3 - '@types/node': 18.0.0 + '@types/node': 18.11.10 dev: true /@grpc/proto-loader/0.6.13: @@ -6594,7 +6635,7 @@ packages: engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} dependencies: '@jest/types': 27.5.1 - '@types/node': 18.0.0 + '@types/node': 18.11.10 chalk: 4.1.2 jest-message-util: 27.5.1 jest-util: 27.5.1 @@ -6606,7 +6647,7 @@ packages: engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: '@jest/types': 28.1.1 - '@types/node': 18.0.0 + '@types/node': 18.11.10 chalk: 4.1.2 jest-message-util: 28.1.1 jest-util: 28.1.1 @@ -6618,7 +6659,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.2.1 - '@types/node': 18.0.0 + '@types/node': 18.11.10 chalk: 4.1.2 jest-message-util: 29.2.1 jest-util: 29.2.1 @@ -6639,7 +6680,7 @@ packages: '@jest/test-result': 27.5.1 '@jest/transform': 27.5.1 '@jest/types': 27.5.1 - '@types/node': 18.0.0 + '@types/node': 18.11.10 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.8.1 @@ -6684,14 +6725,14 @@ packages: '@jest/test-result': 28.1.1 '@jest/transform': 28.1.1 '@jest/types': 28.1.1 - '@types/node': 18.0.0 + '@types/node': 18.11.10 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 3.3.2 exit: 0.1.2 graceful-fs: 4.2.10 jest-changed-files: 28.0.2 - jest-config: 28.1.1_@types+node@18.0.0 + jest-config: 28.1.1_@types+node@18.11.10 jest-haste-map: 28.1.1 jest-message-util: 28.1.1 jest-regex-util: 28.0.2 @@ -6704,7 +6745,7 @@ packages: jest-validate: 28.1.1 jest-watcher: 28.1.1 micromatch: 4.0.5 - pretty-format: 28.1.1 + pretty-format: 28.1.3 rimraf: 3.0.2 slash: 3.0.0 strip-ansi: 6.0.1 @@ -6727,14 +6768,14 @@ packages: '@jest/test-result': 29.2.1 '@jest/transform': 29.3.0 '@jest/types': 29.2.1 - '@types/node': 18.0.0 + '@types/node': 18.11.10 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 3.3.2 exit: 0.1.2 graceful-fs: 4.2.10 jest-changed-files: 29.2.0 - jest-config: 29.3.0_@types+node@18.0.0 + jest-config: 29.3.0_@types+node@18.11.10 jest-haste-map: 29.3.0 jest-message-util: 29.2.1 jest-regex-util: 29.2.0 @@ -6768,7 +6809,7 @@ packages: dependencies: '@jest/fake-timers': 27.5.1 '@jest/types': 27.5.1 - '@types/node': 18.0.0 + '@types/node': 18.11.10 jest-mock: 27.5.1 dev: true @@ -6778,7 +6819,7 @@ packages: dependencies: '@jest/fake-timers': 28.1.1 '@jest/types': 28.1.1 - '@types/node': 18.0.0 + '@types/node': 18.11.10 jest-mock: 28.1.1 dev: true @@ -6788,7 +6829,7 @@ packages: dependencies: '@jest/fake-timers': 28.1.3 '@jest/types': 28.1.3 - '@types/node': 18.0.0 + '@types/node': 18.11.10 jest-mock: 28.1.3 dev: true @@ -6798,7 +6839,7 @@ packages: dependencies: '@jest/fake-timers': 29.3.0 '@jest/types': 29.2.1 - '@types/node': 18.0.0 + '@types/node': 18.11.10 jest-mock: 29.3.0 dev: true @@ -6842,7 +6883,7 @@ packages: dependencies: '@jest/types': 27.5.1 '@sinonjs/fake-timers': 8.1.0 - '@types/node': 18.0.0 + '@types/node': 18.11.10 jest-message-util: 27.5.1 jest-mock: 27.5.1 jest-util: 27.5.1 @@ -6854,7 +6895,7 @@ packages: dependencies: '@jest/types': 28.1.1 '@sinonjs/fake-timers': 9.1.2 - '@types/node': 18.0.0 + '@types/node': 18.11.10 jest-message-util: 28.1.1 jest-mock: 28.1.1 jest-util: 28.1.1 @@ -6866,7 +6907,7 @@ packages: dependencies: '@jest/types': 28.1.3 '@sinonjs/fake-timers': 9.1.2 - '@types/node': 18.0.0 + '@types/node': 18.11.10 jest-message-util: 28.1.3 jest-mock: 28.1.3 jest-util: 28.1.3 @@ -6878,7 +6919,7 @@ packages: dependencies: '@jest/types': 29.2.1 '@sinonjs/fake-timers': 9.1.2 - '@types/node': 18.0.0 + '@types/node': 18.11.10 jest-message-util: 29.2.1 jest-mock: 29.3.0 jest-util: 29.2.1 @@ -6930,7 +6971,7 @@ packages: '@jest/test-result': 27.5.1 '@jest/transform': 27.5.1 '@jest/types': 27.5.1 - '@types/node': 18.0.0 + '@types/node': 18.11.10 chalk: 4.1.2 collect-v8-coverage: 1.0.1 exit: 0.1.2 @@ -6969,7 +7010,7 @@ packages: '@jest/transform': 28.1.1 '@jest/types': 28.1.1 '@jridgewell/trace-mapping': 0.3.13 - '@types/node': 18.0.0 + '@types/node': 18.11.10 chalk: 4.1.2 collect-v8-coverage: 1.0.1 exit: 0.1.2 @@ -7007,7 +7048,7 @@ packages: '@jest/transform': 29.3.0 '@jest/types': 29.2.1 '@jridgewell/trace-mapping': 0.3.17 - '@types/node': 18.0.0 + '@types/node': 18.11.10 chalk: 4.1.2 collect-v8-coverage: 1.0.1 exit: 0.1.2 @@ -7214,7 +7255,7 @@ packages: dependencies: '@types/istanbul-lib-coverage': 2.0.4 '@types/istanbul-reports': 3.0.1 - '@types/node': 18.0.0 + '@types/node': 18.11.10 '@types/yargs': 15.0.14 chalk: 4.1.2 dev: true @@ -7225,7 +7266,7 @@ packages: dependencies: '@types/istanbul-lib-coverage': 2.0.4 '@types/istanbul-reports': 3.0.1 - '@types/node': 18.0.0 + '@types/node': 18.11.10 '@types/yargs': 16.0.4 chalk: 4.1.2 dev: true @@ -7237,7 +7278,7 @@ packages: '@jest/schemas': 28.0.2 '@types/istanbul-lib-coverage': 2.0.4 '@types/istanbul-reports': 3.0.1 - '@types/node': 18.0.0 + '@types/node': 18.11.10 '@types/yargs': 17.0.10 chalk: 4.1.2 dev: true @@ -7249,7 +7290,7 @@ packages: '@jest/schemas': 28.1.3 '@types/istanbul-lib-coverage': 2.0.4 '@types/istanbul-reports': 3.0.1 - '@types/node': 18.0.0 + '@types/node': 18.11.10 '@types/yargs': 17.0.10 chalk: 4.1.2 dev: true @@ -7261,7 +7302,7 @@ packages: '@jest/schemas': 29.0.0 '@types/istanbul-lib-coverage': 2.0.4 '@types/istanbul-reports': 3.0.1 - '@types/node': 18.0.0 + '@types/node': 18.11.10 '@types/yargs': 17.0.10 chalk: 4.1.2 dev: true @@ -8382,26 +8423,26 @@ packages: resolution: {integrity: sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==} dependencies: '@types/connect': 3.4.35 - '@types/node': 18.0.0 + '@types/node': 18.11.10 dev: true /@types/bonjour/3.5.10: resolution: {integrity: sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw==} dependencies: - '@types/node': 18.0.0 + '@types/node': 18.11.10 dev: true /@types/connect-history-api-fallback/1.3.5: resolution: {integrity: sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw==} dependencies: '@types/express-serve-static-core': 4.17.29 - '@types/node': 18.0.0 + '@types/node': 18.11.10 dev: true /@types/connect/3.4.35: resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==} dependencies: - '@types/node': 18.0.0 + '@types/node': 18.11.10 dev: true /@types/cookie/0.4.1: @@ -8421,7 +8462,7 @@ packages: /@types/duplexify/3.6.1: resolution: {integrity: sha512-n0zoEj/fMdMOvqbHxmqnza/kXyoGgJmEpsXjpP+gEqE1Ye4yNqc7xWipKnUoMpWhMuzJQSfK2gMrwlElly7OGQ==} dependencies: - '@types/node': 18.0.0 + '@types/node': 18.11.10 dev: true /@types/eslint-scope/3.7.3: @@ -8445,7 +8486,7 @@ packages: /@types/express-serve-static-core/4.17.29: resolution: {integrity: sha512-uMd++6dMKS32EOuw1Uli3e3BPgdLIXmezcfHv7N4c1s3gkhikBplORPpMq3fuWkxncZN1reb16d5n8yhQ80x7Q==} dependencies: - '@types/node': 18.0.0 + '@types/node': 18.11.10 '@types/qs': 6.9.7 '@types/range-parser': 1.2.4 dev: true @@ -8462,20 +8503,20 @@ packages: /@types/fs-extra/8.1.2: resolution: {integrity: sha512-SvSrYXfWSc7R4eqnOzbQF4TZmfpNSM9FrSWLU3EUnWBuyZqNBOrv1B1JA3byUDPUl9z4Ab3jeZG2eDdySlgNMg==} dependencies: - '@types/node': 18.0.0 + '@types/node': 18.11.10 dev: true /@types/glob/7.2.0: resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==} dependencies: '@types/minimatch': 3.0.5 - '@types/node': 18.0.0 + '@types/node': 18.11.10 dev: true /@types/graceful-fs/4.1.5: resolution: {integrity: sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==} dependencies: - '@types/node': 18.0.0 + '@types/node': 18.11.10 dev: true /@types/hast/2.3.4: @@ -8495,7 +8536,7 @@ packages: /@types/http-proxy/1.17.9: resolution: {integrity: sha512-QsbSjA/fSk7xB+UXlCT3wHBy5ai9wOcNDWwZAtud+jXhwOM3l+EYZh8Lng4+/6n8uar0J7xILzqftJdJ/Wdfkw==} dependencies: - '@types/node': 18.0.0 + '@types/node': 18.11.10 dev: true /@types/istanbul-lib-coverage/2.0.4: @@ -8539,7 +8580,7 @@ packages: /@types/jsdom/16.2.14: resolution: {integrity: sha512-6BAy1xXEmMuHeAJ4Fv4yXKwBDTGTOseExKE3OaHiNycdHdZw59KfYzrt0DkDluvwmik1HRt6QS7bImxUmpSy+w==} dependencies: - '@types/node': 18.0.0 + '@types/node': 18.11.10 '@types/parse5': 6.0.3 '@types/tough-cookie': 4.0.2 dev: true @@ -8555,13 +8596,13 @@ packages: /@types/jsonwebtoken/8.5.8: resolution: {integrity: sha512-zm6xBQpFDIDM6o9r6HSgDeIcLy82TKWctCXEPbJJcXb5AKmi5BNNdLXneixK4lplX3PqIVcwLBCGE/kAGnlD4A==} dependencies: - '@types/node': 18.0.0 + '@types/node': 18.11.10 dev: true /@types/keyv/3.1.4: resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} dependencies: - '@types/node': 18.0.0 + '@types/node': 18.11.10 dev: true /@types/linkify-it/3.0.2: @@ -8618,7 +8659,7 @@ packages: /@types/node-fetch/2.6.2: resolution: {integrity: sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A==} dependencies: - '@types/node': 18.0.0 + '@types/node': 18.11.10 form-data: 3.0.1 dev: true @@ -8630,14 +8671,20 @@ packages: resolution: {integrity: sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==} dev: true - /@types/node/18.0.0: - resolution: {integrity: sha512-cHlGmko4gWLVI27cGJntjs/Sj8th9aYwplmZFwmmgYQQvL5NUsgVJG7OddLvNfLqYS31KFN0s3qlaD9qCaxACA==} + /@types/node/18.11.10: + resolution: {integrity: sha512-juG3RWMBOqcOuXC643OAdSA525V44cVgGV6dUDuiFtss+8Fk5x1hI93Rsld43VeJVIeqlP9I7Fn9/qaVqoEAuQ==} dev: true /@types/nodemailer/6.4.4: resolution: {integrity: sha512-Ksw4t7iliXeYGvIQcSIgWQ5BLuC/mljIEbjf615svhZL10PE9t+ei8O9gDaD3FPCasUJn9KTLwz2JFJyiiyuqw==} dependencies: - '@types/node': 17.0.45 + '@types/node': 18.11.10 + dev: true + + /@types/nodemailer/6.4.6: + resolution: {integrity: sha512-pD6fL5GQtUKvD2WnPmg5bC2e8kWCAPDwMPmHe/ohQbW+Dy0EcHgZ2oCSuPlWNqk74LS5BVMig1SymQbFMPPK3w==} + dependencies: + '@types/node': 18.11.10 dev: true /@types/normalize-package-data/2.4.1: @@ -8647,7 +8694,7 @@ packages: /@types/oauth/0.9.1: resolution: {integrity: sha512-a1iY62/a3yhZ7qH7cNUsxoI3U/0Fe9+RnuFrpTKr+0WVOzbKlSLojShCKe20aOD1Sppv+i8Zlq0pLDuTJnwS4A==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.com/@types/oauth/-/oauth-0.9.1.tgz} dependencies: - '@types/node': 17.0.45 + '@types/node': 18.11.10 dev: true /@types/parse-json/4.0.0: @@ -8813,14 +8860,14 @@ packages: /@types/react-dom/18.0.6: resolution: {integrity: sha512-/5OFZgfIPSwy+YuIBP/FgJnQnsxhZhjjrnxudMddeblOouIodEQ75X14Rr4wGSG/bknL+Omy9iWlLo1u/9GzAA==} dependencies: - '@types/react': 18.0.15 + '@types/react': 18.0.26 dev: true /@types/react-router-config/5.0.6: resolution: {integrity: sha512-db1mx37a1EJDf1XeX8jJN7R3PZABmJQXR8r28yUjVMFSjkmnQo6X6pOEEmNl+Tp2gYQOGPdYbFIipBtdElZ3Yg==} dependencies: '@types/history': 4.7.11 - '@types/react': 18.0.15 + '@types/react': 18.0.26 '@types/react-router': 5.1.18 dev: true @@ -8828,7 +8875,7 @@ packages: resolution: {integrity: sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==} dependencies: '@types/history': 4.7.11 - '@types/react': 18.0.15 + '@types/react': 18.0.26 '@types/react-router': 5.1.18 dev: true @@ -8836,7 +8883,7 @@ packages: resolution: {integrity: sha512-YYknwy0D0iOwKQgz9v8nOzt2J6l4gouBmDnWqUUznltOTaon+r8US8ky8HvN0tXvc38U9m6z/t2RsVsnd1zM0g==} dependencies: '@types/history': 4.7.11 - '@types/react': 18.0.15 + '@types/react': 18.0.26 dev: true /@types/react/18.0.15: @@ -8847,10 +8894,18 @@ packages: csstype: 3.1.0 dev: true + /@types/react/18.0.26: + resolution: {integrity: sha512-hCR3PJQsAIXyxhTNSiDFY//LhnMZWpNNr5etoCqx/iUfGc5gXWtQR2Phl908jVR6uPXacojQWTg4qRpkxTuGug==} + dependencies: + '@types/prop-types': 15.7.5 + '@types/scheduler': 0.16.2 + csstype: 3.1.0 + dev: true + /@types/responselike/1.0.0: resolution: {integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==} dependencies: - '@types/node': 18.0.0 + '@types/node': 18.11.10 dev: true /@types/retry/0.12.0: @@ -8860,7 +8915,7 @@ packages: /@types/sax/1.2.4: resolution: {integrity: sha512-pSAff4IAxJjfAXUG6tFkO7dsSbTmf8CtUpfhhZ5VhkRpC4628tJhh3+V6H1E+/Gs9piSzYKT5yzHO5M4GG9jkw==} dependencies: - '@types/node': 18.0.0 + '@types/node': 18.11.10 dev: true /@types/scheduler/0.16.2: @@ -8877,26 +8932,26 @@ packages: resolution: {integrity: sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==} dependencies: '@types/mime': 1.3.2 - '@types/node': 18.0.0 + '@types/node': 18.11.10 dev: true /@types/set-cookie-parser/2.4.2: resolution: {integrity: sha512-fBZgytwhYAUkj/jC/FAV4RQ5EerRup1YQsXQCh8rZfiHkc4UahC192oH0smGwsXol3cL3A5oETuAHeQHmhXM4w==} dependencies: - '@types/node': 18.0.0 + '@types/node': 18.11.10 dev: true /@types/shelljs/0.8.11: resolution: {integrity: sha512-x9yaMvEh5BEaZKeVQC4vp3l+QoFj3BXcd4aYfuKSzIIyihjdVARAadYy3SMNIz0WCCdS2vB9JL/U6GQk5PaxQw==} dependencies: '@types/glob': 7.2.0 - '@types/node': 18.0.0 + '@types/node': 18.11.10 dev: true /@types/sockjs/0.3.33: resolution: {integrity: sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw==} dependencies: - '@types/node': 18.0.0 + '@types/node': 18.11.10 dev: true /@types/stack-utils/2.0.1: @@ -8906,7 +8961,7 @@ packages: /@types/stoppable/1.1.1: resolution: {integrity: sha512-b8N+fCADRIYYrGZOcmOR8ZNBOqhktWTB/bMUl5LvGtT201QKJZOOH5UsFyI3qtteM6ZAJbJqZoBcLqqxKIwjhw==} dependencies: - '@types/node': 18.0.0 + '@types/node': 18.11.10 dev: true /@types/testing-library__jest-dom/5.14.5: @@ -8922,7 +8977,7 @@ packages: /@types/tunnel/0.0.3: resolution: {integrity: sha512-sOUTGn6h1SfQ+gbgqC364jLFBw2lnFqkgF3q0WovEHRLMrVD1sd5aufqi/aJObLekJO+Aq5z646U4Oxy6shXMA==} dependencies: - '@types/node': 18.0.0 + '@types/node': 18.11.10 dev: true /@types/unist/2.0.6: @@ -8943,14 +8998,14 @@ packages: /@types/whatwg-url/8.2.2: resolution: {integrity: sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==} dependencies: - '@types/node': 18.0.0 + '@types/node': 18.11.10 '@types/webidl-conversions': 6.1.1 dev: true /@types/ws/8.5.3: resolution: {integrity: sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==} dependencies: - '@types/node': 18.0.0 + '@types/node': 18.11.10 dev: true /@types/yargs-parser/21.0.0: @@ -9905,6 +9960,22 @@ packages: hasBin: true dev: true + /autoprefixer/10.4.13_postcss@8.4.19: + resolution: {integrity: sha512-49vKpMqcZYsJjwotvt4+h/BCjJVnhGwcLpDt5xkcaOG3eLrG/HUYLagrihYsQ+qrIBgIzX1Rw7a6L8I/ZA1Atg==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 + dependencies: + browserslist: 4.21.4 + caniuse-lite: 1.0.30001431 + fraction.js: 4.2.0 + normalize-range: 0.1.2 + picocolors: 1.0.0 + postcss: 8.4.19 + postcss-value-parser: 4.2.0 + dev: true + /autoprefixer/10.4.7_postcss@8.4.14: resolution: {integrity: sha512-ypHju4Y2Oav95SipEcCcI5J7CGPuvz8oat7sUtYj3ClK44bldfvtvcxK6IEK++7rqB7YchDGzweZIBG+SD0ZAA==} engines: {node: ^10 || ^12 || >=14} @@ -10711,7 +10782,7 @@ packages: /caniuse-api/3.0.0: resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==} dependencies: - browserslist: 4.21.0 + browserslist: 4.21.4 caniuse-lite: 1.0.30001431 lodash.memoize: 4.1.2 lodash.uniq: 4.5.0 @@ -11466,18 +11537,27 @@ packages: postcss: 8.4.14 dev: true + /css-declaration-sorter/6.3.1_postcss@8.4.19: + resolution: {integrity: sha512-fBffmak0bPAnyqc/HO8C3n2sHrp9wcqQz6ES9koRF2/mLOVAx9zIQ3Y7R29sYCteTPqMCwns4WYQoCX91Xl3+w==} + engines: {node: ^10 || ^12 || >=14} + peerDependencies: + postcss: ^8.0.9 + dependencies: + postcss: 8.4.19 + dev: true + /css-loader/6.7.1_webpack@5.73.0: resolution: {integrity: sha512-yB5CNFa14MbPJcomwNh3wLThtkZgcNyI2bNMRt8iE5Z8Vwl7f8vQXFAzn2HDOJvtDq2NTZBUGMSUNNyrv3/+cw==} engines: {node: '>= 12.13.0'} peerDependencies: webpack: ^5.0.0 dependencies: - icss-utils: 5.1.0_postcss@8.4.14 - postcss: 8.4.14 - postcss-modules-extract-imports: 3.0.0_postcss@8.4.14 - postcss-modules-local-by-default: 4.0.0_postcss@8.4.14 - postcss-modules-scope: 3.0.0_postcss@8.4.14 - postcss-modules-values: 4.0.0_postcss@8.4.14 + icss-utils: 5.1.0_postcss@8.4.19 + postcss: 8.4.19 + postcss-modules-extract-imports: 3.0.0_postcss@8.4.19 + postcss-modules-local-by-default: 4.0.0_postcss@8.4.19 + postcss-modules-scope: 3.0.0_postcss@8.4.19 + postcss-modules-values: 4.0.0_postcss@8.4.19 postcss-value-parser: 4.2.0 semver: 7.3.7 webpack: 5.73.0 @@ -11503,9 +11583,9 @@ packages: optional: true dependencies: clean-css: 5.3.0 - cssnano: 5.1.12_postcss@8.4.14 + cssnano: 5.1.14_postcss@8.4.19 jest-worker: 27.5.1 - postcss: 8.4.14 + postcss: 8.4.19 schema-utils: 4.0.0 serialize-javascript: 6.0.0 source-map: 0.6.1 @@ -11571,19 +11651,19 @@ packages: hasBin: true dev: true - /cssnano-preset-advanced/5.3.8_postcss@8.4.14: + /cssnano-preset-advanced/5.3.8_postcss@8.4.19: resolution: {integrity: sha512-xUlLLnEB1LjpEik+zgRNlk8Y/koBPPtONZjp7JKbXigeAmCrFvq9H0pXW5jJV45bQWAlmJ0sKy+IMr0XxLYQZg==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - autoprefixer: 10.4.7_postcss@8.4.14 - cssnano-preset-default: 5.2.12_postcss@8.4.14 - postcss: 8.4.14 - postcss-discard-unused: 5.1.0_postcss@8.4.14 - postcss-merge-idents: 5.1.1_postcss@8.4.14 - postcss-reduce-idents: 5.2.0_postcss@8.4.14 - postcss-zindex: 5.1.0_postcss@8.4.14 + autoprefixer: 10.4.13_postcss@8.4.19 + cssnano-preset-default: 5.2.13_postcss@8.4.19 + postcss: 8.4.19 + postcss-discard-unused: 5.1.0_postcss@8.4.19 + postcss-merge-idents: 5.1.1_postcss@8.4.19 + postcss-reduce-idents: 5.2.0_postcss@8.4.19 + postcss-zindex: 5.1.0_postcss@8.4.19 dev: true /cssnano-preset-default/5.2.12_postcss@8.4.14: @@ -11624,6 +11704,44 @@ packages: postcss-unique-selectors: 5.1.1_postcss@8.4.14 dev: true + /cssnano-preset-default/5.2.13_postcss@8.4.19: + resolution: {integrity: sha512-PX7sQ4Pb+UtOWuz8A1d+Rbi+WimBIxJTRyBdgGp1J75VU0r/HFQeLnMYgHiCAp6AR4rqrc7Y4R+1Rjk3KJz6DQ==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + css-declaration-sorter: 6.3.1_postcss@8.4.19 + cssnano-utils: 3.1.0_postcss@8.4.19 + postcss: 8.4.19 + postcss-calc: 8.2.4_postcss@8.4.19 + postcss-colormin: 5.3.0_postcss@8.4.19 + postcss-convert-values: 5.1.3_postcss@8.4.19 + postcss-discard-comments: 5.1.2_postcss@8.4.19 + postcss-discard-duplicates: 5.1.0_postcss@8.4.19 + postcss-discard-empty: 5.1.1_postcss@8.4.19 + postcss-discard-overridden: 5.1.0_postcss@8.4.19 + postcss-merge-longhand: 5.1.7_postcss@8.4.19 + postcss-merge-rules: 5.1.3_postcss@8.4.19 + postcss-minify-font-values: 5.1.0_postcss@8.4.19 + postcss-minify-gradients: 5.1.1_postcss@8.4.19 + postcss-minify-params: 5.1.4_postcss@8.4.19 + postcss-minify-selectors: 5.2.1_postcss@8.4.19 + postcss-normalize-charset: 5.1.0_postcss@8.4.19 + postcss-normalize-display-values: 5.1.0_postcss@8.4.19 + postcss-normalize-positions: 5.1.1_postcss@8.4.19 + postcss-normalize-repeat-style: 5.1.1_postcss@8.4.19 + postcss-normalize-string: 5.1.0_postcss@8.4.19 + postcss-normalize-timing-functions: 5.1.0_postcss@8.4.19 + postcss-normalize-unicode: 5.1.1_postcss@8.4.19 + postcss-normalize-url: 5.1.0_postcss@8.4.19 + postcss-normalize-whitespace: 5.1.1_postcss@8.4.19 + postcss-ordered-values: 5.1.3_postcss@8.4.19 + postcss-reduce-initial: 5.1.1_postcss@8.4.19 + postcss-reduce-transforms: 5.1.0_postcss@8.4.19 + postcss-svgo: 5.1.0_postcss@8.4.19 + postcss-unique-selectors: 5.1.1_postcss@8.4.19 + dev: true + /cssnano-utils/3.1.0_postcss@8.4.14: resolution: {integrity: sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==} engines: {node: ^10 || ^12 || >=14.0} @@ -11633,6 +11751,15 @@ packages: postcss: 8.4.14 dev: true + /cssnano-utils/3.1.0_postcss@8.4.19: + resolution: {integrity: sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.19 + dev: true + /cssnano/5.1.12_postcss@8.4.14: resolution: {integrity: sha512-TgvArbEZu0lk/dvg2ja+B7kYoD7BBCmn3+k58xD0qjrGHsFzXY/wKTo9M5egcUCabPol05e/PVoIu79s2JN4WQ==} engines: {node: ^10 || ^12 || >=14.0} @@ -11645,6 +11772,18 @@ packages: yaml: 1.10.2 dev: true + /cssnano/5.1.14_postcss@8.4.19: + resolution: {integrity: sha512-Oou7ihiTocbKqi0J1bB+TRJIQX5RMR3JghA8hcWSw9mjBLQ5Y3RWqEDoYG3sRNlAbCIXpqMoZGbq5KDR3vdzgw==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + cssnano-preset-default: 5.2.13_postcss@8.4.19 + lilconfig: 2.0.5 + postcss: 8.4.19 + yaml: 1.10.2 + dev: true + /csso/4.2.0: resolution: {integrity: sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==} engines: {node: '>=8.0.0'} @@ -13653,7 +13792,7 @@ packages: resolution: {integrity: sha512-EzV94NYKoO09GLXGjXj9JIlXijVck4ONSr5wiCWDvhsvj5jxSrzTmRU/9C1DyB6uToszLs8aifA6NQ7lEQdvFw==} engines: {node: '>= 0.8'} dependencies: - '@types/node': 18.0.0 + '@types/node': 18.11.10 require-like: 0.1.2 dev: true @@ -14401,6 +14540,15 @@ packages: universalify: 2.0.0 dev: true + /fs-extra/11.1.0: + resolution: {integrity: sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw==} + engines: {node: '>=14.14'} + dependencies: + graceful-fs: 4.2.10 + jsonfile: 6.1.0 + universalify: 2.0.0 + dev: true + /fs-extra/8.1.0: resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} engines: {node: '>=6 <7 || >=8'} @@ -15435,13 +15583,13 @@ packages: dependencies: safer-buffer: 2.1.2 - /icss-utils/5.1.0_postcss@8.4.14: + /icss-utils/5.1.0_postcss@8.4.19: resolution: {integrity: sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==} engines: {node: ^10 || ^12 || >= 14} peerDependencies: postcss: ^8.1.0 dependencies: - postcss: 8.4.14 + postcss: 8.4.19 dev: true /idb/7.0.1: @@ -16119,7 +16267,7 @@ packages: '@jest/environment': 27.5.1 '@jest/test-result': 27.5.1 '@jest/types': 27.5.1 - '@types/node': 18.0.0 + '@types/node': 18.11.10 chalk: 4.1.2 co: 4.6.0 dedent: 0.7.0 @@ -16147,7 +16295,7 @@ packages: '@jest/expect': 28.1.1 '@jest/test-result': 28.1.1 '@jest/types': 28.1.1 - '@types/node': 18.0.0 + '@types/node': 18.11.10 chalk: 4.1.2 co: 4.6.0 dedent: 0.7.0 @@ -16158,7 +16306,7 @@ packages: jest-runtime: 28.1.1 jest-snapshot: 28.1.1 jest-util: 28.1.1 - pretty-format: 28.1.1 + pretty-format: 28.1.3 slash: 3.0.0 stack-utils: 2.0.5 throat: 6.0.1 @@ -16174,7 +16322,7 @@ packages: '@jest/expect': 29.3.0 '@jest/test-result': 29.2.1 '@jest/types': 29.2.1 - '@types/node': 18.0.0 + '@types/node': 18.11.10 chalk: 4.1.2 co: 4.6.0 dedent: 0.7.0 @@ -16351,14 +16499,14 @@ packages: jest-validate: 28.1.1 micromatch: 4.0.5 parse-json: 5.2.0 - pretty-format: 28.1.1 + pretty-format: 28.1.3 slash: 3.0.0 strip-json-comments: 3.1.1 transitivePeerDependencies: - supports-color dev: true - /jest-config/28.1.1_@types+node@18.0.0: + /jest-config/28.1.1_@types+node@18.11.10: resolution: {integrity: sha512-tASynMhS+jVV85zKvjfbJ8nUyJS/jUSYZ5KQxLUN2ZCvcQc/OmhQl2j6VEL3ezQkNofxn5pQ3SPYWPHb0unTZA==} engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} peerDependencies: @@ -16373,7 +16521,7 @@ packages: '@babel/core': 7.18.5 '@jest/test-sequencer': 28.1.1 '@jest/types': 28.1.1 - '@types/node': 18.0.0 + '@types/node': 18.11.10 babel-jest: 28.1.1_@babel+core@7.18.5 chalk: 4.1.2 ci-info: 3.3.2 @@ -16390,7 +16538,7 @@ packages: jest-validate: 28.1.1 micromatch: 4.0.5 parse-json: 5.2.0 - pretty-format: 28.1.1 + pretty-format: 28.1.3 slash: 3.0.0 strip-json-comments: 3.1.1 transitivePeerDependencies: @@ -16435,7 +16583,7 @@ packages: - supports-color dev: true - /jest-config/29.3.0_@types+node@18.0.0: + /jest-config/29.3.0_@types+node@18.11.10: resolution: {integrity: sha512-sTSDs/M+//njznsytxiBxwfDnSWRb6OqiNSlO/B2iw1HUaa1YLsdWmV4AWLXss1XKzv1F0yVK+kA4XOhZ0I1qQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: @@ -16450,7 +16598,7 @@ packages: '@babel/core': 7.20.2 '@jest/test-sequencer': 29.3.0 '@jest/types': 29.2.1 - '@types/node': 18.0.0 + '@types/node': 18.11.10 babel-jest: 29.3.0_@babel+core@7.20.2 chalk: 4.1.2 ci-info: 3.3.2 @@ -16501,7 +16649,7 @@ packages: chalk: 4.1.2 diff-sequences: 28.1.1 jest-get-type: 28.0.2 - pretty-format: 28.1.1 + pretty-format: 28.1.3 dev: true /jest-diff/29.2.1: @@ -16554,7 +16702,7 @@ packages: chalk: 4.1.2 jest-get-type: 28.0.2 jest-util: 28.1.1 - pretty-format: 28.1.1 + pretty-format: 28.1.3 dev: true /jest-each/29.2.1: @@ -16575,7 +16723,7 @@ packages: '@jest/environment': 27.5.1 '@jest/fake-timers': 27.5.1 '@jest/types': 27.5.1 - '@types/node': 18.0.0 + '@types/node': 18.11.10 jest-mock: 27.5.1 jest-util: 27.5.1 jsdom: 16.7.0 @@ -16594,7 +16742,7 @@ packages: '@jest/fake-timers': 28.1.1 '@jest/types': 28.1.1 '@types/jsdom': 16.2.14 - '@types/node': 17.0.45 + '@types/node': 18.11.10 jest-mock: 28.1.1 jest-util: 28.1.1 jsdom: 19.0.0 @@ -16612,7 +16760,7 @@ packages: '@jest/environment': 27.5.1 '@jest/fake-timers': 27.5.1 '@jest/types': 27.5.1 - '@types/node': 18.0.0 + '@types/node': 18.11.10 jest-mock: 27.5.1 jest-util: 27.5.1 dev: true @@ -16624,7 +16772,7 @@ packages: '@jest/environment': 28.1.1 '@jest/fake-timers': 28.1.1 '@jest/types': 28.1.1 - '@types/node': 18.0.0 + '@types/node': 18.11.10 jest-mock: 28.1.1 jest-util: 28.1.1 dev: true @@ -16636,7 +16784,7 @@ packages: '@jest/environment': 29.3.0 '@jest/fake-timers': 29.3.0 '@jest/types': 29.2.1 - '@types/node': 18.0.0 + '@types/node': 18.11.10 jest-mock: 29.3.0 jest-util: 29.2.1 dev: true @@ -16667,7 +16815,7 @@ packages: dependencies: '@jest/types': 27.5.1 '@types/graceful-fs': 4.1.5 - '@types/node': 18.0.0 + '@types/node': 18.11.10 anymatch: 3.1.2 fb-watchman: 2.0.1 graceful-fs: 4.2.10 @@ -16687,7 +16835,7 @@ packages: dependencies: '@jest/types': 28.1.1 '@types/graceful-fs': 4.1.5 - '@types/node': 18.0.0 + '@types/node': 18.11.10 anymatch: 3.1.2 fb-watchman: 2.0.1 graceful-fs: 4.2.10 @@ -16706,7 +16854,7 @@ packages: dependencies: '@jest/types': 29.2.1 '@types/graceful-fs': 4.1.5 - '@types/node': 18.0.0 + '@types/node': 18.11.10 anymatch: 3.1.2 fb-watchman: 2.0.1 graceful-fs: 4.2.10 @@ -16727,7 +16875,7 @@ packages: '@jest/source-map': 27.5.1 '@jest/test-result': 27.5.1 '@jest/types': 27.5.1 - '@types/node': 18.0.0 + '@types/node': 18.11.10 chalk: 4.1.2 co: 4.6.0 expect: 27.5.1 @@ -16757,7 +16905,7 @@ packages: engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: jest-get-type: 28.0.2 - pretty-format: 28.1.1 + pretty-format: 28.1.3 dev: true /jest-leak-detector/29.2.1: @@ -16785,7 +16933,7 @@ packages: chalk: 4.1.2 jest-diff: 28.1.1 jest-get-type: 28.0.2 - pretty-format: 28.1.1 + pretty-format: 28.1.3 dev: true /jest-matcher-utils/29.2.2: @@ -16823,7 +16971,7 @@ packages: chalk: 4.1.2 graceful-fs: 4.2.10 micromatch: 4.0.5 - pretty-format: 28.1.1 + pretty-format: 28.1.3 slash: 3.0.0 stack-utils: 2.0.5 dev: true @@ -16863,7 +17011,7 @@ packages: engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} dependencies: '@jest/types': 27.5.1 - '@types/node': 18.0.0 + '@types/node': 18.11.10 dev: true /jest-mock/28.1.1: @@ -16871,7 +17019,7 @@ packages: engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: '@jest/types': 28.1.1 - '@types/node': 18.0.0 + '@types/node': 18.11.10 dev: true /jest-mock/28.1.3: @@ -16879,7 +17027,7 @@ packages: engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: '@jest/types': 28.1.3 - '@types/node': 18.0.0 + '@types/node': 18.11.10 dev: true /jest-mock/29.3.0: @@ -16887,7 +17035,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.2.1 - '@types/node': 18.0.0 + '@types/node': 18.11.10 jest-util: 29.2.1 dev: true @@ -17028,7 +17176,7 @@ packages: '@jest/test-result': 27.5.1 '@jest/transform': 27.5.1 '@jest/types': 27.5.1 - '@types/node': 18.0.0 + '@types/node': 18.11.10 chalk: 4.1.2 emittery: 0.8.1 graceful-fs: 4.2.10 @@ -17060,7 +17208,7 @@ packages: '@jest/test-result': 28.1.1 '@jest/transform': 28.1.1 '@jest/types': 28.1.1 - '@types/node': 18.0.0 + '@types/node': 18.11.10 chalk: 4.1.2 emittery: 0.10.2 graceful-fs: 4.2.10 @@ -17089,7 +17237,7 @@ packages: '@jest/test-result': 29.2.1 '@jest/transform': 29.3.0 '@jest/types': 29.2.1 - '@types/node': 18.0.0 + '@types/node': 18.11.10 chalk: 4.1.2 emittery: 0.13.1 graceful-fs: 4.2.10 @@ -17180,7 +17328,7 @@ packages: '@jest/test-result': 29.2.1 '@jest/transform': 29.3.0 '@jest/types': 29.2.1 - '@types/node': 18.0.0 + '@types/node': 18.11.10 chalk: 4.1.2 cjs-module-lexer: 1.2.2 collect-v8-coverage: 1.0.1 @@ -17203,7 +17351,7 @@ packages: resolution: {integrity: sha512-jZCyo6iIxO1aqUxpuBlwTDMkzOAJS4a3eYz3YzgxxVQFwLeSA7Jfq5cbqCY+JLvTDrWirgusI/0KwxKMgrdf7w==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} dependencies: - '@types/node': 18.0.0 + '@types/node': 18.11.10 graceful-fs: 4.2.10 dev: true @@ -17262,7 +17410,7 @@ packages: jest-message-util: 28.1.1 jest-util: 28.1.1 natural-compare: 1.4.0 - pretty-format: 28.1.1 + pretty-format: 28.1.3 semver: 7.3.7 transitivePeerDependencies: - supports-color @@ -17305,7 +17453,7 @@ packages: engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} dependencies: '@jest/types': 27.5.1 - '@types/node': 18.0.0 + '@types/node': 18.11.10 chalk: 4.1.2 ci-info: 3.3.2 graceful-fs: 4.2.10 @@ -17317,7 +17465,7 @@ packages: engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: '@jest/types': 28.1.1 - '@types/node': 18.0.0 + '@types/node': 18.11.10 chalk: 4.1.2 ci-info: 3.3.2 graceful-fs: 4.2.10 @@ -17329,7 +17477,7 @@ packages: engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: '@jest/types': 28.1.3 - '@types/node': 18.0.0 + '@types/node': 18.11.10 chalk: 4.1.2 ci-info: 3.3.2 graceful-fs: 4.2.10 @@ -17341,7 +17489,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.2.1 - '@types/node': 18.0.0 + '@types/node': 18.11.10 chalk: 4.1.2 ci-info: 3.3.2 graceful-fs: 4.2.10 @@ -17369,7 +17517,7 @@ packages: chalk: 4.1.2 jest-get-type: 28.0.2 leven: 3.1.0 - pretty-format: 28.1.1 + pretty-format: 28.1.3 dev: true /jest-validate/29.2.2: @@ -17406,7 +17554,7 @@ packages: dependencies: '@jest/test-result': 27.5.1 '@jest/types': 27.5.1 - '@types/node': 18.0.0 + '@types/node': 18.11.10 ansi-escapes: 4.3.2 chalk: 4.1.2 jest-util: 27.5.1 @@ -17419,7 +17567,7 @@ packages: dependencies: '@jest/test-result': 28.1.1 '@jest/types': 28.1.1 - '@types/node': 18.0.0 + '@types/node': 18.11.10 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.10.2 @@ -17433,7 +17581,7 @@ packages: dependencies: '@jest/test-result': 28.1.1 '@jest/types': 28.1.1 - '@types/node': 18.0.0 + '@types/node': 18.11.10 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.10.2 @@ -17447,7 +17595,7 @@ packages: dependencies: '@jest/test-result': 29.2.1 '@jest/types': 29.2.1 - '@types/node': 18.0.0 + '@types/node': 18.11.10 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 @@ -17459,7 +17607,7 @@ packages: resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} engines: {node: '>= 10.13.0'} dependencies: - '@types/node': 18.0.0 + '@types/node': 18.11.10 merge-stream: 2.0.0 supports-color: 8.1.1 dev: true @@ -17468,7 +17616,7 @@ packages: resolution: {integrity: sha512-Au7slXB08C6h+xbJPp7VIb6U0XX5Kc9uel/WFc6/rcTzGiaVCBRngBExSYuXSLFPULPSYU3cJ3ybS988lNFQhQ==} engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: - '@types/node': 18.0.0 + '@types/node': 18.11.10 merge-stream: 2.0.0 supports-color: 8.1.1 dev: true @@ -17477,7 +17625,7 @@ packages: resolution: {integrity: sha512-rP8LYClB5NCWW0p8GdQT9vRmZNrDmjypklEYZuGCIU5iNviVWCZK5MILS3rQwD0FY1u96bY7b+KoU17DdZy6Ww==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@types/node': 18.0.0 + '@types/node': 18.11.10 jest-util: 29.2.1 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -17570,6 +17718,9 @@ packages: resolution: {integrity: sha512-wLe+lJHeG8Xt6uEubS4x0LVjS/3kXXu9dGoj9BNnlhYq7Kts0Pbb2pvv5KiI0yaKH/eaiR0LUOBhOVo9ktd05A==} dev: false + /jose/4.11.1: + resolution: {integrity: sha512-YRv4Tk/Wlug8qicwqFNFVEZSdbROCHRAC6qu/i0dyNKr5JQdoa2pIGoS04lLO/jXQX7Z9omoNewYIVIxqZBd9Q==} + /js-beautify/1.14.4: resolution: {integrity: sha512-+b4A9c3glceZEmxyIbxDOYB0ZJdReLvyU1077RqKsO4dZx9FUHjTOJn8VHwpg33QoucIykOiYbh7MfqBOghnrA==} engines: {node: '>=10'} @@ -19449,7 +19600,6 @@ packages: /oauth4webapi/2.0.4: resolution: {integrity: sha512-d6NmQuOlCo6+HzNPG70Pl8T4WnHo/XPvQ3Dxus3fRvRjFmt9H+BggI/APyzQ4/jlcdIjPaOw81wIO6WkRGKfkg==} - dev: true /object-assign/4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} @@ -19458,7 +19608,6 @@ packages: /object-hash/2.2.0: resolution: {integrity: sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==} engines: {node: '>= 6'} - dev: false /object-hash/3.0.0: resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} @@ -19512,7 +19661,6 @@ packages: /oidc-token-hash/5.0.1: resolution: {integrity: sha512-EvoOtz6FIEBzE+9q253HsLCVRiK/0doEJ2HCvvqMQb3dHZrP3WlJKYtJ55CRTw4jmYomzH4wkPuCj/I3ZvpKxQ==} engines: {node: ^10.13.0 || >=12.0.0} - dev: false /on-finished/2.3.0: resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==} @@ -19591,11 +19739,10 @@ packages: resolution: {integrity: sha512-HTFaXWdUHvLFw4GaEMgC0jXYBgpjgzQQNHW1pZsSqJorSgrXzxJ+4u/LWCGaClDEse5HLjXRV+zU5Bn3OefiZw==} engines: {node: ^12.19.0 || ^14.15.0 || ^16.13.0} dependencies: - jose: 4.11.0 + jose: 4.11.1 lru-cache: 6.0.0 object-hash: 2.2.0 oidc-token-hash: 5.0.1 - dev: false /optionator/0.8.3: resolution: {integrity: sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==} @@ -20034,6 +20181,40 @@ packages: postcss-value-parser: 4.2.0 dev: true + /postcss-calc/8.2.4_postcss@8.4.19: + resolution: {integrity: sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==} + peerDependencies: + postcss: ^8.2.2 + dependencies: + postcss: 8.4.19 + postcss-selector-parser: 6.0.10 + postcss-value-parser: 4.2.0 + dev: true + + /postcss-cli/10.1.0_postcss@8.4.19: + resolution: {integrity: sha512-Zu7PLORkE9YwNdvOeOVKPmWghprOtjFQU3srMUGbdz3pHJiFh7yZ4geiZFMkjMfB0mtTFR3h8RemR62rPkbOPA==} + engines: {node: '>=14'} + hasBin: true + peerDependencies: + postcss: ^8.0.0 + dependencies: + chokidar: 3.5.3 + dependency-graph: 0.11.0 + fs-extra: 11.1.0 + get-stdin: 9.0.0 + globby: 13.1.2 + picocolors: 1.0.0 + postcss: 8.4.19 + postcss-load-config: 4.0.1_postcss@8.4.19 + postcss-reporter: 7.0.5_postcss@8.4.19 + pretty-hrtime: 1.0.3 + read-cache: 1.0.0 + slash: 5.0.0 + yargs: 17.5.1 + transitivePeerDependencies: + - ts-node + dev: true + /postcss-cli/9.1.0_postcss@8.4.14: resolution: {integrity: sha512-zvDN2ADbWfza42sAnj+O2uUWyL0eRL1V+6giM2vi4SqTR3gTYy8XzcpfwccayF2szcUif0HMmXiEaDv9iEhcpw==} engines: {node: '>=12'} @@ -20064,24 +20245,48 @@ packages: peerDependencies: postcss: ^8.2.15 dependencies: - browserslist: 4.21.0 + browserslist: 4.21.4 caniuse-api: 3.0.0 colord: 2.9.2 postcss: 8.4.14 postcss-value-parser: 4.2.0 dev: true + /postcss-colormin/5.3.0_postcss@8.4.19: + resolution: {integrity: sha512-WdDO4gOFG2Z8n4P8TWBpshnL3JpmNmJwdnfP2gbk2qBA8PWwOYcmjmI/t3CmMeL72a7Hkd+x/Mg9O2/0rD54Pg==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + browserslist: 4.21.4 + caniuse-api: 3.0.0 + colord: 2.9.2 + postcss: 8.4.19 + postcss-value-parser: 4.2.0 + dev: true + /postcss-convert-values/5.1.2_postcss@8.4.14: resolution: {integrity: sha512-c6Hzc4GAv95B7suy4udszX9Zy4ETyMCgFPUDtWjdFTKH1SE9eFY/jEpHSwTH1QPuwxHpWslhckUQWbNRM4ho5g==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - browserslist: 4.21.0 + browserslist: 4.21.4 postcss: 8.4.14 postcss-value-parser: 4.2.0 dev: true + /postcss-convert-values/5.1.3_postcss@8.4.19: + resolution: {integrity: sha512-82pC1xkJZtcJEfiLw6UXnXVXScgtBrjlO5CBmuDQc+dlb88ZYheFsjTn40+zBVi3DkfF7iezO0nJUPLcJK3pvA==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + browserslist: 4.21.4 + postcss: 8.4.19 + postcss-value-parser: 4.2.0 + dev: true + /postcss-discard-comments/5.1.2_postcss@8.4.14: resolution: {integrity: sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==} engines: {node: ^10 || ^12 || >=14.0} @@ -20091,6 +20296,15 @@ packages: postcss: 8.4.14 dev: true + /postcss-discard-comments/5.1.2_postcss@8.4.19: + resolution: {integrity: sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.19 + dev: true + /postcss-discard-duplicates/5.1.0_postcss@8.4.14: resolution: {integrity: sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==} engines: {node: ^10 || ^12 || >=14.0} @@ -20100,6 +20314,15 @@ packages: postcss: 8.4.14 dev: true + /postcss-discard-duplicates/5.1.0_postcss@8.4.19: + resolution: {integrity: sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.19 + dev: true + /postcss-discard-empty/5.1.1_postcss@8.4.14: resolution: {integrity: sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==} engines: {node: ^10 || ^12 || >=14.0} @@ -20109,6 +20332,15 @@ packages: postcss: 8.4.14 dev: true + /postcss-discard-empty/5.1.1_postcss@8.4.19: + resolution: {integrity: sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.19 + dev: true + /postcss-discard-overridden/5.1.0_postcss@8.4.14: resolution: {integrity: sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==} engines: {node: ^10 || ^12 || >=14.0} @@ -20118,13 +20350,22 @@ packages: postcss: 8.4.14 dev: true - /postcss-discard-unused/5.1.0_postcss@8.4.14: + /postcss-discard-overridden/5.1.0_postcss@8.4.19: + resolution: {integrity: sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.19 + dev: true + + /postcss-discard-unused/5.1.0_postcss@8.4.19: resolution: {integrity: sha512-KwLWymI9hbwXmJa0dkrzpRbSJEh0vVUd7r8t0yOGPcfKzyJJxFM8kLyC5Ev9avji6nY95pOp1W6HqIrfT+0VGw==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.14 + postcss: 8.4.19 postcss-selector-parser: 6.0.10 dev: true @@ -20145,6 +20386,23 @@ packages: yaml: 1.10.2 dev: true + /postcss-load-config/4.0.1_postcss@8.4.19: + resolution: {integrity: sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==} + engines: {node: '>= 14'} + peerDependencies: + postcss: '>=8.0.9' + ts-node: '>=9.0.0' + peerDependenciesMeta: + postcss: + optional: true + ts-node: + optional: true + dependencies: + lilconfig: 2.0.5 + postcss: 8.4.19 + yaml: 2.1.3 + dev: true + /postcss-loader/7.0.0_mepnsno3xmng6eyses4tepu7bu: resolution: {integrity: sha512-IDyttebFzTSY6DI24KuHUcBjbAev1i+RyICoPEWcAstZsj03r533uMXtDn506l6/wlsRYiS5XBdx7TpccCsyUg==} engines: {node: '>= 14.15.0'} @@ -20159,14 +20417,14 @@ packages: webpack: 5.73.0 dev: true - /postcss-merge-idents/5.1.1_postcss@8.4.14: + /postcss-merge-idents/5.1.1_postcss@8.4.19: resolution: {integrity: sha512-pCijL1TREiCoog5nQp7wUe+TUonA2tC2sQ54UGeMmryK3UFGIYKqDyjnqd6RcuI4znFn9hWSLNN8xKE/vWcUQw==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - cssnano-utils: 3.1.0_postcss@8.4.14 - postcss: 8.4.14 + cssnano-utils: 3.1.0_postcss@8.4.19 + postcss: 8.4.19 postcss-value-parser: 4.2.0 dev: true @@ -20181,19 +20439,43 @@ packages: stylehacks: 5.1.0_postcss@8.4.14 dev: true + /postcss-merge-longhand/5.1.7_postcss@8.4.19: + resolution: {integrity: sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.19 + postcss-value-parser: 4.2.0 + stylehacks: 5.1.1_postcss@8.4.19 + dev: true + /postcss-merge-rules/5.1.2_postcss@8.4.14: resolution: {integrity: sha512-zKMUlnw+zYCWoPN6yhPjtcEdlJaMUZ0WyVcxTAmw3lkkN/NDMRkOkiuctQEoWAOvH7twaxUUdvBWl0d4+hifRQ==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - browserslist: 4.21.0 + browserslist: 4.21.4 caniuse-api: 3.0.0 cssnano-utils: 3.1.0_postcss@8.4.14 postcss: 8.4.14 postcss-selector-parser: 6.0.10 dev: true + /postcss-merge-rules/5.1.3_postcss@8.4.19: + resolution: {integrity: sha512-LbLd7uFC00vpOuMvyZop8+vvhnfRGpp2S+IMQKeuOZZapPRY4SMq5ErjQeHbHsjCUgJkRNrlU+LmxsKIqPKQlA==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + browserslist: 4.21.4 + caniuse-api: 3.0.0 + cssnano-utils: 3.1.0_postcss@8.4.19 + postcss: 8.4.19 + postcss-selector-parser: 6.0.10 + dev: true + /postcss-minify-font-values/5.1.0_postcss@8.4.14: resolution: {integrity: sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==} engines: {node: ^10 || ^12 || >=14.0} @@ -20204,6 +20486,16 @@ packages: postcss-value-parser: 4.2.0 dev: true + /postcss-minify-font-values/5.1.0_postcss@8.4.19: + resolution: {integrity: sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.19 + postcss-value-parser: 4.2.0 + dev: true + /postcss-minify-gradients/5.1.1_postcss@8.4.14: resolution: {integrity: sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==} engines: {node: ^10 || ^12 || >=14.0} @@ -20216,18 +20508,42 @@ packages: postcss-value-parser: 4.2.0 dev: true + /postcss-minify-gradients/5.1.1_postcss@8.4.19: + resolution: {integrity: sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + colord: 2.9.2 + cssnano-utils: 3.1.0_postcss@8.4.19 + postcss: 8.4.19 + postcss-value-parser: 4.2.0 + dev: true + /postcss-minify-params/5.1.3_postcss@8.4.14: resolution: {integrity: sha512-bkzpWcjykkqIujNL+EVEPOlLYi/eZ050oImVtHU7b4lFS82jPnsCb44gvC6pxaNt38Els3jWYDHTjHKf0koTgg==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - browserslist: 4.21.0 + browserslist: 4.21.4 cssnano-utils: 3.1.0_postcss@8.4.14 postcss: 8.4.14 postcss-value-parser: 4.2.0 dev: true + /postcss-minify-params/5.1.4_postcss@8.4.19: + resolution: {integrity: sha512-+mePA3MgdmVmv6g+30rn57USjOGSAyuxUmkfiWpzalZ8aiBkdPYjXWtHuwJGm1v5Ojy0Z0LaSYhHaLJQB0P8Jw==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + browserslist: 4.21.4 + cssnano-utils: 3.1.0_postcss@8.4.19 + postcss: 8.4.19 + postcss-value-parser: 4.2.0 + dev: true + /postcss-minify-selectors/5.2.1_postcss@8.4.14: resolution: {integrity: sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg==} engines: {node: ^10 || ^12 || >=14.0} @@ -20238,45 +20554,55 @@ packages: postcss-selector-parser: 6.0.10 dev: true - /postcss-modules-extract-imports/3.0.0_postcss@8.4.14: + /postcss-minify-selectors/5.2.1_postcss@8.4.19: + resolution: {integrity: sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.19 + postcss-selector-parser: 6.0.10 + dev: true + + /postcss-modules-extract-imports/3.0.0_postcss@8.4.19: resolution: {integrity: sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==} engines: {node: ^10 || ^12 || >= 14} peerDependencies: postcss: ^8.1.0 dependencies: - postcss: 8.4.14 + postcss: 8.4.19 dev: true - /postcss-modules-local-by-default/4.0.0_postcss@8.4.14: + /postcss-modules-local-by-default/4.0.0_postcss@8.4.19: resolution: {integrity: sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==} engines: {node: ^10 || ^12 || >= 14} peerDependencies: postcss: ^8.1.0 dependencies: - icss-utils: 5.1.0_postcss@8.4.14 - postcss: 8.4.14 + icss-utils: 5.1.0_postcss@8.4.19 + postcss: 8.4.19 postcss-selector-parser: 6.0.10 postcss-value-parser: 4.2.0 dev: true - /postcss-modules-scope/3.0.0_postcss@8.4.14: + /postcss-modules-scope/3.0.0_postcss@8.4.19: resolution: {integrity: sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==} engines: {node: ^10 || ^12 || >= 14} peerDependencies: postcss: ^8.1.0 dependencies: - postcss: 8.4.14 + postcss: 8.4.19 postcss-selector-parser: 6.0.10 dev: true - /postcss-modules-values/4.0.0_postcss@8.4.14: + /postcss-modules-values/4.0.0_postcss@8.4.19: resolution: {integrity: sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==} engines: {node: ^10 || ^12 || >= 14} peerDependencies: postcss: ^8.1.0 dependencies: - icss-utils: 5.1.0_postcss@8.4.14 - postcss: 8.4.14 + icss-utils: 5.1.0_postcss@8.4.19 + postcss: 8.4.19 dev: true /postcss-nested/5.0.6_postcss@8.4.14: @@ -20289,6 +20615,16 @@ packages: postcss-selector-parser: 6.0.10 dev: true + /postcss-nested/6.0.0_postcss@8.4.19: + resolution: {integrity: sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.2.14 + dependencies: + postcss: 8.4.19 + postcss-selector-parser: 6.0.10 + dev: true + /postcss-normalize-charset/5.1.0_postcss@8.4.14: resolution: {integrity: sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==} engines: {node: ^10 || ^12 || >=14.0} @@ -20298,6 +20634,15 @@ packages: postcss: 8.4.14 dev: true + /postcss-normalize-charset/5.1.0_postcss@8.4.19: + resolution: {integrity: sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.19 + dev: true + /postcss-normalize-display-values/5.1.0_postcss@8.4.14: resolution: {integrity: sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==} engines: {node: ^10 || ^12 || >=14.0} @@ -20308,6 +20653,16 @@ packages: postcss-value-parser: 4.2.0 dev: true + /postcss-normalize-display-values/5.1.0_postcss@8.4.19: + resolution: {integrity: sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.19 + postcss-value-parser: 4.2.0 + dev: true + /postcss-normalize-positions/5.1.1_postcss@8.4.14: resolution: {integrity: sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg==} engines: {node: ^10 || ^12 || >=14.0} @@ -20318,6 +20673,16 @@ packages: postcss-value-parser: 4.2.0 dev: true + /postcss-normalize-positions/5.1.1_postcss@8.4.19: + resolution: {integrity: sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.19 + postcss-value-parser: 4.2.0 + dev: true + /postcss-normalize-repeat-style/5.1.1_postcss@8.4.14: resolution: {integrity: sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g==} engines: {node: ^10 || ^12 || >=14.0} @@ -20328,6 +20693,16 @@ packages: postcss-value-parser: 4.2.0 dev: true + /postcss-normalize-repeat-style/5.1.1_postcss@8.4.19: + resolution: {integrity: sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.19 + postcss-value-parser: 4.2.0 + dev: true + /postcss-normalize-string/5.1.0_postcss@8.4.14: resolution: {integrity: sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==} engines: {node: ^10 || ^12 || >=14.0} @@ -20338,6 +20713,16 @@ packages: postcss-value-parser: 4.2.0 dev: true + /postcss-normalize-string/5.1.0_postcss@8.4.19: + resolution: {integrity: sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.19 + postcss-value-parser: 4.2.0 + dev: true + /postcss-normalize-timing-functions/5.1.0_postcss@8.4.14: resolution: {integrity: sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==} engines: {node: ^10 || ^12 || >=14.0} @@ -20348,17 +20733,38 @@ packages: postcss-value-parser: 4.2.0 dev: true + /postcss-normalize-timing-functions/5.1.0_postcss@8.4.19: + resolution: {integrity: sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.19 + postcss-value-parser: 4.2.0 + dev: true + /postcss-normalize-unicode/5.1.0_postcss@8.4.14: resolution: {integrity: sha512-J6M3MizAAZ2dOdSjy2caayJLQT8E8K9XjLce8AUQMwOrCvjCHv24aLC/Lps1R1ylOfol5VIDMaM/Lo9NGlk1SQ==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - browserslist: 4.21.0 + browserslist: 4.21.4 postcss: 8.4.14 postcss-value-parser: 4.2.0 dev: true + /postcss-normalize-unicode/5.1.1_postcss@8.4.19: + resolution: {integrity: sha512-qnCL5jzkNUmKVhZoENp1mJiGNPcsJCs1aaRmURmeJGES23Z/ajaln+EPTD+rBeNkSryI+2WTdW+lwcVdOikrpA==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + browserslist: 4.21.4 + postcss: 8.4.19 + postcss-value-parser: 4.2.0 + dev: true + /postcss-normalize-url/5.1.0_postcss@8.4.14: resolution: {integrity: sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==} engines: {node: ^10 || ^12 || >=14.0} @@ -20370,6 +20776,17 @@ packages: postcss-value-parser: 4.2.0 dev: true + /postcss-normalize-url/5.1.0_postcss@8.4.19: + resolution: {integrity: sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + normalize-url: 6.1.0 + postcss: 8.4.19 + postcss-value-parser: 4.2.0 + dev: true + /postcss-normalize-whitespace/5.1.1_postcss@8.4.14: resolution: {integrity: sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==} engines: {node: ^10 || ^12 || >=14.0} @@ -20380,6 +20797,16 @@ packages: postcss-value-parser: 4.2.0 dev: true + /postcss-normalize-whitespace/5.1.1_postcss@8.4.19: + resolution: {integrity: sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.19 + postcss-value-parser: 4.2.0 + dev: true + /postcss-ordered-values/5.1.3_postcss@8.4.14: resolution: {integrity: sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ==} engines: {node: ^10 || ^12 || >=14.0} @@ -20391,13 +20818,24 @@ packages: postcss-value-parser: 4.2.0 dev: true - /postcss-reduce-idents/5.2.0_postcss@8.4.14: + /postcss-ordered-values/5.1.3_postcss@8.4.19: + resolution: {integrity: sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + cssnano-utils: 3.1.0_postcss@8.4.19 + postcss: 8.4.19 + postcss-value-parser: 4.2.0 + dev: true + + /postcss-reduce-idents/5.2.0_postcss@8.4.19: resolution: {integrity: sha512-BTrLjICoSB6gxbc58D5mdBK8OhXRDqud/zodYfdSi52qvDHdMwk+9kB9xsM8yJThH/sZU5A6QVSmMmaN001gIg==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.14 + postcss: 8.4.19 postcss-value-parser: 4.2.0 dev: true @@ -20407,11 +20845,22 @@ packages: peerDependencies: postcss: ^8.2.15 dependencies: - browserslist: 4.21.0 + browserslist: 4.21.4 caniuse-api: 3.0.0 postcss: 8.4.14 dev: true + /postcss-reduce-initial/5.1.1_postcss@8.4.19: + resolution: {integrity: sha512-//jeDqWcHPuXGZLoolFrUXBDyuEGbr9S2rMo19bkTIjBQ4PqkaO+oI8wua5BOUxpfi97i3PCoInsiFIEBfkm9w==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + browserslist: 4.21.4 + caniuse-api: 3.0.0 + postcss: 8.4.19 + dev: true + /postcss-reduce-transforms/5.1.0_postcss@8.4.14: resolution: {integrity: sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==} engines: {node: ^10 || ^12 || >=14.0} @@ -20422,6 +20871,16 @@ packages: postcss-value-parser: 4.2.0 dev: true + /postcss-reduce-transforms/5.1.0_postcss@8.4.19: + resolution: {integrity: sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.19 + postcss-value-parser: 4.2.0 + dev: true + /postcss-reporter/7.0.5_postcss@8.4.14: resolution: {integrity: sha512-glWg7VZBilooZGOFPhN9msJ3FQs19Hie7l5a/eE6WglzYqVeH3ong3ShFcp9kDWJT1g2Y/wd59cocf9XxBtkWA==} engines: {node: '>=10'} @@ -20433,6 +20892,17 @@ packages: thenby: 1.3.4 dev: true + /postcss-reporter/7.0.5_postcss@8.4.19: + resolution: {integrity: sha512-glWg7VZBilooZGOFPhN9msJ3FQs19Hie7l5a/eE6WglzYqVeH3ong3ShFcp9kDWJT1g2Y/wd59cocf9XxBtkWA==} + engines: {node: '>=10'} + peerDependencies: + postcss: ^8.1.0 + dependencies: + picocolors: 1.0.0 + postcss: 8.4.19 + thenby: 1.3.4 + dev: true + /postcss-selector-parser/6.0.10: resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==} engines: {node: '>=4'} @@ -20441,13 +20911,13 @@ packages: util-deprecate: 1.0.2 dev: true - /postcss-sort-media-queries/4.2.1_postcss@8.4.14: + /postcss-sort-media-queries/4.2.1_postcss@8.4.19: resolution: {integrity: sha512-9VYekQalFZ3sdgcTjXMa0dDjsfBVHXlraYJEMiOJ/2iMmI2JGCMavP16z3kWOaRu8NSaJCTgVpB/IVpH5yT9YQ==} engines: {node: '>=10.0.0'} peerDependencies: postcss: ^8.4.4 dependencies: - postcss: 8.4.14 + postcss: 8.4.19 sort-css-media-queries: 2.0.4 dev: true @@ -20462,6 +20932,17 @@ packages: svgo: 2.8.0 dev: true + /postcss-svgo/5.1.0_postcss@8.4.19: + resolution: {integrity: sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.19 + postcss-value-parser: 4.2.0 + svgo: 2.8.0 + dev: true + /postcss-unique-selectors/5.1.1_postcss@8.4.14: resolution: {integrity: sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==} engines: {node: ^10 || ^12 || >=14.0} @@ -20472,16 +20953,26 @@ packages: postcss-selector-parser: 6.0.10 dev: true + /postcss-unique-selectors/5.1.1_postcss@8.4.19: + resolution: {integrity: sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.19 + postcss-selector-parser: 6.0.10 + dev: true + /postcss-value-parser/4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - /postcss-zindex/5.1.0_postcss@8.4.14: + /postcss-zindex/5.1.0_postcss@8.4.19: resolution: {integrity: sha512-fgFMf0OtVSBR1va1JNHYgMxYk73yhn/qb4uQDq1DLGYolz8gHCyr/sesEuGUaYs58E3ZJRcpoGuPVoB7Meiq9A==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.14 + postcss: 8.4.19 dev: true /postcss/8.4.14: @@ -20492,6 +20983,15 @@ packages: picocolors: 1.0.0 source-map-js: 1.0.2 + /postcss/8.4.19: + resolution: {integrity: sha512-h+pbPsyhlYj6N2ozBmHhHrs9DzGmbaarbLvWipMRO7RLS+v4onj26MPFXA5OBYFxyqYhUJK456SwDcY9H2/zsA==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.4 + picocolors: 1.0.0 + source-map-js: 1.0.2 + dev: true + /postgres-array/2.0.0: resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} engines: {node: '>=4'} @@ -20693,6 +21193,19 @@ packages: pretty-format: 3.8.0 dev: false + /preact-render-to-string/5.2.3_preact@10.11.3: + resolution: {integrity: sha512-aPDxUn5o3GhWdtJtW0svRC2SS/l8D9MAgo2+AWml+BhDImb27ALf04Q2d+AHqUUOc6RdSXFIBVa2gxzgMKgtZA==} + peerDependencies: + preact: '>=10' + dependencies: + preact: 10.11.3 + pretty-format: 3.8.0 + dev: false + + /preact/10.11.3: + resolution: {integrity: sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==} + dev: false + /preact/10.8.2: resolution: {integrity: sha512-AKGt0BsDSiAYzVS78jZ9qRwuorY2CoSZtf1iOC6gLb/3QyZt+fLT09aYJBjRc/BEcRc4j+j3ggERMdNE43i1LQ==} dev: false @@ -20964,7 +21477,7 @@ packages: '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.0 '@types/long': 4.0.2 - '@types/node': 18.0.0 + '@types/node': 18.11.10 long: 4.0.0 dev: true @@ -20983,7 +21496,7 @@ packages: '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.0 - '@types/node': 18.0.0 + '@types/node': 18.11.10 long: 5.2.1 dev: true @@ -21843,7 +22356,7 @@ packages: dependencies: find-up: 5.0.0 picocolors: 1.0.0 - postcss: 8.4.14 + postcss: 8.4.19 strip-json-comments: 3.1.1 dev: true @@ -22293,6 +22806,11 @@ packages: engines: {node: '>=12'} dev: true + /slash/5.0.0: + resolution: {integrity: sha512-n6KkmvKS0623igEVj3FF0OZs1gYYJ0o0Hj939yc1fyxl2xt+xYpLnzJB6xBSqOfV9ZFLEWodBBN/heZJahuIJQ==} + engines: {node: '>=14.16'} + dev: true + /slice-ansi/4.0.0: resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} engines: {node: '>=10'} @@ -22853,11 +23371,22 @@ packages: peerDependencies: postcss: ^8.2.15 dependencies: - browserslist: 4.21.0 + browserslist: 4.21.4 postcss: 8.4.14 postcss-selector-parser: 6.0.10 dev: true + /stylehacks/5.1.1_postcss@8.4.19: + resolution: {integrity: sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + browserslist: 4.21.4 + postcss: 8.4.19 + postcss-selector-parser: 6.0.10 + dev: true + /stylis/4.1.1: resolution: {integrity: sha512-lVrM/bNdhVX2OgBFNa2YJ9Lxj7kPzylieHd3TNjuGE0Re9JB7joL5VUKOVH1kdNNJTgGPpT8hmwIAPLaSyEVFQ==} dev: false @@ -24739,7 +25268,7 @@ packages: /wkx/0.5.0: resolution: {integrity: sha512-Xng/d4Ichh8uN4l0FToV/258EjMGU9MGcA0HV2d9B/ZpZB3lqQm7nkOdZdm5GhKtLLhAE7PiVQwN4eN+2YJJUg==} dependencies: - '@types/node': 18.0.0 + '@types/node': 18.11.10 dev: true /word-wrap/1.2.3: @@ -24898,6 +25427,11 @@ packages: engines: {node: '>= 6'} dev: true + /yaml/2.1.3: + resolution: {integrity: sha512-AacA8nRULjKMX2DvWvOAdBZMOfQlypSFkjcOcu9FalllIDJ1kvlREzcdIZmidQUqqeMv7jorHjq2HlLv/+c2lg==} + engines: {node: '>= 14'} + dev: true + /yargs-parser/20.2.9: resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} engines: {node: '>=10'} From 166d4a8f7715b8943d1e2141a18294c0743adc2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Tue, 6 Dec 2022 17:03:54 +0100 Subject: [PATCH 45/93] support OIDC --- apps/dev/pages/api/auth/[...nextauth].ts | 3 +- .../core/src/lib/oauth/authorization-url.ts | 5 +- packages/core/src/lib/oauth/callback.ts | 51 ++++++++++++------- packages/core/src/lib/oauth/nonce-handler.ts | 2 +- packages/core/src/lib/oauth/pkce-handler.ts | 2 +- packages/core/src/lib/oauth/state-handler.ts | 2 +- packages/core/src/lib/providers.ts | 1 + packages/core/src/types.ts | 7 +-- packages/next-auth/src/providers/auth0.ts | 2 +- 9 files changed, 48 insertions(+), 27 deletions(-) diff --git a/apps/dev/pages/api/auth/[...nextauth].ts b/apps/dev/pages/api/auth/[...nextauth].ts index 4dc82b6e10..960da72cb6 100644 --- a/apps/dev/pages/api/auth/[...nextauth].ts +++ b/apps/dev/pages/api/auth/[...nextauth].ts @@ -69,7 +69,7 @@ import GitHub from "next-auth/providers/github" export const authOptions: AuthOptions = { // adapter, - debug: process.env.NODE_ENV !== "production", + // debug: process.env.NODE_ENV !== "production", theme: { logo: "https://next-auth.js.org/img/logo/logo-sm.png", brandColor: "#1786fb", @@ -97,7 +97,6 @@ export const authOptions: AuthOptions = { // Facebook({ clientId: process.env.FACEBOOK_ID, clientSecret: process.env.FACEBOOK_SECRET }), // Foursquare({ clientId: process.env.FOURSQUARE_ID, clientSecret: process.env.FOURSQUARE_SECRET }), // Freshbooks({ clientId: process.env.FRESHBOOKS_ID, clientSecret: process.env.FRESHBOOKS_SECRET }), - GitHub({ clientId: process.env.GITHUB_ID, clientSecret: process.env.GITHUB_SECRET }), // Gitlab({ clientId: process.env.GITLAB_ID, clientSecret: process.env.GITLAB_SECRET }), // Google({ clientId: process.env.GOOGLE_ID, clientSecret: process.env.GOOGLE_SECRET }), diff --git a/packages/core/src/lib/oauth/authorization-url.ts b/packages/core/src/lib/oauth/authorization-url.ts index 9cac65cfc4..3214f43c38 100644 --- a/packages/core/src/lib/oauth/authorization-url.ts +++ b/packages/core/src/lib/oauth/authorization-url.ts @@ -55,8 +55,10 @@ export async function getAuthorizationUrl({ // clientId can technically be undefined, should we check this in assert.ts or rely on the Authorization Server to do it? client_id: provider.clientId, redirect_uri: provider.callbackUrl, + // @ts-expect-error TODO: + ...(provider.authorization.params ?? {}), }, // Defaults - Object.fromEntries(authParams.entries()), // From provider config + Object.fromEntries(authParams), // From provider config query // From `signIn` call ) @@ -89,6 +91,7 @@ export async function getAuthorizationUrl({ cookies.push(nonce) } + url.searchParams.delete("nextauth") logger.debug("GET_AUTHORIZATION_URL", { url, cookies, provider }) return { redirect: url, cookies } } diff --git a/packages/core/src/lib/oauth/callback.ts b/packages/core/src/lib/oauth/callback.ts index 4dccc4123c..d82e4080c9 100644 --- a/packages/core/src/lib/oauth/callback.ts +++ b/packages/core/src/lib/oauth/callback.ts @@ -104,41 +104,58 @@ export async function handleOAuthCallback(params: { throw new Error("TODO: Handle OAuth 2.0 redirect error") } - const response = await o.authorizationCodeGrantRequest( + const codeGrantResponse = await o.authorizationCodeGrantRequest( as, client, parameters, provider.callbackUrl, - codeVerifier?.codeVerifier ?? "yooo" // TODO: review fallback code verifier + codeVerifier?.codeVerifier ?? "auth" // TODO: review fallback code verifier ) let challenges: o.WWWAuthenticateChallenge[] | undefined - if ((challenges = o.parseWwwAuthenticateChallenges(response))) { + if ((challenges = o.parseWwwAuthenticateChallenges(codeGrantResponse))) { for (const challenge of challenges) { console.log("challenge", challenge) } throw new Error("TODO: Handle www-authenticate challenges as needed") } - const tokens = await o.processAuthorizationCodeOAuth2Response( - as, - client, - response - ) - if (o.isOAuth2Error(tokens)) { - console.log("error", tokens) - throw new Error("TODO: Handle OAuth 2.0 response body error") - } - let profile: Profile = {} + let tokens: TokenSet + + if (provider.idToken) { + const result = await o.processAuthorizationCodeOpenIDResponse( + as, + client, + codeGrantResponse + ) + + if (o.isOAuth2Error(result)) { + console.log("error", result) + throw new Error("TODO: Handle OIDC response body error") + } - if (provider.userinfo?.url) { - const userinfoResponse = await o.userInfoRequest( + profile = o.getValidatedIdTokenClaims(result) + tokens = result + } else { + tokens = await o.processAuthorizationCodeOAuth2Response( as, client, - tokens.access_token + codeGrantResponse ) - profile = await userinfoResponse.json() + if (o.isOAuth2Error(tokens as any)) { + console.log("error", tokens) + throw new Error("TODO: Handle OAuth 2.0 response body error") + } + + if (provider.userinfo?.url) { + const userinfoResponse = await o.userInfoRequest( + as, + client, + (tokens as any).access_token + ) + profile = await userinfoResponse.json() + } } const profileResult = await getProfile({ diff --git a/packages/core/src/lib/oauth/nonce-handler.ts b/packages/core/src/lib/oauth/nonce-handler.ts index 32e17233ac..1096ae2b1f 100644 --- a/packages/core/src/lib/oauth/nonce-handler.ts +++ b/packages/core/src/lib/oauth/nonce-handler.ts @@ -66,7 +66,7 @@ export async function useNonce( const value = (await jwt.decode({ ...options.jwt, token: nonce })) as any return { - value: value?.nonce ?? undefined, + value: value?.value ?? undefined, cookie: { name: cookies.nonce.name, value: "", diff --git a/packages/core/src/lib/oauth/pkce-handler.ts b/packages/core/src/lib/oauth/pkce-handler.ts index 24c7f70931..ab003b8424 100644 --- a/packages/core/src/lib/oauth/pkce-handler.ts +++ b/packages/core/src/lib/oauth/pkce-handler.ts @@ -77,7 +77,7 @@ export async function usePKCECodeVerifier( })) as any return { - codeVerifier: pkce?.code_verifier ?? undefined, + codeVerifier: pkce?.value ?? undefined, cookie: { name: cookies.pkceCodeVerifier.name, value: "", diff --git a/packages/core/src/lib/oauth/state-handler.ts b/packages/core/src/lib/oauth/state-handler.ts index e0fbb1cd1a..548970cd73 100644 --- a/packages/core/src/lib/oauth/state-handler.ts +++ b/packages/core/src/lib/oauth/state-handler.ts @@ -53,7 +53,7 @@ export async function useState( const value = (await jwt.decode({ ...options.jwt, token: state })) as any return { - value: value?.state ?? undefined, + value: value?.value ?? undefined, cookie: { name: cookies.state.name, value: "", diff --git a/packages/core/src/lib/providers.ts b/packages/core/src/lib/providers.ts index f1590b0082..1c1a688658 100644 --- a/packages/core/src/lib/providers.ts +++ b/packages/core/src/lib/providers.ts @@ -58,6 +58,7 @@ function normalizeOAuth( const hasIssuer = !!c.issuer const authorization = normalizeEndpoint(c.authorization, hasIssuer) + // TODO: deprecate OAuth 1.0 support if (!c.version?.startsWith("1.")) { // Set default check to state c.checks ??= ["pkce"] diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index dab8e8e757..ff9f8fa85f 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -238,14 +238,15 @@ export interface Theme { * Some of them are available with different casing, * but they refer to the same value. */ -export type TokenSet = Partial & - Partial +export type TokenSet = Partial< + OAuth2TokenEndpointResponse | OpenIDTokenEndpointResponse +> /** * Usually contains information about the provider being used * and also extends `TokenSet`, which is different tokens returned by OAuth Providers. */ -export interface Account extends TokenSet { +export interface Account extends Partial { /** * This value depends on the type of the provider being used to create the account. * - oauth: The OAuth account's id, returned from the `profile()` callback. diff --git a/packages/next-auth/src/providers/auth0.ts b/packages/next-auth/src/providers/auth0.ts index cb5e02d592..ca6bede147 100644 --- a/packages/next-auth/src/providers/auth0.ts +++ b/packages/next-auth/src/providers/auth0.ts @@ -16,7 +16,7 @@ export default function Auth0

( wellKnown: `${options.issuer}/.well-known/openid-configuration`, type: "oauth", authorization: { params: { scope: "openid email profile" } }, - checks: ["pkce", "state"], + checks: ["pkce"], idToken: true, profile(profile) { return { From cc098bd73a9ff11727bb218b04b877114debb500 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Tue, 6 Dec 2022 17:47:21 +0100 Subject: [PATCH 46/93] remove `openid-client` --- packages/core/package.json | 1 - packages/core/src/lib/oauth/client.ts | 45 --------------------------- packages/core/src/providers/oauth.ts | 23 ++++++-------- pnpm-lock.yaml | 6 ++-- 4 files changed, 14 insertions(+), 61 deletions(-) delete mode 100644 packages/core/src/lib/oauth/client.ts diff --git a/packages/core/package.json b/packages/core/package.json index 3ae629298e..1467dab5b8 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -45,7 +45,6 @@ "@types/react": "18.0.26", "autoprefixer": "10.4.13", "cssnano": "5.1.14", - "openid-client": "^5.1.0", "postcss": "8.4.19", "postcss-cli": "10.1.0", "postcss-nested": "6.0.0" diff --git a/packages/core/src/lib/oauth/client.ts b/packages/core/src/lib/oauth/client.ts deleted file mode 100644 index 3f93f66b72..0000000000 --- a/packages/core/src/lib/oauth/client.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { custom, Issuer, type Client } from "openid-client" - -import type { InternalOptions } from "../.." - -/** Node.js reliant client supporting OAuth 2.x and OIDC */ -export async function openidClient( - options: InternalOptions<"oauth"> -): Promise { - const provider = options.provider - - if (provider.httpOptions) custom.setHttpOptionsDefaults(provider.httpOptions) - - let issuer: Issuer - if (provider.wellKnown) { - issuer = await Issuer.discover(provider.wellKnown) - } else { - issuer = new Issuer({ - // We verify that either `issuer` or the other endpoints - // are always defined in assert.ts - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - issuer: provider.issuer!, - authorization_endpoint: provider.authorization?.url.toString(), - token_endpoint: provider.token?.url.toString(), - userinfo_endpoint: provider.userinfo?.url.toString(), - }) - } - - const client = new issuer.Client( - { - // clientId can technically be undefined, should we check this in assert.ts or rely on the Authorization Server to do it? - client_id: provider.clientId, - client_secret: provider.clientSecret as string, - redirect_uris: [provider.callbackUrl], - ...provider.client, - }, - provider.jwks - ) - - // allow a 10 second skew - // See https://github.com/nextauthjs/next-auth/issues/3032 - // and https://github.com/nextauthjs/next-auth/issues/3067 - client[custom.clock_tolerance] = 10 - - return client -} diff --git a/packages/core/src/providers/oauth.ts b/packages/core/src/providers/oauth.ts index 4a46ee99d6..652d41e2a8 100644 --- a/packages/core/src/providers/oauth.ts +++ b/packages/core/src/providers/oauth.ts @@ -1,18 +1,17 @@ import type { CommonProviderOptions } from "../providers" import type { Profile, TokenSet, User, Awaitable } from ".." -import type { - AuthorizationParameters, - CallbackParamsType, - Issuer, - ClientMetadata, - IssuerMetadata, - OAuthCallbackChecks, - OpenIDCallbackChecks, - HttpOptions, -} from "openid-client" import type { JWK } from "jose" +// TODO: +type AuthorizationParameters = any +type CallbackParamsType = any +type Issuer = any +type ClientMetadata = any +type IssuerMetadata = any +type OAuthCallbackChecks = any +type OpenIDCallbackChecks = any + type Client = InstanceType export type { OAuthProviderType } from "./oauth-types" @@ -27,7 +26,7 @@ type UrlParams = Record type EndpointRequest = ( context: C & { - /** `openid-client` Client */ + /** `oauth4webapi` Client. TODO: */ client: Client /** Provider is passed for convenience, ans also contains the `callbackUrl`. */ provider: (OAuthConfig

| OAuthConfigInternal

) & { @@ -144,8 +143,6 @@ export interface OAuthConfig

extends CommonProviderOptions, PartialIssuer { region?: string // TODO: only allow for some issuer?: string - /** Read more at: https://github.com/panva/node-openid-client/tree/main/docs#customizing-http-requests */ - httpOptions?: HttpOptions style?: OAuthProviderButtonStyles diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2889dd3906..4d6df67088 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -449,7 +449,6 @@ importers: cssnano: 5.1.14 jose: 4.11.1 oauth4webapi: 2.0.4 - openid-client: ^5.1.0 postcss: 8.4.19 postcss-cli: 10.1.0 postcss-nested: 6.0.0 @@ -471,7 +470,6 @@ importers: '@types/react': 18.0.26 autoprefixer: 10.4.13_postcss@8.4.19 cssnano: 5.1.14_postcss@8.4.19 - openid-client: 5.1.6 postcss: 8.4.19 postcss-cli: 10.1.0_postcss@8.4.19 postcss-nested: 6.0.0_postcss@8.4.19 @@ -17720,6 +17718,7 @@ packages: /jose/4.11.1: resolution: {integrity: sha512-YRv4Tk/Wlug8qicwqFNFVEZSdbROCHRAC6qu/i0dyNKr5JQdoa2pIGoS04lLO/jXQX7Z9omoNewYIVIxqZBd9Q==} + dev: false /js-beautify/1.14.4: resolution: {integrity: sha512-+b4A9c3glceZEmxyIbxDOYB0ZJdReLvyU1077RqKsO4dZx9FUHjTOJn8VHwpg33QoucIykOiYbh7MfqBOghnrA==} @@ -19608,6 +19607,7 @@ packages: /object-hash/2.2.0: resolution: {integrity: sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==} engines: {node: '>= 6'} + dev: false /object-hash/3.0.0: resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} @@ -19661,6 +19661,7 @@ packages: /oidc-token-hash/5.0.1: resolution: {integrity: sha512-EvoOtz6FIEBzE+9q253HsLCVRiK/0doEJ2HCvvqMQb3dHZrP3WlJKYtJ55CRTw4jmYomzH4wkPuCj/I3ZvpKxQ==} engines: {node: ^10.13.0 || >=12.0.0} + dev: false /on-finished/2.3.0: resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==} @@ -19743,6 +19744,7 @@ packages: lru-cache: 6.0.0 object-hash: 2.2.0 oidc-token-hash: 5.0.1 + dev: false /optionator/0.8.3: resolution: {integrity: sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==} From 808d9a27e511762fd99cfb4887433ccce59a4df1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Wed, 7 Dec 2022 12:19:09 +0100 Subject: [PATCH 47/93] temprarily remove duplicate logos --- packages/core/src/pages/signin.tsx | 32 +-- .../core/src/providers/logos/apple-dark.svg | 4 - packages/core/src/providers/logos/apple.svg | 4 - .../src/providers/logos/atlassian-dark.svg | 8 - .../core/src/providers/logos/atlassian.svg | 4 - .../core/src/providers/logos/auth0-dark.svg | 12 - packages/core/src/providers/logos/auth0.svg | 3 - .../core/src/providers/logos/azure-dark.svg | 3 - packages/core/src/providers/logos/azure.svg | 3 - .../src/providers/logos/battlenet-dark.svg | 3 - .../core/src/providers/logos/battlenet.svg | 3 - .../core/src/providers/logos/box-dark.svg | 6 - packages/core/src/providers/logos/box.svg | 6 - packages/core/src/providers/logos/cognito.svg | 10 - .../core/src/providers/logos/discord-dark.svg | 3 - packages/core/src/providers/logos/discord.svg | 3 - .../src/providers/logos/facebook-dark.svg | 4 - .../core/src/providers/logos/facebook.svg | 8 - .../src/providers/logos/foursquare-dark.svg | 18 -- .../core/src/providers/logos/foursquare.svg | 18 -- .../src/providers/logos/freshbooks-dark.svg | 3 - .../core/src/providers/logos/freshbooks.svg | 3 - .../core/src/providers/logos/github-dark.svg | 4 - packages/core/src/providers/logos/github.svg | 4 - .../core/src/providers/logos/gitlab-dark.svg | 8 - packages/core/src/providers/logos/gitlab.svg | 11 - packages/core/src/providers/logos/google.svg | 7 - .../core/src/providers/logos/hubspot-dark.svg | 3 - packages/core/src/providers/logos/hubspot.svg | 3 - .../core/src/providers/logos/instagram.svg | 15 - .../core/src/providers/logos/keycloak.svg | 260 ------------------ packages/core/src/providers/logos/line.svg | 6 - .../src/providers/logos/linkedin-dark.svg | 6 - .../core/src/providers/logos/linkedin.svg | 6 - .../src/providers/logos/mailchimp-dark.svg | 3 - .../core/src/providers/logos/mailchimp.svg | 3 - .../core/src/providers/logos/okta-dark.svg | 3 - packages/core/src/providers/logos/okta.svg | 3 - packages/core/src/providers/logos/patreon.svg | 4 - packages/core/src/providers/logos/slack.svg | 8 - packages/core/src/providers/logos/spotify.svg | 4 - packages/core/src/providers/logos/todoist.svg | 8 - .../core/src/providers/logos/trakt-dark.svg | 17 -- packages/core/src/providers/logos/trakt.svg | 18 -- .../core/src/providers/logos/twitch-dark.svg | 4 - packages/core/src/providers/logos/twitch.svg | 4 - .../core/src/providers/logos/twitter-dark.svg | 3 - packages/core/src/providers/logos/twitter.svg | 3 - packages/core/src/providers/logos/vk-dark.svg | 4 - packages/core/src/providers/logos/vk.svg | 4 - .../src/providers/logos/wikimedia-dark.svg | 10 - .../core/src/providers/logos/wikimedia.svg | 10 - .../core/src/providers/logos/workos-dark.svg | 4 - packages/core/src/providers/logos/workos.svg | 4 - 54 files changed, 16 insertions(+), 599 deletions(-) delete mode 100644 packages/core/src/providers/logos/apple-dark.svg delete mode 100644 packages/core/src/providers/logos/apple.svg delete mode 100644 packages/core/src/providers/logos/atlassian-dark.svg delete mode 100644 packages/core/src/providers/logos/atlassian.svg delete mode 100644 packages/core/src/providers/logos/auth0-dark.svg delete mode 100644 packages/core/src/providers/logos/auth0.svg delete mode 100644 packages/core/src/providers/logos/azure-dark.svg delete mode 100644 packages/core/src/providers/logos/azure.svg delete mode 100644 packages/core/src/providers/logos/battlenet-dark.svg delete mode 100644 packages/core/src/providers/logos/battlenet.svg delete mode 100644 packages/core/src/providers/logos/box-dark.svg delete mode 100644 packages/core/src/providers/logos/box.svg delete mode 100644 packages/core/src/providers/logos/cognito.svg delete mode 100644 packages/core/src/providers/logos/discord-dark.svg delete mode 100644 packages/core/src/providers/logos/discord.svg delete mode 100644 packages/core/src/providers/logos/facebook-dark.svg delete mode 100644 packages/core/src/providers/logos/facebook.svg delete mode 100644 packages/core/src/providers/logos/foursquare-dark.svg delete mode 100644 packages/core/src/providers/logos/foursquare.svg delete mode 100644 packages/core/src/providers/logos/freshbooks-dark.svg delete mode 100644 packages/core/src/providers/logos/freshbooks.svg delete mode 100644 packages/core/src/providers/logos/github-dark.svg delete mode 100644 packages/core/src/providers/logos/github.svg delete mode 100644 packages/core/src/providers/logos/gitlab-dark.svg delete mode 100644 packages/core/src/providers/logos/gitlab.svg delete mode 100644 packages/core/src/providers/logos/google.svg delete mode 100644 packages/core/src/providers/logos/hubspot-dark.svg delete mode 100644 packages/core/src/providers/logos/hubspot.svg delete mode 100644 packages/core/src/providers/logos/instagram.svg delete mode 100644 packages/core/src/providers/logos/keycloak.svg delete mode 100644 packages/core/src/providers/logos/line.svg delete mode 100644 packages/core/src/providers/logos/linkedin-dark.svg delete mode 100644 packages/core/src/providers/logos/linkedin.svg delete mode 100644 packages/core/src/providers/logos/mailchimp-dark.svg delete mode 100644 packages/core/src/providers/logos/mailchimp.svg delete mode 100644 packages/core/src/providers/logos/okta-dark.svg delete mode 100644 packages/core/src/providers/logos/okta.svg delete mode 100644 packages/core/src/providers/logos/patreon.svg delete mode 100644 packages/core/src/providers/logos/slack.svg delete mode 100644 packages/core/src/providers/logos/spotify.svg delete mode 100644 packages/core/src/providers/logos/todoist.svg delete mode 100644 packages/core/src/providers/logos/trakt-dark.svg delete mode 100644 packages/core/src/providers/logos/trakt.svg delete mode 100644 packages/core/src/providers/logos/twitch-dark.svg delete mode 100644 packages/core/src/providers/logos/twitch.svg delete mode 100644 packages/core/src/providers/logos/twitter-dark.svg delete mode 100644 packages/core/src/providers/logos/twitter.svg delete mode 100644 packages/core/src/providers/logos/vk-dark.svg delete mode 100644 packages/core/src/providers/logos/vk.svg delete mode 100644 packages/core/src/providers/logos/wikimedia-dark.svg delete mode 100644 packages/core/src/providers/logos/wikimedia.svg delete mode 100644 packages/core/src/providers/logos/workos-dark.svg delete mode 100644 packages/core/src/providers/logos/workos.svg diff --git a/packages/core/src/pages/signin.tsx b/packages/core/src/pages/signin.tsx index 5a8f1ae9f7..0a2f7891d4 100644 --- a/packages/core/src/pages/signin.tsx +++ b/packages/core/src/pages/signin.tsx @@ -111,22 +111,11 @@ export default function SigninPage(props: SignInServerPageParams) { } as CSSProperties } > - {provider.style?.logo && ( - - )} - {provider.style?.logoDark && ( - - )} +

) } + +function Logo({ id, src }: { id: string; src?: string }) { + if (!src) return null + const _src = `${ + src.startsWith("/") + ? // TODO: move logos + "https://raw.githubusercontent.com/nextauthjs/next-auth/main/packages/next-auth/provider-logos" + : "" + }${src}` + return +} diff --git a/packages/core/src/providers/logos/apple-dark.svg b/packages/core/src/providers/logos/apple-dark.svg deleted file mode 100644 index 60b1a36adf..0000000000 --- a/packages/core/src/providers/logos/apple-dark.svg +++ /dev/null @@ -1,4 +0,0 @@ - - Apple icon - - diff --git a/packages/core/src/providers/logos/apple.svg b/packages/core/src/providers/logos/apple.svg deleted file mode 100644 index 4d08570d78..0000000000 --- a/packages/core/src/providers/logos/apple.svg +++ /dev/null @@ -1,4 +0,0 @@ - - Apple icon - - diff --git a/packages/core/src/providers/logos/atlassian-dark.svg b/packages/core/src/providers/logos/atlassian-dark.svg deleted file mode 100644 index 9c41c73536..0000000000 --- a/packages/core/src/providers/logos/atlassian-dark.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/packages/core/src/providers/logos/atlassian.svg b/packages/core/src/providers/logos/atlassian.svg deleted file mode 100644 index 37a5e7fdb5..0000000000 --- a/packages/core/src/providers/logos/atlassian.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/packages/core/src/providers/logos/auth0-dark.svg b/packages/core/src/providers/logos/auth0-dark.svg deleted file mode 100644 index 1b4a7b6dba..0000000000 --- a/packages/core/src/providers/logos/auth0-dark.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/packages/core/src/providers/logos/auth0.svg b/packages/core/src/providers/logos/auth0.svg deleted file mode 100644 index 102518ef74..0000000000 --- a/packages/core/src/providers/logos/auth0.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/core/src/providers/logos/azure-dark.svg b/packages/core/src/providers/logos/azure-dark.svg deleted file mode 100644 index fb3329d8d7..0000000000 --- a/packages/core/src/providers/logos/azure-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/core/src/providers/logos/azure.svg b/packages/core/src/providers/logos/azure.svg deleted file mode 100644 index 9b29e54f46..0000000000 --- a/packages/core/src/providers/logos/azure.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/core/src/providers/logos/battlenet-dark.svg b/packages/core/src/providers/logos/battlenet-dark.svg deleted file mode 100644 index 58fe8c84d8..0000000000 --- a/packages/core/src/providers/logos/battlenet-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/core/src/providers/logos/battlenet.svg b/packages/core/src/providers/logos/battlenet.svg deleted file mode 100644 index 299d5f7206..0000000000 --- a/packages/core/src/providers/logos/battlenet.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/core/src/providers/logos/box-dark.svg b/packages/core/src/providers/logos/box-dark.svg deleted file mode 100644 index 20a66a6abe..0000000000 --- a/packages/core/src/providers/logos/box-dark.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/packages/core/src/providers/logos/box.svg b/packages/core/src/providers/logos/box.svg deleted file mode 100644 index da4cc59649..0000000000 --- a/packages/core/src/providers/logos/box.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/packages/core/src/providers/logos/cognito.svg b/packages/core/src/providers/logos/cognito.svg deleted file mode 100644 index 012dc5a4a8..0000000000 --- a/packages/core/src/providers/logos/cognito.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/packages/core/src/providers/logos/discord-dark.svg b/packages/core/src/providers/logos/discord-dark.svg deleted file mode 100644 index 49f14c274a..0000000000 --- a/packages/core/src/providers/logos/discord-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/core/src/providers/logos/discord.svg b/packages/core/src/providers/logos/discord.svg deleted file mode 100644 index b313eeb917..0000000000 --- a/packages/core/src/providers/logos/discord.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/core/src/providers/logos/facebook-dark.svg b/packages/core/src/providers/logos/facebook-dark.svg deleted file mode 100644 index db8422508a..0000000000 --- a/packages/core/src/providers/logos/facebook-dark.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/packages/core/src/providers/logos/facebook.svg b/packages/core/src/providers/logos/facebook.svg deleted file mode 100644 index 2443491403..0000000000 --- a/packages/core/src/providers/logos/facebook.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/packages/core/src/providers/logos/foursquare-dark.svg b/packages/core/src/providers/logos/foursquare-dark.svg deleted file mode 100644 index ffe01fbf7d..0000000000 --- a/packages/core/src/providers/logos/foursquare-dark.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - diff --git a/packages/core/src/providers/logos/foursquare.svg b/packages/core/src/providers/logos/foursquare.svg deleted file mode 100644 index 5f63b4521b..0000000000 --- a/packages/core/src/providers/logos/foursquare.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - diff --git a/packages/core/src/providers/logos/freshbooks-dark.svg b/packages/core/src/providers/logos/freshbooks-dark.svg deleted file mode 100644 index c673c4d219..0000000000 --- a/packages/core/src/providers/logos/freshbooks-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/core/src/providers/logos/freshbooks.svg b/packages/core/src/providers/logos/freshbooks.svg deleted file mode 100644 index ff80db2830..0000000000 --- a/packages/core/src/providers/logos/freshbooks.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/core/src/providers/logos/github-dark.svg b/packages/core/src/providers/logos/github-dark.svg deleted file mode 100644 index 41128ce9ed..0000000000 --- a/packages/core/src/providers/logos/github-dark.svg +++ /dev/null @@ -1,4 +0,0 @@ - - GitHub icon - - diff --git a/packages/core/src/providers/logos/github.svg b/packages/core/src/providers/logos/github.svg deleted file mode 100644 index a6f58d977d..0000000000 --- a/packages/core/src/providers/logos/github.svg +++ /dev/null @@ -1,4 +0,0 @@ - - GitHub dark icon - - diff --git a/packages/core/src/providers/logos/gitlab-dark.svg b/packages/core/src/providers/logos/gitlab-dark.svg deleted file mode 100644 index a9d455410d..0000000000 --- a/packages/core/src/providers/logos/gitlab-dark.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - diff --git a/packages/core/src/providers/logos/gitlab.svg b/packages/core/src/providers/logos/gitlab.svg deleted file mode 100644 index 3b6849074a..0000000000 --- a/packages/core/src/providers/logos/gitlab.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/packages/core/src/providers/logos/google.svg b/packages/core/src/providers/logos/google.svg deleted file mode 100644 index 60d0ec131a..0000000000 --- a/packages/core/src/providers/logos/google.svg +++ /dev/null @@ -1,7 +0,0 @@ - - Google icon - - - - - diff --git a/packages/core/src/providers/logos/hubspot-dark.svg b/packages/core/src/providers/logos/hubspot-dark.svg deleted file mode 100644 index e8ef5e7f45..0000000000 --- a/packages/core/src/providers/logos/hubspot-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/core/src/providers/logos/hubspot.svg b/packages/core/src/providers/logos/hubspot.svg deleted file mode 100644 index 3ab02c3bdc..0000000000 --- a/packages/core/src/providers/logos/hubspot.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/core/src/providers/logos/instagram.svg b/packages/core/src/providers/logos/instagram.svg deleted file mode 100644 index 9801b04bef..0000000000 --- a/packages/core/src/providers/logos/instagram.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/packages/core/src/providers/logos/keycloak.svg b/packages/core/src/providers/logos/keycloak.svg deleted file mode 100644 index 4a558aef01..0000000000 --- a/packages/core/src/providers/logos/keycloak.svg +++ /dev/null @@ -1,260 +0,0 @@ - - - - - - - - - - - - keycloak_deliverables - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/core/src/providers/logos/line.svg b/packages/core/src/providers/logos/line.svg deleted file mode 100644 index afbd2758a3..0000000000 --- a/packages/core/src/providers/logos/line.svg +++ /dev/null @@ -1,6 +0,0 @@ - - Line icon - - - - diff --git a/packages/core/src/providers/logos/linkedin-dark.svg b/packages/core/src/providers/logos/linkedin-dark.svg deleted file mode 100644 index 3240302d5d..0000000000 --- a/packages/core/src/providers/logos/linkedin-dark.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/packages/core/src/providers/logos/linkedin.svg b/packages/core/src/providers/logos/linkedin.svg deleted file mode 100644 index 1bc626bcbf..0000000000 --- a/packages/core/src/providers/logos/linkedin.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/packages/core/src/providers/logos/mailchimp-dark.svg b/packages/core/src/providers/logos/mailchimp-dark.svg deleted file mode 100644 index adf1fa2b4d..0000000000 --- a/packages/core/src/providers/logos/mailchimp-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/core/src/providers/logos/mailchimp.svg b/packages/core/src/providers/logos/mailchimp.svg deleted file mode 100644 index 27a2f9bcfa..0000000000 --- a/packages/core/src/providers/logos/mailchimp.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/core/src/providers/logos/okta-dark.svg b/packages/core/src/providers/logos/okta-dark.svg deleted file mode 100644 index a976f88b19..0000000000 --- a/packages/core/src/providers/logos/okta-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/core/src/providers/logos/okta.svg b/packages/core/src/providers/logos/okta.svg deleted file mode 100644 index 2321a15303..0000000000 --- a/packages/core/src/providers/logos/okta.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/core/src/providers/logos/patreon.svg b/packages/core/src/providers/logos/patreon.svg deleted file mode 100644 index 4fa72b5231..0000000000 --- a/packages/core/src/providers/logos/patreon.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/packages/core/src/providers/logos/slack.svg b/packages/core/src/providers/logos/slack.svg deleted file mode 100644 index b80883e74d..0000000000 --- a/packages/core/src/providers/logos/slack.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/packages/core/src/providers/logos/spotify.svg b/packages/core/src/providers/logos/spotify.svg deleted file mode 100644 index 2421491e16..0000000000 --- a/packages/core/src/providers/logos/spotify.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/packages/core/src/providers/logos/todoist.svg b/packages/core/src/providers/logos/todoist.svg deleted file mode 100644 index e229dc2c6f..0000000000 --- a/packages/core/src/providers/logos/todoist.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/packages/core/src/providers/logos/trakt-dark.svg b/packages/core/src/providers/logos/trakt-dark.svg deleted file mode 100644 index 9722816d7f..0000000000 --- a/packages/core/src/providers/logos/trakt-dark.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - diff --git a/packages/core/src/providers/logos/trakt.svg b/packages/core/src/providers/logos/trakt.svg deleted file mode 100644 index 5cb7e1fe56..0000000000 --- a/packages/core/src/providers/logos/trakt.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - diff --git a/packages/core/src/providers/logos/twitch-dark.svg b/packages/core/src/providers/logos/twitch-dark.svg deleted file mode 100644 index 41488e9df3..0000000000 --- a/packages/core/src/providers/logos/twitch-dark.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/packages/core/src/providers/logos/twitch.svg b/packages/core/src/providers/logos/twitch.svg deleted file mode 100644 index 8c08a26001..0000000000 --- a/packages/core/src/providers/logos/twitch.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/packages/core/src/providers/logos/twitter-dark.svg b/packages/core/src/providers/logos/twitter-dark.svg deleted file mode 100644 index 07f05a8634..0000000000 --- a/packages/core/src/providers/logos/twitter-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/core/src/providers/logos/twitter.svg b/packages/core/src/providers/logos/twitter.svg deleted file mode 100644 index 35e715f2ea..0000000000 --- a/packages/core/src/providers/logos/twitter.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/core/src/providers/logos/vk-dark.svg b/packages/core/src/providers/logos/vk-dark.svg deleted file mode 100644 index 6ef4ef9ea5..0000000000 --- a/packages/core/src/providers/logos/vk-dark.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/packages/core/src/providers/logos/vk.svg b/packages/core/src/providers/logos/vk.svg deleted file mode 100644 index f567c75cf7..0000000000 --- a/packages/core/src/providers/logos/vk.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/packages/core/src/providers/logos/wikimedia-dark.svg b/packages/core/src/providers/logos/wikimedia-dark.svg deleted file mode 100644 index 55de1b6361..0000000000 --- a/packages/core/src/providers/logos/wikimedia-dark.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/packages/core/src/providers/logos/wikimedia.svg b/packages/core/src/providers/logos/wikimedia.svg deleted file mode 100644 index 3ae4b1ba4e..0000000000 --- a/packages/core/src/providers/logos/wikimedia.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/packages/core/src/providers/logos/workos-dark.svg b/packages/core/src/providers/logos/workos-dark.svg deleted file mode 100644 index b9047adca4..0000000000 --- a/packages/core/src/providers/logos/workos-dark.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/packages/core/src/providers/logos/workos.svg b/packages/core/src/providers/logos/workos.svg deleted file mode 100644 index 42f799f2e8..0000000000 --- a/packages/core/src/providers/logos/workos.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - From 99abfbecfc65ec39f625ab5bf0cb2ee2de4b542a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Wed, 7 Dec 2022 12:25:20 +0100 Subject: [PATCH 48/93] revert --- packages/next-auth/package.json | 11 +- .../core/lib/oauth/web/authorization-url.ts | 139 ------------------ packages/next-auth/src/web/index.ts | 40 ----- 3 files changed, 2 insertions(+), 188 deletions(-) delete mode 100644 packages/next-auth/src/core/lib/oauth/web/authorization-url.ts delete mode 100644 packages/next-auth/src/web/index.ts diff --git a/packages/next-auth/package.json b/packages/next-auth/package.json index 0537c9ad96..97c41e0c5c 100644 --- a/packages/next-auth/package.json +++ b/packages/next-auth/package.json @@ -32,7 +32,6 @@ "./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" @@ -40,7 +39,7 @@ "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 web 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 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", @@ -63,9 +62,7 @@ "index.js", "adapters.d.ts", "middleware.d.ts", - "middleware.js", - "utils", - "web" + "middleware.js" ], "license": "ISC", "dependencies": { @@ -80,16 +77,12 @@ "uuid": "^8.3.2" }, "peerDependencies": { - "oauth4webapi": "2.0.4", "next": "^12.2.5 || ^13", "nodemailer": "^6.6.5", "react": "^17.0.2 || ^18", "react-dom": "^17.0.2 || ^18" }, "peerDependenciesMeta": { - "oauth4webapi": { - "optional": true - }, "nodemailer": { "optional": true } diff --git a/packages/next-auth/src/core/lib/oauth/web/authorization-url.ts b/packages/next-auth/src/core/lib/oauth/web/authorization-url.ts deleted file mode 100644 index 808f738899..0000000000 --- a/packages/next-auth/src/core/lib/oauth/web/authorization-url.ts +++ /dev/null @@ -1,139 +0,0 @@ -import * as o from "oauth4webapi" - -import type { CookiesOptions, InternalOptions } from "../../../types" -import type { RequestInternal, ResponseInternal } from "../../.." -import type { Cookie } from "../../cookie" - -/** - * Generates an authorization/request token URL. - * - * [OAuth 2](https://www.oauth.com/oauth2-servers/authorization/the-authorization-request/) - */ -export async function getAuthorizationUrl({ - options, - query, -}: { - options: InternalOptions<"oauth"> - query: RequestInternal["query"] -}): Promise { - const { logger, provider } = options - - if (provider.version?.startsWith("1.")) { - const error = new Error("OAuth v1.x is not supported in next-auth/web") - logger.error("GET_AUTHORIZATION_URL", error) - throw error - } - - let url = provider.authorization?.url - let as: o.AuthorizationServer | undefined - - if (!url) { - // If url is undefined, we assume that issuer is always defined - // We check this in assert.ts - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const issuer = new URL(provider.issuer!) - const discoveryResponse = await o.discoveryRequest(issuer) - const as = await o.processDiscoveryResponse(issuer, discoveryResponse) - - if (!as.authorization_endpoint) { - throw new TypeError( - "Authorization server did not provide an authorization endpoint." - ) - } - - url = new URL(as.authorization_endpoint) - } - - const authParams = url.searchParams - const params = Object.assign( - { - response_type: "code", - // clientId can technically be undefined, should we check this in assert.ts or rely on the Authorization Server to do it? - client_id: provider.clientId, - redirect_uri: provider.callbackUrl, - }, // Defaults - Object.fromEntries(authParams.entries()), // From provider config - query // From `signIn` call - ) - - for (const k in params) authParams.set(k, params[k]) - - const cookies: Cookie[] = [] - - if (provider.checks?.includes("state")) { - const { value, raw } = await createState(options) - authParams.set("state", raw) - cookies.push(value) - } - - if (provider.checks?.includes("pkce")) { - if (as && !as.code_challenge_methods_supported?.includes("S256")) { - // We assume S256 PKCE support, if the server does not advertise that, - // a random `nonce` must be used for CSRF protection. - provider.checks = ["nonce"] - } else { - const { code_challenge, pkce } = await createPKCE(options) - authParams.set("code_challenge", code_challenge) - authParams.set("code_challenge_method", "S256") - cookies.push(pkce) - } - } - - if (provider.checks?.includes("nonce")) { - const nonce = await createNonce(options) - authParams.set("nonce", nonce.value) - cookies.push(nonce) - } - - logger.debug("GET_AUTHORIZATION_URL", { url, cookies, provider }) - return { redirect: url, cookies } -} - -/** Returns a signed cookie. */ -export async function signCookie( - type: keyof CookiesOptions, - value: string, - maxAge: number, - options: InternalOptions<"oauth"> -): Promise { - const { cookies, jwt, logger } = options - - logger.debug(`CREATE_${type.toUpperCase()}`, { value, maxAge }) - - const expires = new Date() - expires.setTime(expires.getTime() + maxAge * 1000) - return { - name: cookies[type].name, - value: await jwt.encode({ ...jwt, maxAge, token: { value } }), - options: { ...cookies[type].options, expires }, - } -} - -const STATE_MAX_AGE = 60 * 15 // 15 minutes in seconds -async function createState(options: InternalOptions<"oauth">) { - const raw = o.generateRandomState() - const maxAge = STATE_MAX_AGE - const value = await signCookie("state", raw, maxAge, options) - return { value, raw } -} - -const PKCE_MAX_AGE = 60 * 15 // 15 minutes in seconds -async function createPKCE(options: InternalOptions<"oauth">) { - const code_verifier = o.generateRandomCodeVerifier() - const code_challenge = await o.calculatePKCECodeChallenge(code_verifier) - const maxAge = PKCE_MAX_AGE - const pkce = await signCookie( - "pkceCodeVerifier", - code_verifier, - maxAge, - options - ) - return { code_challenge, pkce } -} - -const NONCE_MAX_AGE = 60 * 15 // 15 minutes in seconds -async function createNonce(options: InternalOptions<"oauth">) { - const raw = o.generateRandomNonce() - const maxAge = NONCE_MAX_AGE - return await signCookie("nonce", raw, maxAge, options) -} diff --git a/packages/next-auth/src/web/index.ts b/packages/next-auth/src/web/index.ts deleted file mode 100644 index 367f0aff29..0000000000 --- a/packages/next-auth/src/web/index.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { AuthHandler } from "../core" - -import type { AuthOptions } from ".." -export * from "../core/types" - -async function WebAuthHandler(req: Request, options: AuthOptions) { - options.secret ??= options.jwt?.secret ?? process.env.NEXTAUTH_SECRET - options.__internal__ = { runtime: "web" } - - const url = new URL(req.url ?? "") - - const res = await AuthHandler(new Request(url, req), options) - - // If the request expects a return URL, send it as JSON - // instead of doing an actual redirect. - const redirect = res.headers.get("Location") - if (req.headers.get("X-Auth-Return-Redirect") && redirect) { - res.headers.delete("Location") - return new Response(JSON.stringify({ url: redirect }), res) - } - - return res -} - -function Auth(options: AuthOptions): any -function Auth(req: Request, options: AuthOptions): any - -/** The main entry point to next-auth/web */ -function Auth(...args: [AuthOptions] | [Request, AuthOptions]) { - if (!args.length || args[0] instanceof Request) { - // @ts-expect-error - return WebAuthHandler(...args) - } - if (args.length === 1) - return async (req: Request) => await WebAuthHandler(req, args[0]) - - return WebAuthHandler(args[0], args[1]) -} - -export default Auth From 8299b977f9a5531002a841d1680c1917f492fc41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Wed, 7 Dec 2022 17:41:04 +0100 Subject: [PATCH 49/93] move redirect logic to core --- apps/dev/pages/api/auth/[...nextauth].ts | 5 +---- packages/next-auth/src/core/index.ts | 10 +++++++++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/apps/dev/pages/api/auth/[...nextauth].ts b/apps/dev/pages/api/auth/[...nextauth].ts index 960da72cb6..4f2b1ee371 100644 --- a/apps/dev/pages/api/auth/[...nextauth].ts +++ b/apps/dev/pages/api/auth/[...nextauth].ts @@ -136,10 +136,7 @@ function Auth(...args: any[]) { return async (req: Request) => { args[0].secret ??= process.env.NEXTAUTH_SECRET const res = await AuthHandler(req, args[0]) - - // If the request expects a return URL, send it as JSON - // instead of doing an actual redirect. - const redirect = res.headers.get("Location") || /* TODO: remove */ (await req.json().json) + const redirect = /* TODO: remove */ await req.json().json if (req.headers.get("X-Auth-Return-Redirect") && redirect) { res.headers.delete("Location") return new Response(JSON.stringify({ url: redirect }), res) diff --git a/packages/next-auth/src/core/index.ts b/packages/next-auth/src/core/index.ts index 020e333904..b0bfe86cee 100644 --- a/packages/next-auth/src/core/index.ts +++ b/packages/next-auth/src/core/index.ts @@ -269,5 +269,13 @@ export async function AuthHandler( ): Promise { const req = await toInternalRequest(request) const internalResponse = await AuthHandlerInternal({ req, options }) - return toResponse(internalResponse) + const response = await toResponse(internalResponse) + const redirect = response.headers.get("Location") + // If the request expects a return URL, send it as JSON + // instead of doing an actual redirect. + if (request.headers.get("X-Auth-Return-Redirect") && redirect) { + response.headers.delete("Location") + return new Response(JSON.stringify({ url: redirect }), response) + } + return response } From 2730249e3068037140aab25a6db12eae39755e4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Wed, 7 Dec 2022 18:00:02 +0100 Subject: [PATCH 50/93] wip fix css --- packages/core/src/css/index.ts | 21 ++------------------- packages/core/src/pages/index.ts | 4 ++-- 2 files changed, 4 insertions(+), 21 deletions(-) diff --git a/packages/core/src/css/index.ts b/packages/core/src/css/index.ts index d6ebc4c630..194715dc0d 100644 --- a/packages/core/src/css/index.ts +++ b/packages/core/src/css/index.ts @@ -1,19 +1,2 @@ -// To support serverless targets (which don't work if you try to read in things -// like CSS files at run time) this file is replaced in production builds with -// a function that returns compiled CSS (embedded as a string in the function). - -export default function css() { - if (globalThis.EdgeRuntime) return "TODO" - // eslint-disable-next-line @typescript-eslint/no-var-requires - const fs = require("fs") - // eslint-disable-next-line @typescript-eslint/no-var-requires - const path = require("path") - - const pathToCss = path.join( - process.cwd(), - process.env.NODE_ENV === "development" - ? "node_modules/next-auth/css/index.css" - : "/src/css/index.css" - ) - return fs.readFileSync(pathToCss, "utf8") -} +// This file is automatically generated from index.css. Do not edit it manually. +export const css = `REPLACE_WITH_MINIFIED_CSS` diff --git a/packages/core/src/pages/index.ts b/packages/core/src/pages/index.ts index 65ff597dc7..ec183d6c16 100644 --- a/packages/core/src/pages/index.ts +++ b/packages/core/src/pages/index.ts @@ -1,5 +1,5 @@ import renderToString from "preact-render-to-string" -import css from "../css" +import { css } from "../css" import ErrorPage from "./error" import SigninPage from "./signin" import SignoutPage from "./signout" @@ -31,7 +31,7 @@ export default function renderPage(params: RenderPageParams) { cookies, status, headers: { "Content-Type": "text/html" }, - body: `${title}${title}
${renderToString(html)}
`, } From 598b3efc074a78cae76a593bd111caf77dfdd6af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Wed, 7 Dec 2022 18:14:29 +0100 Subject: [PATCH 51/93] revert Logo component --- packages/core/src/pages/signin.tsx | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/core/src/pages/signin.tsx b/packages/core/src/pages/signin.tsx index 0a2f7891d4..60a63c5d88 100644 --- a/packages/core/src/pages/signin.tsx +++ b/packages/core/src/pages/signin.tsx @@ -74,6 +74,9 @@ export default function SigninPage(props: SignInServerPageParams) { const error = errorType && (errors[errorType] ?? errors.default) + // TODO: move logos + const logos = + "https://raw.githubusercontent.com/nextauthjs/next-auth/main/packages/next-auth/provider-logos" return (
{theme.brandColor && ( @@ -111,11 +114,19 @@ export default function SigninPage(props: SignInServerPageParams) { } as CSSProperties } > -
) } - -function Logo({ id, src }: { id: string; src?: string }) { - if (!src) return null - const _src = `${ - src.startsWith("/") - ? // TODO: move logos - "https://raw.githubusercontent.com/nextauthjs/next-auth/main/packages/next-auth/provider-logos" - : "" - }${src}` - return -} From df616605f0267bd5e0dca0c19a6565c102eba24b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Thu, 8 Dec 2022 02:34:22 +0100 Subject: [PATCH 52/93] output ESM --- packages/core/package.json | 14 ++++++++++++-- packages/core/tsconfig.json | 8 +------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/packages/core/package.json b/packages/core/package.json index 1467dab5b8..3d11e0f2ff 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -13,8 +13,18 @@ "Thang Huu Vu ", "Iain Collins Date: Thu, 8 Dec 2022 02:34:51 +0100 Subject: [PATCH 53/93] fix logout --- apps/dev/pages/api/auth/[...nextauth].ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/apps/dev/pages/api/auth/[...nextauth].ts b/apps/dev/pages/api/auth/[...nextauth].ts index 4f2b1ee371..8e0533fef7 100644 --- a/apps/dev/pages/api/auth/[...nextauth].ts +++ b/apps/dev/pages/api/auth/[...nextauth].ts @@ -130,16 +130,21 @@ if (authOptions.adapter) { // ) } -// TODO: move +// TODO: move to next-auth/edge function Auth(...args: any[]) { if (args.length === 1) return async (req: Request) => { args[0].secret ??= process.env.NEXTAUTH_SECRET + + // TODO: remove when `next-auth/react` sends `X-Auth-Return-Redirect` + const shouldRedirect = req.method === "POST" && req.headers.get("Content-Type") === "application/json" ? (await req.clone().json()).json : false + + // TODO: This can be directly in core const res = await AuthHandler(req, args[0]) - const redirect = /* TODO: remove */ await req.json().json - if (req.headers.get("X-Auth-Return-Redirect") && redirect) { + if (req.headers.get("X-Auth-Return-Redirect") || shouldRedirect) { + const url = res.headers.get("Location") res.headers.delete("Location") - return new Response(JSON.stringify({ url: redirect }), res) + return new Response(JSON.stringify({ url }), res) } return res } From 5c96c8fe68c1dafd6c2849747933c20c8fe7ebc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Thu, 8 Dec 2022 02:37:49 +0100 Subject: [PATCH 54/93] deprecate OAuth 1, simplify internals, improve defaults --- packages/core/src/lib/assert.ts | 13 ++-- packages/core/src/lib/callback-handler.ts | 2 +- .../core/src/lib/oauth/authorization-url.ts | 15 ++--- packages/core/src/lib/oauth/callback.ts | 11 ++-- packages/core/src/lib/providers.ts | 59 ++++++++----------- packages/core/src/pages/index.ts | 11 +++- packages/core/src/pages/signin.tsx | 26 +++----- packages/core/src/routes/callback.ts | 2 +- packages/core/src/routes/signin.ts | 2 +- 9 files changed, 64 insertions(+), 77 deletions(-) diff --git a/packages/core/src/lib/assert.ts b/packages/core/src/lib/assert.ts index 34ff7b89bc..507155199e 100644 --- a/packages/core/src/lib/assert.ts +++ b/packages/core/src/lib/assert.ts @@ -95,12 +95,12 @@ export function assertConfig(params: { } let hasCredentials, hasEmail - let hasTwitterOAuth2 for (const provider of options.providers) { if ( - provider.type === "oauth" && - !(provider.issuer ?? provider.options?.issuer) + provider.type === "oauth" || + (provider.type === "oidc" && + !(provider.issuer ?? provider.options?.issuer)) ) { const { authorization: a, token: t, userinfo: u } = provider @@ -118,8 +118,6 @@ export function assertConfig(params: { if (provider.type === "credentials") hasCredentials = true else if (provider.type === "email") hasEmail = true - else if (provider.id === "twitter" && provider.version === "2.0") - hasTwitterOAuth2 = true } if (hasCredentials) { @@ -162,10 +160,7 @@ export function assertConfig(params: { } } - if (!warned) { - if (hasTwitterOAuth2) warnings.push("TWITTER_OAUTH_2_BETA") - warned = true - } + if (!warned) warned = true return warnings } diff --git a/packages/core/src/lib/callback-handler.ts b/packages/core/src/lib/callback-handler.ts index 9b3ae760a9..e6230fddee 100644 --- a/packages/core/src/lib/callback-handler.ts +++ b/packages/core/src/lib/callback-handler.ts @@ -29,7 +29,7 @@ export default async function callbackHandler(params: { // Input validation if (!account?.providerAccountId || !account.type) throw new Error("Missing or invalid provider account") - if (!["email", "oauth"].includes(account.type)) + if (!["email", "oauth", "oidc"].includes(account.type)) throw new Error("Provider not supported") const { diff --git a/packages/core/src/lib/oauth/authorization-url.ts b/packages/core/src/lib/oauth/authorization-url.ts index 3214f43c38..275d04c733 100644 --- a/packages/core/src/lib/oauth/authorization-url.ts +++ b/packages/core/src/lib/oauth/authorization-url.ts @@ -22,12 +22,6 @@ export async function getAuthorizationUrl({ }): Promise { const { logger, provider } = options - if (provider.version?.startsWith("1.")) { - const error = new Error("OAuth v1.x is not supported in next-auth/web") - logger.error("GET_AUTHORIZATION_URL", error) - throw error - } - let url = provider.authorization?.url let as: o.AuthorizationServer | undefined @@ -56,7 +50,7 @@ export async function getAuthorizationUrl({ client_id: provider.clientId, redirect_uri: provider.callbackUrl, // @ts-expect-error TODO: - ...(provider.authorization.params ?? {}), + ...provider.authorization?.params, }, // Defaults Object.fromEntries(authParams), // From provider config query // From `signIn` call @@ -92,6 +86,13 @@ export async function getAuthorizationUrl({ } url.searchParams.delete("nextauth") + + // TODO: This does not work in normalizeOAuth because authorization endpoint can come from discovery + // Need to make normalizeOAuth async + if (provider.type === "oidc" && !url.searchParams.has("scope")) { + url.searchParams.set("scope", "openid profile email") + } + logger.debug("GET_AUTHORIZATION_URL", { url, cookies, provider }) return { redirect: url, cookies } } diff --git a/packages/core/src/lib/oauth/callback.ts b/packages/core/src/lib/oauth/callback.ts index d82e4080c9..7b2509780e 100644 --- a/packages/core/src/lib/oauth/callback.ts +++ b/packages/core/src/lib/oauth/callback.ts @@ -70,8 +70,7 @@ export async function handleOAuthCallback(params: { const client: o.Client = { client_id: provider.clientId, client_secret: provider.clientSecret, - token_endpoint_auth_method: - "client_secret_basic" ?? provider.client?.token_endpoint_auth_method, + token_endpoint_auth_method: "client_secret_basic", // TODO: support other client options } @@ -88,7 +87,7 @@ export async function handleOAuthCallback(params: { // TODO: const nonce = await useNonce(cookies?.[options.cookies.nonce.name], options) - if (nonce && provider.idToken) { + if (nonce && provider.type === "oidc") { resCookies.push(nonce.cookie) } @@ -123,7 +122,7 @@ export async function handleOAuthCallback(params: { let profile: Profile = {} let tokens: TokenSet - if (provider.idToken) { + if (provider.type === "oidc") { const result = await o.processAuthorizationCodeOpenIDResponse( as, client, @@ -148,7 +147,9 @@ export async function handleOAuthCallback(params: { throw new Error("TODO: Handle OAuth 2.0 response body error") } - if (provider.userinfo?.url) { + if (provider.userinfo?.request) { + profile = await provider.userinfo.request({ tokens, provider }) + } else if (provider.userinfo?.url) { const userinfoResponse = await o.userInfoRequest( as, client, diff --git a/packages/core/src/lib/providers.ts b/packages/core/src/lib/providers.ts index 1c1a688658..9cbc5baef3 100644 --- a/packages/core/src/lib/providers.ts +++ b/packages/core/src/lib/providers.ts @@ -34,10 +34,8 @@ export default function parseProviders(params: { callbackUrl: `${url}/callback/${id}`, }) - if (provider.type === "oauth") { - const p = normalizeOAuth(merged) - - return p + if (provider.type === "oauth" || provider.type === "oidc") { + return normalizeOAuth(merged) } return merged @@ -50,50 +48,45 @@ export default function parseProviders(params: { } function normalizeOAuth( - c?: OAuthConfig | OAuthUserConfig, - runtime?: "web" | "nodejs" + c?: OAuthConfig | OAuthUserConfig ): OAuthConfigInternal | {} { if (!c) return {} - const hasIssuer = !!c.issuer - const authorization = normalizeEndpoint(c.authorization, hasIssuer) + if (c.issuer) c.wellKnown ??= `${c.issuer}/.well-known/openid-configuration` - // TODO: deprecate OAuth 1.0 support - if (!c.version?.startsWith("1.")) { - // Set default check to state - c.checks ??= ["pkce"] - c.checks = Array.isArray(c.checks) ? c.checks : [c.checks] - if (runtime === "web" && !c.checks.includes("pkce")) c.checks.push("pkce") + const authorization = normalizeEndpoint(c.authorization, c.issuer) + if (authorization && !authorization.url?.searchParams.has("scope")) { + authorization.url.searchParams.set("scope", "openid profile email") + } - if (!Array.isArray(c.checks)) c.checks = [c.checks] + const token = normalizeEndpoint(c.token, c.issuer) - // REVIEW: Deprecate `idToken` in favor of `type: "oidc"`? - c.idToken ??= - // If a provider has as an "openid-configuration" well-known endpoint - c.wellKnown?.includes("openid-configuration") ?? - // or an "openid" scope request, it must also return an `id_token` - authorization?.url.searchParams.get("scope")?.includes("openid") - - if (c.issuer && c.idToken) { - c.wellKnown ??= `${c.issuer}/.well-known/openid-configuration` - } - } + const userinfo = normalizeEndpoint(c.userinfo, c.issuer) return { ...c, - ...(authorization ? { authorization } : undefined), - ...(c.token ? { token: normalizeEndpoint(c.token, hasIssuer) } : undefined), - ...(c.userinfo - ? { userinfo: normalizeEndpoint(c.userinfo, hasIssuer) } - : undefined), + authorization, + token, + checks: c.checks ?? ["pkce"], + userinfo, + profile: c.profile ?? defaultProfile, } } +function defaultProfile(profile: any) { + return { + id: profile.sub ?? profile.id, + name: + profile.name ?? profile.nickname ?? profile.preferred_username ?? null, + email: profile.email ?? null, + image: profile.picture ?? null, + } +} function normalizeEndpoint( e?: OAuthConfig[OAuthEndpointType], - hasIssuer?: boolean + issuer?: string ): OAuthConfigInternal[OAuthEndpointType] { - if (!e || hasIssuer) return + if (!e || issuer) return if (typeof e === "string") { return { url: new URL(e) } } diff --git a/packages/core/src/pages/index.ts b/packages/core/src/pages/index.ts index ec183d6c16..19fc2f7f9e 100644 --- a/packages/core/src/pages/index.ts +++ b/packages/core/src/pages/index.ts @@ -42,7 +42,16 @@ export default function renderPage(params: RenderPageParams) { return send({ html: SigninPage({ csrfToken: params.csrfToken, - providers: params.providers, + // We only want to render providers + providers: params.providers?.filter( + (provider) => + // Always render oauth and email type providers + ["email", "oauth", "oidc"].includes(provider.type) || + // Only render credentials type provider if credentials are defined + (provider.type === "credentials" && provider.credentials) || + // Don't render other provider types + false + ), callbackUrl: params.callbackUrl, theme, ...query, diff --git a/packages/core/src/pages/signin.tsx b/packages/core/src/pages/signin.tsx index 60a63c5d88..db36c3f352 100644 --- a/packages/core/src/pages/signin.tsx +++ b/packages/core/src/pages/signin.tsx @@ -30,24 +30,12 @@ export interface SignInServerPageParams { export default function SigninPage(props: SignInServerPageParams) { const { csrfToken, - providers, + providers = [], callbackUrl, theme, email, error: errorType, } = props - // We only want to render providers - const providersToRender = providers.filter((provider) => { - if (provider.type === "oauth" || provider.type === "email") { - // Always render oauth and email type providers - return true - } else if (provider.type === "credentials" && provider.credentials) { - // Only render credentials type provider if credentials are defined - return true - } - // Don't render other provider types - return false - }) if (typeof document !== "undefined" && theme.brandColor) { document.documentElement.style.setProperty( @@ -93,9 +81,9 @@ export default function SigninPage(props: SignInServerPageParams) {

{error}

)} - {providersToRender.map((provider, i: number) => ( + {providers.map((provider, i) => (
- {provider.type === "oauth" && ( + {provider.type === "oauth" || provider.type === "oidc" ? (
{callbackUrl && ( @@ -130,11 +118,11 @@ export default function SigninPage(props: SignInServerPageParams) { Sign in with {provider.name}
- )} + ) : null} {(provider.type === "email" || provider.type === "credentials") && i > 0 && - providersToRender[i - 1].type !== "email" && - providersToRender[i - 1].type !== "credentials" &&
} + providers[i - 1].type !== "email" && + providers[i - 1].type !== "credentials" &&
} {provider.type === "email" && (
@@ -184,7 +172,7 @@ export default function SigninPage(props: SignInServerPageParams) {
)} {(provider.type === "email" || provider.type === "credentials") && - i + 1 < providersToRender.length &&
} + i + 1 < providers.length &&
}
))} diff --git a/packages/core/src/routes/callback.ts b/packages/core/src/routes/callback.ts index 03c56be919..2bc5fb0657 100644 --- a/packages/core/src/routes/callback.ts +++ b/packages/core/src/routes/callback.ts @@ -36,7 +36,7 @@ export async function callback(params: { const useJwtSession = sessionStrategy === "jwt" - if (provider.type === "oauth") { + if (provider.type === "oauth" || provider.type === "oidc") { try { const { profile, diff --git a/packages/core/src/routes/signin.ts b/packages/core/src/routes/signin.ts index 35a6e54a18..3a4c93430c 100644 --- a/packages/core/src/routes/signin.ts +++ b/packages/core/src/routes/signin.ts @@ -26,7 +26,7 @@ export async function signin(params: { } } - if (provider.type === "oauth") { + if (provider.type === "oauth" || provider.type === "oidc") { try { return await getAuthorizationUrl({ options, query }) } catch (error) { From 5945d9bc0711c1f9b6883d11b998510db4fd1ea3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Thu, 8 Dec 2022 02:38:24 +0100 Subject: [PATCH 55/93] refactor providers, test facebook --- apps/dev/pages/api/auth/[...nextauth].ts | 10 +-- packages/core/src/providers/apple.ts | 6 +- packages/core/src/providers/auth0.ts | 14 +---- packages/core/src/providers/authentik.ts | 13 +--- packages/core/src/providers/azure-ad-b2c.ts | 8 +-- packages/core/src/providers/azure-ad.ts | 21 ++----- packages/core/src/providers/battlenet.ts | 3 +- packages/core/src/providers/boxyhq-saml.ts | 6 +- packages/core/src/providers/cognito.ts | 12 +--- packages/core/src/providers/dropbox.js | 1 - .../src/providers/duende-identity-server6.ts | 14 +---- packages/core/src/providers/facebook.ts | 15 ++--- packages/core/src/providers/github.ts | 10 +-- packages/core/src/providers/gitlab.ts | 6 +- packages/core/src/providers/google.ts | 15 +---- packages/core/src/providers/hubspot.ts | 35 +++-------- .../core/src/providers/identity-server4.js | 14 +---- packages/core/src/providers/index.ts | 8 +-- packages/core/src/providers/instagram.js | 1 + packages/core/src/providers/kakao.ts | 3 +- packages/core/src/providers/keycloak.ts | 14 +---- packages/core/src/providers/line.ts | 13 +--- packages/core/src/providers/linkedin.ts | 1 + packages/core/src/providers/mailru.js | 8 --- packages/core/src/providers/naver.ts | 1 - packages/core/src/providers/oauth.ts | 63 +++++++++---------- packages/core/src/providers/okta.ts | 13 +--- packages/core/src/providers/onelogin.js | 13 +--- packages/core/src/providers/osso.js | 8 --- packages/core/src/providers/osu.ts | 7 +-- packages/core/src/providers/patreon.ts | 1 - packages/core/src/providers/pinterest.ts | 1 - packages/core/src/providers/pipedrive.ts | 3 +- packages/core/src/providers/reddit.js | 10 +-- packages/core/src/providers/salesforce.ts | 1 - packages/core/src/providers/slack.ts | 13 +--- packages/core/src/providers/strava.ts | 1 + packages/core/src/providers/todoist.ts | 7 ++- packages/core/src/providers/trakt.ts | 21 +++---- packages/core/src/providers/twitch.ts | 11 +--- packages/core/src/providers/twitter.ts | 1 + packages/core/src/providers/united-effects.ts | 15 +---- packages/core/src/providers/vk.ts | 1 + packages/core/src/providers/wikimedia.ts | 8 +-- packages/core/src/providers/workos.ts | 5 +- packages/core/src/providers/zitadel.ts | 21 +------ 46 files changed, 118 insertions(+), 358 deletions(-) diff --git a/apps/dev/pages/api/auth/[...nextauth].ts b/apps/dev/pages/api/auth/[...nextauth].ts index 8e0533fef7..3f83b131c2 100644 --- a/apps/dev/pages/api/auth/[...nextauth].ts +++ b/apps/dev/pages/api/auth/[...nextauth].ts @@ -3,19 +3,19 @@ import { AuthHandler, AuthOptions } from "next-auth-core" // Providers // import Apple from "next-auth/providers/apple" -import Auth0 from "next-auth/providers/auth0" +import Auth0 from "next-auth-core/providers/auth0" // import AzureAD from "next-auth/providers/azure-ad" // import AzureB2C from "next-auth/providers/azure-ad-b2c" // import BoxyHQSAML from "next-auth/providers/boxyhq-saml" // import Cognito from "next-auth/providers/cognito" -import Credentials from "next-auth/providers/credentials" +import Credentials from "next-auth-core/providers/credentials" // import Discord from "next-auth/providers/discord" // import DuendeIDS6 from "next-auth/providers/duende-identity-server6" // import Email from "next-auth/providers/email" -// import Facebook from "next-auth/providers/facebook" +import Facebook from "next-auth/providers/facebook" // import Foursquare from "next-auth/providers/foursquare" // import Freshbooks from "next-auth/providers/freshbooks" -import GitHub from "next-auth/providers/github" +import GitHub from "next-auth-core/providers/github" // import Gitlab from "next-auth/providers/gitlab" // import Google from "next-auth/providers/google" // import IDS4 from "next-auth/providers/identity-server4" @@ -94,7 +94,7 @@ export const authOptions: AuthOptions = { // Cognito({ clientId: process.env.COGNITO_ID, clientSecret: process.env.COGNITO_SECRET, issuer: process.env.COGNITO_ISSUER }), // Discord({ clientId: process.env.DISCORD_ID, clientSecret: process.env.DISCORD_SECRET }), // DuendeIDS6({ clientId: "interactive.confidential", clientSecret: "secret", issuer: "https://demo.duendesoftware.com" }), - // Facebook({ clientId: process.env.FACEBOOK_ID, clientSecret: process.env.FACEBOOK_SECRET }), + Facebook({ clientId: process.env.FACEBOOK_ID, clientSecret: process.env.FACEBOOK_SECRET }), // Foursquare({ clientId: process.env.FOURSQUARE_ID, clientSecret: process.env.FOURSQUARE_SECRET }), // Freshbooks({ clientId: process.env.FRESHBOOKS_ID, clientSecret: process.env.FRESHBOOKS_SECRET }), GitHub({ clientId: process.env.GITHUB_ID, clientSecret: process.env.GITHUB_SECRET }), diff --git a/packages/core/src/providers/apple.ts b/packages/core/src/providers/apple.ts index 5ab1688507..9bd2f180dc 100644 --- a/packages/core/src/providers/apple.ts +++ b/packages/core/src/providers/apple.ts @@ -102,12 +102,11 @@ export default function Apple

( return { id: "apple", name: "Apple", - type: "oauth", - wellKnown: "https://appleid.apple.com/.well-known/openid-configuration", + type: "oidc", + issuer: "https://appleid.apple.com", authorization: { params: { scope: "name email", response_mode: "form_post" }, }, - idToken: true, profile(profile) { return { id: profile.sub, @@ -116,7 +115,6 @@ export default function Apple

( image: null, } }, - checks: ["pkce"], style: { logo: "/apple.svg", logoDark: "/apple-dark.svg", diff --git a/packages/core/src/providers/auth0.ts b/packages/core/src/providers/auth0.ts index 8ad5ead098..b812aedabc 100644 --- a/packages/core/src/providers/auth0.ts +++ b/packages/core/src/providers/auth0.ts @@ -13,19 +13,7 @@ export default function Auth0

( return { id: "auth0", name: "Auth0", - wellKnown: `${options.issuer}/.well-known/openid-configuration`, - type: "oauth", - authorization: { params: { scope: "openid email profile" } }, - checks: ["pkce", "state"], - idToken: true, - profile(profile) { - return { - id: profile.sub, - name: profile.nickname, - email: profile.email, - image: profile.picture, - } - }, + type: "oidc", style: { logo: "/auth0.svg", logoDark: "/auth0-dark.svg", diff --git a/packages/core/src/providers/authentik.ts b/packages/core/src/providers/authentik.ts index cacab1bbd7..8bd811944d 100644 --- a/packages/core/src/providers/authentik.ts +++ b/packages/core/src/providers/authentik.ts @@ -27,18 +27,7 @@ export default function Authentik

( return { id: "authentik", name: "Authentik", - wellKnown: `${options.issuer}/.well-known/openid-configuration`, - type: "oauth", - authorization: { params: { scope: "openid email profile" } }, - checks: ["pkce", "state"], - profile(profile) { - return { - id: profile.sub, - name: profile.name ?? profile.preferred_username, - email: profile.email, - image: profile.picture, - } - }, + type: "oidc", options, } } diff --git a/packages/core/src/providers/azure-ad-b2c.ts b/packages/core/src/providers/azure-ad-b2c.ts index fd32be87fc..7f55ff7287 100644 --- a/packages/core/src/providers/azure-ad-b2c.ts +++ b/packages/core/src/providers/azure-ad-b2c.ts @@ -24,15 +24,11 @@ export default function AzureADB2C

( } ): OAuthConfig

{ const { tenantId, primaryUserFlow } = options - const issuer = - options.issuer ?? - `https://${tenantId}.b2clogin.com/${tenantId}.onmicrosoft.com/${primaryUserFlow}/v2.0` + options.issuer ??= `https://${tenantId}.b2clogin.com/${tenantId}.onmicrosoft.com/${primaryUserFlow}/v2.0` return { id: "azure-ad-b2c", name: "Azure Active Directory B2C", - type: "oauth", - wellKnown: `${issuer}/.well-known/openid-configuration`, - idToken: true, + type: "oidc", profile(profile) { return { id: profile.sub, diff --git a/packages/core/src/providers/azure-ad.ts b/packages/core/src/providers/azure-ad.ts index 819bc621f2..a32bb29693 100644 --- a/packages/core/src/providers/azure-ad.ts +++ b/packages/core/src/providers/azure-ad.ts @@ -18,28 +18,18 @@ export default function AzureAD

( tenantId?: string } ): OAuthConfig

{ - const tenant = options.tenantId ?? "common" - const profilePhotoSize = options.profilePhotoSize ?? 48 + const { tenantId = "common", profilePhotoSize = 48, ...rest } = options return { id: "azure-ad", name: "Azure Active Directory", - type: "oauth", - wellKnown: `https://login.microsoftonline.com/${tenant}/v2.0/.well-known/openid-configuration?appid=${options.clientId}`, - authorization: { - params: { - scope: "openid profile email", - }, - }, + type: "oidc", + wellKnown: `https://login.microsoftonline.com/${tenantId}/v2.0/.well-known/openid-configuration?appid=${options.clientId}`, async profile(profile, tokens) { // https://docs.microsoft.com/en-us/graph/api/profilephoto-get?view=graph-rest-1.0#examples const profilePicture = await fetch( `https://graph.microsoft.com/v1.0/me/photos/${profilePhotoSize}x${profilePhotoSize}/$value`, - { - headers: { - Authorization: `Bearer ${tokens.access_token}`, - }, - } + { headers: { Authorization: `Bearer ${tokens.access_token}` } } ) // Confirm that profile photo was returned @@ -57,6 +47,7 @@ export default function AzureAD

( id: profile.sub, name: profile.name, email: profile.email, + image: null, } } }, @@ -68,6 +59,6 @@ export default function AzureAD

( bgDark: "#0072c6", textDark: "#fff", }, - options, + options: rest, } } diff --git a/packages/core/src/providers/battlenet.ts b/packages/core/src/providers/battlenet.ts index c921d5dfe6..e2bbfbafa9 100644 --- a/packages/core/src/providers/battlenet.ts +++ b/packages/core/src/providers/battlenet.ts @@ -16,8 +16,7 @@ export default function BattleNet

( return { id: "battlenet", name: "Battle.net", - type: "oauth", - wellKnown: `${options.issuer}/.well-known/openid-configuration`, + type: "oidc", profile(profile) { return { id: profile.sub, diff --git a/packages/core/src/providers/boxyhq-saml.ts b/packages/core/src/providers/boxyhq-saml.ts index a00e15d580..091341f0f6 100644 --- a/packages/core/src/providers/boxyhq-saml.ts +++ b/packages/core/src/providers/boxyhq-saml.ts @@ -14,13 +14,9 @@ export default function SAMLJackson

( id: "boxyhq-saml", name: "BoxyHQ SAML", type: "oauth", - version: "2.0", - checks: ["pkce", "state"], authorization: { url: `${options.issuer}/api/oauth/authorize`, - params: { - provider: "saml", - }, + params: { provider: "saml" }, }, token: `${options.issuer}/api/oauth/token`, userinfo: `${options.issuer}/api/oauth/userinfo`, diff --git a/packages/core/src/providers/cognito.ts b/packages/core/src/providers/cognito.ts index 26b92231c9..8627c00a2a 100644 --- a/packages/core/src/providers/cognito.ts +++ b/packages/core/src/providers/cognito.ts @@ -13,17 +13,7 @@ export default function Cognito

( return { id: "cognito", name: "Cognito", - type: "oauth", - wellKnown: `${options.issuer}/.well-known/openid-configuration`, - idToken: true, - profile(profile) { - return { - id: profile.sub, - name: profile.name, - email: profile.email, - image: profile.picture, - } - }, + type: "oidc", style: { logo: "/cognito.svg", logoDark: "/cognito.svg", diff --git a/packages/core/src/providers/dropbox.js b/packages/core/src/providers/dropbox.js index c1988a62e7..2c387bd9f7 100644 --- a/packages/core/src/providers/dropbox.js +++ b/packages/core/src/providers/dropbox.js @@ -45,7 +45,6 @@ export default function Dropbox(options) { image: profile.profile_photo_url, } }, - checks: ["state", "pkce"], options, } } diff --git a/packages/core/src/providers/duende-identity-server6.ts b/packages/core/src/providers/duende-identity-server6.ts index fd9f36fba3..94739251f4 100644 --- a/packages/core/src/providers/duende-identity-server6.ts +++ b/packages/core/src/providers/duende-identity-server6.ts @@ -13,19 +13,7 @@ export default function DuendeIdentityServer6

( return { id: "duende-identityserver6", name: "DuendeIdentityServer6", - type: "oauth", - wellKnown: `${options.issuer}/.well-known/openid-configuration`, - authorization: { params: { scope: "openid profile email" } }, - checks: ["pkce", "state"], - idToken: true, - profile(profile) { - return { - id: profile.sub, - name: profile.name, - email: profile.email, - image: null, - } - }, + type: "oidc", options, } } diff --git a/packages/core/src/providers/facebook.ts b/packages/core/src/providers/facebook.ts index a337ecd10d..67cefa32ef 100644 --- a/packages/core/src/providers/facebook.ts +++ b/packages/core/src/providers/facebook.ts @@ -19,18 +19,15 @@ export default function Facebook

( id: "facebook", name: "Facebook", type: "oauth", - authorization: "https://www.facebook.com/v11.0/dialog/oauth?scope=email", + authorization: "https://www.facebook.com/v15.0/dialog/oauth?scope=email", token: "https://graph.facebook.com/oauth/access_token", userinfo: { - url: "https://graph.facebook.com/me", // https://developers.facebook.com/docs/graph-api/reference/user/#fields - params: { fields: "id,name,email,picture" }, - async request({ tokens, client, provider }) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - return await client.userinfo(tokens.access_token!, { - // @ts-expect-error - params: provider.userinfo?.params, - }) + url: "https://graph.facebook.com/me?fields=id,name,email,picture", + async request({ tokens, provider }) { + return await fetch(provider.userinfo?.url as URL, { + headers: { Authorization: `Bearer ${tokens.access_token}` }, + }).then(async (res) => await res.json()) }, }, profile(profile: P) { diff --git a/packages/core/src/providers/github.ts b/packages/core/src/providers/github.ts index ef976d8bd1..0a882af1e9 100644 --- a/packages/core/src/providers/github.ts +++ b/packages/core/src/providers/github.ts @@ -70,15 +70,16 @@ export default function Github

( token: "https://github.com/login/oauth/access_token", userinfo: { url: "https://api.github.com/user", - async request({ client, tokens }) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const profile = await client.userinfo(tokens.access_token!) + async request({ tokens, provider }) { + const profile = await fetch(provider.userinfo?.url as URL, { + headers: { Authorization: `Bearer ${tokens.access_token}` }, + }).then(async (res) => await res.json()) if (!profile.email) { // If the user does not have a public email, get another via the GitHub API // See https://docs.github.com/en/rest/users/emails#list-public-email-addresses-for-the-authenticated-user const res = await fetch("https://api.github.com/user/emails", { - headers: { Authorization: `token ${tokens.access_token}` }, + headers: { Authorization: `Bearer ${tokens.access_token}` }, }) if (res.ok) { @@ -106,7 +107,6 @@ export default function Github

( text: "#000", textDark: "#fff", }, - checks: ["pkce"], options, } } diff --git a/packages/core/src/providers/gitlab.ts b/packages/core/src/providers/gitlab.ts index 65f0fd8d43..daae588fa2 100644 --- a/packages/core/src/providers/gitlab.ts +++ b/packages/core/src/providers/gitlab.ts @@ -52,13 +52,9 @@ export default function GitLab

( id: "gitlab", name: "GitLab", type: "oauth", - authorization: { - url: "https://gitlab.com/oauth/authorize", - params: { scope: "read_user" }, - }, + authorization: "https://gitlab.com/oauth/authorize?scope=read_user", token: "https://gitlab.com/oauth/token", userinfo: "https://gitlab.com/api/v4/user", - checks: ["pkce", "state"], profile(profile) { return { id: profile.id.toString(), diff --git a/packages/core/src/providers/google.ts b/packages/core/src/providers/google.ts index 49bcf29505..e477ebef47 100644 --- a/packages/core/src/providers/google.ts +++ b/packages/core/src/providers/google.ts @@ -24,19 +24,8 @@ export default function Google

( return { id: "google", name: "Google", - type: "oauth", - wellKnown: "https://accounts.google.com/.well-known/openid-configuration", - authorization: { params: { scope: "openid email profile" } }, - idToken: true, - checks: ["pkce", "state"], - profile(profile) { - return { - id: profile.sub, - name: profile.name, - email: profile.email, - image: profile.picture, - } - }, + type: "oidc", + issuer: "https://accounts.google.com", style: { logo: "/google.svg", logoDark: "/google.svg", diff --git a/packages/core/src/providers/hubspot.ts b/packages/core/src/providers/hubspot.ts index ed5d9b6cbf..125c6e5b33 100644 --- a/packages/core/src/providers/hubspot.ts +++ b/packages/core/src/providers/hubspot.ts @@ -11,12 +11,6 @@ interface HubSpotProfile extends Record { hub_id: string } -const HubSpotConfig = { - authorizationUrl: "https://app.hubspot.com/oauth/authorize", - tokenUrl: "https://api.hubapi.com/oauth/v1/token", - profileUrl: "https://api.hubapi.com/oauth/v1/access-tokens", -} - export default function HubSpot

( options: OAuthUserConfig

): OAuthConfig

{ @@ -24,33 +18,24 @@ export default function HubSpot

( id: "hubspot", name: "HubSpot", type: "oauth", - - ...HubSpotConfig, - authorization: { - url: HubSpotConfig.authorizationUrl, - params: { - scope: "oauth", - client_id: options.clientId, - }, + url: "https://app.hubspot.com/oauth/authorize", + params: { scope: "oauth", client_id: options.clientId }, }, + // @ts-expect-error TODO: support client_secret_post and other client options client: { token_endpoint_auth_method: "client_secret_post", }, - token: HubSpotConfig.tokenUrl, + token: "https://api.hubapi.com/oauth/v1/token", userinfo: { - url: HubSpotConfig.profileUrl, - async request(context) { - const url = `${HubSpotConfig.profileUrl}/${context.tokens.access_token}` + url: "https://api.hubapi.com/oauth/v1/access-tokens", + async request({ tokens, provider }) { + const url = `${provider.userinfo?.url}/${tokens.access_token}` - const response = await fetch(url, { - headers: { - "Content-Type": "application/json", - }, + return await fetch(url, { + headers: { "Content-Type": "application/json" }, method: "GET", - }) - - return await response.json() + }).then(async (res) => await res.json()) }, }, profile(profile) { diff --git a/packages/core/src/providers/identity-server4.js b/packages/core/src/providers/identity-server4.js index b105712b10..b4a2b72b00 100644 --- a/packages/core/src/providers/identity-server4.js +++ b/packages/core/src/providers/identity-server4.js @@ -3,19 +3,7 @@ export default function IdentityServer4(options) { return { id: "identity-server4", name: "IdentityServer4", - type: "oauth", - wellKnown: `${options.issuer}/.well-known/openid-configuration`, - authorization: { params: { scope: "openid profile email" } }, - checks: ["pkce", "state"], - idToken: true, - profile(profile) { - return { - id: profile.sub, - name: profile.name, - email: profile.email, - image: null, - } - }, + type: "oidc", options, } } diff --git a/packages/core/src/providers/index.ts b/packages/core/src/providers/index.ts index 96aaa95f65..69f013bae4 100644 --- a/packages/core/src/providers/index.ts +++ b/packages/core/src/providers/index.ts @@ -1,18 +1,16 @@ import type { OAuthConfig, OAuthProvider, OAuthProviderType } from "./oauth" - import type { EmailConfig, EmailProvider, EmailProviderType } from "./email" - import type { CredentialsConfig, CredentialsProvider, CredentialsProviderType, } from "./credentials" -export * from "./oauth" -export * from "./email" export * from "./credentials" +export * from "./email" +export * from "./oauth" -export type ProviderType = "oauth" | "email" | "credentials" +export type ProviderType = "oidc" | "oauth" | "email" | "credentials" export interface CommonProviderOptions { id: string diff --git a/packages/core/src/providers/instagram.js b/packages/core/src/providers/instagram.js index 41b5a79b25..71e4fcd9d6 100644 --- a/packages/core/src/providers/instagram.js +++ b/packages/core/src/providers/instagram.js @@ -35,6 +35,7 @@ export default function Instagram(options) { token: "https://api.instagram.com/oauth/access_token", userinfo: "https://graph.instagram.com/me?fields=id,username,account_type,name", + // @ts-expect-error TODO: support client options client: { token_endpoint_auth_method: "client_secret_post", }, diff --git a/packages/core/src/providers/kakao.ts b/packages/core/src/providers/kakao.ts index b82a92396a..c4a4542c0a 100644 --- a/packages/core/src/providers/kakao.ts +++ b/packages/core/src/providers/kakao.ts @@ -76,12 +76,13 @@ export default function Kakao

( authorization: "https://kauth.kakao.com/oauth/authorize?scope", token: "https://kauth.kakao.com/oauth/token", userinfo: "https://kapi.kakao.com/v2/user/me", + // @ts-expect-error TODO: support client options client: { token_endpoint_auth_method: "client_secret_post", }, profile(profile) { return { - id: String(profile.id), + id: profile.id.toString(), name: profile.kakao_account?.profile?.nickname, email: profile.kakao_account?.email, image: profile.kakao_account?.profile?.profile_image_url, diff --git a/packages/core/src/providers/keycloak.ts b/packages/core/src/providers/keycloak.ts index e3cfb5b3e9..d7cbccdfe3 100644 --- a/packages/core/src/providers/keycloak.ts +++ b/packages/core/src/providers/keycloak.ts @@ -30,19 +30,7 @@ export default function Keycloak

( return { id: "keycloak", name: "Keycloak", - wellKnown: `${options.issuer}/.well-known/openid-configuration`, - type: "oauth", - authorization: { params: { scope: "openid email profile" } }, - checks: ["pkce", "state"], - idToken: true, - profile(profile) { - return { - id: profile.sub, - name: profile.name ?? profile.preferred_username, - email: profile.email, - image: profile.picture, - } - }, + type: "oidc", style: { logo: "/keycloak.svg", logoDark: "/keycloak.svg", diff --git a/packages/core/src/providers/line.ts b/packages/core/src/providers/line.ts index 417680e78a..85ded7ca65 100644 --- a/packages/core/src/providers/line.ts +++ b/packages/core/src/providers/line.ts @@ -18,18 +18,9 @@ export default function LINE

( return { id: "line", name: "LINE", - type: "oauth", - authorization: { params: { scope: "openid profile" } }, - idToken: true, + type: "oidc", wellKnown: "https://access.line.me/.well-known/openid-configuration", - profile(profile) { - return { - id: profile.sub, - name: profile.name, - email: profile.email, - image: profile.picture, - } - }, + // @ts-expect-error TODO: support client options client: { id_token_signed_response_alg: "HS256", }, diff --git a/packages/core/src/providers/linkedin.ts b/packages/core/src/providers/linkedin.ts index deb63cd091..1fbf428a77 100644 --- a/packages/core/src/providers/linkedin.ts +++ b/packages/core/src/providers/linkedin.ts @@ -31,6 +31,7 @@ export default function LinkedIn

( params: { scope: "r_liteprofile r_emailaddress" }, }, token: "https://www.linkedin.com/oauth/v2/accessToken", + // @ts-expect-error TODO: support client options client: { token_endpoint_auth_method: "client_secret_post", }, diff --git a/packages/core/src/providers/mailru.js b/packages/core/src/providers/mailru.js index eee3cfd5fe..d7ff0e6d6c 100644 --- a/packages/core/src/providers/mailru.js +++ b/packages/core/src/providers/mailru.js @@ -7,14 +7,6 @@ export default function MailRu(options) { authorization: "https://oauth.mail.ru/login?scope=userinfo", token: "https://oauth.mail.ru/token", userinfo: "https://oauth.mail.ru/userinfo", - profile(profile) { - return { - id: profile.id, - name: profile.name, - email: profile.email, - image: profile.image, - } - }, options, } } diff --git a/packages/core/src/providers/naver.ts b/packages/core/src/providers/naver.ts index 301eff2847..a8688709fd 100644 --- a/packages/core/src/providers/naver.ts +++ b/packages/core/src/providers/naver.ts @@ -36,7 +36,6 @@ export default function Naver

( image: profile.response.profile_image, } }, - checks: ["state"], options, } } diff --git a/packages/core/src/providers/oauth.ts b/packages/core/src/providers/oauth.ts index 652d41e2a8..d7efc803c6 100644 --- a/packages/core/src/providers/oauth.ts +++ b/packages/core/src/providers/oauth.ts @@ -1,19 +1,13 @@ import type { CommonProviderOptions } from "../providers" import type { Profile, TokenSet, User, Awaitable } from ".." -import type { JWK } from "jose" - // TODO: type AuthorizationParameters = any type CallbackParamsType = any -type Issuer = any -type ClientMetadata = any type IssuerMetadata = any type OAuthCallbackChecks = any type OpenIDCallbackChecks = any -type Client = InstanceType - export type { OAuthProviderType } from "./oauth-types" type ChecksType = "pkce" | "state" | "none" | "nonce" @@ -26,10 +20,8 @@ type UrlParams = Record type EndpointRequest = ( context: C & { - /** `oauth4webapi` Client. TODO: */ - client: Client /** Provider is passed for convenience, ans also contains the `callbackUrl`. */ - provider: (OAuthConfig

| OAuthConfigInternal

) & { + provider: OAuthConfigInternal

& { signinUrl: string callbackUrl: string } @@ -102,7 +94,7 @@ export interface OAuthProviderButtonStyles { textDark: string } -export interface OAuthConfig

extends CommonProviderOptions, PartialIssuer { +export interface OAuth2Config

extends CommonProviderOptions, PartialIssuer { /** * OpenID Connect (OIDC) compliant providers can configure * this instead of `authorize`/`token`/`userinfo` options @@ -113,6 +105,7 @@ export interface OAuthConfig

extends CommonProviderOptions, PartialIssuer { * [Authorization Server Metadata](https://datatracker.ietf.org/doc/html/rfc8414#section-3) */ wellKnown?: string + issuer?: string /** * The login process will be initiated by sending the user to this URL. * @@ -122,45 +115,45 @@ export interface OAuthConfig

extends CommonProviderOptions, PartialIssuer { token?: string | TokenEndpointHandler userinfo?: string | UserinfoEndpointHandler type: "oauth" - version?: string - profile?: ProfileCallback

- checks?: ChecksType | ChecksType[] - client?: Partial - jwks?: { keys: JWK[] } - clientId?: string - clientSecret?: string /** - * If set to `true`, the user information will be extracted - * from the `id_token` claims, instead of - * making a request to the `userinfo` endpoint. + * Receives the profile object returned by the OAuth provider, and returns the user object. + * This will be used to create the user in the database. + * Defaults to: `id`, `email`, `name`, `image` * - * `id_token` is usually present in OpenID Connect (OIDC) compliant providers. + * [Documentation](https://next-auth.js.org/adapters/models#user) + */ + profile?: ProfileCallback

+ /** + * The CSRF protection performed on the callback endpoint. + * Defaults to `["pkce"]` if undefined. * - * [`id_token` explanation](https://www.oauth.com/oauth2-servers/openid-connect/id-tokens) + * [RFC 7636 - Proof Key for Code Exchange by OAuth Public Clients (PKCE)](https://www.rfc-editor.org/rfc/rfc7636.html#section-4) | + * [RFC 6749 - The OAuth 2.0 Authorization Framework](https://www.rfc-editor.org/rfc/rfc6749.html#section-4.1.1) | + * [OpenID Connect Core 1.0](https://openid.net/specs/openid-connect-core-1_0.html#IDToken) | */ - idToken?: boolean - // TODO: only allow for BattleNet - region?: string - // TODO: only allow for some - issuer?: string - + checks?: ChecksType[] + clientId?: string + clientSecret?: string style?: OAuthProviderButtonStyles - /** + * [Documentation](https://next-auth.js.org/configuration/providers/oauth#allowdangerousemailaccountlinking-option) + */ + allowDangerousEmailAccountLinking?: boolean + /** + * @internal * The options provided by the user. * We will perform a deep-merge of these values * with the default configuration. */ options?: OAuthUserConfig

+} - // These are kept around for backwards compatibility with OAuth 1.x - accessTokenUrl?: string - requestTokenUrl?: string - profileUrl?: string - encoding?: string - allowDangerousEmailAccountLinking?: boolean +export interface OIDCConfig

extends Omit, "type"> { + type: "oidc" } +export type OAuthConfig

= OIDCConfig

| OAuth2Config

+ export type OAuthEndpointType = "authorization" | "token" | "userinfo" /** diff --git a/packages/core/src/providers/okta.ts b/packages/core/src/providers/okta.ts index de2f855c7e..954d87cdbf 100644 --- a/packages/core/src/providers/okta.ts +++ b/packages/core/src/providers/okta.ts @@ -40,18 +40,7 @@ export default function Okta

( return { id: "okta", name: "Okta", - type: "oauth", - wellKnown: `${options.issuer}/.well-known/openid-configuration`, - authorization: { params: { scope: "openid email profile" } }, - idToken: true, - profile(profile) { - return { - id: profile.sub, - name: profile.name ?? profile.preferred_username, - email: profile.email, - image: profile.picture, - } - }, + type: "oidc", style: { logo: "/okta.svg", logoDark: "/okta-dark.svg", diff --git a/packages/core/src/providers/onelogin.js b/packages/core/src/providers/onelogin.js index c8593ae661..cdadb9dd96 100644 --- a/packages/core/src/providers/onelogin.js +++ b/packages/core/src/providers/onelogin.js @@ -3,18 +3,9 @@ export default function OneLogin(options) { return { id: "onelogin", name: "OneLogin", - type: "oauth", + type: "oidc", + // TODO: Verify if issuer has "oidc/2" and remove if it does wellKnown: `${options.issuer}/oidc/2/.well-known/openid-configuration`, - authorization: { params: { scope: "openid profile email" } }, - idToken: true, - profile(profile) { - return { - id: profile.sub, - name: profile.nickname, - email: profile.email, - image: profile.picture, - } - }, options, } } diff --git a/packages/core/src/providers/osso.js b/packages/core/src/providers/osso.js index 6a1e001b5e..7be1949ca4 100644 --- a/packages/core/src/providers/osso.js +++ b/packages/core/src/providers/osso.js @@ -7,14 +7,6 @@ export default function Osso(options) { authorization: `${options.issuer}oauth/authorize`, token: `${options.issuer}oauth/token`, userinfo: `${options.issuer}oauth/me`, - profile(profile) { - return { - id: profile.id, - name: profile.name, - email: profile.email, - image: null, - } - }, options, } } diff --git a/packages/core/src/providers/osu.ts b/packages/core/src/providers/osu.ts index c2790008c0..2c933ad976 100644 --- a/packages/core/src/providers/osu.ts +++ b/packages/core/src/providers/osu.ts @@ -57,12 +57,7 @@ export default function Osu

( name: "Osu!", type: "oauth", token: "https://osu.ppy.sh/oauth/token", - authorization: { - url: "https://osu.ppy.sh/oauth/authorize", - params: { - scope: "identify", - }, - }, + authorization: "https://osu.ppy.sh/oauth/authorize?scope=identify", userinfo: "https://osu.ppy.sh/api/v2/me", profile(profile) { return { diff --git a/packages/core/src/providers/patreon.ts b/packages/core/src/providers/patreon.ts index 63079f5e78..d8430ed2ac 100644 --- a/packages/core/src/providers/patreon.ts +++ b/packages/core/src/providers/patreon.ts @@ -14,7 +14,6 @@ export default function Patreon

( id: "patreon", name: "Patreon", type: "oauth", - version: "2.0", authorization: { url: "https://www.patreon.com/oauth2/authorize", params: { scope: "identity identity[email]" }, diff --git a/packages/core/src/providers/pinterest.ts b/packages/core/src/providers/pinterest.ts index 891dbd17fc..76a9645e46 100644 --- a/packages/core/src/providers/pinterest.ts +++ b/packages/core/src/providers/pinterest.ts @@ -18,7 +18,6 @@ export default function PinterestProvider

( url: "https://www.pinterest.com/oauth", params: { scope: "user_accounts:read" }, }, - checks: ["state"], token: "https://api.pinterest.com/v5/oauth/token", userinfo: "https://api.pinterest.com/v5/user_account", profile({ username, profile_image }) { diff --git a/packages/core/src/providers/pipedrive.ts b/packages/core/src/providers/pipedrive.ts index 5cdab2a2d2..b691d764b6 100644 --- a/packages/core/src/providers/pipedrive.ts +++ b/packages/core/src/providers/pipedrive.ts @@ -42,13 +42,12 @@ export default function Pipedrive

( id: "pipedrive", name: "Pipedrive", type: "oauth", - version: "2.0", authorization: "https://oauth.pipedrive.com/oauth/authorize", token: "https://oauth.pipedrive.com/oauth/token", userinfo: "https://api.pipedrive.com/users/me", profile: ({ data: profile }) => { return { - id: String(profile.id), + id: profile.id.toString(), name: profile.name, email: profile.email, image: profile.icon_url, diff --git a/packages/core/src/providers/reddit.js b/packages/core/src/providers/reddit.js index 814b32e005..fe5c2ff829 100644 --- a/packages/core/src/providers/reddit.js +++ b/packages/core/src/providers/reddit.js @@ -5,16 +5,8 @@ export default function Reddit(options) { name: "Reddit", type: "oauth", authorization: "https://www.reddit.com/api/v1/authorize?scope=identity", - token: " https://www.reddit.com/api/v1/access_token", + token: "https://www.reddit.com/api/v1/access_token", userinfo: "https://oauth.reddit.com/api/v1/me", - profile(profile) { - return { - id: profile.id, - name: profile.name, - email: null, - image: null, - } - }, options, } } diff --git a/packages/core/src/providers/salesforce.ts b/packages/core/src/providers/salesforce.ts index d4cd39c2e4..555b598b0c 100644 --- a/packages/core/src/providers/salesforce.ts +++ b/packages/core/src/providers/salesforce.ts @@ -26,7 +26,6 @@ export default function Salesforce

( image: profile.picture, } }, - checks: ["none"], options, } } diff --git a/packages/core/src/providers/slack.ts b/packages/core/src/providers/slack.ts index c1378da538..d51b4824d1 100644 --- a/packages/core/src/providers/slack.ts +++ b/packages/core/src/providers/slack.ts @@ -38,17 +38,8 @@ export default function Slack

( return { id: "slack", name: "Slack", - type: "oauth", - wellKnown: "https://slack.com/.well-known/openid-configuration", - authorization: { params: { scope: "openid profile email" } }, - profile(profile) { - return { - id: profile.sub, - name: profile.name, - email: profile.email, - image: profile.picture, - } - }, + type: "oidc", + issuer: "https://slack.com", style: { logo: "/slack.svg", logoDark: "/slack.svg", diff --git a/packages/core/src/providers/strava.ts b/packages/core/src/providers/strava.ts index ce20b33c7a..b81fe85c4e 100644 --- a/packages/core/src/providers/strava.ts +++ b/packages/core/src/providers/strava.ts @@ -26,6 +26,7 @@ export default function Strava

( url: "https://www.strava.com/api/v3/oauth/token", }, userinfo: "https://www.strava.com/api/v3/athlete", + // @ts-expect-error TODO: support client options client: { token_endpoint_auth_method: "client_secret_post", }, diff --git a/packages/core/src/providers/todoist.ts b/packages/core/src/providers/todoist.ts index 576bca392e..c576c369fd 100644 --- a/packages/core/src/providers/todoist.ts +++ b/packages/core/src/providers/todoist.ts @@ -22,11 +22,12 @@ export default function TodoistProvider

( params: { scope: "data:read" }, }, token: "https://todoist.com/oauth/access_token", + // @ts-expect-error TODO: support client options client: { token_endpoint_auth_method: "client_secret_post", }, userinfo: { - request: async ({ tokens }) => { + async request({ tokens }) { // To obtain Todoist user info, we need to call the Sync API // See https://developer.todoist.com/sync/v9 const res = await fetch("https://api.todoist.com/sync/v9/sync", { @@ -45,7 +46,7 @@ export default function TodoistProvider

( return profile }, }, - profile: async (profile) => { + profile(profile) { return { id: profile.id, email: profile.email, @@ -61,6 +62,6 @@ export default function TodoistProvider

( bgDark: "#000", textDark: "#E44332", }, - ...options, + options, } } diff --git a/packages/core/src/providers/trakt.ts b/packages/core/src/providers/trakt.ts index e3b720a7c4..cbb481c19d 100644 --- a/packages/core/src/providers/trakt.ts +++ b/packages/core/src/providers/trakt.ts @@ -22,25 +22,18 @@ export default function Trakt

( id: "trakt", name: "Trakt", type: "oauth", - authorization: { - url: "https://trakt.tv/oauth/authorize", - params: { scope: "" }, // when default, trakt returns auth error - }, + // when default, trakt returns auth error. TODO: Does it? + authorization: "https://trakt.tv/oauth/authorize?scope=", token: "https://api.trakt.tv/oauth/token", - userinfo: { - async request(context) { - const res = await fetch("https://api.trakt.tv/users/me?extended=full", { + async request({ tokens, provider }) { + return await fetch("https://api.trakt.tv/users/me?extended=full", { headers: { - Authorization: `Bearer ${context.tokens.access_token}`, + Authorization: `Bearer ${tokens.access_token}`, "trakt-api-version": "2", - "trakt-api-key": context.provider.clientId as string, + "trakt-api-key": provider.clientId, }, - }) - - if (res.ok) return await res.json() - - throw new Error("Expected 200 OK from the userinfo endpoint") + }).then(async (res) => await res.json()) }, }, profile(profile) { diff --git a/packages/core/src/providers/twitch.ts b/packages/core/src/providers/twitch.ts index 122aa91d81..338e0d6451 100644 --- a/packages/core/src/providers/twitch.ts +++ b/packages/core/src/providers/twitch.ts @@ -14,7 +14,7 @@ export default function Twitch

( issuer: "https://id.twitch.tv/oauth2", id: "twitch", name: "Twitch", - type: "oauth", + type: "oidc", authorization: { params: { scope: "openid user:read:email", @@ -27,15 +27,6 @@ export default function Twitch

( }, }, }, - idToken: true, - profile(profile) { - return { - id: profile.sub, - name: profile.preferred_username, - email: profile.email, - image: profile.picture, - } - }, style: { logo: "/twitch.svg", logoDark: "/twitch-dark.svg", diff --git a/packages/core/src/providers/twitter.ts b/packages/core/src/providers/twitter.ts index ce55c238a8..2bdb6d6a0f 100644 --- a/packages/core/src/providers/twitter.ts +++ b/packages/core/src/providers/twitter.ts @@ -1,3 +1,4 @@ +// @ts-nocheck TODO: Deprecate / move OAuth 1.0 import type { OAuthConfig, OAuthUserConfig } from "." export interface TwitterLegacyProfile { diff --git a/packages/core/src/providers/united-effects.ts b/packages/core/src/providers/united-effects.ts index 61315f35e7..0ca0ce646f 100644 --- a/packages/core/src/providers/united-effects.ts +++ b/packages/core/src/providers/united-effects.ts @@ -11,21 +11,10 @@ export default function UnitedEffects

( return { id: "united-effects", name: "United Effects", - wellKnown: `${options.issuer}/.well-known/openid-configuration`, - type: "oauth", + type: "oidc", authorization: { params: { scope: "openid email profile", resource: options.issuer }, }, - checks: ["pkce", "state"], - idToken: true, - profile(profile) { - return { - id: profile.sub, - name: null, - email: profile.email, - image: null, - } - }, options, } -} \ No newline at end of file +} diff --git a/packages/core/src/providers/vk.ts b/packages/core/src/providers/vk.ts index ee6aee9d49..41fd3eec5d 100644 --- a/packages/core/src/providers/vk.ts +++ b/packages/core/src/providers/vk.ts @@ -291,6 +291,7 @@ export default function VK

= VkProfile>( name: "VK", type: "oauth", authorization: `https://oauth.vk.com/authorize?scope=email&v=${apiVersion}`, + // @ts-expect-error TODO: support client options client: { token_endpoint_auth_method: "client_secret_post", }, diff --git a/packages/core/src/providers/wikimedia.ts b/packages/core/src/providers/wikimedia.ts index a3666eab88..6372d46753 100644 --- a/packages/core/src/providers/wikimedia.ts +++ b/packages/core/src/providers/wikimedia.ts @@ -169,15 +169,15 @@ export default function Wikimedia

( type: "oauth", token: "https://meta.wikimedia.org/w/rest.php/oauth2/access_token", userinfo: "https://meta.wikimedia.org/w/rest.php/oauth2/resource/profile", - authorization: { - url: "https://meta.wikimedia.org/w/rest.php/oauth2/authorize", - params: { scope: "" }, - }, + // TODO: is empty scope necessary? + authorization: + "https://meta.wikimedia.org/w/rest.php/oauth2/authorize?scope=", profile(profile) { return { id: profile.sub, name: profile.username, email: profile.email, + image: null, } }, style: { diff --git a/packages/core/src/providers/workos.ts b/packages/core/src/providers/workos.ts index 69e070b5e9..5f06c84fc3 100644 --- a/packages/core/src/providers/workos.ts +++ b/packages/core/src/providers/workos.ts @@ -29,9 +29,8 @@ export default function WorkOS

( name: "WorkOS", type: "oauth", authorization: `${issuer}sso/authorize`, - token: { - url: `${issuer}sso/token`, - }, + token: `${issuer}sso/token`, + // @ts-expect-error TODO: support client options client: { token_endpoint_auth_method: "client_secret_post", }, diff --git a/packages/core/src/providers/zitadel.ts b/packages/core/src/providers/zitadel.ts index 57ecbd25d5..60ec31838a 100644 --- a/packages/core/src/providers/zitadel.ts +++ b/packages/core/src/providers/zitadel.ts @@ -1,4 +1,4 @@ -import type { OAuthConfig, OAuthUserConfig } from "." +import type { OIDCConfig, OAuthUserConfig } from "." export interface ZitadelProfile extends Record { amr: string // Authentication Method References as defined in RFC8176 @@ -26,26 +26,11 @@ export interface ZitadelProfile extends Record { export default function Zitadel

( options: OAuthUserConfig

-): OAuthConfig

{ - const { issuer } = options - +): OIDCConfig

{ return { id: "zitadel", name: "ZITADEL", - type: "oauth", - version: "2", - wellKnown: `${issuer}/.well-known/openid-configuration`, - authorization: { params: { scope: "openid email profile" } }, - idToken: true, - checks: ["pkce", "state"], - async profile(profile) { - return { - id: profile.sub, - name: profile.name, - email: profile.email, - image: profile.picture, - } - }, + type: "oidc", options, } } From 563395928d96a4facd21ce6d5c33ed5b063ef5df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Thu, 8 Dec 2022 03:10:42 +0100 Subject: [PATCH 56/93] fix providers --- apps/dev/pages/api/auth/[...nextauth].ts | 116 +++++++++---------- packages/core/src/lib/assert.ts | 5 +- packages/core/src/providers/azure-ad.ts | 10 +- packages/core/src/providers/foursquare.js | 33 +----- packages/core/src/providers/freshbooks.js | 10 +- packages/core/src/providers/line.ts | 2 +- packages/core/src/providers/trakt.ts | 3 +- packages/core/src/providers/twitter.ts | 130 ++++++++-------------- 8 files changed, 120 insertions(+), 189 deletions(-) diff --git a/apps/dev/pages/api/auth/[...nextauth].ts b/apps/dev/pages/api/auth/[...nextauth].ts index 3f83b131c2..8991defbde 100644 --- a/apps/dev/pages/api/auth/[...nextauth].ts +++ b/apps/dev/pages/api/auth/[...nextauth].ts @@ -2,39 +2,39 @@ import { AuthHandler, AuthOptions } from "next-auth-core" // Providers -// import Apple from "next-auth/providers/apple" +import Apple from "next-auth-core/providers/apple" import Auth0 from "next-auth-core/providers/auth0" -// import AzureAD from "next-auth/providers/azure-ad" -// import AzureB2C from "next-auth/providers/azure-ad-b2c" -// import BoxyHQSAML from "next-auth/providers/boxyhq-saml" -// import Cognito from "next-auth/providers/cognito" +import AzureAD from "next-auth-core/providers/azure-ad" +import AzureB2C from "next-auth-core/providers/azure-ad-b2c" +import BoxyHQSAML from "next-auth-core/providers/boxyhq-saml" +// import Cognito from "next-auth-core/providers/cognito" import Credentials from "next-auth-core/providers/credentials" -// import Discord from "next-auth/providers/discord" -// import DuendeIDS6 from "next-auth/providers/duende-identity-server6" -// import Email from "next-auth/providers/email" -import Facebook from "next-auth/providers/facebook" -// import Foursquare from "next-auth/providers/foursquare" -// import Freshbooks from "next-auth/providers/freshbooks" +import Discord from "next-auth-core/providers/discord" +import DuendeIDS6 from "next-auth-core/providers/duende-identity-server6" +// import Email from "next-auth-core/providers/email" +import Facebook from "next-auth-core/providers/facebook" +import Foursquare from "next-auth-core/providers/foursquare" +import Freshbooks from "next-auth-core/providers/freshbooks" import GitHub from "next-auth-core/providers/github" -// import Gitlab from "next-auth/providers/gitlab" -// import Google from "next-auth/providers/google" -// import IDS4 from "next-auth/providers/identity-server4" -// import Instagram from "next-auth/providers/instagram" -// import Keycloak from "next-auth/providers/keycloak" -// import Line from "next-auth/providers/line" -// import LinkedIn from "next-auth/providers/linkedin" -// import Mailchimp from "next-auth/providers/mailchimp" -// import Okta from "next-auth/providers/okta" -// import Osu from "next-auth/providers/osu" -// import Patreon from "next-auth/providers/patreon" -// import Slack from "next-auth/providers/slack" -// import Spotify from "next-auth/providers/spotify" -// import Trakt from "next-auth/providers/trakt" -// import Twitch from "next-auth/providers/twitch" -// import Twitter, { TwitterLegacy } from "next-auth/providers/twitter" -// import Vk from "next-auth/providers/vk" -// import Wikimedia from "next-auth/providers/wikimedia" -// import WorkOS from "next-auth/providers/workos" +import Gitlab from "next-auth-core/providers/gitlab" +import Google from "next-auth-core/providers/google" +// import IDS4 from "next-auth-core/providers/identity-server4" +import Instagram from "next-auth-core/providers/instagram" +// import Keycloak from "next-auth-core/providers/keycloak" +import Line from "next-auth-core/providers/line" +import LinkedIn from "next-auth-core/providers/linkedin" +import Mailchimp from "next-auth-core/providers/mailchimp" +// import Okta from "next-auth-core/providers/okta" +import Osu from "next-auth-core/providers/osu" +import Patreon from "next-auth-core/providers/patreon" +import Slack from "next-auth-core/providers/slack" +import Spotify from "next-auth-core/providers/spotify" +import Trakt from "next-auth-core/providers/trakt" +import Twitch from "next-auth-core/providers/twitch" +import Twitter, { TwitterLegacy } from "next-auth-core/providers/twitter" +import Vk from "next-auth-core/providers/vk" +import Wikimedia from "next-auth-core/providers/wikimedia" +import WorkOS from "next-auth-core/providers/workos" // // Prisma // import { PrismaClient } from "@prisma/client" @@ -82,42 +82,42 @@ export const authOptions: AuthOptions = { return { name: "Fill Murray", email: "bill@fillmurray.com", image: "https://www.fillmurray.com/64/64", id: "1", foo: "" } }, }), - // Apple({ clientId: process.env.APPLE_ID, clientSecret: process.env.APPLE_SECRET }), + Apple({ clientId: process.env.APPLE_ID, clientSecret: process.env.APPLE_SECRET }), Auth0({ clientId: process.env.AUTH0_ID, clientSecret: process.env.AUTH0_SECRET, issuer: process.env.AUTH0_ISSUER }), - // AzureAD({ - // clientId: process.env.AZURE_AD_CLIENT_ID, - // clientSecret: process.env.AZURE_AD_CLIENT_SECRET, - // tenantId: process.env.AZURE_AD_TENANT_ID, - // }), - // AzureB2C({ clientId: process.env.AZURE_B2C_ID, clientSecret: process.env.AZURE_B2C_SECRET, issuer: process.env.AZURE_B2C_ISSUER }), - // BoxyHQSAML({ issuer: "https://jackson-demo.boxyhq.com", clientId: "tenant=boxyhq.com&product=saml-demo.boxyhq.com", clientSecret: "dummy" }), + AzureAD({ + clientId: process.env.AZURE_AD_CLIENT_ID, + clientSecret: process.env.AZURE_AD_CLIENT_SECRET, + tenantId: process.env.AZURE_AD_TENANT_ID, + }), + AzureB2C({ clientId: process.env.AZURE_B2C_ID, clientSecret: process.env.AZURE_B2C_SECRET, issuer: process.env.AZURE_B2C_ISSUER }), + BoxyHQSAML({ issuer: "https://jackson-demo.boxyhq.com", clientId: "tenant=boxyhq.com&product=saml-demo.boxyhq.com", clientSecret: "dummy" }), // Cognito({ clientId: process.env.COGNITO_ID, clientSecret: process.env.COGNITO_SECRET, issuer: process.env.COGNITO_ISSUER }), - // Discord({ clientId: process.env.DISCORD_ID, clientSecret: process.env.DISCORD_SECRET }), - // DuendeIDS6({ clientId: "interactive.confidential", clientSecret: "secret", issuer: "https://demo.duendesoftware.com" }), + Discord({ clientId: process.env.DISCORD_ID, clientSecret: process.env.DISCORD_SECRET }), + DuendeIDS6({ clientId: "interactive.confidential", clientSecret: "secret", issuer: "https://demo.duendesoftware.com" }), Facebook({ clientId: process.env.FACEBOOK_ID, clientSecret: process.env.FACEBOOK_SECRET }), - // Foursquare({ clientId: process.env.FOURSQUARE_ID, clientSecret: process.env.FOURSQUARE_SECRET }), - // Freshbooks({ clientId: process.env.FRESHBOOKS_ID, clientSecret: process.env.FRESHBOOKS_SECRET }), + Foursquare({ clientId: process.env.FOURSQUARE_ID, clientSecret: process.env.FOURSQUARE_SECRET }), + Freshbooks({ clientId: process.env.FRESHBOOKS_ID, clientSecret: process.env.FRESHBOOKS_SECRET }), GitHub({ clientId: process.env.GITHUB_ID, clientSecret: process.env.GITHUB_SECRET }), - // Gitlab({ clientId: process.env.GITLAB_ID, clientSecret: process.env.GITLAB_SECRET }), - // Google({ clientId: process.env.GOOGLE_ID, clientSecret: process.env.GOOGLE_SECRET }), + Gitlab({ clientId: process.env.GITLAB_ID, clientSecret: process.env.GITLAB_SECRET }), + Google({ clientId: process.env.GOOGLE_ID, clientSecret: process.env.GOOGLE_SECRET }), // IDS4({ clientId: process.env.IDS4_ID, clientSecret: process.env.IDS4_SECRET, issuer: process.env.IDS4_ISSUER }), - // Instagram({ clientId: process.env.INSTAGRAM_ID, clientSecret: process.env.INSTAGRAM_SECRET }), + Instagram({ clientId: process.env.INSTAGRAM_ID, clientSecret: process.env.INSTAGRAM_SECRET }), // Keycloak({ clientId: process.env.KEYCLOAK_ID, clientSecret: process.env.KEYCLOAK_SECRET, issuer: process.env.KEYCLOAK_ISSUER }), - // Line({ clientId: process.env.LINE_ID, clientSecret: process.env.LINE_SECRET }), - // LinkedIn({ clientId: process.env.LINKEDIN_ID, clientSecret: process.env.LINKEDIN_SECRET }), - // Mailchimp({ clientId: process.env.MAILCHIMP_ID, clientSecret: process.env.MAILCHIMP_SECRET }), + Line({ clientId: process.env.LINE_ID, clientSecret: process.env.LINE_SECRET }), + LinkedIn({ clientId: process.env.LINKEDIN_ID, clientSecret: process.env.LINKEDIN_SECRET }), + Mailchimp({ clientId: process.env.MAILCHIMP_ID, clientSecret: process.env.MAILCHIMP_SECRET }), // Okta({ clientId: process.env.OKTA_ID, clientSecret: process.env.OKTA_SECRET, issuer: process.env.OKTA_ISSUER }), - // Osu({ clientId: process.env.OSU_CLIENT_ID, clientSecret: process.env.OSU_CLIENT_SECRET }), - // Patreon({ clientId: process.env.PATREON_ID, clientSecret: process.env.PATREON_SECRET }), - // Slack({ clientId: process.env.SLACK_ID, clientSecret: process.env.SLACK_SECRET }), - // Spotify({ clientId: process.env.SPOTIFY_ID, clientSecret: process.env.SPOTIFY_SECRET }), - // Trakt({ clientId: process.env.TRAKT_ID, clientSecret: process.env.TRAKT_SECRET }), - // Twitch({ clientId: process.env.TWITCH_ID, clientSecret: process.env.TWITCH_SECRET }), - // Twitter({ version: "2.0", clientId: process.env.TWITTER_ID, clientSecret: process.env.TWITTER_SECRET }), + Osu({ clientId: process.env.OSU_CLIENT_ID, clientSecret: process.env.OSU_CLIENT_SECRET }), + Patreon({ clientId: process.env.PATREON_ID, clientSecret: process.env.PATREON_SECRET }), + Slack({ clientId: process.env.SLACK_ID, clientSecret: process.env.SLACK_SECRET }), + Spotify({ clientId: process.env.SPOTIFY_ID, clientSecret: process.env.SPOTIFY_SECRET }), + Trakt({ clientId: process.env.TRAKT_ID, clientSecret: process.env.TRAKT_SECRET }), + Twitch({ clientId: process.env.TWITCH_ID, clientSecret: process.env.TWITCH_SECRET }), + Twitter({ version: "2.0", clientId: process.env.TWITTER_ID, clientSecret: process.env.TWITTER_SECRET }), // TwitterLegacy({ clientId: process.env.TWITTER_LEGACY_ID, clientSecret: process.env.TWITTER_LEGACY_SECRET }), - // Vk({ clientId: process.env.VK_ID, clientSecret: process.env.VK_SECRET }), - // Wikimedia({ clientId: process.env.WIKIMEDIA_ID, clientSecret: process.env.WIKIMEDIA_SECRET }), - // WorkOS({ clientId: process.env.WORKOS_ID, clientSecret: process.env.WORKOS_SECRET }), + Vk({ clientId: process.env.VK_ID, clientSecret: process.env.VK_SECRET }), + Wikimedia({ clientId: process.env.WIKIMEDIA_ID, clientSecret: process.env.WIKIMEDIA_SECRET }), + WorkOS({ clientId: process.env.WORKOS_ID, clientSecret: process.env.WORKOS_SECRET }), ], } diff --git a/packages/core/src/lib/assert.ts b/packages/core/src/lib/assert.ts index 507155199e..b9e68cd103 100644 --- a/packages/core/src/lib/assert.ts +++ b/packages/core/src/lib/assert.ts @@ -98,9 +98,8 @@ export function assertConfig(params: { for (const provider of options.providers) { if ( - provider.type === "oauth" || - (provider.type === "oidc" && - !(provider.issuer ?? provider.options?.issuer)) + (provider.type === "oauth" || provider.type === "oidc") && + !(provider.issuer ?? provider.options?.issuer) ) { const { authorization: a, token: t, userinfo: u } = provider diff --git a/packages/core/src/providers/azure-ad.ts b/packages/core/src/providers/azure-ad.ts index a32bb29693..64c237fd76 100644 --- a/packages/core/src/providers/azure-ad.ts +++ b/packages/core/src/providers/azure-ad.ts @@ -19,22 +19,22 @@ export default function AzureAD

( } ): OAuthConfig

{ const { tenantId = "common", profilePhotoSize = 48, ...rest } = options - + rest.issuer ??= `https://login.microsoftonline.com/${tenantId}/v2.0` return { id: "azure-ad", name: "Azure Active Directory", type: "oidc", - wellKnown: `https://login.microsoftonline.com/${tenantId}/v2.0/.well-known/openid-configuration?appid=${options.clientId}`, + wellKnown: `${rest.issuer}}/.well-known/openid-configuration?appid=${options.clientId}`, async profile(profile, tokens) { // https://docs.microsoft.com/en-us/graph/api/profilephoto-get?view=graph-rest-1.0#examples - const profilePicture = await fetch( + const response = await fetch( `https://graph.microsoft.com/v1.0/me/photos/${profilePhotoSize}x${profilePhotoSize}/$value`, { headers: { Authorization: `Bearer ${tokens.access_token}` } } ) // Confirm that profile photo was returned - if (profilePicture.ok) { - const pictureBuffer = await profilePicture.arrayBuffer() + if (response.ok) { + const pictureBuffer = await response.arrayBuffer() const pictureBase64 = Buffer.from(pictureBuffer).toString("base64") return { id: profile.sub, diff --git a/packages/core/src/providers/foursquare.js b/packages/core/src/providers/foursquare.js index d5a2068b60..2afb7022d2 100644 --- a/packages/core/src/providers/foursquare.js +++ b/packages/core/src/providers/foursquare.js @@ -1,7 +1,3 @@ -import { get } from "https" -import { once } from "events" - -/** @type {import("src/providers").OAuthProvider} */ /** @type {import(".").OAuthProvider} */ export default function Foursquare(options) { const { apiVersion = "20210801" } = options @@ -12,32 +8,11 @@ export default function Foursquare(options) { authorization: "https://foursquare.com/oauth2/authenticate", token: "https://foursquare.com/oauth2/access_token", userinfo: { - async request({ tokens }) { - const url = new URL("https://api.foursquare.com/v2/users/self") - url.searchParams.append("v", apiVersion) + url: `https://api.foursquare.com/v2/users/self?v=${apiVersion}`, + request({ tokens, provider }) { + const url = new URL(provider.userinfo.url) url.searchParams.append("oauth_token", tokens.access_token) - - const req = get(url, { timeout: 3500 }) - const [response] = await Promise.race([ - once(req, "response"), - once(req, "timeout"), - ]) - - // timeout reached - if (!response) { - req.destroy() - throw new Error("HTTP Request Timed Out") - } - if (response.statusCode !== 200) { - throw new Error("Expected 200 OK from the userinfo endpoint") - } - - const parts = [] - for await (const part of response) { - parts.push(part) - } - - return JSON.parse(Buffer.concat(parts)) + return fetch(url).then((res) => res.json()) }, }, profile({ response: { profile } }) { diff --git a/packages/core/src/providers/freshbooks.js b/packages/core/src/providers/freshbooks.js index b253ed38b2..0205795e29 100644 --- a/packages/core/src/providers/freshbooks.js +++ b/packages/core/src/providers/freshbooks.js @@ -4,17 +4,15 @@ export default function Freshbooks(options) { id: "freshbooks", name: "Freshbooks", type: "oauth", - version: "2.0", - params: { grant_type: "authorization_code" }, - accessTokenUrl: "https://api.freshbooks.com/auth/oauth/token", - authorizationUrl: - "https://auth.freshbooks.com/service/auth/oauth/authorize?response_type=code", - profileUrl: "https://api.freshbooks.com/auth/api/v1/users/me", + authorization: "https://auth.freshbooks.com/service/auth/oauth/authorize", + token: "https://api.freshbooks.com/auth/oauth/token", + userinfo: "https://api.freshbooks.com/auth/api/v1/users/me", async profile(profile) { return { id: profile.response.id, name: `${profile.response.first_name} ${profile.response.last_name}`, email: profile.response.email, + image: null, } }, style: { diff --git a/packages/core/src/providers/line.ts b/packages/core/src/providers/line.ts index 85ded7ca65..ff15cd3237 100644 --- a/packages/core/src/providers/line.ts +++ b/packages/core/src/providers/line.ts @@ -19,7 +19,7 @@ export default function LINE

( id: "line", name: "LINE", type: "oidc", - wellKnown: "https://access.line.me/.well-known/openid-configuration", + issuer: "https://access.line.me", // @ts-expect-error TODO: support client options client: { id_token_signed_response_alg: "HS256", diff --git a/packages/core/src/providers/trakt.ts b/packages/core/src/providers/trakt.ts index cbb481c19d..929c408949 100644 --- a/packages/core/src/providers/trakt.ts +++ b/packages/core/src/providers/trakt.ts @@ -26,8 +26,9 @@ export default function Trakt

( authorization: "https://trakt.tv/oauth/authorize?scope=", token: "https://api.trakt.tv/oauth/token", userinfo: { + url: "https://api.trakt.tv/users/me?extended=full", async request({ tokens, provider }) { - return await fetch("https://api.trakt.tv/users/me?extended=full", { + return await fetch(provider.userinfo?.url as URL, { headers: { Authorization: `Bearer ${tokens.access_token}`, "trakt-api-version": "2", diff --git a/packages/core/src/providers/twitter.ts b/packages/core/src/providers/twitter.ts index 2bdb6d6a0f..764d5c89be 100644 --- a/packages/core/src/providers/twitter.ts +++ b/packages/core/src/providers/twitter.ts @@ -1,4 +1,4 @@ -// @ts-nocheck TODO: Deprecate / move OAuth 1.0 +// TODO: move OAuth 1.0 support or remove it? import type { OAuthConfig, OAuthUserConfig } from "." export interface TwitterLegacyProfile { @@ -96,42 +96,6 @@ export interface TwitterLegacyProfile { needs_phone_verification: boolean } -export function TwitterLegacy< - P extends Record = TwitterLegacyProfile ->(options: OAuthUserConfig

): OAuthConfig

{ - return { - id: "twitter", - name: "Twitter (Legacy)", - type: "oauth", - version: "1.0A", - authorization: "https://api.twitter.com/oauth/authenticate", - accessTokenUrl: "https://api.twitter.com/oauth/access_token", - requestTokenUrl: "https://api.twitter.com/oauth/request_token", - profileUrl: - "https://api.twitter.com/1.1/account/verify_credentials.json?include_email=true", - profile(profile) { - return { - id: profile.id_str, - name: profile.name, - email: profile.email, - image: profile.profile_image_url_https.replace( - /_normal\.(jpg|png|gif)$/, - ".$1" - ), - } - }, - style: { - logo: "/twitter.svg", - logoDark: "/twitter-dark.svg", - bg: "#fff", - text: "#1da1f2", - bgDark: "#1da1f2", - textDark: "#fff", - }, - options, - } -} - /** * [Documentation](https://developer.twitter.com/en/docs/twitter-api/users/lookup/api-reference/get-users-me) */ @@ -177,55 +141,49 @@ export interface TwitterProfile { export default function Twitter< P extends Record = TwitterLegacyProfile | TwitterProfile ->(options: OAuthUserConfig

): OAuthConfig

{ - if (options.version === "2.0") { - return { - id: "twitter", - name: "Twitter", - version: "2.0", - type: "oauth", - authorization: { - url: "https://twitter.com/i/oauth2/authorize", - params: { scope: "users.read tweet.read offline.access" }, - }, - token: { - url: "https://api.twitter.com/2/oauth2/token", - // TODO: Remove this - async request({ client, params, checks, provider }) { - const response = await client.oauthCallback( - provider.callbackUrl, - params, - checks, - { exchangeBody: { client_id: options.clientId } } - ) - return { tokens: response } - }, - }, - userinfo: { - url: "https://api.twitter.com/2/users/me", - params: { "user.fields": "profile_image_url" }, - }, - profile({ data }) { - return { - id: data.id, - name: data.name, - // NOTE: E-mail is currently unsupported by OAuth 2 Twitter. - email: null, - image: data.profile_image_url, - } - }, - checks: ["pkce", "state"], - style: { - logo: "/twitter.svg", - logoDark: "/twitter-dark.svg", - bg: "#fff", - text: "#1da1f2", - bgDark: "#1da1f2", - textDark: "#fff", +>(options: OAuthUserConfig

& { version?: "2.0" }): OAuthConfig

{ + return { + id: "twitter", + name: "Twitter", + type: "oauth", + authorization: { + url: "https://twitter.com/i/oauth2/authorize", + params: { scope: "users.read tweet.read offline.access" }, + }, + token: { + url: "https://api.twitter.com/2/oauth2/token", + // @ts-expect-error TODO: Remove this + async request({ client, params, checks, provider }) { + const response = await client.oauthCallback( + provider.callbackUrl, + params, + checks, + { exchangeBody: { client_id: options.clientId } } + ) + return { tokens: response } }, - options, - } + }, + userinfo: { + url: "https://api.twitter.com/2/users/me", + params: { "user.fields": "profile_image_url" }, + }, + profile({ data }) { + return { + id: data.id, + name: data.name, + // NOTE: E-mail is currently unsupported by OAuth 2 Twitter. + email: null, + image: data.profile_image_url, + } + }, + style: { + logo: "/twitter.svg", + logoDark: "/twitter-dark.svg", + bg: "#fff", + text: "#1da1f2", + bgDark: "#1da1f2", + textDark: "#fff", + }, + options, } - - return TwitterLegacy(options) } From 860c8b50b2b8b3a9754e76801f1308e43db303f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Thu, 8 Dec 2022 03:10:51 +0100 Subject: [PATCH 57/93] target es2020 --- packages/core/tsconfig.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json index 50476426af..47c2a5b009 100644 --- a/packages/core/tsconfig.json +++ b/packages/core/tsconfig.json @@ -10,6 +10,7 @@ "dom.iterable", "esnext" ], + "target": "ES2020", "module": "ESNext", "moduleResolution": "node", "outDir": "dist", From 187d38b4c1277ea8105df9cf2efbc732e04cc62b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Thu, 8 Dec 2022 03:33:29 +0100 Subject: [PATCH 58/93] fix CSS --- packages/core/package.json | 6 ++--- packages/core/scripts/generate-css.js | 22 +++++++++++++++++++ packages/core/src/css/index.ts | 2 -- packages/core/src/pages/index.ts | 2 +- .../core/src/{css/index.css => styles.css} | 0 packages/core/src/styles.ts | 2 ++ 6 files changed, 28 insertions(+), 6 deletions(-) create mode 100644 packages/core/scripts/generate-css.js delete mode 100644 packages/core/src/css/index.ts rename packages/core/src/{css/index.css => styles.css} (100%) create mode 100644 packages/core/src/styles.ts diff --git a/packages/core/package.json b/packages/core/package.json index 3d11e0f2ff..99deb972f9 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -44,8 +44,9 @@ } }, "scripts": { - "build": "tsc", - "dev": "tsc -w", + "build": "tsc && pnpm css", + "css": "node ./scripts/generate-css.js", + "dev": "pnpm css && tsc -w", "test": "jest" }, "devDependencies": { @@ -56,7 +57,6 @@ "autoprefixer": "10.4.13", "cssnano": "5.1.14", "postcss": "8.4.19", - "postcss-cli": "10.1.0", "postcss-nested": "6.0.0" } } diff --git a/packages/core/scripts/generate-css.js b/packages/core/scripts/generate-css.js new file mode 100644 index 0000000000..c0702697c6 --- /dev/null +++ b/packages/core/scripts/generate-css.js @@ -0,0 +1,22 @@ +import fs from "fs" +import path from "path" +import postcss from "postcss" + +import autoprefixer from "autoprefixer" +import postCssNested from "postcss-nested" +import cssNano from "cssnano" + +const from = path.join(process.cwd(), "src/styles.css") +const css = fs.readFileSync(from) + +const processedCss = await postcss([ + autoprefixer, + postCssNested, + cssNano({ preset: "default" }), +]).process(css, { from }) + +fs.writeFileSync( + path.join(process.cwd(), "src/styles.ts"), + `export default \`${processedCss.css}\` +// Run \`pnpm css\`` +) diff --git a/packages/core/src/css/index.ts b/packages/core/src/css/index.ts deleted file mode 100644 index 194715dc0d..0000000000 --- a/packages/core/src/css/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -// This file is automatically generated from index.css. Do not edit it manually. -export const css = `REPLACE_WITH_MINIFIED_CSS` diff --git a/packages/core/src/pages/index.ts b/packages/core/src/pages/index.ts index 19fc2f7f9e..6a7cd60d10 100644 --- a/packages/core/src/pages/index.ts +++ b/packages/core/src/pages/index.ts @@ -1,5 +1,5 @@ import renderToString from "preact-render-to-string" -import { css } from "../css" +import css from "../styles" import ErrorPage from "./error" import SigninPage from "./signin" import SignoutPage from "./signout" diff --git a/packages/core/src/css/index.css b/packages/core/src/styles.css similarity index 100% rename from packages/core/src/css/index.css rename to packages/core/src/styles.css diff --git a/packages/core/src/styles.ts b/packages/core/src/styles.ts new file mode 100644 index 0000000000..a88a9f2bf5 --- /dev/null +++ b/packages/core/src/styles.ts @@ -0,0 +1,2 @@ +export default `:root{--border-width:1px;--border-radius:0.5rem;--color-error:#c94b4b;--color-info:#157efb;--color-info-text:#fff}.__next-auth-theme-auto,.__next-auth-theme-light{--color-background:#fff;--color-text:#000;--color-primary:#444;--color-control-border:#bbb;--color-button-active-background:#f9f9f9;--color-button-active-border:#aaa;--color-seperator:#ccc}.__next-auth-theme-dark{--color-background:#000;--color-text:#fff;--color-primary:#ccc;--color-control-border:#555;--color-button-active-background:#060606;--color-button-active-border:#666;--color-seperator:#444}@media (prefers-color-scheme:dark){.__next-auth-theme-auto{--color-background:#000;--color-text:#fff;--color-primary:#ccc;--color-control-border:#555;--color-button-active-background:#060606;--color-button-active-border:#666;--color-seperator:#444}}body{background-color:var(--color-background);font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;margin:0;padding:0}h1{font-weight:400;margin-bottom:1.5rem;padding:0 1rem}h1,p{color:var(--color-text)}form{margin:0;padding:0}label{font-weight:500;margin-bottom:.25rem;text-align:left}input[type],label{color:var(--color-text);display:block}input[type]{background:var(--color-background);border:var(--border-width) solid var(--color-control-border);border-radius:var(--border-radius);box-shadow:inset 0 .1rem .2rem rgba(0,0,0,.2);box-sizing:border-box;font-size:1rem;padding:.5rem 1rem;width:100%}input[type]:focus{box-shadow:none}p{font-size:1.1rem;line-height:2rem;margin:0 0 1.5rem;padding:0 1rem}a.button{line-height:1rem;text-decoration:none}a.button:link,a.button:visited{background-color:var(--color-background);color:var(--color-primary)}a.button,button{align-items:center;background-color:var(--provider-bg,var(--color-background));border-color:rgba(0,0,0,.1);border-radius:var(--border-radius);box-shadow:0 0 0 0 #000,0 0 0 0 #000,0 10px 15px -3px rgba(0,0,0,.2),0 4px 6px -4px rgba(0,0,0,.1);color:var(--provider-color,var(--color-primary));display:flex;font-size:1.1rem;font-weight:500;justify-content:center;margin:0 0 .75rem;min-height:62px;padding:.75rem 1rem;position:relative;transition:all .1s ease-in-out}a.button:has(img),button:has(img){justify-content:unset}a.button:has(img) span,button:has(img) span{flex-grow:1}a.button:hover,button:hover{cursor:pointer}a.button:active,button:active{box-shadow:0 .15rem .3rem rgba(0,0,0,.15),inset 0 .1rem .2rem var(--color-background),inset 0 -.1rem .1rem rgba(0,0,0,.1);cursor:pointer}a.button #provider-logo,button #provider-logo{display:block}a.button #provider-logo-dark,button #provider-logo-dark{display:none}@media (prefers-color-scheme:dark){a.button,button{background-color:var(--provider-dark-bg,var(--color-background));border:1px solid #0d0d0d;box-shadow:0 0 0 0 #000,0 0 0 0 #ccc,0 5px 5px -3px hsla(0,0%,100%,.01),0 4px 6px -4px hsla(0,0%,100%,.05);color:var(--provider-dark-color,var(--color-primary))}#provider-logo{display:none!important}#provider-logo-dark{display:block!important}}a.site{color:var(--color-primary);font-size:1rem;line-height:2rem;text-decoration:none}a.site:hover{text-decoration:underline}.page{display:grid;height:100%;margin:0;padding:0;place-items:center;position:absolute;width:100%}.page>div{padding:.5rem;text-align:center}.error a.button{display:inline-block;margin-top:.5rem;padding-left:2rem;padding-right:2rem}.error .message{margin-bottom:1.5rem}.signin input[type=text]{display:block;margin-left:auto;margin-right:auto}.signin hr{border:0;border-top:1px solid var(--color-seperator);display:block;margin:1.5em auto 0;overflow:visible}.signin hr:before{background:var(--color-background);color:#888;content:"or";padding:0 .4rem;position:relative;top:-.6rem}.signin .error{background:#f5f5f5;background:var(--color-info);border-radius:.3rem;font-weight:500}.signin .error p{color:var(--color-info-text);font-size:.9rem;line-height:1.2rem;padding:.5rem 1rem;text-align:left}.signin form,.signin>div{display:block}.signin form input[type],.signin>div input[type]{margin-bottom:.5rem}.signin form button,.signin>div button{width:100%}.signin form,.signin>div{max-width:300px}.signout .message{margin-bottom:1.5rem}.logo{display:inline-block;margin-top:100px;max-height:150px;max-width:300px}.card{border:1px solid var(--color-control-border);border-radius:5px;margin:50px auto;max-width:-moz-max-content;max-width:max-content;padding:20px 50px}.card .header{color:var(--color-primary)}.section-header{color:var(--brand-color,var(--color-text))}` +// Run `pnpm css` \ No newline at end of file From 0b8d3fdfc6289c15cf6312ca7e612474041bfc60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Sun, 11 Dec 2022 16:06:53 +0100 Subject: [PATCH 59/93] update lock file --- pnpm-lock.yaml | 73 -------------------------------------------------- 1 file changed, 73 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4d6df67088..a73249ae0a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -450,7 +450,6 @@ importers: jose: 4.11.1 oauth4webapi: 2.0.4 postcss: 8.4.19 - postcss-cli: 10.1.0 postcss-nested: 6.0.0 preact: 10.11.3 preact-render-to-string: 5.2.3 @@ -471,7 +470,6 @@ importers: autoprefixer: 10.4.13_postcss@8.4.19 cssnano: 5.1.14_postcss@8.4.19 postcss: 8.4.19 - postcss-cli: 10.1.0_postcss@8.4.19 postcss-nested: 6.0.0_postcss@8.4.19 packages/next-auth: @@ -14538,15 +14536,6 @@ packages: universalify: 2.0.0 dev: true - /fs-extra/11.1.0: - resolution: {integrity: sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw==} - engines: {node: '>=14.14'} - dependencies: - graceful-fs: 4.2.10 - jsonfile: 6.1.0 - universalify: 2.0.0 - dev: true - /fs-extra/8.1.0: resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} engines: {node: '>=6 <7 || >=8'} @@ -20193,30 +20182,6 @@ packages: postcss-value-parser: 4.2.0 dev: true - /postcss-cli/10.1.0_postcss@8.4.19: - resolution: {integrity: sha512-Zu7PLORkE9YwNdvOeOVKPmWghprOtjFQU3srMUGbdz3pHJiFh7yZ4geiZFMkjMfB0mtTFR3h8RemR62rPkbOPA==} - engines: {node: '>=14'} - hasBin: true - peerDependencies: - postcss: ^8.0.0 - dependencies: - chokidar: 3.5.3 - dependency-graph: 0.11.0 - fs-extra: 11.1.0 - get-stdin: 9.0.0 - globby: 13.1.2 - picocolors: 1.0.0 - postcss: 8.4.19 - postcss-load-config: 4.0.1_postcss@8.4.19 - postcss-reporter: 7.0.5_postcss@8.4.19 - pretty-hrtime: 1.0.3 - read-cache: 1.0.0 - slash: 5.0.0 - yargs: 17.5.1 - transitivePeerDependencies: - - ts-node - dev: true - /postcss-cli/9.1.0_postcss@8.4.14: resolution: {integrity: sha512-zvDN2ADbWfza42sAnj+O2uUWyL0eRL1V+6giM2vi4SqTR3gTYy8XzcpfwccayF2szcUif0HMmXiEaDv9iEhcpw==} engines: {node: '>=12'} @@ -20388,23 +20353,6 @@ packages: yaml: 1.10.2 dev: true - /postcss-load-config/4.0.1_postcss@8.4.19: - resolution: {integrity: sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==} - engines: {node: '>= 14'} - peerDependencies: - postcss: '>=8.0.9' - ts-node: '>=9.0.0' - peerDependenciesMeta: - postcss: - optional: true - ts-node: - optional: true - dependencies: - lilconfig: 2.0.5 - postcss: 8.4.19 - yaml: 2.1.3 - dev: true - /postcss-loader/7.0.0_mepnsno3xmng6eyses4tepu7bu: resolution: {integrity: sha512-IDyttebFzTSY6DI24KuHUcBjbAev1i+RyICoPEWcAstZsj03r533uMXtDn506l6/wlsRYiS5XBdx7TpccCsyUg==} engines: {node: '>= 14.15.0'} @@ -20894,17 +20842,6 @@ packages: thenby: 1.3.4 dev: true - /postcss-reporter/7.0.5_postcss@8.4.19: - resolution: {integrity: sha512-glWg7VZBilooZGOFPhN9msJ3FQs19Hie7l5a/eE6WglzYqVeH3ong3ShFcp9kDWJT1g2Y/wd59cocf9XxBtkWA==} - engines: {node: '>=10'} - peerDependencies: - postcss: ^8.1.0 - dependencies: - picocolors: 1.0.0 - postcss: 8.4.19 - thenby: 1.3.4 - dev: true - /postcss-selector-parser/6.0.10: resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==} engines: {node: '>=4'} @@ -22808,11 +22745,6 @@ packages: engines: {node: '>=12'} dev: true - /slash/5.0.0: - resolution: {integrity: sha512-n6KkmvKS0623igEVj3FF0OZs1gYYJ0o0Hj939yc1fyxl2xt+xYpLnzJB6xBSqOfV9ZFLEWodBBN/heZJahuIJQ==} - engines: {node: '>=14.16'} - dev: true - /slice-ansi/4.0.0: resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} engines: {node: '>=10'} @@ -25429,11 +25361,6 @@ packages: engines: {node: '>= 6'} dev: true - /yaml/2.1.3: - resolution: {integrity: sha512-AacA8nRULjKMX2DvWvOAdBZMOfQlypSFkjcOcu9FalllIDJ1kvlREzcdIZmidQUqqeMv7jorHjq2HlLv/+c2lg==} - engines: {node: '>= 14'} - dev: true - /yargs-parser/20.2.9: resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} engines: {node: '>=10'} From fa864e1753713d06ab9a89229c41522458c837b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Mon, 12 Dec 2022 15:26:01 +0100 Subject: [PATCH 60/93] make logos optional --- packages/core/src/pages/signin.tsx | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/packages/core/src/pages/signin.tsx b/packages/core/src/pages/signin.tsx index db36c3f352..bae1d4634b 100644 --- a/packages/core/src/pages/signin.tsx +++ b/packages/core/src/pages/signin.tsx @@ -102,19 +102,22 @@ export default function SigninPage(props: SignInServerPageParams) { } as CSSProperties } > - - - + {provider.style?.logo && ( + + )} + {provider.style?.logoDark && ( + + )} Sign in with {provider.name} From 92cfb9199ea6223ad78cc37bf70353e2763e90dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Mon, 12 Dec 2022 15:28:39 +0100 Subject: [PATCH 61/93] sync with `next-auth` --- packages/core/src/errors.ts | 11 +++- packages/core/src/index.ts | 62 +++++++++++++++----- packages/core/src/init.ts | 40 +++++++------ packages/core/src/lib/assert.ts | 13 ++--- packages/core/src/lib/providers.ts | 4 +- packages/core/src/lib/web.ts | 66 ++++++++++++++-------- packages/core/src/pages/error.tsx | 3 +- packages/core/src/pages/signout.tsx | 3 +- packages/core/src/pages/verify-request.tsx | 3 +- packages/core/src/types.ts | 12 +--- packages/core/src/utils/parse-url.ts | 2 +- 11 files changed, 135 insertions(+), 84 deletions(-) diff --git a/packages/core/src/errors.ts b/packages/core/src/errors.ts index 2e3df56f62..3b03895157 100644 --- a/packages/core/src/errors.ts +++ b/packages/core/src/errors.ts @@ -1,4 +1,4 @@ -import type { EventCallbacks, LoggerInstance } from "." +import { EventCallbacks, LoggerInstance } from "./types" /** * Same as the default `Error`, but it is JSON serializable. @@ -76,6 +76,15 @@ export class InvalidEndpoints extends UnknownError { name = "InvalidEndpoints" code = "INVALID_ENDPOINTS_ERROR" } +export class UnknownAction extends UnknownError { + name = "UnknownAction" + code = "UNKNOWN_ACTION_ERROR" +} + +export class UntrustedHost extends UnknownError { + name = "UntrustedHost" + code = "UNTRUST_HOST_ERROR" +} type Method = (...args: any[]) => Promise diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 6341f179f1..c20c071877 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -8,9 +8,13 @@ import logger, { setLogger } from "./utils/logger" import type { ErrorType } from "./pages/error" import type { AuthOptions, RequestInternal, ResponseInternal } from "./types" +import { UntrustedHost } from "./errors" export * from "./types" +const configErrorMessage = + "There is a problem with the server configuration. Check the server logs for more information." + async function AuthHandlerInternal< Body extends string | Record | any[] >(params: { @@ -19,10 +23,9 @@ async function AuthHandlerInternal< /** REVIEW: Is this the best way to skip parsing the body in Node.js? */ parsedBody?: any }): Promise> { - const { options: userOptions, req } = params - setLogger(userOptions.logger, userOptions.debug) + const { options: authOptions, req } = params - const assertionResult = assertConfig({ options: userOptions, req }) + const assertionResult = assertConfig({ options: authOptions, req }) if (Array.isArray(assertionResult)) { assertionResult.forEach(logger.warn) @@ -32,18 +35,13 @@ async function AuthHandlerInternal< const htmlPages = ["signin", "signout", "error", "verify-request"] if (!htmlPages.includes(req.action) || req.method !== "GET") { - const message = `There is a problem with the server configuration. Check the server logs for more information.` return { status: 500, headers: { "Content-Type": "application/json" }, - body: { message } as any, + body: { message: configErrorMessage } 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 { pages, theme } = authOptions const authOnErrorPage = pages?.error && req.query?.callbackUrl?.startsWith(pages.error) @@ -66,13 +64,13 @@ async function AuthHandlerInternal< } } - const { action, providerId, error, method = "GET" } = req + const { action, providerId, error, method } = req const { options, cookies } = await init({ - userOptions, + authOptions, action, providerId, - host: req.host, + url: req.url, callbackUrl: req.body?.callbackUrl ?? req.query?.callbackUrl, csrfToken: req.body?.csrfToken, cookies: req.cookies, @@ -216,7 +214,7 @@ async function AuthHandlerInternal< } break case "_log": - if (userOptions.logger) { + if (authOptions.logger) { try { const { code, level, ...metadata } = req.body ?? {} logger[level](code, metadata) @@ -245,7 +243,41 @@ export async function AuthHandler( request: Request, options: AuthOptions ): Promise { + setLogger(options.logger, options.debug) + + if (!options.trustHost) { + const error = new UntrustedHost( + `Host must be trusted. URL was: ${request.url}` + ) + logger.error(error.code, error) + + return new Response(JSON.stringify({ message: configErrorMessage }), { + status: 500, + headers: { "Content-Type": "application/json" }, + }) + } + const req = await toInternalRequest(request) + if (req instanceof Error) { + logger.error((req as any).code, req) + return new Response( + `Error: This action with HTTP ${request.method} is not supported.`, + { status: 400 } + ) + } const internalResponse = await AuthHandlerInternal({ req, options }) - return toResponse(internalResponse) + + const response = await toResponse(internalResponse) + + // If the request expects a return URL, send it as JSON + // instead of doing an actual redirect. + const redirect = response.headers.get("Location") + if (request.headers.has("X-Auth-Return-Redirect") && redirect) { + response.headers.delete("Location") + response.headers.set("Content-Type", "application/json") + return new Response(JSON.stringify({ url: redirect }), { + headers: response.headers, + }) + } + return response } diff --git a/packages/core/src/init.ts b/packages/core/src/init.ts index 9f6b804900..ad014c6900 100644 --- a/packages/core/src/init.ts +++ b/packages/core/src/init.ts @@ -12,8 +12,8 @@ import parseUrl from "./utils/parse-url" import type { AuthOptions, InternalOptions, RequestInternal } from "." interface InitParams { - host?: string - userOptions: AuthOptions + url: URL + authOptions: AuthOptions providerId?: string action: InternalOptions["action"] /** Callback URL value extracted from the incoming request. */ @@ -27,10 +27,10 @@ interface InitParams { /** Initialize all internal options and cookies. */ export async function init({ - userOptions, + authOptions, providerId, action, - host, + url: reqUrl, cookies: reqCookies, callbackUrl: reqCallbackUrl, csrfToken: reqCsrfToken, @@ -39,7 +39,12 @@ export async function init({ options: InternalOptions cookies: cookie.Cookie[] }> { - const url = parseUrl(host) + // TODO: move this to web.ts + const parsed = parseUrl( + reqUrl.origin + + reqUrl.pathname.replace(`/${action}`, "").replace(`/${providerId}`, "") + ) + const url = new URL(parsed.toString()) /** * Secret used to salt cookies and tokens (e.g. for CSRF protection). @@ -49,15 +54,14 @@ export async function init({ * If no secret provided in production, we throw an error. */ const secret = - userOptions.secret ?? + authOptions.secret ?? // TODO: Remove this, always ask the user for a secret, even in dev! (Fix assert.ts too) - (await createHash(JSON.stringify({ ...url, ...userOptions }))) + (await createHash(JSON.stringify({ ...url, ...authOptions }))) const { providers, provider } = parseProviders({ - providers: userOptions.providers, + providers: authOptions.providers, url, providerId, - runtime: userOptions.__internal__?.runtime, }) const maxAge = 30 * 24 * 60 * 60 // Sessions expire after 30 days of being idle by default @@ -74,7 +78,7 @@ export async function init({ buttonText: "", }, // Custom options override defaults - ...userOptions, + ...authOptions, // These computed settings can have values in userOptions but we override them // and are request-specific. url, @@ -83,21 +87,21 @@ export async function init({ provider, cookies: { ...cookie.defaultCookies( - userOptions.useSecureCookies ?? url.base.startsWith("https://") + authOptions.useSecureCookies ?? url.protocol === "https:" ), // Allow user cookie options to override any cookie settings above - ...userOptions.cookies, + ...authOptions.cookies, }, secret, providers, // Session options session: { // If no adapter specified, force use of JSON Web Tokens (stateless) - strategy: userOptions.adapter ? "database" : "jwt", + strategy: authOptions.adapter ? "database" : "jwt", maxAge, updateAge: 24 * 60 * 60, generateSessionToken: crypto.randomUUID, - ...userOptions.session, + ...authOptions.session, }, // JWT options jwt: { @@ -105,13 +109,13 @@ export async function init({ maxAge, // same as session maxAge, encode: jwt.encode, decode: jwt.decode, - ...userOptions.jwt, + ...authOptions.jwt, }, // Event messages - events: eventsErrorHandler(userOptions.events ?? {}, logger), - adapter: adapterErrorHandler(userOptions.adapter, logger), + events: eventsErrorHandler(authOptions.events ?? {}, logger), + adapter: adapterErrorHandler(authOptions.adapter, logger), // Callback functions - callbacks: { ...defaultCallbacks, ...userOptions.callbacks }, + callbacks: { ...defaultCallbacks, ...authOptions.callbacks }, logger, callbackUrl: url.origin, } diff --git a/packages/core/src/lib/assert.ts b/packages/core/src/lib/assert.ts index b9e68cd103..fdd6177f41 100644 --- a/packages/core/src/lib/assert.ts +++ b/packages/core/src/lib/assert.ts @@ -8,7 +8,6 @@ import { MissingSecret, UnsupportedStrategy, } from "../errors" -import parseUrl from "../utils/parse-url" import { defaultCookies } from "./cookie" import type { AuthOptions, RequestInternal } from ".." @@ -48,11 +47,11 @@ export function assertConfig(params: { req: RequestInternal }): ConfigError | WarningCode[] { const { options, req } = params - + const { url } = req const warnings: WarningCode[] = [] if (!warned) { - if (!req.host) warnings.push("NEXTAUTH_URL") + if (!url.origin) warnings.push("NEXTAUTH_URL") // TODO: Make this throw an error in next major. This will also get rid of `NODE_ENV` if (!options.secret && process.env.NODE_ENV !== "production") @@ -74,21 +73,19 @@ export function assertConfig(params: { const callbackUrlParam = req.query?.callbackUrl as string | undefined - const url = parseUrl(req.host) - - if (callbackUrlParam && !isValidHttpUrl(callbackUrlParam, url.base)) { + if (callbackUrlParam && !isValidHttpUrl(callbackUrlParam, url.origin)) { return new InvalidCallbackUrl( `Invalid callback URL. Received: ${callbackUrlParam}` ) } const { callbackUrl: defaultCallbackUrl } = defaultCookies( - options.useSecureCookies ?? url.base.startsWith("https://") + options.useSecureCookies ?? url.protocol === "https://" ) const callbackUrlCookie = req.cookies?.[options.cookies?.callbackUrl?.name ?? defaultCallbackUrl.name] - if (callbackUrlCookie && !isValidHttpUrl(callbackUrlCookie, url.base)) { + if (callbackUrlCookie && !isValidHttpUrl(callbackUrlCookie, url.origin)) { return new InvalidCallbackUrl( `Invalid callback URL. Received: ${callbackUrlCookie}` ) diff --git a/packages/core/src/lib/providers.ts b/packages/core/src/lib/providers.ts index 9cbc5baef3..80f4e0b41e 100644 --- a/packages/core/src/lib/providers.ts +++ b/packages/core/src/lib/providers.ts @@ -8,7 +8,6 @@ import type { OAuthUserConfig, Provider, } from "../providers" -import type { InternalUrl } from "../utils/parse-url" /** * Adds `signinUrl` and `callbackUrl` to each provider @@ -16,9 +15,8 @@ import type { InternalUrl } from "../utils/parse-url" */ export default function parseProviders(params: { providers: Provider[] - url: InternalUrl + url: URL providerId?: string - runtime?: "web" | "nodejs" }): { providers: InternalProvider[] provider?: InternalProvider diff --git a/packages/core/src/lib/web.ts b/packages/core/src/lib/web.ts index 1ffbb32ad7..123b6b61dd 100644 --- a/packages/core/src/lib/web.ts +++ b/packages/core/src/lib/web.ts @@ -1,5 +1,7 @@ import { parse as parseCookie, serialize } from "cookie" -import type { AuthAction, RequestInternal, ResponseInternal } from ".." +import type { RequestInternal, ResponseInternal } from ".." +import { UnknownAction } from "../errors" +import type { AuthAction } from "../types" async function getBody(req: Request): Promise | undefined> { if (!("body" in req) || !req.body || req.method !== "POST") return @@ -12,31 +14,46 @@ async function getBody(req: Request): Promise | undefined> { return Object.fromEntries(params) } } +// prettier-ignore +const actions: AuthAction[] = [ "providers", "session", "csrf", "signin", "signout", "callback", "verify-request", "error", "_log" ] export async function toInternalRequest( req: Request -): Promise { - const url = new URL(req.url) - const nextauth = url.pathname.split("/").slice(3) - const headers = Object.fromEntries(req.headers) - const query: Record = Object.fromEntries(url.searchParams) +): Promise { + try { + // TODO: url.toString() should not include action and providerId + // see init.ts + const url = new URL(req.url.replace(/\/$/, "")) + const { pathname } = url - const cookieHeader = req.headers.get("cookie") ?? "" - const cookies = - parseCookie( - Array.isArray(cookieHeader) ? cookieHeader.join(";") : cookieHeader - ) ?? {} + const action = actions.find((a) => pathname.includes(a)) + if (!action) { + throw new UnknownAction("Cannot detect action.") + } - return { - action: nextauth[0] as AuthAction, - method: req.method, - headers, - body: req.body ? await getBody(req) : undefined, - cookies: cookies, - providerId: nextauth[1], - error: url.searchParams.get("error") ?? undefined, - host: new URL(req.url).origin, - query, + const providerIdOrAction = pathname.split("/").pop() + let providerId + if ( + providerIdOrAction && + !action.includes(providerIdOrAction) && + ["signin", "callback"].includes(action) + ) { + providerId = providerIdOrAction + } + + return { + url, + action, + providerId, + method: req.method ?? "GET", + headers: Object.fromEntries(req.headers), + body: req.body ? await getBody(req) : undefined, + cookies: parseCookie(req.headers.get("cookie") ?? "") ?? {}, + error: url.searchParams.get("error") ?? undefined, + query: Object.fromEntries(url.searchParams), + } + } catch (error) { + return error } } @@ -46,8 +63,11 @@ export function toResponse(res: ResponseInternal): Response { res.cookies?.forEach((cookie) => { const { name, value, options } = cookie const cookieHeader = serialize(name, value, options) - // FIXME: Should be .append. Cannot set multiple cookies right now. - headers.set("Set-Cookie", cookieHeader) + if (headers.has("Set-Cookie")) { + headers.append("Set-Cookie", cookieHeader) + } else { + headers.set("Set-Cookie", cookieHeader) + } }) const body = diff --git a/packages/core/src/pages/error.tsx b/packages/core/src/pages/error.tsx index a119bd98bb..38b4a8405f 100644 --- a/packages/core/src/pages/error.tsx +++ b/packages/core/src/pages/error.tsx @@ -1,5 +1,4 @@ import type { Theme } from ".." -import type { InternalUrl } from "../utils/parse-url" /** * The following errors are passed as error query parameters to the default or overridden error page. @@ -12,7 +11,7 @@ export type ErrorType = | "verification" export interface ErrorProps { - url?: InternalUrl + url?: URL theme?: Theme error?: ErrorType } diff --git a/packages/core/src/pages/signout.tsx b/packages/core/src/pages/signout.tsx index 1d8de05411..2108d3fcf5 100644 --- a/packages/core/src/pages/signout.tsx +++ b/packages/core/src/pages/signout.tsx @@ -1,8 +1,7 @@ import type { Theme } from ".." -import type { InternalUrl } from "../utils/parse-url" export interface SignoutProps { - url: InternalUrl + url: URL csrfToken: string theme: Theme } diff --git a/packages/core/src/pages/verify-request.tsx b/packages/core/src/pages/verify-request.tsx index 91b378ab4b..fdd827a0a5 100644 --- a/packages/core/src/pages/verify-request.tsx +++ b/packages/core/src/pages/verify-request.tsx @@ -1,8 +1,7 @@ import type { Theme } from ".." -import type { InternalUrl } from "../utils/parse-url" interface VerifyRequestPageProps { - url: InternalUrl + url: URL theme: Theme } diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index ff9f8fa85f..1cd8f5ef9e 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -15,7 +15,6 @@ import type { import type { JWT, JWTOptions } from "./jwt" import type { Cookie } from "./lib/cookie" import type { LoggerInstance } from "./utils/logger" -import type { InternalUrl } from "./utils/parse-url" export type Awaitable = T | PromiseLike @@ -211,7 +210,7 @@ export interface AuthOptions { * - ⚠ **This is an advanced option.** Advanced options are passed the same way as basic options, * but **may have complex implications** or side effects. * You should **try to avoid using advanced options** unless you are very comfortable using them. - * @default Boolean(process.env.AUTH_TRUST_HOST ?? process.env.VERCEL) + * @default Boolean(process.env.NEXTAUTH_URL ?? process.env.AUTH_TRUST_HOST ?? process.env.VERCEL) */ trustHost?: boolean /** @internal */ @@ -532,8 +531,7 @@ export type AuthAction = /** @internal */ export interface RequestInternal { - /** @default "http://localhost:3000" */ - host?: string + url: URL method?: string cookies?: Partial> headers?: Record @@ -561,11 +559,7 @@ export interface InternalOptions< WithVerificationToken = TProviderType extends "email" ? true : false > { providers: InternalProvider[] - /** - * Parsed from `NEXTAUTH_URL` or `x-forwarded-host` on Vercel. - * @default "http://localhost:3000/api/auth" - */ - url: InternalUrl + url: URL action: AuthAction provider: InternalProvider csrfToken?: string diff --git a/packages/core/src/utils/parse-url.ts b/packages/core/src/utils/parse-url.ts index 7f63b0ada2..e8e4bca913 100644 --- a/packages/core/src/utils/parse-url.ts +++ b/packages/core/src/utils/parse-url.ts @@ -1,4 +1,4 @@ -export interface InternalUrl { +interface InternalUrl { /** @default "http://localhost:3000" */ origin: string /** @default "localhost:3000" */ From 0469fc65fb91b167d1eaff7a8182aa4a459ba6be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Mon, 12 Dec 2022 15:29:04 +0100 Subject: [PATCH 62/93] clean up `next-auth/edge` --- apps/dev/pages/api/auth/[...nextauth].ts | 35 ++++++++++++------------ packages/core/package.json | 3 +- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/apps/dev/pages/api/auth/[...nextauth].ts b/apps/dev/pages/api/auth/[...nextauth].ts index 8991defbde..cb8335569a 100644 --- a/apps/dev/pages/api/auth/[...nextauth].ts +++ b/apps/dev/pages/api/auth/[...nextauth].ts @@ -31,7 +31,7 @@ import Slack from "next-auth-core/providers/slack" import Spotify from "next-auth-core/providers/spotify" import Trakt from "next-auth-core/providers/trakt" import Twitch from "next-auth-core/providers/twitch" -import Twitter, { TwitterLegacy } from "next-auth-core/providers/twitter" +import Twitter from "next-auth-core/providers/twitter" import Vk from "next-auth-core/providers/vk" import Wikimedia from "next-auth-core/providers/wikimedia" import WorkOS from "next-auth-core/providers/workos" @@ -113,7 +113,7 @@ export const authOptions: AuthOptions = { Spotify({ clientId: process.env.SPOTIFY_ID, clientSecret: process.env.SPOTIFY_SECRET }), Trakt({ clientId: process.env.TRAKT_ID, clientSecret: process.env.TRAKT_SECRET }), Twitch({ clientId: process.env.TWITCH_ID, clientSecret: process.env.TWITCH_SECRET }), - Twitter({ version: "2.0", clientId: process.env.TWITTER_ID, clientSecret: process.env.TWITTER_SECRET }), + Twitter({ clientId: process.env.TWITTER_ID, clientSecret: process.env.TWITTER_SECRET }), // TwitterLegacy({ clientId: process.env.TWITTER_LEGACY_ID, clientSecret: process.env.TWITTER_LEGACY_SECRET }), Vk({ clientId: process.env.VK_ID, clientSecret: process.env.VK_SECRET }), Wikimedia({ clientId: process.env.WIKIMEDIA_ID, clientSecret: process.env.WIKIMEDIA_SECRET }), @@ -132,25 +132,24 @@ if (authOptions.adapter) { // TODO: move to next-auth/edge function Auth(...args: any[]) { - if (args.length === 1) - return async (req: Request) => { - args[0].secret ??= process.env.NEXTAUTH_SECRET - - // TODO: remove when `next-auth/react` sends `X-Auth-Return-Redirect` - const shouldRedirect = req.method === "POST" && req.headers.get("Content-Type") === "application/json" ? (await req.clone().json()).json : false - - // TODO: This can be directly in core - const res = await AuthHandler(req, args[0]) - if (req.headers.get("X-Auth-Return-Redirect") || shouldRedirect) { - const url = res.headers.get("Location") - res.headers.delete("Location") - return new Response(JSON.stringify({ url }), res) - } - return res + const envSecret = process.env.NEXTAUTH_SECRET + const envTrustHost = !!(process.env.NEXTAUTH_URL ?? process.env.AUTH_TRUST_HOST ?? process.env.VERCEL ?? process.env.NODE_ENV !== "production") + if (args.length === 1) { + return (req: Request) => { + args[0].secret ??= envSecret + args[0].trustHost ??= envTrustHost + return AuthHandler(req, args[0]) } + } + args[1].secret ??= envSecret + args[1].trustHost ??= envTrustHost return AuthHandler(args[0], args[1]) } -export default Auth(authOptions) +// export default Auth(authOptions) + +export default function handle(request: Request) { + return Auth(request, authOptions) +} export const config = { runtime: "experimental-edge" } diff --git a/packages/core/package.json b/packages/core/package.json index 99deb972f9..5e55f358da 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -44,7 +44,8 @@ } }, "scripts": { - "build": "tsc && pnpm css", + "build": "pnpm clean && tsc && pnpm css", + "clean": "rm -rf dist", "css": "node ./scripts/generate-css.js", "dev": "pnpm css && tsc -w", "test": "jest" From 73026a4cadb705b043d0c5f9b3eecc71b0d1dd14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Mon, 12 Dec 2022 15:51:31 +0100 Subject: [PATCH 63/93] sync --- packages/core/src/errors.ts | 2 +- packages/next-auth/src/core/lib/web.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/src/errors.ts b/packages/core/src/errors.ts index 3b03895157..1f569103ed 100644 --- a/packages/core/src/errors.ts +++ b/packages/core/src/errors.ts @@ -1,4 +1,4 @@ -import { EventCallbacks, LoggerInstance } from "./types" +import type { EventCallbacks, LoggerInstance } from "./types" /** * Same as the default `Error`, but it is JSON serializable. diff --git a/packages/next-auth/src/core/lib/web.ts b/packages/next-auth/src/core/lib/web.ts index f66b47bd33..488fddc4e3 100644 --- a/packages/next-auth/src/core/lib/web.ts +++ b/packages/next-auth/src/core/lib/web.ts @@ -26,7 +26,7 @@ export async function toInternalRequest( cookies: cookies, providerId: nextauth[1], error: url.searchParams.get("error") ?? undefined, - host: new URL(req.url).origin, + url, query, } } From 5d15ac53ddccfbdcdd7f7061af5585b7e667d537 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Tue, 13 Dec 2022 12:08:50 +0100 Subject: [PATCH 64/93] remove uuid --- packages/core/package.json | 3 +-- packages/core/src/jwt/index.ts | 3 +-- pnpm-lock.yaml | 2 -- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/core/package.json b/packages/core/package.json index 5e55f358da..8292f9bb5d 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -32,8 +32,7 @@ "jose": "4.11.1", "oauth4webapi": "2.0.4", "preact": "10.11.3", - "preact-render-to-string": "5.2.3", - "uuid": "9.0.0" + "preact-render-to-string": "5.2.3" }, "peerDependencies": { "nodemailer": "6.8.0" diff --git a/packages/core/src/jwt/index.ts b/packages/core/src/jwt/index.ts index dcaeef4d02..323d8eee6f 100644 --- a/packages/core/src/jwt/index.ts +++ b/packages/core/src/jwt/index.ts @@ -1,6 +1,5 @@ import { EncryptJWT, jwtDecrypt } from "jose" import hkdf from "@panva/hkdf" -import { v4 as uuid } from "uuid" import { SessionStore } from "../lib/cookie" import type { JWT, JWTDecodeParams, JWTEncodeParams, JWTOptions } from "./types" import type { LoggerInstance } from ".." @@ -19,7 +18,7 @@ export async function encode(params: JWTEncodeParams) { .setProtectedHeader({ alg: "dir", enc: "A256GCM" }) .setIssuedAt() .setExpirationTime(now() + maxAge) - .setJti(uuid()) + .setJti(crypto.randomUUID()) .encrypt(encryptionSecret) } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a73249ae0a..5f697f7a0a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -453,7 +453,6 @@ importers: postcss-nested: 6.0.0 preact: 10.11.3 preact-render-to-string: 5.2.3 - uuid: 9.0.0 dependencies: '@panva/hkdf': 1.0.2 cookie: 0.5.0 @@ -461,7 +460,6 @@ importers: oauth4webapi: 2.0.4 preact: 10.11.3 preact-render-to-string: 5.2.3_preact@10.11.3 - uuid: 9.0.0 devDependencies: '@next-auth/tsconfig': link:../tsconfig '@types/node': 18.11.10 From e651df57a04c16d08217a2c35855c874c4a30afe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Tue, 13 Dec 2022 12:19:47 +0100 Subject: [PATCH 65/93] make secret required in dev --- packages/core/src/init.ts | 18 +++--------------- packages/core/src/lib/assert.ts | 7 +------ packages/core/src/types.ts | 11 ++++++----- packages/core/src/utils/logger.ts | 6 +----- 4 files changed, 11 insertions(+), 31 deletions(-) diff --git a/packages/core/src/init.ts b/packages/core/src/init.ts index ad014c6900..ff3622f952 100644 --- a/packages/core/src/init.ts +++ b/packages/core/src/init.ts @@ -5,7 +5,6 @@ import * as cookie from "./lib/cookie" import { createCSRFToken } from "./lib/csrf-token" import { defaultCallbacks } from "./lib/default-callbacks" import parseProviders from "./lib/providers" -import { createHash } from "./lib/web" import logger from "./utils/logger" import parseUrl from "./utils/parse-url" @@ -46,18 +45,6 @@ export async function init({ ) const url = new URL(parsed.toString()) - /** - * 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 = - authOptions.secret ?? - // TODO: Remove this, always ask the user for a secret, even in dev! (Fix assert.ts too) - (await createHash(JSON.stringify({ ...url, ...authOptions }))) - const { providers, provider } = parseProviders({ providers: authOptions.providers, url, @@ -92,7 +79,6 @@ export async function init({ // Allow user cookie options to override any cookie settings above ...authOptions.cookies, }, - secret, providers, // Session options session: { @@ -105,7 +91,9 @@ export async function init({ }, // JWT options jwt: { - secret, // Use application secret if no keys specified + // Asserted in assert.ts + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + secret: authOptions.secret!, maxAge, // same as session maxAge, encode: jwt.encode, decode: jwt.decode, diff --git a/packages/core/src/lib/assert.ts b/packages/core/src/lib/assert.ts index fdd6177f41..10e0f672b7 100644 --- a/packages/core/src/lib/assert.ts +++ b/packages/core/src/lib/assert.ts @@ -52,15 +52,10 @@ export function assertConfig(params: { if (!warned) { if (!url.origin) warnings.push("NEXTAUTH_URL") - - // TODO: Make this throw an error in next major. This will also get rid of `NODE_ENV` - if (!options.secret && process.env.NODE_ENV !== "production") - warnings.push("NO_SECRET") - if (options.debug) warnings.push("DEBUG_ENABLED") } - if (!options.secret && process.env.NODE_ENV === "production") { + if (!options.secret) { return new MissingSecret("Please define a `secret` in production.") } diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 1cd8f5ef9e..9cf77d7925 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -38,12 +38,13 @@ export interface AuthOptions { providers: Provider[] /** * A random string used to hash tokens, sign cookies and generate cryptographic keys. - * If not specified, it falls back to `jwt.secret` or `NEXTAUTH_SECRET` from environment variables. - * Otherwise, it will use a hash of all configuration options, including Client ID / Secrets for entropy. + * If not specified, it falls back to `AUTH_SECRET` or `NEXTAUTH_SECRET` from environment variables. + * To generate a random string, you can use the following command: * - * NOTE: The last behavior is extremely volatile, and will throw an error in production. - * * **Default value**: `string` (SHA hash of the "options" object) - * * **Required**: No - **but strongly recommended**! + * On Unix systems: `openssl rand -hex 32` + * Or go to https://generate-secret.vercel.app/32 + * + * @default process.env.AUTH_SECRET ?? process.env.NEXTAUTH_SECRET * * [Documentation](https://next-auth.js.org/configuration/options#secret) */ diff --git a/packages/core/src/utils/logger.ts b/packages/core/src/utils/logger.ts index 70c766a466..7919baa43f 100644 --- a/packages/core/src/utils/logger.ts +++ b/packages/core/src/utils/logger.ts @@ -19,11 +19,7 @@ function hasErrorProperty( return !!(x as any)?.error } -export type WarningCode = - | "NEXTAUTH_URL" - | "NO_SECRET" - | "TWITTER_OAUTH_2_BETA" - | "DEBUG_ENABLED" +export type WarningCode = "NEXTAUTH_URL" | "DEBUG_ENABLED" /** * Override any of the methods, and the rest will use the default logger. From 9d0c138b2db10104d5c2a897cd33db0a24684bd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Tue, 13 Dec 2022 12:26:04 +0100 Subject: [PATCH 66/93] remove todo comments --- packages/core/src/providers/azure-ad-b2c.ts | 1 - packages/core/src/providers/hubspot.ts | 3 --- packages/core/src/types.ts | 2 +- packages/core/src/utils/logger.ts | 1 - 4 files changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/core/src/providers/azure-ad-b2c.ts b/packages/core/src/providers/azure-ad-b2c.ts index 7f55ff7287..92cd50d7c7 100644 --- a/packages/core/src/providers/azure-ad-b2c.ts +++ b/packages/core/src/providers/azure-ad-b2c.ts @@ -34,7 +34,6 @@ export default function AzureADB2C

( id: profile.sub, name: profile.name, email: profile.emails[0], - // TODO: Find out how to retrieve the profile picture image: null, } }, diff --git a/packages/core/src/providers/hubspot.ts b/packages/core/src/providers/hubspot.ts index 125c6e5b33..94ddda8cd9 100644 --- a/packages/core/src/providers/hubspot.ts +++ b/packages/core/src/providers/hubspot.ts @@ -1,12 +1,9 @@ import type { OAuthConfig, OAuthUserConfig } from "." interface HubSpotProfile extends Record { - // TODO: figure out additional fields, for now using // https://legacydocs.hubspot.com/docs/methods/oauth2/get-access-token-information - user: string user_id: string - hub_domain: string hub_id: string } diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 9cf77d7925..e1a29ed881 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -550,7 +550,7 @@ export interface ResponseInternal< status?: number headers?: Headers | HeadersInit body?: Body - redirect?: URL | string // TODO: refactor to only allow URL + redirect?: URL | string cookies?: Cookie[] } diff --git a/packages/core/src/utils/logger.ts b/packages/core/src/utils/logger.ts index 7919baa43f..03c7c516e8 100644 --- a/packages/core/src/utils/logger.ts +++ b/packages/core/src/utils/logger.ts @@ -1,6 +1,5 @@ import { UnknownError } from "../errors" -// TODO: better typing /** Makes sure that error is always serializable */ function formatError(o: unknown): unknown { if (o instanceof Error && !(o instanceof UnknownError)) { From e9f0828c97d9e96710b96ad1248292d430389c86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Tue, 13 Dec 2022 12:35:22 +0100 Subject: [PATCH 67/93] pass through OAuth client options --- packages/core/src/lib/oauth/callback.ts | 3 +-- packages/core/src/providers/hubspot.ts | 1 - packages/core/src/providers/instagram.js | 1 - packages/core/src/providers/kakao.ts | 1 - packages/core/src/providers/line.ts | 1 - packages/core/src/providers/linkedin.ts | 1 - packages/core/src/providers/oauth.ts | 2 ++ packages/core/src/providers/strava.ts | 1 - packages/core/src/providers/todoist.ts | 1 - packages/core/src/providers/vk.ts | 1 - packages/core/src/providers/workos.ts | 1 - packages/next-auth/src/providers/oauth.ts | 3 --- 12 files changed, 3 insertions(+), 14 deletions(-) diff --git a/packages/core/src/lib/oauth/callback.ts b/packages/core/src/lib/oauth/callback.ts index 7b2509780e..e74882fb61 100644 --- a/packages/core/src/lib/oauth/callback.ts +++ b/packages/core/src/lib/oauth/callback.ts @@ -70,8 +70,7 @@ export async function handleOAuthCallback(params: { const client: o.Client = { client_id: provider.clientId, client_secret: provider.clientSecret, - token_endpoint_auth_method: "client_secret_basic", - // TODO: support other client options + ...provider.client, } const resCookies: Cookie[] = [] diff --git a/packages/core/src/providers/hubspot.ts b/packages/core/src/providers/hubspot.ts index 94ddda8cd9..be8b27c990 100644 --- a/packages/core/src/providers/hubspot.ts +++ b/packages/core/src/providers/hubspot.ts @@ -19,7 +19,6 @@ export default function HubSpot

( url: "https://app.hubspot.com/oauth/authorize", params: { scope: "oauth", client_id: options.clientId }, }, - // @ts-expect-error TODO: support client_secret_post and other client options client: { token_endpoint_auth_method: "client_secret_post", }, diff --git a/packages/core/src/providers/instagram.js b/packages/core/src/providers/instagram.js index 71e4fcd9d6..41b5a79b25 100644 --- a/packages/core/src/providers/instagram.js +++ b/packages/core/src/providers/instagram.js @@ -35,7 +35,6 @@ export default function Instagram(options) { token: "https://api.instagram.com/oauth/access_token", userinfo: "https://graph.instagram.com/me?fields=id,username,account_type,name", - // @ts-expect-error TODO: support client options client: { token_endpoint_auth_method: "client_secret_post", }, diff --git a/packages/core/src/providers/kakao.ts b/packages/core/src/providers/kakao.ts index c4a4542c0a..a3f889b7dc 100644 --- a/packages/core/src/providers/kakao.ts +++ b/packages/core/src/providers/kakao.ts @@ -76,7 +76,6 @@ export default function Kakao

( authorization: "https://kauth.kakao.com/oauth/authorize?scope", token: "https://kauth.kakao.com/oauth/token", userinfo: "https://kapi.kakao.com/v2/user/me", - // @ts-expect-error TODO: support client options client: { token_endpoint_auth_method: "client_secret_post", }, diff --git a/packages/core/src/providers/line.ts b/packages/core/src/providers/line.ts index ff15cd3237..2188a37501 100644 --- a/packages/core/src/providers/line.ts +++ b/packages/core/src/providers/line.ts @@ -20,7 +20,6 @@ export default function LINE

( name: "LINE", type: "oidc", issuer: "https://access.line.me", - // @ts-expect-error TODO: support client options client: { id_token_signed_response_alg: "HS256", }, diff --git a/packages/core/src/providers/linkedin.ts b/packages/core/src/providers/linkedin.ts index 1fbf428a77..deb63cd091 100644 --- a/packages/core/src/providers/linkedin.ts +++ b/packages/core/src/providers/linkedin.ts @@ -31,7 +31,6 @@ export default function LinkedIn

( params: { scope: "r_liteprofile r_emailaddress" }, }, token: "https://www.linkedin.com/oauth/v2/accessToken", - // @ts-expect-error TODO: support client options client: { token_endpoint_auth_method: "client_secret_post", }, diff --git a/packages/core/src/providers/oauth.ts b/packages/core/src/providers/oauth.ts index d7efc803c6..e70ed79b39 100644 --- a/packages/core/src/providers/oauth.ts +++ b/packages/core/src/providers/oauth.ts @@ -1,5 +1,6 @@ import type { CommonProviderOptions } from "../providers" import type { Profile, TokenSet, User, Awaitable } from ".." +import type { Client } from "oauth4webapi" // TODO: type AuthorizationParameters = any @@ -134,6 +135,7 @@ export interface OAuth2Config

extends CommonProviderOptions, PartialIssuer { checks?: ChecksType[] clientId?: string clientSecret?: string + client?: Partial style?: OAuthProviderButtonStyles /** * [Documentation](https://next-auth.js.org/configuration/providers/oauth#allowdangerousemailaccountlinking-option) diff --git a/packages/core/src/providers/strava.ts b/packages/core/src/providers/strava.ts index b81fe85c4e..ce20b33c7a 100644 --- a/packages/core/src/providers/strava.ts +++ b/packages/core/src/providers/strava.ts @@ -26,7 +26,6 @@ export default function Strava

( url: "https://www.strava.com/api/v3/oauth/token", }, userinfo: "https://www.strava.com/api/v3/athlete", - // @ts-expect-error TODO: support client options client: { token_endpoint_auth_method: "client_secret_post", }, diff --git a/packages/core/src/providers/todoist.ts b/packages/core/src/providers/todoist.ts index c576c369fd..dc31aedb85 100644 --- a/packages/core/src/providers/todoist.ts +++ b/packages/core/src/providers/todoist.ts @@ -22,7 +22,6 @@ export default function TodoistProvider

( params: { scope: "data:read" }, }, token: "https://todoist.com/oauth/access_token", - // @ts-expect-error TODO: support client options client: { token_endpoint_auth_method: "client_secret_post", }, diff --git a/packages/core/src/providers/vk.ts b/packages/core/src/providers/vk.ts index 41fd3eec5d..ee6aee9d49 100644 --- a/packages/core/src/providers/vk.ts +++ b/packages/core/src/providers/vk.ts @@ -291,7 +291,6 @@ export default function VK

= VkProfile>( name: "VK", type: "oauth", authorization: `https://oauth.vk.com/authorize?scope=email&v=${apiVersion}`, - // @ts-expect-error TODO: support client options client: { token_endpoint_auth_method: "client_secret_post", }, diff --git a/packages/core/src/providers/workos.ts b/packages/core/src/providers/workos.ts index 5f06c84fc3..088913cb1a 100644 --- a/packages/core/src/providers/workos.ts +++ b/packages/core/src/providers/workos.ts @@ -30,7 +30,6 @@ export default function WorkOS

( type: "oauth", authorization: `${issuer}sso/authorize`, token: `${issuer}sso/token`, - // @ts-expect-error TODO: support client options client: { token_endpoint_auth_method: "client_secret_post", }, diff --git a/packages/next-auth/src/providers/oauth.ts b/packages/next-auth/src/providers/oauth.ts index c7509f9edb..2e66fd8bef 100644 --- a/packages/next-auth/src/providers/oauth.ts +++ b/packages/next-auth/src/providers/oauth.ts @@ -140,9 +140,6 @@ export interface OAuthConfig

extends CommonProviderOptions, PartialIssuer { * [`id_token` explanation](https://www.oauth.com/oauth2-servers/openid-connect/id-tokens) */ idToken?: boolean - // TODO: only allow for BattleNet - region?: string - // TODO: only allow for some issuer?: string /** Read more at: https://github.com/panva/node-openid-client/tree/main/docs#customizing-http-requests */ httpOptions?: HttpOptions From f231016913ae47a697bff9892762258d94ac6b5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Tue, 13 Dec 2022 13:14:35 +0100 Subject: [PATCH 68/93] generate declaration map --- packages/core/package.json | 12 ++++++++---- packages/core/tsconfig.json | 2 ++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/core/package.json b/packages/core/package.json index 8292f9bb5d..e242fbc5c8 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -15,14 +15,18 @@ ], "type": "module", "types": "./dist/index.d.ts", + "files": [ + "dist", + "src" + ], "exports": { ".": { - "import": "./dist/index.js", - "types": "./dist/index.d.ts" + "types": "./dist/index.d.ts", + "import": "./dist/index.js" }, "./providers/*": { - "import": "./dist/providers/*.js", - "types": "./dist/providers/*.d.ts" + "types": "./dist/providers/*.d.ts", + "import": "./dist/providers/*.js" } }, "license": "ISC", diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json index 47c2a5b009..e79e696a31 100644 --- a/packages/core/tsconfig.json +++ b/packages/core/tsconfig.json @@ -19,6 +19,8 @@ "strict": false, "strictNullChecks": true, "stripInternal": true, + "declarationMap": true, + "declaration": true, }, "include": [ "src/**/*", From b2e66cc9619d21657a9693861beed256af0c6379 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Tue, 13 Dec 2022 13:30:33 +0100 Subject: [PATCH 69/93] default env secret to AUTH_SECRET --- apps/dev/pages/api/auth/[...nextauth].ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/apps/dev/pages/api/auth/[...nextauth].ts b/apps/dev/pages/api/auth/[...nextauth].ts index cb8335569a..f0a49fbce2 100644 --- a/apps/dev/pages/api/auth/[...nextauth].ts +++ b/apps/dev/pages/api/auth/[...nextauth].ts @@ -1,4 +1,3 @@ -// @ts-nocheck import { AuthHandler, AuthOptions } from "next-auth-core" // Providers @@ -132,13 +131,13 @@ if (authOptions.adapter) { // TODO: move to next-auth/edge function Auth(...args: any[]) { - const envSecret = process.env.NEXTAUTH_SECRET + const envSecret = process.env.AUTH_SECRET ?? process.env.NEXTAUTH_SECRET const envTrustHost = !!(process.env.NEXTAUTH_URL ?? process.env.AUTH_TRUST_HOST ?? process.env.VERCEL ?? process.env.NODE_ENV !== "production") if (args.length === 1) { - return (req: Request) => { + return async (req: Request) => { args[0].secret ??= envSecret args[0].trustHost ??= envTrustHost - return AuthHandler(req, args[0]) + return await AuthHandler(req, args[0]) } } args[1].secret ??= envSecret From f2d193b852f35ed662f46ced3dda01356698ef6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Tue, 13 Dec 2022 13:30:57 +0100 Subject: [PATCH 70/93] temporary Headers fix --- packages/core/src/lib/web.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/core/src/lib/web.ts b/packages/core/src/lib/web.ts index 123b6b61dd..e85e9f05bc 100644 --- a/packages/core/src/lib/web.ts +++ b/packages/core/src/lib/web.ts @@ -68,6 +68,7 @@ export function toResponse(res: ResponseInternal): Response { } else { headers.set("Set-Cookie", cookieHeader) } + // headers.set("Set-Cookie", cookieHeader) // TODO: Remove. Seems to be a bug with Headers in the runtime }) const body = From 90df63efead87c0c0201e9fe27eaa6234b85a8cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Tue, 13 Dec 2022 13:40:14 +0100 Subject: [PATCH 71/93] move pages to lib --- packages/core/src/index.ts | 4 +- packages/core/src/{ => lib}/pages/error.tsx | 2 +- packages/core/src/{ => lib}/pages/signin.tsx | 2 +- packages/core/src/{ => lib}/pages/signout.tsx | 2 +- .../src/{ => lib}/pages/verify-request.tsx | 2 +- packages/core/src/pages/index.ts | 87 ------------------- 6 files changed, 6 insertions(+), 93 deletions(-) rename packages/core/src/{ => lib}/pages/error.tsx (98%) rename packages/core/src/{ => lib}/pages/signin.tsx (99%) rename packages/core/src/{ => lib}/pages/signout.tsx (96%) rename packages/core/src/{ => lib}/pages/verify-request.tsx (95%) delete mode 100644 packages/core/src/pages/index.ts diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index c20c071877..58b1b29a50 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -2,11 +2,11 @@ import { init } from "./init" import { assertConfig } from "./lib/assert" import { SessionStore } from "./lib/cookie" import { toInternalRequest, toResponse } from "./lib/web" -import renderPage from "./pages" +import renderPage from "./lib/pages" import * as routes from "./routes" import logger, { setLogger } from "./utils/logger" -import type { ErrorType } from "./pages/error" +import type { ErrorType } from "./lib/pages/error" import type { AuthOptions, RequestInternal, ResponseInternal } from "./types" import { UntrustedHost } from "./errors" diff --git a/packages/core/src/pages/error.tsx b/packages/core/src/lib/pages/error.tsx similarity index 98% rename from packages/core/src/pages/error.tsx rename to packages/core/src/lib/pages/error.tsx index 38b4a8405f..a2ea8e5f64 100644 --- a/packages/core/src/pages/error.tsx +++ b/packages/core/src/lib/pages/error.tsx @@ -1,4 +1,4 @@ -import type { Theme } from ".." +import type { Theme } from "../.." /** * The following errors are passed as error query parameters to the default or overridden error page. diff --git a/packages/core/src/pages/signin.tsx b/packages/core/src/lib/pages/signin.tsx similarity index 99% rename from packages/core/src/pages/signin.tsx rename to packages/core/src/lib/pages/signin.tsx index bae1d4634b..0543ea5160 100644 --- a/packages/core/src/pages/signin.tsx +++ b/packages/core/src/lib/pages/signin.tsx @@ -1,4 +1,4 @@ -import type { InternalProvider, Theme } from ".." +import type { InternalProvider, Theme } from "../.." import type { CSSProperties } from "react" /** diff --git a/packages/core/src/pages/signout.tsx b/packages/core/src/lib/pages/signout.tsx similarity index 96% rename from packages/core/src/pages/signout.tsx rename to packages/core/src/lib/pages/signout.tsx index 2108d3fcf5..fb45c26c4d 100644 --- a/packages/core/src/pages/signout.tsx +++ b/packages/core/src/lib/pages/signout.tsx @@ -1,4 +1,4 @@ -import type { Theme } from ".." +import type { Theme } from "../.." export interface SignoutProps { url: URL diff --git a/packages/core/src/pages/verify-request.tsx b/packages/core/src/lib/pages/verify-request.tsx similarity index 95% rename from packages/core/src/pages/verify-request.tsx rename to packages/core/src/lib/pages/verify-request.tsx index fdd827a0a5..66280bb760 100644 --- a/packages/core/src/pages/verify-request.tsx +++ b/packages/core/src/lib/pages/verify-request.tsx @@ -1,4 +1,4 @@ -import type { Theme } from ".." +import type { Theme } from "../.." interface VerifyRequestPageProps { url: URL diff --git a/packages/core/src/pages/index.ts b/packages/core/src/pages/index.ts deleted file mode 100644 index 6a7cd60d10..0000000000 --- a/packages/core/src/pages/index.ts +++ /dev/null @@ -1,87 +0,0 @@ -import renderToString from "preact-render-to-string" -import css from "../styles" -import ErrorPage from "./error" -import SigninPage from "./signin" -import SignoutPage from "./signout" -import VerifyRequestPage from "./verify-request" - -import type { InternalOptions, RequestInternal, ResponseInternal } from ".." -import type { Cookie } from "../lib/cookie" -import type { ErrorType } from "./error" - -type RenderPageParams = { - query?: RequestInternal["query"] - cookies?: Cookie[] -} & Partial< - Pick< - InternalOptions, - "url" | "callbackUrl" | "csrfToken" | "providers" | "theme" - > -> - -/** - * Unless the user defines their [own pages](https://next-auth.js.org/configuration/pages), - * we render a set of default ones, using Preact SSR. - */ -export default function renderPage(params: RenderPageParams) { - const { url, theme, query, cookies } = params - - function send({ html, title, status }: any): ResponseInternal { - return { - cookies, - status, - headers: { "Content-Type": "text/html" }, - body: `${title}

${renderToString(html)}
`, - } - } - - return { - signin(props?: any) { - return send({ - html: SigninPage({ - csrfToken: params.csrfToken, - // We only want to render providers - providers: params.providers?.filter( - (provider) => - // Always render oauth and email type providers - ["email", "oauth", "oidc"].includes(provider.type) || - // Only render credentials type provider if credentials are defined - (provider.type === "credentials" && provider.credentials) || - // Don't render other provider types - false - ), - callbackUrl: params.callbackUrl, - theme, - ...query, - ...props, - }), - title: "Sign In", - }) - }, - signout(props?: any) { - return send({ - html: SignoutPage({ - csrfToken: params.csrfToken, - url, - theme, - ...props, - }), - title: "Sign Out", - }) - }, - verifyRequest(props?: any) { - return send({ - html: VerifyRequestPage({ url, theme, ...props }), - title: "Verify Request", - }) - }, - error(props?: { error?: ErrorType }) { - return send({ - ...ErrorPage({ url, theme, ...props }), - title: "Error", - }) - }, - } -} From c229183d2df58d9f5c7ae7d7ff253765c3be0dbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Tue, 13 Dec 2022 13:40:40 +0100 Subject: [PATCH 72/93] move errors to lib --- packages/core/src/index.ts | 2 +- packages/core/src/init.ts | 2 +- packages/core/src/lib/assert.ts | 2 +- packages/core/src/lib/callback-handler.ts | 2 +- packages/core/src/{ => lib}/errors.ts | 2 +- packages/core/src/lib/oauth/callback.ts | 2 +- packages/core/src/lib/web.ts | 2 +- packages/core/src/utils/logger.ts | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) rename packages/core/src/{ => lib}/errors.ts (98%) diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 58b1b29a50..d212bea7fe 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -8,7 +8,7 @@ import logger, { setLogger } from "./utils/logger" import type { ErrorType } from "./lib/pages/error" import type { AuthOptions, RequestInternal, ResponseInternal } from "./types" -import { UntrustedHost } from "./errors" +import { UntrustedHost } from "./lib/errors" export * from "./types" diff --git a/packages/core/src/init.ts b/packages/core/src/init.ts index ff3622f952..87134d34b5 100644 --- a/packages/core/src/init.ts +++ b/packages/core/src/init.ts @@ -1,4 +1,4 @@ -import { adapterErrorHandler, eventsErrorHandler } from "./errors" +import { adapterErrorHandler, eventsErrorHandler } from "./lib/errors" import * as jwt from "./jwt" import { createCallbackUrl } from "./lib/callback-url" import * as cookie from "./lib/cookie" diff --git a/packages/core/src/lib/assert.ts b/packages/core/src/lib/assert.ts index 10e0f672b7..8475ea98cf 100644 --- a/packages/core/src/lib/assert.ts +++ b/packages/core/src/lib/assert.ts @@ -7,7 +7,7 @@ import { MissingAuthorize, MissingSecret, UnsupportedStrategy, -} from "../errors" +} from "./errors" import { defaultCookies } from "./cookie" import type { AuthOptions, RequestInternal } from ".." diff --git a/packages/core/src/lib/callback-handler.ts b/packages/core/src/lib/callback-handler.ts index e6230fddee..f57e491f46 100644 --- a/packages/core/src/lib/callback-handler.ts +++ b/packages/core/src/lib/callback-handler.ts @@ -1,4 +1,4 @@ -import { AccountNotLinkedError } from "../errors" +import { AccountNotLinkedError } from "./errors" import { fromDate } from "../utils/date" import type { Account, InternalOptions, User } from ".." diff --git a/packages/core/src/errors.ts b/packages/core/src/lib/errors.ts similarity index 98% rename from packages/core/src/errors.ts rename to packages/core/src/lib/errors.ts index 1f569103ed..1b8ac37bb7 100644 --- a/packages/core/src/errors.ts +++ b/packages/core/src/lib/errors.ts @@ -1,4 +1,4 @@ -import type { EventCallbacks, LoggerInstance } from "./types" +import type { EventCallbacks, LoggerInstance } from "../types" /** * Same as the default `Error`, but it is JSON serializable. diff --git a/packages/core/src/lib/oauth/callback.ts b/packages/core/src/lib/oauth/callback.ts index e74882fb61..e31df48879 100644 --- a/packages/core/src/lib/oauth/callback.ts +++ b/packages/core/src/lib/oauth/callback.ts @@ -1,4 +1,4 @@ -import { OAuthCallbackError } from "../../errors" +import { OAuthCallbackError } from "../errors" import { useNonce } from "./nonce-handler" import { usePKCECodeVerifier } from "./pkce-handler" import { useState } from "./state-handler" diff --git a/packages/core/src/lib/web.ts b/packages/core/src/lib/web.ts index e85e9f05bc..a228d1f962 100644 --- a/packages/core/src/lib/web.ts +++ b/packages/core/src/lib/web.ts @@ -1,6 +1,6 @@ import { parse as parseCookie, serialize } from "cookie" import type { RequestInternal, ResponseInternal } from ".." -import { UnknownAction } from "../errors" +import { UnknownAction } from "./errors" import type { AuthAction } from "../types" async function getBody(req: Request): Promise | undefined> { diff --git a/packages/core/src/utils/logger.ts b/packages/core/src/utils/logger.ts index 03c7c516e8..ee44e95188 100644 --- a/packages/core/src/utils/logger.ts +++ b/packages/core/src/utils/logger.ts @@ -1,4 +1,4 @@ -import { UnknownError } from "../errors" +import { UnknownError } from "../lib/errors" /** Makes sure that error is always serializable */ function formatError(o: unknown): unknown { From ffee34df90af28628e728219cdfd188e23c12517 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Tue, 13 Dec 2022 13:41:14 +0100 Subject: [PATCH 73/93] move pages/index to lib --- packages/core/src/lib/pages/index.ts | 87 ++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 packages/core/src/lib/pages/index.ts diff --git a/packages/core/src/lib/pages/index.ts b/packages/core/src/lib/pages/index.ts new file mode 100644 index 0000000000..1def2a4d07 --- /dev/null +++ b/packages/core/src/lib/pages/index.ts @@ -0,0 +1,87 @@ +import renderToString from "preact-render-to-string" +import css from "../../styles" +import ErrorPage from "./error" +import SigninPage from "./signin" +import SignoutPage from "./signout" +import VerifyRequestPage from "./verify-request" + +import type { InternalOptions, RequestInternal, ResponseInternal } from "../.." +import type { Cookie } from "../cookie" +import type { ErrorType } from "./error" + +type RenderPageParams = { + query?: RequestInternal["query"] + cookies?: Cookie[] +} & Partial< + Pick< + InternalOptions, + "url" | "callbackUrl" | "csrfToken" | "providers" | "theme" + > +> + +/** + * Unless the user defines their [own pages](https://next-auth.js.org/configuration/pages), + * we render a set of default ones, using Preact SSR. + */ +export default function renderPage(params: RenderPageParams) { + const { url, theme, query, cookies } = params + + function send({ html, title, status }: any): ResponseInternal { + return { + cookies, + status, + headers: { "Content-Type": "text/html" }, + body: `${title}
${renderToString(html)}
`, + } + } + + return { + signin(props?: any) { + return send({ + html: SigninPage({ + csrfToken: params.csrfToken, + // We only want to render providers + providers: params.providers?.filter( + (provider) => + // Always render oauth and email type providers + ["email", "oauth", "oidc"].includes(provider.type) || + // Only render credentials type provider if credentials are defined + (provider.type === "credentials" && provider.credentials) || + // Don't render other provider types + false + ), + callbackUrl: params.callbackUrl, + theme, + ...query, + ...props, + }), + title: "Sign In", + }) + }, + signout(props?: any) { + return send({ + html: SignoutPage({ + csrfToken: params.csrfToken, + url, + theme, + ...props, + }), + title: "Sign Out", + }) + }, + verifyRequest(props?: any) { + return send({ + html: VerifyRequestPage({ url, theme, ...props }), + title: "Verify Request", + }) + }, + error(props?: { error?: ErrorType }) { + return send({ + ...ErrorPage({ url, theme, ...props }), + title: "Error", + }) + }, + } +} From d96947c27778e4d98f0af40ddf9075971ebc9511 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Tue, 13 Dec 2022 13:42:12 +0100 Subject: [PATCH 74/93] move routes to lib --- packages/core/src/index.ts | 2 +- packages/core/src/{ => lib}/routes/callback.ts | 0 packages/core/src/{ => lib}/routes/index.ts | 0 packages/core/src/{ => lib}/routes/providers.ts | 0 packages/core/src/{ => lib}/routes/session.ts | 0 packages/core/src/{ => lib}/routes/signin.ts | 0 packages/core/src/{ => lib}/routes/signout.ts | 0 7 files changed, 1 insertion(+), 1 deletion(-) rename packages/core/src/{ => lib}/routes/callback.ts (100%) rename packages/core/src/{ => lib}/routes/index.ts (100%) rename packages/core/src/{ => lib}/routes/providers.ts (100%) rename packages/core/src/{ => lib}/routes/session.ts (100%) rename packages/core/src/{ => lib}/routes/signin.ts (100%) rename packages/core/src/{ => lib}/routes/signout.ts (100%) diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index d212bea7fe..028c605076 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -3,7 +3,7 @@ import { assertConfig } from "./lib/assert" import { SessionStore } from "./lib/cookie" import { toInternalRequest, toResponse } from "./lib/web" import renderPage from "./lib/pages" -import * as routes from "./routes" +import * as routes from "./lib/routes" import logger, { setLogger } from "./utils/logger" import type { ErrorType } from "./lib/pages/error" diff --git a/packages/core/src/routes/callback.ts b/packages/core/src/lib/routes/callback.ts similarity index 100% rename from packages/core/src/routes/callback.ts rename to packages/core/src/lib/routes/callback.ts diff --git a/packages/core/src/routes/index.ts b/packages/core/src/lib/routes/index.ts similarity index 100% rename from packages/core/src/routes/index.ts rename to packages/core/src/lib/routes/index.ts diff --git a/packages/core/src/routes/providers.ts b/packages/core/src/lib/routes/providers.ts similarity index 100% rename from packages/core/src/routes/providers.ts rename to packages/core/src/lib/routes/providers.ts diff --git a/packages/core/src/routes/session.ts b/packages/core/src/lib/routes/session.ts similarity index 100% rename from packages/core/src/routes/session.ts rename to packages/core/src/lib/routes/session.ts diff --git a/packages/core/src/routes/signin.ts b/packages/core/src/lib/routes/signin.ts similarity index 100% rename from packages/core/src/routes/signin.ts rename to packages/core/src/lib/routes/signin.ts diff --git a/packages/core/src/routes/signout.ts b/packages/core/src/lib/routes/signout.ts similarity index 100% rename from packages/core/src/routes/signout.ts rename to packages/core/src/lib/routes/signout.ts From 1d9e5918f4f969f5de14e4588bc82d23482c5c00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Tue, 13 Dec 2022 13:42:43 +0100 Subject: [PATCH 75/93] move init to lib --- packages/core/src/index.ts | 2 +- packages/core/src/{ => lib}/init.ts | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) rename packages/core/src/{ => lib}/init.ts (89%) diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 028c605076..04959ff6da 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,4 +1,4 @@ -import { init } from "./init" +import { init } from "./lib/init" import { assertConfig } from "./lib/assert" import { SessionStore } from "./lib/cookie" import { toInternalRequest, toResponse } from "./lib/web" diff --git a/packages/core/src/init.ts b/packages/core/src/lib/init.ts similarity index 89% rename from packages/core/src/init.ts rename to packages/core/src/lib/init.ts index 87134d34b5..91781e7b9d 100644 --- a/packages/core/src/init.ts +++ b/packages/core/src/lib/init.ts @@ -1,14 +1,14 @@ -import { adapterErrorHandler, eventsErrorHandler } from "./lib/errors" -import * as jwt from "./jwt" -import { createCallbackUrl } from "./lib/callback-url" -import * as cookie from "./lib/cookie" -import { createCSRFToken } from "./lib/csrf-token" -import { defaultCallbacks } from "./lib/default-callbacks" -import parseProviders from "./lib/providers" -import logger from "./utils/logger" -import parseUrl from "./utils/parse-url" +import { adapterErrorHandler, eventsErrorHandler } from "./errors" +import * as jwt from "../jwt" +import { createCallbackUrl } from "./callback-url" +import * as cookie from "./cookie" +import { createCSRFToken } from "./csrf-token" +import { defaultCallbacks } from "./default-callbacks" +import parseProviders from "./providers" +import logger from "../utils/logger" +import parseUrl from "../utils/parse-url" -import type { AuthOptions, InternalOptions, RequestInternal } from "." +import type { AuthOptions, InternalOptions, RequestInternal } from ".." interface InitParams { url: URL From cb5a2adbc13f8f76972160a02aaeb7d2d8486f07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Tue, 13 Dec 2022 13:46:19 +0100 Subject: [PATCH 76/93] move styles to lib --- packages/core/scripts/generate-css.js | 6 +++--- packages/core/src/lib/pages/index.ts | 2 +- packages/core/src/{styles.css => lib/styles/index.css} | 0 packages/core/src/{styles.ts => lib/styles/index.ts} | 0 4 files changed, 4 insertions(+), 4 deletions(-) rename packages/core/src/{styles.css => lib/styles/index.css} (100%) rename packages/core/src/{styles.ts => lib/styles/index.ts} (100%) diff --git a/packages/core/scripts/generate-css.js b/packages/core/scripts/generate-css.js index c0702697c6..0a3af5d2be 100644 --- a/packages/core/scripts/generate-css.js +++ b/packages/core/scripts/generate-css.js @@ -6,7 +6,7 @@ import autoprefixer from "autoprefixer" import postCssNested from "postcss-nested" import cssNano from "cssnano" -const from = path.join(process.cwd(), "src/styles.css") +const from = path.join(process.cwd(), "src/lib/styles/index.css") const css = fs.readFileSync(from) const processedCss = await postcss([ @@ -16,7 +16,7 @@ const processedCss = await postcss([ ]).process(css, { from }) fs.writeFileSync( - path.join(process.cwd(), "src/styles.ts"), + path.join(process.cwd(), "src/lib/styles/index.ts"), `export default \`${processedCss.css}\` -// Run \`pnpm css\`` +// Generated by \`pnpm css\`` ) diff --git a/packages/core/src/lib/pages/index.ts b/packages/core/src/lib/pages/index.ts index 1def2a4d07..0ab4f369f8 100644 --- a/packages/core/src/lib/pages/index.ts +++ b/packages/core/src/lib/pages/index.ts @@ -1,5 +1,5 @@ import renderToString from "preact-render-to-string" -import css from "../../styles" +import css from "../styles" import ErrorPage from "./error" import SigninPage from "./signin" import SignoutPage from "./signout" diff --git a/packages/core/src/styles.css b/packages/core/src/lib/styles/index.css similarity index 100% rename from packages/core/src/styles.css rename to packages/core/src/lib/styles/index.css diff --git a/packages/core/src/styles.ts b/packages/core/src/lib/styles/index.ts similarity index 100% rename from packages/core/src/styles.ts rename to packages/core/src/lib/styles/index.ts From 3e3f9a92acfad5e4e1b4e105c3b3a5d678e462c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Tue, 13 Dec 2022 13:46:57 +0100 Subject: [PATCH 77/93] move types to lib --- packages/core/src/index.ts | 8 ++++++-- packages/core/src/lib/csrf-token.ts | 2 +- packages/core/src/lib/errors.ts | 2 +- packages/core/src/{ => lib}/types.ts | 10 +++++----- packages/core/src/lib/web.ts | 2 +- 5 files changed, 14 insertions(+), 10 deletions(-) rename packages/core/src/{ => lib}/types.ts (98%) diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 04959ff6da..2816e5d877 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -7,10 +7,14 @@ import * as routes from "./lib/routes" import logger, { setLogger } from "./utils/logger" import type { ErrorType } from "./lib/pages/error" -import type { AuthOptions, RequestInternal, ResponseInternal } from "./types" +import type { + AuthOptions, + RequestInternal, + ResponseInternal, +} from "./lib/types" import { UntrustedHost } from "./lib/errors" -export * from "./types" +export * from "./lib/types" const configErrorMessage = "There is a problem with the server configuration. Check the server logs for more information." diff --git a/packages/core/src/lib/csrf-token.ts b/packages/core/src/lib/csrf-token.ts index 15f75726ca..5864bd6e67 100644 --- a/packages/core/src/lib/csrf-token.ts +++ b/packages/core/src/lib/csrf-token.ts @@ -1,5 +1,5 @@ import { createHash, randomString } from "./web" -import type { InternalOptions } from "../types" +import type { InternalOptions } from "./types" interface CreateCSRFTokenParams { options: InternalOptions diff --git a/packages/core/src/lib/errors.ts b/packages/core/src/lib/errors.ts index 1b8ac37bb7..1f569103ed 100644 --- a/packages/core/src/lib/errors.ts +++ b/packages/core/src/lib/errors.ts @@ -1,4 +1,4 @@ -import type { EventCallbacks, LoggerInstance } from "../types" +import type { EventCallbacks, LoggerInstance } from "./types" /** * Same as the default `Error`, but it is JSON serializable. diff --git a/packages/core/src/types.ts b/packages/core/src/lib/types.ts similarity index 98% rename from packages/core/src/types.ts rename to packages/core/src/lib/types.ts index e1a29ed881..01c652bde3 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/lib/types.ts @@ -1,5 +1,5 @@ import type { CookieSerializeOptions } from "cookie" -import type { Adapter, AdapterUser } from "./adapters" +import type { Adapter, AdapterUser } from "../adapters" import type { CredentialInput, CredentialsConfig, @@ -7,14 +7,14 @@ import type { OAuthConfigInternal, Provider, ProviderType, -} from "./providers" +} from "../providers" import type { OAuth2TokenEndpointResponse, OpenIDTokenEndpointResponse, } from "oauth4webapi" -import type { JWT, JWTOptions } from "./jwt" -import type { Cookie } from "./lib/cookie" -import type { LoggerInstance } from "./utils/logger" +import type { JWT, JWTOptions } from "../jwt" +import type { Cookie } from "./cookie" +import type { LoggerInstance } from "../utils/logger" export type Awaitable = T | PromiseLike diff --git a/packages/core/src/lib/web.ts b/packages/core/src/lib/web.ts index a228d1f962..697bcd0755 100644 --- a/packages/core/src/lib/web.ts +++ b/packages/core/src/lib/web.ts @@ -1,7 +1,7 @@ import { parse as parseCookie, serialize } from "cookie" import type { RequestInternal, ResponseInternal } from ".." import { UnknownAction } from "./errors" -import type { AuthAction } from "../types" +import type { AuthAction } from "./types" async function getBody(req: Request): Promise | undefined> { if (!("body" in req) || !req.body || req.method !== "POST") return From 0939b13f288eb05f5ecefec37ef1caf64f485bae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Tue, 13 Dec 2022 13:50:08 +0100 Subject: [PATCH 78/93] move utils to lib --- packages/core/src/index.ts | 2 +- packages/core/src/lib/assert.ts | 2 +- packages/core/src/lib/callback-handler.ts | 2 +- packages/core/src/lib/init.ts | 4 ++-- packages/core/src/lib/providers.ts | 2 +- packages/core/src/lib/routes/session.ts | 6 +++--- packages/core/src/lib/types.ts | 2 +- packages/core/src/{ => lib}/utils/date.ts | 0 packages/core/src/{ => lib}/utils/logger.ts | 0 packages/core/src/{ => lib}/utils/merge.ts | 0 packages/core/src/{ => lib}/utils/node.ts | 0 packages/core/src/{ => lib}/utils/parse-url.ts | 0 12 files changed, 10 insertions(+), 10 deletions(-) rename packages/core/src/{ => lib}/utils/date.ts (100%) rename packages/core/src/{ => lib}/utils/logger.ts (100%) rename packages/core/src/{ => lib}/utils/merge.ts (100%) rename packages/core/src/{ => lib}/utils/node.ts (100%) rename packages/core/src/{ => lib}/utils/parse-url.ts (100%) diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 2816e5d877..1bef8228ac 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -4,7 +4,7 @@ import { SessionStore } from "./lib/cookie" import { toInternalRequest, toResponse } from "./lib/web" import renderPage from "./lib/pages" import * as routes from "./lib/routes" -import logger, { setLogger } from "./utils/logger" +import logger, { setLogger } from "./lib/utils/logger" import type { ErrorType } from "./lib/pages/error" import type { diff --git a/packages/core/src/lib/assert.ts b/packages/core/src/lib/assert.ts index 8475ea98cf..585eba3424 100644 --- a/packages/core/src/lib/assert.ts +++ b/packages/core/src/lib/assert.ts @@ -11,7 +11,7 @@ import { import { defaultCookies } from "./cookie" import type { AuthOptions, RequestInternal } from ".." -import type { WarningCode } from "../utils/logger" +import type { WarningCode } from "./utils/logger" type ConfigError = | MissingAdapter diff --git a/packages/core/src/lib/callback-handler.ts b/packages/core/src/lib/callback-handler.ts index f57e491f46..76a4b03710 100644 --- a/packages/core/src/lib/callback-handler.ts +++ b/packages/core/src/lib/callback-handler.ts @@ -1,5 +1,5 @@ import { AccountNotLinkedError } from "./errors" -import { fromDate } from "../utils/date" +import { fromDate } from "./utils/date" import type { Account, InternalOptions, User } from ".." import type { AdapterSession, AdapterUser } from "../adapters" diff --git a/packages/core/src/lib/init.ts b/packages/core/src/lib/init.ts index 91781e7b9d..2d21101363 100644 --- a/packages/core/src/lib/init.ts +++ b/packages/core/src/lib/init.ts @@ -5,8 +5,8 @@ import * as cookie from "./cookie" import { createCSRFToken } from "./csrf-token" import { defaultCallbacks } from "./default-callbacks" import parseProviders from "./providers" -import logger from "../utils/logger" -import parseUrl from "../utils/parse-url" +import logger from "./utils/logger" +import parseUrl from "./utils/parse-url" import type { AuthOptions, InternalOptions, RequestInternal } from ".." diff --git a/packages/core/src/lib/providers.ts b/packages/core/src/lib/providers.ts index 80f4e0b41e..9ecb5a963f 100644 --- a/packages/core/src/lib/providers.ts +++ b/packages/core/src/lib/providers.ts @@ -1,4 +1,4 @@ -import { merge } from "../utils/merge" +import { merge } from "./utils/merge" import type { InternalProvider } from ".." import type { diff --git a/packages/core/src/lib/routes/session.ts b/packages/core/src/lib/routes/session.ts index 8ad3f0c931..8ce651f939 100644 --- a/packages/core/src/lib/routes/session.ts +++ b/packages/core/src/lib/routes/session.ts @@ -1,8 +1,8 @@ import { fromDate } from "../utils/date" -import type { InternalOptions, ResponseInternal, Session } from ".." -import type { Adapter } from "../adapters" -import type { SessionStore } from "../lib/cookie" +import type { InternalOptions, ResponseInternal, Session } from "../.." +import type { Adapter } from "../../adapters" +import type { SessionStore } from "../cookie" interface SessionParams { options: InternalOptions diff --git a/packages/core/src/lib/types.ts b/packages/core/src/lib/types.ts index 01c652bde3..4a0c31834a 100644 --- a/packages/core/src/lib/types.ts +++ b/packages/core/src/lib/types.ts @@ -14,7 +14,7 @@ import type { } from "oauth4webapi" import type { JWT, JWTOptions } from "../jwt" import type { Cookie } from "./cookie" -import type { LoggerInstance } from "../utils/logger" +import type { LoggerInstance } from "./utils/logger" export type Awaitable = T | PromiseLike diff --git a/packages/core/src/utils/date.ts b/packages/core/src/lib/utils/date.ts similarity index 100% rename from packages/core/src/utils/date.ts rename to packages/core/src/lib/utils/date.ts diff --git a/packages/core/src/utils/logger.ts b/packages/core/src/lib/utils/logger.ts similarity index 100% rename from packages/core/src/utils/logger.ts rename to packages/core/src/lib/utils/logger.ts diff --git a/packages/core/src/utils/merge.ts b/packages/core/src/lib/utils/merge.ts similarity index 100% rename from packages/core/src/utils/merge.ts rename to packages/core/src/lib/utils/merge.ts diff --git a/packages/core/src/utils/node.ts b/packages/core/src/lib/utils/node.ts similarity index 100% rename from packages/core/src/utils/node.ts rename to packages/core/src/lib/utils/node.ts diff --git a/packages/core/src/utils/parse-url.ts b/packages/core/src/lib/utils/parse-url.ts similarity index 100% rename from packages/core/src/utils/parse-url.ts rename to packages/core/src/lib/utils/parse-url.ts From 9fcf85413c406a06d0bf3fe30e9827c050d21407 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Tue, 13 Dec 2022 13:50:50 +0100 Subject: [PATCH 79/93] fix imports --- packages/core/src/lib/routes/signin.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/core/src/lib/routes/signin.ts b/packages/core/src/lib/routes/signin.ts index 3a4c93430c..91c6f48f59 100644 --- a/packages/core/src/lib/routes/signin.ts +++ b/packages/core/src/lib/routes/signin.ts @@ -1,13 +1,13 @@ -import getAdapterUserFromEmail from "../lib/email/getUserFromEmail" -import emailSignin from "../lib/email/signin" -import { getAuthorizationUrl } from "../lib/oauth/authorization-url" +import getAdapterUserFromEmail from "../email/getUserFromEmail" +import emailSignin from "../email/signin" +import { getAuthorizationUrl } from "../oauth/authorization-url" import type { Account, InternalOptions, RequestInternal, ResponseInternal, -} from ".." +} from "../.." /** Handle requests to /api/auth/signin */ export async function signin(params: { From cea596968fc698f7b2e8c3dc6857fbce1cb772de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Tue, 13 Dec 2022 13:52:24 +0100 Subject: [PATCH 80/93] update ignore/clean patterns --- packages/core/.gitignore | 5 +++++ packages/core/package.json | 18 +++++++++++------- packages/core/tsconfig.json | 9 ++++++++- 3 files changed, 24 insertions(+), 8 deletions(-) create mode 100644 packages/core/.gitignore diff --git a/packages/core/.gitignore b/packages/core/.gitignore new file mode 100644 index 0000000000..c58936a81b --- /dev/null +++ b/packages/core/.gitignore @@ -0,0 +1,5 @@ +./adapters.* +./index.* +./jwt +./lib +./providers \ No newline at end of file diff --git a/packages/core/package.json b/packages/core/package.json index e242fbc5c8..d66c1a74c6 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -14,19 +14,23 @@ "Iain Collins Date: Tue, 13 Dec 2022 13:53:49 +0100 Subject: [PATCH 81/93] fix imports --- packages/core/src/lib/routes/callback.ts | 16 ++++++++-------- packages/core/src/lib/routes/providers.ts | 2 +- packages/core/src/lib/routes/signout.ts | 6 +++--- packages/core/src/lib/utils/logger.ts | 2 +- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/core/src/lib/routes/callback.ts b/packages/core/src/lib/routes/callback.ts index 2bc5fb0657..82180316a8 100644 --- a/packages/core/src/lib/routes/callback.ts +++ b/packages/core/src/lib/routes/callback.ts @@ -1,11 +1,11 @@ -import callbackHandler from "../lib/callback-handler" -import getAdapterUserFromEmail from "../lib/email/getUserFromEmail" -import { handleOAuthCallback } from "../lib/oauth/callback" -import { createHash } from "../lib/web" - -import type { RequestInternal, ResponseInternal, User } from ".." -import type { AdapterSession } from "../adapters" -import type { Cookie, SessionStore } from "../lib/cookie" +import callbackHandler from "../callback-handler" +import getAdapterUserFromEmail from "../email/getUserFromEmail" +import { handleOAuthCallback } from "../oauth/callback" +import { createHash } from "../web" + +import type { RequestInternal, ResponseInternal, User } from "../.." +import type { AdapterSession } from "../../adapters" +import type { Cookie, SessionStore } from "../cookie" import type { InternalOptions } from "../types" /** Handle callbacks from login services */ diff --git a/packages/core/src/lib/routes/providers.ts b/packages/core/src/lib/routes/providers.ts index 18ef2696e7..1246ea6c02 100644 --- a/packages/core/src/lib/routes/providers.ts +++ b/packages/core/src/lib/routes/providers.ts @@ -1,4 +1,4 @@ -import type { InternalProvider, ResponseInternal } from ".." +import type { InternalProvider, ResponseInternal } from "../.." export interface PublicProvider { id: string diff --git a/packages/core/src/lib/routes/signout.ts b/packages/core/src/lib/routes/signout.ts index 77427b368c..acb2735d89 100644 --- a/packages/core/src/lib/routes/signout.ts +++ b/packages/core/src/lib/routes/signout.ts @@ -1,6 +1,6 @@ -import type { InternalOptions, ResponseInternal } from ".." -import type { Adapter } from "../adapters" -import type { SessionStore } from "../lib/cookie" +import type { InternalOptions, ResponseInternal } from "../.." +import type { Adapter } from "../../adapters" +import type { SessionStore } from "../cookie" /** Handle requests to /api/auth/signout */ export async function signout(params: { diff --git a/packages/core/src/lib/utils/logger.ts b/packages/core/src/lib/utils/logger.ts index ee44e95188..03c7c516e8 100644 --- a/packages/core/src/lib/utils/logger.ts +++ b/packages/core/src/lib/utils/logger.ts @@ -1,4 +1,4 @@ -import { UnknownError } from "../lib/errors" +import { UnknownError } from "../errors" /** Makes sure that error is always serializable */ function formatError(o: unknown): unknown { From 249ebf4f782be2411a97b782e855c4c840f0dbca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Tue, 13 Dec 2022 13:55:04 +0100 Subject: [PATCH 82/93] update styles ts --- packages/core/src/lib/styles/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/lib/styles/index.ts b/packages/core/src/lib/styles/index.ts index a88a9f2bf5..6406dbf2e6 100644 --- a/packages/core/src/lib/styles/index.ts +++ b/packages/core/src/lib/styles/index.ts @@ -1,2 +1,2 @@ export default `:root{--border-width:1px;--border-radius:0.5rem;--color-error:#c94b4b;--color-info:#157efb;--color-info-text:#fff}.__next-auth-theme-auto,.__next-auth-theme-light{--color-background:#fff;--color-text:#000;--color-primary:#444;--color-control-border:#bbb;--color-button-active-background:#f9f9f9;--color-button-active-border:#aaa;--color-seperator:#ccc}.__next-auth-theme-dark{--color-background:#000;--color-text:#fff;--color-primary:#ccc;--color-control-border:#555;--color-button-active-background:#060606;--color-button-active-border:#666;--color-seperator:#444}@media (prefers-color-scheme:dark){.__next-auth-theme-auto{--color-background:#000;--color-text:#fff;--color-primary:#ccc;--color-control-border:#555;--color-button-active-background:#060606;--color-button-active-border:#666;--color-seperator:#444}}body{background-color:var(--color-background);font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;margin:0;padding:0}h1{font-weight:400;margin-bottom:1.5rem;padding:0 1rem}h1,p{color:var(--color-text)}form{margin:0;padding:0}label{font-weight:500;margin-bottom:.25rem;text-align:left}input[type],label{color:var(--color-text);display:block}input[type]{background:var(--color-background);border:var(--border-width) solid var(--color-control-border);border-radius:var(--border-radius);box-shadow:inset 0 .1rem .2rem rgba(0,0,0,.2);box-sizing:border-box;font-size:1rem;padding:.5rem 1rem;width:100%}input[type]:focus{box-shadow:none}p{font-size:1.1rem;line-height:2rem;margin:0 0 1.5rem;padding:0 1rem}a.button{line-height:1rem;text-decoration:none}a.button:link,a.button:visited{background-color:var(--color-background);color:var(--color-primary)}a.button,button{align-items:center;background-color:var(--provider-bg,var(--color-background));border-color:rgba(0,0,0,.1);border-radius:var(--border-radius);box-shadow:0 0 0 0 #000,0 0 0 0 #000,0 10px 15px -3px rgba(0,0,0,.2),0 4px 6px -4px rgba(0,0,0,.1);color:var(--provider-color,var(--color-primary));display:flex;font-size:1.1rem;font-weight:500;justify-content:center;margin:0 0 .75rem;min-height:62px;padding:.75rem 1rem;position:relative;transition:all .1s ease-in-out}a.button:has(img),button:has(img){justify-content:unset}a.button:has(img) span,button:has(img) span{flex-grow:1}a.button:hover,button:hover{cursor:pointer}a.button:active,button:active{box-shadow:0 .15rem .3rem rgba(0,0,0,.15),inset 0 .1rem .2rem var(--color-background),inset 0 -.1rem .1rem rgba(0,0,0,.1);cursor:pointer}a.button #provider-logo,button #provider-logo{display:block}a.button #provider-logo-dark,button #provider-logo-dark{display:none}@media (prefers-color-scheme:dark){a.button,button{background-color:var(--provider-dark-bg,var(--color-background));border:1px solid #0d0d0d;box-shadow:0 0 0 0 #000,0 0 0 0 #ccc,0 5px 5px -3px hsla(0,0%,100%,.01),0 4px 6px -4px hsla(0,0%,100%,.05);color:var(--provider-dark-color,var(--color-primary))}#provider-logo{display:none!important}#provider-logo-dark{display:block!important}}a.site{color:var(--color-primary);font-size:1rem;line-height:2rem;text-decoration:none}a.site:hover{text-decoration:underline}.page{display:grid;height:100%;margin:0;padding:0;place-items:center;position:absolute;width:100%}.page>div{padding:.5rem;text-align:center}.error a.button{display:inline-block;margin-top:.5rem;padding-left:2rem;padding-right:2rem}.error .message{margin-bottom:1.5rem}.signin input[type=text]{display:block;margin-left:auto;margin-right:auto}.signin hr{border:0;border-top:1px solid var(--color-seperator);display:block;margin:1.5em auto 0;overflow:visible}.signin hr:before{background:var(--color-background);color:#888;content:"or";padding:0 .4rem;position:relative;top:-.6rem}.signin .error{background:#f5f5f5;background:var(--color-info);border-radius:.3rem;font-weight:500}.signin .error p{color:var(--color-info-text);font-size:.9rem;line-height:1.2rem;padding:.5rem 1rem;text-align:left}.signin form,.signin>div{display:block}.signin form input[type],.signin>div input[type]{margin-bottom:.5rem}.signin form button,.signin>div button{width:100%}.signin form,.signin>div{max-width:300px}.signout .message{margin-bottom:1.5rem}.logo{display:inline-block;margin-top:100px;max-height:150px;max-width:300px}.card{border:1px solid var(--color-control-border);border-radius:5px;margin:50px auto;max-width:-moz-max-content;max-width:max-content;padding:20px 50px}.card .header{color:var(--color-primary)}.section-header{color:var(--brand-color,var(--color-text))}` -// Run `pnpm css` \ No newline at end of file +// Generated by `pnpm css` \ No newline at end of file From 2c209d7fefd3d4434a06198136dac1f8df8b5273 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Tue, 13 Dec 2022 13:56:07 +0100 Subject: [PATCH 83/93] update gitignore --- packages/core/.gitignore | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/core/.gitignore b/packages/core/.gitignore index c58936a81b..f768a12d22 100644 --- a/packages/core/.gitignore +++ b/packages/core/.gitignore @@ -1,5 +1,5 @@ -./adapters.* -./index.* -./jwt -./lib -./providers \ No newline at end of file +adapters.* +index.* +jwt +lib +providers \ No newline at end of file From 4582348bfd5da4ddc2b991ca7392f1fa0aaece61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Tue, 13 Dec 2022 13:59:42 +0100 Subject: [PATCH 84/93] update exports field --- packages/core/package.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/core/package.json b/packages/core/package.json index d66c1a74c6..a66b6f9297 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -28,6 +28,13 @@ "types": "./index.d.ts", "import": "./index.js" }, + "./adapters": { + "types": "./adapters.d.ts" + }, + "./jwt": { + "types": "./jwt/index.d.ts", + "import": "./jwt/index.js" + }, "./providers/*": { "types": "./providers/*.d.ts", "import": "./providers/*.js" From fef845762ecfa496427ecf65f88f8b38fbb509ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Tue, 13 Dec 2022 14:04:46 +0100 Subject: [PATCH 85/93] revert `next-auth` --- packages/next-auth/package.json | 6 +- packages/next-auth/src/core/errors.ts | 6 +- packages/next-auth/src/core/index.ts | 2 +- packages/next-auth/src/core/init.ts | 22 +-- packages/next-auth/src/core/lib/assert.ts | 27 +--- packages/next-auth/src/core/lib/csrf-token.ts | 19 +-- .../next-auth/src/core/lib/email/signin.ts | 9 +- .../src/core/lib/oauth/authorization-url.ts | 17 ++- .../next-auth/src/core/lib/oauth/callback.ts | 21 ++- .../src/core/lib/oauth/client-legacy.ts | 2 +- .../next-auth/src/core/lib/oauth/client.ts | 22 +-- packages/next-auth/src/core/lib/providers.ts | 138 ++++++++---------- packages/next-auth/src/core/lib/utils.ts | 32 ++++ packages/next-auth/src/core/lib/web.ts | 115 --------------- .../next-auth/src/core/routes/callback.ts | 13 +- packages/next-auth/src/core/routes/index.ts | 10 +- .../next-auth/src/core/routes/providers.ts | 2 +- packages/next-auth/src/core/routes/session.ts | 2 +- packages/next-auth/src/core/routes/signin.ts | 9 +- packages/next-auth/src/core/routes/signout.ts | 2 +- packages/next-auth/src/core/types.ts | 21 ++- packages/next-auth/src/css/index.ts | 16 +- packages/next-auth/src/next/index.ts | 9 +- packages/next-auth/src/next/inject-globals.ts | 29 ---- packages/next-auth/src/providers/auth0.ts | 2 +- packages/next-auth/src/providers/github.ts | 1 - packages/next-auth/src/providers/oauth.ts | 35 ++--- packages/next-auth/src/providers/twitch.ts | 2 +- packages/next-auth/src/react/index.tsx | 11 +- packages/next-auth/src/utils/node.ts | 5 - packages/next-auth/src/utils/web.ts | 2 +- 31 files changed, 223 insertions(+), 386 deletions(-) delete mode 100644 packages/next-auth/src/core/lib/web.ts delete mode 100644 packages/next-auth/src/next/inject-globals.ts diff --git a/packages/next-auth/package.json b/packages/next-auth/package.json index 719fff0f4f..08dc2bb0bd 100644 --- a/packages/next-auth/package.json +++ b/packages/next-auth/package.json @@ -62,7 +62,8 @@ "index.js", "adapters.d.ts", "middleware.d.ts", - "middleware.js" + "middleware.js", + "utils" ], "license": "ISC", "dependencies": { @@ -97,7 +98,6 @@ "@babel/preset-typescript": "^7.17.12", "@edge-runtime/jest-environment": "1.1.0-beta.35", "@next-auth/tsconfig": "workspace:*", - "oauth4webapi": "2.0.4", "@swc/core": "^1.2.198", "@swc/jest": "^0.2.21", "@testing-library/dom": "^8.13.0", @@ -131,4 +131,4 @@ "engines": { "node": "^12.19.0 || ^14.15.0 || ^16.13.0 || ^18.12.0" } -} +} \ No newline at end of file diff --git a/packages/next-auth/src/core/errors.ts b/packages/next-auth/src/core/errors.ts index a52d0b1fc7..1b9b91e26c 100644 --- a/packages/next-auth/src/core/errors.ts +++ b/packages/next-auth/src/core/errors.ts @@ -68,14 +68,10 @@ export class UnsupportedStrategy extends UnknownError { } export class InvalidCallbackUrl extends UnknownError { - name = "InvalidCallbackUrlError" + name = "InvalidCallbackUrl" code = "INVALID_CALLBACK_URL_ERROR" } -export class InvalidEndpoints extends UnknownError { - name = "InvalidEndpoints" - code = "INVALID_ENDPOINTS_ERROR" -} export class UnknownAction extends UnknownError { name = "UnknownAction" code = "UNKNOWN_ACTION_ERROR" diff --git a/packages/next-auth/src/core/index.ts b/packages/next-auth/src/core/index.ts index f027fbe6b7..9b7e4c09a0 100644 --- a/packages/next-auth/src/core/index.ts +++ b/packages/next-auth/src/core/index.ts @@ -32,7 +32,7 @@ export interface ResponseInternal< status?: number headers?: Record body?: Body - redirect?: URL | string // TODO: refactor to only allow URL + redirect?: string cookies?: Cookie[] } diff --git a/packages/next-auth/src/core/init.ts b/packages/next-auth/src/core/init.ts index 804a8bc337..ede030a56e 100644 --- a/packages/next-auth/src/core/init.ts +++ b/packages/next-auth/src/core/init.ts @@ -1,8 +1,9 @@ -import { createHash, randomUUID } from "./lib/web" +import { randomBytes, randomUUID } from "crypto" import { AuthOptions } from ".." import logger from "../utils/logger" 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" @@ -48,17 +49,7 @@ export async function init({ ) const url = new URL(parsed.toString()) - /** - * 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 = - authOptions.secret ?? - // TODO: Remove this, always ask the user for a secret, even in dev! (Fix assert.ts too) - (await createHash(JSON.stringify({ ...url, ...authOptions }))) + const secret = createSecret({ authOptions, url }) const { providers, provider } = parseProviders({ providers: authOptions.providers, @@ -102,7 +93,10 @@ export async function init({ strategy: authOptions.adapter ? "database" : "jwt", maxAge, updateAge: 24 * 60 * 60, - generateSessionToken: randomUUID, + generateSessionToken: () => { + // Use `randomUUID` if available. (Node 15.6+) + return randomUUID?.() ?? randomBytes(32).toString("hex") + }, ...authOptions.session, }, // JWT options @@ -130,7 +124,7 @@ export async function init({ csrfToken, cookie: csrfCookie, csrfTokenVerified, - } = await createCSRFToken({ + } = createCSRFToken({ options, cookieValue: reqCookies?.[options.cookies.csrfToken.name], isPost, diff --git a/packages/next-auth/src/core/lib/assert.ts b/packages/next-auth/src/core/lib/assert.ts index 37fad8dfe0..589a025d82 100644 --- a/packages/next-auth/src/core/lib/assert.ts +++ b/packages/next-auth/src/core/lib/assert.ts @@ -1,12 +1,11 @@ import { MissingAdapter, - MissingAdapterMethods, MissingAPIRoute, MissingAuthorize, MissingSecret, - InvalidCallbackUrl, - InvalidEndpoints, UnsupportedStrategy, + InvalidCallbackUrl, + MissingAdapterMethods, } from "../errors" import { defaultCookies } from "./cookie" @@ -15,15 +14,11 @@ import type { WarningCode } from "../../utils/logger" import type { AuthOptions } from "../types" type ConfigError = - | MissingAdapter - | MissingAdapterMethods | MissingAPIRoute - | MissingAuthorize | MissingSecret - | InvalidCallbackUrl - | UnsupportedStrategy - | InvalidEndpoints | UnsupportedStrategy + | MissingAuthorize + | MissingAdapter let warned = false @@ -96,20 +91,6 @@ export function assertConfig(params: { let hasTwitterOAuth2 for (const provider of options.providers) { - if (provider.type === "oauth" && !provider.issuer) { - 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") diff --git a/packages/next-auth/src/core/lib/csrf-token.ts b/packages/next-auth/src/core/lib/csrf-token.ts index 15f75726ca..cd61492218 100644 --- a/packages/next-auth/src/core/lib/csrf-token.ts +++ b/packages/next-auth/src/core/lib/csrf-token.ts @@ -1,4 +1,5 @@ -import { createHash, randomString } from "./web" +import { createHash, randomBytes } from "crypto" + import type { InternalOptions } from "../types" interface CreateCSRFTokenParams { @@ -22,7 +23,7 @@ interface CreateCSRFTokenParams { * https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#double-submit-cookie * https://owasp.org/www-chapter-london/assets/slides/David_Johansson-Double_Defeat_of_Double-Submit_Cookie.pdf */ -export async function createCSRFToken({ +export function createCSRFToken({ options, cookieValue, isPost, @@ -30,11 +31,9 @@ export async function createCSRFToken({ }: CreateCSRFTokenParams) { if (cookieValue) { const [csrfToken, csrfTokenHash] = cookieValue.split("|") - - const expectedCsrfTokenHash = await createHash( - `${csrfToken}${options.secret}` - ) - + const expectedCsrfTokenHash = createHash("sha256") + .update(`${csrfToken}${options.secret}`) + .digest("hex") if (csrfTokenHash === expectedCsrfTokenHash) { // If hash matches then we trust the CSRF token value // If this is a POST request and the CSRF Token in the POST request matches @@ -46,8 +45,10 @@ export async function createCSRFToken({ } // New CSRF token - const csrfToken = randomString(32) - const csrfTokenHash = await createHash(`${csrfToken}${options.secret}`) + const csrfToken = randomBytes(32).toString("hex") + const csrfTokenHash = createHash("sha256") + .update(`${csrfToken}${options.secret}`) + .digest("hex") const cookie = `${csrfToken}|${csrfTokenHash}` return { cookie, csrfToken } diff --git a/packages/next-auth/src/core/lib/email/signin.ts b/packages/next-auth/src/core/lib/email/signin.ts index 41b86aa3a8..fa4ba15143 100644 --- a/packages/next-auth/src/core/lib/email/signin.ts +++ b/packages/next-auth/src/core/lib/email/signin.ts @@ -1,4 +1,5 @@ -import { randomString, createHash } from "../web" +import { randomBytes } from "crypto" +import { hashToken } from "../utils" import type { InternalOptions } from "../../types" /** @@ -12,7 +13,8 @@ export default async function email( const { url, adapter, provider, callbackUrl, theme } = options // Generate token const token = - (await provider.generateVerificationToken?.()) ?? randomString(32) + (await provider.generateVerificationToken?.()) ?? + randomBytes(32).toString("hex") const ONE_DAY_IN_SECONDS = 86400 const expires = new Date( @@ -23,7 +25,6 @@ export default async function email( const params = new URLSearchParams({ callbackUrl, token, email: identifier }) const _url = `${url}/callback/${provider.id}?${params}` - const secret = provider.secret ?? options.secret await Promise.all([ // Send to user provider.sendVerificationRequest({ @@ -37,7 +38,7 @@ export default async function email( // Save in database adapter.createVerificationToken({ identifier, - token: await createHash(`${token}${secret}`), + token: hashToken(token, options), expires, }), ]) diff --git a/packages/next-auth/src/core/lib/oauth/authorization-url.ts b/packages/next-auth/src/core/lib/oauth/authorization-url.ts index 4d1a51f468..de392db192 100644 --- a/packages/next-auth/src/core/lib/oauth/authorization-url.ts +++ b/packages/next-auth/src/core/lib/oauth/authorization-url.ts @@ -15,7 +15,7 @@ import type { Cookie } from "../cookie" * * [OAuth 2](https://www.oauth.com/oauth2-servers/authorization/the-authorization-request/) | [OAuth 1](https://oauth.net/core/1.0a/#auth_step2) */ -export async function getAuthorizationUrl({ +export default async function getAuthorizationUrl({ options, query, }: { @@ -23,17 +23,18 @@ export async function getAuthorizationUrl({ query: RequestInternal["query"] }) { const { logger, provider } = options - let params + let params: any = {} - if (provider.authorization?.url) { - params = Object.assign( - Object.fromEntries(provider.authorization.url.searchParams.entries()), - query - ) + if (typeof provider.authorization === "string") { + const parsedUrl = new URL(provider.authorization) + const parsedParams = Object.fromEntries(parsedUrl.searchParams) + params = { ...params, ...parsedParams } } else { - params = query + params = { ...params, ...provider.authorization?.params } } + params = { ...params, ...query } + // Handle OAuth v1.x if (provider.version?.startsWith("1.")) { const client = oAuth1Client(options) diff --git a/packages/next-auth/src/core/lib/oauth/callback.ts b/packages/next-auth/src/core/lib/oauth/callback.ts index 5661bb5565..3185bf4c1f 100644 --- a/packages/next-auth/src/core/lib/oauth/callback.ts +++ b/packages/next-auth/src/core/lib/oauth/callback.ts @@ -8,12 +8,12 @@ import { OAuthCallbackError } from "../../errors" import type { CallbackParamsType, OpenIDCallbackChecks } from "openid-client" import type { LoggerInstance, Profile } from "../../.." -import type { OAuthConfigInternal } from "../../../providers" +import type { OAuthChecks, OAuthConfig } from "../../../providers" import type { InternalOptions } from "../../types" import type { RequestInternal } from "../.." import type { Cookie } from "../cookie" -export async function handleOAuthCallback(params: { +export default async function oAuthCallback(params: { options: InternalOptions<"oauth"> query: RequestInternal["query"] body: RequestInternal["body"] @@ -68,7 +68,7 @@ export async function handleOAuthCallback(params: { let tokens: TokenSet - const checks: OpenIDCallbackChecks = {} + const checks: OAuthChecks = {} const resCookies: Cookie[] = [] const state = await useState(cookies?.[options.cookies.state.name], options) @@ -79,7 +79,7 @@ export async function handleOAuthCallback(params: { const nonce = await useNonce(cookies?.[options.cookies.nonce.name], options) if (nonce && provider.idToken) { - checks.nonce = nonce.value + ;(checks as OpenIDCallbackChecks).nonce = nonce.value resCookies.push(nonce.cookie) } @@ -99,7 +99,7 @@ export async function handleOAuthCallback(params: { body, method, }), - ...Object.fromEntries(provider.token?.url.searchParams.entries() ?? []), + ...provider.token?.params, } if (provider.token?.request) { @@ -131,10 +131,9 @@ export async function handleOAuthCallback(params: { } else if (provider.idToken) { profile = tokens.claims() } else { - const params = Object.fromEntries( - provider.userinfo?.url.searchParams.entries() ?? [] - ) - profile = await client.userinfo(tokens, { params }) + profile = await client.userinfo(tokens, { + params: provider.userinfo?.params, + }) } const profileResult = await getProfile({ @@ -149,10 +148,10 @@ export async function handleOAuthCallback(params: { } } -interface GetProfileParams { +export interface GetProfileParams { profile: Profile tokens: TokenSet - provider: OAuthConfigInternal + provider: OAuthConfig logger: LoggerInstance } diff --git a/packages/next-auth/src/core/lib/oauth/client-legacy.ts b/packages/next-auth/src/core/lib/oauth/client-legacy.ts index a809012f6d..b6eb1f71c8 100644 --- a/packages/next-auth/src/core/lib/oauth/client-legacy.ts +++ b/packages/next-auth/src/core/lib/oauth/client-legacy.ts @@ -13,7 +13,7 @@ export function oAuth1Client(options: InternalOptions<"oauth">) { const oauth1Client = new OAuth( provider.requestTokenUrl as string, provider.accessTokenUrl as string, - provider.clientId, + provider.clientId as string, provider.clientSecret as string, provider.version ?? "1.0", provider.callbackUrl, diff --git a/packages/next-auth/src/core/lib/oauth/client.ts b/packages/next-auth/src/core/lib/oauth/client.ts index 82c2d075d8..ba9aff5337 100644 --- a/packages/next-auth/src/core/lib/oauth/client.ts +++ b/packages/next-auth/src/core/lib/oauth/client.ts @@ -2,7 +2,13 @@ import { Issuer, custom } from "openid-client" import type { Client } from "openid-client" import type { InternalOptions } from "../../types" -/** Node.js reliant client supporting OAuth 2.x and OIDC */ +/** + * NOTE: We can add auto discovery of the provider's endpoint + * that requires only one endpoint to be specified by the user. + * Check out `Issuer.discover` + * + * Client supporting OAuth 2.x and OIDC + */ export async function openidClient( options: InternalOptions<"oauth"> ): Promise { @@ -15,20 +21,16 @@ export async function openidClient( issuer = await Issuer.discover(provider.wellKnown) } else { issuer = new Issuer({ - // We verify that either `issuer` or the other endpoints - // are always defined in assert.ts - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - issuer: provider.issuer!, - authorization_endpoint: provider.authorization?.url.toString(), - token_endpoint: provider.token?.url.toString(), - userinfo_endpoint: provider.userinfo?.url.toString(), + issuer: provider.issuer as string, + authorization_endpoint: provider.authorization?.url, + token_endpoint: provider.token?.url, + userinfo_endpoint: provider.userinfo?.url, }) } const client = new issuer.Client( { - // clientId can technically be undefined, should we check this in assert.ts or rely on the Authorization Server to do it? - client_id: provider.clientId, + client_id: provider.clientId as string, client_secret: provider.clientSecret as string, redirect_uris: [provider.callbackUrl], ...provider.client, diff --git a/packages/next-auth/src/core/lib/providers.ts b/packages/next-auth/src/core/lib/providers.ts index 9d6b8768cb..5f350add7f 100644 --- a/packages/next-auth/src/core/lib/providers.ts +++ b/packages/next-auth/src/core/lib/providers.ts @@ -5,8 +5,6 @@ import type { OAuthConfigInternal, OAuthConfig, Provider, - OAuthUserConfig, - OAuthEndpointType, } from "../../providers" /** @@ -17,30 +15,32 @@ export default function parseProviders(params: { providers: Provider[] url: URL providerId?: string - runtime?: "web" | "nodejs" }): { providers: InternalProvider[] provider?: InternalProvider } { const { url, providerId } = params - const providers = params.providers.map((provider) => { - const { options: userOptions, ...defaults } = provider - - const id = (userOptions?.id ?? defaults.id) as string - const merged = merge(defaults, userOptions, { - signinUrl: `${url}/signin/${id}`, - callbackUrl: `${url}/callback/${id}`, - }) - - if (provider.type === "oauth") { - const p = normalizeOAuth(merged) - - return p + const providers = params.providers.map( + ({ options: userOptions, ...rest }) => { + if (rest.type === "oauth") { + const normalizedOptions = normalizeOAuthOptions(rest) + const normalizedUserOptions = normalizeOAuthOptions(userOptions, true) + const id = normalizedUserOptions?.id ?? rest.id + return merge(normalizedOptions, { + ...normalizedUserOptions, + signinUrl: `${url}/signin/${id}`, + callbackUrl: `${url}/callback/${id}`, + }) + } + const id = (userOptions?.id as string) ?? rest.id + return merge(rest, { + ...userOptions, + signinUrl: `${url}/signin/${id}`, + callbackUrl: `${url}/callback/${id}`, + }) } - - return merged - }) + ) return { providers, @@ -48,61 +48,49 @@ export default function parseProviders(params: { } } -function normalizeOAuth( - c?: OAuthConfig | OAuthUserConfig, - runtime?: "web" | "nodejs" -): OAuthConfigInternal | {} { - if (!c) return {} - - const hasIssuer = !!c.issuer - const authorization = normalizeEndpoint(c.authorization, hasIssuer) - - if (!c.version?.startsWith("1.")) { - // Set default check to state - c.checks ??= ["pkce"] - c.checks = Array.isArray(c.checks) ? c.checks : [c.checks] - if (runtime === "web" && !c.checks.includes("pkce")) c.checks.push("pkce") - - if (!Array.isArray(c.checks)) c.checks = [c.checks] - - // REVIEW: Deprecate `idToken` in favor of `type: "oidc"`? - c.idToken ??= - // If a provider has as an "openid-configuration" well-known endpoint - c.wellKnown?.includes("openid-configuration") ?? - // or an "openid" scope request, it must also return an `id_token` - authorization?.url.searchParams.get("scope")?.includes("openid") - - if (c.issuer && c.idToken) { - c.wellKnown ??= `${c.issuer}/.well-known/openid-configuration` - } - } - - return { - ...c, - ...(authorization ? { authorization } : undefined), - ...(c.token ? { token: normalizeEndpoint(c.token, hasIssuer) } : undefined), - ...(c.userinfo - ? { userinfo: normalizeEndpoint(c.userinfo, hasIssuer) } - : undefined), - } -} - -function normalizeEndpoint( - e?: OAuthConfig[OAuthEndpointType], - hasIssuer?: boolean -): OAuthConfigInternal[OAuthEndpointType] { - if (!e || hasIssuer) return - if (typeof e === "string") { - return { url: new URL(e) } +/** + * Transform OAuth options `authorization`, `token` and `profile` strings to `{ url: string; params: Record }` + */ +function normalizeOAuthOptions( + oauthOptions?: Partial> | Record, + isUserOptions = false +) { + if (!oauthOptions) return + + const normalized = Object.entries(oauthOptions).reduce< + OAuthConfigInternal> + >( + (acc, [key, value]) => { + if ( + ["authorization", "token", "userinfo"].includes(key) && + typeof value === "string" + ) { + const url = new URL(value) + acc[key] = { + url: `${url.origin}${url.pathname}`, + params: Object.fromEntries(url.searchParams ?? []), + } + } else { + acc[key] = value + } + + return acc + }, + // eslint-disable-next-line @typescript-eslint/prefer-reduce-type-parameter + {} as any + ) + + if (!isUserOptions && !normalized.version?.startsWith("1.")) { + // If provider has as an "openid-configuration" well-known endpoint + // or an "openid" scope request, it will also likely be able to receive an `id_token` + // Only do this if this function is not called with user options to avoid overriding in later stage. + normalized.idToken = Boolean( + normalized.idToken ?? + normalized.wellKnown?.includes("openid-configuration") ?? + normalized.authorization?.params?.scope?.includes("openid") + ) + + if (!normalized.checks) normalized.checks = ["state"] } - // If v.url is undefined, it's because the provider config - // assumes that we will use the issuer endpoint. - // The existence of either v.url or provider.issuer is checked in - // assert.ts - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const url = new URL(e.url!) - // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion - for (const k in e.params) url.searchParams.set(k, e.params[k] as any) - - return { url } + return normalized } diff --git a/packages/next-auth/src/core/lib/utils.ts b/packages/next-auth/src/core/lib/utils.ts index 79894224d9..c2ed6991c5 100644 --- a/packages/next-auth/src/core/lib/utils.ts +++ b/packages/next-auth/src/core/lib/utils.ts @@ -1,3 +1,8 @@ +import { createHash } from "crypto" + +import type { AuthOptions } from "../.." +import type { InternalOptions } from "../types" + /** * Takes a number in seconds and returns the date in the future. * Optionally takes a second date parameter. In that case @@ -6,3 +11,30 @@ export function fromDate(time: number, date = Date.now()) { return new Date(date + time * 1000) } + +export function hashToken(token: string, options: InternalOptions<"email">) { + const { provider, secret } = options + return ( + createHash("sha256") + // Prefer provider specific secret, but use default secret if none specified + .update(`${token}${provider.secret ?? secret}`) + .digest("hex") + ) +} + +/** + * Secret used 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. */ +export function createSecret(params: { authOptions: AuthOptions; url: URL }) { + const { authOptions, url } = params + + return ( + authOptions.secret ?? + // TODO: Remove falling back to default secret, and error in dev if one isn't provided + createHash("sha256") + .update(JSON.stringify({ ...url, ...authOptions })) + .digest("hex") + ) +} diff --git a/packages/next-auth/src/core/lib/web.ts b/packages/next-auth/src/core/lib/web.ts deleted file mode 100644 index 488fddc4e3..0000000000 --- a/packages/next-auth/src/core/lib/web.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { serialize, parse as parseCookie } from "cookie" -import type { ResponseInternal, RequestInternal } from ".." -import type { AuthAction } from "../types" - -export async function toInternalRequest( - req: Request -): Promise { - const url = new URL(req.url) - const nextauth = url.pathname.split("/").slice(3) - const headers = Object.fromEntries(req.headers.entries()) - const query: Record = Object.fromEntries( - url.searchParams.entries() - ) - - const cookieHeader = req.headers.get("cookie") ?? "" - const cookies = - parseCookie( - Array.isArray(cookieHeader) ? cookieHeader.join(";") : cookieHeader - ) ?? {} - - return { - action: nextauth[0] as AuthAction, - method: req.method, - headers, - body: await getBody(req), - cookies: cookies, - providerId: nextauth[1], - error: url.searchParams.get("error") ?? undefined, - url, - query, - } -} - -export function toResponse(res: ResponseInternal): Response { - const headers = new Headers(res.headers) - - res.cookies?.forEach((cookie) => { - const { name, value, options } = cookie - const cookieHeader = serialize(name, value, options) - headers.append("Set-Cookie", cookieHeader) - }) - - const body = - headers.get("content-type") === "application/json" - ? JSON.stringify(res.body) - : res.body - - const response = new Response(body, { - headers, - status: res.redirect ? 301 : res.status ?? 200, - }) - - if (res.redirect) { - response.headers.set("Location", res.redirect.toString()) - } - - return response -} - -/** Web/Node.js compatible method to create a hash, using SHA256 */ -export async function createHash(message) { - if (crypto.createHash) { - return crypto.createHash("sha256").update(message).digest("hex") - } - if (crypto.subtle) { - const data = new TextEncoder().encode(message) - const hash = await crypto.subtle.digest("SHA-256", data) - return Array.from(new Uint8Array(hash)) - .map((b) => b.toString(16).padStart(2, "0")) - .join("") - .toString() - } - - throw new TypeError("`crypto` module is missing method(s) to create hash") -} - -/** Web/Node.js compatible method to create a random string of a given length */ -export function randomString(size: number) { - if (crypto.getRandomValues) { - const i2hex = (i: number) => ("0" + i.toString(16)).slice(-2) - const r = (a: string, i: number): string => a + i2hex(i) - const bytes = crypto.getRandomValues(new Uint8Array(size)) - return Array.from(bytes).reduce(r, "") - } - if (crypto.randomBytes) { - return crypto.randomBytes(size).toString("hex") - } - - throw new TypeError( - "`crypto` module is missing method(s) to create random bytes" - ) -} - -/** Web/Node.js compatible method to create a random UUID */ -export function randomUUID() { - if (crypto.randomUUID) return crypto.randomUUID() - else if (crypto.randomBytes) return crypto.randomBytes(32).toString("hex") - throw new TypeError( - "`crypto` module is missing method(s) to create random UUID" - ) -} - -async function getBody(req: Request): Promise | undefined> { - if (!("body" in req) || !req.body || req.method !== "POST") { - return - } - - const contentType = req.headers.get("content-type") - if (contentType?.includes("application/json")) { - return await req.json() - } else if (contentType?.includes("application/x-www-form-urlencoded")) { - const params = new URLSearchParams(await req.text()) - return Object.fromEntries(params) - } -} diff --git a/packages/next-auth/src/core/routes/callback.ts b/packages/next-auth/src/core/routes/callback.ts index 67090e3bb4..b7008d6453 100644 --- a/packages/next-auth/src/core/routes/callback.ts +++ b/packages/next-auth/src/core/routes/callback.ts @@ -1,8 +1,6 @@ -// TODO: Make this interchangeable with the Node.js import -// based on import of `next-auth` or `next-auth/web` -import { handleOAuthCallback } from "../lib/oauth/callback" +import oAuthCallback from "../lib/oauth/callback" import callbackHandler from "../lib/callback-handler" -import { createHash } from "../lib/web" +import { hashToken } from "../lib/utils" import getAdapterUserFromEmail from "../lib/email/getUserFromEmail" import type { InternalOptions } from "../types" @@ -12,7 +10,7 @@ import type { User } from "../.." import type { AdapterSession } from "../../adapters" /** Handle callbacks from login services */ -export async function callback(params: { +export default async function callback(params: { options: InternalOptions query: RequestInternal["query"] method: Required["method"] @@ -46,7 +44,7 @@ export async function callback(params: { account, OAuthProfile, cookies: oauthCookies, - } = await handleOAuthCallback({ + } = await oAuthCallback({ query, body, method, @@ -208,11 +206,10 @@ export async function callback(params: { return { redirect: `${url}/error?error=configuration`, cookies } } - const secret = provider.secret ?? options.secret // @ts-expect-error -- Verified in `assertConfig`. adapter: Adapter const invite = await adapter.useVerificationToken({ identifier, - token: await createHash(`${token}${secret}`), + token: hashToken(token, options), }) const invalidInvite = !invite || invite.expires.valueOf() < Date.now() diff --git a/packages/next-auth/src/core/routes/index.ts b/packages/next-auth/src/core/routes/index.ts index a1c28f4cc3..4cf7daa8f3 100644 --- a/packages/next-auth/src/core/routes/index.ts +++ b/packages/next-auth/src/core/routes/index.ts @@ -1,5 +1,5 @@ -export { callback } from "./callback" -export { signin } from "./signin" -export { signout } from "./signout" -export { session } from "./session" -export { providers } from "./providers" +export { default as callback } from './callback' +export { default as signin } from './signin' +export { default as signout } from './signout' +export { default as session } from './session' +export { default as providers } from './providers' diff --git a/packages/next-auth/src/core/routes/providers.ts b/packages/next-auth/src/core/routes/providers.ts index 3a9f978c04..2f6f1b0fab 100644 --- a/packages/next-auth/src/core/routes/providers.ts +++ b/packages/next-auth/src/core/routes/providers.ts @@ -14,7 +14,7 @@ export interface PublicProvider { * and their signin and callback URLs. This makes it possible to automatically * generate buttons for all providers when rendering client side. */ -export function providers( +export default function providers( providers: InternalProvider[] ): ResponseInternal> { return { diff --git a/packages/next-auth/src/core/routes/session.ts b/packages/next-auth/src/core/routes/session.ts index d1d2bf58cd..a7eabc5113 100644 --- a/packages/next-auth/src/core/routes/session.ts +++ b/packages/next-auth/src/core/routes/session.ts @@ -16,7 +16,7 @@ interface SessionParams { * for Single Page App clients */ -export async function session( +export default async function session( params: SessionParams ): Promise> { const { options, sessionStore } = params diff --git a/packages/next-auth/src/core/routes/signin.ts b/packages/next-auth/src/core/routes/signin.ts index d239fe7d72..201f279101 100644 --- a/packages/next-auth/src/core/routes/signin.ts +++ b/packages/next-auth/src/core/routes/signin.ts @@ -1,6 +1,4 @@ -// TODO: Make this interchangeable with the Node.js import -// based on import of `next-auth` or `next-auth/web` -import { getAuthorizationUrl } from "../lib/oauth/authorization-url" +import getAuthorizationUrl from "../lib/oauth/authorization-url" import emailSignin from "../lib/email/signin" import getAdapterUserFromEmail from "../lib/email/getUserFromEmail" import type { RequestInternal, ResponseInternal } from ".." @@ -8,7 +6,7 @@ import type { InternalOptions } from "../types" import type { Account } from "../.." /** Handle requests to /api/auth/signin */ -export async function signin(params: { +export default async function signin(params: { options: InternalOptions<"oauth" | "email"> query: RequestInternal["query"] body: RequestInternal["body"] @@ -26,7 +24,8 @@ export async function signin(params: { if (provider.type === "oauth") { try { - return await getAuthorizationUrl({ options, query }) + const response = await getAuthorizationUrl({ options, query }) + return response } catch (error) { logger.error("SIGNIN_OAUTH_ERROR", { error: error as Error, diff --git a/packages/next-auth/src/core/routes/signout.ts b/packages/next-auth/src/core/routes/signout.ts index f87abe3b59..5078d53fb5 100644 --- a/packages/next-auth/src/core/routes/signout.ts +++ b/packages/next-auth/src/core/routes/signout.ts @@ -4,7 +4,7 @@ import type { ResponseInternal } from ".." import type { SessionStore } from "../lib/cookie" /** Handle requests to /api/auth/signout */ -export async function signout(params: { +export default async function signout(params: { options: InternalOptions sessionStore: SessionStore }): Promise { diff --git a/packages/next-auth/src/core/types.ts b/packages/next-auth/src/core/types.ts index 09d065bf6d..11137b7a2b 100644 --- a/packages/next-auth/src/core/types.ts +++ b/packages/next-auth/src/core/types.ts @@ -12,6 +12,8 @@ import type { JWT, JWTOptions } from "../jwt" import type { LoggerInstance } from "../utils/logger" import type { CookieSerializeOptions } from "cookie" +import type { NextApiRequest, NextApiResponse } from "next" + export type Awaitable = T | PromiseLike export type { LoggerInstance } @@ -209,10 +211,6 @@ export interface AuthOptions { * @default Boolean(process.env.NEXTAUTH_URL ?? process.env.AUTH_TRUST_HOST ?? process.env.VERCEL) */ trustHost?: boolean - /** @internal */ - __internal__?: { - runtime?: "web" | "nodejs" - } } /** @@ -548,3 +546,18 @@ export interface InternalOptions< cookies: CookiesOptions callbackUrl: string } + +/** @internal */ +export interface NextAuthRequest extends NextApiRequest { + options: InternalOptions +} + +/** @internal */ +export type NextAuthResponse = NextApiResponse + +/** @internal */ +// eslint-disable-next-line @typescript-eslint/no-invalid-void-type +export type NextAuthApiHandler = ( + req: NextAuthRequest, + res: NextAuthResponse +) => Awaitable diff --git a/packages/next-auth/src/css/index.ts b/packages/next-auth/src/css/index.ts index 1774f34040..a62e822f77 100644 --- a/packages/next-auth/src/css/index.ts +++ b/packages/next-auth/src/css/index.ts @@ -1,19 +1,11 @@ // To support serverless targets (which don"t work if you try to read in things // like CSS files at run time) this file is replaced in production builds with // a function that returns compiled CSS (embedded as a string in the function). +import fs from "fs" +import path from "path" -export default function css() { - if (globalThis.EdgeRuntime) return "TODO" - // eslint-disable-next-line @typescript-eslint/no-var-requires - const fs = require("fs") - // eslint-disable-next-line @typescript-eslint/no-var-requires - const path = require("path") +const pathToCss = path.join(process.cwd(), process.env.NODE_ENV === "development" ? "node_modules/next-auth/css/index.css" : "/src/css/index.css") - const pathToCss = path.join( - process.cwd(), - process.env.NODE_ENV === "development" - ? "node_modules/next-auth/css/index.css" - : "/src/css/index.css" - ) +export default function css() { return fs.readFileSync(pathToCss, "utf8") } diff --git a/packages/next-auth/src/next/index.ts b/packages/next-auth/src/next/index.ts index 5cf52c1779..7697892fad 100644 --- a/packages/next-auth/src/next/index.ts +++ b/packages/next-auth/src/next/index.ts @@ -1,4 +1,3 @@ -import "./inject-globals" import { AuthHandler } from "../core" import { getBody, getURL, setHeaders } from "../utils/node" @@ -8,7 +7,11 @@ import type { NextApiResponse, } from "next" import type { AuthOptions, Session } from ".." -import type { CallbacksOptions } from "../core/types" +import type { + CallbacksOptions, + NextAuthRequest, + NextAuthResponse, +} from "../core/types" async function NextAuthHandler( req: NextApiRequest, @@ -61,7 +64,7 @@ function NextAuth( ...args: [AuthOptions] | [NextApiRequest, NextApiResponse, AuthOptions] ) { if (args.length === 1) { - return async (req: NextApiRequest, res: NextApiResponse) => + return async (req: NextAuthRequest, res: NextAuthResponse) => await NextAuthHandler(req, res, args[0]) } diff --git a/packages/next-auth/src/next/inject-globals.ts b/packages/next-auth/src/next/inject-globals.ts deleted file mode 100644 index 9551fd4b5e..0000000000 --- a/packages/next-auth/src/next/inject-globals.ts +++ /dev/null @@ -1,29 +0,0 @@ -import * as crypto from "crypto" - -declare global { - interface Crypto { - createHash?: typeof crypto.createHash - randomBytes?: typeof crypto.randomBytes - // @ts-expect-error - subtle?: SubtleCrypto - // @ts-expect-error - getRandomValues?: < - T extends - | Int8Array - | Int16Array - | Int32Array - | Uint8Array - | Uint16Array - | Uint32Array - | Uint8ClampedArray - | Float32Array - | Float64Array - | DataView - | null - >( - array: T - ) => T - } -} - -if (!globalThis.crypto) globalThis.crypto = crypto diff --git a/packages/next-auth/src/providers/auth0.ts b/packages/next-auth/src/providers/auth0.ts index ca6bede147..cb5e02d592 100644 --- a/packages/next-auth/src/providers/auth0.ts +++ b/packages/next-auth/src/providers/auth0.ts @@ -16,7 +16,7 @@ export default function Auth0

( wellKnown: `${options.issuer}/.well-known/openid-configuration`, type: "oauth", authorization: { params: { scope: "openid email profile" } }, - checks: ["pkce"], + checks: ["pkce", "state"], idToken: true, profile(profile) { return { diff --git a/packages/next-auth/src/providers/github.ts b/packages/next-auth/src/providers/github.ts index b2063fd153..87ff9bef84 100644 --- a/packages/next-auth/src/providers/github.ts +++ b/packages/next-auth/src/providers/github.ts @@ -107,7 +107,6 @@ export default function Github

( text: "#000", textDark: "#fff", }, - checks: ["pkce"], options, } } diff --git a/packages/next-auth/src/providers/oauth.ts b/packages/next-auth/src/providers/oauth.ts index 2e66fd8bef..c476279b12 100644 --- a/packages/next-auth/src/providers/oauth.ts +++ b/packages/next-auth/src/providers/oauth.ts @@ -30,7 +30,7 @@ type EndpointRequest = ( /** `openid-client` Client */ client: Client /** Provider is passed for convenience, ans also contains the `callbackUrl`. */ - provider: (OAuthConfig

| OAuthConfigInternal

) & { + provider: OAuthConfig

& { signinUrl: string callbackUrl: string } @@ -89,11 +89,6 @@ export type UserinfoEndpointHandler = EndpointHandler< Profile > -export type ProfileCallback

= ( - profile: P, - tokens: TokenSet -) => Awaitable - export interface OAuthProviderButtonStyles { logo: string logoDark: string @@ -124,7 +119,7 @@ export interface OAuthConfig

extends CommonProviderOptions, PartialIssuer { userinfo?: string | UserinfoEndpointHandler type: "oauth" version?: string - profile?: ProfileCallback

+ profile: (profile: P, tokens: TokenSet) => Awaitable checks?: ChecksType | ChecksType[] client?: Partial jwks?: { keys: JWK[] } @@ -140,6 +135,9 @@ export interface OAuthConfig

extends CommonProviderOptions, PartialIssuer { * [`id_token` explanation](https://www.oauth.com/oauth2-servers/openid-connect/id-tokens) */ idToken?: boolean + // TODO: only allow for BattleNet + region?: string + // TODO: only allow for some issuer?: string /** Read more at: https://github.com/panva/node-openid-client/tree/main/docs#customizing-http-requests */ httpOptions?: HttpOptions @@ -161,23 +159,12 @@ export interface OAuthConfig

extends CommonProviderOptions, PartialIssuer { allowDangerousEmailAccountLinking?: boolean } -export type OAuthEndpointType = "authorization" | "token" | "userinfo" - -/** - * @internal - * We parsed `authorization`, `token` and `userinfo` - * to always contain a valid `URL`, with the params - */ -export type OAuthConfigInternal

= Omit< - OAuthConfig

, - OAuthEndpointType | "clientId" | "checks" | "profile" -> & { - clientId: string - authorization?: { url: URL } - token?: { url: URL; request?: TokenEndpointHandler["request"] } - userinfo?: { url: URL; request?: UserinfoEndpointHandler["request"] } - checks: ChecksType[] - profile: ProfileCallback

+/** @internal */ +export interface OAuthConfigInternal

+ extends Omit, "authorization" | "token" | "userinfo"> { + authorization?: AuthorizationEndpointHandler + token?: TokenEndpointHandler + userinfo?: UserinfoEndpointHandler } export type OAuthUserConfig

= Omit< diff --git a/packages/next-auth/src/providers/twitch.ts b/packages/next-auth/src/providers/twitch.ts index 71aef987ff..c22bc90d48 100644 --- a/packages/next-auth/src/providers/twitch.ts +++ b/packages/next-auth/src/providers/twitch.ts @@ -11,7 +11,7 @@ export default function Twitch

( options: OAuthUserConfig

): OAuthConfig

{ return { - issuer: "https://id.twitch.tv/oauth2", + wellKnown: "https://id.twitch.tv/oauth2/.well-known/openid-configuration", id: "twitch", name: "Twitch", type: "oauth", diff --git a/packages/next-auth/src/react/index.tsx b/packages/next-auth/src/react/index.tsx index b1a3bdaa8f..5f9ce9072e 100644 --- a/packages/next-auth/src/react/index.tsx +++ b/packages/next-auth/src/react/index.tsx @@ -1,10 +1,11 @@ // Note about signIn() and signOut() methods: // -// On signIn() and signOut() we pass a "X-Auth-Return-Redirect" header -// to request a response in JSON instead of HTTP as redirect URLs on other domains -// are not returned to requests made using the fetch API in the browser, -// and we need to ask the API to return the response as a JSON object -// (the endpoint still defaults to returning an HTTP response with a redirect for non-JavaScript clients). +// On signIn() and signOut() we pass 'json: true' to request a response in JSON +// instead of HTTP as redirect URLs on other domains are not returned to +// requests made using the fetch API in the browser, and we need to ask the API +// to return the response as a JSON object (the end point still defaults to +// returning an HTTP response with a redirect for non-JavaScript clients). +// // We use HTTP POST requests with CSRF Tokens to protect against CSRF attacks. import * as React from "react" diff --git a/packages/next-auth/src/utils/node.ts b/packages/next-auth/src/utils/node.ts index eccdcef15b..608af63cca 100644 --- a/packages/next-auth/src/utils/node.ts +++ b/packages/next-auth/src/utils/node.ts @@ -22,11 +22,6 @@ export function getBody( if (req.body instanceof ReadableStream) { return { body: req.body } } - - if (req.headers["content-type"] === "application/x-www-form-urlencoded") { - return { body: new URLSearchParams(req.body) } - } - return { body: JSON.stringify(req.body) } } diff --git a/packages/next-auth/src/utils/web.ts b/packages/next-auth/src/utils/web.ts index 048b2a9dc2..14db7c520c 100644 --- a/packages/next-auth/src/utils/web.ts +++ b/packages/next-auth/src/utils/web.ts @@ -109,7 +109,7 @@ export function toResponse(res: ResponseInternal): Response { }) if (res.redirect) { - response.headers.set("Location", res.redirect.toString()) + response.headers.set("Location", res.redirect) } return response From ced1ca440a03865024962da845ba79fedb5f2ab9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Tue, 13 Dec 2022 14:07:19 +0100 Subject: [PATCH 86/93] remove extra tsconfig files --- packages/core/tsconfig.dev.json | 21 --------------------- packages/core/tsconfig.eslint.json | 8 -------- 2 files changed, 29 deletions(-) delete mode 100644 packages/core/tsconfig.dev.json delete mode 100644 packages/core/tsconfig.eslint.json diff --git a/packages/core/tsconfig.dev.json b/packages/core/tsconfig.dev.json deleted file mode 100644 index 2c634905f7..0000000000 --- a/packages/core/tsconfig.dev.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "watch": true, - "emitDeclarationOnly": false - }, - "watchOptions": { - "excludeDirectories": [ - "lib", - "css", - "jwt", - "providers", - "core", - "utils" - ], - "excludeFiles": [ - "index.d.ts", - "index.js", - ] - } -} \ No newline at end of file diff --git a/packages/core/tsconfig.eslint.json b/packages/core/tsconfig.eslint.json deleted file mode 100644 index 80c95afec5..0000000000 --- a/packages/core/tsconfig.eslint.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "types": ["@types/jest"], - "typeRoots": ["./node_modules/@types"] - }, - "exclude": ["./coverage", "./*.js", "./*.d.ts"] -} From 369a27b5ed7324b8ee75fde7a9432c387697ec85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Tue, 13 Dec 2022 14:09:45 +0100 Subject: [PATCH 87/93] remove `private` from package.json --- packages/core/package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/core/package.json b/packages/core/package.json index a66b6f9297..59e65506f6 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,7 +1,6 @@ { "name": "next-auth-core", - "private": true, - "version": "0.0.1", + "version": "0.0.0", "description": "Authentication for the web.", "homepage": "https://next-auth.js.org", "repository": "https://github.com/nextauthjs/next-auth.git", From df061b97c1bab9aae44f21e26256a9cd89b17b3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Tue, 13 Dec 2022 16:37:19 +0100 Subject: [PATCH 88/93] remove unused file, expose type --- packages/core/src/lib/types.ts | 1 - packages/core/src/lib/utils/node.ts | 35 ----------------------------- 2 files changed, 36 deletions(-) delete mode 100644 packages/core/src/lib/utils/node.ts diff --git a/packages/core/src/lib/types.ts b/packages/core/src/lib/types.ts index 4a0c31834a..e278e077eb 100644 --- a/packages/core/src/lib/types.ts +++ b/packages/core/src/lib/types.ts @@ -518,7 +518,6 @@ export type InternalProvider = (T extends "oauth" callbackUrl: string } -/** @internal */ export type AuthAction = | "providers" | "session" diff --git a/packages/core/src/lib/utils/node.ts b/packages/core/src/lib/utils/node.ts deleted file mode 100644 index 48a4996744..0000000000 --- a/packages/core/src/lib/utils/node.ts +++ /dev/null @@ -1,35 +0,0 @@ -export function setCookie(res, value: string) { - // Preserve any existing cookies that have already been set in the same session - let setCookieHeader = res.getHeader("Set-Cookie") ?? [] - // If not an array (i.e. a string with a single cookie) convert it into an array - if (!Array.isArray(setCookieHeader)) { - setCookieHeader = [setCookieHeader] - } - setCookieHeader.push(value) - res.setHeader("Set-Cookie", setCookieHeader) -} - -/** Extract the host from the environment */ -export function getURL( - url: string | undefined | null, - trusted: boolean | undefined = !!( - process.env.AUTH_TRUST_HOST ?? process.env.VERCEL - ), - forwardedValue: string | string[] | undefined | null -): URL | Error { - try { - let host = - process.env.NEXTAUTH_URL ?? - (process.env.NODE_ENV !== "production" && "http://localhost:3000") - - if (trusted && forwardedValue) { - host = Array.isArray(forwardedValue) ? forwardedValue[0] : forwardedValue - } - - if (!host) throw new TypeError("Invalid host") - - return new URL(url ?? "", new URL(host)) - } catch (error) { - return error as Error - } -} From 18b8247d4c38f2aaa299dad6e734243d584feec8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Tue, 13 Dec 2022 17:14:43 +0100 Subject: [PATCH 89/93] move gitignore, reduce exposed types --- .gitignore | 9 ++++++++- packages/core/.gitignore | 5 ----- packages/core/src/lib/types.ts | 3 ++- 3 files changed, 10 insertions(+), 7 deletions(-) delete mode 100644 packages/core/.gitignore diff --git a/.gitignore b/.gitignore index 9a03a4fdcf..39e2cbac32 100644 --- a/.gitignore +++ b/.gitignore @@ -79,4 +79,11 @@ test.schema.gql # docusaurus docs/.docusaurus -docs/providers.json \ No newline at end of file +docs/providers.json + +# Core +packages/core/adapters.* +packages/core/index.* +packages/core/jwt +packages/core/lib +packages/core/providers \ No newline at end of file diff --git a/packages/core/.gitignore b/packages/core/.gitignore deleted file mode 100644 index f768a12d22..0000000000 --- a/packages/core/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -adapters.* -index.* -jwt -lib -providers \ No newline at end of file diff --git a/packages/core/src/lib/types.ts b/packages/core/src/lib/types.ts index e278e077eb..736b330214 100644 --- a/packages/core/src/lib/types.ts +++ b/packages/core/src/lib/types.ts @@ -16,6 +16,7 @@ import type { JWT, JWTOptions } from "../jwt" import type { Cookie } from "./cookie" import type { LoggerInstance } from "./utils/logger" +/** @internal */ export type Awaitable = T | PromiseLike export type { LoggerInstance } @@ -426,7 +427,7 @@ export interface PagesOptions { newUser: string } -export type ISODateString = string +type ISODateString = string export interface DefaultSession { user?: { From 6c098d79733589b018af32e1f16eb08906bc4cea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Tue, 13 Dec 2022 17:24:18 +0100 Subject: [PATCH 90/93] add back tsconfig files --- packages/core/tsconfig.dev.json | 18 ++++++++++++++++++ packages/core/tsconfig.eslint.json | 7 +++++++ 2 files changed, 25 insertions(+) create mode 100644 packages/core/tsconfig.dev.json create mode 100644 packages/core/tsconfig.eslint.json diff --git a/packages/core/tsconfig.dev.json b/packages/core/tsconfig.dev.json new file mode 100644 index 0000000000..a86fd87a0d --- /dev/null +++ b/packages/core/tsconfig.dev.json @@ -0,0 +1,18 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "watch": true, + "emitDeclarationOnly": false + }, + "watchOptions": { + "excludeDirectories": [ + "jwt", + "lib", + "providers", + ], + "excludeFiles": [ + "./*.d.ts", + "./*.js" + ] + } +} \ No newline at end of file diff --git a/packages/core/tsconfig.eslint.json b/packages/core/tsconfig.eslint.json new file mode 100644 index 0000000000..8193ffaf5d --- /dev/null +++ b/packages/core/tsconfig.eslint.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "exclude": [ + "./*.js", + "./*.d.ts" + ] +} \ No newline at end of file From 1d1dfb740f647d814df5d2ed143d09cbda9e56f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Tue, 13 Dec 2022 17:24:31 +0100 Subject: [PATCH 91/93] remove leftover --- packages/core/src/lib/types.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/core/src/lib/types.ts b/packages/core/src/lib/types.ts index 736b330214..de0ccbaf2a 100644 --- a/packages/core/src/lib/types.ts +++ b/packages/core/src/lib/types.ts @@ -215,10 +215,6 @@ export interface AuthOptions { * @default Boolean(process.env.NEXTAUTH_URL ?? process.env.AUTH_TRUST_HOST ?? process.env.VERCEL) */ trustHost?: boolean - /** @internal */ - __internal__?: { - runtime?: "web" | "nodejs" - } } /** From 34e4471e1b7aa563023ab78530f7247d6ea6fdbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Tue, 13 Dec 2022 19:02:22 +0100 Subject: [PATCH 92/93] revert gitignore --- .gitignore | 1 - apps/dev/package.json | 2 +- apps/dev/pages/api/auth/[...nextauth].ts | 68 ++++++++++----------- packages/core/package.json | 2 +- packages/core/src/index.ts | 2 +- packages/core/src/lib/assert.ts | 2 +- pnpm-lock.yaml | 78 ++++++++++++++++++++++-- 7 files changed, 112 insertions(+), 43 deletions(-) diff --git a/.gitignore b/.gitignore index 39e2cbac32..ee626052ce 100644 --- a/.gitignore +++ b/.gitignore @@ -34,7 +34,6 @@ 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 diff --git a/apps/dev/package.json b/apps/dev/package.json index 89d53f861f..24b51e7fe1 100644 --- a/apps/dev/package.json +++ b/apps/dev/package.json @@ -23,7 +23,7 @@ "faunadb": "^4", "next": "13.0.6", "next-auth": "workspace:*", - "next-auth-core": "workspace:*", + "@auth/core": "workspace:*", "nodemailer": "^6", "react": "^18", "react-dom": "^18" diff --git a/apps/dev/pages/api/auth/[...nextauth].ts b/apps/dev/pages/api/auth/[...nextauth].ts index f0a49fbce2..ccdcc23ebd 100644 --- a/apps/dev/pages/api/auth/[...nextauth].ts +++ b/apps/dev/pages/api/auth/[...nextauth].ts @@ -1,39 +1,39 @@ -import { AuthHandler, AuthOptions } from "next-auth-core" +import { AuthHandler, type AuthOptions } from "@auth/core" // Providers -import Apple from "next-auth-core/providers/apple" -import Auth0 from "next-auth-core/providers/auth0" -import AzureAD from "next-auth-core/providers/azure-ad" -import AzureB2C from "next-auth-core/providers/azure-ad-b2c" -import BoxyHQSAML from "next-auth-core/providers/boxyhq-saml" -// import Cognito from "next-auth-core/providers/cognito" -import Credentials from "next-auth-core/providers/credentials" -import Discord from "next-auth-core/providers/discord" -import DuendeIDS6 from "next-auth-core/providers/duende-identity-server6" -// import Email from "next-auth-core/providers/email" -import Facebook from "next-auth-core/providers/facebook" -import Foursquare from "next-auth-core/providers/foursquare" -import Freshbooks from "next-auth-core/providers/freshbooks" -import GitHub from "next-auth-core/providers/github" -import Gitlab from "next-auth-core/providers/gitlab" -import Google from "next-auth-core/providers/google" -// import IDS4 from "next-auth-core/providers/identity-server4" -import Instagram from "next-auth-core/providers/instagram" -// import Keycloak from "next-auth-core/providers/keycloak" -import Line from "next-auth-core/providers/line" -import LinkedIn from "next-auth-core/providers/linkedin" -import Mailchimp from "next-auth-core/providers/mailchimp" -// import Okta from "next-auth-core/providers/okta" -import Osu from "next-auth-core/providers/osu" -import Patreon from "next-auth-core/providers/patreon" -import Slack from "next-auth-core/providers/slack" -import Spotify from "next-auth-core/providers/spotify" -import Trakt from "next-auth-core/providers/trakt" -import Twitch from "next-auth-core/providers/twitch" -import Twitter from "next-auth-core/providers/twitter" -import Vk from "next-auth-core/providers/vk" -import Wikimedia from "next-auth-core/providers/wikimedia" -import WorkOS from "next-auth-core/providers/workos" +import Apple from "@auth/core/providers/apple" +import Auth0 from "@auth/core/providers/auth0" +import AzureAD from "@auth/core/providers/azure-ad" +import AzureB2C from "@auth/core/providers/azure-ad-b2c" +import BoxyHQSAML from "@auth/core/providers/boxyhq-saml" +// import Cognito from "@auth/core/providers/cognito" +import Credentials from "@auth/core/providers/credentials" +import Discord from "@auth/core/providers/discord" +import DuendeIDS6 from "@auth/core/providers/duende-identity-server6" +// import Email from "@auth/core/providers/email" +import Facebook from "@auth/core/providers/facebook" +import Foursquare from "@auth/core/providers/foursquare" +import Freshbooks from "@auth/core/providers/freshbooks" +import GitHub from "@auth/core/providers/github" +import Gitlab from "@auth/core/providers/gitlab" +import Google from "@auth/core/providers/google" +// import IDS4 from "@auth/core/providers/identity-server4" +import Instagram from "@auth/core/providers/instagram" +// import Keycloak from "@auth/core/providers/keycloak" +import Line from "@auth/core/providers/line" +import LinkedIn from "@auth/core/providers/linkedin" +import Mailchimp from "@auth/core/providers/mailchimp" +// import Okta from "@auth/core/providers/okta" +import Osu from "@auth/core/providers/osu" +import Patreon from "@auth/core/providers/patreon" +import Slack from "@auth/core/providers/slack" +import Spotify from "@auth/core/providers/spotify" +import Trakt from "@auth/core/providers/trakt" +import Twitch from "@auth/core/providers/twitch" +import Twitter from "@auth/core/providers/twitter" +import Vk from "@auth/core/providers/vk" +import Wikimedia from "@auth/core/providers/wikimedia" +import WorkOS from "@auth/core/providers/workos" // // Prisma // import { PrismaClient } from "@prisma/client" diff --git a/packages/core/package.json b/packages/core/package.json index 59e65506f6..54e397a8c8 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,5 +1,5 @@ { - "name": "next-auth-core", + "name": "@auth/core", "version": "0.0.0", "description": "Authentication for the web.", "homepage": "https://next-auth.js.org", diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 1bef8228ac..39886a1594 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -239,7 +239,7 @@ async function AuthHandlerInternal< } /** - * The core functionality of `next-auth`. + * The core functionality of Auth.js. * 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). */ diff --git a/packages/core/src/lib/assert.ts b/packages/core/src/lib/assert.ts index 585eba3424..16c75eb68f 100644 --- a/packages/core/src/lib/assert.ts +++ b/packages/core/src/lib/assert.ts @@ -37,7 +37,7 @@ function isValidHttpUrl(url: string, baseUrl: string) { } /** - * Verify that the user configured `next-auth` correctly. + * Verify that the user configured Auth.js correctly. * Good place to mention deprecations as well. * * REVIEW: Make some of these and corresponding docs less Next.js specific? diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fa1d53023e..da2df5a7bb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -50,6 +50,7 @@ importers: apps/dev: specifiers: + '@auth/core': workspace:* '@next-auth/fauna-adapter': workspace:* '@next-auth/prisma-adapter': workspace:* '@next-auth/supabase-adapter': workspace:* @@ -63,7 +64,6 @@ importers: faunadb: ^4 next: 13.0.6 next-auth: workspace:* - next-auth-core: workspace:* nodemailer: ^6 pg: ^8.7.3 prisma: ^3 @@ -72,6 +72,7 @@ importers: sqlite3: ^5.0.8 typeorm: 0.3.7 dependencies: + '@auth/core': link:../../packages/core '@next-auth/fauna-adapter': link:../../packages/adapter-fauna '@next-auth/prisma-adapter': link:../../packages/adapter-prisma '@next-auth/supabase-adapter': link:../../packages/adapter-supabase @@ -81,7 +82,6 @@ importers: faunadb: 4.6.0 next: 13.0.6_biqbaboplfbrettd7655fr4n2y next-auth: link:../../packages/next-auth - next-auth-core: link:../../packages/core nodemailer: 6.7.5 react: 18.2.0 react-dom: 18.2.0_react@18.2.0 @@ -470,6 +470,19 @@ importers: postcss: 8.4.19 postcss-nested: 6.0.0_postcss@8.4.19 + packages/frameworks/nextjs: + specifiers: + '@auth/core': workspace:* + '@next-auth/tsconfig': workspace:* + '@types/node': 18.11.10 + next: 13.0.6 + dependencies: + '@auth/core': link:../../core + devDependencies: + '@next-auth/tsconfig': link:../../tsconfig + '@types/node': 18.11.10 + next: 13.0.6 + packages/next-auth: specifiers: '@babel/cli': ^7.17.10 @@ -509,7 +522,6 @@ importers: msw: ^0.42.3 next: 13.0.6 oauth: ^0.9.15 - oauth4webapi: 2.0.4 openid-client: ^5.1.0 postcss: ^8.4.14 postcss-cli: ^9.1.0 @@ -563,7 +575,6 @@ importers: jest-watch-typeahead: 1.1.0_jest@28.1.1 msw: 0.42.3 next: 13.0.6_4cc5zw5azim2bix77d63le72su - oauth4webapi: 2.0.4 postcss: 8.4.14 postcss-cli: 9.1.0_postcss@8.4.14 postcss-nested: 5.0.6_postcss@8.4.14 @@ -19254,6 +19265,48 @@ packages: /next-tick/1.1.0: resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==} + /next/13.0.6: + resolution: {integrity: sha512-COvigvms2LRt1rrzfBQcMQ2GZd86Mvk1z+LOLY5pniFtL4VrTmhZ9salrbKfSiXbhsD01TrDdD68ec3ABDyscA==} + engines: {node: '>=14.6.0'} + hasBin: true + peerDependencies: + fibers: '>= 3.1.0' + node-sass: ^6.0.0 || ^7.0.0 + react: ^18.2.0 + react-dom: ^18.2.0 + sass: ^1.3.0 + peerDependenciesMeta: + fibers: + optional: true + node-sass: + optional: true + sass: + optional: true + dependencies: + '@next/env': 13.0.6 + '@swc/helpers': 0.4.14 + caniuse-lite: 1.0.30001431 + postcss: 8.4.14 + styled-jsx: 5.1.0 + optionalDependencies: + '@next/swc-android-arm-eabi': 13.0.6 + '@next/swc-android-arm64': 13.0.6 + '@next/swc-darwin-arm64': 13.0.6 + '@next/swc-darwin-x64': 13.0.6 + '@next/swc-freebsd-x64': 13.0.6 + '@next/swc-linux-arm-gnueabihf': 13.0.6 + '@next/swc-linux-arm64-gnu': 13.0.6 + '@next/swc-linux-arm64-musl': 13.0.6 + '@next/swc-linux-x64-gnu': 13.0.6 + '@next/swc-linux-x64-musl': 13.0.6 + '@next/swc-win32-arm64-msvc': 13.0.6 + '@next/swc-win32-ia32-msvc': 13.0.6 + '@next/swc-win32-x64-msvc': 13.0.6 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + dev: true + /next/13.0.6_4cc5zw5azim2bix77d63le72su: resolution: {integrity: sha512-COvigvms2LRt1rrzfBQcMQ2GZd86Mvk1z+LOLY5pniFtL4VrTmhZ9salrbKfSiXbhsD01TrDdD68ec3ABDyscA==} engines: {node: '>=14.6.0'} @@ -19572,6 +19625,7 @@ packages: /oauth4webapi/2.0.4: resolution: {integrity: sha512-d6NmQuOlCo6+HzNPG70Pl8T4WnHo/XPvQ3Dxus3fRvRjFmt9H+BggI/APyzQ4/jlcdIjPaOw81wIO6WkRGKfkg==} + dev: false /object-assign/4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} @@ -23248,6 +23302,22 @@ packages: supports-color: 5.5.0 dev: false + /styled-jsx/5.1.0: + resolution: {integrity: sha512-/iHaRJt9U7T+5tp6TRelLnqBqiaIT0HsO0+vgyj8hK2KUk7aejFqRrumqPUlAqDwAj8IbS/1hk3IhBAAK/FCUQ==} + engines: {node: '>= 12.0.0'} + peerDependencies: + '@babel/core': '*' + babel-plugin-macros: '*' + react: '>= 16.8.0 || 17.x.x || ^18.0.0-0' + peerDependenciesMeta: + '@babel/core': + optional: true + babel-plugin-macros: + optional: true + dependencies: + client-only: 0.0.1 + dev: true + /styled-jsx/5.1.0_5wvcx74lvxq2lfpc5x4ihgp2jm: resolution: {integrity: sha512-/iHaRJt9U7T+5tp6TRelLnqBqiaIT0HsO0+vgyj8hK2KUk7aejFqRrumqPUlAqDwAj8IbS/1hk3IhBAAK/FCUQ==} engines: {node: '>= 12.0.0'} From ef3dbfbb497570b435c1a6e8b2de6326662a6d5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Tue, 13 Dec 2022 19:18:54 +0100 Subject: [PATCH 93/93] remove test script --- packages/core/package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/core/package.json b/packages/core/package.json index 54e397a8c8..72d8608b83 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -60,8 +60,7 @@ "build": "pnpm clean && tsc && pnpm css", "clean": "rm -rf adapters.* index.* jwt lib providers", "css": "node ./scripts/generate-css.js", - "dev": "pnpm css && tsc -w", - "test": "jest" + "dev": "pnpm css && tsc -w" }, "devDependencies": { "@next-auth/tsconfig": "workspace:*",