diff --git a/.npmignore b/.npmignore deleted file mode 100644 index 80cc946df1..0000000000 --- a/.npmignore +++ /dev/null @@ -1,3 +0,0 @@ -./types/tests/ -./types/tests/tsconfig.json -./types/tests/tslint.json \ No newline at end of file diff --git a/README.md b/README.md index 2c18c67dc7..6ea78d51ed 100644 --- a/README.md +++ b/README.md @@ -84,13 +84,9 @@ Advanced options allow you to define your own routines to handle controlling wha ### TypeScript -You can install the appropriate types via the following command: +NextAuth.js comes with built-in types. For more information and usage, check out the [TypeScript section](https://next-auth.js.org/getting-started/typescript) in the documentaion. -``` -npm install --save-dev @types/next-auth -``` - -As of now, TypeScript is a community effort. If you encounter any problems with the types package, please create an issue at [DefinitelyTyped](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/next-auth). Alternatively, you can open a pull request directly with your fixes there. We welcome anyone to start a discussion on migrating this package to TypeScript, or how to improve the TypeScript experience in general. +The package at `@types/next-auth` is now deprecated. ## Example diff --git a/adapters.js b/adapters.js deleted file mode 100644 index 72cecb489d..0000000000 --- a/adapters.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('./dist/adapters').default diff --git a/client.js b/client.js deleted file mode 100644 index 319ab6f1af..0000000000 --- a/client.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('./dist/client').default diff --git a/config/build-types.js b/config/build-types.js deleted file mode 100644 index c746a5662f..0000000000 --- a/config/build-types.js +++ /dev/null @@ -1,23 +0,0 @@ -const fs = require('fs') -const path = require('path') - -const BUILD_TARGETS = [ - 'index.d.ts', - 'client.d.ts', - 'adapters.d.ts', - 'providers.d.ts', - 'jwt.d.ts', - '_next.d.ts', - '_utils.d.ts' -] - -BUILD_TARGETS.forEach((target) => { - fs.copyFile( - path.resolve('types', target), - path.join(process.cwd(), target), - (err) => { - if (err) throw err - console.log(`[build-types] copying "${target}" to root folder`) - } - ) -}) diff --git a/config/build.js b/config/build.js new file mode 100644 index 0000000000..879def37c3 --- /dev/null +++ b/config/build.js @@ -0,0 +1,45 @@ +const fs = require("fs-extra") +const path = require("path") + +const MODULE_ENTRIES = { + SERVER: "index", + CLIENT: "client", + PROVIDERS: "providers", + ADAPTERS: "adapters", + JWT: "jwt", +} + +const BUILD_TARGETS = { + [`${MODULE_ENTRIES.SERVER}.js`]: "module.exports = require('./dist/server').default\n", + [`${MODULE_ENTRIES.CLIENT}.js`]: "module.exports = require('./dist/client').default\n", + [`${MODULE_ENTRIES.ADAPTERS}.js`]: "module.exports = require('./dist/adapters').default\n", + [`${MODULE_ENTRIES.PROVIDERS}.js`]: "module.exports = require('./dist/providers').default\n", + [`${MODULE_ENTRIES.JWT}.js`]: "module.exports = require('./dist/lib/jwt').default\n", +} + +Object.entries(BUILD_TARGETS).forEach(([target, content]) => { + fs.writeFile(path.join(process.cwd(), target), content, (err) => { + if (err) throw err + console.log(`[build] created "${target}" in root folder`) + }) +}) + +const TYPES_TARGETS = [ + `${MODULE_ENTRIES.SERVER}.d.ts`, + `${MODULE_ENTRIES.CLIENT}.d.ts`, + `${MODULE_ENTRIES.ADAPTERS}.d.ts`, + `${MODULE_ENTRIES.PROVIDERS}.d.ts`, + `${MODULE_ENTRIES.JWT}.d.ts`, + "internals", +] + +TYPES_TARGETS.forEach((target) => { + fs.copy( + path.resolve("types", target), + path.join(process.cwd(), target), + (err) => { + if (err) throw err + console.log(`[build-types] copying "${target}" to root folder`) + } + ) +}) diff --git a/index.js b/index.js deleted file mode 100644 index 3b4b9b0eef..0000000000 --- a/index.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('./dist/server') diff --git a/jwt.js b/jwt.js deleted file mode 100644 index 01ca6ac72a..0000000000 --- a/jwt.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('./dist/lib/jwt').default diff --git a/package.json b/package.json index 2856fa52dc..d3021871ff 100644 --- a/package.json +++ b/package.json @@ -7,10 +7,9 @@ "author": "Iain Collins ", "main": "index.js", "scripts": { - "build": "npm run build:js && npm run build:css && npm run build:types", - "build:js": "babel --config-file ./config/babel.config.json src --out-dir dist", + "build": "npm run build:js && npm run build:css", + "build:js": "babel --config-file ./config/babel.config.json src --out-dir dist && node ./config/build.js", "build:css": "postcss --config config/postcss.config.js src/**/*.css --base src --dir dist && node config/wrap-css.js", - "build:types": "node ./config/build-types.js", "dev": "next | npm run watch:css", "watch": "npm run watch:js | npm run watch:css", "watch:js": "babel --config-file ./config/babel.config.json --watch src --out-dir dist", @@ -46,8 +45,7 @@ "client.d.ts", "jwt.js", "jwt.d.ts", - "_next.d.ts", - "_utils.d.ts" + "internals" ], "license": "ISC", "dependencies": { diff --git a/providers.js b/providers.js deleted file mode 100644 index a136cdb377..0000000000 --- a/providers.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('./dist/providers').default diff --git a/src/client/index.d.ts b/src/client/index.d.ts deleted file mode 100644 index ae31051e21..0000000000 --- a/src/client/index.d.ts +++ /dev/null @@ -1,103 +0,0 @@ -import * as React from 'react' -import { GetServerSidePropsContext } from 'next' - -interface DefaultSession { - user: { - name: string | null - email: string | null - image: string | null - } - expires: Date | string -} - -interface BroadcastMessage { - event?: 'session' - data?: { - trigger?: 'signout' | 'getSession' - } - clientId: string - timestamp: number -} - -type GetSession = DefaultSession> = (options: { - ctx?: GetServerSidePropsContext - req?: GetServerSidePropsContext['req'] - event?: 'storage' | 'timer' | 'hidden' | string - triggerEvent?: boolean -}) => Promise - -export interface NextAuthConfig { - baseUrl: string - basePath: string - baseUrlServer: string - basePathServer: string - /** 0 means disabled (don't send); 60 means send every 60 seconds */ - keepAlive: number - /** 0 means disabled (only use cache); 60 means sync if last checked > 60 seconds ago */ - clientMaxAge: number - /** Used for timestamp since last sycned (in seconds) */ - _clientLastSync: number - /** Stores timer for poll interval */ - _clientSyncTimer: ReturnType - /** Tracks if event listeners have been added */ - _eventListenersAdded: boolean - /** Stores last session response from hook */ - _clientSession: DefaultSession | null | undefined - /** Used to store to function export by getSession() hook */ - _getSession: any -} - -export type GetCsrfToken = ( - ctxOrReq: GetServerSidePropsContext & GetServerSidePropsContext['req'] -) => Promise - -export interface SessionOptions { - baseUrl?: string - basePath?: string - clientMaxAge?: number - keepAlive?: number -} - -export type Provider = DefaultSession > = (options: { - children: React.ReactNode - session: S - options: SessionOptions -}) => React.ReactNode - -export type SetOptions = (options: SessionOptions) => void - -export type SessionContext = React.createContext<[DefaultSession | null, boolean]> - -export type UseSession = () => [any, boolean] - -export type GetProviders = () => Promise - -// Sign in types - -export interface SignInOptions { - /** Defaults to the current URL. */ - callbackUrl?: string - redirect?: boolean -} -export interface SignInResponse { - error: string | null - status: number - ok: boolean - url: string | null -} - -export type SignIn> = ( - provider?: string, - options?: SignInOptions, - authorizationParams?: AuthorizationParams -) => SignInResponse - -// Sign out types - -interface SignOutResponse { - /** Defaults to the current URL. */ - callbackUrl?: string - redirect?: RedirectType -} - -export type SignOut = (params: SignOutResponse) => RedirectType extends true ? Promise<{url?: string} | undefined> : undefined diff --git a/src/client/index.js b/src/client/index.js index 19a9e3da29..d3deec324d 100644 --- a/src/client/index.js +++ b/src/client/index.js @@ -18,7 +18,7 @@ import parseUrl from '../lib/parse-url' // 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'. -/** @type {import(".").NextAuthConfig} */ +/** @type {import("types/internals/client").NextAuthConfig} */ const __NEXTAUTH = { baseUrl: parseUrl(process.env.NEXTAUTH_URL || process.env.VERCEL_URL).baseUrl, basePath: parseUrl(process.env.NEXTAUTH_URL).basePath, @@ -60,15 +60,9 @@ if (typeof window !== 'undefined' && !__NEXTAUTH._eventListenersAdded) { } // Context to store session data globally +/** @type {import("types/internals/client").SessionContext} */ const SessionContext = createContext() -/** - * React Hook that gives you access - * to the logged in user's session data. - * - * [Documentation](https://next-auth.js.org/getting-started/client#usesession) - * @type {import(".").UseSession} - */ export function useSession (session) { const context = useContext(SessionContext) if (context) return context @@ -143,14 +137,6 @@ function _useSessionHook (session) { return [data, loading] } -/** - * Can be called client or server side to return a session asynchronously. - * It calls `/api/auth/session` and returns a promise with a session object, - * or null if no session exists. - * - * [Documentation](https://next-auth.js.org/getting-started/client#getsession) - * @type {import(".").GetSession} - */ export async function getSession (ctx) { const session = await _fetchData('session', ctx) if (ctx?.triggerEvent ?? true) { @@ -159,39 +145,14 @@ export async function getSession (ctx) { return session } -/** - * Returns the current Cross Site Request Forgery Token (CSRF Token) - * required to make POST requests (e.g. for signing in and signing out). - * You likely only need to use this if you are not using the built-in - * `signIn()` and `signOut()` methods. - * - * [Documentation](https://next-auth.js.org/getting-started/client#getcsrftoken) - * @type {import(".").GetCsrfToken} - */ async function getCsrfToken (ctx) { return (await _fetchData('csrf', ctx))?.csrfToken } -/** - * It calls `/api/auth/providers` and returns - * a list of the currently configured authentication providers. - * It can be useful if you are creating a dynamic custom sign in page. - * - * [Documentation](https://next-auth.js.org/getting-started/client#getproviders) - * @type {import(".").GetProviders} - */ export async function getProviders () { return _fetchData('providers') } -/** - * Client-side method to initiate a signin flow - * or send the user to the signin page listing all possible providers. - * Automatically adds the CSRF token to the request. - * - * [Documentation](https://next-auth.js.org/getting-started/client#signin) - * @type {import(".").SignIn} - */ export async function signIn (provider, options = {}, authorizationParams = {}) { const { callbackUrl = window.location, @@ -255,13 +216,6 @@ export async function signIn (provider, options = {}, authorizationParams = {}) } } -/** - * Signs the user out, by removing the session cookie. - * Automatically adds the CSRF token to the request. - * - * [Documentation](https://next-auth.js.org/getting-started/client#signout) - * @type {import(".").SignOut} - */ export async function signOut (options = {}) { const { callbackUrl = window.location, @@ -298,7 +252,6 @@ export async function signOut (options = {}) { // Method to set options. The documented way is to use the provider, but this // method is being left in as an alternative, that will be helpful if/when we // expose a vanilla JavaScript version that doesn't depend on React. -/** @type {import(".").SetOptions} */ export function setOptions ({ baseUrl, basePath, clientMaxAge, keepAlive } = {}) { if (baseUrl) __NEXTAUTH.baseUrl = baseUrl if (basePath) __NEXTAUTH.basePath = basePath @@ -321,14 +274,6 @@ export function setOptions ({ baseUrl, basePath, clientMaxAge, keepAlive } = {}) } } -/** - * Provider to wrap the app in to make session data available globally. - * Can also be used to throttle the number of requests to the endpoint - * `/api/auth/session`. - * - * [Documentation](https://next-auth.js.org/getting-started/client#provider) - * @type {import(".").Provider} - */ export function Provider ({ children, session, options }) { setOptions(options) return createElement( @@ -387,13 +332,13 @@ function BroadcastChannel (name = 'nextauth.message') { return { /** * Get notified by other tabs/windows. - * @param {(message: import(".").BroadcastMessage) => void} onReceive + * @param {(message: import("types/internals/client").BroadcastMessage) => void} onReceive */ receive (onReceive) { if (typeof window === 'undefined') return window.addEventListener('storage', async (event) => { if (event.key !== name) return - /** @type {import(".").BroadcastMessage} */ + /** @type {import("types/internals/client").BroadcastMessage} */ const message = JSON.parse(event.newValue) if (message?.event !== 'session' || !message?.data) return diff --git a/src/lib/logger.d.ts b/src/lib/logger.d.ts deleted file mode 100644 index 45c4996d46..0000000000 --- a/src/lib/logger.d.ts +++ /dev/null @@ -1,10 +0,0 @@ -export interface LoggerInstance { - warn: (code?: string, ...message: unknown[]) => void - error: (code?: string, ...message: unknown[]) => void - debug: (code?: string, ...message: unknown[]) => void -} - -export declare function proxyLogger (logger: LoggerInstance, basePath: string): LoggerInstance - -const _logger: LoggerInstance -export default _logger diff --git a/src/lib/logger.js b/src/lib/logger.js index a954a4c9d6..7f48ff3e15 100644 --- a/src/lib/logger.js +++ b/src/lib/logger.js @@ -1,4 +1,4 @@ -/** @type {import("./logger").LoggerInstance} */ +/** @type {import("types").LoggerInstance} */ const _logger = { error (code, ...message) { console.error( @@ -26,7 +26,7 @@ const _logger = { /** * Override the built-in logger. * Any `undefined` level will use the default logger. - * @param {Partial} newLogger + * @param {Partial} newLogger */ export function setLogger (newLogger = {}) { if (newLogger.error) _logger.error = newLogger.error @@ -38,9 +38,9 @@ export default _logger /** * Serializes client-side log messages and sends them to the server - * @param {import("./logger").LoggerInstance} logger + * @param {import("types").LoggerInstance} logger * @param {string} basePath - * @return {import("./logger").LoggerInstance} + * @return {import("types").LoggerInstance} */ export function proxyLogger (logger = _logger, basePath) { try { @@ -48,7 +48,7 @@ export function proxyLogger (logger = _logger, basePath) { return logger } - const clientLogger = {} + const clientLogger = console for (const level in logger) { clientLogger[level] = (code, ...message) => { _logger[level](code, ...message) // Log on client as usual diff --git a/src/providers/instagram.js b/src/providers/instagram.js index ec1c51c6b9..a7db755935 100644 --- a/src/providers/instagram.js +++ b/src/providers/instagram.js @@ -1,5 +1,5 @@ /** - * @param {import("../server").Provider} options + * @type {import("types/providers").OAuthProvider} options * @example * * ```js @@ -22,30 +22,29 @@ * * ... * ``` - * *Resources:* - * - [NextAuth.js Documentation](https://next-auth.js.org/providers/instagram) - * - [Instagram Documentation](https://developers.facebook.com/docs/instagram-basic-display-api/getting-started) - * - [Configuration](https://developers.facebook.com/apps) + * [NextAuth.js Documentation](https://next-auth.js.org/providers/instagram) | [Instagram Documentation](https://developers.facebook.com/docs/instagram-basic-display-api/getting-started) | [Configuration](https://developers.facebook.com/apps) */ -export default function Instagram (options) { +export default function Instagram(options) { return { - id: 'instagram', - name: 'Instagram', - type: 'oauth', - version: '2.0', - scope: 'user_profile', - params: { grant_type: 'authorization_code' }, - accessTokenUrl: 'https://api.instagram.com/oauth/access_token', - authorizationUrl: 'https://api.instagram.com/oauth/authorize?response_type=code', - profileUrl: 'https://graph.instagram.com/me?fields=id,username,account_type,name', - async profile (profile) { + id: "instagram", + name: "Instagram", + type: "oauth", + version: "2.0", + scope: "user_profile", + params: { grant_type: "authorization_code" }, + accessTokenUrl: "https://api.instagram.com/oauth/access_token", + authorizationUrl: + "https://api.instagram.com/oauth/authorize?response_type=code", + profileUrl: + "https://graph.instagram.com/me?fields=id,username,account_type,name", + async profile(profile) { return { id: profile.id, name: profile.username, email: null, - image: null + image: null, } }, - ...options + ...options, } } diff --git a/src/server/index.d.ts b/src/server/index.d.ts deleted file mode 100644 index 8b993ac684..0000000000 --- a/src/server/index.d.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { NextApiHandler, NextApiRequest, NextApiResponse } from 'next' -import { LoggerInstance } from 'src/lib/logger' -import { CallbacksOptions } from './lib/callbacks' -import { CookiesOptions } from './lib/cookie' -import { EventsOptions } from './lib/events' - -export interface Provider { - id: string - name: string - type: string - version: string - params: Record - scope: string - accessTokenUrl: string - authorizationUrl: string - profileUrl?: string - grant_type?: string - profile?: (profile: any) => Promise -} - -/** @docs https://next-auth.js.org/configuration/options */ -export interface NextAuthOptions { - /** @docs https://next-auth.js.org/configuration/options#theme */ - theme?: 'auto' | 'dark' | 'light' - /** @docs https://next-auth.js.org/configuration/options#providers */ - providers: Provider[] - /** @docs https://next-auth.js.org/configuration/options#database */ - database?: any - /** @docs https://next-auth.js.org/configuration/options#secret */ - secret?: any - /** @docs https://next-auth.js.org/configuration/options#session */ - session?: any - /** @docs https://next-auth.js.org/configuration/options#jwt */ - jwt?: any - /** @docs https://next-auth.js.org/configuration/options#pages */ - pages?: { - 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 - } - /** - * 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. - * @docs https://next-auth.js.org/configuration/options#callbacks - */ - callbacks?: CallbacksOptions - /** @docs https://next-auth.js.org/configuration/options#events */ - events?: EventsOptions - /** @docs https://next-auth.js.org/configuration/options#adapter */ - adapter?: any - /** @docs https://next-auth.js.org/configuration/options#debug */ - debug?: boolean - /** @docs https://next-auth.js.org/configuration/options#usesecurecookies */ - useSecureCookies?: boolean - /** @docs https://next-auth.js.org/configuration/options#cookies */ - cookies?: CookiesOptions - /** @docs https://next-auth.js.org/configuration/options#logger */ - logger: LoggerInstance -} - -/** Options that are the same both in internal and user provided options. */ -export type NextAuthSharedOptions = 'pages' | 'jwt' | 'events' | 'callbacks' | 'cookies' | 'secret' | 'adapter' | 'theme' | 'debug' | 'logger' - -export interface NextAuthInternalOptions extends Pick { - pkce?: { - code_verifier?: string - /** - * Could be `"plain"`, but not recommended. - * We ignore it for now. - * @spec https://tools.ietf.org/html/rfc7636#section-4.2. - */ - code_challenge_method?: 'S256' - } - provider?: Provider - baseUrl?: string - basePath?: string - action?: string - csrfToken?: string -} - -export interface NextAuthRequest extends NextApiRequest { - options: NextAuthInternalOptions -} - -export interface NextAuthResponse extends NextApiResponse {} - -export declare function NextAuthHandler (req: NextAuthRequest, res: NextAuthResponse, options: NextAuthOptions): ReturnType -export declare function NextAuthHandler (options: NextAuthOptions): ReturnType diff --git a/src/server/index.js b/src/server/index.js index ef7f2ee7a5..33134cec90 100644 --- a/src/server/index.js +++ b/src/server/index.js @@ -24,7 +24,7 @@ if (!process.env.NEXTAUTH_URL) { /** * @param {import("next").NextApiRequest} req * @param {import("next").NextApiResponse} res - * @param {import(".").NextAuthOptions} userOptions + * @param {import("types").NextAuthOptions} userOptions */ async function NextAuthHandler (req, res, userOptions) { if (userOptions.logger) { diff --git a/src/server/lib/callbacks.d.ts b/src/server/lib/callbacks.d.ts deleted file mode 100644 index 34b807e55e..0000000000 --- a/src/server/lib/callbacks.d.ts +++ /dev/null @@ -1,7 +0,0 @@ - -export interface CallbacksOptions { - signIn?: (user: any, account: any, profile: any) => Promise - jwt?: (token: any, user: any, account: any, profile: any, isNewUser?: boolean) => Promise - session?: (session: any, userOrToken: any) => Promise - redirect?: (url: string, baseUrl: string) => Promise -} diff --git a/src/server/lib/cookie.d.ts b/src/server/lib/cookie.d.ts deleted file mode 100644 index 523c91c8e9..0000000000 --- a/src/server/lib/cookie.d.ts +++ /dev/null @@ -1,16 +0,0 @@ -export interface CookieOption { - name: string - options: { - httpOnly: boolean - sameSite: string - path?: string - secure: boolean - } -} - -export interface CookiesOptions { - sessionToken: CookieOption - callbackUrl: CookieOption - csrfToken: CookieOption - pkceCodeVerifier: CookieOption -} diff --git a/src/server/lib/cookie.js b/src/server/lib/cookie.js index a07aebcb6e..e45c06d97a 100644 --- a/src/server/lib/cookie.js +++ b/src/server/lib/cookie.js @@ -9,7 +9,8 @@ * (with fixes for specific issues) to keep dependancy size down. */ export function set (res, name, value, options = {}) { - const stringValue = typeof value === 'object' ? 'j:' + JSON.stringify(value) : String(value) + const stringValue = + typeof value === 'object' ? 'j:' + JSON.stringify(value) : String(value) if ('maxAge' in options) { options.expires = new Date(Date.now() + options.maxAge) @@ -19,7 +20,9 @@ export function set (res, name, value, options = {}) { // 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] } + if (!Array.isArray(setCookieHeader)) { + setCookieHeader = [setCookieHeader] + } setCookieHeader.push(_serialize(name, String(stringValue), options)) res.setHeader('Set-Cookie', setCookieHeader) } @@ -30,32 +33,44 @@ function _serialize (name, val, options) { const opt = options || {} const enc = opt.encode || encodeURIComponent - if (typeof enc !== 'function') { throw new TypeError('option encode is invalid') } + if (typeof enc !== 'function') { + throw new TypeError('option encode is invalid') + } - if (!fieldContentRegExp.test(name)) { throw new TypeError('argument name is invalid') } + if (!fieldContentRegExp.test(name)) { + throw new TypeError('argument name is invalid') + } const value = enc(val) - if (value && !fieldContentRegExp.test(value)) { throw new TypeError('argument val is invalid') } + if (value && !fieldContentRegExp.test(value)) { + throw new TypeError('argument val is invalid') + } let str = name + '=' + value if (opt.maxAge != null) { const maxAge = opt.maxAge - 0 - if (isNaN(maxAge) || !isFinite(maxAge)) { throw new TypeError('option maxAge is invalid') } + if (isNaN(maxAge) || !isFinite(maxAge)) { + throw new TypeError('option maxAge is invalid') + } str += '; Max-Age=' + Math.floor(maxAge) } if (opt.domain) { - if (!fieldContentRegExp.test(opt.domain)) { throw new TypeError('option domain is invalid') } + if (!fieldContentRegExp.test(opt.domain)) { + throw new TypeError('option domain is invalid') + } str += '; Domain=' + opt.domain } if (opt.path) { - if (!fieldContentRegExp.test(opt.path)) { throw new TypeError('option path is invalid') } + if (!fieldContentRegExp.test(opt.path)) { + throw new TypeError('option path is invalid') + } str += '; Path=' + opt.path } else { @@ -73,12 +88,19 @@ function _serialize (name, val, options) { str += '; Expires=' + expires } - if (opt.httpOnly) { str += '; HttpOnly' } + if (opt.httpOnly) { + str += '; HttpOnly' + } - if (opt.secure) { str += '; Secure' } + if (opt.secure) { + str += '; Secure' + } if (opt.sameSite) { - const sameSite = typeof opt.sameSite === 'string' ? opt.sameSite.toLowerCase() : opt.sameSite + const sameSite = + typeof opt.sameSite === 'string' + ? opt.sameSite.toLowerCase() + : opt.sameSite switch (sameSite) { case true: @@ -110,7 +132,7 @@ function _serialize (name, val, options) { * For more on prefixes see https://googlechrome.github.io/samples/cookie-prefixes/ * * @TODO Review cookie settings (names, options) - * @return {import("./cookie").CookiesOptions} + * @return {import("types").CookiesOptions} */ export function defaultCookies (useSecureCookies) { const cookiePrefix = useSecureCookies ? '__Secure-' : '' diff --git a/src/server/lib/events.d.ts b/src/server/lib/events.d.ts deleted file mode 100644 index e09536b21d..0000000000 --- a/src/server/lib/events.d.ts +++ /dev/null @@ -1,12 +0,0 @@ -export type EventType= - | 'signIn' - | 'signOut' - | 'createUser' - | 'updateUser' - | 'linkAccount' - | 'session' - | 'error' - -export type EventCallback = (message: any) => Promise - -export type EventsOptions = Partial> diff --git a/src/server/lib/oauth/callback.js b/src/server/lib/oauth/callback.js index c33fcc7d62..d9207aaf6f 100644 --- a/src/server/lib/oauth/callback.js +++ b/src/server/lib/oauth/callback.js @@ -3,7 +3,7 @@ import oAuthClient from './client' import logger from '../../../lib/logger' import { OAuthCallbackError } from '../../../lib/errors' -/** @param {import("../..").NextAuthRequest} req */ +/** @param {import("types/internals").NextAuthRequest} req */ export default async function oAuthCallback (req) { const { provider, pkce } = req.options const client = oAuthClient(provider) @@ -81,16 +81,8 @@ export default async function oAuthCallback (req) { * Returns profile, raw profile and auth provider details * @param {{ * profileData: object | string - * tokens: { - * accessToken: string - * idToken?: string - * refreshToken?: string - * access_token: string - * expires_in?: string | Date | null - * refresh_token?: string - * id_token?: string - * } - * provider: import("../..").Provider + * tokens: import("types").TokenSet + * provider: import("types/providers").OAuthConfig * user?: object * }} profileParams */ diff --git a/src/server/lib/oauth/client.js b/src/server/lib/oauth/client.js index 7ef9ae3863..70195137ae 100644 --- a/src/server/lib/oauth/client.js +++ b/src/server/lib/oauth/client.js @@ -7,7 +7,7 @@ import { sign as jwtSign } from 'jsonwebtoken' * @TODO Refactor to remove dependancy on 'oauth' package * It is already quite monkey patched, we don't use all the features and and it * would be easier to maintain if all the code was native to next-auth. - * @param {import("../..").Provider} provider + * @param {import("types/providers").OAuthConfig} provider */ export default function oAuthClient (provider) { if (provider.version?.startsWith('2.')) { @@ -88,7 +88,7 @@ export default function oAuthClient (provider) { /** * Ported from https://github.com/ciaranj/node-oauth/blob/a7f8a1e21c362eb4ed2039431fb9ac2ae749f26a/lib/oauth2.js * @param {string} code - * @param {import("../..").Provider} provider + * @param {import("types/providers").OAuthConfig} provider * @param {string | undefined} codeVerifier */ async function getOAuth2AccessToken (code, provider, codeVerifier) { @@ -196,7 +196,7 @@ async function getOAuth2AccessToken (code, provider, codeVerifier) { * * 18/08/2020 @robertcraigie added results parameter to pass data to an optional request preparer. * e.g. see providers/bungie - * @param {import("../..").Provider} provider + * @param {import("types/providers").OAuthConfig} provider * @param {string} accessToken * @param {any} results */ diff --git a/src/server/lib/oauth/pkce-handler.js b/src/server/lib/oauth/pkce-handler.js index 2cfaf8ee1a..b5ab756ed3 100644 --- a/src/server/lib/oauth/pkce-handler.js +++ b/src/server/lib/oauth/pkce-handler.js @@ -10,8 +10,8 @@ const PKCE_MAX_AGE = 60 * 15 // 15 minutes in seconds /** * Adds `code_verifier` to `req.options.pkce`, and removes the corresponding cookie - * @param {import("../..").NextAuthRequest} req - * @param {import("../..").NextAuthResponse} res + * @param {import("types/internals").NextAuthRequest} req + * @param {import("types/internals").NextAuthResponse} res */ export async function handleCallback (req, res) { const { cookies, provider, baseUrl, basePath } = req.options @@ -44,8 +44,8 @@ export async function handleCallback (req, res) { /** * Adds `code_challenge` and `code_challenge_method` to `req.options.pkce`. - * @param {import("../..").NextAuthRequest} req - * @param {import("../..").NextAuthResponse} res + * @param {import("types/internals").NextAuthRequest} req + * @param {import("types/internals").NextAuthResponse} res */ export async function handleSignin (req, res) { const { cookies, provider, baseUrl, basePath } = req.options diff --git a/src/server/lib/oauth/state-handler.js b/src/server/lib/oauth/state-handler.js index 45ee4bd158..3409b36ef0 100644 --- a/src/server/lib/oauth/state-handler.js +++ b/src/server/lib/oauth/state-handler.js @@ -6,8 +6,8 @@ import { OAuthCallbackError } from '../../../lib/errors' * For OAuth 2.0 flows, if the provider supports state, * check if state matches the one sent on signin * (a hash of the NextAuth.js CSRF token). - * @param {import("../..").NextAuthRequest} req - * @param {import("../..").NextAuthResponse} res + * @param {import("types/internals").NextAuthRequest} req + * @param {import("types/internals").NextAuthResponse} res */ export async function handleCallback (req, res) { const { csrfToken, provider, baseUrl, basePath } = req.options @@ -35,8 +35,8 @@ export async function handleCallback (req, res) { /** * Adds CSRF token to the authorizationParams. - * @param {import("../..").NextAuthRequest} req - * @param {import("../..").NextAuthResponse} res + * @param {import("types/internals").NextAuthRequest} req + * @param {import("types/internals").NextAuthResponse} res */ export async function handleSignin (req, res) { const { provider, baseUrl, basePath, csrfToken } = req.options diff --git a/src/server/lib/signin/oauth.js b/src/server/lib/signin/oauth.js index da37ed3a43..1b3a206f65 100644 --- a/src/server/lib/signin/oauth.js +++ b/src/server/lib/signin/oauth.js @@ -1,7 +1,7 @@ import oAuthClient from '../oauth/client' import logger from '../../../lib/logger' -/** @param {import("../..").NextAuthRequest} req */ +/** @param {import("types/internals").NextAuthRequest} req */ export default async function getAuthorizationUrl (req) { const { provider } = req.options diff --git a/src/server/pages/error.js b/src/server/pages/error.js index 0ab3df5012..4b23272580 100644 --- a/src/server/pages/error.js +++ b/src/server/pages/error.js @@ -7,7 +7,7 @@ import { h } from 'preact' // eslint-disable-line no-unused-vars * baseUrl: string * basePath: string * error?: string - * res: import("..").NextAuthResponse + * res: import("types/internals").NextAuthResponse * }} params */ export default function error ({ baseUrl, basePath, error = 'default', res }) { diff --git a/src/server/routes/callback.js b/src/server/routes/callback.js index 73b8211f2b..a54888488e 100644 --- a/src/server/routes/callback.js +++ b/src/server/routes/callback.js @@ -6,8 +6,8 @@ import dispatchEvent from '../lib/dispatch-event' /** * Handle callbacks from login services - * @param {import("..").NextAuthRequest} req - * @param {import("..").NextAuthResponse} res + * @param {import("types/internals").NextAuthRequest} req + * @param {import("types/internals").NextAuthResponse} res */ export default async function callback (req, res) { const { diff --git a/src/server/routes/providers.js b/src/server/routes/providers.js index 3debfe448a..8bf8c16c6d 100644 --- a/src/server/routes/providers.js +++ b/src/server/routes/providers.js @@ -2,8 +2,8 @@ * 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. - * @param {import("..").NextAuthRequest} req - * @param {import("..").NextAuthResponse} res + * @param {import("types/internals").NextAuthRequest} req + * @param {import("types/internals").NextAuthResponse} res */ export default function providers (req, res) { const { providers } = req.options diff --git a/tsconfig.json b/tsconfig.json index 2a7e176a12..292cac8d22 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,6 +3,9 @@ "strictNullChecks": true, "baseUrl": ".", "paths": { + "types": [ + "./types" + ], "next-auth": [ "./src/server" ], diff --git a/types/_utils.d.ts b/types/_utils.d.ts deleted file mode 100644 index 6c155e5774..0000000000 --- a/types/_utils.d.ts +++ /dev/null @@ -1,14 +0,0 @@ -export type NonNullParams = { - [K in keyof T]: T[K] extends Record - ? NonNullParams - : NonNullable -} - -export type NullableParams = { - [K in keyof T]: T[K] | undefined | null -} - -export type WithAdditionalParams> = T & - Record - -export type Awaitable = T | PromiseLike diff --git a/types/adapters.d.ts b/types/adapters.d.ts index 520a3fc457..82aaf94bdf 100644 --- a/types/adapters.d.ts +++ b/types/adapters.d.ts @@ -1,6 +1,7 @@ +import { AppOptions } from "internals" import { ConnectionOptions, EntitySchema } from "typeorm" -import { AppOptions, User } from "." -import { AppProvider } from "./providers" +import { User } from "." +import { AppProvider } from "internals/providers" export interface Profile { id: string @@ -99,7 +100,7 @@ interface Adapter< type Schema = EntitySchema["options"] -interface Adapters { +interface BuiltInAdapters { Default: TypeORMAdapter["Adapter"] TypeORM: TypeORMAdapter Prisma: PrismaAdapter @@ -170,8 +171,6 @@ interface PrismaAdapter { }) => Adapter } -declare const Adapters: Adapters - declare class TypeORMAccountModel { compoundId: string userId: number @@ -229,10 +228,13 @@ declare class TypeORMVerificationRequestModel implements VerificationRequest { constructor(identifier: string, token: string, expires: Date) } +declare const Adapters: BuiltInAdapters + export default Adapters + export { Adapter, - Adapters, + BuiltInAdapters as Adapters, TypeORMAdapter, TypeORMAccountModel, TypeORMUserModel, diff --git a/types/client.d.ts b/types/client.d.ts index 878451d1ee..eb905e49c0 100644 --- a/types/client.d.ts +++ b/types/client.d.ts @@ -1,97 +1,218 @@ -import { FC } from "react" +import * as React from "react" import { IncomingMessage } from "http" -import { WithAdditionalParams } from "./_utils" import { Session } from "." -import { AppProvider, DefaultProviders, Providers } from "./providers" +import { ProviderType } from "./providers" -interface ContextProviderProps { - session: WithAdditionalParams | null | undefined - options?: SetOptionsParams +export interface CtxOrReq { + req?: IncomingMessage + ctx?: { req: IncomingMessage } } -interface SetOptionsParams { - baseUrl?: string - basePath?: string - clientMaxAge?: number - keepAlive?: number +/*************** + * Session types + **************/ + +export type GetSessionOptions = CtxOrReq & { + event?: "storage" | "timer" | "hidden" | string + triggerEvent?: boolean +} + +/** + * React Hook that gives you access + * to the logged in user's session data. + * + * [Documentation](https://next-auth.js.org/getting-started/client#usesession) + */ +export function useSession(): [Session | null, boolean] + +/** + * Can be called client or server side to return a session asynchronously. + * It calls `/api/auth/session` and returns a promise with a session object, + * or null if no session exists. + * + * [Documentation](https://next-auth.js.org/getting-started/client#getsession) + */ +export function getSession(options: GetSessionOptions): Promise + +/** + * Alias for `getSession` + * @docs https://next-auth.js.org/getting-started/client#getsession + */ +export const session: typeof getSession + +/******************* + * CSRF Token types + ******************/ + +/** + * Returns the current Cross Site Request Forgery Token (CSRF Token) + * required to make POST requests (e.g. for signing in and signing out). + * You likely only need to use this if you are not using the built-in + * `signIn()` and `signOut()` methods. + * + * [Documentation](https://next-auth.js.org/getting-started/client#getcsrftoken) + */ +export function getCsrfToken(ctxOrReq: CtxOrReq): Promise + +/** + * Alias for `getCsrfToken` + * @docs https://next-auth.js.org/getting-started/client#getcsrftoken + */ +export const csrfToken: typeof getCsrfToken + +/****************** + * Providers types + *****************/ + +export interface ClientSafeProvider { + id: string + name: string + type: ProviderType + signinUrl: string + callbackUrl: string +} + +/** + * It calls `/api/auth/providers` and returns + * a list of the currently configured authentication providers. + * It can be useful if you are creating a dynamic custom sign in page. + * + * [Documentation](https://next-auth.js.org/getting-started/client#getproviders) + */ +export function getProviders(): Promise | null> + +/** + * Alias for `getProviders` + * @docs https://next-auth.js.org/getting-started/client#getproviders + */ +export const providers: typeof getProviders + +/**************** + * Sign in types + ***************/ + +export type RedirectableProvider = "email" | "credentials" + +export type SignInProvider = RedirectableProvider | string | undefined + +export interface SignInOptions extends Record { + /** + * Defaults to the current URL. + * @docs https://next-auth.js.org/getting-started/client#specifying-a-callbackurl + */ + callbackUrl?: string + /** @docs https://next-auth.js.org/getting-started/client#using-the-redirect-false-option */ + redirect?: boolean } -interface SignInResponse { +export interface SignInResponse { error: string | undefined status: number ok: boolean url: string | null } -type ContextProvider = FC +/** Match `inputType` of `new URLSearchParams(inputType)` */ +export type SignInAuthorisationParams = + | string + | string[][] + | Record + | URLSearchParams -interface NextContext { - req?: IncomingMessage - ctx?: { req: IncomingMessage } -} +/** + * Client-side method to initiate a signin flow + * or send the user to the signin page listing all possible providers. + * Automatically adds the CSRF token to the request. + * + * [Documentation](https://next-auth.js.org/getting-started/client#signin) + */ +export function signIn

( + provider?: P, + options?: SignInOptions, + authorizationParams?: SignInAuthorisationParams +): Promise< + P extends RedirectableProvider ? SignInResponse | undefined : undefined +> -declare function useSession(): [Session | null | undefined, boolean] +/** + * Alias for `signIn` + * @docs https://next-auth.js.org/getting-started/client#signin + */ +export const signin: typeof signIn -declare function providers(): Promise | null> -declare const getProviders: typeof providers -declare function session( - context?: NextContext & { - triggerEvent?: boolean - } -): Promise -declare const getSession: typeof session -declare function csrfToken(context?: NextContext): Promise -declare const getCsrfToken: typeof csrfToken -declare function signin( - provider: "credentials" | "email", - data?: Record & { - callbackUrl?: string - redirect?: false - }, - authorizationParams?: - | string - | string[][] - | Record - | URLSearchParams -): Promise -declare function signin( - provider?: string, - data?: Record & { - callbackUrl?: string - redirect?: boolean - }, - authorizationParams?: - | string - | string[][] - | Record - | URLSearchParams -): Promise -declare const signIn: typeof signin -declare function signout(data?: { +/**************** + * Sign out types + ****************/ + +/** @docs https://next-auth.js.org/getting-started/client#using-the-redirect-false-option-1 */ +export interface SignOutResponse { + url: string +} + +export interface SignOutParams { + /** @docs https://next-auth.js.org/getting-started/client#specifying-a-callbackurl-1 */ callbackUrl?: string - redirect?: boolean -}): Promise -declare const signOut: typeof signout -declare function options(options: SetOptionsParams): void -declare const setOptions: typeof options -declare const Provider: ContextProvider - -export { - useSession, - session, - getSession, - providers, - getProviders, - csrfToken, - getCsrfToken, - signin, - signIn, - signout, - signOut, - options, - setOptions, - Provider, + /** @docs https://next-auth.js.org/getting-started/client#using-the-redirect-false-option-1 */ + redirect?: R } + +/** + * Signs the user out, by removing the session cookie. + * Automatically adds the CSRF token to the request. + * + * [Documentation](https://next-auth.js.org/getting-started/client#signout) + */ +export function signOut( + params?: SignOutParams +): Promise + +/** + * @docs https://next-auth.js.org/getting-started/client#signout + * Alias for `signOut` + */ +export const signout: typeof signOut +/************************ + * SessionProvider types + ***********************/ + +/** @docs: https://next-auth.js.org/getting-started/client#options */ +export interface SessionProviderOptions { + baseUrl?: string + basePath?: string + clientMaxAge?: number + keepAlive?: number +} + +/** + * Provider to wrap the app in to make session data available globally. + * Can also be used to throttle the number of requests to the endpoint + * `/api/auth/session`. + * + * [Documentation](https://next-auth.js.org/getting-started/client#provider) + */ +export type SessionProvider = React.FC<{ + children: React.ReactNode + session?: Session + options?: SessionProviderOptions +}> + +/** + * Provider to wrap the app in to make session data available globally. + * Can also be used to throttle the number of requests to the endpoint + * `/api/auth/session`. + * + * [Documentation](https://next-auth.js.org/getting-started/client#provider) + */ +export const Provider: SessionProvider + +/** @docs: https://next-auth.js.org/getting-started/client#options */ +export function setOptions(options: SessionProviderOptions): void + +/** + * Alias for `setOptions` + * @docs: https://next-auth.js.org/getting-started/client#options + */ +export const options: typeof setOptions diff --git a/types/index.d.ts b/types/index.d.ts index 77e5f1d7b8..1d9f69742f 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -5,64 +5,222 @@ import { ConnectionOptions } from "typeorm" import { Adapter } from "./adapters" import { JWTOptions, JWT } from "./jwt" -import { AppProvider, Providers } from "./providers" -import { NextApiRequest, NextApiResponse, NextApiHandler } from "./_next" -import { Awaitable, NonNullParams, WithAdditionalParams } from "./_utils" +import { AppProviders } from "./providers" +import { + Awaitable, + NextApiRequest, + NextApiResponse, + NextApiHandler, +} from "internals/utils" +/** + * Configure your NextAuth instance + * + * [Documentation](https://next-auth.js.org/configuration/options#options) + */ export interface NextAuthOptions { - providers: Providers + /** + * 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 documentaion](https://next-auth.js.org/configuration/providers) + */ + providers: AppProviders + /** + * A database connection string or configuration object. + * * **Default value**: `null` + * * **Required**: *No (unless using email provider)* + * + * [Documentation](https://next-auth.js.org/configuration/options#database) | [Databases](https://next-auth.js.org/configuration/databases) + */ database?: string | Record | ConnectionOptions + /** + * A random string used to hash tokens, sign cookies and generate crytographic keys. + * If not specified is uses a hash of all configuration options, including Client ID / Secrets for entropy. + * The default behaviour is volatile, and **it is strongly recommended** you explicitly specify a value + * to avoid invalidating end user sessions when configuration changes are deployed. + * * **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 documentaion page + * * **Required**: No + * + * [Documentation](https://next-auth.js.org/configuration/options#session) + */ session?: SessionOptions + /** + * JSON Web Tokens can be used for session tokens if enabled with the `session: { jwt: true }` option. + * JSON Web Tokens are enabled by default if you have not specified a database. + * By default JSON Web Tokens are signed (JWS) but not encrypted (JWE), + * as JWT encryption adds additional overhead and comes with some caveats. + * You can enable encryption by setting `encryption: true`. + * * **Default value**: See the documentation page + * * **Required**: *No* + * + * [Documentation](https://next-auth.js.org/configuration/options#jwt) + */ jwt?: JWTOptions + /** + * 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: null + * } + * ``` + * + * [Documentation](https://next-auth.js.org/configuration/options#pages) | [Pages documentaion](https://next-auth.js.org/configuration/pages) + */ pages?: PagesOptions + /** + * 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 documentaion + * * **Required**: *No* + * + * [Documentation](https://next-auth.js.org/configuration/options#callbacks) | [Callbacks documentaion](https://next-auth.js.org/configuration/callbacks) + */ callbacks?: CallbacksOptions - debug?: boolean - adapter?: Adapter + /** + * 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 documentaion](https://next-auth.js.org/configuration/events) + */ events?: EventsOptions + /** + * By default NextAuth.js uses a database adapter that uses TypeORM and supports MySQL, MariaDB, Postgres and MongoDB and SQLite databases. + * An alternative adapter that uses Prisma, which currently supports MySQL, MariaDB and Postgres, is also included. + * You can use the adapter option to use the Prisma adapter - or pass in your own adapter + * if you want to use a database that is not supported by one of the built-in adapters. + * * **Default value**: TypeORM adapter + * * **Required**: *No* + * + * - ⚠ If the `adapter` option is specified it overrides the `database` option, only specify one or the other. + * - ⚠ Adapters are being migrated to their own home in a Community maintained repository. + * + * [Documentation](https://next-auth.js.org/configuration/options#adapter) | + * [Default adapter](https://next-auth.js.org/schemas/adapters#typeorm-adapter) | + * [Community adapters](https://github.com/nextauthjs/adapters) + */ + 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 documentaion](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 documentaion](https://next-auth.js.org/configuration/options#debug) + */ + logger?: LoggerInstance + /** + * 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?: "auto" | "dark" | "light" + /** + * 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 behaviour + * 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?: CookiesOptions - logger?: LoggerInstance - theme?: "light" | "dark" | "auto" } export interface LoggerInstance { - warn: (code?: string, ...message: unknown[]) => void - error: (code?: string, ...message: unknown[]) => void - debug: (code?: string, ...message: unknown[]) => void + warn(code: string, ...message: unknown[]): void + error(code: string, ...message: unknown[]): void + debug(code: string, ...message: unknown[]): void } -interface InternalOptions - extends Omit< - NextAuthOptions, - "providers" | "database" | "session" | "useSecureCookie" - > { - pkce: { - code_verifier?: string - code_challenge_method?: "S256" - } - provider?: string - baseUrl?: string - basePath?: string - action?: - | "providers" - | "session" - | "csrf" - | "signin" - | "signout" - | "callback" - | "verify-request" - | "error" - csrfToken?: string -} - -export interface AppOptions - extends Omit, - NonNullParams { - providers: AppProvider[] -} - -export interface Account extends Record { +export interface TokenSet { accessToken: string idToken?: string refreshToken?: string @@ -70,11 +228,20 @@ export interface Account extends Record { expires_in?: number | null refresh_token?: string id_token?: string +} + +export interface Account extends TokenSet, Record { id: string provider: string type: string } -export interface Profile extends Record {} + +export interface Profile extends Record { + sub?: string + name?: string + email?: string + image?: string +} export interface CallbacksOptions< P extends Record = Profile, @@ -127,13 +294,15 @@ export type EventsOptions = Partial> export interface PagesOptions { signIn?: string signOut?: string + /** Error code passed in query string as ?error= */ error?: string verifyRequest?: string - newUser?: string | null + /** If set, new users will be directed here on first sign in */ + newUser?: string } -export interface Session { - user: WithAdditionalParams +export interface Session extends Record { + user?: User accessToken?: string expires: string } @@ -150,24 +319,12 @@ export interface User { image?: string | null } -export interface NextAuthRequest extends NextApiRequest { - options: InternalOptions -} -export type NextAuthResponse = NextApiResponse - -declare function NextAuthHandler( - req: NextApiRequest, - res: NextApiResponse, - options?: NextAuthOptions -): ReturnType declare function NextAuth( req: NextApiRequest, res: NextApiResponse, - options?: NextAuthOptions -): ReturnType -declare function NextAuth( options: NextAuthOptions -): ReturnType +): ReturnType + +declare function NextAuth(options: NextAuthOptions): ReturnType -export { NextAuthHandler, NextAuth } export default NextAuth diff --git a/types/internals/client.d.ts b/types/internals/client.d.ts new file mode 100644 index 0000000000..71c8d45851 --- /dev/null +++ b/types/internals/client.d.ts @@ -0,0 +1,34 @@ +import * as React from "react" +import { Session } from ".." + +export interface BroadcastMessage { + event?: "session" + data?: { + trigger?: "signout" | "getSession" + } + clientId: string + timestamp: number +} + +export interface NextAuthConfig { + baseUrl: string + basePath: string + baseUrlServer: string + basePathServer: string + /** 0 means disabled (don't send); 60 means send every 60 seconds */ + keepAlive: number + /** 0 means disabled (only use cache); 60 means sync if last checked > 60 seconds ago */ + clientMaxAge: number + /** Used for timestamp since last sycned (in seconds) */ + _clientLastSync: number + /** Stores timer for poll interval */ + _clientSyncTimer: ReturnType + /** Tracks if event listeners have been added */ + _eventListenersAdded: boolean + /** Stores last session response from hook */ + _clientSession: Session | null | undefined + /** Used to store to function export by getSession() hook */ + _getSession: any +} + +export type SessionContext = React.Context diff --git a/types/internals/index.d.ts b/types/internals/index.d.ts new file mode 100644 index 0000000000..1e3834e814 --- /dev/null +++ b/types/internals/index.d.ts @@ -0,0 +1,49 @@ +import { NextApiRequest, NextApiResponse } from "internals/utils" +import { NextAuthOptions } from ".." +import { AppProvider } from "internals/providers" + +/** Options that are the same both in internal and user provided options. */ +export type NextAuthSharedOptions = + | "pages" + | "jwt" + | "events" + | "callbacks" + | "cookies" + | "secret" + | "adapter" + | "theme" + | "debug" + | "logger" + +export interface AppOptions + extends Pick { + pkce?: { + code_verifier?: string + /** + * Could be `"plain"`, but not recommended. + * We ignore it for now. + * @spec https://tools.ietf.org/html/rfc7636#section-4.2. + */ + code_challenge_method?: "S256" + } + provider?: AppProvider + providers: AppProvider[] + baseUrl?: string + basePath?: string + action?: + | "providers" + | "session" + | "csrf" + | "signin" + | "signout" + | "callback" + | "verify-request" + | "error" + csrfToken?: string +} + +export interface NextAuthRequest extends NextApiRequest { + options: AppOptions +} + +export type NextAuthResponse = NextApiResponse diff --git a/types/_next.d.ts b/types/internals/next.d.ts similarity index 100% rename from types/_next.d.ts rename to types/internals/next.d.ts diff --git a/types/internals/providers.d.ts b/types/internals/providers.d.ts new file mode 100644 index 0000000000..115c732475 --- /dev/null +++ b/types/internals/providers.d.ts @@ -0,0 +1,6 @@ +import { CommonProviderOptions } from "next-auth/providers" + +export interface AppProvider extends CommonProviderOptions { + signinUrl: string + callbackUrl: string +} diff --git a/types/internals/utils.d.ts b/types/internals/utils.d.ts new file mode 100644 index 0000000000..4f5fa07133 --- /dev/null +++ b/types/internals/utils.d.ts @@ -0,0 +1,42 @@ +import { IncomingMessage, ServerResponse } from "http" + +export type Awaitable = T | PromiseLike + +// ------------------------------------------------------ +// Types from next@10, +// see: https://github.com/microsoft/dtslint/issues/297 +// ------------------------------------------------------ +export interface NextApiRequest extends IncomingMessage { + query: { + [key: string]: string | string[] + } + cookies: { + [key: string]: string + } + body: any + env: any + preview?: boolean + previewData?: any +} + +export type Send = (body: T) => void + +export type NextApiResponse = ServerResponse & { + send: Send + json: Send + status: (statusCode: number) => NextApiResponse + redirect: ((url: string) => NextApiResponse) & + ((status: number, url: string) => NextApiResponse) + setPreviewData: ( + data: object | string, + options?: { + maxAge?: number + } + ) => NextApiResponse + clearPreviewData: () => NextApiResponse +} + +export type NextApiHandler = ( + req: NextApiRequest, + res: NextApiResponse +) => void | Promise diff --git a/types/jwt.d.ts b/types/jwt.d.ts index 393334c41c..23ecbd2361 100644 --- a/types/jwt.d.ts +++ b/types/jwt.d.ts @@ -1,6 +1,5 @@ -import { JWT, JWE } from "jose" -import { NextApiRequest } from "./_next" -import { WithAdditionalParams } from "./_utils" +import { JWT as JoseJWT, JWE } from "jose" +import { NextApiRequest } from "internals/utils" export interface JWT extends Record { name?: string | null @@ -9,59 +8,53 @@ export interface JWT extends Record { } export interface JWTEncodeParams { - token?: WithAdditionalParams + token?: JWT maxAge?: number secret: string | Buffer signingKey?: string - signingOptions?: JWT.SignOptions + signingOptions?: JoseJWT.SignOptions encryptionKey?: string encryptionOptions?: object encryption?: boolean } +export function encode(params?: JWTEncodeParams): Promise + export interface JWTDecodeParams { token?: string maxAge?: number secret: string | Buffer signingKey?: string verificationKey?: string - verificationOptions?: JWT.VerifyOptions + verificationOptions?: JoseJWT.VerifyOptions encryptionKey?: string decryptionKey?: string decryptionOptions?: JWE.DecryptOptions encryption?: boolean } +export function decode(params?: JWTDecodeParams): Promise + +export type GetTokenParams = { + req: NextApiRequest + secureCookie?: boolean + cookieName?: string + raw?: R + decode?: typeof decode + secret?: string +} & Omit + +export function getToken( + params?: GetTokenParams +): Promise + export interface JWTOptions { secret?: string maxAge?: number encryption?: boolean signingKey?: string encryptionKey?: string - encode?: (options: JWTEncodeParams) => Promise - decode?: (options: JWTDecodeParams) => Promise> + encode?: typeof encode + decode?: typeof decode + verificationOptions?: JoseJWT.VerifyOptions } - -declare function encode(args?: JWTEncodeParams): Promise - -declare function decode( - args?: JWTDecodeParams & { token: string } -): Promise> - -declare function getToken( - args?: { - req: NextApiRequest - secureCookie?: boolean - cookieName?: string - raw?: string - } & JWTDecodeParams -): Promise> - -declare function getToken(args?: { - req: NextApiRequest - secureCookie?: boolean - cookieName?: string - raw: true -}): Promise - -export { encode, decode, getToken } diff --git a/types/providers.d.ts b/types/providers.d.ts index 71819cd003..553c24a908 100644 --- a/types/providers.d.ts +++ b/types/providers.d.ts @@ -1,435 +1,164 @@ -import { User } from "." -import { JWT } from "./jwt" -import { NonNullParams, NullableParams, WithAdditionalParams } from "./_utils" +import { Profile, TokenSet, User } from "." +import { Awaitable } from "internals/utils" -export interface Provider< - T extends string | undefined = undefined, - U = T extends string ? "oauth" : string -> { - id: T - name: string - type: U extends string ? U : "oauth" | "email" | "credentials" - version: string - scope: string - params: { grant_type: string } - accessTokenUrl: string - requestTokenUrl: string - authorizationUrl: string - profileUrl: string - profile: ( - profile: Record, - tokens: any - ) => (User & { id: string }) | Promise - clientId: string - clientSecret: string | Record - idToken?: boolean -} - -export interface AppProvider extends Pick { - signinUrl: string - callbackUrl: string -} - -export interface DefaultProviders { - Apple: Apple - Attlassian: Atlassian - Auth0: Auth0 - AzureADB2C: AzureADB2C - Basecamp: Basecamp - BattleNet: BattleNet - Box: Box - Bungie: Bungie - Cognito: Cognito - Credentials: Credentials - Discord: Discord - Email: Email - EVEOnline: EVEOnline - Facebook: Facebook - FACEIT: FACEIT - Foursquare: Foursquare - FusionAuth: FusionAuth - GitHub: GitHub - GitLab: GitLab - Google: Google - IdentityServer4: IdentityServer4 - Instagram: Instagram - Kakao: Kakao - LINE: LINE - LinkedIn: LinkedIn - MailRu: MailRu - Medium: Medium - Netlify: Netlify - Okta: Okta - Osso: Osso - Reddit: Reddit - Salesforce: Salesforce - Slack: Slack - Spotify: Spotify - Strava: Strava - Twitch: Twitch - Twitter: Twitter - VK: VK - Yandex: Yandex - Zoho: Zoho -} - -export type Providers = Array< - Provider | ReturnType -> - -declare const Providers: DefaultProviders - -export default Providers - -/** - * Email - */ -type Email = ( - options: ProviderEmailOptions -) => NonNullParams & { id: "email"; type: "email" } - -interface VerificationRequestParams extends Provider { - identifier: string - url: string - baseUrl: string - token: string - provider: ProviderEmailOptions -} +export type ProviderType = "oauth" | "email" | "credentials" -interface ProviderEmailOptions { - name?: string - server?: string | ProviderEmailServer - from?: string - maxAge?: number - sendVerificationRequest?: ( - options: VerificationRequestParams - ) => Promise -} - -interface ProviderEmailServer { - host: string - port: number - auth: { - user: string - pass: string - } +export interface CommonProviderOptions { + id: string + name: string + type: ProviderType } /** - * Credentials + * OAuth Provider */ -type Credentials = ( - options: ProviderCredentialsOptions -) => NonNullParams & { - id: "credentials" - type: "credentials" -} - -interface ProviderCredentialsOptions { - id?: string - name: string - credentials: CredentialInput - authorize: (credentials: Record) => Promise -} - -interface CredentialInput { - [key: string]: { - label?: string - type?: string - value?: string - placeholder?: string - } -} -type OptionsBase = { - [K in keyof Omit]?: Provider[K] -} +type ProtectionType = "pkce" | "state" | "both" | "none" /** - * Provider options - * @link https://next-auth.js.org/configuration/providers#oauth-provider-options + * OAuth provider options + * + * [Documentation](https://next-auth.js.org/configuration/providers#oauth-provider-options) */ -interface ProviderCommonOptions extends OptionsBase { +export interface OAuthConfig

= Profile> + extends CommonProviderOptions { authorizationParams?: Record - clientId: string - clientSecret: string headers?: Record + type: "oauth" + version: string + scope: string + params: { grant_type: string } + accessTokenUrl: string + requestTokenUrl: string + authorizationUrl: string + profileUrl: string + profile(profile: P, tokens: TokenSet): Awaitable + protection?: ProtectionType | ProtectionType[] + clientId: string + clientSecret: + | string + // TODO: only allow for Apple + | Record<"appleId" | "teamId" | "privateKey" | "keyId", string> idToken?: boolean - name?: string - protection?: "pkce" | "state" | "both" | "none" + /** + * @deprecated Will be removed in an upcoming major release. Use `protection: ["state"]` instead. + */ state?: boolean -} - -/** - * Apple - */ -type Apple = ( - options: ProviderAppleOptions -) => Provider<"apple"> & { protection: "none" } - -interface ProviderAppleOptions - extends Omit { - name?: string - clientId: string - clientSecret: Record<"appleId" | "teamId" | "privateKey" | "keyId", string> -} - -interface ProviderAppleSecret { - appleId: string - teamId: string - privateKey: string - keyId: string -} - -/** - * Twitter - */ -type Twitter = (options: ProviderCommonOptions) => Provider<"twitter"> - -/** - * Facebook - */ -type Facebook = (options: ProviderCommonOptions) => Provider<"facebook"> - -/** - * GitHub - */ -type GitHub = (options: ProviderGitHubOptions) => Provider<"github"> - -interface ProviderGitHubOptions extends Omit { - scope?: string -} - -/** - * GitLab - */ -type GitLab = (options: ProviderCommonOptions) => Provider<"gitlab"> - -/** - * Slack - */ -type Slack = (options: ProviderCommonOptions) => Provider<"slack"> - -/** - * Google - */ -type Google = (options: ProviderGoogleOptions) => Provider<"google"> - -interface ProviderGoogleOptions extends ProviderCommonOptions { - authorizationUrl?: string -} - -/** - * Auth0 - */ -type Auth0 = ( - options: ProviderAuth0Options -) => Provider<"auth0"> & { domain: string } - -interface ProviderAuth0Options extends Omit { - domain: string - profile?: (profile: Auth0Profile) => User & { id: string } -} -interface Auth0Profile { - sub: string - nickname: string - email: string - picture: string -} - -/** - * IS4 - */ - -type IdentityServer4 = ( - options: ProviderIS4Options -) => Provider<"identity-server4" | string> & { domain: string } - -interface ProviderIS4Options extends Omit { - id: string - scope: string - domain: string -} - -/** - * Discord - */ -type Discord = (options: ProviderCommonOptions) => Provider<"discord"> - -/** - * Twitch - */ -type Twitch = (options: ProviderCommonOptions) => Provider<"twitch"> - -/** - * Okta - */ -type Okta = ( - options: ProviderOktaOptions -) => Provider<"okta"> & { domain: string } - -interface ProviderOktaOptions extends ProviderCommonOptions { - domain: string -} - -/** - * Battle.net - */ -type BattleNet = ( - options: ProviderBattleNetOptions -) => Provider<"battlenet"> & { region: string } - -interface ProviderBattleNetOptions extends ProviderCommonOptions { - region: string -} - -/** - * Box - */ -type Box = (options: ProviderCommonOptions) => Provider<"box"> - -/** - * Cognito - */ -type Cognito = ( - options: ProviderCognitoOptions -) => Provider<"cognito"> & { domain: string } - -interface ProviderCognitoOptions extends ProviderCommonOptions { - domain: string -} - -/** - * Yandex - */ -type Yandex = (options: ProviderCommonOptions) => Provider<"yandex"> - -/** - * LinkedIn - */ -type LinkedIn = (options: ProviderLinkedInOptions) => Provider<"linkedin"> - -interface ProviderLinkedInOptions extends ProviderCommonOptions { - scope?: string -} - -/** - * Spotify - */ -type Spotify = (options: ProviderSpotifyOptions) => Provider<"spotify"> - -interface ProviderSpotifyOptions extends ProviderCommonOptions { - scope?: string -} - -/** - * Basecamp - */ -type Basecamp = (options: ProviderCommonOptions) => Provider<"basecamp"> - -/** - * Reddit - */ -type Reddit = (options: ProviderCommonOptions) => Provider<"reddit"> - -/** - * Atlassian - */ -type Atlassian = (options: ProviderCommonOptions) => Provider<"atlassian"> - -/** - * AzureADB2C - */ -type AzureADB2C = ( - options: ProviderAzureADB2COptions -) => Provider<"azure-ad-b2c"> - -interface ProviderAzureADB2COptions extends ProviderCommonOptions { + // TODO: only allow for BattleNet + region?: string + // TODO: only allow for some + domain?: string + // TODO: only allow for Azure Active Directory B2C and FusionAuth tenantId?: string } -/** - * Bungie - */ -type Bungie = (options: ProviderCommonOptions) => Provider<"bungie"> - -/** - * EVEOnline - */ -type EVEOnline = (options: ProviderCommonOptions) => Provider<"eveonline"> - -/** - * FACEIT - */ -type FACEIT = (options: ProviderCommonOptions) => Provider<"faceit"> - -/** - * Foursquare - */ -type Foursquare = (options: ProviderCommonOptions) => Provider<"foursquare"> - -/** - * FusionAuth +export type OAuthProviderType = + | "Apple" + | "Attlassian" + | "Auth0" + | "AzureADB2C" + | "Basecamp" + | "BattleNet" + | "Box" + | "Bungie" + | "Cognito" + | "Discord" + | "EVEOnline" + | "Facebook" + | "FACEIT" + | "Foursquare" + | "FusionAuth" + | "GitHub" + | "GitLab" + | "Google" + | "IdentityServer4" + | "Instagram" + | "Kakao" + | "LINE" + | "LinkedIn" + | "MailRu" + | "Medium" + | "Netlify" + | "Okta" + | "Osso" + | "Reddit" + | "Salesforce" + | "Slack" + | "Spotify" + | "Strava" + | "Twitch" + | "Twitter" + | "VK" + | "Yandex" + | "Zoho" + +export type OAuthProvider = (options: Partial) => OAuthConfig + +/** + * Credentials Provider */ -type FusionAuth = (options: ProviderFusionAuthOptions) => Provider<"fusionauth"> -interface ProviderFusionAuthOptions extends ProviderCommonOptions { - tenantId?: string - domain?: string +interface CredentialInput { + label?: string + type?: string + value?: string + placeholder?: string } -/** - * Instagram - */ -type Instagram = (options: ProviderCommonOptions) => Provider<"instagram"> - -/** - * Kakao - */ -type Kakao = (options: ProviderCommonOptions) => Provider<"kakao"> - -/** - * LINE - */ -type LINE = (options: ProviderCommonOptions) => Provider<"line"> - -/** - * MailRu - */ -type MailRu = (options: ProviderCommonOptions) => Provider<"mailru"> - -/** - * Medium - */ -type Medium = (options: ProviderCommonOptions) => Provider<"medium"> +interface CredentialsConfig = {}> + extends CommonProviderOptions { + type: "credentials" + credentials: C + authorize(credentials: Record): Awaitable +} + +export type CredentialsProvider = ( + options: Partial +) => CredentialsConfig + +export type CredentialsProviderType = "Credentials" + +/** Email Provider */ + +export interface EmailConfig extends CommonProviderOptions { + type: "email" + server: + | string + | { + host: string + port: number + auth: { + user: string + pass: string + } + } + from?: string + maxAge?: number + sendVerificationRequest(params: { + identifier: string + url: string + baseUrl: string + token: string + provider: EmailConfig + }): Awaitable +} +export type EmailProvider = (options: Partial) => EmailConfig -/** - * Netlify - */ -type Netlify = (options: ProviderCommonOptions) => Provider<"netlify"> +// TODO: Rename to Token provider +// when started working on https://github.com/nextauthjs/next-auth/discussions/1465 +export type EmailProviderType = "Email" -/** - * Osso - */ -type Osso = (options: ProviderCommonOptions) => Provider<"osso"> +export type Provider = OAuthConfig | EmailConfig | CredentialsConfig -/** - * Salesforce - */ -type Salesforce = (options: ProviderCommonOptions) => Provider<"salesforce"> +export type BuiltInProviders = Record & + Record & + Record -/** - * Strava - */ -type Strava = (options: ProviderCommonOptions) => Provider<"strava"> +export type AppProviders = Array< + Provider | ReturnType +> -/** - * VK - */ -type VK = (options: ProviderCommonOptions) => Provider<"vk"> +declare const Providers: BuiltInProviders -/** - * Zoho - */ -type Zoho = (options: ProviderCommonOptions) => Provider<"zoho"> +export default Providers diff --git a/types/tests/adapters.test.ts b/types/tests/adapters.test.ts index d2e02985b8..61530ce19b 100644 --- a/types/tests/adapters.test.ts +++ b/types/tests/adapters.test.ts @@ -1,4 +1,4 @@ -import Adapters, { TypeORMAdapter } from "next-auth/adapters" +import Adapters from "next-auth/adapters" // ExpectType TypeORMAdapter["Adapter"] Adapters.Default({ diff --git a/types/tests/client.test.ts b/types/tests/client.test.ts index 1dcde4ee45..ed6921c71c 100644 --- a/types/tests/client.test.ts +++ b/types/tests/client.test.ts @@ -11,7 +11,7 @@ const clientSession = { expires: "1234", } -// $ExpectType [Session | null | undefined, boolean] +// $ExpectType [Session | null, boolean] client.useSession() // $ExpectType Promise @@ -20,10 +20,10 @@ client.getSession({ req: nextReq }) // $ExpectType Promise client.session({ req: nextReq }) -// $ExpectType Promise | null> +// $ExpectType Promise | null> client.getProviders() -// $ExpectType Promise | null> +// $ExpectType Promise | null> client.providers() // $ExpectType Promise @@ -32,26 +32,36 @@ client.getCsrfToken({ req: nextReq }) // $ExpectType Promise client.csrfToken({ req: nextReq }) -// $ExpectType Promise -client.signin("github", { data: "foo", redirect: false }, { login: "username" }) +// $ExpectType Promise +client.csrfToken({ ctx: { req: nextReq } }) + +// $ExpectType Promise +client.signin("github", { callbackUrl: "foo" }, { login: "username" }) + +// $ExpectType Promise +client.signin("credentials", { callbackUrl: "foo", redirect: true }) -// $ExpectType Promise -client.signin("credentials", { data: "foo", redirect: false }) +// $ExpectType Promise +client.signin("credentials", { redirect: false }) -// $ExpectType Promise -client.signin("email", { data: "foo", redirect: false }) +// $ExpectType Promise +client.signin("email", { callbackUrl: "foo", redirect: false }) -// $ExpectType Promise -client.signin("email", { data: "foo", redirect: true }) +// $ExpectType Promise +client.signin("email", { callbackUrl: "foo", redirect: true }) -// $ExpectType Promise +// $ExpectType Promise client.signout() -// $ExpectType Promise +// $ExpectType Promise client.signout({ callbackUrl: "https://foo.com/callback", redirect: true }) +// $ExpectType Promise +client.signOut({ callbackUrl: "https://foo.com/callback", redirect: false }) + // $ExpectType ReactElement | null client.Provider({ + children: null, session: clientSession, options: { baseUrl: "https://foo.com", @@ -62,18 +72,22 @@ client.Provider({ // $ExpectType ReactElement | null client.Provider({ + children: null, session: clientSession, }) // $ExpectType ReactElement | null client.Provider({ - session: undefined, + children: null, options: {}, }) // $ExpectType ReactElement | null client.Provider({ - session: null, + children: null, + session: { + expires: "", + }, options: { baseUrl: "https://foo.com", basePath: "/", diff --git a/types/tests/jwt.test.ts b/types/tests/jwt.test.ts index 98ae162dae..4945ae4114 100644 --- a/types/tests/jwt.test.ts +++ b/types/tests/jwt.test.ts @@ -7,7 +7,7 @@ JWTType.encode({ secret: "secret", }) -// $ExpectType Promise> +// $ExpectType Promise JWTType.decode({ token: "token", secret: "secret", @@ -19,7 +19,7 @@ JWTType.getToken({ raw: true, }) -// $ExpectType Promise> +// $ExpectType Promise JWTType.getToken({ req: nextReq, secret: "secret", diff --git a/types/tests/providers.test.ts b/types/tests/providers.test.ts index c5f77b4cbb..fe8e901300 100644 --- a/types/tests/providers.test.ts +++ b/types/tests/providers.test.ts @@ -1,12 +1,12 @@ import Providers from "next-auth/providers" -// $ExpectType NonNullParams & { id: "email"; type: "email"; } +// $ExpectType EmailConfig Providers.Email({ server: "path/to/server", from: "path/from", }) -// $ExpectType NonNullParams & { id: "email"; type: "email"; } +// $ExpectType EmailConfig Providers.Email({ server: { host: "host", @@ -19,7 +19,7 @@ Providers.Email({ from: "path/from", }) -// $ExpectType NonNullParams & { id: "credentials"; type: "credentials"; } +// $ExpectType CredentialsConfig<{}> Providers.Credentials({ id: "login", name: "account", @@ -41,7 +41,7 @@ Providers.Credentials({ }, }) -// $ExpectType Provider<"apple", "oauth"> & { protection: "none"; } +// $ExpectType OAuthConfig Providers.Apple({ clientId: "foo123", clientSecret: { @@ -52,64 +52,64 @@ Providers.Apple({ }, }) -// $ExpectType Provider<"twitter", "oauth"> +// $ExpectType OAuthConfig Providers.Twitter({ clientId: "foo123", clientSecret: "bar123", }) -// $ExpectType Provider<"facebook", "oauth"> +// $ExpectType OAuthConfig Providers.Facebook({ clientId: "foo123", clientSecret: "bar123", }) -// $ExpectType Provider<"github", "oauth"> +// $ExpectType OAuthConfig Providers.GitHub({ clientId: "foo123", clientSecret: "bar123", }) -// $ExpectType Provider<"github", "oauth"> +// $ExpectType OAuthConfig Providers.GitHub({ clientId: "foo123", clientSecret: "bar123", scope: "change:thing read:that", }) -// $ExpectType Provider<"gitlab", "oauth"> +// $ExpectType OAuthConfig Providers.GitLab({ clientId: "foo123", clientSecret: "bar123", }) -// $ExpectType Provider<"slack", "oauth"> +// $ExpectType OAuthConfig Providers.Slack({ clientId: "foo123", clientSecret: "bar123", }) -// $ExpectType Provider<"google", "oauth"> +// $ExpectType OAuthConfig Providers.Google({ clientId: "foo123", clientSecret: "bar123", }) -// $ExpectType Provider<"google", "oauth"> +// $ExpectType OAuthConfig Providers.Google({ clientId: "foo123", clientSecret: "bar123", authorizationUrl: "https://foo.google.com", }) -// $ExpectType Provider<"auth0", "oauth"> & { domain: string; } +// $ExpectType OAuthConfig Providers.Auth0({ clientId: "foo123", clientSecret: "bar123", domain: "https://foo.auth0.com", }) -// $ExpectType Provider<"auth0", "oauth"> & { domain: string; } +// $ExpectType OAuthConfig Providers.Auth0({ clientId: "foo123", clientSecret: "bar123", @@ -122,7 +122,7 @@ Providers.Auth0({ }), }) -// $ExpectType Provider & { domain: string; } +// $ExpectType OAuthConfig Providers.IdentityServer4({ id: "identity-server4", name: "IdentityServer4", @@ -132,85 +132,85 @@ Providers.IdentityServer4({ clientSecret: "bar123", }) -// $ExpectType Provider<"discord", "oauth"> +// $ExpectType OAuthConfig Providers.Discord({ clientId: "foo123", clientSecret: "bar123", scope: "identify", }) -// $ExpectType Provider<"twitch", "oauth"> +// $ExpectType OAuthConfig Providers.Twitch({ clientId: "foo123", clientSecret: "bar123", }) -// $ExpectType Provider<"okta", "oauth"> & { domain: string; } +// $ExpectType OAuthConfig Providers.Okta({ clientId: "foo123", clientSecret: "bar123", domain: "https://foo.auth0.com", }) -// $ExpectType Provider<"battlenet", "oauth"> & { region: string; } +// $ExpectType OAuthConfig Providers.BattleNet({ clientId: "foo123", clientSecret: "bar123", region: "europe", }) -// $ExpectType Provider<"box", "oauth"> +// $ExpectType OAuthConfig Providers.Box({ clientId: "foo123", clientSecret: "bar123", }) -// $ExpectType Provider<"cognito", "oauth"> & { domain: string; } +// $ExpectType OAuthConfig Providers.Cognito({ clientId: "foo123", clientSecret: "bar123", domain: "https://foo.auth0.com", }) -// $ExpectType Provider<"yandex", "oauth"> +// $ExpectType OAuthConfig Providers.Yandex({ clientId: "foo123", clientSecret: "bar123", }) -// $ExpectType Provider<"linkedin", "oauth"> +// $ExpectType OAuthConfig Providers.LinkedIn({ clientId: "foo123", clientSecret: "bar123", scope: "r_emailaddress r_liteprofile", }) -// $ExpectType Provider<"spotify", "oauth"> +// $ExpectType OAuthConfig Providers.Spotify({ clientId: "foo123", clientSecret: "bar123", }) -// $ExpectType Provider<"spotify", "oauth"> +// $ExpectType OAuthConfig Providers.Spotify({ clientId: "foo123", clientSecret: "bar123", scope: "user-read-email", }) -// $ExpectType Provider<"basecamp", "oauth"> +// $ExpectType OAuthConfig Providers.Basecamp({ clientId: "foo123", clientSecret: "bar123", }) -// $ExpectType Provider<"reddit", "oauth"> +// $ExpectType OAuthConfig Providers.Reddit({ clientId: "foo123", clientSecret: "bar123", }) -// $ExpectType Provider<"azure-ad-b2c", "oauth"> +// $ExpectType OAuthConfig Providers.AzureADB2C({ clientId: "foo123", clientSecret: "bar123", @@ -219,7 +219,7 @@ Providers.AzureADB2C({ idToken: true, }) -// $ExpectType Provider<"fusionauth", "oauth"> +// $ExpectType OAuthConfig Providers.FusionAuth({ name: "FusionAuth", domain: "domain", @@ -228,31 +228,31 @@ Providers.FusionAuth({ tenantId: "tenantId", }) -// $ExpectType Provider<"faceit", "oauth"> +// $ExpectType OAuthConfig Providers.FACEIT({ clientId: "foo123", clientSecret: "bar123", }) -// $ExpectType Provider<"instagram", "oauth"> +// $ExpectType OAuthConfig Providers.Instagram({ clientId: "foo123", clientSecret: "bar123", }) -// $ExpectType Provider<"kakao", "oauth"> +// $ExpectType OAuthConfig Providers.Kakao({ clientId: "foo123", clientSecret: "bar123", }) -// $ExpectType Provider<"osso", "oauth"> +// $ExpectType OAuthConfig Providers.Osso({ clientId: "foo123", clientSecret: "bar123", }) -// $ExpectType Provider<"zoho", "oauth"> +// $ExpectType OAuthConfig Providers.Zoho({ clientId: "foo123", clientSecret: "bar123", diff --git a/types/tests/server.test.ts b/types/tests/server.test.ts index fdab54e04a..5db2245d68 100644 --- a/types/tests/server.test.ts +++ b/types/tests/server.test.ts @@ -1,5 +1,5 @@ -import Providers, { AppProvider, Provider } from "next-auth/providers" -import Adapters, { +import Providers, { OAuthConfig } from "next-auth/providers" +import { Adapter, EmailAppProvider, Profile, @@ -10,7 +10,9 @@ import NextAuth, * as NextAuthTypes from "next-auth" import { IncomingMessage, ServerResponse } from "http" import * as JWTType from "next-auth/jwt" import { Socket } from "net" -import { NextApiRequest, NextApiResponse } from "next" +import { NextApiRequest, NextApiResponse } from "internals/utils" +import { AppOptions } from "internals" +import { AppProvider } from "internals/providers" const req: NextApiRequest = Object.assign(new IncomingMessage(new Socket()), { query: {}, @@ -37,7 +39,6 @@ const pageOptions = { } const simpleConfig = { - site: "https://foo.com", providers: [ Providers.GitHub({ clientId: "123", @@ -73,7 +74,7 @@ const adapter: Adapter< Session, VerificationRequest > = { - async getAdapter(appOptions: NextAuthTypes.AppOptions) { + async getAdapter(appOptions: AppOptions) { return { createUser: async (profile: Profile) => exampleUser, getUser: async (id: string) => exampleUser, @@ -103,7 +104,7 @@ const adapter: Adapter< token: string, secret: string, provider: EmailAppProvider, - options: NextAuthTypes.AppOptions + options: AppOptions ) => exampleVerificatoinRequest, getVerificationRequest: async ( email: string, @@ -209,7 +210,12 @@ const allConfig = { }, } -const customProvider: Provider<"google"> = { +const customProvider: OAuthConfig<{ + id: string + name: string + email: string + picture: string +}> = { id: "google", name: "Google", type: "oauth", @@ -235,7 +241,6 @@ const customProvider: Provider<"google"> = { } const customProviderConfig = { - site: "https://foo.com", providers: [customProvider], } diff --git a/types/tests/test-helpers.ts b/types/tests/test-helpers.ts index 683081cd17..3465aac99e 100644 --- a/types/tests/test-helpers.ts +++ b/types/tests/test-helpers.ts @@ -1,6 +1,6 @@ -import { IncomingMessage, ServerResponse } from "http" +import { IncomingMessage } from "http" import { Socket } from "net" -import { NextApiRequest } from "next" +import { NextApiRequest } from "internals/utils" export const nextReq: NextApiRequest = Object.assign( new IncomingMessage(new Socket()), diff --git a/types/tsconfig.json b/types/tsconfig.json index 70ee7e0682..ee05ce08fb 100644 --- a/types/tsconfig.json +++ b/types/tsconfig.json @@ -17,7 +17,7 @@ "next-auth/adapters": ["./adapters"], "next-auth/client": ["./client"], "next-auth/jwt": ["./jwt"], - "next": ["./_next"] + "internals": ["./internals"] } } } diff --git a/types/tslint.json b/types/tslint.json index 295142061d..456e0907ff 100644 --- a/types/tslint.json +++ b/types/tslint.json @@ -1,6 +1,7 @@ { "extends": "dtslint/dtslint.json", "rules": { - "semicolon": false + "semicolon": false, + "no-redundant-jsdoc": false } } diff --git a/www/docs/configuration/pages.md b/www/docs/configuration/pages.md index ad5793e08e..fca0fac4e3 100644 --- a/www/docs/configuration/pages.md +++ b/www/docs/configuration/pages.md @@ -21,6 +21,38 @@ To add a custom login page, you can use the `pages` option: ... ``` +## Error codes +We purposefully restrict the returned error codes for increased security. + +### Error page +The following errors are passed as error query parameters to the default or overriden error page: + +- **Configuration**: There is a problem with the server configuration. Check if your [options](/configuration/options#options) is correct. +- **AccessDenied**: Usually occurs, when you restriected access through the [`signIn` callback](/configuration/callbacks#sign-in-callback), or [`redirect` callback](/configuration/callbacks#redirect-callback) +- **Verification**: Related to the Email provider. The token has expired or has already been used +- **Default**: Catch all, will apply, if none of the above matched + +Example: `/auth/error?error=Configuration` + +### Sign-in page +The following errors are passed as error query parameters to the default or overriden sign-in page: + +- **OAuthSignin**: Error in constructing an authorization URL ([1](https://github.com/nextauthjs/next-auth/blob/457952bb5abf08b09861b0e5da403080cd5525be/src/server/lib/signin/oauth.js), [2](https://github.com/nextauthjs/next-auth/blob/main/src/server/lib/oauth/pkce-handler.js), [3](https://github.com/nextauthjs/next-auth/blob/main/src/server/lib/oauth/state-handler.js)), +- **OAuthCallback**: Error in handling the response ([1](https://github.com/nextauthjs/next-auth/blob/main/src/server/lib/oauth/callback.js), [2](https://github.com/nextauthjs/next-auth/blob/main/src/server/lib/oauth/pkce-handler.js), [3](https://github.com/nextauthjs/next-auth/blob/main/src/server/lib/oauth/state-handler.js)) from an OAuth provider. +- **OAuthCreateAccount**: Could not create OAuth provider user in the database. +- **EmailCreateAccount**: Could not create email provider user in the database. +- **Callback**: Error in the [OAuth callback handler route](https://github.com/nextauthjs/next-auth/blob/main/src/server/routes/callback.js) +- **OAuthAccountNotLinked**: If the email on the account is already linked, but not with this OAuth account +- **EmailSignin**: Sending the e-mail with the verification token failed +- **CredentialsSignin**: The `authorize` callback returned `null` in the [Credentials provider](/providers/credentials). We don't recommend providing information about which part of the credentials were wrong, as it might be abused by malicious hackers. +- **Default**: Catch all, will apply, if none of the above matched + +Example: `/auth/error?error=Default` + +## Theming + +By default, the built-in pages will follow the system theme, utilizing the [`prefer-color-scheme`](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme) Media Query. You can override this to always use a dark or light theme, through the [`theme` option](/configuration/options#theme). + ## Examples ### OAuth Sign in