Compare commits

..

12 Commits

Author SHA1 Message Date
Balázs Orbán
7335759b04 fix(ts): extract EmailConfigServerOptions to interface 2021-04-19 17:02:53 +02:00
Balázs Orbán
97c77ff44b refactor(ts): de-duplicate types (#1690)
* refactor(ts): deduplicate internal types

* refactor(ts): ease up providers typings

* test(ts): fix failing TS tests

* test(ts): rename TS property to fix test

* docs(ts): mention TS docs in README.md

* feat(ts): move/update client types

* refactor(TS): rename some types

* test(ts): fix client tests

* docs(ts): move function descriptions to .d.ts

* chore: fix lint error

* refactor(ts): separate internal types

* chore: simplify build-types script

* chore: update type import paths in src

* chore(build): create root files at build

* chore: remove unnecessary .npmignore

* chore: run prettier on types

* fix(ts): clean up jwt types

* fix(ts): make getToken return type depend on raw param

* docs(page): explain page errors, add theming note

* docs(ts): add JSDoc to NextAuthOptions props

* chore(ts): remove unused import

* docs(ts): change JSDOC docs notation

* refactor(build): extract module entries into enum

* chore(ts): move ClientSafeProvider

* chore(ts): simplify GetTokenParams generic

* style(lint): fix linting errors

* chore: re-add generic extension to GetTokenParams
2021-04-19 16:54:03 +02:00
Nico Domino
b6963abda7 docs(lint): update contributing.md (#1760)
Regarding ESLint / Prettier use and link to their VSCode extensions
2021-04-18 13:47:02 +02:00
Nico Domino
bdb12adb28 style: replace ts-standard with eslint/prettier (#1724)
* style: move from ts-standard to eslint/prettier

* fix: install remaining eslint-config-standard peer deps

* fix: add remaining missing dependencies/config

Co-authored-by: Balázs Orbán <info@balazsorban.com>
2021-04-14 20:22:51 +02:00
Balázs Orbán
a1e30507c2 feat(ts): support module augmentation (#1681)
* chore(ts): remove unused imports

* refactor(ts): clean up CallbackOptions

* docs(ts): explain Module Augmentation

* docs(ts): don't use @ in folder name "types"

* test(ts): make jwt params optional

* docs(ts): fix typo (TypeScript -> NextAuth.js)
2021-04-10 23:48:26 +02:00
Balázs Orbán
2c4fce3699 fix(build): fix release 2021-04-09 21:26:00 +02:00
Balázs Orbán
8fa71512d1 fix(built): typo in package.json 2021-04-09 21:20:41 +02:00
Balázs Orbán
d420eeff9d fix(ts): add .d.ts sub-module files to package.json
#1677 seemed to miss this
2021-04-09 21:10:43 +02:00
Lluis Agusti
0d863d38bc fix(ts): correctly export sub-module types (#1677)
* chore(types): build types script

Adds a script that moves the declaration files we have in `./types` to `./dist` relative to the files they intend to type.

This is the first step, we still need to change what we declare in `package.json`, add the script to the CI pipeline if we're happy with it and figure out how to type `next-auth/jwt`.

* refactor(lint): fix build-types script
2021-04-09 20:28:11 +02:00
Lluis Agusti
6f9f42a85b chore(ci): fix typo on types workflow 2021-04-07 17:05:48 +02:00
Lluis Agusti
2160be2a8a feat(ts): expose types from the package (#1665)
* chore(types): move existing types to the repo
* feat(ts): expose types from the main package
* chore(deps): bring back `react-dom` version range
* chore(ts): cleanup deps and comments
* chore(ci): run types tests on a separate workflow
2021-04-07 17:03:17 +02:00
Balázs Orbán
55eb066793 chore: add beta to release flow/GH actions 2021-04-04 22:08:25 +02:00
107 changed files with 798 additions and 1219 deletions

6
.github/labeler.yml vendored
View File

@@ -1,6 +1,5 @@
test:
- test/**/*
- types/tests/**/*
documentation:
- www/**/*
@@ -33,7 +32,4 @@ client:
pages:
- src/server/pages/**/*
- www/docs/configuration/pages.md
TypeScript:
- types/**/*
- www/docs/configuration/pages.md

View File

@@ -2,14 +2,14 @@ name: Release
on:
push:
branches:
- "main"
- "beta"
- "next"
- "3.x"
- 'main'
- 'beta'
- 'next'
- '3.x'
pull_request:
jobs:
release:
name: "Release"
name: 'Release'
runs-on: ubuntu-latest
steps:
- name: Checkout
@@ -20,6 +20,7 @@ jobs:
node-version: 14
- name: Install dependencies
uses: bahmutov/npm-install@v1
- run: npm run build
- run: npx semantic-release@17
env:
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}

20
.gitignore vendored
View File

@@ -27,24 +27,6 @@ node_modules
.cache-loader
.next
www/providers.json
src/providers/index.js
internals
adapters.d.ts
adapters.js
client.d.ts
client.js
index.d.ts
index.js
jwt.d.ts
jwt.js
providers.d.ts
providers.js
# Development app
app/next-auth
app/dist/css
app/package-lock.json
app/yarn.lock
# VS
/.vs/slnx.sqlite-journal
@@ -57,4 +39,4 @@ app/yarn.lock
/_work
# Prisma migrations
/prisma/migrations
/prisma/migrations

View File

@@ -32,17 +32,17 @@ cd next-auth
2. Install packages:
```sh
npm i && npm dev:setup
npm i
```
3. Populate `.env.local`:
Copy `app/.env.local.example` to `app/.env.local`, and add your env variables for each provider you want to test.
Copy `.env.local.example` to `.env.local`, and add your env variables for each provider you want to test.
> NOTE: You can add any environment variables to .env.local that you would like to use in your dev app.
> You can find the next-auth config under`app/pages/api/auth/[...nextauth].js`.
> You can find the next-auth config under`pages/api/auth/[...nextauth].js`.
1. Start the dev application/server:
1. Start the dev application/server and CSS watching:
```sh
npm run dev
```
@@ -57,23 +57,11 @@ If you need an example project to link to, you can use [next-auth-example](https
When running `npm run dev`, you start a Next.js dev server on `http://localhost:3000`, which includes hot reloading out of the box. Make changes on any of the files in `src` and see the changes immediately.
> NOTE: When working on CSS, you will have to manually refresh the page after changes. The reason for this is our pages using CSS are server-side rendered. (Improving this through a PR is very welcome!)
> NOTE: The setup is as follows: The development application lives inside the `app` folder, and whenever you make a change to the `src` folder in the root (where next-auth is), it gets copied into `app` every time (gitignored), so Next.js can pick them up and apply hot reloading. This is to avoid some annoying issues with how symlinks are working with different React builds, and also to provide a super-fast feedback loop while developing core features.
#### 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.
>NOTE: When working on CSS, you will need to manually refresh the page after changes. (Improving this through a PR is very welcome!)
#### Databases
Included is a Docker Compose file that starts up MySQL, PostgreSQL, and MongoDB databases on localhost.
Included is a Docker Compose file that starts up MySQL, Postgres, and MongoDB databases on localhost.
It will use port `3306`, `5432`, and `27017` on localhost respectively; please make sure those ports are not used by other services on localhost.

View File

@@ -1,6 +0,0 @@
# NextAuth.js Development App
This folder contains a Next.js app using NextAuth.js for local development. See the following section on how to start:
[Setting up local environment
](https://github.com/nextauthjs/next-auth/blob/main/CONTRIBUTING.md#setting-up-local-environment)

View File

@@ -1,5 +0,0 @@
{
"compilerOptions": {
"baseUrl": "."
}
}

View File

@@ -1,19 +0,0 @@
const path = require("path")
module.exports = {
webpack(config) {
config.resolve = {
...config.resolve,
alias: {
...config.resolve.alias,
"next-auth$": path.join(process.cwd(), "next-auth/server"),
"next-auth/client$": path.join(process.cwd(), "next-auth/client"),
"next-auth/jwt$": path.join(process.cwd(), "next-auth/lib/jwt"),
"next-auth/adapters": path.join(process.cwd(), "next-auth/adapters"),
"next-auth/providers": path.join(process.cwd(), "next-auth/providers"),
},
}
return config
},
}

View File

@@ -1,25 +0,0 @@
{
"name": "next-auth-app",
"version": "1.0.0",
"description": "NextAuth.js Developer app",
"private": true,
"scripts": {
"dev": "npm-run-all --parallel copy:app dev:css dev:next",
"dev:next": "next dev",
"copy:app": "cpx \"../src/**/*\" next-auth --watch",
"copy:css": "cpx \"../dist/css/**/*\" dist/css --watch",
"watch:css": "cd .. && npm run watch:css",
"dev:css": "npm-run-all --parallel watch:css copy:css",
"start": "next start"
},
"license": "ISC",
"dependencies": {
"next": "^10.1.3",
"react": "^17.0.2",
"react-dom": "^17.0.2"
},
"devDependencies": {
"cpx": "^1.5.0",
"npm-run-all": "^4.1.5"
}
}

View File

@@ -9,8 +9,6 @@ const MODULE_ENTRIES = {
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",
@@ -26,8 +24,6 @@ Object.entries(BUILD_TARGETS).forEach(([target, content]) => {
})
})
// Building types
const TYPES_TARGETS = [
`${MODULE_ENTRIES.SERVER}.d.ts`,
`${MODULE_ENTRIES.CLIENT}.d.ts`,
@@ -47,42 +43,3 @@ TYPES_TARGETS.forEach((target) => {
}
)
})
// Building providers
const providersDir = path.join(process.cwd(), "/src/providers")
const files = fs
.readdirSync(providersDir, "utf8")
.filter((file) => file !== "index.js")
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")
)

View File

58
package-lock.json generated
View File

@@ -4332,12 +4332,6 @@
"yallist": "^3.0.2"
}
},
"y18n": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz",
"integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==",
"dev": true
},
"yallist": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
@@ -4698,6 +4692,11 @@
"strip-ansi": "^6.0.0"
}
},
"y18n": {
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.5.tgz",
"integrity": "sha512-hsRUr4FFrvhhRH12wOdfs38Gy7k2FFzB9qgN9v3aLykRq0dRcdcpz5C9FxdS2NuhOrI/628b/KSTJ3rwHysYSg=="
},
"yargs": {
"version": "16.2.0",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
@@ -6074,12 +6073,6 @@
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
"dev": true
},
"y18n": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
"integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
"dev": true
},
"yargs": {
"version": "15.4.1",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
@@ -8437,9 +8430,9 @@
}
},
"jose": {
"version": "1.28.1",
"resolved": "https://registry.npmjs.org/jose/-/jose-1.28.1.tgz",
"integrity": "sha512-6JK28rFu5ENp/yxMwM+iN7YeaInnY9B9Bggjkz5fuwLiJhbVrl2O4SJr65bdNBPl9y27fdC3Mymh+FVCvozLIg==",
"version": "1.28.0",
"resolved": "https://registry.npmjs.org/jose/-/jose-1.28.0.tgz",
"integrity": "sha512-JmfDRzt/HSj8ipd9TsDtEHoLUnLYavG+7e8F6s1mx2jfVSfXOTaFQsJUydbjJpTnTDHP1+yKL9Ke7ktS/a0Eiw==",
"requires": {
"@panva/asn1.js": "^1.0.0"
}
@@ -13472,9 +13465,8 @@
"dev": true
},
"y18n": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz",
"integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==",
"version": "4.0.0",
"bundled": true,
"dev": true
},
"yallist": {
@@ -14518,12 +14510,6 @@
"is-number": "^7.0.0"
}
},
"y18n": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz",
"integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==",
"dev": true
},
"yargs": {
"version": "15.3.1",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.3.1.tgz",
@@ -17843,6 +17829,11 @@
"strip-ansi": "^6.0.0"
}
},
"y18n": {
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.5.tgz",
"integrity": "sha512-hsRUr4FFrvhhRH12wOdfs38Gy7k2FFzB9qgN9v3aLykRq0dRcdcpz5C9FxdS2NuhOrI/628b/KSTJ3rwHysYSg=="
},
"yargs": {
"version": "16.2.0",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
@@ -18674,9 +18665,10 @@
"dev": true
},
"y18n": {
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.5.tgz",
"integrity": "sha512-hsRUr4FFrvhhRH12wOdfs38Gy7k2FFzB9qgN9v3aLykRq0dRcdcpz5C9FxdS2NuhOrI/628b/KSTJ3rwHysYSg=="
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
"integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==",
"dev": true
},
"yallist": {
"version": "4.0.0",
@@ -18843,12 +18835,6 @@
"strip-ansi": "^5.0.0"
}
},
"y18n": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz",
"integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==",
"dev": true
},
"yargs-parser": {
"version": "13.1.2",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz",
@@ -18978,12 +18964,6 @@
"strip-ansi": "^5.0.0"
}
},
"y18n": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz",
"integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==",
"dev": true
},
"yargs": {
"version": "14.2.3",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-14.2.3.tgz",

View File

@@ -6,22 +6,11 @@
"repository": "https://github.com/nextauthjs/next-auth.git",
"author": "Iain Collins <me@iaincollins.com>",
"main": "index.js",
"types": "./index.d.ts",
"keywords": ["react", "nodejs", "oauth", "jwt", "oauth2", "authentication", "nextjs", "csrf", "oidc", "nextauth"],
"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"
},
"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 && node ./config/build.js",
"build:css": "postcss --config config/postcss.config.js src/**/*.css --base src --dir dist && node config/wrap-css.js",
"dev:setup": "npm run build:css && cd app && npm i",
"dev": "cd app && npm run dev",
"dev": "next | npm run watch:css",
"watch": "npm run watch:js | npm run watch:css",
"watch:js": "babel --config-file ./config/babel.config.json --watch src --out-dir dist",
"watch:css": "postcss --config config/postcss.config.js --watch src/**/*.css --base src --dir dist",
@@ -153,14 +142,6 @@
"fetch": "readonly"
}
},
"release": {
"branches": [
"+([0-9])?(.{+([0-9]),x}).x",
"main",
{ "name": "beta", "prerelease": true },
{ "name": "next", "prerelease": true }
]
},
"funding": [
{
"type": "github",

View File

@@ -1,9 +1,9 @@
import { Provider } from "next-auth/client"
import "./styles.css"
import { Provider } from 'next-auth/client'
import './styles.css'
// Use the <Provider> to improve performance and allow components that call
// `useSession()` anywhere in your application to access the `session` object.
export default function App({ Component, pageProps }) {
export default function App ({ Component, pageProps }) {
return (
<Provider
// Provider options are not required but can be useful in situations where
@@ -21,7 +21,7 @@ export default function App({ Component, pageProps }) {
//
// Note: If a session has expired when keep alive is triggered, all open
// windows / tabs will be updated to reflect the user is signed out.
keepAlive: 0,
keepAlive: 0
}}
session={pageProps.session}
>

View File

@@ -6,27 +6,6 @@ import Providers from 'next-auth/providers'
// const prisma = new PrismaClient()
export default NextAuth({
// Used to debug https://github.com/nextauthjs/next-auth/issues/1664
// cookies: {
// csrfToken: {
// name: 'next-auth.csrf-token',
// options: {
// httpOnly: true,
// sameSite: 'none',
// path: '/',
// secure: true
// }
// },
// pkceCodeVerifier: {
// name: 'next-auth.pkce.code_verifier',
// options: {
// httpOnly: true,
// sameSite: 'none',
// path: '/',
// secure: true
// }
// }
// },
providers: [
Providers.Email({
server: process.env.EMAIL_SERVER,
@@ -40,11 +19,6 @@ export default NextAuth({
clientId: process.env.AUTH0_ID,
clientSecret: process.env.AUTH0_SECRET,
domain: process.env.AUTH0_DOMAIN,
// Used to debug https://github.com/nextauthjs/next-auth/issues/1664
// protection: ["pkce", "state"],
// authorizationParams: {
// response_mode: 'form_post'
// }
protection: 'pkce'
}),
Providers.Twitter({

8
release.config.js Normal file
View File

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

View File

@@ -145,7 +145,7 @@ export async function getSession (ctx) {
return session
}
export async function getCsrfToken (ctx) {
async function getCsrfToken (ctx) {
return (await _fetchData('csrf', ctx))?.csrfToken
}

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
}

View File

@@ -1,23 +1,26 @@
/** @type {import("types").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
)
}
}
/**
@@ -25,7 +28,7 @@ const _logger = {
* Any `undefined` level will use the default logger.
* @param {Partial<import("types").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
@@ -39,13 +42,13 @@ export default _logger
* @param {string} basePath
* @return {import("types").LoggerInstance}
*/
export function proxyLogger(logger = _logger, basePath) {
export function proxyLogger (logger = _logger, basePath) {
try {
if (typeof window === "undefined") {
if (typeof window === 'undefined') {
return logger
}
const clientLogger = {}
const clientLogger = console
for (const level in logger) {
clientLogger[level] = (code, ...message) => {
_logger[level](code, ...message) // Log on client as usual
@@ -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,54 +1,48 @@
import nodemailer from "nodemailer"
import logger from "../lib/logger"
import nodemailer from 'nodemailer'
import logger from '../lib/logger'
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,
}) => {
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?:\/\//, "")
const site = baseUrl.replace(/^https?:\/\//, '')
nodemailer.createTransport(server).sendMail(
{
nodemailer
.createTransport(server)
.sendMail({
to: email,
from,
subject: `Sign in to ${site}`,
text: text({ url, site, email }),
html: html({ url, site, email }),
},
(error) => {
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))
logger.error('SEND_VERIFICATION_EMAIL_ERROR', email, error)
return reject(new Error('SEND_VERIFICATION_EMAIL_ERROR', error))
}
return resolve()
}
)
})
})
}
@@ -58,16 +52,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,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
}
}

View File

@@ -6,12 +6,12 @@ import * as cookie from './lib/cookie'
import * as defaultEvents from './lib/default-events'
import * as defaultCallbacks from './lib/default-callbacks'
import parseProviders from './lib/providers'
import callbackUrlHandler from './lib/callback-url-handler'
import extendRes from './lib/extend-req'
import * as routes from './routes'
import renderPage from './pages'
import createSecret from './lib/create-secret'
import callbackUrlHandler from './lib/callback-url-handler'
import extendRes from './lib/extend-res'
import csrfTokenHandler from './lib/csrf-token-handler'
import createSecret from './lib/create-secret'
import * as pkce from './lib/oauth/pkce-handler'
import * as state from './lib/oauth/state-handler'
@@ -67,18 +67,16 @@ async function NextAuthHandler (req, res, userOptions) {
const secret = createSecret({ userOptions, basePath, baseUrl })
const { csrfToken, csrfTokenVerified } = csrfTokenHandler(req, res, cookies, secret)
const providers = parseProviders({ providers: userOptions.providers, baseUrl, basePath })
const provider = providers.find(({ id }) => id === providerId)
// 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) {
// 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') {
provider.protection = [provider.protection]
}
if (provider &&
provider.type === 'oauth' && provider.version?.startsWith('2') &&
(!provider.protection && provider.state !== false)
) {
provider.protection = 'state' // Default to state, as we did in 3.1 REVIEW: should we use "pkce" or "none" as default?
}
const maxAge = 30 * 24 * 60 * 60 // Sessions expire after 30 days of being idle
@@ -105,6 +103,7 @@ async function NextAuthHandler (req, res, userOptions) {
provider,
cookies,
secret,
csrfToken,
providers,
// Session options
session: {
@@ -135,7 +134,6 @@ async function NextAuthHandler (req, res, userOptions) {
logger
}
csrfTokenHandler(req, res)
await callbackUrlHandler(req, res)
const render = renderPage(req, res)
@@ -148,7 +146,7 @@ async function NextAuthHandler (req, res, userOptions) {
case 'session':
return routes.session(req, res)
case 'csrf':
return res.json({ csrfToken: req.options.csrfToken })
return res.json({ csrfToken })
case 'signin':
if (pages.signIn) {
let signinUrl = `${pages.signIn}${pages.signIn.includes('?') ? '&' : '?'}callbackUrl=${req.options.callbackUrl}`
@@ -201,7 +199,7 @@ async function NextAuthHandler (req, res, userOptions) {
switch (action) {
case 'signin':
// Verified CSRF Token required for all sign in routes
if (req.options.csrfTokenVerified && provider) {
if (csrfTokenVerified && provider) {
if (await pkce.handleSignin(req, res)) return
if (await state.handleSignin(req, res)) return
return routes.signin(req, res)
@@ -210,14 +208,14 @@ async function NextAuthHandler (req, res, userOptions) {
return res.redirect(`${baseUrl}${basePath}/signin?csrf=true`)
case 'signout':
// Verified CSRF Token required for signout
if (req.options.csrfTokenVerified) {
if (csrfTokenVerified) {
return routes.signout(req, res)
}
return res.redirect(`${baseUrl}${basePath}/signout?csrf=true`)
case 'callback':
if (provider) {
// Verified CSRF Token required for credentials providers only
if (provider.type === 'credentials' && !req.options.csrfTokenVerified) {
if (provider.type === 'credentials' && !csrfTokenVerified) {
return res.redirect(`${baseUrl}${basePath}/signin?csrf=true`)
}

View File

@@ -14,30 +14,29 @@ import * as cookie from './cookie'
* For more details, see the following OWASP links:
* https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#double-submit-cookie
* https://owasp.org/www-chapter-london/assets/slides/David_Johansson-Double_Defeat_of_Double-Submit_Cookie.pdf
* @param {import("..").NextAuthRequest} req
* @param {import("..").NextAuthResponse} res
*/
export default function csrfTokenHandler (req, res) {
const { cookies, secret } = req.options
if (cookies.csrfToken.name in req.cookies) {
const [csrfToken, csrfTokenHash] = req.cookies[cookies.csrfToken.name].split('|')
const expectedCsrfTokenHash = createHash('sha256').update(`${csrfToken}${secret}`).digest('hex')
if (csrfTokenHash === expectedCsrfTokenHash) {
export default function csrfTokenHandler (req, res, cookies, secret) {
const { csrfToken: csrfTokenFromRequest } = req.body
let csrfTokenFromCookie
let csrfTokenVerified = false
if (req.cookies[cookies.csrfToken.name]) {
const [csrfTokenValue, csrfTokenHash] = req.cookies[cookies.csrfToken.name].split('|')
if (csrfTokenHash === createHash('sha256').update(`${csrfTokenValue}${secret}`).digest('hex')) {
// If hash matches then we trust the CSRF token value
// If this is a POST request and the CSRF Token in the POST request matches
// the cookie we have already verified is the one we have set, then the token is verified!
const csrfTokenVerified = req.method === 'POST' && csrfToken === req.body.csrfToken
req.options.csrfToken = csrfToken
req.options.csrfTokenVerified = csrfTokenVerified
return
csrfTokenFromCookie = csrfTokenValue
// If this is a POST request and the CSRF Token in the Post request matches
// the cookie we have already verified is one we have set, then token is verified!
if (req.method === 'POST' && csrfTokenFromCookie === csrfTokenFromRequest) { csrfTokenVerified = true }
}
}
// If no csrfToken from cookie - because it's not been set yet,
// or because the hash doesn't match (e.g. because it's been modifed or because the secret has changed)
// create a new token.
const csrfToken = randomBytes(32).toString('hex')
const csrfTokenHash = createHash('sha256').update(`${csrfToken}${secret}`).digest('hex')
const csrfTokenCookie = `${csrfToken}|${csrfTokenHash}`
cookie.set(res, cookies.csrfToken.name, csrfTokenCookie, cookies.csrfToken.options)
req.options.csrfToken = csrfToken
if (!csrfTokenFromCookie) {
// If no csrfToken - because it's not been set yet, or because the hash doesn't match
// (e.g. because it's been modifed or because the secret has changed) create a new token.
csrfTokenFromCookie = randomBytes(32).toString('hex')
const newCsrfTokenCookie = `${csrfTokenFromCookie}|${createHash('sha256').update(`${csrfTokenFromCookie}${secret}`).digest('hex')}`
cookie.set(res, cookies.csrfToken.name, newCsrfTokenCookie, cookies.csrfToken.options)
}
return { csrfToken: csrfTokenFromCookie, csrfTokenVerified }
}

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) {
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
}
}
@@ -93,27 +81,15 @@ export default async function oAuthCallback(req) {
* Returns profile, raw profile and auth provider details
* @param {{
* profileData: object | string
* tokens: {
* accessToken: string
* idToken?: string
* refreshToken?: string
* access_token: string
* expires_in?: string | Date | null
* refresh_token?: string
* id_token?: string
* token?: string
* token_secret?: string
* tokenSecret?: string
* params?: any
* }
* provider: import("../..").Provider
* tokens: import("types").TokenSet
* provider: import("types/providers").OAuthConfig
* 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 +98,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 +123,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

@@ -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)
})
})
}
@@ -149,7 +136,7 @@ async function getOAuth2AccessToken (code, provider, codeVerifier) {
headers.Authorization = `Bearer ${code}`
}
if (provider.protection.includes('pkce')) {
if (provider.protection === 'pkce') {
params.code_verifier = codeVerifier
}

View File

@@ -16,8 +16,7 @@ const PKCE_MAX_AGE = 60 * 15 // 15 minutes in seconds
export async function handleCallback (req, res) {
const { cookies, provider, baseUrl, basePath } = req.options
try {
// Provider does not support PKCE, nothing to do.
if (!provider.protection?.includes('pkce')) {
if (provider.protection !== 'pkce') { // Provider does not support PKCE, nothing to do.
return
}
@@ -51,7 +50,7 @@ export async function handleCallback (req, res) {
export async function handleSignin (req, res) {
const { cookies, provider, baseUrl, basePath } = req.options
try {
if (!provider.protection?.includes('pkce')) { // Provider does not support PKCE, nothing to do.
if (provider.protection !== 'pkce') { // Provider does not support PKCE, nothing to do.
return
}
// Started login flow, add generated pkce to req.options and (encrypted) code_verifier to a cookie

View File

@@ -12,12 +12,11 @@ import { OAuthCallbackError } from '../../../lib/errors'
export async function handleCallback (req, res) {
const { csrfToken, provider, baseUrl, basePath } = req.options
try {
// Provider does not support state, nothing to do.
if (!provider.protection?.includes('state')) {
if (provider.protection !== 'state') { // Provider does not support state, nothing to do.
return
}
const state = req.query.state || req.body.state
const { state } = req.query
const expectedState = createHash('sha256').update(csrfToken).digest('hex')
logger.debug(
@@ -42,7 +41,7 @@ export async function handleCallback (req, res) {
export async function handleSignin (req, res) {
const { provider, baseUrl, basePath, csrfToken } = req.options
try {
if (!provider.protection?.includes('state')) { // Provider does not support state, nothing to do.
if (provider.protection !== 'state') { // Provider does not support state, nothing to do.
return
}

View File

@@ -5,17 +5,13 @@ import logger from '../../../lib/logger'
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

@@ -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

@@ -262,8 +262,7 @@ export default async function callback (req, res) {
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)

4
types/adapters.d.ts vendored
View File

@@ -1,7 +1,7 @@
import { AppOptions } from "./internals"
import { AppOptions } from "internals"
import { ConnectionOptions, EntitySchema } from "typeorm"
import { User } from "."
import { AppProvider } from "./internals/providers"
import { AppProvider } from "internals/providers"
export interface Profile {
id: string

122
types/index.d.ts vendored
View File

@@ -11,7 +11,7 @@ import {
NextApiRequest,
NextApiResponse,
NextApiHandler,
} from "./internals/utils"
} from "internals/utils"
/**
* Configure your NextAuth instance
@@ -26,7 +26,7 @@ export interface NextAuthOptions {
* * **Default value**: `[]`
* * **Required**: *Yes*
*
* [Documentation](https://next-auth.js.org/configuration/options#providers) | [Providers documentation](https://next-auth.js.org/configuration/providers)
* [Documentation](https://next-auth.js.org/configuration/options#providers) | [Providers documentaion](https://next-auth.js.org/configuration/providers)
*/
providers: AppProviders
/**
@@ -38,9 +38,9 @@ export interface NextAuthOptions {
*/
database?: string | Record<string, any> | ConnectionOptions
/**
* A random string used to hash tokens, sign cookies and generate cryptographic keys.
* A random string used to hash tokens, sign cookies and generate crytographic keys.
* If not specified is uses a hash of all configuration options, including Client ID / Secrets for entropy.
* The default behavior is volatile, and **it is strongly recommended** you explicitly specify a value
* The default behaviour is volatile, and **it is strongly recommended** you explicitly specify a value
* to avoid invalidating end user sessions when configuration changes are deployed.
* * **Default value**: `string` (SHA hash of the "options" object)
* * **Required**: No - **but strongly recommended**!
@@ -51,7 +51,7 @@ export interface NextAuthOptions {
/**
* 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
* * **Default value**: See the documentaion page
* * **Required**: No
*
* [Documentation](https://next-auth.js.org/configuration/options#session)
@@ -86,17 +86,17 @@ export interface NextAuthOptions {
* }
* ```
*
* [Documentation](https://next-auth.js.org/configuration/options#pages) | [Pages documentation](https://next-auth.js.org/configuration/pages)
* [Documentation](https://next-auth.js.org/configuration/options#pages) | [Pages documentaion](https://next-auth.js.org/configuration/pages)
*/
pages?: PagesOptions
/**
* Callbacks are asynchronous functions you can use to control what happens when an action is performed.
* Callbacks are *extremely powerful*, especially in scenarios involving JSON Web Tokens
* as they **allow you to implement access controls without a database** and to **integrate with external databases or APIs**.
* * **Default value**: See the Callbacks documentation
* * **Default value**: See the Callbacks documentaion
* * **Required**: *No*
*
* [Documentation](https://next-auth.js.org/configuration/options#callbacks) | [Callbacks documentation](https://next-auth.js.org/configuration/callbacks)
* [Documentation](https://next-auth.js.org/configuration/options#callbacks) | [Callbacks documentaion](https://next-auth.js.org/configuration/callbacks)
*/
callbacks?: CallbacksOptions
/**
@@ -109,7 +109,7 @@ export interface NextAuthOptions {
* * **Default value**: `{}`
* * **Required**: *No*
*
* [Documentation](https://next-auth.js.org/configuration/options#events) | [Events documentation](https://next-auth.js.org/configuration/events)
* [Documentation](https://next-auth.js.org/configuration/options#events) | [Events documentaion](https://next-auth.js.org/configuration/events)
*/
events?: EventsOptions
/**
@@ -135,7 +135,7 @@ export interface NextAuthOptions {
*
* - ⚠ 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)
* [Documentation](https://next-auth.js.org/configuration/options#debug) | [Logger documentaion](https://next-auth.js.org/configuration/options#logger)
*/
debug?: boolean
/**
@@ -166,8 +166,7 @@ export interface NextAuthOptions {
*
* - ⚠ 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)
* [Documentation](https://next-auth.js.org/configuration/options#logger) | [Debug documentaion](https://next-auth.js.org/configuration/options#debug)
*/
logger?: LoggerInstance
/**
@@ -183,7 +182,7 @@ export interface NextAuthOptions {
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.
* 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
@@ -200,7 +199,7 @@ export interface NextAuthOptions {
* 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
* If you use this feature, you will likely want to create conditional behaviour
* to support setting different cookies policies in development and production builds,
* as you will be opting out of the built-in dynamic policy.
* * **Default value**: `{}`
@@ -215,22 +214,12 @@ export interface NextAuthOptions {
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
@@ -241,17 +230,12 @@ export interface TokenSet {
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
@@ -259,54 +243,13 @@ export interface Profile extends Record<string, unknown> {
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,
@@ -316,7 +259,6 @@ export interface CallbacksOptions<
): Awaitable<JWT>
}
/** [Documentation](https://next-auth.js.org/configuration/options#cookies) */
export interface CookieOption {
name: string
options: {
@@ -329,7 +271,6 @@ export interface CookieOption {
}
}
/** [Documentation](https://next-auth.js.org/configuration/options#cookies) */
export interface CookiesOptions {
sessionToken?: CookieOption
callbackUrl?: CookieOption
@@ -337,7 +278,6 @@ export interface CookiesOptions {
pkceCodeVerifier?: CookieOption
}
/** [Documentation](https://next-auth.js.org/configuration/events) */
export type EventType =
| "signIn"
| "signOut"
@@ -347,13 +287,10 @@ export type EventType =
| "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
@@ -364,43 +301,18 @@ export interface PagesOptions {
newUser?: string
}
export interface DefaultSession extends Record<string, unknown> {
user?: {
name?: string | null
email?: string | null
image?: string | null
}
expires?: string
export interface Session extends Record<string, unknown> {
user?: User
accessToken?: string
expires: 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>, DefaultSession {}
/** [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

View File

@@ -1,6 +1,6 @@
import { NextApiRequest, NextApiResponse } from "./utils"
import { NextApiRequest, NextApiResponse } from "internals/utils"
import { NextAuthOptions } from ".."
import { AppProvider } from "./providers"
import { AppProvider } from "internals/providers"
/** Options that are the same both in internal and user provided options. */
export type NextAuthSharedOptions =
@@ -40,7 +40,6 @@ export interface AppOptions
| "verify-request"
| "error"
csrfToken?: string
csrfTokenVerified?: boolean
}
export interface NextAuthRequest extends NextApiRequest {

View File

@@ -1,4 +1,4 @@
import { CommonProviderOptions } from "../providers"
import { CommonProviderOptions } from "next-auth/providers"
export interface AppProvider extends CommonProviderOptions {
signinUrl: string

15
types/jwt.d.ts vendored
View File

@@ -1,20 +1,12 @@
import { JWT as JoseJWT, JWE } from "jose"
import { NextApiRequest } from "./internals/utils"
import { NextApiRequest } from "internals/utils"
export interface DefaultJWT extends Record<string, unknown> {
export interface JWT extends Record<string, unknown> {
name?: string | null
email?: string | null
picture?: string | null
sub?: string
}
/**
* Returned by the `jwt` callback and `getToken`, when using JWT sessions
*
* [`jwt` callback](https://next-auth.js.org/configuration/callbacks#jwt-callback) | [`getToken`](https://next-auth.js.org/tutorials/securing-pages-and-api-routes#using-gettoken)
*/
export interface JWT extends Record<string, unknown>, DefaultJWT {}
export interface JWTEncodeParams {
token?: JWT
maxAge?: number
@@ -52,10 +44,9 @@ export type GetTokenParams<R extends boolean = false> = {
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>
): Promise<R extends true ? string : JWT>
export interface JWTOptions {
secret?: string

View File

@@ -1,5 +1,5 @@
import { Profile, TokenSet, User } from "."
import { Awaitable } from "./internals/utils"
import { Awaitable } from "internals/utils"
export type ProviderType = "oauth" | "email" | "credentials"
@@ -55,7 +55,7 @@ export interface OAuthConfig<P extends Record<string, unknown> = Profile>
export type OAuthProviderType =
| "Apple"
| "Atlassian"
| "Attlassian"
| "Auth0"
| "AzureADB2C"
| "Basecamp"
@@ -144,7 +144,6 @@ export interface EmailConfig extends CommonProviderOptions {
provider: EmailConfig
}): Awaitable<void>
}
export type EmailProvider = (options: Partial<EmailConfig>) => EmailConfig
// TODO: Rename to Token provider

View File

@@ -19,7 +19,7 @@ JWTType.getToken({
raw: true,
})
// $ExpectType Promise<JWT | null>
// $ExpectType Promise<JWT>
JWTType.getToken({
req: nextReq,
secret: "secret",

View File

@@ -16,7 +16,8 @@
"next-auth/providers": ["./providers"],
"next-auth/adapters": ["./adapters"],
"next-auth/client": ["./client"],
"next-auth/jwt": ["./jwt"]
"next-auth/jwt": ["./jwt"],
"internals": ["./internals"]
}
}
}

View File

@@ -79,7 +79,7 @@ When using NextAuth.js without a database, the user object it will always be a p
:::
:::tip
If you only want to allow users who already have accounts in the database to sign in, you can check for the existence of a `user.id` property and reject any sign in attempts from accounts that do not have one.
If you only want to allow users who already have accounts in the database to sign in, you can check for the existance of a `user.id` property and reject any sign in attempts from accounts that do not have one.
If you are using NextAuth.js without database and want to control who can sign in, you can check their email address or profile against a hard coded list in the `signIn()` callback.
:::

View File

@@ -121,29 +121,11 @@ By default JSON Web Tokens are signed (JWS) but not encrypted (JWE), as JWT encr
jwt: {
// A secret to use for key generation - you should set this explicitly
// Defaults to NextAuth.js secret if not explicitly specified.
// This is used to generate the actual signingKey and produces a warning
// message if not defined explicitly.
// secret: 'INp8IvdIyeMcoGAgFGoA61DdBglwwSqnXJZkgz8PSnw',
// You can generate a signing key using `jose newkey -s 512 -t oct -a HS512`
// This gives you direct knowledge of the key used to sign the token so you can use it
// to authenticate indirectly (eg. to a database driver)
// signingKey: {"kty":"oct","kid":"Dl893BEV-iVE-x9EC52TDmlJUgGm9oZ99_ZL025Hc5Q","alg":"HS512","k":"K7QqRmJOKRK2qcCKV_pi9PSBv3XP0fpTu30TP8xn4w01xR3ZMZM38yL2DnTVPVw6e4yhdh0jtoah-i4c_pZagA"},
// If you chose something other than the default algorithm for the signingKey (HS512)
// you also need to configure the algorithm
// verificationOptions: {
// algorithms: ['HS256']
// },
// Set to true to use encryption. Defaults to false (signing only).
// encryption: true,
// encryptionKey: "",
// decryptionKey = encryptionKey,
// decryptionOptions = {
// algorithms: ['A256GCM']
// },
// You can define your own encode/decode functions for signing and encryption
// if you want to override the default behaviour.
// async encode({ secret, token, maxAge }) {},

View File

@@ -111,7 +111,14 @@ providers: [
...
```
:::tip
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 three changes:
1. Add your config: [`src/providers/{provider}.js`](https://github.com/nextauthjs/next-auth/tree/main/src/providers)
2. Re-export your config: at [`src/providers/index.js`](https://github.com/nextauthjs/next-auth/blob/main/src/providers/index.js)
3. Add provider documentation: [`www/docs/providers/{provider}.md`](https://github.com/nextauthjs/next-auth/tree/main/www/docs/providers)
You can look at the existing built-in providers for inspiration.
:::
### OAuth provider options
@@ -134,7 +141,7 @@ providers: [
| profile | An callback returning an object with the user's info | `object` | No |
| idToken | Set to `true` for services that use ID Tokens (e.g. OpenID) | `boolean` | No |
| headers | Any headers that should be sent to the OAuth provider | `object` | No |
| protection | Additional security for OAuth login flows (defaults to `state`) |`[pkce]`,`[state]`,`[pkce,state]`| No |
| protection | Additional security for OAuth login flows (defaults to `state`) | `pkce`, `state`, `none` | No |
| state | Same as `protection: "state"`. Being deprecated, use protection. | `boolean` | No |
## Sign in with Email
@@ -223,14 +230,3 @@ export const Image = ({ children, src, alt = '' }) => (
<img alt={alt} src={src} />
</div>
)
## Adding a new built-in provider
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.

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