mirror of
https://github.com/SrIzan10/next-auth.git
synced 2026-05-01 10:55:20 +00:00
Compare commits
56 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fb04ab4e76 | ||
|
|
07e2a83ccb | ||
|
|
065d9eb310 | ||
|
|
5da19f3c9a | ||
|
|
88ec3bad71 | ||
|
|
5ab7868533 | ||
|
|
835dda0899 | ||
|
|
ad4709764a | ||
|
|
55a2932973 | ||
|
|
49cb7e5bd7 | ||
|
|
b95182ded7 | ||
|
|
be28672fd4 | ||
|
|
e26c5fc905 | ||
|
|
543f812eb3 | ||
|
|
0c9f9777c5 | ||
|
|
34f334a71d | ||
|
|
172ad02f8c | ||
|
|
eed0001524 | ||
|
|
a2705fb5b9 | ||
|
|
cb1e5a7174 | ||
|
|
8cba5d06b5 | ||
|
|
c52ce57296 | ||
|
|
4dae822806 | ||
|
|
901f6fb189 | ||
|
|
bb2237d0f9 | ||
|
|
fab7ce8f94 | ||
|
|
2becdad990 | ||
|
|
e3c2c7756d | ||
|
|
718f2537cb | ||
|
|
ae26df091d | ||
|
|
1cbf73b2f6 | ||
|
|
46b62d723c | ||
|
|
457952bb5a | ||
|
|
17b789822d | ||
|
|
fd12194c0c | ||
|
|
1c662e9ddc | ||
|
|
968903d227 | ||
|
|
3dedf6c26c | ||
|
|
d1dbfe1023 | ||
|
|
63171a0271 | ||
|
|
872e180339 | ||
|
|
a7709df796 | ||
|
|
dbe283f0fa | ||
|
|
727426bbec | ||
|
|
5a3ee47337 | ||
|
|
8dd8f7c48a | ||
|
|
072c59d85a | ||
|
|
d0e8147a48 | ||
|
|
5bc8f8b986 | ||
|
|
136361e1f4 | ||
|
|
cc9869592c | ||
|
|
073da60c3d | ||
|
|
aacc34bbfd | ||
|
|
074688d10e | ||
|
|
b3ffe50c03 | ||
|
|
e6d063825d |
1
.github/CODEOWNERS
vendored
Normal file
1
.github/CODEOWNERS
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/types/ @balazsorban44 @lluia
|
||||
0
FUNDING.yml → .github/FUNDING.yml
vendored
0
FUNDING.yml → .github/FUNDING.yml
vendored
6
.github/labeler.yml
vendored
6
.github/labeler.yml
vendored
@@ -1,5 +1,6 @@
|
||||
test:
|
||||
- test/**/*
|
||||
- types/tests/**/*
|
||||
|
||||
documentation:
|
||||
- www/**/*
|
||||
@@ -32,4 +33,7 @@ client:
|
||||
|
||||
pages:
|
||||
- src/server/pages/**/*
|
||||
- www/docs/configuration/pages.md
|
||||
- www/docs/configuration/pages.md
|
||||
|
||||
TypeScript:
|
||||
- types/**/*
|
||||
|
||||
16
.github/workflows/build.yml
vendored
16
.github/workflows/build.yml
vendored
@@ -4,20 +4,22 @@ name: Lint/Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- next
|
||||
branches:
|
||||
- main
|
||||
- beta
|
||||
- next
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- next
|
||||
- main
|
||||
- beta
|
||||
- next
|
||||
|
||||
jobs:
|
||||
lint-and-build:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [10, 12, 14]
|
||||
node-version: [12, 14, 16]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
@@ -27,4 +29,4 @@ jobs:
|
||||
- name: Install dependencies
|
||||
uses: bahmutov/npm-install@v1
|
||||
- run: npm run lint
|
||||
- run: npm run build
|
||||
- run: npm run build
|
||||
|
||||
2
.github/workflows/codeql-analysis.yml
vendored
2
.github/workflows/codeql-analysis.yml
vendored
@@ -13,7 +13,7 @@ name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, next ]
|
||||
branches: [ main, beta, next ]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ main ]
|
||||
|
||||
13
.github/workflows/integration.yml
vendored
13
.github/workflows/integration.yml
vendored
@@ -2,9 +2,10 @@ name: Integration Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- next
|
||||
branches:
|
||||
- main
|
||||
- beta
|
||||
- next
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
@@ -17,7 +18,7 @@ jobs:
|
||||
if: github.event.pull_request.head.repo.full_name == github.repository
|
||||
|
||||
# We use self-hosted runners as cloud based runnners (e.g. AWS, GPC)
|
||||
# fail due to IP Address checks done by providers, which enforce
|
||||
# fail due to IP Address checks done by providers, which enforce
|
||||
# CAPTCHA checks on login request from cloud compute IP addresses to
|
||||
# prevent abuse.
|
||||
runs-on: self-hosted
|
||||
@@ -29,7 +30,7 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [10, 12, 14]
|
||||
node-version: [12, 14, 16]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
@@ -45,7 +46,7 @@ jobs:
|
||||
- run: npm test
|
||||
# TODO Tests should exit out if env vars not set (currently hangs)
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
|
||||
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
|
||||
NEXTAUTH_TWITTER_ID: ${{secrets.NEXTAUTH_TWITTER_ID}}
|
||||
NEXTAUTH_TWITTER_SECRET: ${{secrets.NEXTAUTH_TWITTER_SECRET}}
|
||||
NEXTAUTH_TWITTER_USERNAME: ${{secrets.NEXTAUTH_TWITTER_USERNAME}}
|
||||
|
||||
10
.github/workflows/release.yml
vendored
10
.github/workflows/release.yml
vendored
@@ -2,13 +2,14 @@ name: Release
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
- 'next'
|
||||
- '3.x'
|
||||
- "main"
|
||||
- "beta"
|
||||
- "next"
|
||||
- "3.x"
|
||||
pull_request:
|
||||
jobs:
|
||||
release:
|
||||
name: 'Release'
|
||||
name: "Release"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -19,7 +20,6 @@ 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}}
|
||||
|
||||
27
.github/workflows/types.yml
vendored
Normal file
27
.github/workflows/types.yml
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
name: Types
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- beta
|
||||
- next
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- beta
|
||||
- next
|
||||
|
||||
jobs:
|
||||
lint-and-build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 14
|
||||
- name: Install dependencies
|
||||
uses: bahmutov/npm-install@v1
|
||||
- name: Check types
|
||||
run: npm run test:types
|
||||
23
.gitignore
vendored
23
.gitignore
vendored
@@ -25,8 +25,27 @@ node_modules
|
||||
# Generated files
|
||||
.docusaurus
|
||||
.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
|
||||
/errors.js
|
||||
/errors.d.ts
|
||||
|
||||
# Development app
|
||||
app/next-auth
|
||||
app/dist/css
|
||||
app/package-lock.json
|
||||
app/yarn.lock
|
||||
|
||||
# VS
|
||||
/.vs/slnx.sqlite-journal
|
||||
@@ -39,4 +58,4 @@ www/providers.json
|
||||
/_work
|
||||
|
||||
# Prisma migrations
|
||||
/prisma/migrations
|
||||
/prisma/migrations
|
||||
@@ -16,7 +16,7 @@ Anyone can be a contributor. Either you found a typo, or you have an awesome fea
|
||||
* The latest changes are always in `main`, so please make your Pull Request against that branch.
|
||||
* Pull Requests should be raised for any change
|
||||
* Pull Requests need approval of a [core contributor](https://next-auth.js.org/contributors#core-team) before merging
|
||||
* Run `npm run lint:fix` before committing to make resolving conflicts easier (VSCode users, check out [this extension](https://marketplace.visualstudio.com/items?itemName=chenxsan.vscode-standardjs) to fix lint issues in development)
|
||||
* We use ESLint/Prettier for linting/formatting, so please run `npm run lint:fix` before committing to make resolving conflicts easier (VSCode users, check out [this ESLint extension](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) and [this Prettier extension](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) to fix lint and formatting issues in development)
|
||||
* We encourage you to test your changes, and if you have the opportunity, please make those tests part of the Pull Request
|
||||
* If you add new functionality, please provide the corresponding documentation as well and make it part of the Pull Request
|
||||
|
||||
@@ -32,17 +32,17 @@ cd next-auth
|
||||
|
||||
2. Install packages:
|
||||
```sh
|
||||
npm i
|
||||
npm i && npm dev:setup
|
||||
```
|
||||
|
||||
3. Populate `.env.local`:
|
||||
|
||||
Copy `.env.local.example` to `.env.local`, and add your env variables for each provider you want to test.
|
||||
Copy `app/.env.local.example` to `app/.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`pages/api/auth/[...nextauth].js`.
|
||||
> You can find the next-auth config under`app/pages/api/auth/[...nextauth].js`.
|
||||
|
||||
1. Start the dev application/server and CSS watching:
|
||||
1. Start the dev application/server:
|
||||
```sh
|
||||
npm run dev
|
||||
```
|
||||
@@ -57,11 +57,23 @@ 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 need to manually refresh the page after changes. (Improving this through a PR is very welcome!)
|
||||
> 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.
|
||||
|
||||
#### Databases
|
||||
|
||||
Included is a Docker Compose file that starts up MySQL, Postgres, and MongoDB databases on localhost.
|
||||
Included is a Docker Compose file that starts up MySQL, PostgreSQL, 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.
|
||||
|
||||
|
||||
@@ -84,13 +84,9 @@ Advanced options allow you to define your own routines to handle controlling wha
|
||||
|
||||
### TypeScript
|
||||
|
||||
You can install the appropriate types via the following command:
|
||||
NextAuth.js comes with built-in types. For more information and usage, check out the [TypeScript section](https://next-auth.js.org/getting-started/typescript) in the documentaion.
|
||||
|
||||
```
|
||||
npm install --save-dev @types/next-auth
|
||||
```
|
||||
|
||||
As of now, TypeScript is a community effort. If you encounter any problems with the types package, please create an issue at [DefinitelyTyped](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/next-auth). Alternatively, you can open a pull request directly with your fixes there. We welcome anyone to start a discussion on migrating this package to TypeScript, or how to improve the TypeScript experience in general.
|
||||
The package at `@types/next-auth` is now deprecated.
|
||||
|
||||
## Example
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
module.exports = require('./dist/adapters').default
|
||||
6
app/README.md
Normal file
6
app/README.md
Normal file
@@ -0,0 +1,6 @@
|
||||
# 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)
|
||||
5
app/jsconfig.json
Normal file
5
app/jsconfig.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": "."
|
||||
}
|
||||
}
|
||||
0
next-env.d.ts → app/next-env.d.ts
vendored
0
next-env.d.ts → app/next-env.d.ts
vendored
19
app/next.config.js
Normal file
19
app/next.config.js
Normal file
@@ -0,0 +1,19 @@
|
||||
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
|
||||
},
|
||||
}
|
||||
25
app/package.json
Normal file
25
app/package.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
@@ -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}
|
||||
>
|
||||
@@ -6,6 +6,27 @@ 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,
|
||||
@@ -19,6 +40,11 @@ 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({
|
||||
@@ -56,6 +82,6 @@ export default NextAuth({
|
||||
// Prisma Database Adapter
|
||||
// To configure this app to use the schema in `prisma/schema.prisma` run:
|
||||
// npx prisma generate
|
||||
// npx prisma migrate dev --preview-feature
|
||||
// npx prisma migrate dev
|
||||
// adapter: Adapters.Prisma.Adapter({ prisma })
|
||||
})
|
||||
@@ -1,3 +1,4 @@
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
import * as React from 'react'
|
||||
import { signIn, signOut, useSession } from 'next-auth/client'
|
||||
import Layout from 'components/layout'
|
||||
@@ -1,3 +1,4 @@
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
import * as React from 'react'
|
||||
import { signIn, signOut, useSession } from 'next-auth/client'
|
||||
import Layout from 'components/layout'
|
||||
@@ -2,6 +2,9 @@
|
||||
"presets": [
|
||||
["@babel/preset-env", { "targets": { "esmodules": true } }]
|
||||
],
|
||||
"plugins": [
|
||||
"@babel/plugin-proposal-class-properties"
|
||||
],
|
||||
"comments": false,
|
||||
"overrides": [
|
||||
{
|
||||
|
||||
91
config/build.js
Normal file
91
config/build.js
Normal file
@@ -0,0 +1,91 @@
|
||||
const fs = require("fs-extra")
|
||||
const path = require("path")
|
||||
|
||||
const MODULE_ENTRIES = {
|
||||
SERVER: "index",
|
||||
CLIENT: "client",
|
||||
PROVIDERS: "providers",
|
||||
ADAPTERS: "adapters",
|
||||
JWT: "jwt",
|
||||
ERRORS: "errors",
|
||||
}
|
||||
|
||||
// Building submodule entries
|
||||
|
||||
const BUILD_TARGETS = {
|
||||
[`${MODULE_ENTRIES.SERVER}.js`]: "module.exports = require('./dist/server').default\n",
|
||||
[`${MODULE_ENTRIES.CLIENT}.js`]: "module.exports = require('./dist/client').default\n",
|
||||
[`${MODULE_ENTRIES.ADAPTERS}.js`]: "module.exports = require('./dist/adapters').default\n",
|
||||
[`${MODULE_ENTRIES.PROVIDERS}.js`]: "module.exports = require('./dist/providers').default\n",
|
||||
[`${MODULE_ENTRIES.JWT}.js`]: "module.exports = require('./dist/lib/jwt').default\n",
|
||||
[`${MODULE_ENTRIES.ERRORS}.js`]: "module.exports = require('./dist/lib/errors').default\n",
|
||||
}
|
||||
|
||||
Object.entries(BUILD_TARGETS).forEach(([target, content]) => {
|
||||
fs.writeFile(path.join(process.cwd(), target), content, (err) => {
|
||||
if (err) throw err
|
||||
console.log(`[build] created "${target}" in root folder`)
|
||||
})
|
||||
})
|
||||
|
||||
// Building types
|
||||
|
||||
const TYPES_TARGETS = [
|
||||
`${MODULE_ENTRIES.SERVER}.d.ts`,
|
||||
`${MODULE_ENTRIES.CLIENT}.d.ts`,
|
||||
`${MODULE_ENTRIES.ADAPTERS}.d.ts`,
|
||||
`${MODULE_ENTRIES.PROVIDERS}.d.ts`,
|
||||
`${MODULE_ENTRIES.JWT}.d.ts`,
|
||||
`${MODULE_ENTRIES.ERRORS}.d.ts`,
|
||||
"internals",
|
||||
]
|
||||
|
||||
TYPES_TARGETS.forEach((target) => {
|
||||
fs.copy(
|
||||
path.resolve("types", target),
|
||||
path.join(process.cwd(), target),
|
||||
(err) => {
|
||||
if (err) throw err
|
||||
console.log(`[build-types] copying "${target}" to root folder`)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
// Building providers
|
||||
|
||||
const providersDir = path.join(process.cwd(), "/src/providers")
|
||||
|
||||
const files = fs
|
||||
.readdirSync(providersDir, "utf8")
|
||||
.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")
|
||||
)
|
||||
2479
package-lock.json
generated
2479
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
117
package.json
117
package.json
@@ -6,39 +6,71 @@
|
||||
"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",
|
||||
"./errors": "./dist/lib/errors.js"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "npm run build:js && npm run build:css",
|
||||
"build:js": "babel --config-file ./config/babel.config.json src --out-dir dist",
|
||||
"build:js": "node ./config/build.js && babel --config-file ./config/babel.config.json src --out-dir dist",
|
||||
"build:css": "postcss --config config/postcss.config.js src/**/*.css --base src --dir dist && node config/wrap-css.js",
|
||||
"dev": "next | npm run watch:css",
|
||||
"dev:setup": "npm run build:css && cd app && npm i",
|
||||
"dev": "cd app && npm run dev",
|
||||
"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",
|
||||
"test:app:start": "docker-compose -f test/docker/app.yml up -d",
|
||||
"test:app:rebuild": "npm run build && docker-compose -f test/docker/app.yml up -d --build",
|
||||
"test:app:stop": "docker-compose -f test/docker/app.yml down",
|
||||
"test": "npm run test:app:rebuild && npm run test:integration && npm run test:app:stop",
|
||||
"test": "npm run test:app:rebuild && npm run test:integration && npm run test:app:stop && npm run test:types",
|
||||
"test:db": "npm run test:db:mysql && npm run test:db:postgres && npm run test:db:mongodb && npm run test:db:mssql",
|
||||
"test:db:mysql": "node test/mysql.js",
|
||||
"test:db:postgres": "node test/postgres.js",
|
||||
"test:db:mongodb": "node test/mongodb.js",
|
||||
"test:db:mssql": "node test/mssql.js",
|
||||
"test:integration": "mocha test/integration",
|
||||
"test:types": "dtslint types",
|
||||
"db:start": "docker-compose -f test/docker/databases.yml up -d",
|
||||
"db:stop": "docker-compose -f test/docker/databases.yml down",
|
||||
"prepublishOnly": "npm run build",
|
||||
"publish:beta": "npm publish --tag beta",
|
||||
"publish:canary": "npm publish --tag canary",
|
||||
"lint": "ts-standard",
|
||||
"lint:fix": "ts-standard --fix"
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint . --fix"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"index.js",
|
||||
"index.d.ts",
|
||||
"providers.js",
|
||||
"providers.d.ts",
|
||||
"adapters.js",
|
||||
"adapters.d.ts",
|
||||
"client.js",
|
||||
"jwt.js"
|
||||
"client.d.ts",
|
||||
"errors.js",
|
||||
"errors.d.ts",
|
||||
"jwt.js",
|
||||
"jwt.d.ts",
|
||||
"internals"
|
||||
],
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
@@ -57,7 +89,7 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.13.1 || ^17",
|
||||
"react-dom": "^16.13.1 || ^17"
|
||||
"react-dom": "16.13.1 || ^17"
|
||||
},
|
||||
"peerOptionalDependencies": {
|
||||
"mongodb": "^3.5.9",
|
||||
@@ -69,6 +101,7 @@
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.8.4",
|
||||
"@babel/core": "^7.9.6",
|
||||
"@babel/plugin-proposal-class-properties": "^7.13.0",
|
||||
"@babel/preset-env": "^7.9.6",
|
||||
"@prisma/client": "^2.16.1",
|
||||
"@semantic-release/commit-analyzer": "^8.0.1",
|
||||
@@ -76,12 +109,21 @@
|
||||
"@semantic-release/npm": "7.0.8",
|
||||
"@semantic-release/release-notes-generator": "^9.0.1",
|
||||
"@types/react": "^17.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4.22.0",
|
||||
"@typescript-eslint/parser": "^4.22.0",
|
||||
"autoprefixer": "^9.7.6",
|
||||
"babel-preset-preact": "^2.0.0",
|
||||
"conventional-changelog-conventionalcommits": "4.4.0",
|
||||
"cssnano": "^4.1.10",
|
||||
"dotenv": "^8.2.0",
|
||||
"dtslint": "^4.0.8",
|
||||
"eslint": "^7.19.0",
|
||||
"eslint-config-prettier": "^8.2.0",
|
||||
"eslint-config-standard-with-typescript": "^19.0.1",
|
||||
"eslint-plugin-import": "^2.22.1",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^4.3.1",
|
||||
"eslint-plugin-standard": "^5.0.0",
|
||||
"mocha": "^8.1.3",
|
||||
"mongodb": "^3.5.9",
|
||||
"mssql": "^6.2.1",
|
||||
@@ -90,25 +132,66 @@
|
||||
"pg": "^8.2.1",
|
||||
"postcss-cli": "^7.1.1",
|
||||
"postcss-nested": "^4.2.1",
|
||||
"prettier": "^2.2.1",
|
||||
"prisma": "^2.16.1",
|
||||
"puppeteer": "^5.2.1",
|
||||
"puppeteer-extra": "^3.1.15",
|
||||
"puppeteer-extra-plugin-stealth": "^2.6.1",
|
||||
"react": "^17.0.1",
|
||||
"react-dom": "^17.0.1",
|
||||
"ts-standard": "^10.0.0",
|
||||
"typescript": "^4.1.3"
|
||||
},
|
||||
"ts-standard": {
|
||||
"project": "./tsconfig.json",
|
||||
"ignore": [
|
||||
"test/",
|
||||
"next-env.d.ts"
|
||||
"prettier": {
|
||||
"semi": false
|
||||
},
|
||||
"eslintConfig": {
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"project": "./tsconfig.json"
|
||||
},
|
||||
"extends": [
|
||||
"standard-with-typescript",
|
||||
"prettier"
|
||||
],
|
||||
"globals": [
|
||||
"localStorage",
|
||||
"location",
|
||||
"fetch"
|
||||
"ignorePatterns": [
|
||||
"node_modules",
|
||||
"test",
|
||||
"next-env.d.ts",
|
||||
"types",
|
||||
"www",
|
||||
".next",
|
||||
"dist"
|
||||
],
|
||||
"globals": {
|
||||
"localStorage": "readonly",
|
||||
"location": "readonly",
|
||||
"fetch": "readonly"
|
||||
}
|
||||
},
|
||||
"release": {
|
||||
"branches": [
|
||||
"+([0-9])?(.{+([0-9]),x}).x",
|
||||
"main",
|
||||
{
|
||||
"name": "beta",
|
||||
"prerelease": true
|
||||
},
|
||||
{
|
||||
"name": "next",
|
||||
"prerelease": true
|
||||
}
|
||||
],
|
||||
"plugins": [
|
||||
"@semantic-release/commit-analyzer",
|
||||
"@semantic-release/release-notes-generator",
|
||||
"@semantic-release/npm",
|
||||
[
|
||||
"@semantic-release/github",
|
||||
{
|
||||
"releasedLabels": false,
|
||||
"successComment": false
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"funding": [
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
module.exports = require('./dist/providers').default
|
||||
@@ -1,7 +0,0 @@
|
||||
module.exports = {
|
||||
branches: [
|
||||
'+([0-9])?(.{+([0-9]),x}).x',
|
||||
'main',
|
||||
{ name: 'next', prerelease: true }
|
||||
]
|
||||
}
|
||||
103
src/client/index.d.ts
vendored
103
src/client/index.d.ts
vendored
@@ -1,103 +0,0 @@
|
||||
import * as React from 'react'
|
||||
import { GetServerSidePropsContext } from 'next'
|
||||
|
||||
interface DefaultSession {
|
||||
user: {
|
||||
name: string | null
|
||||
email: string | null
|
||||
image: string | null
|
||||
}
|
||||
expires: Date | string
|
||||
}
|
||||
|
||||
interface BroadcastMessage {
|
||||
event?: 'session'
|
||||
data?: {
|
||||
trigger?: 'signout' | 'getSession'
|
||||
}
|
||||
clientId: string
|
||||
timestamp: number
|
||||
}
|
||||
|
||||
type GetSession<S extends Record<string, unknown> = DefaultSession> = (options: {
|
||||
ctx?: GetServerSidePropsContext
|
||||
req?: GetServerSidePropsContext['req']
|
||||
event?: 'storage' | 'timer' | 'hidden' | string
|
||||
triggerEvent?: boolean
|
||||
}) => Promise<S>
|
||||
|
||||
export interface NextAuthConfig {
|
||||
baseUrl: string
|
||||
basePath: string
|
||||
baseUrlServer: string
|
||||
basePathServer: string
|
||||
/** 0 means disabled (don't send); 60 means send every 60 seconds */
|
||||
keepAlive: number
|
||||
/** 0 means disabled (only use cache); 60 means sync if last checked > 60 seconds ago */
|
||||
clientMaxAge: number
|
||||
/** Used for timestamp since last sycned (in seconds) */
|
||||
_clientLastSync: number
|
||||
/** Stores timer for poll interval */
|
||||
_clientSyncTimer: ReturnType<typeof setTimeout>
|
||||
/** Tracks if event listeners have been added */
|
||||
_eventListenersAdded: boolean
|
||||
/** Stores last session response from hook */
|
||||
_clientSession: DefaultSession | null | undefined
|
||||
/** Used to store to function export by getSession() hook */
|
||||
_getSession: any
|
||||
}
|
||||
|
||||
export type GetCsrfToken = (
|
||||
ctxOrReq: GetServerSidePropsContext & GetServerSidePropsContext['req']
|
||||
) => Promise<string | null>
|
||||
|
||||
export interface SessionOptions {
|
||||
baseUrl?: string
|
||||
basePath?: string
|
||||
clientMaxAge?: number
|
||||
keepAlive?: number
|
||||
}
|
||||
|
||||
export type Provider<S extends Record<string, unknown> = DefaultSession > = (options: {
|
||||
children: React.ReactNode
|
||||
session: S
|
||||
options: SessionOptions
|
||||
}) => React.ReactNode
|
||||
|
||||
export type SetOptions = (options: SessionOptions) => void
|
||||
|
||||
export type SessionContext = React.createContext<[DefaultSession | null, boolean]>
|
||||
|
||||
export type UseSession = () => [any, boolean]
|
||||
|
||||
export type GetProviders = () => Promise<any[]>
|
||||
|
||||
// Sign in types
|
||||
|
||||
export interface SignInOptions {
|
||||
/** Defaults to the current URL. */
|
||||
callbackUrl?: string
|
||||
redirect?: boolean
|
||||
}
|
||||
export interface SignInResponse {
|
||||
error: string | null
|
||||
status: number
|
||||
ok: boolean
|
||||
url: string | null
|
||||
}
|
||||
|
||||
export type SignIn<AuthorizationParams = Record<string, string>> = (
|
||||
provider?: string,
|
||||
options?: SignInOptions,
|
||||
authorizationParams?: AuthorizationParams
|
||||
) => SignInResponse
|
||||
|
||||
// Sign out types
|
||||
|
||||
interface SignOutResponse<RedirectType extends boolean=true> {
|
||||
/** Defaults to the current URL. */
|
||||
callbackUrl?: string
|
||||
redirect?: RedirectType
|
||||
}
|
||||
|
||||
export type SignOut<RedirectType extends boolean = true> = (params: SignOutResponse<RedirectType>) => RedirectType extends true ? Promise<{url?: string} | undefined> : undefined
|
||||
@@ -18,7 +18,7 @@ import parseUrl from '../lib/parse-url'
|
||||
// relative URLs are valid in that context and so defaults to empty.
|
||||
// 2. When invoked server side the value is picked up from an environment
|
||||
// variable and defaults to 'http://localhost:3000'.
|
||||
/** @type {import(".").NextAuthConfig} */
|
||||
/** @type {import("types/internals/client").NextAuthConfig} */
|
||||
const __NEXTAUTH = {
|
||||
baseUrl: parseUrl(process.env.NEXTAUTH_URL || process.env.VERCEL_URL).baseUrl,
|
||||
basePath: parseUrl(process.env.NEXTAUTH_URL).basePath,
|
||||
@@ -60,15 +60,9 @@ if (typeof window !== 'undefined' && !__NEXTAUTH._eventListenersAdded) {
|
||||
}
|
||||
|
||||
// Context to store session data globally
|
||||
/** @type {import("types/internals/client").SessionContext} */
|
||||
const SessionContext = createContext()
|
||||
|
||||
/**
|
||||
* React Hook that gives you access
|
||||
* to the logged in user's session data.
|
||||
*
|
||||
* [Documentation](https://next-auth.js.org/getting-started/client#usesession)
|
||||
* @type {import(".").UseSession}
|
||||
*/
|
||||
export function useSession (session) {
|
||||
const context = useContext(SessionContext)
|
||||
if (context) return context
|
||||
@@ -143,14 +137,6 @@ function _useSessionHook (session) {
|
||||
return [data, loading]
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be called client or server side to return a session asynchronously.
|
||||
* It calls `/api/auth/session` and returns a promise with a session object,
|
||||
* or null if no session exists.
|
||||
*
|
||||
* [Documentation](https://next-auth.js.org/getting-started/client#getsession)
|
||||
* @type {import(".").GetSession}
|
||||
*/
|
||||
export async function getSession (ctx) {
|
||||
const session = await _fetchData('session', ctx)
|
||||
if (ctx?.triggerEvent ?? true) {
|
||||
@@ -159,39 +145,14 @@ export async function getSession (ctx) {
|
||||
return session
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current Cross Site Request Forgery Token (CSRF Token)
|
||||
* required to make POST requests (e.g. for signing in and signing out).
|
||||
* You likely only need to use this if you are not using the built-in
|
||||
* `signIn()` and `signOut()` methods.
|
||||
*
|
||||
* [Documentation](https://next-auth.js.org/getting-started/client#getcsrftoken)
|
||||
* @type {import(".").GetCsrfToken}
|
||||
*/
|
||||
async function getCsrfToken (ctx) {
|
||||
export async function getCsrfToken (ctx) {
|
||||
return (await _fetchData('csrf', ctx))?.csrfToken
|
||||
}
|
||||
|
||||
/**
|
||||
* It calls `/api/auth/providers` and returns
|
||||
* a list of the currently configured authentication providers.
|
||||
* It can be useful if you are creating a dynamic custom sign in page.
|
||||
*
|
||||
* [Documentation](https://next-auth.js.org/getting-started/client#getproviders)
|
||||
* @type {import(".").GetProviders}
|
||||
*/
|
||||
export async function getProviders () {
|
||||
return _fetchData('providers')
|
||||
}
|
||||
|
||||
/**
|
||||
* Client-side method to initiate a signin flow
|
||||
* or send the user to the signin page listing all possible providers.
|
||||
* Automatically adds the CSRF token to the request.
|
||||
*
|
||||
* [Documentation](https://next-auth.js.org/getting-started/client#signin)
|
||||
* @type {import(".").SignIn}
|
||||
*/
|
||||
export async function signIn (provider, options = {}, authorizationParams = {}) {
|
||||
const {
|
||||
callbackUrl = window.location,
|
||||
@@ -255,13 +216,6 @@ export async function signIn (provider, options = {}, authorizationParams = {})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Signs the user out, by removing the session cookie.
|
||||
* Automatically adds the CSRF token to the request.
|
||||
*
|
||||
* [Documentation](https://next-auth.js.org/getting-started/client#signout)
|
||||
* @type {import(".").SignOut}
|
||||
*/
|
||||
export async function signOut (options = {}) {
|
||||
const {
|
||||
callbackUrl = window.location,
|
||||
@@ -298,7 +252,6 @@ export async function signOut (options = {}) {
|
||||
// Method to set options. The documented way is to use the provider, but this
|
||||
// method is being left in as an alternative, that will be helpful if/when we
|
||||
// expose a vanilla JavaScript version that doesn't depend on React.
|
||||
/** @type {import(".").SetOptions} */
|
||||
export function setOptions ({ baseUrl, basePath, clientMaxAge, keepAlive } = {}) {
|
||||
if (baseUrl) __NEXTAUTH.baseUrl = baseUrl
|
||||
if (basePath) __NEXTAUTH.basePath = basePath
|
||||
@@ -321,14 +274,6 @@ export function setOptions ({ baseUrl, basePath, clientMaxAge, keepAlive } = {})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provider to wrap the app in to make session data available globally.
|
||||
* Can also be used to throttle the number of requests to the endpoint
|
||||
* `/api/auth/session`.
|
||||
*
|
||||
* [Documentation](https://next-auth.js.org/getting-started/client#provider)
|
||||
* @type {import(".").Provider}
|
||||
*/
|
||||
export function Provider ({ children, session, options }) {
|
||||
setOptions(options)
|
||||
return createElement(
|
||||
@@ -387,13 +332,13 @@ function BroadcastChannel (name = 'nextauth.message') {
|
||||
return {
|
||||
/**
|
||||
* Get notified by other tabs/windows.
|
||||
* @param {(message: import(".").BroadcastMessage) => void} onReceive
|
||||
* @param {(message: import("types/internals/client").BroadcastMessage) => void} onReceive
|
||||
*/
|
||||
receive (onReceive) {
|
||||
if (typeof window === 'undefined') return
|
||||
window.addEventListener('storage', async (event) => {
|
||||
if (event.key !== name) return
|
||||
/** @type {import(".").BroadcastMessage} */
|
||||
/** @type {import("types/internals/client").BroadcastMessage} */
|
||||
const message = JSON.parse(event.newValue)
|
||||
if (message?.event !== 'session' || !message?.data) return
|
||||
|
||||
|
||||
@@ -1,39 +1,98 @@
|
||||
/**
|
||||
* Same as the default `Error`, but it is JSON serializable.
|
||||
* @source https://iaincollins.medium.com/error-handling-in-javascript-a6172ccdf9af
|
||||
*/
|
||||
export class UnknownError extends Error {
|
||||
constructor (message) {
|
||||
super(message)
|
||||
this.name = 'UnknownError'
|
||||
constructor(error) {
|
||||
// Support passing error or string
|
||||
super(error?.message ?? error)
|
||||
this.name = "UnknownError"
|
||||
if (error instanceof Error) {
|
||||
this.stack = error.stack
|
||||
}
|
||||
}
|
||||
|
||||
toJSON () {
|
||||
toJSON() {
|
||||
return {
|
||||
error: {
|
||||
name: this.name,
|
||||
message: this.message
|
||||
// stack: this.stack
|
||||
}
|
||||
name: this.name,
|
||||
message: this.message,
|
||||
stack: this.stack,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class CreateUserError extends UnknownError {
|
||||
constructor (message) {
|
||||
super(message)
|
||||
this.name = 'CreateUserError'
|
||||
}
|
||||
}
|
||||
|
||||
// Thrown when an Email address is already associated with an account
|
||||
// but the user is trying an OAuth account that is not linked to it.
|
||||
export class AccountNotLinkedError extends UnknownError {
|
||||
constructor (message) {
|
||||
super(message)
|
||||
this.name = 'AccountNotLinkedError'
|
||||
}
|
||||
}
|
||||
|
||||
export class OAuthCallbackError extends UnknownError {
|
||||
constructor (message) {
|
||||
super(message)
|
||||
this.name = 'OAuthCallbackError'
|
||||
}
|
||||
name = "OAuthCallbackError"
|
||||
}
|
||||
|
||||
/**
|
||||
* Thrown when an Email address is already associated with an account
|
||||
* but the user is trying an OAuth account that is not linked to it.
|
||||
*/
|
||||
export class AccountNotLinkedError extends UnknownError {
|
||||
name = "AccountNotLinkedError"
|
||||
}
|
||||
|
||||
export class CreateUserError extends UnknownError {
|
||||
name = "CreateUserError"
|
||||
}
|
||||
|
||||
export class GetUserError extends UnknownError {
|
||||
name = "GetUserError"
|
||||
}
|
||||
|
||||
export class GetUserByEmailError extends UnknownError {
|
||||
name = "GetUserByEmailError"
|
||||
}
|
||||
|
||||
export class GetUserByIdError extends UnknownError {
|
||||
name = "GetUserByIdError"
|
||||
}
|
||||
|
||||
export class GetUserByProviderAccountIdError extends UnknownError {
|
||||
name = "GetUserByProviderAccountIdError"
|
||||
}
|
||||
|
||||
export class UpdateUserError extends UnknownError {
|
||||
name = "UpdateUserError"
|
||||
}
|
||||
|
||||
export class DeleteUserError extends UnknownError {
|
||||
name = "DeleteUserError"
|
||||
}
|
||||
|
||||
export class LinkAccountError extends UnknownError {
|
||||
name = "LinkAccountError"
|
||||
}
|
||||
|
||||
export class UnlinkAccountError extends UnknownError {
|
||||
name = "UnlinkAccountError"
|
||||
}
|
||||
|
||||
export class CreateSessionError extends UnknownError {
|
||||
name = "CreateSessionError"
|
||||
}
|
||||
|
||||
export class GetSessionError extends UnknownError {
|
||||
name = "GetSessionError"
|
||||
}
|
||||
|
||||
export class UpdateSessionError extends UnknownError {
|
||||
name = "UpdateSessionError"
|
||||
}
|
||||
|
||||
export class DeleteSessionError extends UnknownError {
|
||||
name = "DeleteSessionError"
|
||||
}
|
||||
|
||||
export class CreateVerificationRequestError extends UnknownError {
|
||||
name = "CreateVerificationRequestError"
|
||||
}
|
||||
|
||||
export class GetVerificationRequestError extends UnknownError {
|
||||
name = "GetVerificationRequestError"
|
||||
}
|
||||
|
||||
export class DeleteVerificationRequestError extends UnknownError {
|
||||
name = "DeleteVerificationRequestError"
|
||||
}
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
import crypto from 'crypto'
|
||||
import jose from 'jose'
|
||||
import logger from './logger'
|
||||
import crypto from "crypto"
|
||||
import jose from "jose"
|
||||
import logger from "./logger"
|
||||
|
||||
// Set default algorithm to use for auto-generated signing key
|
||||
const DEFAULT_SIGNATURE_ALGORITHM = 'HS512'
|
||||
const DEFAULT_SIGNATURE_ALGORITHM = "HS512"
|
||||
|
||||
// Set default algorithm for auto-generated symmetric encryption key
|
||||
const DEFAULT_ENCRYPTION_ALGORITHM = 'A256GCM'
|
||||
const DEFAULT_ENCRYPTION_ALGORITHM = "A256GCM"
|
||||
|
||||
// Use encryption or not by default
|
||||
const DEFAULT_ENCRYPTION_ENABLED = false
|
||||
|
||||
const DEFAULT_MAX_AGE = 30 * 24 * 60 * 60 // 30 days
|
||||
|
||||
async function encode ({
|
||||
export async function encode({
|
||||
token = {},
|
||||
maxAge = DEFAULT_MAX_AGE,
|
||||
secret,
|
||||
signingKey,
|
||||
signingOptions = {
|
||||
expiresIn: `${maxAge}s`
|
||||
expiresIn: `${maxAge}s`,
|
||||
},
|
||||
encryptionKey,
|
||||
encryptionOptions = {
|
||||
alg: 'dir',
|
||||
alg: "dir",
|
||||
enc: DEFAULT_ENCRYPTION_ALGORITHM,
|
||||
zip: 'DEF'
|
||||
zip: "DEF",
|
||||
},
|
||||
encryption = DEFAULT_ENCRYPTION_ENABLED
|
||||
encryption = DEFAULT_ENCRYPTION_ENABLED,
|
||||
} = {}) {
|
||||
// Signing Key
|
||||
const _signingKey = signingKey
|
||||
@@ -49,7 +49,7 @@ async function encode ({
|
||||
return signedToken
|
||||
}
|
||||
|
||||
async function decode ({
|
||||
export async function decode({
|
||||
secret,
|
||||
token,
|
||||
maxAge = DEFAULT_MAX_AGE,
|
||||
@@ -57,14 +57,14 @@ async function decode ({
|
||||
verificationKey = signingKey, // Optional (defaults to encryptionKey)
|
||||
verificationOptions = {
|
||||
maxTokenAge: `${maxAge}s`,
|
||||
algorithms: [DEFAULT_SIGNATURE_ALGORITHM]
|
||||
algorithms: [DEFAULT_SIGNATURE_ALGORITHM],
|
||||
},
|
||||
encryptionKey,
|
||||
decryptionKey = encryptionKey, // Optional (defaults to encryptionKey)
|
||||
decryptionOptions = {
|
||||
algorithms: [DEFAULT_ENCRYPTION_ALGORITHM]
|
||||
algorithms: [DEFAULT_ENCRYPTION_ALGORITHM],
|
||||
},
|
||||
encryption = DEFAULT_ENCRYPTION_ENABLED
|
||||
encryption = DEFAULT_ENCRYPTION_ENABLED,
|
||||
} = {}) {
|
||||
if (!token) return null
|
||||
|
||||
@@ -77,8 +77,12 @@ async function decode ({
|
||||
: getDerivedEncryptionKey(secret)
|
||||
|
||||
// Decrypt token
|
||||
const decryptedToken = jose.JWE.decrypt(token, _encryptionKey, decryptionOptions)
|
||||
tokenToVerify = decryptedToken.toString('utf8')
|
||||
const decryptedToken = jose.JWE.decrypt(
|
||||
token,
|
||||
_encryptionKey,
|
||||
decryptionOptions
|
||||
)
|
||||
tokenToVerify = decryptedToken.toString("utf8")
|
||||
}
|
||||
|
||||
// Signing Key
|
||||
@@ -99,16 +103,22 @@ async function decode ({
|
||||
* raw?: boolean
|
||||
* }} params
|
||||
*/
|
||||
async function getToken (params) {
|
||||
export async function getToken(params) {
|
||||
const {
|
||||
req,
|
||||
// Use secure prefix for cookie name, unless URL is NEXTAUTH_URL is http://
|
||||
// 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
|
||||
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,
|
||||
} = params
|
||||
if (!req) throw new Error('Must pass `req` to JWT getToken()')
|
||||
if (!req) throw new Error("Must pass `req` to JWT getToken()")
|
||||
|
||||
// Try to get token from cookie
|
||||
let token = req.cookies[cookieName]
|
||||
@@ -116,8 +126,8 @@ async function getToken (params) {
|
||||
// If cookie not found in cookie look for bearer token in authorization header.
|
||||
// This allows clients that pass through tokens in headers rather than as
|
||||
// cookies to use this helper function.
|
||||
if (!token && req.headers.authorization?.split(' ')[0] === 'Bearer') {
|
||||
const urlEncodedToken = req.headers.authorization.split(' ')[1]
|
||||
if (!token && req.headers.authorization?.split(" ")[0] === "Bearer") {
|
||||
const urlEncodedToken = req.headers.authorization.split(" ")[1]
|
||||
token = decodeURIComponent(urlEncodedToken)
|
||||
}
|
||||
|
||||
@@ -126,7 +136,7 @@ async function getToken (params) {
|
||||
}
|
||||
|
||||
try {
|
||||
return decode({ token, ...params })
|
||||
return _decode({ token, ...params })
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
@@ -137,7 +147,7 @@ let DERIVED_SIGNING_KEY_WARNING = false
|
||||
let DERIVED_ENCRYPTION_KEY_WARNING = false
|
||||
|
||||
// Do the better hkdf of Node.js one added in `v15.0.0` and Third Party one
|
||||
function hkdf (secret, { byteLength, encryptionInfo, digest = 'sha256' }) {
|
||||
function hkdf(secret, { byteLength, encryptionInfo, digest = "sha256" }) {
|
||||
if (crypto.hkdfSync) {
|
||||
return Buffer.from(
|
||||
crypto.hkdfSync(
|
||||
@@ -149,39 +159,50 @@ function hkdf (secret, { byteLength, encryptionInfo, digest = 'sha256' }) {
|
||||
)
|
||||
)
|
||||
}
|
||||
return require('futoin-hkdf')(secret, byteLength, { info: encryptionInfo, hash: digest })
|
||||
return require("futoin-hkdf")(secret, byteLength, {
|
||||
info: encryptionInfo,
|
||||
hash: digest,
|
||||
})
|
||||
}
|
||||
|
||||
function getDerivedSigningKey (secret) {
|
||||
function getDerivedSigningKey(secret) {
|
||||
if (!DERIVED_SIGNING_KEY_WARNING) {
|
||||
logger.warn('JWT_AUTO_GENERATED_SIGNING_KEY')
|
||||
logger.warn("JWT_AUTO_GENERATED_SIGNING_KEY")
|
||||
DERIVED_SIGNING_KEY_WARNING = true
|
||||
}
|
||||
|
||||
const buffer = hkdf(secret, {
|
||||
byteLength: 64,
|
||||
encryptionInfo: 'NextAuth.js Generated Signing Key'
|
||||
encryptionInfo: "NextAuth.js Generated Signing Key",
|
||||
})
|
||||
const key = jose.JWK.asKey(buffer, {
|
||||
alg: DEFAULT_SIGNATURE_ALGORITHM,
|
||||
use: "sig",
|
||||
kid: "nextauth-auto-generated-signing-key",
|
||||
})
|
||||
const key = jose.JWK.asKey(buffer, { alg: DEFAULT_SIGNATURE_ALGORITHM, use: 'sig', kid: 'nextauth-auto-generated-signing-key' })
|
||||
return key
|
||||
}
|
||||
|
||||
function getDerivedEncryptionKey (secret) {
|
||||
function getDerivedEncryptionKey(secret) {
|
||||
if (!DERIVED_ENCRYPTION_KEY_WARNING) {
|
||||
logger.warn('JWT_AUTO_GENERATED_ENCRYPTION_KEY')
|
||||
logger.warn("JWT_AUTO_GENERATED_ENCRYPTION_KEY")
|
||||
DERIVED_ENCRYPTION_KEY_WARNING = true
|
||||
}
|
||||
|
||||
const buffer = hkdf(secret, {
|
||||
byteLength: 32,
|
||||
encryptionInfo: 'NextAuth.js Generated Encryption Key'
|
||||
encryptionInfo: "NextAuth.js Generated Encryption Key",
|
||||
})
|
||||
const key = jose.JWK.asKey(buffer, {
|
||||
alg: DEFAULT_ENCRYPTION_ALGORITHM,
|
||||
use: "enc",
|
||||
kid: "nextauth-auto-generated-encryption-key",
|
||||
})
|
||||
const key = jose.JWK.asKey(buffer, { alg: DEFAULT_ENCRYPTION_ALGORITHM, use: 'enc', kid: 'nextauth-auto-generated-encryption-key' })
|
||||
return key
|
||||
}
|
||||
|
||||
export default {
|
||||
encode,
|
||||
decode,
|
||||
getToken
|
||||
getToken,
|
||||
}
|
||||
|
||||
10
src/lib/logger.d.ts
vendored
10
src/lib/logger.d.ts
vendored
@@ -1,10 +0,0 @@
|
||||
export interface LoggerInstance {
|
||||
warn: (code?: string, ...message: unknown[]) => void
|
||||
error: (code?: string, ...message: unknown[]) => void
|
||||
debug: (code?: string, ...message: unknown[]) => void
|
||||
}
|
||||
|
||||
export declare function proxyLogger (logger: LoggerInstance, basePath: string): LoggerInstance
|
||||
|
||||
const _logger: LoggerInstance
|
||||
export default _logger
|
||||
@@ -1,34 +1,31 @@
|
||||
/** @type {import("./logger").LoggerInstance} */
|
||||
/** @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)
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the built-in logger.
|
||||
* Any `undefined` level will use the default logger.
|
||||
* @param {Partial<import("./logger").LoggerInstance>} newLogger
|
||||
* @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
|
||||
@@ -38,13 +35,13 @@ export default _logger
|
||||
|
||||
/**
|
||||
* Serializes client-side log messages and sends them to the server
|
||||
* @param {import("./logger").LoggerInstance} logger
|
||||
* @param {import("types").LoggerInstance} logger
|
||||
* @param {string} basePath
|
||||
* @return {import("./logger").LoggerInstance}
|
||||
* @return {import("types").LoggerInstance}
|
||||
*/
|
||||
export function proxyLogger (logger = _logger, basePath) {
|
||||
export function proxyLogger(logger = _logger, basePath) {
|
||||
try {
|
||||
if (typeof window === 'undefined') {
|
||||
if (typeof window === "undefined") {
|
||||
return logger
|
||||
}
|
||||
|
||||
@@ -57,21 +54,23 @@ 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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +1,34 @@
|
||||
export default (options) => {
|
||||
export default function Apple(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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
export default (options) => {
|
||||
export default function Atlassian(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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
export default (options) => {
|
||||
export default function Auth0(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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
export default (options) => {
|
||||
const tenant = options.tenantId ? options.tenantId : 'common'
|
||||
export default function AzureADB2C(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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,22 @@
|
||||
export default (options) => {
|
||||
export default function Basecamp(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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
export default (options) => {
|
||||
export default function BattleNet(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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
export default (options) => {
|
||||
export default function Box(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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +1,34 @@
|
||||
export default (options) => {
|
||||
export default function Bungie(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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
export default (options) => {
|
||||
export default function Cognito(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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
export default (options) => {
|
||||
export default function Credentials(options) {
|
||||
return {
|
||||
id: 'credentials',
|
||||
name: 'Credentials',
|
||||
type: 'credentials',
|
||||
id: "credentials",
|
||||
name: "Credentials",
|
||||
type: "credentials",
|
||||
authorize: null,
|
||||
credentials: null,
|
||||
...options
|
||||
...options,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +1,30 @@
|
||||
export default (options) => {
|
||||
export default function Discord(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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,48 +1,54 @@
|
||||
import nodemailer from 'nodemailer'
|
||||
import logger from '../lib/logger'
|
||||
import nodemailer from "nodemailer"
|
||||
import logger from "../lib/logger"
|
||||
|
||||
export default (options) => {
|
||||
export default function Email(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, // How long email links are valid for (default 24h)
|
||||
from: "NextAuth <no-reply@example.com>",
|
||||
maxAge: 24 * 60 * 60,
|
||||
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()
|
||||
})
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -52,16 +58,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, '​.')}`
|
||||
const escapedSite = `${site.replace(/\./g, '​.')}`
|
||||
const escapedEmail = `${email.replace(/\./g, "​.")}`
|
||||
const escapedSite = `${site.replace(/\./g, "​.")}`
|
||||
|
||||
// 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};">
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
export default (options) => {
|
||||
export default function EVEOnline(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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
export default (options) => {
|
||||
export default function Facebook(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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,28 @@
|
||||
export default (options) => {
|
||||
export default function FACEIT(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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
export default ({ apiVersion, ...options }) => {
|
||||
export default function Foursquare(options) {
|
||||
const { 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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
export default (options) => {
|
||||
export default function FusionAuth(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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
export default (options) => {
|
||||
export default function GitHub(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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
export default (options) => {
|
||||
export default function GitLab(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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,25 @@
|
||||
export default (options) => {
|
||||
export default function Google(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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
export default (options) => {
|
||||
export default function IdentityServer4(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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @param {import("../server").Provider} options
|
||||
* @type {import("types/providers").OAuthProvider} options
|
||||
* @example
|
||||
*
|
||||
* ```js
|
||||
@@ -22,29 +22,29 @@
|
||||
* </button>
|
||||
* ...
|
||||
* ```
|
||||
* *Resources:*
|
||||
* - [NextAuth.js Documentation](https://next-auth.js.org/providers/instagram)
|
||||
* - [Instagram Documentation](https://developers.facebook.com/docs/instagram-basic-display-api/getting-started)
|
||||
* - [Configuration](https://developers.facebook.com/apps)
|
||||
* [NextAuth.js Documentation](https://next-auth.js.org/providers/instagram) | [Instagram Documentation](https://developers.facebook.com/docs/instagram-basic-display-api/getting-started) | [Configuration](https://developers.facebook.com/apps)
|
||||
*/
|
||||
export default function Instagram (options) {
|
||||
export default function Instagram(options) {
|
||||
return {
|
||||
id: 'instagram',
|
||||
name: 'Instagram',
|
||||
type: 'oauth',
|
||||
version: '2.0',
|
||||
scope: 'user_profile',
|
||||
params: { grant_type: 'authorization_code' },
|
||||
accessTokenUrl: 'https://api.instagram.com/oauth/access_token',
|
||||
authorizationUrl: 'https://api.instagram.com/oauth/authorize?response_type=code',
|
||||
profileUrl: 'https://graph.instagram.com/me?fields=id,username,account_type,name',
|
||||
async profile (profile) {
|
||||
id: "instagram",
|
||||
name: "Instagram",
|
||||
type: "oauth",
|
||||
version: "2.0",
|
||||
scope: "user_profile",
|
||||
params: { grant_type: "authorization_code" },
|
||||
accessTokenUrl: "https://api.instagram.com/oauth/access_token",
|
||||
authorizationUrl:
|
||||
"https://api.instagram.com/oauth/authorize?response_type=code",
|
||||
profileUrl:
|
||||
"https://graph.instagram.com/me?fields=id,username,account_type,name",
|
||||
async profile(profile) {
|
||||
return {
|
||||
id: profile.id,
|
||||
name: profile.username,
|
||||
email: null,
|
||||
image: null
|
||||
image: null,
|
||||
}
|
||||
}
|
||||
},
|
||||
...options,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
export default (options) => {
|
||||
export default function Kakao(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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
export default (options) => {
|
||||
export default function LINE(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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +1,28 @@
|
||||
export default (options) => {
|
||||
export default function LinkedIn(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,
|
||||
}
|
||||
}
|
||||
|
||||
22
src/providers/mailchimp.js
Normal file
22
src/providers/mailchimp.js
Normal file
@@ -0,0 +1,22 @@
|
||||
export default function Mailchimp(options) {
|
||||
return {
|
||||
id: 'mailchimp',
|
||||
name: 'Mailchimp',
|
||||
type: 'oauth',
|
||||
version: '2.0',
|
||||
scope: '',
|
||||
params: { grant_type: 'authorization_code' },
|
||||
accessTokenUrl: 'https://login.mailchimp.com/oauth2/token',
|
||||
authorizationUrl: 'https://login.mailchimp.com/oauth2/authorize?response_type=code',
|
||||
profileUrl: 'https://login.mailchimp.com/oauth2/metadata',
|
||||
profile: (profile) => {
|
||||
return {
|
||||
id: profile.login.login_id,
|
||||
name: profile.accountname,
|
||||
email: profile.login.email,
|
||||
image: null
|
||||
}
|
||||
},
|
||||
...options
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,25 @@
|
||||
export default (options) => {
|
||||
export default function MailRu(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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
export default (options) => {
|
||||
export default function Medium(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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
export default (options) => {
|
||||
export default function Netlify(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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
export default (options) => {
|
||||
export default function Okta(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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
export default (options) => {
|
||||
export default function Osso(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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
export default (options) => {
|
||||
export default function Reddit(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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
export default (options) => {
|
||||
export default function Salesforce(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', // REVIEW: Can we use "pkce" ?
|
||||
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",
|
||||
profile(profile) {
|
||||
return {
|
||||
...profile,
|
||||
id: profile.user_id,
|
||||
image: profile.picture
|
||||
image: profile.picture,
|
||||
}
|
||||
},
|
||||
...options
|
||||
...options,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,26 @@
|
||||
export default (options) => {
|
||||
export default function Slack(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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
export default (options) => {
|
||||
export default function Spotify(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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
export default (options) => {
|
||||
export default function Strava(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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
export default (options) => {
|
||||
export default function Twitch(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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
export default (options) => {
|
||||
export default function Twitter(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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +1,29 @@
|
||||
export default (options) => {
|
||||
const apiVersion = '5.126' // https://vk.com/dev/versions
|
||||
export default function VK(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,
|
||||
}
|
||||
}
|
||||
|
||||
23
src/providers/wordpress.js
Normal file
23
src/providers/wordpress.js
Normal file
@@ -0,0 +1,23 @@
|
||||
export default function WordPress(options) {
|
||||
return {
|
||||
id: "wordpress",
|
||||
name: "WordPress.com",
|
||||
type: "oauth",
|
||||
version: "2.0",
|
||||
scope: "auth",
|
||||
params: { grant_type: "authorization_code" },
|
||||
accessTokenUrl: "https://public-api.wordpress.com/oauth2/token",
|
||||
authorizationUrl:
|
||||
"https://public-api.wordpress.com/oauth2/authorize?response_type=code",
|
||||
profileUrl: "https://public-api.wordpress.com/rest/v1/me",
|
||||
profile(profile) {
|
||||
return {
|
||||
id: profile.ID,
|
||||
name: profile.display_name,
|
||||
email: profile.email,
|
||||
image: profile.avatar_URL,
|
||||
}
|
||||
},
|
||||
...options,
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,23 @@
|
||||
export default (options) => {
|
||||
export default function Yandex(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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
export default (options) => {
|
||||
export default function Zoho(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,
|
||||
}
|
||||
}
|
||||
|
||||
94
src/server/index.d.ts
vendored
94
src/server/index.d.ts
vendored
@@ -1,94 +0,0 @@
|
||||
import { NextApiHandler, NextApiRequest, NextApiResponse } from 'next'
|
||||
import { LoggerInstance } from 'src/lib/logger'
|
||||
import { CallbacksOptions } from './lib/callbacks'
|
||||
import { CookiesOptions } from './lib/cookie'
|
||||
import { EventsOptions } from './lib/events'
|
||||
|
||||
export interface Provider {
|
||||
id: string
|
||||
name: string
|
||||
type: string
|
||||
version: string
|
||||
params: Record<string, unknown>
|
||||
scope: string
|
||||
accessTokenUrl: string
|
||||
authorizationUrl: string
|
||||
profileUrl?: string
|
||||
grant_type?: string
|
||||
profile?: (profile: any) => Promise<any>
|
||||
}
|
||||
|
||||
/** @docs https://next-auth.js.org/configuration/options */
|
||||
export interface NextAuthOptions {
|
||||
/** @docs https://next-auth.js.org/configuration/options#theme */
|
||||
theme?: 'auto' | 'dark' | 'light'
|
||||
/** @docs https://next-auth.js.org/configuration/options#providers */
|
||||
providers: Provider[]
|
||||
/** @docs https://next-auth.js.org/configuration/options#database */
|
||||
database?: any
|
||||
/** @docs https://next-auth.js.org/configuration/options#secret */
|
||||
secret?: any
|
||||
/** @docs https://next-auth.js.org/configuration/options#session */
|
||||
session?: any
|
||||
/** @docs https://next-auth.js.org/configuration/options#jwt */
|
||||
jwt?: any
|
||||
/** @docs https://next-auth.js.org/configuration/options#pages */
|
||||
pages?: {
|
||||
signIn?: string
|
||||
signOut?: string
|
||||
/** Error code passed in query string as ?error= */
|
||||
error?: string
|
||||
verifyRequest?: string
|
||||
/** If set, new users will be directed here on first sign in */
|
||||
newUser?: string
|
||||
}
|
||||
/**
|
||||
* Callbacks are asynchronous functions you can use to control what happens when an action is performed.
|
||||
* Callbacks are extremely powerful, especially in scenarios involving JSON Web Tokens as
|
||||
* they allow you to implement access controls without a database and
|
||||
* to integrate with external databases or APIs.
|
||||
* @docs https://next-auth.js.org/configuration/options#callbacks
|
||||
*/
|
||||
callbacks?: CallbacksOptions
|
||||
/** @docs https://next-auth.js.org/configuration/options#events */
|
||||
events?: EventsOptions
|
||||
/** @docs https://next-auth.js.org/configuration/options#adapter */
|
||||
adapter?: any
|
||||
/** @docs https://next-auth.js.org/configuration/options#debug */
|
||||
debug?: boolean
|
||||
/** @docs https://next-auth.js.org/configuration/options#usesecurecookies */
|
||||
useSecureCookies?: boolean
|
||||
/** @docs https://next-auth.js.org/configuration/options#cookies */
|
||||
cookies?: CookiesOptions
|
||||
/** @docs https://next-auth.js.org/configuration/options#logger */
|
||||
logger: LoggerInstance
|
||||
}
|
||||
|
||||
/** Options that are the same both in internal and user provided options. */
|
||||
export type NextAuthSharedOptions = 'pages' | 'jwt' | 'events' | 'callbacks' | 'cookies' | 'secret' | 'adapter' | 'theme' | 'debug' | 'logger'
|
||||
|
||||
export interface NextAuthInternalOptions extends Pick<NextAuthOptions, NextAuthSharedOptions> {
|
||||
pkce?: {
|
||||
code_verifier?: string
|
||||
/**
|
||||
* Could be `"plain"`, but not recommended.
|
||||
* We ignore it for now.
|
||||
* @spec https://tools.ietf.org/html/rfc7636#section-4.2.
|
||||
*/
|
||||
code_challenge_method?: 'S256'
|
||||
}
|
||||
provider?: Provider
|
||||
baseUrl?: string
|
||||
basePath?: string
|
||||
action?: string
|
||||
csrfToken?: string
|
||||
}
|
||||
|
||||
export interface NextAuthRequest extends NextApiRequest {
|
||||
options: NextAuthInternalOptions
|
||||
}
|
||||
|
||||
export interface NextAuthResponse extends NextApiResponse {}
|
||||
|
||||
export declare function NextAuthHandler (req: NextAuthRequest, res: NextAuthResponse, options: NextAuthOptions): ReturnType<NextApiHandler>
|
||||
export declare function NextAuthHandler (options: NextAuthOptions): ReturnType<NextApiHandler>
|
||||
@@ -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 csrfTokenHandler from './lib/csrf-token-handler'
|
||||
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 * as pkce from './lib/oauth/pkce-handler'
|
||||
import * as state from './lib/oauth/state-handler'
|
||||
|
||||
@@ -24,7 +24,7 @@ if (!process.env.NEXTAUTH_URL) {
|
||||
/**
|
||||
* @param {import("next").NextApiRequest} req
|
||||
* @param {import("next").NextApiResponse} res
|
||||
* @param {import(".").NextAuthOptions} userOptions
|
||||
* @param {import("types").NextAuthOptions} userOptions
|
||||
*/
|
||||
async function NextAuthHandler (req, res, userOptions) {
|
||||
if (userOptions.logger) {
|
||||
@@ -67,16 +67,18 @@ 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)
|
||||
|
||||
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?
|
||||
// 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]
|
||||
}
|
||||
}
|
||||
|
||||
const maxAge = 30 * 24 * 60 * 60 // Sessions expire after 30 days of being idle
|
||||
@@ -103,7 +105,6 @@ async function NextAuthHandler (req, res, userOptions) {
|
||||
provider,
|
||||
cookies,
|
||||
secret,
|
||||
csrfToken,
|
||||
providers,
|
||||
// Session options
|
||||
session: {
|
||||
@@ -134,6 +135,7 @@ async function NextAuthHandler (req, res, userOptions) {
|
||||
logger
|
||||
}
|
||||
|
||||
csrfTokenHandler(req, res)
|
||||
await callbackUrlHandler(req, res)
|
||||
|
||||
const render = renderPage(req, res)
|
||||
@@ -146,7 +148,7 @@ async function NextAuthHandler (req, res, userOptions) {
|
||||
case 'session':
|
||||
return routes.session(req, res)
|
||||
case 'csrf':
|
||||
return res.json({ csrfToken })
|
||||
return res.json({ csrfToken: req.options.csrfToken })
|
||||
case 'signin':
|
||||
if (pages.signIn) {
|
||||
let signinUrl = `${pages.signIn}${pages.signIn.includes('?') ? '&' : '?'}callbackUrl=${req.options.callbackUrl}`
|
||||
@@ -199,7 +201,7 @@ async function NextAuthHandler (req, res, userOptions) {
|
||||
switch (action) {
|
||||
case 'signin':
|
||||
// Verified CSRF Token required for all sign in routes
|
||||
if (csrfTokenVerified && provider) {
|
||||
if (req.options.csrfTokenVerified && provider) {
|
||||
if (await pkce.handleSignin(req, res)) return
|
||||
if (await state.handleSignin(req, res)) return
|
||||
return routes.signin(req, res)
|
||||
@@ -208,14 +210,14 @@ async function NextAuthHandler (req, res, userOptions) {
|
||||
return res.redirect(`${baseUrl}${basePath}/signin?csrf=true`)
|
||||
case 'signout':
|
||||
// Verified CSRF Token required for signout
|
||||
if (csrfTokenVerified) {
|
||||
if (req.options.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' && !csrfTokenVerified) {
|
||||
if (provider.type === 'credentials' && !req.options.csrfTokenVerified) {
|
||||
return res.redirect(`${baseUrl}${basePath}/signin?csrf=true`)
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user