Compare commits

..

10 Commits

Author SHA1 Message Date
Balázs Orbán
a6f5f4c184 fix: use upgraded require optional (#1743)
* chore(deps): switch back to (updated) require_optional

* fix: use @balazsorban/require-optional
2021-04-16 16:05:44 +02:00
Balázs Orbán
9fa93e3b5e fix(build): use optional-require dependency (#1736)
* chore(deps): add optional-require

* refactor: use optional-require
2021-04-16 00:23:29 +02:00
Balázs Orbán
cb4342fdda feat(build): modernize how we bundle next-auth (#1682)
* feat(build): optionally include TypeORM

If the user doesn't use databases,
it shouldn't be necessary to iclude it in the bundle.
This can more than half the package size!

* feat(build): clean up in dependencies

Remove unused dependencies, move optional ones to be optional

* feat(build): add exports field

* fix: use peerDependenciesMeta instead of non-standard peerOptionalDependecns field

* fix: ts-standard string quotes

* fix: ts-standard string quotes

* refactor: use asnyc/await for sendVerificationRequest

* chore(deps): upgrade mongodb, remove require_optional

Co-authored-by: ndom91 <yo@ndo.dev>

BREAKING CHANGE:
`typeorm`, and `nodemailer` are no longer dependencies added by default.
If you need any of them, you will have to install them yourself in your project directory.
TypeOrm is the default adapter, so if you only provide an `adapter` configuration or a `database`, you will need `typeorm`. You could also check out `@next-auth/typeorm-adapter`. In case you are using the Email provider, you will have to install `nodemailer` (or you can use the choice of your library in the `sendVerificationRequest` callback to send out the e-mail.)
2021-04-15 23:40:33 +02:00
Balázs Orbán
5f717b3914 chore: merge main into next 2021-04-12 00:46:27 +02:00
Balázs Orbán
d09a45ec7c chore: merge main into next 2021-03-26 16:23:35 +01:00
Balázs Orbán
930f58eba3 chore: merge main into next 2021-03-08 01:05:54 +01:00
Balázs Orbán
c20b7f2930 feat: use IE11 as client code bundle target (#1402) 2021-03-03 20:25:42 +01:00
Balázs Orbán
e418cddd96 chore: merge main into next 2021-03-03 20:25:42 +01:00
Balázs Orbán
111e7aabdf feat(provider): remove state property
BREAKING CHANGE: adding `state: true` is already redundant
as `protection: "state` is the default value. `state: false`
can be substituted with `protection: "state"`
2021-02-15 21:47:47 +01:00
Balázs Orbán
a113ef6fab feat: encourage returning strings instead of throwing
BREAKING CHANGE: We have supported throwing strings
for redirections, while we were showing a waring.
From now on, it is not possible. The user MUST return a string,
rather than throw it.
2021-02-15 21:47:35 +01:00
110 changed files with 3253 additions and 4781 deletions

View File

@@ -6,12 +6,10 @@ on:
push:
branches:
- main
- beta
- next
pull_request:
branches:
- main
- beta
- next
jobs:

View File

@@ -13,7 +13,7 @@ name: "CodeQL"
on:
push:
branches: [ main, beta, next ]
branches: [ main, next ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ main ]

View File

@@ -2,10 +2,9 @@ name: Integration Test
on:
push:
branches:
- main
- beta
- next
branches:
- main
- next
pull_request:
jobs:
@@ -18,7 +17,7 @@ jobs:
if: github.event.pull_request.head.repo.full_name == github.repository
# We use self-hosted runners as cloud based runnners (e.g. AWS, GPC)
# fail due to IP Address checks done by providers, which enforce
# fail due to IP Address checks done by providers, which enforce
# CAPTCHA checks on login request from cloud compute IP addresses to
# prevent abuse.
runs-on: self-hosted
@@ -46,7 +45,7 @@ jobs:
- run: npm test
# TODO Tests should exit out if env vars not set (currently hangs)
env:
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
NEXTAUTH_TWITTER_ID: ${{secrets.NEXTAUTH_TWITTER_ID}}
NEXTAUTH_TWITTER_SECRET: ${{secrets.NEXTAUTH_TWITTER_SECRET}}
NEXTAUTH_TWITTER_USERNAME: ${{secrets.NEXTAUTH_TWITTER_USERNAME}}

View File

@@ -3,7 +3,6 @@ on:
push:
branches:
- 'main'
- 'beta'
- 'next'
- '3.x'
pull_request:

View File

@@ -1,25 +0,0 @@
name: Types
on:
push:
branches:
- main
- beta
- next
pull_request:
branches:
- main
- beta
- next
jobs:
lint-and-build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Use Node.js
uses: actions/setup-node@v1
- name: Install dependencies
uses: bahmutov/npm-install@v1
- name: Check types
run: npm run test:types

1
.gitignore vendored
View File

@@ -27,7 +27,6 @@ node_modules
.cache-loader
.next
www/providers.json
src/providers/index.js
# VS
/.vs/slnx.sqlite-journal

View File

@@ -16,7 +16,7 @@ Anyone can be a contributor. Either you found a typo, or you have an awesome fea
* The latest changes are always in `main`, so please make your Pull Request against that branch.
* Pull Requests should be raised for any change
* Pull Requests need approval of a [core contributor](https://next-auth.js.org/contributors#core-team) before merging
* We use ESLint/Prettier for linting/formatting, so please run `npm run lint:fix` before committing to make resolving conflicts easier (VSCode users, check out [this ESLint extension](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) and [this Prettier extension](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) to fix lint and formatting issues in development)
* Run `npm run lint:fix` before committing to make resolving conflicts easier (VSCode users, check out [this extension](https://marketplace.visualstudio.com/items?itemName=chenxsan.vscode-standardjs) to fix lint issues in development)
* We encourage you to test your changes, and if you have the opportunity, please make those tests part of the Pull Request
* If you add new functionality, please provide the corresponding documentation as well and make it part of the Pull Request
@@ -59,16 +59,6 @@ When running `npm run dev`, you start a Next.js dev server on `http://localhost:
>NOTE: When working on CSS, you will need to manually refresh the page after changes. (Improving this through a PR is very welcome!)
#### Providers
If you think your custom provider might be useful to others, we encourage you to open a PR and add it to the built-in list so others can discover it much more easily! You only need to add two changes:
1. Add your config: [`src/providers/{provider}.js`](https://github.com/nextauthjs/next-auth/tree/main/src/providers) (Make sure you use a named default export, like `export default function YourProvider`!)
2. Add provider documentation: [`www/docs/providers/{provider}.md`](https://github.com/nextauthjs/next-auth/tree/main/www/docs/providers)
That's it! 🎉 Others will be able to discover this provider much more easily now!
You can look at the existing built-in providers for inspiration.
#### Databases
Included is a Docker Compose file that starts up MySQL, Postgres, and MongoDB databases on localhost.

View File

@@ -84,9 +84,13 @@ Advanced options allow you to define your own routines to handle controlling wha
### TypeScript
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.
You can install the appropriate types via the following command:
The package at `@types/next-auth` is now deprecated.
```
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.
## Example

1
adapters.js Normal file
View File

@@ -0,0 +1 @@
module.exports = require('./dist/adapters').default

1
client.js Normal file
View File

@@ -0,0 +1 @@
module.exports = require('./dist/client').default

View File

@@ -1,12 +1,19 @@
{
"presets": [
["@babel/preset-env", { "targets": { "esmodules": true } }]
["@babel/preset-env", { "targets": { "node": "10" } }]
],
"comments": false,
"overrides": [
{
"test": ["../src/client/**"],
"comments": true,
"presets": [
["@babel/preset-env", { "targets": { "ie": "11" } }]
]
},
{
"test": ["../src/server/pages/**"],
"presets": ["preact"]
}
]
}
}

View File

@@ -1,86 +0,0 @@
const fs = require("fs-extra")
const path = require("path")
const MODULE_ENTRIES = {
SERVER: "index",
CLIENT: "client",
PROVIDERS: "providers",
ADAPTERS: "adapters",
JWT: "jwt",
}
// Building submodule entries
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`)
})
})
// Building types
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`)
}
)
})
// Building providers
const providersDir = path.join(process.cwd(), "/src/providers")
const files = fs.readdirSync(providersDir, "utf8")
let importLines = ""
let exportLines = `export default {\n`
files.forEach((file) => {
const provider = fs.readFileSync(path.join(providersDir, file), "utf8")
try {
// NOTE: If this fails, the default export probably wasn't a named function.
// Always use a named function as default export.
// Eg.: export default function YourProvider ...
const { functionName } = provider.match(
/export default function (?<functionName>.+)\s?\(/
).groups
importLines += `import ${functionName} from "./${file}"\n`
exportLines += ` ${functionName},\n`
} catch (error) {
console.error(
[
`\nThe provider file '${file}' should have a single named default export`,
"Example: 'export default function YourProvider'\n\n",
].join("\n")
)
process.exit(1)
}
})
exportLines += `}\n`
fs.writeFile(
path.join(process.cwd(), "src/providers/index.js"),
[importLines, exportLines].join("\n")
)

1
index.js Normal file
View File

@@ -0,0 +1 @@
module.exports = require('./dist/server')

1
jwt.js Normal file
View File

@@ -0,0 +1 @@
module.exports = require('./dist/lib/jwt').default

3691
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,10 +5,9 @@
"homepage": "https://next-auth.js.org",
"repository": "https://github.com/nextauthjs/next-auth.git",
"author": "Iain Collins <me@iaincollins.com>",
"main": "index.js",
"scripts": {
"build": "npm run build:js && npm run build:css",
"build:js": "node ./config/build.js && babel --config-file ./config/babel.config.json src --out-dir dist",
"build:js": "babel --config-file ./config/babel.config.json src --out-dir dist",
"build:css": "postcss --config config/postcss.config.js src/**/*.css --base src --dir dist && node config/wrap-css.js",
"dev:with-css": "next | npm run watch:css",
"dev": "next",
@@ -18,61 +17,82 @@
"test:app:start": "docker-compose -f test/docker/app.yml up -d",
"test:app:rebuild": "npm run build && docker-compose -f test/docker/app.yml up -d --build",
"test:app:stop": "docker-compose -f test/docker/app.yml down",
"test": "npm run test:app:rebuild && npm run test:integration && npm run test:app:stop && npm run test:types",
"test": "npm run test:app:rebuild && npm run test:integration && npm run test:app:stop",
"test:db": "npm run test:db:mysql && npm run test:db:postgres && npm run test:db:mongodb && npm run test:db:mssql",
"test:db:mysql": "node test/mysql.js",
"test:db:postgres": "node test/postgres.js",
"test:db:mongodb": "node test/mongodb.js",
"test:db:mssql": "node test/mssql.js",
"test:integration": "mocha test/integration",
"test:types": "dtslint types",
"db:start": "docker-compose -f test/docker/databases.yml up -d",
"db:stop": "docker-compose -f test/docker/databases.yml down",
"prepublishOnly": "npm run build",
"publish:beta": "npm publish --tag beta",
"publish:canary": "npm publish --tag canary",
"lint": "eslint .",
"lint:fix": "eslint . --fix"
"lint": "ts-standard",
"lint:fix": "ts-standard --fix"
},
"main": "index.js",
"exports": {
".": "./dist/server/index.js",
"./jwt": "./dist/lib/jwt.js",
"./adapters": "./dist/adapters/index.js",
"./client": "./dist/client/index.js",
"./providers": "./dist/providers/index.js",
"./providers/*": "./dist/providers/*.js"
},
"files": [
"dist",
"index.js",
"index.d.ts",
"providers.js",
"providers.d.ts",
"adapters.js",
"adapters.d.ts",
"client.js",
"client.d.ts",
"jwt.js",
"jwt.d.ts",
"internals"
"jwt.js"
],
"license": "ISC",
"dependencies": {
"crypto-js": "^4.0.0",
"@balazsorban/require-optional": "^1.0.0",
"futoin-hkdf": "^1.3.2",
"jose": "^1.27.2",
"jsonwebtoken": "^8.5.1",
"nodemailer": "^6.4.16",
"oauth": "^0.9.15",
"pkce-challenge": "^2.1.0",
"preact": "^10.4.1",
"preact-render-to-string": "^5.1.14",
"querystring": "^0.2.0",
"require_optional": "^1.0.1",
"typeorm": "^0.2.30"
"preact-render-to-string": "^5.1.14"
},
"peerDependencies": {
"react": "^16.13.1 || ^17",
"react-dom": "16.13.1 || ^17"
},
"peerOptionalDependencies": {
"mongodb": "^3.5.9",
"react-dom": "^16.13.1 || ^17",
"mongodb": "^3.6.6",
"mysql": "^2.18.1",
"mssql": "^6.2.1",
"pg": "^8.2.1",
"@prisma/client": "^2.16.1"
"@prisma/client": "^2.16.1",
"nodemailer": "^6.4.16",
"typeorm": "^0.2.30"
},
"peerDependenciesMeta": {
"mongodb": {
"optional": true
},
"mysql": {
"optional": true
},
"mssql": {
"optional": true
},
"pg": {
"optional": true
},
"@prisma/client": {
"optional": true
},
"nodemailer": {
"optional": true
},
"typeorm": {
"optional": true
}
},
"devDependencies": {
"@babel/cli": "^7.8.4",
@@ -84,64 +104,40 @@
"@semantic-release/npm": "7.0.8",
"@semantic-release/release-notes-generator": "^9.0.1",
"@types/react": "^17.0.0",
"@typescript-eslint/eslint-plugin": "^4.22.0",
"@typescript-eslint/parser": "^4.22.0",
"autoprefixer": "^9.7.6",
"babel-preset-preact": "^2.0.0",
"conventional-changelog-conventionalcommits": "4.4.0",
"cssnano": "^4.1.10",
"dotenv": "^8.2.0",
"dtslint": "^4.0.8",
"eslint": "^7.19.0",
"eslint-config-prettier": "^8.2.0",
"eslint-config-standard-with-typescript": "^19.0.1",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.3.1",
"eslint-plugin-standard": "^5.0.0",
"mocha": "^8.1.3",
"mongodb": "^3.5.9",
"mongodb": "^3.6.6",
"mssql": "^6.2.1",
"mysql": "^2.18.1",
"next": "^10.0.5",
"pg": "^8.2.1",
"postcss-cli": "^7.1.1",
"postcss-nested": "^4.2.1",
"prettier": "^2.2.1",
"prisma": "^2.16.1",
"puppeteer": "^5.2.1",
"puppeteer-extra": "^3.1.15",
"puppeteer-extra-plugin-stealth": "^2.6.1",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"ts-standard": "^10.0.0",
"typescript": "^4.1.3"
},
"prettier": {
"semi": false
},
"eslintConfig": {
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./tsconfig.json"
},
"extends": [
"standard-with-typescript",
"prettier"
"ts-standard": {
"project": "./tsconfig.json",
"ignore": [
"test/",
"next-env.d.ts"
],
"ignorePatterns": [
"node_modules",
"test",
"next-env.d.ts",
"types",
"www",
".next",
"dist"
],
"globals": {
"localStorage": "readonly",
"location": "readonly",
"fetch": "readonly"
}
"globals": [
"localStorage",
"location",
"fetch"
]
},
"funding": [
{

View File

@@ -1,4 +1,3 @@
// eslint-disable-next-line no-use-before-define
import * as React from 'react'
import { signIn, signOut, useSession } from 'next-auth/client'
import Layout from 'components/layout'

View File

@@ -1,4 +1,3 @@
// eslint-disable-next-line no-use-before-define
import * as React from 'react'
import { signIn, signOut, useSession } from 'next-auth/client'
import Layout from 'components/layout'

1
providers.js Normal file
View File

@@ -0,0 +1 @@
module.exports = require('./dist/providers').default

View File

@@ -2,7 +2,6 @@ module.exports = {
branches: [
'+([0-9])?(.{+([0-9]),x}).x',
'main',
{ name: 'beta', prerelease: true },
{ name: 'next', prerelease: true }
]
}

View File

@@ -1,6 +1,5 @@
import { createConnection, getConnection } from 'typeorm'
import { createHash } from 'crypto'
import require_optional from 'require_optional' // eslint-disable-line camelcase
import { CreateUserError } from '../../lib/errors'
import adapterConfig from './lib/config'
@@ -9,6 +8,8 @@ import Models from './models'
import { updateConnectionEntities } from './lib/utils'
import requireOptional from '@balazsorban/require-optional'
const Adapter = (typeOrmConfig, options = {}) => {
// Ensure typeOrmConfigObject is normalized to an object
const typeOrmConfigObject = (typeof typeOrmConfig === 'string')
@@ -94,12 +95,12 @@ const Adapter = (typeOrmConfig, options = {}) => {
let ObjectId
if (config.type === 'mongodb') {
idKey = '_id'
// Using a dynamic import causes problems for some compilers/bundlers
// that don't handle dynamic imports. To try and work around this we are
// using the same method mongodb uses to load Object ID type, which is to
// use the require_optional loader.
const mongodb = require_optional('mongodb')
ObjectId = mongodb.ObjectId
// We should/could use dynamic import here, but
// bundlers like webpack will try to import the module,
// even if this conditional branch is never entered.
// We work around this with requireOptional.
const mongodb = requireOptional('mongodb')
ObjectId = mongodb.ObjectID
}
// These values are stored as seconds, but to use them with dates in

103
src/client/index.d.ts vendored Normal file
View File

@@ -0,0 +1,103 @@
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<S extends Record<string, unknown> = DefaultSession> = (options: {
ctx?: GetServerSidePropsContext
req?: GetServerSidePropsContext['req']
event?: 'storage' | 'timer' | 'hidden' | string
triggerEvent?: boolean
}) => Promise<S>
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<typeof setTimeout>
/** 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<string | null>
export interface SessionOptions {
baseUrl?: string
basePath?: string
clientMaxAge?: number
keepAlive?: number
}
export type Provider<S extends Record<string, unknown> = 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<any[]>
// 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<AuthorizationParams = Record<string, string>> = (
provider?: string,
options?: SignInOptions,
authorizationParams?: AuthorizationParams
) => SignInResponse
// Sign out types
interface SignOutResponse<RedirectType extends boolean=true> {
/** Defaults to the current URL. */
callbackUrl?: string
redirect?: RedirectType
}
export type SignOut<RedirectType extends boolean = true> = (params: SignOutResponse<RedirectType>) => RedirectType extends true ? Promise<{url?: string} | undefined> : undefined

View File

@@ -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("types/internals/client").NextAuthConfig} */
/** @type {import(".").NextAuthConfig} */
const __NEXTAUTH = {
baseUrl: parseUrl(process.env.NEXTAUTH_URL || process.env.VERCEL_URL).baseUrl,
basePath: parseUrl(process.env.NEXTAUTH_URL).basePath,
@@ -60,9 +60,15 @@ 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
@@ -137,6 +143,14 @@ 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) {
@@ -145,14 +159,39 @@ 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,
@@ -216,6 +255,13 @@ 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,
@@ -252,6 +298,7 @@ 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
@@ -274,6 +321,14 @@ 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(
@@ -332,13 +387,13 @@ function BroadcastChannel (name = 'nextauth.message') {
return {
/**
* Get notified by other tabs/windows.
* @param {(message: import("types/internals/client").BroadcastMessage) => void} onReceive
* @param {(message: import(".").BroadcastMessage) => void} onReceive
*/
receive (onReceive) {
if (typeof window === 'undefined') return
window.addEventListener('storage', async (event) => {
if (event.key !== name) return
/** @type {import("types/internals/client").BroadcastMessage} */
/** @type {import(".").BroadcastMessage} */
const message = JSON.parse(event.newValue)
if (message?.event !== 'session' || !message?.data) return

View File

@@ -106,8 +106,7 @@ async function getToken (params) {
// or not set (e.g. development or test instance) case use unprefixed name
secureCookie = !(!process.env.NEXTAUTH_URL || process.env.NEXTAUTH_URL.startsWith('http://')),
cookieName = (secureCookie) ? '__Secure-next-auth.session-token' : 'next-auth.session-token',
raw = false,
decode: _decode = decode
raw = false
} = params
if (!req) throw new Error('Must pass `req` to JWT getToken()')
@@ -127,7 +126,7 @@ async function getToken (params) {
}
try {
return _decode({ token, ...params })
return decode({ token, ...params })
} catch {
return null
}

10
src/lib/logger.d.ts vendored Normal file
View File

@@ -0,0 +1,10 @@
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

View File

@@ -1,31 +1,34 @@
/** @type {import("types").LoggerInstance} */
/** @type {import("./logger").LoggerInstance} */
const _logger = {
error(code, ...message) {
error (code, ...message) {
console.error(
`[next-auth][error][${code.toLowerCase()}]`,
`\nhttps://next-auth.js.org/errors#${code.toLowerCase()}`,
...message
)
},
warn(code, ...message) {
warn (code, ...message) {
console.warn(
`[next-auth][warn][${code.toLowerCase()}]`,
`\nhttps://next-auth.js.org/warnings#${code.toLowerCase()}`,
...message
)
},
debug(code, ...message) {
debug (code, ...message) {
if (!process?.env?._NEXTAUTH_DEBUG) return
console.log(`[next-auth][debug][${code.toLowerCase()}]`, ...message)
},
console.log(
`[next-auth][debug][${code.toLowerCase()}]`,
...message
)
}
}
/**
* Override the built-in logger.
* Any `undefined` level will use the default logger.
* @param {Partial<import("types").LoggerInstance>} newLogger
* @param {Partial<import("./logger").LoggerInstance>} newLogger
*/
export function setLogger(newLogger = {}) {
export function setLogger (newLogger = {}) {
if (newLogger.error) _logger.error = newLogger.error
if (newLogger.warn) _logger.warn = newLogger.warn
if (newLogger.debug) _logger.debug = newLogger.debug
@@ -35,13 +38,13 @@ export default _logger
/**
* Serializes client-side log messages and sends them to the server
* @param {import("types").LoggerInstance} logger
* @param {import("./logger").LoggerInstance} logger
* @param {string} basePath
* @return {import("types").LoggerInstance}
* @return {import("./logger").LoggerInstance}
*/
export function proxyLogger(logger = _logger, basePath) {
export function proxyLogger (logger = _logger, basePath) {
try {
if (typeof window === "undefined") {
if (typeof window === 'undefined') {
return logger
}
@@ -54,23 +57,21 @@ export function proxyLogger(logger = _logger, basePath) {
const body = new URLSearchParams({
level,
code,
message: JSON.stringify(
message.map((m) => {
if (m instanceof Error) {
// Serializing errors: https://iaincollins.medium.com/error-handling-in-javascript-a6172ccdf9af
return { name: m.name, message: m.message, stack: m.stack }
}
return m
})
),
message: JSON.stringify(message.map(m => {
if (m instanceof Error) {
// Serializing errors: https://iaincollins.medium.com/error-handling-in-javascript-a6172ccdf9af
return { name: m.name, message: m.message, stack: m.stack }
}
return m
}))
})
if (navigator.sendBeacon) {
return navigator.sendBeacon(url, body)
}
return fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body,
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body
})
}
}

View File

@@ -1,34 +1,30 @@
export default function Apple(options) {
export default (options) => {
return {
id: "apple",
name: "Apple",
type: "oauth",
version: "2.0",
scope: "name email",
params: { grant_type: "authorization_code" },
accessTokenUrl: "https://appleid.apple.com/auth/token",
authorizationUrl:
"https://appleid.apple.com/auth/authorize?response_type=code&id_token&response_mode=form_post",
id: 'apple',
name: 'Apple',
type: 'oauth',
version: '2.0',
scope: 'name email',
params: { grant_type: 'authorization_code' },
accessTokenUrl: 'https://appleid.apple.com/auth/token',
authorizationUrl: 'https://appleid.apple.com/auth/authorize?response_type=code&id_token&response_mode=form_post',
profileUrl: null,
idToken: true,
profile(profile) {
profile: (profile) => {
// The name of the user will only return on first login
return {
id: profile.sub,
name:
profile.user != null
? profile.user.name.firstName + " " + profile.user.name.lastName
: null,
email: profile.email,
name: profile.user != null ? profile.user.name.firstName + ' ' + profile.user.name.lastName : null,
email: profile.email
}
},
clientId: null,
clientSecret: {
teamId: null,
privateKey: null,
keyId: null,
keyId: null
},
protection: "none", // REVIEW: Apple does not support state, as far as I know. Can we use "pkce" then?
...options,
protection: 'none', // REVIEW: Apple does not support state, as far as I know. Can we use "pkce" then?
...options
}
}

View File

@@ -1,24 +1,24 @@
export default function Atlassian(options) {
export default (options) => {
return {
id: "atlassian",
name: "Atlassian",
type: "oauth",
version: "2.0",
id: 'atlassian',
name: 'Atlassian',
type: 'oauth',
version: '2.0',
params: {
grant_type: "authorization_code",
grant_type: 'authorization_code'
},
accessTokenUrl: "https://auth.atlassian.com/oauth/token",
accessTokenUrl: 'https://auth.atlassian.com/oauth/token',
authorizationUrl:
"https://auth.atlassian.com/authorize?audience=api.atlassian.com&response_type=code&prompt=consent",
profileUrl: "https://api.atlassian.com/me",
profile(profile) {
'https://auth.atlassian.com/authorize?audience=api.atlassian.com&response_type=code&prompt=consent',
profileUrl: 'https://api.atlassian.com/me',
profile: (profile) => {
return {
id: profile.account_id,
name: profile.name,
email: profile.email,
image: profile.picture,
image: profile.picture
}
},
...options,
...options
}
}

View File

@@ -1,22 +1,22 @@
export default function Auth0(options) {
export default (options) => {
return {
id: "auth0",
name: "Auth0",
type: "oauth",
version: "2.0",
params: { grant_type: "authorization_code" },
scope: "openid email profile",
id: 'auth0',
name: 'Auth0',
type: 'oauth',
version: '2.0',
params: { grant_type: 'authorization_code' },
scope: 'openid email profile',
accessTokenUrl: `https://${options.domain}/oauth/token`,
authorizationUrl: `https://${options.domain}/authorize?response_type=code`,
profileUrl: `https://${options.domain}/userinfo`,
profile(profile) {
profile: (profile) => {
return {
id: profile.sub,
name: profile.nickname,
email: profile.email,
image: profile.picture,
image: profile.picture
}
},
...options,
...options
}
}

View File

@@ -1,24 +1,24 @@
export default function AzureADB2C(options) {
const tenant = options.tenantId ? options.tenantId : "common"
export default (options) => {
const tenant = options.tenantId ? options.tenantId : 'common'
return {
id: "azure-ad-b2c",
name: "Azure Active Directory B2C",
type: "oauth",
version: "2.0",
id: 'azure-ad-b2c',
name: 'Azure Active Directory B2C',
type: 'oauth',
version: '2.0',
params: {
grant_type: "authorization_code",
grant_type: 'authorization_code'
},
accessTokenUrl: `https://login.microsoftonline.com/${tenant}/oauth2/v2.0/token`,
authorizationUrl: `https://login.microsoftonline.com/${tenant}/oauth2/v2.0/authorize?response_type=code&response_mode=query`,
profileUrl: "https://graph.microsoft.com/v1.0/me/",
profile(profile) {
profileUrl: 'https://graph.microsoft.com/v1.0/me/',
profile: (profile) => {
return {
id: profile.id,
name: profile.displayName,
email: profile.userPrincipalName,
email: profile.userPrincipalName
}
},
...options,
...options
}
}

View File

@@ -1,22 +1,20 @@
export default function Basecamp(options) {
export default (options) => {
return {
id: "basecamp",
name: "Basecamp",
type: "oauth",
version: "2.0",
accessTokenUrl:
"https://launchpad.37signals.com/authorization/token?type=web_server",
authorizationUrl:
"https://launchpad.37signals.com/authorization/new?type=web_server",
profileUrl: "https://launchpad.37signals.com/authorization.json",
profile(profile) {
id: 'basecamp',
name: 'Basecamp',
type: 'oauth',
version: '2.0',
accessTokenUrl: 'https://launchpad.37signals.com/authorization/token?type=web_server',
authorizationUrl: 'https://launchpad.37signals.com/authorization/new?type=web_server',
profileUrl: 'https://launchpad.37signals.com/authorization.json',
profile: (profile) => {
return {
id: profile.identity.id,
name: `${profile.identity.first_name} ${profile.identity.last_name}`,
email: profile.identity.email_address,
image: null,
image: null
}
},
...options,
...options
}
}

View File

@@ -1,29 +1,29 @@
export default function BattleNet(options) {
export default (options) => {
const { region } = options
return {
id: "battlenet",
name: "Battle.net",
type: "oauth",
version: "2.0",
scope: "openid",
params: { grant_type: "authorization_code" },
id: 'battlenet',
name: 'Battle.net',
type: 'oauth',
version: '2.0',
scope: 'openid',
params: { grant_type: 'authorization_code' },
accessTokenUrl:
region === "CN"
? "https://www.battlenet.com.cn/oauth/token"
region === 'CN'
? 'https://www.battlenet.com.cn/oauth/token'
: `https://${region}.battle.net/oauth/token`,
authorizationUrl:
region === "CN"
? "https://www.battlenet.com.cn/oauth/authorize?response_type=code"
region === 'CN'
? 'https://www.battlenet.com.cn/oauth/authorize?response_type=code'
: `https://${region}.battle.net/oauth/authorize?response_type=code`,
profileUrl: "https://us.battle.net/oauth/userinfo",
profile(profile) {
profileUrl: 'https://us.battle.net/oauth/userinfo',
profile: (profile) => {
return {
id: profile.id,
name: profile.battletag,
email: null,
image: null,
image: null
}
},
...options,
...options
}
}

View File

@@ -1,23 +1,22 @@
export default function Box(options) {
export default (options) => {
return {
id: "box",
name: "Box",
type: "oauth",
version: "2.0",
scope: "",
params: { grant_type: "authorization_code" },
accessTokenUrl: "https://api.box.com/oauth2/token",
authorizationUrl:
"https://account.box.com/api/oauth2/authorize?response_type=code",
profileUrl: "https://api.box.com/2.0/users/me",
profile(profile) {
id: 'box',
name: 'Box',
type: 'oauth',
version: '2.0',
scope: '',
params: { grant_type: 'authorization_code' },
accessTokenUrl: 'https://api.box.com/oauth2/token',
authorizationUrl: 'https://account.box.com/api/oauth2/authorize?response_type=code',
profileUrl: 'https://api.box.com/2.0/users/me',
profile: (profile) => {
return {
id: profile.id,
name: profile.name,
email: profile.login,
image: profile.avatar_url,
image: profile.avatar_url
}
},
...options,
...options
}
}

View File

@@ -1,34 +1,30 @@
export default function Bungie(options) {
export default (options) => {
return {
id: "bungie",
name: "Bungie",
type: "oauth",
version: "2.0",
scope: "",
params: { reauth: "true", grant_type: "authorization_code" },
accessTokenUrl: "https://www.bungie.net/platform/app/oauth/token/",
requestTokenUrl: "https://www.bungie.net/platform/app/oauth/token/",
authorizationUrl:
"https://www.bungie.net/en/OAuth/Authorize?response_type=code",
profileUrl:
"https://www.bungie.net/platform/User/GetBungieAccount/{membershipId}/254/",
profile(profile) {
id: 'bungie',
name: 'Bungie',
type: 'oauth',
version: '2.0',
scope: '',
params: { reauth: 'true', grant_type: 'authorization_code' },
accessTokenUrl: 'https://www.bungie.net/platform/app/oauth/token/',
requestTokenUrl: 'https://www.bungie.net/platform/app/oauth/token/',
authorizationUrl: 'https://www.bungie.net/en/OAuth/Authorize?response_type=code',
profileUrl: 'https://www.bungie.net/platform/User/GetBungieAccount/{membershipId}/254/',
profile: (profile) => {
const { bungieNetUser: user } = profile.Response
return {
id: user.membershipId,
name: user.displayName,
image: `https://www.bungie.net${
user.profilePicturePath.startsWith("/") ? "" : "/"
}${user.profilePicturePath}`,
email: null,
image: `https://www.bungie.net${user.profilePicturePath.startsWith('/') ? '' : '/'}${user.profilePicturePath}`,
email: null
}
},
headers: {
"X-API-Key": null,
'X-API-Key': null
},
clientId: null,
clientSecret: null,
...options,
...options
}
}

View File

@@ -1,23 +1,23 @@
export default function Cognito(options) {
export default (options) => {
const { domain } = options
return {
id: "cognito",
name: "Cognito",
type: "oauth",
version: "2.0",
scope: "openid profile email",
params: { grant_type: "authorization_code" },
id: 'cognito',
name: 'Cognito',
type: 'oauth',
version: '2.0',
scope: 'openid profile email',
params: { grant_type: 'authorization_code' },
accessTokenUrl: `https://${domain}/oauth2/token`,
authorizationUrl: `https://${domain}/oauth2/authorize?response_type=code`,
profileUrl: `https://${domain}/oauth2/userInfo`,
profile(profile) {
profile: (profile) => {
return {
id: profile.sub,
name: profile.username,
email: profile.email,
image: null,
image: null
}
},
...options,
...options
}
}

View File

@@ -1,10 +1,10 @@
export default function Credentials(options) {
export default (options) => {
return {
id: "credentials",
name: "Credentials",
type: "credentials",
id: 'credentials',
name: 'Credentials',
type: 'credentials',
authorize: null,
credentials: null,
...options,
...options
}
}

View File

@@ -1,30 +1,29 @@
export default function Discord(options) {
export default (options) => {
return {
id: "discord",
name: "Discord",
type: "oauth",
version: "2.0",
scope: "identify email",
params: { grant_type: "authorization_code" },
accessTokenUrl: "https://discord.com/api/oauth2/token",
authorizationUrl:
"https://discord.com/api/oauth2/authorize?response_type=code&prompt=none",
profileUrl: "https://discord.com/api/users/@me",
profile(profile) {
id: 'discord',
name: 'Discord',
type: 'oauth',
version: '2.0',
scope: 'identify email',
params: { grant_type: 'authorization_code' },
accessTokenUrl: 'https://discord.com/api/oauth2/token',
authorizationUrl: 'https://discord.com/api/oauth2/authorize?response_type=code&prompt=none',
profileUrl: 'https://discord.com/api/users/@me',
profile: (profile) => {
if (profile.avatar === null) {
const defaultAvatarNumber = parseInt(profile.discriminator) % 5
profile.image_url = `https://cdn.discordapp.com/embed/avatars/${defaultAvatarNumber}.png`
} else {
const format = profile.avatar.startsWith("a_") ? "gif" : "png"
const format = profile.avatar.startsWith('a_') ? 'gif' : 'png'
profile.image_url = `https://cdn.discordapp.com/avatars/${profile.id}/${profile.avatar}.${format}`
}
return {
id: profile.id,
name: profile.username,
image: profile.image_url,
email: profile.email,
email: profile.email
}
},
...options,
...options
}
}

View File

@@ -1,55 +1,46 @@
import nodemailer from "nodemailer"
import logger from "../lib/logger"
import logger from '../lib/logger'
import requireOptional from '@balazsorban/require-optional'
export default function Email(options) {
export default (options) => {
return {
id: "email",
type: "email",
name: "Email",
id: 'email',
type: 'email',
name: 'Email',
// Server can be an SMTP connection string or a nodemailer config object
server: {
host: "localhost",
host: 'localhost',
port: 25,
auth: {
user: "",
pass: "",
},
user: '',
pass: ''
}
},
from: "NextAuth <no-reply@example.com>",
maxAge: 24 * 60 * 60,
from: 'NextAuth <no-reply@example.com>',
maxAge: 24 * 60 * 60, // How long email links are valid for (default 24h)
sendVerificationRequest,
...options,
...options
}
}
const sendVerificationRequest = ({
identifier: email,
url,
baseUrl,
provider,
}) => {
return new Promise((resolve, reject) => {
const { server, from } = provider
// Strip protocol from URL and use domain as site name
const site = baseUrl.replace(/^https?:\/\//, "")
nodemailer.createTransport(server).sendMail(
{
async function sendVerificationRequest ({ identifier: email, url, baseUrl, provider }) {
const { server, from } = provider
// Strip protocol from URL and use domain as site name
const site = baseUrl.replace(/^https?:\/\//, '')
try {
const nodemailer = requireOptional('nodemailer')
await nodemailer
.createTransport(server)
.sendMail({
to: email,
from,
subject: `Sign in to ${site}`,
text: text({ url, site, email }),
html: html({ url, site, email }),
},
(error) => {
if (error) {
logger.error("SEND_VERIFICATION_EMAIL_ERROR", email, error)
return reject(new Error("SEND_VERIFICATION_EMAIL_ERROR", error))
}
return resolve()
}
)
})
html: html({ url, site, email })
})
} catch (error) {
logger.error('SEND_VERIFICATION_EMAIL_ERROR', email, error)
throw new Error('SEND_VERIFICATION_EMAIL_ERROR')
}
}
// Email HTML body
@@ -58,16 +49,16 @@ const html = ({ url, site, email }) => {
// email address and the domain from being turned into a hyperlink by email
// clients like Outlook and Apple mail, as this is confusing because it seems
// like they are supposed to click on their email address to sign in.
const escapedEmail = `${email.replace(/\./g, "&#8203;.")}`
const escapedSite = `${site.replace(/\./g, "&#8203;.")}`
const escapedEmail = `${email.replace(/\./g, '&#8203;.')}`
const escapedSite = `${site.replace(/\./g, '&#8203;.')}`
// Some simple styling options
const backgroundColor = "#f9f9f9"
const textColor = "#444444"
const mainBackgroundColor = "#ffffff"
const buttonBackgroundColor = "#346df1"
const buttonBorderColor = "#346df1"
const buttonTextColor = "#ffffff"
const backgroundColor = '#f9f9f9'
const textColor = '#444444'
const mainBackgroundColor = '#ffffff'
const buttonBackgroundColor = '#346df1'
const buttonBorderColor = '#346df1'
const buttonTextColor = '#ffffff'
return `
<body style="background: ${backgroundColor};">

View File

@@ -1,22 +1,21 @@
export default function EVEOnline(options) {
export default (options) => {
return {
id: "eveonline",
name: "EVE Online",
type: "oauth",
version: "2.0",
params: { grant_type: "authorization_code" },
accessTokenUrl: "https://login.eveonline.com/oauth/token",
authorizationUrl:
"https://login.eveonline.com/oauth/authorize?response_type=code",
profileUrl: "https://login.eveonline.com/oauth/verify",
profile(profile) {
id: 'eveonline',
name: 'EVE Online',
type: 'oauth',
version: '2.0',
params: { grant_type: 'authorization_code' },
accessTokenUrl: 'https://login.eveonline.com/oauth/token',
authorizationUrl: 'https://login.eveonline.com/oauth/authorize?response_type=code',
profileUrl: 'https://login.eveonline.com/oauth/verify',
profile: (profile) => {
return {
id: profile.CharacterID,
name: profile.CharacterName,
image: `https://image.eveonline.com/Character/${profile.CharacterID}_128.jpg`,
email: null,
email: null
}
},
...options,
...options
}
}

View File

@@ -1,22 +1,21 @@
export default function Facebook(options) {
export default (options) => {
return {
id: "facebook",
name: "Facebook",
type: "oauth",
version: "2.0",
scope: "email",
accessTokenUrl: "https://graph.facebook.com/oauth/access_token",
authorizationUrl:
"https://www.facebook.com/v7.0/dialog/oauth?response_type=code",
profileUrl: "https://graph.facebook.com/me?fields=email,name,picture",
profile(profile) {
id: 'facebook',
name: 'Facebook',
type: 'oauth',
version: '2.0',
scope: 'email',
accessTokenUrl: 'https://graph.facebook.com/oauth/access_token',
authorizationUrl: 'https://www.facebook.com/v7.0/dialog/oauth?response_type=code',
profileUrl: 'https://graph.facebook.com/me?fields=email,name,picture',
profile: (profile) => {
return {
id: profile.id,
name: profile.name,
email: profile.email,
image: profile.picture.data.url,
image: profile.picture.data.url
}
},
...options,
...options
}
}

View File

@@ -1,28 +1,25 @@
export default function FACEIT(options) {
export default (options) => {
return {
id: "faceit",
name: "FACEIT",
type: "oauth",
version: "2.0",
params: { grant_type: "authorization_code" },
id: 'faceit',
name: 'FACEIT',
type: 'oauth',
version: '2.0',
params: { grant_type: 'authorization_code' },
headers: {
Authorization: `Basic ${Buffer.from(
`${options.clientId}:${options.clientSecret}`
).toString("base64")}`,
Authorization: `Basic ${Buffer.from(`${options.clientId}:${options.clientSecret}`).toString('base64')}`
},
accessTokenUrl: "https://api.faceit.com/auth/v1/oauth/token",
authorizationUrl:
"https://accounts.faceit.com/accounts?redirect_popup=true&response_type=code",
profileUrl: "https://api.faceit.com/auth/v1/resources/userinfo",
profile(profile) {
accessTokenUrl: 'https://api.faceit.com/auth/v1/oauth/token',
authorizationUrl: 'https://accounts.faceit.com/accounts?redirect_popup=true&response_type=code',
profileUrl: 'https://api.faceit.com/auth/v1/resources/userinfo',
profile (profile) {
const { guid: id, nickname: name, email, picture: image } = profile
return {
id,
name,
email,
image,
image
}
},
...options,
...options
}
}

View File

@@ -1,23 +1,22 @@
export default function Foursquare(options) {
const { apiVersion } = options
export default ({ apiVersion, ...options }) => {
return {
id: "foursquare",
name: "Foursquare",
type: "oauth",
version: "2.0",
params: { grant_type: "authorization_code" },
accessTokenUrl: "https://foursquare.com/oauth2/access_token",
id: 'foursquare',
name: 'Foursquare',
type: 'oauth',
version: '2.0',
params: { grant_type: 'authorization_code' },
accessTokenUrl: 'https://foursquare.com/oauth2/access_token',
authorizationUrl:
"https://foursquare.com/oauth2/authenticate?response_type=code",
'https://foursquare.com/oauth2/authenticate?response_type=code',
profileUrl: `https://api.foursquare.com/v2/users/self?v=${apiVersion}`,
profile(profile) {
profile: (profile) => {
return {
id: profile.id,
name: `${profile.firstName} ${profile.lastName}`,
image: `${profile.prefix}original${profile.suffix}`,
email: profile.contact.email,
email: profile.contact.email
}
},
...options,
...options
}
}

View File

@@ -1,27 +1,27 @@
export default function FusionAuth(options) {
export default (options) => {
let authorizationUrl = `https://${options.domain}/oauth2/authorize?response_type=code`
if (options.tenantId) {
authorizationUrl += `&tenantId=${options.tenantId}`
}
return {
id: "fusionauth",
name: "FusionAuth",
type: "oauth",
version: "2.0",
scope: "openid",
params: { grant_type: "authorization_code" },
id: 'fusionauth',
name: 'FusionAuth',
type: 'oauth',
version: '2.0',
scope: 'openid',
params: { grant_type: 'authorization_code' },
accessTokenUrl: `https://${options.domain}/oauth2/token`,
authorizationUrl,
profileUrl: `https://${options.domain}/oauth2/userinfo`,
profile(profile) {
profile: (profile) => {
return {
id: profile.sub,
name: profile.name,
email: profile.email,
image: profile.picture,
image: profile.picture
}
},
...options,
...options
}
}

View File

@@ -1,21 +1,21 @@
export default function GitHub(options) {
export default (options) => {
return {
id: "github",
name: "GitHub",
type: "oauth",
version: "2.0",
scope: "user",
accessTokenUrl: "https://github.com/login/oauth/access_token",
authorizationUrl: "https://github.com/login/oauth/authorize",
profileUrl: "https://api.github.com/user",
profile(profile) {
id: 'github',
name: 'GitHub',
type: 'oauth',
version: '2.0',
scope: 'user',
accessTokenUrl: 'https://github.com/login/oauth/access_token',
authorizationUrl: 'https://github.com/login/oauth/authorize',
profileUrl: 'https://api.github.com/user',
profile: (profile) => {
return {
id: profile.id,
name: profile.name || profile.login,
email: profile.email,
image: profile.avatar_url,
image: profile.avatar_url
}
},
...options,
...options
}
}

View File

@@ -1,22 +1,22 @@
export default function GitLab(options) {
export default (options) => {
return {
id: "gitlab",
name: "GitLab",
type: "oauth",
version: "2.0",
scope: "read_user",
params: { grant_type: "authorization_code" },
accessTokenUrl: "https://gitlab.com/oauth/token",
authorizationUrl: "https://gitlab.com/oauth/authorize?response_type=code",
profileUrl: "https://gitlab.com/api/v4/user",
profile(profile) {
id: 'gitlab',
name: 'GitLab',
type: 'oauth',
version: '2.0',
scope: 'read_user',
params: { grant_type: 'authorization_code' },
accessTokenUrl: 'https://gitlab.com/oauth/token',
authorizationUrl: 'https://gitlab.com/oauth/authorize?response_type=code',
profileUrl: 'https://gitlab.com/api/v4/user',
profile: (profile) => {
return {
id: profile.id,
name: profile.username,
email: profile.email,
image: profile.avatar_url,
image: profile.avatar_url
}
},
...options,
...options
}
}

View File

@@ -1,25 +1,23 @@
export default function Google(options) {
export default (options) => {
return {
id: "google",
name: "Google",
type: "oauth",
version: "2.0",
scope:
"https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email",
params: { grant_type: "authorization_code" },
accessTokenUrl: "https://accounts.google.com/o/oauth2/token",
requestTokenUrl: "https://accounts.google.com/o/oauth2/auth",
authorizationUrl:
"https://accounts.google.com/o/oauth2/auth?response_type=code",
profileUrl: "https://www.googleapis.com/oauth2/v1/userinfo?alt=json",
profile(profile) {
id: 'google',
name: 'Google',
type: 'oauth',
version: '2.0',
scope: 'https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email',
params: { grant_type: 'authorization_code' },
accessTokenUrl: 'https://accounts.google.com/o/oauth2/token',
requestTokenUrl: 'https://accounts.google.com/o/oauth2/auth',
authorizationUrl: 'https://accounts.google.com/o/oauth2/auth?response_type=code',
profileUrl: 'https://www.googleapis.com/oauth2/v1/userinfo?alt=json',
profile: (profile) => {
return {
id: profile.id,
name: profile.name,
email: profile.email,
image: profile.picture,
image: profile.picture
}
},
...options,
...options
}
}

View File

@@ -1,17 +1,17 @@
export default function IdentityServer4(options) {
export default (options) => {
return {
id: "identity-server4",
name: "IdentityServer4",
type: "oauth",
version: "2.0",
scope: "openid profile email",
params: { grant_type: "authorization_code" },
id: 'identity-server4',
name: 'IdentityServer4',
type: 'oauth',
version: '2.0',
scope: 'openid profile email',
params: { grant_type: 'authorization_code' },
accessTokenUrl: `https://${options.domain}/connect/token`,
authorizationUrl: `https://${options.domain}/connect/authorize?response_type=code`,
profileUrl: `https://${options.domain}/connect/userinfo`,
profile(profile) {
profile: (profile) => {
return { ...profile, id: profile.sub }
},
...options,
...options
}
}

83
src/providers/index.js Normal file
View File

@@ -0,0 +1,83 @@
import Apple from './apple'
import Atlassian from './atlassian'
import Auth0 from './auth0'
import AzureADB2C from './azure-ad-b2c'
import Basecamp from './basecamp'
import BattleNet from './battlenet'
import Box from './box'
import Bungie from './bungie'
import Cognito from './cognito'
import Credentials from './credentials'
import Discord from './discord'
import Email from './email'
import EVEOnline from './eveonline'
import Facebook from './facebook'
import FACEIT from './faceit'
import Foursquare from './foursquare'
import FusionAuth from './fusionauth'
import GitHub from './github'
import GitLab from './gitlab'
import Google from './google'
import IdentityServer4 from './identity-server4'
import Instagram from './instagram'
import Kakao from './kakao'
import LINE from './line'
import LinkedIn from './linkedin'
import MailRu from './mailru'
import Medium from './medium'
import Netlify from './netlify'
import Okta from './okta'
import Osso from './osso'
import Reddit from './reddit'
import Salesforce from './salesforce'
import Slack from './slack'
import Spotify from './spotify'
import Strava from './strava'
import Twitch from './twitch'
import Twitter from './twitter'
import VK from './vk'
import Yandex from './yandex'
import Zoho from './zoho'
export default {
Apple,
Atlassian,
Auth0,
AzureADB2C,
Basecamp,
BattleNet,
Box,
Bungie,
Cognito,
Credentials,
Discord,
Email,
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
}

View File

@@ -1,5 +1,5 @@
/**
* @type {import("types/providers").OAuthProvider} options
* @param {import("../server").Provider} options
* @example
*
* ```js
@@ -22,29 +22,30 @@
* </button>
* ...
* ```
* [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)
* *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)
*/
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
}
}

View File

@@ -1,22 +1,21 @@
export default function Kakao(options) {
export default (options) => {
return {
id: "kakao",
name: "Kakao",
type: "oauth",
version: "2.0",
params: { grant_type: "authorization_code" },
accessTokenUrl: "https://kauth.kakao.com/oauth/token",
authorizationUrl:
"https://kauth.kakao.com/oauth/authorize?response_type=code",
profileUrl: "https://kapi.kakao.com/v2/user/me",
profile(profile) {
id: 'kakao',
name: 'Kakao',
type: 'oauth',
version: '2.0',
params: { grant_type: 'authorization_code' },
accessTokenUrl: 'https://kauth.kakao.com/oauth/token',
authorizationUrl: 'https://kauth.kakao.com/oauth/authorize?response_type=code',
profileUrl: 'https://kapi.kakao.com/v2/user/me',
profile: (profile) => {
return {
id: profile.id,
name: profile.kakao_account?.profile.nickname,
email: profile.kakao_account?.email,
image: profile.kakao_account?.profile.profile_image_url,
image: profile.kakao_account?.profile.profile_image_url
}
},
...options,
...options
}
}

View File

@@ -1,23 +1,22 @@
export default function LINE(options) {
export default (options) => {
return {
id: "line",
name: "LINE",
type: "oauth",
version: "2.0",
scope: "profile openid",
params: { grant_type: "authorization_code" },
accessTokenUrl: "https://api.line.me/oauth2/v2.1/token",
authorizationUrl:
"https://access.line.me/oauth2/v2.1/authorize?response_type=code",
profileUrl: "https://api.line.me/v2/profile",
profile(profile) {
id: 'line',
name: 'LINE',
type: 'oauth',
version: '2.0',
scope: 'profile openid',
params: { grant_type: 'authorization_code' },
accessTokenUrl: 'https://api.line.me/oauth2/v2.1/token',
authorizationUrl: 'https://access.line.me/oauth2/v2.1/authorize?response_type=code',
profileUrl: 'https://api.line.me/v2/profile',
profile: (profile) => {
return {
id: profile.userId,
name: profile.displayName,
email: null,
image: profile.pictureUrl,
image: profile.pictureUrl
}
},
...options,
...options
}
}

View File

@@ -1,28 +1,26 @@
export default function LinkedIn(options) {
export default (options) => {
return {
id: "linkedin",
name: "LinkedIn",
type: "oauth",
version: "2.0",
scope: "r_liteprofile",
id: 'linkedin',
name: 'LinkedIn',
type: 'oauth',
version: '2.0',
scope: 'r_liteprofile',
params: {
grant_type: "authorization_code",
grant_type: 'authorization_code',
client_id: options.clientId,
client_secret: options.clientSecret,
client_secret: options.clientSecret
},
accessTokenUrl: "https://www.linkedin.com/oauth/v2/accessToken",
authorizationUrl:
"https://www.linkedin.com/oauth/v2/authorization?response_type=code",
profileUrl:
"https://api.linkedin.com/v2/me?projection=(id,localizedFirstName,localizedLastName)",
profile(profile) {
accessTokenUrl: 'https://www.linkedin.com/oauth/v2/accessToken',
authorizationUrl: 'https://www.linkedin.com/oauth/v2/authorization?response_type=code',
profileUrl: 'https://api.linkedin.com/v2/me?projection=(id,localizedFirstName,localizedLastName)',
profile: (profile) => {
return {
id: profile.id,
name: profile.localizedFirstName + " " + profile.localizedLastName,
name: profile.localizedFirstName + ' ' + profile.localizedLastName,
email: null,
image: null,
image: null
}
},
...options,
...options
}
}

View File

@@ -1,25 +1,25 @@
export default function MailRu(options) {
export default (options) => {
return {
id: "mailru",
name: "Mail.ru",
type: "oauth",
version: "2.0",
scope: "userinfo",
id: 'mailru',
name: 'Mail.ru',
type: 'oauth',
version: '2.0',
scope: 'userinfo',
params: {
grant_type: "authorization_code",
grant_type: 'authorization_code'
},
accessTokenUrl: "https://oauth.mail.ru/token",
requestTokenUrl: "https://oauth.mail.ru/token",
authorizationUrl: "https://oauth.mail.ru/login?response_type=code",
profileUrl: "https://oauth.mail.ru/userinfo",
profile(profile) {
accessTokenUrl: 'https://oauth.mail.ru/token',
requestTokenUrl: 'https://oauth.mail.ru/token',
authorizationUrl: 'https://oauth.mail.ru/login?response_type=code',
profileUrl: 'https://oauth.mail.ru/userinfo',
profile: (profile) => {
return {
id: profile.id,
name: profile.name,
email: profile.email,
image: profile.image,
image: profile.image
}
},
...options,
...options
}
}

View File

@@ -1,22 +1,22 @@
export default function Medium(options) {
export default (options) => {
return {
id: "medium",
name: "Medium",
type: "oauth",
version: "2.0",
scope: "basicProfile",
params: { grant_type: "authorization_code" },
accessTokenUrl: "https://api.medium.com/v1/tokens",
authorizationUrl: "https://medium.com/m/oauth/authorize?response_type=code",
profileUrl: "https://api.medium.com/v1/me",
profile(profile) {
id: 'medium',
name: 'Medium',
type: 'oauth',
version: '2.0',
scope: 'basicProfile',
params: { grant_type: 'authorization_code' },
accessTokenUrl: 'https://api.medium.com/v1/tokens',
authorizationUrl: 'https://medium.com/m/oauth/authorize?response_type=code',
profileUrl: 'https://api.medium.com/v1/me',
profile: (profile) => {
return {
id: profile.data.id,
name: profile.data.name,
email: null,
image: profile.data.imageUrl,
image: profile.data.imageUrl
}
},
...options,
...options
}
}

View File

@@ -1,21 +1,21 @@
export default function Netlify(options) {
export default (options) => {
return {
id: "netlify",
name: "Netlify",
type: "oauth",
version: "2.0",
params: { grant_type: "authorization_code" },
accessTokenUrl: "https://api.netlify.com/oauth/token",
authorizationUrl: "https://app.netlify.com/authorize?response_type=code",
profileUrl: "https://api.netlify.com/api/v1/user",
profile(profile) {
id: 'netlify',
name: 'Netlify',
type: 'oauth',
version: '2.0',
params: { grant_type: 'authorization_code' },
accessTokenUrl: 'https://api.netlify.com/oauth/token',
authorizationUrl: 'https://app.netlify.com/authorize?response_type=code',
profileUrl: 'https://api.netlify.com/api/v1/user',
profile: (profile) => {
return {
id: profile.id,
name: profile.full_name,
email: profile.email,
image: profile.avatar_url,
image: profile.avatar_url
}
},
...options,
...options
}
}

View File

@@ -1,22 +1,22 @@
export default function Okta(options) {
export default (options) => {
return {
id: "okta",
name: "Okta",
type: "oauth",
version: "2.0",
scope: "openid profile email",
id: 'okta',
name: 'Okta',
type: 'oauth',
version: '2.0',
scope: 'openid profile email',
params: {
grant_type: "authorization_code",
grant_type: 'authorization_code',
client_id: options.clientId,
client_secret: options.clientSecret,
client_secret: options.clientSecret
},
// These will be different depending on the Org.
accessTokenUrl: `https://${options.domain}/v1/token`,
authorizationUrl: `https://${options.domain}/v1/authorize/?response_type=code`,
profileUrl: `https://${options.domain}/v1/userinfo/`,
profile(profile) {
profile: (profile) => {
return { ...profile, id: profile.sub }
},
...options,
...options
}
}

View File

@@ -1,20 +1,20 @@
export default function Osso(options) {
export default (options) => {
return {
id: "osso",
name: "SAML SSO",
type: "oauth",
version: "2.0",
params: { grant_type: "authorization_code" },
id: 'osso',
name: 'SAML SSO',
type: 'oauth',
version: '2.0',
params: { grant_type: 'authorization_code' },
accessTokenUrl: `https://${options.domain}/oauth/token`,
authorizationUrl: `https://${options.domain}/oauth/authorize?response_type=code`,
profileUrl: `https://${options.domain}/oauth/me`,
profile(profile) {
profile: (profile) => {
return {
id: profile.id,
name: profile.name || profile.email,
email: profile.email,
email: profile.email
}
},
...options,
...options
}
}

View File

@@ -1,23 +1,23 @@
export default function Reddit(options) {
export default (options) => {
return {
id: "reddit",
name: "Reddit",
type: "oauth",
version: "2.0",
scope: "identity",
params: { grant_type: "authorization_code" },
accessTokenUrl: " https://www.reddit.com/api/v1/access_token",
id: 'reddit',
name: 'Reddit',
type: 'oauth',
version: '2.0',
scope: 'identity',
params: { grant_type: 'authorization_code' },
accessTokenUrl: ' https://www.reddit.com/api/v1/access_token',
authorizationUrl:
"https://www.reddit.com/api/v1/authorize?response_type=code",
profileUrl: "https://oauth.reddit.com/api/v1/me",
profile(profile) {
'https://www.reddit.com/api/v1/authorize?response_type=code',
profileUrl: 'https://oauth.reddit.com/api/v1/me',
profile: (profile) => {
return {
id: profile.id,
name: profile.name,
image: null,
email: null,
email: null
}
},
...options,
...options
}
}

View File

@@ -1,22 +1,21 @@
export default function Salesforce(options) {
export default (options) => {
return {
id: "salesforce",
name: "Salesforce",
type: "oauth",
version: "2.0",
params: { display: "page", grant_type: "authorization_code" },
accessTokenUrl: "https://login.salesforce.com/services/oauth2/token",
authorizationUrl:
"https://login.salesforce.com/services/oauth2/authorize?response_type=code",
profileUrl: "https://login.salesforce.com/services/oauth2/userinfo",
protection: "none",
profile(profile) {
id: 'salesforce',
name: 'Salesforce',
type: 'oauth',
version: '2.0',
params: { display: 'page', grant_type: 'authorization_code' },
accessTokenUrl: 'https://login.salesforce.com/services/oauth2/token',
authorizationUrl: 'https://login.salesforce.com/services/oauth2/authorize?response_type=code',
profileUrl: 'https://login.salesforce.com/services/oauth2/userinfo',
protection: 'none', // REVIEW: Can we use "pkce" ?
profile: (profile) => {
return {
...profile,
id: profile.user_id,
image: profile.picture,
image: profile.picture
}
},
...options,
...options
}
}

View File

@@ -1,26 +1,24 @@
export default function Slack(options) {
export default (options) => {
return {
id: "slack",
name: "Slack",
type: "oauth",
version: "2.0",
id: 'slack',
name: 'Slack',
type: 'oauth',
version: '2.0',
scope: [],
params: { grant_type: "authorization_code" },
accessTokenUrl: "https://slack.com/api/oauth.v2.access",
authorizationUrl: "https://slack.com/oauth/v2/authorize",
authorizationParams: {
user_scope: "identity.basic,identity.email,identity.avatar",
},
profileUrl: "https://slack.com/api/users.identity",
profile(profile) {
params: { grant_type: 'authorization_code' },
accessTokenUrl: 'https://slack.com/api/oauth.v2.access',
authorizationUrl: 'https://slack.com/oauth/v2/authorize',
authorizationParams: { user_scope: 'identity.basic,identity.email,identity.avatar' },
profileUrl: 'https://slack.com/api/users.identity',
profile: (profile) => {
const { user } = profile
return {
id: user.id,
name: user.name,
image: user.image_512,
email: user.email,
email: user.email
}
},
...options,
...options
}
}

View File

@@ -1,23 +1,23 @@
export default function Spotify(options) {
export default (options) => {
return {
id: "spotify",
name: "Spotify",
type: "oauth",
version: "2.0",
scope: "user-read-email",
params: { grant_type: "authorization_code" },
accessTokenUrl: "https://accounts.spotify.com/api/token",
id: 'spotify',
name: 'Spotify',
type: 'oauth',
version: '2.0',
scope: 'user-read-email',
params: { grant_type: 'authorization_code' },
accessTokenUrl: 'https://accounts.spotify.com/api/token',
authorizationUrl:
"https://accounts.spotify.com/authorize?response_type=code",
profileUrl: "https://api.spotify.com/v1/me",
profile(profile) {
'https://accounts.spotify.com/authorize?response_type=code',
profileUrl: 'https://api.spotify.com/v1/me',
profile: (profile) => {
return {
id: profile.id,
name: profile.display_name,
email: profile.email,
image: profile.images?.[0]?.url,
image: profile.images?.[0]?.url
}
},
...options,
...options
}
}

View File

@@ -1,22 +1,22 @@
export default function Strava(options) {
export default (options) => {
return {
id: "strava",
name: "Strava",
type: "oauth",
version: "2.0",
scope: "read",
params: { grant_type: "authorization_code" },
accessTokenUrl: "https://www.strava.com/api/v3/oauth/token",
id: 'strava',
name: 'Strava',
type: 'oauth',
version: '2.0',
scope: 'read',
params: { grant_type: 'authorization_code' },
accessTokenUrl: 'https://www.strava.com/api/v3/oauth/token',
authorizationUrl:
"https://www.strava.com/api/v3/oauth/authorize?response_type=code",
profileUrl: "https://www.strava.com/api/v3/athlete",
profile(profile) {
'https://www.strava.com/api/v3/oauth/authorize?response_type=code',
profileUrl: 'https://www.strava.com/api/v3/athlete',
profile: (profile) => {
return {
id: profile.id,
name: profile.firstname,
image: profile.profile,
image: profile.profile
}
},
...options,
...options
}
}

View File

@@ -1,24 +1,24 @@
export default function Twitch(options) {
export default (options) => {
return {
id: "twitch",
name: "Twitch",
type: "oauth",
version: "2.0",
scope: "user:read:email",
params: { grant_type: "authorization_code" },
accessTokenUrl: "https://id.twitch.tv/oauth2/token",
id: 'twitch',
name: 'Twitch',
type: 'oauth',
version: '2.0',
scope: 'user:read:email',
params: { grant_type: 'authorization_code' },
accessTokenUrl: 'https://id.twitch.tv/oauth2/token',
authorizationUrl:
"https://id.twitch.tv/oauth2/authorize?response_type=code",
profileUrl: "https://api.twitch.tv/helix/users",
profile(profile) {
'https://id.twitch.tv/oauth2/authorize?response_type=code',
profileUrl: 'https://api.twitch.tv/helix/users',
profile: (profile) => {
const data = profile.data[0]
return {
id: data.id,
name: data.display_name,
image: data.profile_image_url,
email: data.email,
email: data.email
}
},
...options,
...options
}
}

View File

@@ -1,23 +1,23 @@
export default function Twitter(options) {
export default (options) => {
return {
id: "twitter",
name: "Twitter",
type: "oauth",
version: "1.0A",
scope: "",
accessTokenUrl: "https://api.twitter.com/oauth/access_token",
requestTokenUrl: "https://api.twitter.com/oauth/request_token",
authorizationUrl: "https://api.twitter.com/oauth/authenticate",
id: 'twitter',
name: 'Twitter',
type: 'oauth',
version: '1.0A',
scope: '',
accessTokenUrl: 'https://api.twitter.com/oauth/access_token',
requestTokenUrl: 'https://api.twitter.com/oauth/request_token',
authorizationUrl: 'https://api.twitter.com/oauth/authenticate',
profileUrl:
"https://api.twitter.com/1.1/account/verify_credentials.json?include_email=true",
profile(profile) {
'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$/, ".jpg"),
image: profile.profile_image_url_https.replace(/_normal\.jpg$/, '.jpg')
}
},
...options,
...options
}
}

View File

@@ -1,29 +1,30 @@
export default function VK(options) {
const apiVersion = "5.126" // https://vk.com/dev/versions
export default (options) => {
const apiVersion = '5.126' // https://vk.com/dev/versions
return {
id: "vk",
name: "VK",
type: "oauth",
version: "2.0",
scope: "email",
id: 'vk',
name: 'VK',
type: 'oauth',
version: '2.0',
scope: 'email',
params: {
grant_type: "authorization_code",
grant_type: 'authorization_code'
},
accessTokenUrl: `https://oauth.vk.com/access_token?v=${apiVersion}`,
requestTokenUrl: `https://oauth.vk.com/access_token?v=${apiVersion}`,
authorizationUrl: `https://oauth.vk.com/authorize?response_type=code&v=${apiVersion}`,
authorizationUrl:
`https://oauth.vk.com/authorize?response_type=code&v=${apiVersion}`,
profileUrl: `https://api.vk.com/method/users.get?fields=photo_100&v=${apiVersion}`,
profile: (result) => {
const profile = result.response?.[0] ?? {}
return {
id: profile.id,
name: [profile.first_name, profile.last_name].filter(Boolean).join(" "),
name: [profile.first_name, profile.last_name].filter(Boolean).join(' '),
email: profile.email,
image: profile.photo_100,
image: profile.photo_100
}
},
...options,
...options
}
}

View File

@@ -1,23 +1,23 @@
export default function Yandex(options) {
export default (options) => {
return {
id: "yandex",
name: "Yandex",
type: "oauth",
version: "2.0",
scope: "login:email login:info",
params: { grant_type: "authorization_code" },
accessTokenUrl: "https://oauth.yandex.ru/token",
requestTokenUrl: "https://oauth.yandex.ru/token",
authorizationUrl: "https://oauth.yandex.ru/authorize?response_type=code",
profileUrl: "https://login.yandex.ru/info?format=json",
profile(profile) {
id: 'yandex',
name: 'Yandex',
type: 'oauth',
version: '2.0',
scope: 'login:email login:info',
params: { grant_type: 'authorization_code' },
accessTokenUrl: 'https://oauth.yandex.ru/token',
requestTokenUrl: 'https://oauth.yandex.ru/token',
authorizationUrl: 'https://oauth.yandex.ru/authorize?response_type=code',
profileUrl: 'https://login.yandex.ru/info?format=json',
profile: (profile) => {
return {
id: profile.id,
name: profile.real_name,
email: profile.default_email,
image: null,
image: null
}
},
...options,
...options
}
}

View File

@@ -1,23 +1,22 @@
export default function Zoho(options) {
export default (options) => {
return {
id: "zoho",
name: "Zoho",
type: "oauth",
version: "2.0",
scope: "AaaServer.profile.Read",
params: { grant_type: "authorization_code" },
accessTokenUrl: "https://accounts.zoho.com/oauth/v2/token",
authorizationUrl:
"https://accounts.zoho.com/oauth/v2/auth?response_type=code",
profileUrl: "https://accounts.zoho.com/oauth/user/info",
profile(profile) {
id: 'zoho',
name: 'Zoho',
type: 'oauth',
version: '2.0',
scope: 'AaaServer.profile.Read',
params: { grant_type: 'authorization_code' },
accessTokenUrl: 'https://accounts.zoho.com/oauth/v2/token',
authorizationUrl: 'https://accounts.zoho.com/oauth/v2/auth?response_type=code',
profileUrl: '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,
image: null
}
},
...options,
...options
}
}

95
src/server/index.d.ts vendored Normal file
View File

@@ -0,0 +1,95 @@
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<string, unknown>
scope: string
accessTokenUrl: string
authorizationUrl: string
profileUrl?: string
grant_type?: string
profile?: (profile: any) => Promise<any>
}
/** @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<NextAuthOptions, NextAuthSharedOptions> {
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
csrfTokenVerified?: boolean
}
export interface NextAuthRequest extends NextApiRequest {
options: NextAuthInternalOptions
}
export interface NextAuthResponse extends NextApiResponse {}
export declare function NextAuthHandler (req: NextAuthRequest, res: NextAuthResponse, options: NextAuthOptions): ReturnType<NextApiHandler>
export declare function NextAuthHandler (options: NextAuthOptions): ReturnType<NextApiHandler>

View File

@@ -1,4 +1,3 @@
import adapters from '../adapters'
import jwt from '../lib/jwt'
import parseUrl from '../lib/parse-url'
import logger, { setLogger } from '../lib/logger'
@@ -15,6 +14,8 @@ import csrfTokenHandler from './lib/csrf-token-handler'
import * as pkce from './lib/oauth/pkce-handler'
import * as state from './lib/oauth/state-handler'
import optionalRequire from '@balazsorban/require-optional'
// To work properly in production with OAuth providers the NEXTAUTH_URL
// environment variable must be set.
if (!process.env.NEXTAUTH_URL) {
@@ -24,7 +25,7 @@ if (!process.env.NEXTAUTH_URL) {
/**
* @param {import("next").NextApiRequest} req
* @param {import("next").NextApiResponse} res
* @param {import("types").NextAuthOptions} userOptions
* @param {import(".").NextAuthOptions} userOptions
*/
async function NextAuthHandler (req, res, userOptions) {
if (userOptions.logger) {
@@ -73,7 +74,7 @@ async function NextAuthHandler (req, res, userOptions) {
// Protection only works on OAuth 2.x providers
if (provider?.type === 'oauth' && provider.version?.startsWith('2')) {
// When provider.state is undefined, we still want this to pass
if (!provider.protection && provider.state !== false) {
if (!provider.protection) {
// Default to state, as we did in 3.1 REVIEW: should we use "pkce" or "none" as default?
provider.protection = ['state']
} else if (typeof provider.protection === 'string') {
@@ -86,7 +87,11 @@ async function NextAuthHandler (req, res, userOptions) {
// Parse database / adapter
// If adapter is provided, use it (advanced usage, overrides database)
// If database URI or config object is provided, use it (simple usage)
const adapter = userOptions.adapter ?? (userOptions.database && adapters.Default(userOptions.database))
let adapter = userOptions.adapter
if ((!adapter && !!userOptions.database)) {
const TypeOrm = optionalRequire('../adapters/typeorm')
adapter = TypeOrm.Adapter(userOptions.database)
}
// User provided options are overriden by other options,
// except for the options with special handling above

7
src/server/lib/callbacks.d.ts vendored Normal file
View File

@@ -0,0 +1,7 @@
export interface CallbacksOptions {
signIn?: (user: any, account: any, profile: any) => Promise<never | string>
jwt?: (token: any, user: any, account: any, profile: any, isNewUser?: boolean) => Promise<any>
session?: (session: any, userOrToken: any) => Promise<any>
redirect?: (url: string, baseUrl: string) => Promise<string>
}

16
src/server/lib/cookie.d.ts vendored Normal file
View File

@@ -0,0 +1,16 @@
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
}

View File

@@ -9,8 +9,7 @@
* (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)
@@ -20,9 +19,7 @@ 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)
}
@@ -33,44 +30,32 @@ 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 {
@@ -88,19 +73,12 @@ 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:
@@ -132,7 +110,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("types").CookiesOptions}
* @return {import("./cookie").CookiesOptions}
*/
export function defaultCookies (useSecureCookies) {
const cookiePrefix = useSecureCookies ? '__Secure-' : ''

12
src/server/lib/events.d.ts vendored Normal file
View File

@@ -0,0 +1,12 @@
export type EventType=
| 'signIn'
| 'signOut'
| 'createUser'
| 'updateUser'
| 'linkAccount'
| 'session'
| 'error'
export type EventCallback = (message: any) => Promise<void>
export type EventsOptions = Partial<Record<EventType, EventCallback>>

View File

@@ -1,19 +1,19 @@
import { decode as jwtDecode } from "jsonwebtoken"
import oAuthClient from "./client"
import logger from "../../../lib/logger"
import { OAuthCallbackError } from "../../../lib/errors"
import { decode as jwtDecode } from 'jsonwebtoken'
import oAuthClient from './client'
import logger from '../../../lib/logger'
import { OAuthCallbackError } from '../../../lib/errors'
/** @param {import("types/internals").NextAuthRequest} req */
export default async function oAuthCallback(req) {
/** @param {import("../..").NextAuthRequest} req */
export default async function oAuthCallback (req) {
const { provider, pkce } = req.options
const client = oAuthClient(provider)
if (provider.version?.startsWith("2.")) {
if (provider.version?.startsWith('2.')) {
// The "user" object is specific to the Apple provider and is provided on first sign in
// e.g. {"name":{"firstName":"Johnny","lastName":"Appleseed"},"email":"johnny.appleseed@nextauth.com"}
let { code, user } = req.query // eslint-disable-line camelcase
if (req.method === "POST") {
if (req.method === 'POST') {
try {
const body = JSON.parse(JSON.stringify(req.body))
if (body.error) {
@@ -23,35 +23,25 @@ export default async function oAuthCallback(req) {
code = body.code
user = body.user != null ? JSON.parse(body.user) : null
} catch (error) {
logger.error(
"OAUTH_CALLBACK_HANDLER_ERROR",
error,
req.body,
provider.id,
code
)
logger.error('OAUTH_CALLBACK_HANDLER_ERROR', error, req.body, provider.id, code)
throw error
}
}
// REVIEW: Is this used by any of the providers?
// Pass authToken in header by default (unless 'useAuthTokenHeader: false' is set)
if (Object.prototype.hasOwnProperty.call(provider, "useAuthTokenHeader")) {
if (Object.prototype.hasOwnProperty.call(provider, 'useAuthTokenHeader')) {
client.useAuthorizationHeaderforGET(provider.useAuthTokenHeader)
} else {
client.useAuthorizationHeaderforGET(true)
}
try {
const tokens = await client.getOAuthAccessToken(
code,
provider,
pkce.code_verifier
)
const tokens = await client.getOAuthAccessToken(code, provider, pkce.code_verifier)
let profileData
if (provider.idToken) {
if (!tokens?.id_token) {
throw new OAuthCallbackError("Missing JWT ID Token")
throw new OAuthCallbackError('Missing JWT ID Token')
}
// Support services that use OpenID ID Tokens to encode profile data
@@ -62,28 +52,26 @@ export default async function oAuthCallback(req) {
return getProfile({ profileData, provider, tokens, user })
} catch (error) {
logger.error("OAUTH_GET_ACCESS_TOKEN_ERROR", error, provider.id, code)
logger.error('OAUTH_GET_ACCESS_TOKEN_ERROR', error, provider.id, code)
throw error
}
}
try {
// Handle OAuth v1.x
// eslint-disable-next-line camelcase
const { oauth_token, oauth_verifier } = req.query
// eslint-disable-next-line camelcase
const { token_secret } = await client.getOAuthRequestToken(provider.params)
const tokens = await client.getOAuthAccessToken(oauth_token, token_secret, oauth_verifier)
const {
oauth_token: oauthToken, oauth_verifier: oauthVerifier
} = req.query
const tokens = await client.getOAuthAccessToken(oauthToken, null, oauthVerifier)
const profileData = await client.get(
provider.profileUrl,
tokens.oauth_token,
tokens.oauth_token_secret
tokens.accessToken,
tokens.refreshToken
)
return getProfile({ profileData, tokens, provider })
} catch (error) {
logger.error("OAUTH_V1_GET_ACCESS_TOKEN_ERROR", error)
logger.error('OAUTH_V1_GET_ACCESS_TOKEN_ERROR', error)
throw error
}
}
@@ -101,19 +89,15 @@ export default async function oAuthCallback(req) {
* expires_in?: string | Date | null
* refresh_token?: string
* id_token?: string
* token?: string
* token_secret?: string
* tokenSecret?: string
* params?: any
* }
* provider: import("../..").Provider
* user?: object
* }} profileParams
*/
async function getProfile({ profileData, tokens, provider, user }) {
async function getProfile ({ profileData, tokens, provider, user }) {
try {
// Convert profileData into an object if it's a string
if (typeof profileData === "string" || profileData instanceof String) {
if (typeof profileData === 'string' || profileData instanceof String) {
profileData = JSON.parse(profileData)
}
@@ -122,22 +106,22 @@ async function getProfile({ profileData, tokens, provider, user }) {
profileData.user = user
}
logger.debug("PROFILE_DATA", profileData)
logger.debug('PROFILE_DATA', profileData)
const profile = await provider.profile(profileData, tokens)
// Return profile, raw profile and auth provider details
return {
profile: {
...profile,
email: profile.email?.toLowerCase() ?? null,
email: profile.email?.toLowerCase() ?? null
},
account: {
provider: provider.id,
type: provider.type,
id: profile.id,
...tokens,
...tokens
},
OAuthProfile: profileData,
OAuthProfile: profileData
}
} catch (exception) {
// If we didn't get a response either there was a problem with the provider
@@ -147,11 +131,11 @@ async function getProfile({ profileData, tokens, provider, user }) {
// 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", exception, profileData)
logger.error('OAUTH_PARSE_PROFILE_ERROR', exception, profileData)
return {
profile: null,
account: null,
OAuthProfile: profileData,
OAuthProfile: profileData
}
}
}

View File

@@ -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("types/providers").OAuthConfig} provider
* @param {import("../..").Provider} provider
*/
export default function oAuthClient (provider) {
if (provider.version?.startsWith('2.')) {
@@ -54,36 +54,23 @@ export default function oAuthClient (provider) {
const originalGetOAuth1AccessToken = oauth1Client.getOAuthAccessToken.bind(oauth1Client)
oauth1Client.getOAuthAccessToken = (...args) => {
return new Promise((resolve, reject) => {
// eslint-disable-next-line camelcase
originalGetOAuth1AccessToken(...args, (error, oauth_token, oauth_token_secret, params) => {
originalGetOAuth1AccessToken(...args, (error, accessToken, refreshToken, results) => {
if (error) {
return reject(error)
}
resolve({
// TODO: Remove, this is only kept for backward compativility
// These are not in the OAuth 1.x spec
accessToken: oauth_token,
refreshToken: oauth_token_secret,
results: params,
oauth_token,
oauth_token_secret,
params
})
resolve({ accessToken, refreshToken, results })
})
})
}
const originalGetOAuthRequestToken = oauth1Client.getOAuthRequestToken.bind(oauth1Client)
oauth1Client.getOAuthRequestToken = (params = {}) => {
oauth1Client.getOAuthRequestToken = (...args) => {
return new Promise((resolve, reject) => {
// eslint-disable-next-line camelcase
originalGetOAuthRequestToken(params, (error, oauth_token, oauth_token_secret, params) => {
originalGetOAuthRequestToken(...args, (error, oauthToken) => {
if (error) {
return reject(error)
}
resolve({ oauth_token, oauth_token_secret, params })
resolve(oauthToken)
})
})
}
@@ -101,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("types/providers").OAuthConfig} provider
* @param {import("../..").Provider} provider
* @param {string | undefined} codeVerifier
*/
async function getOAuth2AccessToken (code, provider, codeVerifier) {
@@ -209,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("types/providers").OAuthConfig} provider
* @param {import("../..").Provider} provider
* @param {string} accessToken
* @param {any} results
*/

View File

@@ -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("types/internals").NextAuthRequest} req
* @param {import("types/internals").NextAuthResponse} res
* @param {import("../..").NextAuthRequest} req
* @param {import("../..").NextAuthResponse} res
*/
export async function handleCallback (req, res) {
const { cookies, provider, baseUrl, basePath } = req.options
@@ -45,8 +45,8 @@ export async function handleCallback (req, res) {
/**
* Adds `code_challenge` and `code_challenge_method` to `req.options.pkce`.
* @param {import("types/internals").NextAuthRequest} req
* @param {import("types/internals").NextAuthResponse} res
* @param {import("../..").NextAuthRequest} req
* @param {import("../..").NextAuthResponse} res
*/
export async function handleSignin (req, res) {
const { cookies, provider, baseUrl, basePath } = req.options

View File

@@ -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("types/internals").NextAuthRequest} req
* @param {import("types/internals").NextAuthResponse} res
* @param {import("../..").NextAuthRequest} req
* @param {import("../..").NextAuthResponse} res
*/
export async function handleCallback (req, res) {
const { csrfToken, provider, baseUrl, basePath } = req.options
@@ -36,8 +36,8 @@ export async function handleCallback (req, res) {
/**
* Adds CSRF token to the authorizationParams.
* @param {import("types/internals").NextAuthRequest} req
* @param {import("types/internals").NextAuthResponse} res
* @param {import("../..").NextAuthRequest} req
* @param {import("../..").NextAuthResponse} res
*/
export async function handleSignin (req, res) {
const { provider, baseUrl, basePath, csrfToken } = req.options

View File

@@ -1,21 +1,17 @@
import oAuthClient from '../oauth/client'
import logger from '../../../lib/logger'
/** @param {import("types/internals").NextAuthRequest} req */
/** @param {import("../..").NextAuthRequest} req */
export default async function getAuthorizationUrl (req) {
const { provider } = req.options
delete req.query?.nextauth
const params = {
...provider.authorizationParams,
...req.query
}
const client = oAuthClient(provider)
if (provider.version?.startsWith('2.')) {
delete req.query?.nextauth
// Handle OAuth v2.x
let url = client.getAuthorizeUrl({
...params,
...provider.authorizationParams,
...req.query,
redirect_uri: provider.callbackUrl,
scope: provider.scope
})
@@ -38,12 +34,8 @@ export default async function getAuthorizationUrl (req) {
}
try {
const tokens = await client.getOAuthRequestToken(params)
const url = `${provider.authorizationUrl}?${new URLSearchParams({
oauth_token: tokens.oauth_token,
oauth_token_secret: tokens.oauth_token_secret,
...tokens.params
})}`
const oAuthToken = await client.getOAuthRequestToken()
const url = `${provider.authorizationUrl}?oauth_token=${oAuthToken}`
logger.debug('GET_AUTHORIZATION_URL', url)
return url
} catch (error) {

View File

@@ -7,7 +7,7 @@ import { h } from 'preact' // eslint-disable-line no-unused-vars
* baseUrl: string
* basePath: string
* error?: string
* res: import("types/internals").NextAuthResponse
* res: import("..").NextAuthResponse
* }} params
*/
export default function error ({ baseUrl, basePath, error = 'default', res }) {
@@ -52,7 +52,7 @@ export default function error ({ baseUrl, basePath, error = 'default', res }) {
}
}
const { statusCode, heading, message, signin } = errors[error.toLowerCase()] ?? errors.default
const { statusCode, heading, message, signin } = errors[error.toLowerCase()]
res.status(statusCode)

View File

@@ -6,8 +6,8 @@ import dispatchEvent from '../lib/dispatch-event'
/**
* Handle callbacks from login services
* @param {import("types/internals").NextAuthRequest} req
* @param {import("types/internals").NextAuthResponse} res
* @param {import("..").NextAuthRequest} req
* @param {import("..").NextAuthResponse} res
*/
export default async function callback (req, res) {
const {
@@ -65,18 +65,13 @@ export default async function callback (req, res) {
try {
const signInCallbackResponse = await callbacks.signIn(userOrProfile, account, OAuthProfile)
if (signInCallbackResponse === false) {
if (!signInCallbackResponse) {
return res.redirect(`${baseUrl}${basePath}/error?error=AccessDenied`)
} else if (typeof signInCallbackResponse === 'string') {
return res.redirect(signInCallbackResponse)
}
} catch (error) {
if (error instanceof Error) {
return res.redirect(`${baseUrl}${basePath}/error?error=${encodeURIComponent(error.message)}`)
}
// TODO: Remove in a future major release
logger.warn('SIGNIN_CALLBACK_REJECT_REDIRECT')
return res.redirect(error)
return res.redirect(`${baseUrl}${basePath}/error?error=${encodeURIComponent(error.message)}`)
}
// Sign user in
@@ -161,18 +156,13 @@ export default async function callback (req, res) {
// Check if user is allowed to sign in
try {
const signInCallbackResponse = await callbacks.signIn(profile, account, { email })
if (signInCallbackResponse === false) {
if (!signInCallbackResponse) {
return res.redirect(`${baseUrl}${basePath}/error?error=AccessDenied`)
} else if (typeof signInCallbackResponse === 'string') {
return res.redirect(signInCallbackResponse)
}
} catch (error) {
if (error instanceof Error) {
return res.redirect(`${baseUrl}${basePath}/error?error=${encodeURIComponent(error.message)}`)
}
// TODO: Remove in a future major release
logger.warn('SIGNIN_CALLBACK_REJECT_REDIRECT')
return res.redirect(error)
return res.redirect(`${baseUrl}${basePath}/error?error=${encodeURIComponent(error.message)}`)
}
// Sign user in
@@ -236,12 +226,11 @@ export default async function callback (req, res) {
userObjectReturnedFromAuthorizeHandler = await provider.authorize(credentials)
if (!userObjectReturnedFromAuthorizeHandler) {
return res.status(401).redirect(`${baseUrl}${basePath}/error?error=CredentialsSignin&provider=${encodeURIComponent(provider.id)}`)
} else if (typeof userObjectReturnedFromAuthorizeHandler === 'string') {
return res.redirect(userObjectReturnedFromAuthorizeHandler)
}
} catch (error) {
if (error instanceof Error) {
return res.redirect(`${baseUrl}${basePath}/error?error=${encodeURIComponent(error.message)}`)
}
return res.redirect(error)
return res.redirect(`${baseUrl}${basePath}/error?error=${encodeURIComponent(error.message)}`)
}
const user = userObjectReturnedFromAuthorizeHandler
@@ -249,21 +238,19 @@ export default async function callback (req, res) {
try {
const signInCallbackResponse = await callbacks.signIn(user, account, credentials)
if (signInCallbackResponse === false) {
if (!signInCallbackResponse) {
return res.status(403).redirect(`${baseUrl}${basePath}/error?error=AccessDenied`)
} else if (typeof signInCallbackResponse === 'string') {
return res.redirect(signInCallbackResponse)
}
} catch (error) {
if (error instanceof Error) {
return res.redirect(`${baseUrl}${basePath}/error?error=${encodeURIComponent(error.message)}`)
}
return res.redirect(error)
return res.redirect(`${baseUrl}${basePath}/error?error=${encodeURIComponent(error.message)}`)
}
const defaultJwtPayload = {
name: user.name,
email: user.email,
picture: user.image,
sub: user.id?.toString()
picture: user.image
}
const jwtPayload = await callbacks.jwt(defaultJwtPayload, user, account, userObjectReturnedFromAuthorizeHandler, false)

View File

@@ -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("types/internals").NextAuthRequest} req
* @param {import("types/internals").NextAuthResponse} res
* @param {import("..").NextAuthRequest} req
* @param {import("..").NextAuthResponse} res
*/
export default function providers (req, res) {
const { providers } = req.options

View File

@@ -45,18 +45,13 @@ export default async function signin (req, res) {
// Check if user is allowed to sign in
try {
const signInCallbackResponse = await callbacks.signIn(profile, account, { email, verificationRequest: true })
if (signInCallbackResponse === false) {
if (!signInCallbackResponse) {
return res.redirect(`${baseUrl}${basePath}/error?error=AccessDenied`)
} else if (typeof signInCallbackResponse === 'string') {
return res.redirect(signInCallbackResponse)
}
} catch (error) {
if (error instanceof Error) {
return res.redirect(`${baseUrl}${basePath}/error?error=${encodeURIComponent(error)}`)
}
// TODO: Remove in a future major release
logger.warn('SIGNIN_CALLBACK_REJECT_REDIRECT')
return res.redirect(error)
return res.redirect(`${baseUrl}${basePath}/error?error=${encodeURIComponent(error)}`)
}
try {

View File

@@ -3,9 +3,6 @@
"strictNullChecks": true,
"baseUrl": ".",
"paths": {
"types": [
"./types"
],
"next-auth": [
"./src/server"
],
@@ -43,8 +40,7 @@
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
"**/*.js"
"**/*.tsx"
],
"exclude": [
"node_modules"

244
types/adapters.d.ts vendored
View File

@@ -1,244 +0,0 @@
import { AppOptions } from "./internals"
import { ConnectionOptions, EntitySchema } from "typeorm"
import { User } from "."
import { AppProvider } from "./internals/providers"
export interface Profile {
id: string
name: string
email: string | null
image?: string | null
}
export interface Session {
userId: string | number | object
expires: Date
sessionToken: string
accessToken: string
}
export interface VerificationRequest {
identifier: string
token: string
expires: Date
}
export interface SendVerificationRequestParams {
identifier: string
url: string
token: string
baseUrl: string
provider: AppProvider
}
export type EmailAppProvider = AppProvider & {
sendVerificationRequest: (
params: SendVerificationRequestParams
) => Promise<void>
maxAge: number | undefined
}
export interface AdapterInstance<
TUser,
TProfile,
TSession,
TVerificationRequest
> {
createUser: (profile: TProfile) => Promise<TUser>
getUser: (id: string) => Promise<TUser | null>
getUserByEmail: (email: string) => Promise<TUser | null>
getUserByProviderAccountId: (
providerId: string,
providerAccountId: string
) => Promise<TUser | null>
updateUser: (user: TUser) => Promise<TUser>
linkAccount: (
userId: string,
providerId: string,
providerType: string,
providerAccountId: string,
refreshToken: string,
accessToken: string,
accessTokenExpires: number
) => Promise<void>
createSession: (user: TUser) => Promise<TSession>
getSession: (sessionToken: string) => Promise<TSession | null>
updateSession: (session: TSession, force?: boolean) => Promise<TSession>
deleteSession: (sessionToken: string) => Promise<void>
createVerificationRequest?: (
email: string,
url: string,
token: string,
secret: string,
provider: EmailAppProvider,
options: AppOptions
) => Promise<TVerificationRequest>
getVerificationRequest?: (
email: string,
verificationToken: string,
secret: string,
provider: AppProvider
) => Promise<TVerificationRequest | null>
deleteVerificationRequest?: (
email: string,
verificationToken: string,
secret: string,
provider: AppProvider
) => Promise<void>
}
interface Adapter<
TUser extends User = any,
TProfile extends Profile = any,
TSession extends Session = any,
TVerificationRequest extends VerificationRequest = any
> {
getAdapter: (
appOptions: AppOptions
) => Promise<AdapterInstance<TUser, TProfile, TSession, TVerificationRequest>>
}
type Schema<T = any> = EntitySchema<T>["options"]
interface BuiltInAdapters {
Default: TypeORMAdapter["Adapter"]
TypeORM: TypeORMAdapter
Prisma: PrismaAdapter
}
/**
* TODO: fix auto-type schema
*/
interface TypeORMAdapter<
A extends TypeORMAccountModel = any,
U extends TypeORMUserModel = any,
S extends TypeORMSessionModel = any,
VR extends TypeORMVerificationRequestModel = any
> {
Adapter: (
typeOrmConfig: ConnectionOptions,
options?: {
models?: {
Account?: {
model: A
schema: Schema<A>
}
User?: {
model: U
schema: Schema<U>
}
Session?: {
model: S
schema: Schema<S>
}
VerificationRequest?: {
model: VR
schema: Schema<VR>
}
}
}
) => Adapter<U, Profile, S, VR>
Models: {
Account: {
model: TypeORMAccountModel
schema: Schema<TypeORMAccountModel>
}
User: {
model: TypeORMUserModel
schema: Schema<TypeORMUserModel>
}
Session: {
model: TypeORMSessionModel
schema: Schema<TypeORMSessionModel>
}
VerificationRequest: {
model: TypeORMVerificationRequestModel
schema: Schema<TypeORMVerificationRequestModel>
}
}
}
interface PrismaAdapter {
Adapter: (config: {
prisma: any
modelMapping?: {
User: string
Account: string
Session: string
VerificationRequest: string
}
}) => Adapter
}
declare class TypeORMAccountModel {
compoundId: string
userId: number
providerType: string
providerId: string
providerAccountId: string
refreshToken?: string
accessToken?: string
accessTokenExpires?: Date
constructor(
userId: number,
providerId: string,
providerType: string,
providerAccountId: string,
refreshToken?: string,
accessToken?: string,
accessTokenExpires?: Date
)
}
declare class TypeORMUserModel implements User {
name?: string
email?: string
image?: string
emailVerified?: Date
constructor(
name?: string,
email?: string,
image?: string,
emailVerified?: Date
)
}
declare class TypeORMSessionModel implements Session {
userId: number
expires: Date
sessionToken: string
accessToken: string
constructor(
userId: number,
expires: Date,
sessionToken?: string,
accessToken?: string
)
}
declare class TypeORMVerificationRequestModel implements VerificationRequest {
identifier: string
token: string
expires: Date
constructor(identifier: string, token: string, expires: Date)
}
declare const Adapters: BuiltInAdapters
export default Adapters
export {
Adapter,
BuiltInAdapters as Adapters,
TypeORMAdapter,
TypeORMAccountModel,
TypeORMUserModel,
TypeORMSessionModel,
TypeORMVerificationRequestModel,
PrismaAdapter,
}

218
types/client.d.ts vendored
View File

@@ -1,218 +0,0 @@
import * as React from "react"
import { IncomingMessage } from "http"
import { Session } from "."
import { ProviderType } from "./providers"
export interface CtxOrReq {
req?: IncomingMessage
ctx?: { req: IncomingMessage }
}
/***************
* 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<Session | null>
/**
* 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<string | null>
/**
* 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<Record<
string,
ClientSafeProvider
> | 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<string, unknown> {
/**
* 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
}
export interface SignInResponse {
error: string | undefined
status: number
ok: boolean
url: string | null
}
/** Match `inputType` of `new URLSearchParams(inputType)` */
export type SignInAuthorisationParams =
| string
| string[][]
| Record<string, string>
| URLSearchParams
/**
* 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<P extends SignInProvider = undefined>(
provider?: P,
options?: SignInOptions,
authorizationParams?: SignInAuthorisationParams
): Promise<
P extends RedirectableProvider ? SignInResponse | undefined : undefined
>
/**
* Alias for `signIn`
* @docs https://next-auth.js.org/getting-started/client#signin
*/
export const signin: typeof signIn
/****************
* 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<R extends boolean = true> {
/** @docs https://next-auth.js.org/getting-started/client#specifying-a-callbackurl-1 */
callbackUrl?: string
/** @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<R extends boolean = true>(
params?: SignOutParams<R>
): Promise<R extends true ? undefined : SignOutResponse>
/**
* @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

413
types/index.d.ts vendored
View File

@@ -1,413 +0,0 @@
// Minimum TypeScript Version: 3.5
/// <reference types="node" />
import { ConnectionOptions } from "typeorm"
import { Adapter } from "./adapters"
import { JWTOptions, JWT } from "./jwt"
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 {
/**
* 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: 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<string, any> | ConnectionOptions
/**
* A random string used to hash tokens, sign cookies and generate cryptographic keys.
* If not specified is uses a hash of all configuration options, including Client ID / Secrets for entropy.
* The default behavior 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 documentation 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 documentation](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 documentation
* * **Required**: *No*
*
* [Documentation](https://next-auth.js.org/configuration/options#callbacks) | [Callbacks documentation](https://next-auth.js.org/configuration/callbacks)
*/
callbacks?: CallbacksOptions
/**
* 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?: 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 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?: 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 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?: CookiesOptions
}
/**
* 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 {
warn(code: string, ...message: unknown[]): void
error(code: string, ...message: unknown[]): void
debug(code: string, ...message: unknown[]): void
}
/**
* Different tokens returned by OAuth Providers.
* Some of them are available with different casing,
* but they refer to the same value.
*/
export interface TokenSet {
accessToken: string
idToken?: string
refreshToken?: string
access_token: string
expires_in?: number | null
refresh_token?: string
id_token?: string
}
/**
* 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, Record<string, unknown> {
id: string
provider: string
type: string
}
/** The OAuth profile returned from your provider */
export interface Profile extends Record<string, unknown> {
sub?: string
name?: string
email?: string
image?: string
}
/** [Documentation](https://next-auth.js.org/configuration/callbacks) */
export interface CallbacksOptions<
P extends Record<string, unknown> = Profile,
A extends Record<string, unknown> = Account
> {
/**
* 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?(user: User, account: A, profile: P): Awaitable<string | boolean>
/**
* 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?(url: string, baseUrl: string): Awaitable<string>
/**
* This callback is called whenever a session is checked.
* (Eg.: invoking the `/api/session` endpoint, using `useSession` or `getSession`)
*
* - ⚠ By default, only a subset 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 explicitely 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?(session: Session, userOrToken: JWT | User): Awaitable<Session>
/**
* 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?(
token: JWT,
user?: User,
account?: A,
profile?: P,
isNewUser?: boolean
): Awaitable<JWT>
}
/** [Documentation](https://next-auth.js.org/configuration/options#cookies) */
export interface CookieOption {
name: string
options: {
httpOnly: boolean
sameSite: true | "strict" | "lax" | "none"
path?: string
secure: boolean
maxAge?: number
domain?: string
}
}
/** [Documentation](https://next-auth.js.org/configuration/options#cookies) */
export interface CookiesOptions {
sessionToken?: CookieOption
callbackUrl?: CookieOption
csrfToken?: CookieOption
pkceCodeVerifier?: CookieOption
}
/** [Documentation](https://next-auth.js.org/configuration/events) */
export type EventType =
| "signIn"
| "signOut"
| "createUser"
| "updateUser"
| "linkAccount"
| "session"
| "error"
/** [Documentation](https://next-auth.js.org/configuration/events) */
export type EventCallback = (message: any) => Promise<void>
/** [Documentation](https://next-auth.js.org/configuration/events) */
export type EventsOptions = Partial<Record<EventType, EventCallback>>
/** [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
}
/**
* Returned by `useSession`, `getSession`, returned by the `session` callback
* and also the shape received as a prop on the `Provider` React Context
*
* [`useSession`](https://next-auth.js.org/getting-started/client#usesession) |
* [`getSession`](https://next-auth.js.org/getting-started/client#getsession) |
* [`Provider`](https://next-auth.js.org/getting-started/client#provider) |
* [`session` callback](https://next-auth.js.org/configuration/callbacks#jwt-callback)
*/
export interface Session extends Record<string, unknown> {
user?: User
accessToken?: string
expires: string
}
/** [Documentation](https://next-auth.js.org/configuration/options#session) */
export interface SessionOptions {
jwt?: boolean
maxAge?: number
updateAge?: number
}
/**
* 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 {
name?: string | null
email?: string | null
image?: string | null
}
declare function NextAuth(
req: NextApiRequest,
res: NextApiResponse,
options: NextAuthOptions
): ReturnType<NextApiHandler>
declare function NextAuth(options: NextAuthOptions): ReturnType<NextApiHandler>
export default NextAuth

View File

@@ -1,34 +0,0 @@
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<typeof setTimeout>
/** 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<Session>

View File

@@ -1,50 +0,0 @@
import { NextApiRequest, NextApiResponse } from "./utils"
import { NextAuthOptions } from ".."
import { AppProvider } from "./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<NextAuthOptions, NextAuthSharedOptions> {
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
csrfTokenVerified?: boolean
}
export interface NextAuthRequest extends NextApiRequest {
options: AppOptions
}
export type NextAuthResponse = NextApiResponse

View File

@@ -1,40 +0,0 @@
import { IncomingMessage, ServerResponse } from "http"
// ------------------------------------------------------
// 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<T> = (body: T) => void
export type NextApiResponse<T = any> = ServerResponse & {
send: Send<T>
json: Send<T>
status: (statusCode: number) => NextApiResponse<T>
redirect: ((url: string) => NextApiResponse<T>) &
((status: number, url: string) => NextApiResponse<T>)
setPreviewData: (
data: object | string,
options?: {
maxAge?: number
}
) => NextApiResponse<T>
clearPreviewData: () => NextApiResponse<T>
}
export type NextApiHandler<T = any> = (
req: NextApiRequest,
res: NextApiResponse<T>
) => void | Promise<void>

View File

@@ -1,6 +0,0 @@
import { CommonProviderOptions } from "../providers"
export interface AppProvider extends CommonProviderOptions {
signinUrl: string
callbackUrl: string
}

View File

@@ -1,42 +0,0 @@
import { IncomingMessage, ServerResponse } from "http"
export type Awaitable<T> = T | PromiseLike<T>
// ------------------------------------------------------
// 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<T> = (body: T) => void
export type NextApiResponse<T = any> = ServerResponse & {
send: Send<T>
json: Send<T>
status: (statusCode: number) => NextApiResponse<T>
redirect: ((url: string) => NextApiResponse<T>) &
((status: number, url: string) => NextApiResponse<T>)
setPreviewData: (
data: object | string,
options?: {
maxAge?: number
}
) => NextApiResponse<T>
clearPreviewData: () => NextApiResponse<T>
}
export type NextApiHandler<T = any> = (
req: NextApiRequest,
res: NextApiResponse<T>
) => void | Promise<void>

66
types/jwt.d.ts vendored
View File

@@ -1,66 +0,0 @@
import { JWT as JoseJWT, JWE } from "jose"
import { NextApiRequest } from "./internals/utils"
/**
* 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<string, unknown> {
name?: string | null
email?: string | null
picture?: string | null
}
export interface JWTEncodeParams {
token?: JWT
maxAge?: number
secret: string | Buffer
signingKey?: string
signingOptions?: JoseJWT.SignOptions
encryptionKey?: string
encryptionOptions?: object
encryption?: boolean
}
export function encode(params?: JWTEncodeParams): Promise<string>
export interface JWTDecodeParams {
token?: string
maxAge?: number
secret: string | Buffer
signingKey?: string
verificationKey?: string
verificationOptions?: JoseJWT.VerifyOptions<false>
encryptionKey?: string
decryptionKey?: string
decryptionOptions?: JWE.DecryptOptions<false>
encryption?: boolean
}
export function decode(params?: JWTDecodeParams): Promise<JWT>
export type GetTokenParams<R extends boolean = false> = {
req: NextApiRequest
secureCookie?: boolean
cookieName?: string
raw?: R
decode?: typeof decode
secret?: string
} & Omit<JWTDecodeParams, "secret">
/** [Documentation](https://next-auth.js.org/tutorials/securing-pages-and-api-routes#using-gettoken) */
export function getToken<R extends boolean = false>(
params?: GetTokenParams<R>
): Promise<R extends true ? string : JWT | null>
export interface JWTOptions {
secret?: string
maxAge?: number
encryption?: boolean
signingKey?: string
encryptionKey?: string
encode?: typeof encode
decode?: typeof decode
verificationOptions?: JoseJWT.VerifyOptions<false>
}

166
types/providers.d.ts vendored
View File

@@ -1,166 +0,0 @@
import { Profile, TokenSet, User } from "."
import { Awaitable } from "./internals/utils"
export type ProviderType = "oauth" | "email" | "credentials"
export interface CommonProviderOptions {
id: string
name: string
type: ProviderType
}
/**
* OAuth Provider
*/
type ProtectionType = "pkce" | "state" | "both" | "none"
/**
* OAuth provider options
*
* [Documentation](https://next-auth.js.org/configuration/providers#oauth-provider-options)
*/
export interface OAuthConfig<P extends Record<string, unknown> = Profile>
extends CommonProviderOptions {
authorizationParams?: Record<string, string>
headers?: Record<string, any>
type: "oauth"
version: string
scope: string
params: { grant_type: string }
accessTokenUrl: string
requestTokenUrl: string
authorizationUrl: string
profileUrl: string
profile(profile: P, tokens: TokenSet): Awaitable<User & { id: string }>
protection?: ProtectionType | ProtectionType[]
clientId: string
clientSecret:
| string
// TODO: only allow for Apple
| Record<"appleId" | "teamId" | "privateKey" | "keyId", string>
idToken?: boolean
/**
* @deprecated Will be removed in an upcoming major release. Use `protection: ["state"]` instead.
*/
state?: boolean
// 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
}
export type OAuthProviderType =
| "Apple"
| "Atlassian"
| "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>) => OAuthConfig
/**
* Credentials Provider
*/
interface CredentialInput {
label?: string
type?: string
value?: string
placeholder?: string
}
interface CredentialsConfig<C extends Record<string, CredentialInput> = {}>
extends CommonProviderOptions {
type: "credentials"
credentials: C
authorize(credentials: Record<keyof C, string>): Awaitable<User | null>
}
export type CredentialsProvider = (
options: Partial<CredentialsConfig>
) => CredentialsConfig
export type CredentialsProviderType = "Credentials"
/** Email Provider */
export interface EmailConfigServerOptions {
host: string
port: number
auth: {
user: string
pass: string
}
}
export interface EmailConfig extends CommonProviderOptions {
type: "email"
// TODO: Make use of https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html
server: string | EmailConfigServerOptions
from?: string
maxAge?: number
sendVerificationRequest(params: {
identifier: string
url: string
baseUrl: string
token: string
provider: EmailConfig
}): Awaitable<void>
}
export type EmailProvider = (options: Partial<EmailConfig>) => EmailConfig
// TODO: Rename to Token provider
// when started working on https://github.com/nextauthjs/next-auth/discussions/1465
export type EmailProviderType = "Email"
export type Provider = OAuthConfig | EmailConfig | CredentialsConfig
export type BuiltInProviders = Record<OAuthProviderType, OAuthProvider> &
Record<CredentialsProviderType, CredentialsProvider> &
Record<EmailProviderType, EmailProvider>
export type AppProviders = Array<
Provider | ReturnType<BuiltInProviders[keyof BuiltInProviders]>
>
declare const Providers: BuiltInProviders
export default Providers

View File

@@ -1,26 +0,0 @@
import Adapters from "next-auth/adapters"
// ExpectType TypeORMAdapter["Adapter"]
Adapters.Default({
type: "sqlite",
database: ":memory:",
synchronize: true,
})
// ExpectType TypeORMAdapter
Adapters.TypeORM.Adapter({
type: "sqlite",
database: ":memory:",
synchronize: true,
})
// ExpectType PrismaAdapter
Adapters.Prisma.Adapter({
prisma: {},
modelMapping: {
User: "foo",
Account: "bar",
Session: "session",
VerificationRequest: "foo",
},
})

View File

@@ -1,97 +0,0 @@
import * as client from "next-auth/client"
import { nextReq } from "./test-helpers"
const clientSession = {
user: {
name: "Bruce",
email: "bruce@lee.com",
image: "path/to/img",
},
accessToken: "123z",
expires: "1234",
}
// $ExpectType [Session | null, boolean]
client.useSession()
// $ExpectType Promise<Session | null>
client.getSession({ req: nextReq })
// $ExpectType Promise<Session | null>
client.session({ req: nextReq })
// $ExpectType Promise<Record<string, ClientSafeProvider> | null>
client.getProviders()
// $ExpectType Promise<Record<string, ClientSafeProvider> | null>
client.providers()
// $ExpectType Promise<string | null>
client.getCsrfToken({ req: nextReq })
// $ExpectType Promise<string | null>
client.csrfToken({ req: nextReq })
// $ExpectType Promise<string | null>
client.csrfToken({ ctx: { req: nextReq } })
// $ExpectType Promise<undefined>
client.signin("github", { callbackUrl: "foo" }, { login: "username" })
// $ExpectType Promise<SignInResponse | undefined>
client.signin("credentials", { callbackUrl: "foo", redirect: true })
// $ExpectType Promise<SignInResponse | undefined>
client.signin("credentials", { redirect: false })
// $ExpectType Promise<SignInResponse | undefined>
client.signin("email", { callbackUrl: "foo", redirect: false })
// $ExpectType Promise<SignInResponse | undefined>
client.signin("email", { callbackUrl: "foo", redirect: true })
// $ExpectType Promise<undefined>
client.signout()
// $ExpectType Promise<undefined>
client.signout({ callbackUrl: "https://foo.com/callback", redirect: true })
// $ExpectType Promise<SignOutResponse>
client.signOut({ callbackUrl: "https://foo.com/callback", redirect: false })
// $ExpectType ReactElement<any, any> | null
client.Provider({
children: null,
session: clientSession,
options: {
baseUrl: "https://foo.com",
basePath: "/",
clientMaxAge: 1234,
},
})
// $ExpectType ReactElement<any, any> | null
client.Provider({
children: null,
session: clientSession,
})
// $ExpectType ReactElement<any, any> | null
client.Provider({
children: null,
options: {},
})
// $ExpectType ReactElement<any, any> | null
client.Provider({
children: null,
session: {
expires: "",
},
options: {
baseUrl: "https://foo.com",
basePath: "/",
clientMaxAge: 1234,
keepAlive: 4321,
},
})

View File

@@ -1,26 +0,0 @@
import * as JWTType from "next-auth/jwt"
import { nextReq } from "./test-helpers"
// $ExpectType Promise<string>
JWTType.encode({
token: { key: "value" },
secret: "secret",
})
// $ExpectType Promise<JWT>
JWTType.decode({
token: "token",
secret: "secret",
})
// $ExpectType Promise<string>
JWTType.getToken({
req: nextReq,
raw: true,
})
// $ExpectType Promise<JWT | null>
JWTType.getToken({
req: nextReq,
secret: "secret",
})

View File

@@ -1,259 +0,0 @@
import Providers from "next-auth/providers"
// $ExpectType EmailConfig
Providers.Email({
server: "path/to/server",
from: "path/from",
})
// $ExpectType EmailConfig
Providers.Email({
server: {
host: "host",
port: 123,
auth: {
user: "foo",
pass: "123",
},
},
from: "path/from",
})
// $ExpectType CredentialsConfig<{}>
Providers.Credentials({
id: "login",
name: "account",
credentials: {
user: {
label: "Password",
type: "password",
},
password: {
label: "Password",
type: "password",
},
},
authorize: async (credentials) => {
const user = {
/* fetched user */
}
return user
},
})
// $ExpectType OAuthConfig<Profile>
Providers.Apple({
clientId: "foo123",
clientSecret: {
appleId: "foo@icloud.com",
teamId: "foo",
privateKey: "123xyz",
keyId: "1234",
},
})
// $ExpectType OAuthConfig<Profile>
Providers.Twitter({
clientId: "foo123",
clientSecret: "bar123",
})
// $ExpectType OAuthConfig<Profile>
Providers.Facebook({
clientId: "foo123",
clientSecret: "bar123",
})
// $ExpectType OAuthConfig<Profile>
Providers.GitHub({
clientId: "foo123",
clientSecret: "bar123",
})
// $ExpectType OAuthConfig<Profile>
Providers.GitHub({
clientId: "foo123",
clientSecret: "bar123",
scope: "change:thing read:that",
})
// $ExpectType OAuthConfig<Profile>
Providers.GitLab({
clientId: "foo123",
clientSecret: "bar123",
})
// $ExpectType OAuthConfig<Profile>
Providers.Slack({
clientId: "foo123",
clientSecret: "bar123",
})
// $ExpectType OAuthConfig<Profile>
Providers.Google({
clientId: "foo123",
clientSecret: "bar123",
})
// $ExpectType OAuthConfig<Profile>
Providers.Google({
clientId: "foo123",
clientSecret: "bar123",
authorizationUrl: "https://foo.google.com",
})
// $ExpectType OAuthConfig<Profile>
Providers.Auth0({
clientId: "foo123",
clientSecret: "bar123",
domain: "https://foo.auth0.com",
})
// $ExpectType OAuthConfig<Profile>
Providers.Auth0({
clientId: "foo123",
clientSecret: "bar123",
domain: "https://foo.auth0.com",
profile: () => ({
id: "foo123",
name: "foo",
email: "foo@bar.io",
image: "https://foo.auth0.com/image/1.png",
}),
})
// $ExpectType OAuthConfig<Profile>
Providers.IdentityServer4({
id: "identity-server4",
name: "IdentityServer4",
scope: "change:thing read:that",
domain: "https://foo.is4.com",
clientId: "foo123",
clientSecret: "bar123",
})
// $ExpectType OAuthConfig<Profile>
Providers.Discord({
clientId: "foo123",
clientSecret: "bar123",
scope: "identify",
})
// $ExpectType OAuthConfig<Profile>
Providers.Twitch({
clientId: "foo123",
clientSecret: "bar123",
})
// $ExpectType OAuthConfig<Profile>
Providers.Okta({
clientId: "foo123",
clientSecret: "bar123",
domain: "https://foo.auth0.com",
})
// $ExpectType OAuthConfig<Profile>
Providers.BattleNet({
clientId: "foo123",
clientSecret: "bar123",
region: "europe",
})
// $ExpectType OAuthConfig<Profile>
Providers.Box({
clientId: "foo123",
clientSecret: "bar123",
})
// $ExpectType OAuthConfig<Profile>
Providers.Cognito({
clientId: "foo123",
clientSecret: "bar123",
domain: "https://foo.auth0.com",
})
// $ExpectType OAuthConfig<Profile>
Providers.Yandex({
clientId: "foo123",
clientSecret: "bar123",
})
// $ExpectType OAuthConfig<Profile>
Providers.LinkedIn({
clientId: "foo123",
clientSecret: "bar123",
scope: "r_emailaddress r_liteprofile",
})
// $ExpectType OAuthConfig<Profile>
Providers.Spotify({
clientId: "foo123",
clientSecret: "bar123",
})
// $ExpectType OAuthConfig<Profile>
Providers.Spotify({
clientId: "foo123",
clientSecret: "bar123",
scope: "user-read-email",
})
// $ExpectType OAuthConfig<Profile>
Providers.Basecamp({
clientId: "foo123",
clientSecret: "bar123",
})
// $ExpectType OAuthConfig<Profile>
Providers.Reddit({
clientId: "foo123",
clientSecret: "bar123",
})
// $ExpectType OAuthConfig<Profile>
Providers.AzureADB2C({
clientId: "foo123",
clientSecret: "bar123",
scope: "offline_access User.Read",
tenantId: "tenantId",
idToken: true,
})
// $ExpectType OAuthConfig<Profile>
Providers.FusionAuth({
name: "FusionAuth",
domain: "domain",
clientId: "clientId",
clientSecret: "clientSecret",
tenantId: "tenantId",
})
// $ExpectType OAuthConfig<Profile>
Providers.FACEIT({
clientId: "foo123",
clientSecret: "bar123",
})
// $ExpectType OAuthConfig<Profile>
Providers.Instagram({
clientId: "foo123",
clientSecret: "bar123",
})
// $ExpectType OAuthConfig<Profile>
Providers.Kakao({
clientId: "foo123",
clientSecret: "bar123",
})
// $ExpectType OAuthConfig<Profile>
Providers.Osso({
clientId: "foo123",
clientSecret: "bar123",
})
// $ExpectType OAuthConfig<Profile>
Providers.Zoho({
clientId: "foo123",
clientSecret: "bar123",
})

View File

@@ -1,260 +0,0 @@
import Providers, { OAuthConfig } from "next-auth/providers"
import {
Adapter,
EmailAppProvider,
Profile,
Session,
VerificationRequest,
} from "next-auth/adapters"
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 "internals/utils"
import { AppOptions } from "internals"
import { AppProvider } from "internals/providers"
const req: NextApiRequest = Object.assign(new IncomingMessage(new Socket()), {
query: {},
cookies: {},
body: {},
env: {},
})
const res: NextApiResponse = Object.assign(new ServerResponse(req), {
send: (body: string) => undefined,
json: (body: string) => undefined,
status: (code: number) => res,
redirect: (statusOrUrl: number | string, url?: string) => res as any,
setPreviewData: (data: object | string) => res,
clearPreviewData: () => res,
})
const pageOptions = {
signin: "path/to/signin",
signout: "path/to/signout",
error: "path/to/error",
verifyRequest: "path/to/verify",
newUsers: "path/to/signup",
}
const simpleConfig = {
providers: [
Providers.GitHub({
clientId: "123",
clientSecret: "123",
scope:
"user public_repo repo repo_deployment repo:status read:repo_hook read:org read:public_key read:gpg_key",
}),
],
}
const exampleUser: NextAuthTypes.User = {
name: "",
image: "",
email: "",
}
const exampleSession: Session = {
userId: "",
accessToken: "",
sessionToken: "",
expires: new Date(),
}
const exampleVerificatoinRequest: VerificationRequest = {
identifier: "",
token: "",
expires: new Date(),
}
const adapter: Adapter<
NextAuthTypes.User,
Profile,
Session,
VerificationRequest
> = {
async getAdapter(appOptions: AppOptions) {
return {
createUser: async (profile: Profile) => exampleUser,
getUser: async (id: string) => exampleUser,
getUserByEmail: async (email: string) => exampleUser,
getUserByProviderAccountId: async (
providerId: string,
providerAccountId: string
) => exampleUser,
updateUser: async (user: NextAuthTypes.User) => exampleUser,
linkAccount: async (
userId: string,
providerId: string,
providerType: string,
providerAccountId: string,
refreshToken: string,
accessToken: string,
accessTokenExpires: number
) => undefined,
createSession: async (user: NextAuthTypes.User) => exampleSession,
getSession: async (sessionToken: string) => exampleSession,
updateSession: async (session: Session, force?: boolean) =>
exampleSession,
deleteSession: async (sessionToken: string) => undefined,
createVerificationRequest: async (
email: string,
url: string,
token: string,
secret: string,
provider: EmailAppProvider,
options: AppOptions
) => exampleVerificatoinRequest,
getVerificationRequest: async (
email: string,
verificationToken: string,
secret: string,
provider: AppProvider
) => exampleVerificatoinRequest,
deleteVerificationRequest: async (
email: string,
verificationToken: string,
secret: string,
provider: AppProvider
) => undefined,
}
},
}
const allConfig = {
providers: [
Providers.Twitter({
clientId: "123",
clientSecret: "123",
}),
],
database: "path/to/db",
debug: true,
secret: "my secret",
session: {
jwt: true,
maxAge: 365,
updateAge: 60,
},
jwt: {
secret: "secret-thing",
maxAge: 365,
encryption: true,
signingKey: "some-key",
encryptionKey: "some-key",
encode: async () => "foo",
decode: async () => ({}),
},
pages: pageOptions,
callbacks: {
async signIn(
user: NextAuthTypes.User,
account: Record<string, unknown>,
profile: Record<string, unknown>
) {
return true
},
async redirect(url: string, baseUrl: string) {
return "path/to/foo"
},
async session(
session: NextAuthTypes.Session,
userOrToken: NextAuthTypes.User
) {
return { ...session }
},
async jwt(
token: JWTType.JWT,
user?: NextAuthTypes.User,
account?: Record<string, unknown>,
profile?: Record<string, unknown>,
isNewUser?: boolean
) {
return token
},
},
events: {
async signIn(message: string) {
return undefined
},
async signOut(message: string) {
return undefined
},
async createUser(message: string) {
return undefined
},
async linkAccount(message: string) {
return undefined
},
async session(message: string) {
return undefined
},
async error(message: string) {
return undefined
},
},
adapter,
useSecureCookies: true,
cookies: {
sessionToken: {
name: "__Secure-next-auth.session-token",
options: {
httpOnly: true,
sameSite: true as true,
path: "/",
secure: true,
domain: "foo.com",
},
},
},
}
const customProvider: OAuthConfig<{
id: string
name: string
email: string
picture: string
}> = {
id: "google",
name: "Google",
type: "oauth",
version: "2.0",
scope:
"https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email",
params: { grant_type: "authorization_code" },
accessTokenUrl: "https://accounts.google.com/o/oauth2/token",
requestTokenUrl: "https://accounts.google.com/o/oauth2/auth",
authorizationUrl:
"https://accounts.google.com/o/oauth2/auth?response_type=code",
profileUrl: "https://www.googleapis.com/oauth2/v1/userinfo?alt=json",
async profile(profile, tokens) {
return {
id: profile.id,
name: profile.name,
email: profile.email,
image: profile.picture,
}
},
clientId: "",
clientSecret: "",
}
const customProviderConfig = {
providers: [customProvider],
}
// $ExpectType void | Promise<void>
NextAuth(simpleConfig)
// $ExpectType void | Promise<void>
NextAuth(allConfig)
// $ExpectType void | Promise<void>
NextAuth(customProviderConfig)
// $ExpectType void | Promise<void>
NextAuth(req, res, simpleConfig)
// $ExpectType void | Promise<void>
NextAuth(req, res, allConfig)

View File

@@ -1,13 +0,0 @@
import { IncomingMessage } from "http"
import { Socket } from "net"
import { NextApiRequest } from "internals/utils"
export const nextReq: NextApiRequest = Object.assign(
new IncomingMessage(new Socket()),
{
query: {},
cookies: {},
body: {},
env: {},
}
)

View File

@@ -1,22 +0,0 @@
{
"compilerOptions": {
"module": "commonjs",
"lib": ["es6", "dom"],
"jsx": "react",
"noImplicitAny": true,
"noImplicitThis": true,
"strictFunctionTypes": true,
"strictNullChecks": true,
"esModuleInterop": true,
"noEmit": true,
"forceConsistentCasingInFileNames": true,
"baseUrl": ".",
"paths": {
"next-auth": ["."],
"next-auth/providers": ["./providers"],
"next-auth/adapters": ["./adapters"],
"next-auth/client": ["./client"],
"next-auth/jwt": ["./jwt"]
}
}
}

Some files were not shown because too many files have changed in this diff Show More