mirror of
https://github.com/SrIzan10/next-auth.git
synced 2026-05-01 10:55:20 +00:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
564b342f69 | ||
|
|
63638d81dc | ||
|
|
28683015f1 | ||
|
|
726c49603d | ||
|
|
a7113c6d3e | ||
|
|
910514c6e2 | ||
|
|
b7cca484cf | ||
|
|
e293e786a8 | ||
|
|
82dd6ba3e4 | ||
|
|
6e28a07746 | ||
|
|
61047e3c14 | ||
|
|
dc5f3f481d | ||
|
|
0343344802 | ||
|
|
134a95a4bd | ||
|
|
52a4bd97cd | ||
|
|
87d43e4038 | ||
|
|
68695af1f3 | ||
|
|
76df2b5e70 | ||
|
|
8bd9d87633 | ||
|
|
6af40e3fe2 |
@@ -1,8 +1,13 @@
|
||||
# Rename file to .env.local and populate values
|
||||
# Rename file to .env.local (or .env) and populate values
|
||||
# to be able to run the dev app
|
||||
|
||||
NEXTAUTH_URL=http://localhost:3000
|
||||
SECRET= Linux: `openssl rand -hex 32` or https://generate-secret.now.sh/32
|
||||
|
||||
# You can use `openssl rand -hex 32` or
|
||||
# https://generate-secret.now.sh/32 to generate a secret.
|
||||
# Note: Changing a secret may invalidate existing sessions
|
||||
# and/or verificaion tokens.
|
||||
SECRET=
|
||||
|
||||
AUTH0_ID=
|
||||
AUTH0_DOMAIN=
|
||||
@@ -12,4 +17,17 @@ GITHUB_ID=
|
||||
GITHUB_SECRET=
|
||||
|
||||
TWITTER_ID=
|
||||
TWITTER_SECRET=
|
||||
TWITTER_SECRET=
|
||||
|
||||
# Example configuration for a Gmail account (will need SMTP enabled)
|
||||
EMAIL_SERVER=smtps://user@gmail.com:password@smtp.gmail.com:465
|
||||
EMAIL_FROM=user@gmail.com
|
||||
|
||||
# You can use any of these as the "DATABASE_URL" for
|
||||
# databases started with Docker using `npm run db:start`.
|
||||
# Note: If using with Prisma adapter, you need to use a `.env`
|
||||
# file rather than a `.env.local` file to configure env vars.
|
||||
# Postgres: DATABASE_URL=postgres://nextauth:password@127.0.0.1:5432/nextauth?synchronize=true
|
||||
# MySQL: DATABASE_URL=mysql://nextauth:password@127.0.0.1:3306/nextauth?synchronize=true
|
||||
# MongoDB: DATABASE_URL=mongodb://nextauth:password@127.0.0.1:27017/nextauth?synchronize=true
|
||||
DATABASE_URL=
|
||||
14
.github/labeler.yml
vendored
14
.github/labeler.yml
vendored
@@ -19,3 +19,17 @@ databases:
|
||||
- test/docker/databases/**/*
|
||||
- www/docs/configuration/databases.md
|
||||
- test/fixtures/**/*
|
||||
|
||||
core:
|
||||
- src/**/*
|
||||
|
||||
style:
|
||||
- src/css/**/*
|
||||
|
||||
client:
|
||||
- src/client/**/*
|
||||
- www/docs/getting-started/client.md
|
||||
|
||||
pages:
|
||||
- src/server/pages/**/*
|
||||
- www/docs/configuration/pages.md
|
||||
19
.github/workflows/build.yml
vendored
19
.github/workflows/build.yml
vendored
@@ -1,31 +1,30 @@
|
||||
# Simple check that the build is valid and no linting errors.
|
||||
# Currently is run as a seperate workflow as it's fast to fail.
|
||||
name: Build Test
|
||||
name: Lint/Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- canary
|
||||
- next
|
||||
pull_request:
|
||||
branches:
|
||||
branches:
|
||||
- main
|
||||
- canary
|
||||
- next
|
||||
|
||||
jobs:
|
||||
build:
|
||||
lint-and-build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [10.x, 12.x, 14.x]
|
||||
|
||||
node-version: [10, 12, 14]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- run: npm ci
|
||||
- run: npm run build
|
||||
- name: Install dependencies
|
||||
uses: bahmutov/npm-install@v1
|
||||
- run: npm run lint
|
||||
- run: npm run build
|
||||
67
.github/workflows/codeql-analysis.yml
vendored
Normal file
67
.github/workflows/codeql-analysis.yml
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
#
|
||||
# ******** NOTE ********
|
||||
# We have attempted to detect the languages in your repository. Please check
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, next ]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ main ]
|
||||
schedule:
|
||||
- cron: '43 17 * * 2'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'javascript' ]
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
|
||||
# Learn more:
|
||||
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||
# and modify them (or add more) to build your code if your project
|
||||
# uses a compiled language
|
||||
|
||||
#- run: |
|
||||
# make bootstrap
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
11
.github/workflows/integration.yml
vendored
11
.github/workflows/integration.yml
vendored
@@ -2,9 +2,10 @@ name: Integration Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, canary ]
|
||||
branches:
|
||||
- main
|
||||
- next
|
||||
pull_request:
|
||||
branches: [ main, canary ]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
@@ -28,7 +29,7 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [12.x]
|
||||
node-version: [10, 12, 14]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
@@ -37,8 +38,8 @@ jobs:
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
|
||||
# Install dependencies
|
||||
- run: npm ci
|
||||
- name: Install dependencies
|
||||
uses: bahmutov/npm-install@v1
|
||||
|
||||
# Run tests (build library, build + start test app in Docker, run tests)
|
||||
- run: npm test
|
||||
|
||||
1
.github/workflows/labeler.yml
vendored
1
.github/workflows/labeler.yml
vendored
@@ -9,4 +9,3 @@ jobs:
|
||||
- uses: actions/labeler@main
|
||||
with:
|
||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
sync-labels: true
|
||||
28
.github/workflows/release.yml
vendored
28
.github/workflows/release.yml
vendored
@@ -2,29 +2,25 @@ name: Release
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- canary
|
||||
- 'main'
|
||||
- 'next'
|
||||
- '3.x'
|
||||
pull_request:
|
||||
jobs:
|
||||
release:
|
||||
name: Release
|
||||
runs-on: ubuntu-20.04
|
||||
name: 'Release'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12
|
||||
node-version: 14
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Lint
|
||||
run: npm run lint
|
||||
- name: Build
|
||||
run: npm run build
|
||||
- name: Release
|
||||
uses: bahmutov/npm-install@v1
|
||||
- run: npm run build
|
||||
- run: npx semantic-release@17
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
run: npx semantic-release
|
||||
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
||||
NPM_TOKEN: ${{secrets.NPM_TOKEN}}
|
||||
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -24,6 +24,7 @@ node_modules
|
||||
.docusaurus
|
||||
.cache-loader
|
||||
.next
|
||||
www/providers.json
|
||||
|
||||
# VS
|
||||
/.vs/slnx.sqlite-journal
|
||||
@@ -33,4 +34,7 @@ node_modules
|
||||
|
||||
# GitHub Actions runner
|
||||
/actions-runner
|
||||
/_work
|
||||
/_work
|
||||
|
||||
# Prisma migrations
|
||||
/prisma/migrations
|
||||
@@ -1,39 +0,0 @@
|
||||
{
|
||||
"branches": [
|
||||
"main",
|
||||
{ "name": "canary", "prerelease": true }
|
||||
],
|
||||
"plugins": [
|
||||
["@semantic-release/commit-analyzer", {
|
||||
"preset": "conventionalcommits",
|
||||
"releaseRules": [
|
||||
{ "breaking": true, "release": "major" },
|
||||
{ "revert": true, "release": "patch" },
|
||||
{ "type": "feat", "release": "minor" },
|
||||
{ "type": "fix", "release": "patch" },
|
||||
{ "type": "perf", "release": "patch" },
|
||||
{ "type": "docs", "release": "patch" }
|
||||
]
|
||||
}],
|
||||
["@semantic-release/release-notes-generator", {
|
||||
"preset": "conventionalcommits",
|
||||
"presetConfig": {
|
||||
"types": [
|
||||
{ "type": "feat", "section": "Features", "hidden": false },
|
||||
{ "type": "fix", "section": "Bug Fixes", "hidden": false },
|
||||
{ "type": "perf", "section": "Performance Improvements", "hidden": false },
|
||||
{ "type": "revert", "section": "Reverts", "hidden": false },
|
||||
{ "type": "docs", "section": "Documentation", "hidden": false },
|
||||
{ "type": "style", "section": "Styles", "hidden": false },
|
||||
{ "type": "chore", "section": "Miscellaneous Chores", "hidden": false },
|
||||
{ "type": "refactor", "section": "Code Refactoring", "hidden": false },
|
||||
{ "type": "test", "section": "Tests", "hidden": false },
|
||||
{ "type": "build", "section": "Build System", "hidden": false },
|
||||
{ "type": "ci", "section": "Continuous Integration", "hidden": false }
|
||||
]
|
||||
}
|
||||
}],
|
||||
"@semantic-release/github",
|
||||
"@semantic-release/npm"
|
||||
]
|
||||
}
|
||||
@@ -13,10 +13,9 @@ Please raise any significant new functionality or breaking change an issue for d
|
||||
Anyone can be a contributor. Either you found a typo, or you have an awesome feature request you could implement, we encourage you to create a Pull Request.
|
||||
### Pull Requests
|
||||
|
||||
* The latest changes are always in `canary`, so please make your Pull Request against that branch.
|
||||
* 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
|
||||
* Rebasing in Pull Requests is preferred to keep a clean commit history (see below)
|
||||
* Run `npm run lint:fix` before committing to make resolving conflicts easier (VSCode users, check out [this extension](https://marketplace.visualstudio.com/items?itemName=chenxsan.vscode-standardjs) to fix lint issues in development)
|
||||
* We encourage you to test your changes, and if you have the opportunity, please make those tests part of the Pull Request
|
||||
* If you add new functionality, please provide the corresponding documentation as well and make it part of the Pull Request
|
||||
@@ -89,7 +88,7 @@ We use [semantic-release](https://github.com/semantic-release/semantic-release)
|
||||
When accepting Pull Requests, make sure the following:
|
||||
|
||||
* Use "Squash and merge"
|
||||
* Make sure you merge contributor PRs into `canary`
|
||||
* Make sure you merge contributor PRs into `main`
|
||||
* Rewrite the commit message to conform to the `Conventional Commits` style. Check the "Recommended Scopes" section for further advice.
|
||||
* Optionally link issues the PR will resolve (You can add "close" in front of the issue numbers to close the issues automatically, when the PR is merged. `semantic-release` will also comment back to connected issues and PRs, notifying the users that a feature is added/bug fixed, etc.)
|
||||
|
||||
|
||||
3
FUNDING.yml
Normal file
3
FUNDING.yml
Normal file
@@ -0,0 +1,3 @@
|
||||
# https://docs.github.com/en/github/administering-a-repository/displaying-a-sponsor-button-in-your-repository
|
||||
|
||||
github: [balazsorban44]
|
||||
25
README.md
25
README.md
@@ -7,12 +7,25 @@
|
||||
Open Source. Full Stack. Own Your Data.
|
||||
</p>
|
||||
<p align="center" style="align: center;">
|
||||
<img src="https://github.com/nextauthjs/next-auth/workflows/Build%20Test/badge.svg" alt="Build Test" />
|
||||
<img src="https://github.com/nextauthjs/next-auth/workflows/Integration%20Test/badge.svg" alt="Integration Test" />
|
||||
<img src="https://img.shields.io/bundlephobia/minzip/next-auth" alt="Bundle Size"/>
|
||||
<img src="https://img.shields.io/npm/dm/next-auth" alt="Downloads" />
|
||||
<img src="https://img.shields.io/github/stars/nextauthjs/next-auth" alt="Github Stars" />
|
||||
<img src="https://img.shields.io/github/v/release/nextauthjs/next-auth?include_prereleases" alt="Github Release" />
|
||||
<a href="https://github.com/nextauthjs/next-auth/actions?query=workflow%3ARelease">
|
||||
<img src="https://github.com/nextauthjs/next-auth/workflows/Release/badge.svg" alt="Release" />
|
||||
</a>
|
||||
<a href="https://github.com/nextauthjs/next-auth/actions?query=workflow%3A%22Integration+Test%22">
|
||||
<img src="https://github.com/nextauthjs/next-auth/workflows/Integration%20Test/badge.svg" alt="Integration Test" />
|
||||
</a>
|
||||
<a href="https://bundlephobia.com/result?p=next-auth">
|
||||
<img src="https://img.shields.io/bundlephobia/minzip/next-auth" alt="Bundle Size"/>
|
||||
</a>
|
||||
<a href="https://www.npmtrends.com/next-auth">
|
||||
<img src="https://img.shields.io/npm/dm/next-auth" alt="Downloads" />
|
||||
</a>
|
||||
<a href="https://github.com/nextauthjs/next-auth/stargazers">
|
||||
<img src="https://img.shields.io/github/stars/nextauthjs/next-auth" alt="Github Stars" />
|
||||
</a>
|
||||
<a href="https://www.npmjs.com/package/next-auth">
|
||||
<img src="https://img.shields.io/github/v/release/nextauthjs/next-auth" alt="Github Stable Release" />
|
||||
</a>
|
||||
<img src="https://img.shields.io/github/v/release/nextauthjs/next-auth?include_prereleases" alt="Github Prelease" />
|
||||
</p>
|
||||
</p>
|
||||
|
||||
|
||||
@@ -5,11 +5,14 @@ export default function AccessDenied () {
|
||||
<>
|
||||
<h1>Access Denied</h1>
|
||||
<p>
|
||||
<a href="/api/auth/signin"
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
signIn()
|
||||
}}>You must be signed in to view this page</a>
|
||||
<a
|
||||
href='/api/auth/signin'
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
signIn()
|
||||
}}
|
||||
>You must be signed in to view this page
|
||||
</a>
|
||||
</p>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import Link from 'next/link'
|
||||
import { signIn, signOut, useSession } from 'next-auth/client'
|
||||
import * as client from 'next-auth/client'
|
||||
import styles from './header.module.css'
|
||||
|
||||
// The approach used in this component shows how to built a sign in and sign out
|
||||
@@ -26,7 +25,7 @@ export default function Header () {
|
||||
You are not signed in
|
||||
</span>
|
||||
<a
|
||||
href="/api/auth/signin"
|
||||
href='/api/auth/signin'
|
||||
className={styles.buttonPrimary}
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
@@ -51,7 +50,7 @@ export default function Header () {
|
||||
<strong>{session.user.email || session.user.name}</strong>
|
||||
</span>
|
||||
<a
|
||||
href="/api/auth/signout"
|
||||
href='/api/auth/signout'
|
||||
className={styles.button}
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
@@ -96,6 +95,11 @@ export default function Header () {
|
||||
<a>API</a>
|
||||
</Link>
|
||||
</li>
|
||||
<li className={styles.navItem}>
|
||||
<Link href='/credentials'>
|
||||
<a>Credentials</a>
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"next-auth": ["./src/server"],
|
||||
"next-auth/adapters": ["./src/adapters"],
|
||||
"next-auth/client": ["./src/client"],
|
||||
"next-auth/jwt": ["./src/lib/jwt"],
|
||||
"next-auth/providers": ["./src/providers"]
|
||||
}
|
||||
}
|
||||
}
|
||||
2
next-env.d.ts
vendored
Normal file
2
next-env.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/types/global" />
|
||||
859
package-lock.json
generated
859
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
30
package.json
30
package.json
@@ -29,8 +29,8 @@
|
||||
"prepublishOnly": "npm run build",
|
||||
"publish:beta": "npm publish --tag beta",
|
||||
"publish:canary": "npm publish --tag canary",
|
||||
"lint": "standard",
|
||||
"lint:fix": "standard --fix"
|
||||
"lint": "ts-standard",
|
||||
"lint:fix": "ts-standard --fix"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
@@ -64,21 +64,24 @@
|
||||
"mysql": "^2.18.1",
|
||||
"mssql": "^6.2.1",
|
||||
"pg": "^8.2.1",
|
||||
"@prisma/client": "^2.12.0"
|
||||
"@prisma/client": "^2.16.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.8.4",
|
||||
"@babel/core": "^7.9.6",
|
||||
"@babel/preset-env": "^7.9.6",
|
||||
"@prisma/client": "^2.16.1",
|
||||
"@semantic-release/commit-analyzer": "^8.0.1",
|
||||
"@semantic-release/github": "^7.2.0",
|
||||
"@semantic-release/npm": "7.0.8",
|
||||
"@semantic-release/release-notes-generator": "^9.0.1",
|
||||
"@types/react": "^17.0.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",
|
||||
"eslint": "^7.19.0",
|
||||
"mocha": "^8.1.3",
|
||||
"mongodb": "^3.5.9",
|
||||
"mssql": "^6.2.1",
|
||||
@@ -87,18 +90,29 @@
|
||||
"pg": "^8.2.1",
|
||||
"postcss-cli": "^7.1.1",
|
||||
"postcss-nested": "^4.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",
|
||||
"standard": "^16.0.3"
|
||||
"ts-standard": "^10.0.0",
|
||||
"typescript": "^4.1.3"
|
||||
},
|
||||
"standard": {
|
||||
"ts-standard": {
|
||||
"project": "./tsconfig.json",
|
||||
"ignore": [
|
||||
"test/",
|
||||
"pages/",
|
||||
"components/"
|
||||
"next-env.d.ts"
|
||||
],
|
||||
"globals": [
|
||||
"fetch"
|
||||
]
|
||||
}
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"type" : "github",
|
||||
"url" : "https://github.com/sponsors/balazsorban44"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -8,10 +8,10 @@ export default function Page () {
|
||||
<p><em>You must be signed in to see responses.</em></p>
|
||||
<h2>Session</h2>
|
||||
<p>/api/examples/session</p>
|
||||
<iframe src="/api/examples/session"/>
|
||||
<iframe src='/api/examples/session' />
|
||||
<h2>JSON Web Token</h2>
|
||||
<p>/api/examples/jwt</p>
|
||||
<iframe src="/api/examples/jwt"/>
|
||||
<iframe src='/api/examples/jwt' />
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,92 +1,61 @@
|
||||
import NextAuth from "next-auth"
|
||||
import Providers from "next-auth/providers"
|
||||
import NextAuth from 'next-auth'
|
||||
import Providers from 'next-auth/providers'
|
||||
|
||||
// import Adapters from 'next-auth/adapters'
|
||||
// import { PrismaClient } from '@prisma/client'
|
||||
// const prisma = new PrismaClient()
|
||||
|
||||
export default NextAuth({
|
||||
// https://next-auth.js.org/configuration/providers
|
||||
providers: [
|
||||
Providers.Email({
|
||||
server: process.env.EMAIL_SERVER,
|
||||
from: process.env.EMAIL_FROM
|
||||
}),
|
||||
Providers.GitHub({
|
||||
clientId: process.env.GITHUB_ID,
|
||||
clientSecret: process.env.GITHUB_SECRET,
|
||||
clientSecret: process.env.GITHUB_SECRET
|
||||
}),
|
||||
Providers.Auth0({
|
||||
clientId: process.env.AUTH0_ID,
|
||||
clientSecret: process.env.AUTH0_SECRET,
|
||||
domain: process.env.AUTH0_DOMAIN,
|
||||
protection: "pkce"
|
||||
protection: 'pkce'
|
||||
}),
|
||||
Providers.Twitter({
|
||||
clientId: process.env.TWITTER_ID,
|
||||
clientSecret: process.env.TWITTER_SECRET,
|
||||
clientSecret: process.env.TWITTER_SECRET
|
||||
}),
|
||||
Providers.Credentials({
|
||||
name: 'Credentials',
|
||||
credentials: {
|
||||
password: { label: 'Password', type: 'password' }
|
||||
},
|
||||
async authorize (credentials) {
|
||||
if (credentials.password === 'password') {
|
||||
return {
|
||||
id: 1,
|
||||
name: 'Fill Murray',
|
||||
email: 'bill@fillmurray.com',
|
||||
image: 'https://www.fillmurray.com/64/64'
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
})
|
||||
],
|
||||
// Database optional. MySQL, Maria DB, Postgres and MongoDB are supported.
|
||||
// https://next-auth.js.org/configuration/databases
|
||||
//
|
||||
// Notes:
|
||||
// * You must to install an appropriate node_module for your database
|
||||
// * The Email provider requires a database (OAuth providers do not)
|
||||
|
||||
// The secret should be set to a reasonably long random string.
|
||||
// It is used to sign cookies and to sign and encrypt JSON Web Tokens, unless
|
||||
// a separate secret is defined explicitly for encrypting the JWT.
|
||||
|
||||
session: {
|
||||
// Use JSON Web Tokens for session instead of database sessions.
|
||||
// This option can be used with or without a database for users/accounts.
|
||||
// Note: `jwt` is automatically set to `true` if no database is specified.
|
||||
jwt: true,
|
||||
|
||||
// Seconds - How long until an idle session expires and is no longer valid.
|
||||
// maxAge: 30 * 24 * 60 * 60, // 30 days
|
||||
|
||||
// Seconds - Throttle how frequently to write to database to extend a session.
|
||||
// Use it to limit write operations. Set to 0 to always update the database.
|
||||
// Note: This option is ignored if using JSON Web Tokens
|
||||
// updateAge: 24 * 60 * 60, // 24 hours
|
||||
},
|
||||
|
||||
// JSON Web tokens are only used for sessions if the `jwt: true` session
|
||||
// option is set - or by default if no database is specified.
|
||||
// https://next-auth.js.org/configuration/options#jwt
|
||||
jwt: {
|
||||
encryption: true,
|
||||
secret: process.env.SECRET,
|
||||
// A secret to use for key generation (you should set this explicitly)
|
||||
// secret: 'INp8IvdIyeMcoGAgFGoA61DdBglwwSqnXJZkgz8PSnw',
|
||||
// Set to true to use encryption (default: false)
|
||||
// encryption: true,
|
||||
// You can define your own encode/decode functions for signing and encryption
|
||||
// if you want to override the default behaviour.
|
||||
// encode: async ({ secret, token, maxAge }) => {},
|
||||
// decode: async ({ secret, token, maxAge }) => {},
|
||||
secret: process.env.SECRET
|
||||
},
|
||||
|
||||
// You can define custom pages to override the built-in pages.
|
||||
// The routes shown here are the default URLs that will be used when a custom
|
||||
// pages is not specified for that route.
|
||||
// https://next-auth.js.org/configuration/pages
|
||||
pages: {
|
||||
// signIn: '/api/auth/signin', // Displays signin buttons
|
||||
// signOut: '/api/auth/signout', // Displays form with sign out button
|
||||
// error: '/api/auth/error', // Error code passed in query string as ?error=
|
||||
// verifyRequest: '/api/auth/verify-request', // Used for check email page
|
||||
// newUser: null // If set, new users will be directed here on first sign in
|
||||
},
|
||||
|
||||
// Callbacks are asynchronous functions you can use to control what happens
|
||||
// when an action is performed.
|
||||
// https://next-auth.js.org/configuration/callbacks
|
||||
callbacks: {
|
||||
// signIn: async (user, account, profile) => { return Promise.resolve(true) },
|
||||
// redirect: async (url, baseUrl) => { return Promise.resolve(baseUrl) },
|
||||
// session: async (session, user) => { return Promise.resolve(session) },
|
||||
// jwt: async (token, user, account, profile, isNewUser) => { return Promise.resolve(token) }
|
||||
},
|
||||
|
||||
// Events are useful for logging
|
||||
// https://next-auth.js.org/configuration/events
|
||||
events: {},
|
||||
|
||||
// Enable debug messages in the console if you are having problems
|
||||
debug: false,
|
||||
theme: 'auto'
|
||||
|
||||
// Default Database Adapter (TypeORM)
|
||||
// database: process.env.DATABASE_URL
|
||||
|
||||
// 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
|
||||
// adapter: Adapters.Prisma.Adapter({ prisma })
|
||||
})
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// This is an example of how to read a JSON Web Token from an API route
|
||||
import jwt from "next-auth/jwt"
|
||||
import jwt from 'next-auth/jwt'
|
||||
|
||||
const secret = process.env.SECRET
|
||||
|
||||
export default async (req, res) => {
|
||||
const token = await jwt.getToken({ req, secret, encryption: true })
|
||||
const token = await jwt.getToken({ req, secret })
|
||||
res.send(JSON.stringify(token, null, 2))
|
||||
}
|
||||
|
||||
@@ -9,4 +9,4 @@ export default async (req, res) => {
|
||||
} else {
|
||||
res.send({ error: 'You must be sign in to view the protected content on this page.' })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,4 +4,4 @@ import { getSession } from 'next-auth/client'
|
||||
export default async (req, res) => {
|
||||
const session = await getSession({ req })
|
||||
res.send(JSON.stringify(session, null, 2))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,4 +19,4 @@ export default function Page () {
|
||||
</p>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
52
pages/credentials.js
Normal file
52
pages/credentials.js
Normal file
@@ -0,0 +1,52 @@
|
||||
import * as React from 'react'
|
||||
import { signIn, signOut, useSession } from 'next-auth/client'
|
||||
import Layout from 'components/layout'
|
||||
|
||||
export default function Page () {
|
||||
const [response, setResponse] = React.useState(null)
|
||||
const handleLogin = (options) => async () => {
|
||||
if (options.redirect) {
|
||||
return signIn('credentials', options)
|
||||
}
|
||||
const response = await signIn('credentials', options)
|
||||
setResponse(response)
|
||||
}
|
||||
|
||||
const handleLogout = (options) => async () => {
|
||||
if (options.redirect) {
|
||||
return signOut(options)
|
||||
}
|
||||
const response = await signOut(options)
|
||||
setResponse(response)
|
||||
}
|
||||
|
||||
const [session] = useSession()
|
||||
|
||||
if (session) {
|
||||
return (
|
||||
<Layout>
|
||||
<h1>Test different flows for Credentials logout</h1>
|
||||
<span className='spacing'>Default:</span>
|
||||
<button onClick={handleLogout({ redirect: true })}>Logout</button><br />
|
||||
<span className='spacing'>No redirect:</span>
|
||||
<button onClick={handleLogout({ redirect: false })}>Logout</button><br />
|
||||
<p>Response:</p>
|
||||
<pre style={{ background: '#eee', padding: 16 }}>{JSON.stringify(response, null, 2)}</pre>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<h1>Test different flows for Credentials login</h1>
|
||||
<span className='spacing'>Default:</span>
|
||||
<button onClick={handleLogin({ redirect: true, password: 'password' })}>Login</button><br />
|
||||
<span className='spacing'>No redirect:</span>
|
||||
<button onClick={handleLogin({ redirect: false, password: 'password' })}>Login</button><br />
|
||||
<span className='spacing'>No redirect, wrong password:</span>
|
||||
<button onClick={handleLogin({ redirect: false, password: '' })}>Login</button>
|
||||
<p>Response:</p>
|
||||
<pre style={{ background: '#eee', padding: 16 }}>{JSON.stringify(response, null, 2)}</pre>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
@@ -4,7 +4,7 @@ export default function Page () {
|
||||
return (
|
||||
<Layout>
|
||||
<p>
|
||||
This is an example site to demonstrate how to use <a href={`https://next-auth.js.org`}>NextAuth.js</a> for authentication.
|
||||
This is an example site to demonstrate how to use <a href='https://next-auth.js.org'>NextAuth.js</a> for authentication.
|
||||
</p>
|
||||
<h2>Terms of Service</h2>
|
||||
<p>
|
||||
@@ -27,4 +27,4 @@ export default function Page () {
|
||||
</p>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import AccessDenied from '../components/access-denied'
|
||||
|
||||
export default function Page ({ content, session }) {
|
||||
// If no session exists, display access denied message
|
||||
if (!session) { return <Layout><AccessDenied/></Layout> }
|
||||
if (!session) { return <Layout><AccessDenied /></Layout> }
|
||||
|
||||
// If session exists, display content
|
||||
return (
|
||||
@@ -16,7 +16,7 @@ export default function Page ({ content, session }) {
|
||||
)
|
||||
}
|
||||
|
||||
export async function getServerSideProps(context) {
|
||||
export async function getServerSideProps (context) {
|
||||
const session = await getSession(context)
|
||||
let content = null
|
||||
|
||||
|
||||
@@ -4,24 +4,24 @@ import Layout from '../components/layout'
|
||||
import AccessDenied from '../components/access-denied'
|
||||
|
||||
export default function Page () {
|
||||
const [ session, loading ] = useSession()
|
||||
const [ content , setContent ] = useState()
|
||||
const [session, loading] = useSession()
|
||||
const [content, setContent] = useState()
|
||||
|
||||
// Fetch content from protected route
|
||||
useEffect(()=>{
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
const res = await fetch('/api/examples/protected')
|
||||
const json = await res.json()
|
||||
if (json.content) { setContent(json.content) }
|
||||
}
|
||||
fetchData()
|
||||
},[session])
|
||||
}, [session])
|
||||
|
||||
// When rendering client side don't display anything until loading is complete
|
||||
if (typeof window !== 'undefined' && loading) return null
|
||||
|
||||
// If no session exists, display access denied message
|
||||
if (!session) { return <Layout><AccessDenied/></Layout> }
|
||||
if (!session) { return <Layout><AccessDenied /></Layout> }
|
||||
|
||||
// If session exists, display content
|
||||
return (
|
||||
@@ -30,4 +30,4 @@ export default function Page () {
|
||||
<p><strong>{content}</strong></p>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useSession, getSession } from 'next-auth/client'
|
||||
import { getSession } from 'next-auth/client'
|
||||
import Layout from '../components/layout'
|
||||
|
||||
export default function Page () {
|
||||
@@ -6,7 +6,6 @@ export default function Page () {
|
||||
// populated on render without needing to go through a loading stage.
|
||||
// This is possible because of the shared context configured in `_app.js` that
|
||||
// is used by `useSession()`.
|
||||
const [ session, loading ] = useSession()
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
@@ -29,7 +28,7 @@ export default function Page () {
|
||||
}
|
||||
|
||||
// Export the `session` prop to use sessions with Server Side Rendering
|
||||
export async function getServerSideProps(context) {
|
||||
export async function getServerSideProps (context) {
|
||||
return {
|
||||
props: {
|
||||
session: await getSession(context)
|
||||
|
||||
63
prisma/schema.prisma
Normal file
63
prisma/schema.prisma
Normal file
@@ -0,0 +1,63 @@
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
model Account {
|
||||
id Int @default(autoincrement()) @id
|
||||
compoundId String @unique @map(name: "compound_id")
|
||||
userId Int @map(name: "user_id")
|
||||
providerType String @map(name: "provider_type")
|
||||
providerId String @map(name: "provider_id")
|
||||
providerAccountId String @map(name: "provider_account_id")
|
||||
refreshToken String? @map(name: "refresh_token")
|
||||
accessToken String? @map(name: "access_token")
|
||||
accessTokenExpires DateTime? @map(name: "access_token_expires")
|
||||
createdAt DateTime @default(now()) @map(name: "created_at")
|
||||
updatedAt DateTime @default(now()) @map(name: "updated_at")
|
||||
|
||||
@@index([providerAccountId], name: "providerAccountId")
|
||||
@@index([providerId], name: "providerId")
|
||||
@@index([userId], name: "userId")
|
||||
|
||||
@@map(name: "accounts")
|
||||
}
|
||||
|
||||
model Session {
|
||||
id Int @default(autoincrement()) @id
|
||||
userId Int @map(name: "user_id")
|
||||
expires DateTime
|
||||
sessionToken String @unique @map(name: "session_token")
|
||||
accessToken String @unique @map(name: "access_token")
|
||||
createdAt DateTime @default(now()) @map(name: "created_at")
|
||||
updatedAt DateTime @default(now()) @map(name: "updated_at")
|
||||
|
||||
@@map(name: "sessions")
|
||||
}
|
||||
|
||||
model User {
|
||||
id Int @default(autoincrement()) @id
|
||||
name String?
|
||||
email String? @unique
|
||||
emailVerified DateTime? @map(name: "email_verified")
|
||||
image String?
|
||||
createdAt DateTime @default(now()) @map(name: "created_at")
|
||||
updatedAt DateTime @default(now()) @map(name: "updated_at")
|
||||
|
||||
@@map(name: "users")
|
||||
}
|
||||
|
||||
model VerificationRequest {
|
||||
id Int @default(autoincrement()) @id
|
||||
identifier String
|
||||
token String @unique
|
||||
expires DateTime
|
||||
createdAt DateTime @default(now()) @map(name: "created_at")
|
||||
updatedAt DateTime @default(now()) @map(name: "updated_at")
|
||||
|
||||
@@map(name: "verification_requests")
|
||||
}
|
||||
7
release.config.js
Normal file
7
release.config.js
Normal file
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
branches: [
|
||||
'+([0-9])?(.{+([0-9]),x}).x',
|
||||
'main',
|
||||
{ name: 'next', prerelease: true }
|
||||
]
|
||||
}
|
||||
@@ -1,84 +1,83 @@
|
||||
const Adapter = (config, options = {}) => {
|
||||
async function getAdapter (appOptions) {
|
||||
const { logger } = appOptions
|
||||
// Display debug output if debug option enabled
|
||||
function _debug (...args) {
|
||||
if (appOptions.debug) {
|
||||
console.log('[next-auth][debug]', ...args)
|
||||
}
|
||||
function debug (debugCode, ...args) {
|
||||
logger.debug(`ADAPTER_${debugCode}`, ...args)
|
||||
}
|
||||
|
||||
async function createUser (profile) {
|
||||
_debug('createUser', profile)
|
||||
debug('createUser', profile)
|
||||
return null
|
||||
}
|
||||
|
||||
async function getUser (id) {
|
||||
_debug('getUser', id)
|
||||
debug('getUser', id)
|
||||
return null
|
||||
}
|
||||
|
||||
async function getUserByEmail (email) {
|
||||
_debug('getUserByEmail', email)
|
||||
debug('getUserByEmail', email)
|
||||
return null
|
||||
}
|
||||
|
||||
async function getUserByProviderAccountId (providerId, providerAccountId) {
|
||||
_debug('getUserByProviderAccountId', providerId, providerAccountId)
|
||||
debug('getUserByProviderAccountId', providerId, providerAccountId)
|
||||
return null
|
||||
}
|
||||
|
||||
async function updateUser (user) {
|
||||
_debug('updateUser', user)
|
||||
debug('updateUser', user)
|
||||
return null
|
||||
}
|
||||
|
||||
async function deleteUser (userId) {
|
||||
_debug('deleteUser', userId)
|
||||
debug('deleteUser', userId)
|
||||
return null
|
||||
}
|
||||
|
||||
async function linkAccount (userId, providerId, providerType, providerAccountId, refreshToken, accessToken, accessTokenExpires) {
|
||||
_debug('linkAccount', userId, providerId, providerType, providerAccountId, refreshToken, accessToken, accessTokenExpires)
|
||||
debug('linkAccount', userId, providerId, providerType, providerAccountId, refreshToken, accessToken, accessTokenExpires)
|
||||
return null
|
||||
}
|
||||
|
||||
async function unlinkAccount (userId, providerId, providerAccountId) {
|
||||
_debug('unlinkAccount', userId, providerId, providerAccountId)
|
||||
debug('unlinkAccount', userId, providerId, providerAccountId)
|
||||
return null
|
||||
}
|
||||
|
||||
async function createSession (user) {
|
||||
_debug('createSession', user)
|
||||
debug('createSession', user)
|
||||
return null
|
||||
}
|
||||
|
||||
async function getSession (sessionToken) {
|
||||
_debug('getSession', sessionToken)
|
||||
debug('getSession', sessionToken)
|
||||
return null
|
||||
}
|
||||
|
||||
async function updateSession (session, force) {
|
||||
_debug('updateSession', session)
|
||||
debug('updateSession', session)
|
||||
return null
|
||||
}
|
||||
|
||||
async function deleteSession (sessionToken) {
|
||||
_debug('deleteSession', sessionToken)
|
||||
debug('deleteSession', sessionToken)
|
||||
return null
|
||||
}
|
||||
|
||||
async function createVerificationRequest (identifier, url, token, secret, provider) {
|
||||
_debug('createVerificationRequest', identifier)
|
||||
debug('createVerificationRequest', identifier)
|
||||
return null
|
||||
}
|
||||
|
||||
async function getVerificationRequest (identifier, token, secret, provider) {
|
||||
_debug('getVerificationRequest', identifier, token)
|
||||
debug('getVerificationRequest', identifier, token)
|
||||
return null
|
||||
}
|
||||
|
||||
async function deleteVerificationRequest (identifier, token, secret, provider) {
|
||||
_debug('deleteVerification', identifier, token)
|
||||
debug('deleteVerification', identifier, token)
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { createHash, randomBytes } from 'crypto'
|
||||
|
||||
import { CreateUserError } from '../../lib/errors'
|
||||
import logger from '../../lib/logger'
|
||||
|
||||
const Adapter = (config) => {
|
||||
const {
|
||||
@@ -21,6 +20,7 @@ const Adapter = (config) => {
|
||||
}
|
||||
|
||||
async function getAdapter (appOptions) {
|
||||
const { logger } = appOptions
|
||||
function debug (debugCode, ...args) {
|
||||
logger.debug(`PRISMA_${debugCode}`, ...args)
|
||||
}
|
||||
@@ -280,11 +280,15 @@ const Adapter = (config) => {
|
||||
// Hash token provided with secret before trying to match it with database
|
||||
// @TODO Use bcrypt instead of salted SHA-256 hash for token
|
||||
const hashedToken = createHash('sha256').update(`${token}${secret}`).digest('hex')
|
||||
const verificationRequest = await prisma[VerificationRequest].findUnique({ where: { token: hashedToken } })
|
||||
|
||||
const verificationRequest = await prisma[VerificationRequest].findFirst({
|
||||
where: {
|
||||
identifier,
|
||||
token: hashedToken
|
||||
}
|
||||
})
|
||||
if (verificationRequest && verificationRequest.expires && new Date() > verificationRequest.expires) {
|
||||
// Delete verification entry so it cannot be used again
|
||||
await prisma[VerificationRequest].delete({ where: { token: hashedToken } })
|
||||
await prisma[VerificationRequest].deleteMany({ where: { identifier, token: hashedToken } })
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -300,7 +304,7 @@ const Adapter = (config) => {
|
||||
try {
|
||||
// Delete verification entry so it cannot be used again
|
||||
const hashedToken = createHash('sha256').update(`${token}${secret}`).digest('hex')
|
||||
await prisma[VerificationRequest].delete({ where: { token: hashedToken } })
|
||||
await prisma[VerificationRequest].deleteMany({ where: { identifier, token: hashedToken } })
|
||||
} catch (error) {
|
||||
logger.error('DELETE_VERIFICATION_REQUEST_ERROR', error)
|
||||
return Promise.reject(new Error('DELETE_VERIFICATION_REQUEST_ERROR', error))
|
||||
|
||||
@@ -6,7 +6,7 @@ import { CreateUserError } from '../../lib/errors'
|
||||
import adapterConfig from './lib/config'
|
||||
import adapterTransform from './lib/transform'
|
||||
import Models from './models'
|
||||
import logger from '../../lib/logger'
|
||||
|
||||
import { updateConnectionEntities } from './lib/utils'
|
||||
|
||||
const Adapter = (typeOrmConfig, options = {}) => {
|
||||
@@ -41,6 +41,12 @@ const Adapter = (typeOrmConfig, options = {}) => {
|
||||
let connection = null
|
||||
|
||||
async function getAdapter (appOptions) {
|
||||
const { logger } = appOptions
|
||||
// Display debug output if debug option enabled
|
||||
function debug (debugCode, ...args) {
|
||||
logger.debug(`TYPEORM_${debugCode}`, ...args)
|
||||
}
|
||||
|
||||
// Helper function to reuse / restablish connections
|
||||
// (useful if they drop when after being idle)
|
||||
async function _connect () {
|
||||
@@ -77,12 +83,6 @@ const Adapter = (typeOrmConfig, options = {}) => {
|
||||
// https://github.com/typeorm/typeorm/blob/master/docs/entity-manager-api.md
|
||||
const { manager } = connection
|
||||
|
||||
// Display debug output if debug option enabled
|
||||
// @TODO Refactor logger so is passed in appOptions
|
||||
function debug (debugCode, ...args) {
|
||||
logger.debug(`TYPEORM_${debugCode}`, ...args)
|
||||
}
|
||||
|
||||
// The models are primarily designed for ANSI SQL database, but some
|
||||
// flexiblity is required in the adapter to support non-SQL databases such
|
||||
// as MongoDB which have different pragmas.
|
||||
@@ -331,7 +331,7 @@ const Adapter = (typeOrmConfig, options = {}) => {
|
||||
|
||||
if (verificationRequest && verificationRequest.expires && new Date() > new Date(verificationRequest.expires)) {
|
||||
// Delete verification entry so it cannot be used again
|
||||
await manager.delete(VerificationRequest, { token: hashedToken })
|
||||
await manager.delete(VerificationRequest, { identifier, token: hashedToken })
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -347,7 +347,7 @@ const Adapter = (typeOrmConfig, options = {}) => {
|
||||
try {
|
||||
// Delete verification entry so it cannot be used again
|
||||
const hashedToken = createHash('sha256').update(`${token}${secret}`).digest('hex')
|
||||
await manager.delete(VerificationRequest, { token: hashedToken })
|
||||
await manager.delete(VerificationRequest, { identifier, token: hashedToken })
|
||||
} catch (error) {
|
||||
logger.error('DELETE_VERIFICATION_REQUEST_ERROR', error)
|
||||
return Promise.reject(new Error('DELETE_VERIFICATION_REQUEST_ERROR', error))
|
||||
|
||||
@@ -10,9 +10,8 @@
|
||||
//
|
||||
// We use HTTP POST requests with CSRF Tokens to protect against CSRF attacks.
|
||||
|
||||
/* global fetch:false */
|
||||
import { useState, useEffect, useContext, createContext, createElement } from 'react'
|
||||
import logger from '../lib/logger'
|
||||
import _logger, { proxyLogger } from '../lib/logger'
|
||||
import parseUrl from '../lib/parse-url'
|
||||
|
||||
// This behaviour mirrors the default behaviour for getting the site name that
|
||||
@@ -38,6 +37,8 @@ const __NEXTAUTH = {
|
||||
_getSession: () => {}
|
||||
}
|
||||
|
||||
const logger = proxyLogger(_logger, __NEXTAUTH.basePath)
|
||||
|
||||
// Add event listners on load
|
||||
if (typeof window !== 'undefined') {
|
||||
if (__NEXTAUTH._eventListenersAdded === false) {
|
||||
@@ -115,12 +116,10 @@ const setOptions = ({
|
||||
}
|
||||
|
||||
// Universal method (client + server)
|
||||
export const getSession = async ({ req, ctx, triggerEvent = true } = {}) => {
|
||||
// If passed 'appContext' via getInitialProps() in _app.js then get the req
|
||||
// object from ctx and use that for the req value to allow getSession() to
|
||||
// work seemlessly in getInitialProps() on server side pages *and* in _app.js.
|
||||
if (!req && ctx && ctx.req) { req = ctx.req }
|
||||
|
||||
// If passed 'appContext' via getInitialProps() in _app.js then get the req
|
||||
// object from ctx and use that for the req value to allow getSession() to
|
||||
// work seemlessly in getInitialProps() on server side pages *and* in _app.js.
|
||||
export async function getSession ({ ctx, req = ctx?.req, triggerEvent = true } = {}) {
|
||||
const baseUrl = _apiBaseUrl()
|
||||
const fetchOptions = req ? { headers: { cookie: req.headers.cookie } } : {}
|
||||
const session = await _fetchData(`${baseUrl}/session`, fetchOptions)
|
||||
@@ -131,12 +130,10 @@ export const getSession = async ({ req, ctx, triggerEvent = true } = {}) => {
|
||||
}
|
||||
|
||||
// Universal method (client + server)
|
||||
const getCsrfToken = async ({ req, ctx } = {}) => {
|
||||
// If passed 'appContext' via getInitialProps() in _app.js then get the req
|
||||
// object from ctx and use that for the req value to allow getCsrfToken() to
|
||||
// work seemlessly in getInitialProps() on server side pages *and* in _app.js.
|
||||
if (!req && ctx && ctx.req) { req = ctx.req }
|
||||
|
||||
// If passed 'appContext' via getInitialProps() in _app.js then get the req
|
||||
// object from ctx and use that for the req value to allow getCsrfToken() to
|
||||
// work seemlessly in getInitialProps() on server side pages *and* in _app.js.
|
||||
async function getCsrfToken ({ ctx, req = ctx?.req } = {}) {
|
||||
const baseUrl = _apiBaseUrl()
|
||||
const fetchOptions = req ? { headers: { cookie: req.headers.cookie } } : {}
|
||||
const data = await _fetchData(`${baseUrl}/csrf`, fetchOptions)
|
||||
@@ -233,62 +230,116 @@ const _useSessionHook = (session) => {
|
||||
return [data, loading]
|
||||
}
|
||||
|
||||
// Client side method
|
||||
export const signIn = async (provider, args = {}, authorizationParams = {}) => {
|
||||
/**
|
||||
* 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)
|
||||
* @see https://next-auth.js.org/getting-started/client#signin
|
||||
* @param {string} [provider]
|
||||
* @param {SignInOptions} [options]
|
||||
* @param {object} [authorizationParams]
|
||||
* @return {Promise<SignInResponse | undefined>}
|
||||
* @typedef {{callbackUrl?: string; redirect?: boolean}} SignInOptions
|
||||
* @typedef {{error: string | null; status: number; ok: boolean}} SignInResponse
|
||||
*/
|
||||
export async function signIn (provider, options = {}, authorizationParams = {}) {
|
||||
const {
|
||||
callbackUrl = window.location,
|
||||
redirect = true
|
||||
} = options
|
||||
|
||||
const baseUrl = _apiBaseUrl()
|
||||
const callbackUrl = args?.callbackUrl ?? window.location
|
||||
const providers = await getProviders()
|
||||
|
||||
// Redirect to sign in page if no valid provider specified
|
||||
if (!(provider in providers)) {
|
||||
// If Provider not recognized, redirect to sign in page
|
||||
window.location = `${baseUrl}/signin?callbackUrl=${encodeURIComponent(callbackUrl)}`
|
||||
} else {
|
||||
const signInUrl = (providers[provider].type === 'credentials')
|
||||
? `${baseUrl}/callback/${provider}`
|
||||
: `${baseUrl}/signin/${provider}`
|
||||
return
|
||||
}
|
||||
const isCredentials = providers[provider].type === 'credentials'
|
||||
const signInUrl = isCredentials
|
||||
? `${baseUrl}/callback/${provider}`
|
||||
: `${baseUrl}/signin/${provider}`
|
||||
|
||||
// If is any other provider type, POST to provider URL with CSRF Token,
|
||||
// callback URL and any other parameters supplied.
|
||||
const fetchOptions = {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
body: _encodedForm({
|
||||
...args,
|
||||
csrfToken: await getCsrfToken(),
|
||||
callbackUrl: callbackUrl,
|
||||
json: true
|
||||
})
|
||||
}
|
||||
const _signInUrl = `${signInUrl}?${_encodedForm(authorizationParams)}`
|
||||
const res = await fetch(_signInUrl, fetchOptions)
|
||||
const data = await res.json()
|
||||
window.location = data.url ?? callbackUrl
|
||||
// If is any other provider type, POST to provider URL with CSRF Token,
|
||||
// callback URL and any other parameters supplied.
|
||||
const fetchOptions = {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
body: new URLSearchParams({
|
||||
...options,
|
||||
csrfToken: await getCsrfToken(),
|
||||
callbackUrl,
|
||||
json: true
|
||||
})
|
||||
}
|
||||
const _signInUrl = `${signInUrl}?${new URLSearchParams(authorizationParams)}`
|
||||
const res = await fetch(_signInUrl, fetchOptions)
|
||||
const data = await res.json()
|
||||
if (redirect || !isCredentials) {
|
||||
const url = data.url ?? callbackUrl
|
||||
window.location = url
|
||||
// If url contains a hash, the browser does not reload the page. We reload manually
|
||||
if (url.includes('#')) window.location.reload()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const error = new URL(data.url).searchParams.get('error')
|
||||
|
||||
if (res.ok) {
|
||||
await __NEXTAUTH._getSession({ event: 'storage' })
|
||||
}
|
||||
|
||||
return {
|
||||
error,
|
||||
status: res.status,
|
||||
ok: res.ok,
|
||||
url: error ? null : data.url
|
||||
}
|
||||
}
|
||||
|
||||
// Client side method
|
||||
export const signOut = async (args = {}) => {
|
||||
const callbackUrl = args.callbackUrl ?? window.location
|
||||
|
||||
/**
|
||||
* Signs the user out, by removing the session cookie.
|
||||
* (Automatically adds the CSRF token to the request)
|
||||
* @param {SignOutOptions} [options]
|
||||
* @returns {Promise<{url?: string} | undefined>}
|
||||
* @typedef {{callbackUrl?: string; redirect?: boolean;}} SignOutOptions
|
||||
*/
|
||||
export async function signOut (options = {}) {
|
||||
const {
|
||||
callbackUrl = window.location,
|
||||
redirect = true
|
||||
} = options
|
||||
const baseUrl = _apiBaseUrl()
|
||||
const fetchOptions = {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
body: _encodedForm({
|
||||
body: new URLSearchParams({
|
||||
csrfToken: await getCsrfToken(),
|
||||
callbackUrl: callbackUrl,
|
||||
callbackUrl,
|
||||
json: true
|
||||
})
|
||||
}
|
||||
const res = await fetch(`${baseUrl}/signout`, fetchOptions)
|
||||
const data = await res.json()
|
||||
_sendMessage({ event: 'session', data: { trigger: 'signout' } })
|
||||
window.location = data.url ?? callbackUrl
|
||||
if (redirect) {
|
||||
const url = data.url ?? callbackUrl
|
||||
window.location = url
|
||||
// If url contains a hash, the browser does not reload the page. We reload manually
|
||||
if (url.includes('#')) window.location.reload()
|
||||
return
|
||||
}
|
||||
|
||||
await __NEXTAUTH._getSession({ event: 'storage' })
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
// Provider to wrap the app in to make session data available globally
|
||||
@@ -321,12 +372,6 @@ const _apiBaseUrl = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const _encodedForm = (formData) => {
|
||||
return Object.keys(formData).map((key) => {
|
||||
return encodeURIComponent(key) + '=' + encodeURIComponent(formData[key])
|
||||
}).join('&')
|
||||
}
|
||||
|
||||
const _sendMessage = (message) => {
|
||||
if (typeof localStorage !== 'undefined') {
|
||||
const timestamp = Math.floor(new Date().getTime() / 1000)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
--border-radius: .3rem;
|
||||
--color-error: #c94b4b;
|
||||
--color-info: #157efb;
|
||||
--color-info-text: #fff;
|
||||
}
|
||||
|
||||
.__next-auth-theme-auto,
|
||||
@@ -23,7 +24,6 @@
|
||||
--color-control-border: #555;
|
||||
--color-button-active-background: #060606;
|
||||
--color-button-active-border: #666;
|
||||
|
||||
--color-seperator: #444;
|
||||
}
|
||||
|
||||
@@ -80,6 +80,7 @@ input[type] {
|
||||
font-size: 1rem;
|
||||
border-radius: var(--border-radius);
|
||||
box-shadow: inset 0 .1rem .2rem rgba(0, 0, 0, .2);
|
||||
color: var(--color-text);
|
||||
|
||||
&:focus {
|
||||
box-shadow: none;
|
||||
@@ -202,13 +203,13 @@ a.site {
|
||||
font-weight: 500;
|
||||
border-radius: 0.3rem;
|
||||
background: var(--color-info);
|
||||
color: var(--color-text);
|
||||
|
||||
p {
|
||||
text-align: left;
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.2rem;
|
||||
color: var(--color-info-text);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
5
src/lib/logger.d.ts
vendored
Normal file
5
src/lib/logger.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
export interface LoggerInstance {
|
||||
warn: (code?: string, ...message: unknown[]) => void
|
||||
error: (code?: string, ...message: unknown[]) => void
|
||||
debug: (code?: string, ...message: unknown[]) => void
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
const logger = {
|
||||
/** @type {import("./logger").LoggerInstance} */
|
||||
const _logger = {
|
||||
error (code, ...message) {
|
||||
console.error(
|
||||
`[next-auth][error][${code.toLowerCase()}]`,
|
||||
@@ -22,4 +23,60 @@ const logger = {
|
||||
}
|
||||
}
|
||||
|
||||
export default logger
|
||||
/**
|
||||
* Override the built-in logger.
|
||||
* Any `undefined` level will use the default logger.
|
||||
* @param {Partial<import("./logger").LoggerInstance>} 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
|
||||
}
|
||||
|
||||
export default _logger
|
||||
|
||||
/**
|
||||
* Serializes client-side log messages and sends them to the server
|
||||
* @param {import("./logger").LoggerInstance} logger
|
||||
* @param {string} basePath
|
||||
* @return {import("./logger").LoggerInstance}
|
||||
*/
|
||||
export function proxyLogger (logger = _logger, basePath) {
|
||||
try {
|
||||
if (typeof window === 'undefined') {
|
||||
return logger
|
||||
}
|
||||
|
||||
const clientLogger = {}
|
||||
for (const level in logger) {
|
||||
clientLogger[level] = (code, ...message) => {
|
||||
_logger[level](code, ...message) // Log on client as usual
|
||||
|
||||
const url = `${basePath}/_log`
|
||||
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
|
||||
}))
|
||||
})
|
||||
if (navigator.sendBeacon) {
|
||||
return navigator.sendBeacon(url, body)
|
||||
}
|
||||
return fetch(url, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body
|
||||
})
|
||||
}
|
||||
}
|
||||
return clientLogger
|
||||
} catch {
|
||||
return _logger
|
||||
}
|
||||
}
|
||||
|
||||
21
src/providers/eveonline.js
Normal file
21
src/providers/eveonline.js
Normal file
@@ -0,0 +1,21 @@
|
||||
export default (options) => {
|
||||
return {
|
||||
id: 'eveonline',
|
||||
name: 'EVE Online',
|
||||
type: 'oauth',
|
||||
version: '2.0',
|
||||
params: { grant_type: 'authorization_code' },
|
||||
accessTokenUrl: 'https://login.eveonline.com/oauth/token',
|
||||
authorizationUrl: 'https://login.eveonline.com/oauth/authorize?response_type=code',
|
||||
profileUrl: 'https://login.eveonline.com/oauth/verify',
|
||||
profile: (profile) => {
|
||||
return {
|
||||
id: profile.CharacterID,
|
||||
name: profile.CharacterName,
|
||||
image: `https://image.eveonline.com/Character/${profile.CharacterID}_128.jpg`,
|
||||
email: null
|
||||
}
|
||||
},
|
||||
...options
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ 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 Foursquare from './foursquare'
|
||||
import FusionAuth from './fusionauth'
|
||||
@@ -46,6 +47,7 @@ export default {
|
||||
Credentials,
|
||||
Discord,
|
||||
Email,
|
||||
EVEOnline,
|
||||
Facebook,
|
||||
Foursquare,
|
||||
FusionAuth,
|
||||
|
||||
@@ -3,7 +3,7 @@ export default (options) => {
|
||||
|
||||
return {
|
||||
id: 'vk',
|
||||
name: 'vk.com',
|
||||
name: 'VK',
|
||||
type: 'oauth',
|
||||
version: '2.0',
|
||||
scope: 'email',
|
||||
|
||||
94
src/server/index.d.ts
vendored
Normal file
94
src/server/index.d.ts
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
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>
|
||||
@@ -1,7 +1,7 @@
|
||||
import adapters from '../adapters'
|
||||
import jwt from '../lib/jwt'
|
||||
import parseUrl from '../lib/parse-url'
|
||||
import logger from '../lib/logger'
|
||||
import logger, { setLogger } from '../lib/logger'
|
||||
import * as cookie from './lib/cookie'
|
||||
import * as defaultEvents from './lib/default-events'
|
||||
import * as defaultCallbacks from './lib/default-callbacks'
|
||||
@@ -21,7 +21,15 @@ if (!process.env.NEXTAUTH_URL) {
|
||||
logger.warn('NEXTAUTH_URL', 'NEXTAUTH_URL environment variable not set')
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import("next").NextApiRequest} req
|
||||
* @param {import("next").NextApiResponse} res
|
||||
* @param {import(".").NextAuthOptions} userOptions
|
||||
*/
|
||||
async function NextAuthHandler (req, res, userOptions) {
|
||||
if (userOptions.logger) {
|
||||
setLogger(userOptions.logger)
|
||||
}
|
||||
// If debug enabled, set ENV VAR so that logger logs debug messages
|
||||
if (userOptions.debug) {
|
||||
process.env._NEXTAUTH_DEBUG = true
|
||||
@@ -122,7 +130,8 @@ async function NextAuthHandler (req, res, userOptions) {
|
||||
...defaultCallbacks,
|
||||
...userOptions.callbacks
|
||||
},
|
||||
pkce: {}
|
||||
pkce: {},
|
||||
logger
|
||||
}
|
||||
|
||||
await callbackUrlHandler(req, res)
|
||||
@@ -215,6 +224,21 @@ async function NextAuthHandler (req, res, userOptions) {
|
||||
return routes.callback(req, res)
|
||||
}
|
||||
break
|
||||
case '_log':
|
||||
try {
|
||||
if (!userOptions.logger) return
|
||||
const {
|
||||
code = 'CLIENT_ERROR',
|
||||
level = 'error',
|
||||
message = '[]'
|
||||
} = req.body
|
||||
|
||||
logger[level](code, ...JSON.parse(message))
|
||||
} catch (error) {
|
||||
// If logging itself failed...
|
||||
logger.error('LOGGER_ERROR', error)
|
||||
}
|
||||
return res.end()
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
7
src/server/lib/callbacks.d.ts
vendored
Normal file
7
src/server/lib/callbacks.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
|
||||
export interface CallbacksOptions {
|
||||
signIn?: (user: any, account: any, profile: any) => Promise<never | string>
|
||||
jwt?: (token: any, user: any, account: any, profile: any, isNewUser?: boolean) => Promise<any>
|
||||
session?: (session: any, userOrToken: any) => Promise<any>
|
||||
redirect?: (url: string, baseUrl: string) => Promise<string>
|
||||
}
|
||||
16
src/server/lib/cookie.d.ts
vendored
Normal file
16
src/server/lib/cookie.d.ts
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
export interface CookieOption {
|
||||
name: string
|
||||
options: {
|
||||
httpOnly: boolean
|
||||
sameSite: string
|
||||
path?: string
|
||||
secure: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export interface CookiesOptions {
|
||||
sessionToken: CookieOption
|
||||
callbackUrl: CookieOption
|
||||
csrfToken: CookieOption
|
||||
pkceCodeVerifier: CookieOption
|
||||
}
|
||||
@@ -110,6 +110,7 @@ function _serialize (name, val, options) {
|
||||
* For more on prefixes see https://googlechrome.github.io/samples/cookie-prefixes/
|
||||
*
|
||||
* @TODO Review cookie settings (names, options)
|
||||
* @return {import("./cookie").CookiesOptions}
|
||||
*/
|
||||
export function defaultCookies (useSecureCookies) {
|
||||
const cookiePrefix = useSecureCookies ? '__Secure-' : ''
|
||||
|
||||
12
src/server/lib/events.d.ts
vendored
Normal file
12
src/server/lib/events.d.ts
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
export type EventType=
|
||||
| 'signIn'
|
||||
| 'signOut'
|
||||
| 'createUser'
|
||||
| 'updateUser'
|
||||
| 'linkAccount'
|
||||
| 'session'
|
||||
| 'error'
|
||||
|
||||
export type EventCallback = (message: any) => Promise<void>
|
||||
|
||||
export type EventsOptions = Partial<Record<EventType, EventCallback>>
|
||||
@@ -3,6 +3,7 @@ import oAuthClient from './client'
|
||||
import logger from '../../../lib/logger'
|
||||
import { OAuthCallbackError } from '../../../lib/errors'
|
||||
|
||||
/** @param {import("../..").NextAuthRequest} req */
|
||||
export default async function oAuthCallback (req) {
|
||||
const { provider, pkce } = req.options
|
||||
const client = oAuthClient(provider)
|
||||
@@ -89,7 +90,7 @@ export default async function oAuthCallback (req) {
|
||||
* refresh_token?: string
|
||||
* id_token?: string
|
||||
* }
|
||||
* provider: object
|
||||
* provider: import("../..").Provider
|
||||
* user?: object
|
||||
* }} profileParams
|
||||
*/
|
||||
|
||||
@@ -7,6 +7,7 @@ import { sign as jwtSign } from 'jsonwebtoken'
|
||||
* @TODO Refactor to remove dependancy on 'oauth' package
|
||||
* It is already quite monkey patched, we don't use all the features and and it
|
||||
* would be easier to maintain if all the code was native to next-auth.
|
||||
* @param {import("../..").Provider} provider
|
||||
*/
|
||||
export default function oAuthClient (provider) {
|
||||
if (provider.version?.startsWith('2.')) {
|
||||
@@ -86,6 +87,9 @@ export default function oAuthClient (provider) {
|
||||
|
||||
/**
|
||||
* Ported from https://github.com/ciaranj/node-oauth/blob/a7f8a1e21c362eb4ed2039431fb9ac2ae749f26a/lib/oauth2.js
|
||||
* @param {string} code
|
||||
* @param {import("../..").Provider} provider
|
||||
* @param {string | undefined} codeVerifier
|
||||
*/
|
||||
async function getOAuth2AccessToken (code, provider, codeVerifier) {
|
||||
const url = provider.accessTokenUrl
|
||||
@@ -128,7 +132,7 @@ async function getOAuth2AccessToken (code, provider, codeVerifier) {
|
||||
headers.Authorization = 'Basic ' + Buffer.from((provider.clientId + ':' + provider.clientSecret)).toString('base64')
|
||||
}
|
||||
|
||||
if ((provider.id === 'okta' || provider.id === 'identity-server4') && !headers.Authorization) {
|
||||
if (provider.id === 'identity-server4' && !headers.Authorization) {
|
||||
headers.Authorization = `Bearer ${code}`
|
||||
}
|
||||
|
||||
@@ -184,6 +188,9 @@ async function getOAuth2AccessToken (code, provider, codeVerifier) {
|
||||
*
|
||||
* 18/08/2020 @robertcraigie added results parameter to pass data to an optional request preparer.
|
||||
* e.g. see providers/bungie
|
||||
* @param {import("../..").Provider} provider
|
||||
* @param {string} accessToken
|
||||
* @param {any} results
|
||||
*/
|
||||
async function getOAuth2 (provider, accessToken, results) {
|
||||
let url = provider.profileUrl
|
||||
|
||||
@@ -8,7 +8,11 @@ const PKCE_LENGTH = 64
|
||||
const PKCE_CODE_CHALLENGE_METHOD = 'S256' // can be 'plain', not recommended https://tools.ietf.org/html/rfc7636#section-4.2
|
||||
const PKCE_MAX_AGE = 60 * 15 // 15 minutes in seconds
|
||||
|
||||
/** Adds `code_verifier` to `req.options.pkce`, and removes the corresponding cookie */
|
||||
/**
|
||||
* Adds `code_verifier` to `req.options.pkce`, and removes the corresponding cookie
|
||||
* @param {import("../..").NextAuthRequest} req
|
||||
* @param {import("../..").NextAuthResponse} res
|
||||
*/
|
||||
export async function handleCallback (req, res) {
|
||||
const { cookies, provider, baseUrl, basePath } = req.options
|
||||
try {
|
||||
@@ -38,7 +42,11 @@ export async function handleCallback (req, res) {
|
||||
}
|
||||
}
|
||||
|
||||
/** Adds `code_challenge` and `code_challenge_method` to `req.options.pkce`. */
|
||||
/**
|
||||
* Adds `code_challenge` and `code_challenge_method` to `req.options.pkce`.
|
||||
* @param {import("../..").NextAuthRequest} req
|
||||
* @param {import("../..").NextAuthResponse} res
|
||||
*/
|
||||
export async function handleSignin (req, res) {
|
||||
const { cookies, provider, baseUrl, basePath } = req.options
|
||||
try {
|
||||
|
||||
@@ -6,6 +6,8 @@ import { OAuthCallbackError } from '../../../lib/errors'
|
||||
* For OAuth 2.0 flows, if the provider supports state,
|
||||
* check if state matches the one sent on signin
|
||||
* (a hash of the NextAuth.js CSRF token).
|
||||
* @param {import("../..").NextAuthRequest} req
|
||||
* @param {import("../..").NextAuthResponse} res
|
||||
*/
|
||||
export async function handleCallback (req, res) {
|
||||
const { csrfToken, provider, baseUrl, basePath } = req.options
|
||||
@@ -31,7 +33,11 @@ export async function handleCallback (req, res) {
|
||||
}
|
||||
}
|
||||
|
||||
/** Adds CSRF token to the authorizationParams. */
|
||||
/**
|
||||
* Adds CSRF token to the authorizationParams.
|
||||
* @param {import("../..").NextAuthRequest} req
|
||||
* @param {import("../..").NextAuthResponse} res
|
||||
*/
|
||||
export async function handleSignin (req, res) {
|
||||
const { provider, baseUrl, basePath, csrfToken } = req.options
|
||||
try {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import oAuthClient from '../oauth/client'
|
||||
import logger from '../../../lib/logger'
|
||||
|
||||
/** @param {import("../..").NextAuthRequest} req */
|
||||
export default async function getAuthorizationUrl (req) {
|
||||
const { provider } = req.options
|
||||
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
// @ts-check
|
||||
import { h } from 'preact' // eslint-disable-line no-unused-vars
|
||||
import render from 'preact-render-to-string'
|
||||
|
||||
/** Renders an error page. */
|
||||
export default function error ({ baseUrl, basePath, error, res }) {
|
||||
/**
|
||||
* Renders an error page.
|
||||
* @param {{
|
||||
* baseUrl: string
|
||||
* basePath: string
|
||||
* error?: string
|
||||
* res: import("..").NextAuthResponse
|
||||
* }} params
|
||||
*/
|
||||
export default function error ({ baseUrl, basePath, error = 'default', res }) {
|
||||
const signinPageUrl = `${baseUrl}${basePath}/signin`
|
||||
|
||||
const errors = {
|
||||
@@ -37,14 +46,14 @@ export default function error ({ baseUrl, basePath, error, res }) {
|
||||
message: (
|
||||
<div>
|
||||
<p>The sign in link is no longer valid.</p>
|
||||
<p>It may have be used already or it may have expired.</p>
|
||||
<p>It may have been used already or it may have expired.</p>
|
||||
</div>
|
||||
),
|
||||
signin: <p><a className='button' href={signinPageUrl}>Sign in</a></p>
|
||||
}
|
||||
}
|
||||
|
||||
const { statusCode, heading, message, signin } = errors[error.toLowerCase()] || errors.default
|
||||
const { statusCode, heading, message, signin } = errors[error.toLowerCase()]
|
||||
|
||||
res.status(statusCode)
|
||||
|
||||
|
||||
@@ -4,7 +4,11 @@ import * as cookie from '../lib/cookie'
|
||||
import logger from '../../lib/logger'
|
||||
import dispatchEvent from '../lib/dispatch-event'
|
||||
|
||||
/** Handle callbacks from login services */
|
||||
/**
|
||||
* Handle callbacks from login services
|
||||
* @param {import("..").NextAuthRequest} req
|
||||
* @param {import("..").NextAuthResponse} res
|
||||
*/
|
||||
export default async function callback (req, res) {
|
||||
const {
|
||||
provider,
|
||||
@@ -217,12 +221,12 @@ export default async function callback (req, res) {
|
||||
} else if (provider.type === 'credentials' && req.method === 'POST') {
|
||||
if (!useJwtSession) {
|
||||
logger.error('CALLBACK_CREDENTIALS_JWT_ERROR', 'Signin in with credentials is only supported if JSON Web Tokens are enabled')
|
||||
return res.redirect(`${baseUrl}${basePath}/error?error=Configuration`)
|
||||
return res.status(500).redirect(`${baseUrl}${basePath}/error?error=Configuration`)
|
||||
}
|
||||
|
||||
if (!provider.authorize) {
|
||||
logger.error('CALLBACK_CREDENTIALS_HANDLER_ERROR', 'Must define an authorize() handler to use credentials authentication provider')
|
||||
return res.redirect(`${baseUrl}${basePath}/error?error=Configuration`)
|
||||
return res.status(500).redirect(`${baseUrl}${basePath}/error?error=Configuration`)
|
||||
}
|
||||
|
||||
const credentials = req.body
|
||||
@@ -231,7 +235,7 @@ export default async function callback (req, res) {
|
||||
try {
|
||||
userObjectReturnedFromAuthorizeHandler = await provider.authorize(credentials)
|
||||
if (!userObjectReturnedFromAuthorizeHandler) {
|
||||
return res.redirect(`${baseUrl}${basePath}/error?error=CredentialsSignin&provider=${encodeURIComponent(provider.id)}`)
|
||||
return res.status(401).redirect(`${baseUrl}${basePath}/error?error=CredentialsSignin&provider=${encodeURIComponent(provider.id)}`)
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
@@ -246,7 +250,7 @@ export default async function callback (req, res) {
|
||||
try {
|
||||
const signInCallbackResponse = await callbacks.signIn(user, account, credentials)
|
||||
if (signInCallbackResponse === false) {
|
||||
return res.redirect(`${baseUrl}${basePath}/error?error=AccessDenied`)
|
||||
return res.status(403).redirect(`${baseUrl}${basePath}/error?error=AccessDenied`)
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
* Return a JSON object with a list of all OAuth providers currently configured
|
||||
* and their signin and callback URLs. This makes it possible to automatically
|
||||
* generate buttons for all providers when rendering client side.
|
||||
* @param {import("..").NextAuthRequest} req
|
||||
* @param {import("..").NextAuthResponse} res
|
||||
*/
|
||||
export default function providers (req, res) {
|
||||
const { providers } = req.options
|
||||
|
||||
@@ -44,7 +44,7 @@ export default async function signin (req, res) {
|
||||
|
||||
// Check if user is allowed to sign in
|
||||
try {
|
||||
const signInCallbackResponse = await callbacks.signIn(profile, account, { email })
|
||||
const signInCallbackResponse = await callbacks.signIn(profile, account, { email, verificationRequest: true })
|
||||
if (signInCallbackResponse === false) {
|
||||
return res.redirect(`${baseUrl}${basePath}/error?error=AccessDenied`)
|
||||
} else if (typeof signInCallbackResponse === 'string') {
|
||||
|
||||
5339
test/docker/app/package-lock.json
generated
5339
test/docker/app/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -11,8 +11,8 @@
|
||||
"author": "Iain Collins <me@iaincollins.com>",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"next": "^9.5.4",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1"
|
||||
"next": "^10.0.6",
|
||||
"react": "^17.0.1",
|
||||
"react-dom": "^17.0.1"
|
||||
}
|
||||
}
|
||||
|
||||
48
tsconfig.json
Normal file
48
tsconfig.json
Normal file
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"strictNullChecks": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"next-auth": [
|
||||
"./src/server"
|
||||
],
|
||||
"next-auth/adapters": [
|
||||
"./src/adapters"
|
||||
],
|
||||
"next-auth/client": [
|
||||
"./src/client"
|
||||
],
|
||||
"next-auth/jwt": [
|
||||
"./src/lib/jwt"
|
||||
],
|
||||
"next-auth/providers": [
|
||||
"./src/providers"
|
||||
]
|
||||
},
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": false,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve"
|
||||
},
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
@@ -307,6 +307,42 @@ Set debug to `true` to enable debug messages for authentication and database ope
|
||||
|
||||
---
|
||||
|
||||
### logger
|
||||
|
||||
* **Default value**: `console`
|
||||
* **Required**: *No*
|
||||
|
||||
#### Description
|
||||
|
||||
Override any of the logger levels (`undefined` levels will use the built-in logger), and intercept logs in NextAuth. You can use this to send NextAuth logs to a third-party logging service.
|
||||
|
||||
Example:
|
||||
```js title="/pages/api/auth/[...nextauth].js"
|
||||
import log from "logging-service"
|
||||
|
||||
export default NextAuth({
|
||||
...
|
||||
logger: {
|
||||
error(code, ...message) {
|
||||
log.error(code, message)
|
||||
},
|
||||
warn(code, ...message) {
|
||||
log.warn(code, message)
|
||||
}
|
||||
debug(code, ...message) {
|
||||
log.debug(code, message)
|
||||
}
|
||||
}
|
||||
...
|
||||
})
|
||||
```
|
||||
|
||||
:::note
|
||||
If the `debug` level is defined by the user, it will be called regardless of the `debug: false` [option](#debug).
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
### theme
|
||||
|
||||
* **Default value**: `"auto"`
|
||||
|
||||
@@ -118,3 +118,7 @@ You can also use the `signIn()` function which will handle obtaining the CSRF to
|
||||
```js
|
||||
signIn('credentials', { username: 'jsmith', password: '1234' })
|
||||
```
|
||||
|
||||
:::tip
|
||||
Remember to put any custom pages in a folder outside **/pages/api** which is reserved for API code. As per the examples above, a location convention suggestion is `pages/auth/...`.
|
||||
:::
|
||||
@@ -112,6 +112,16 @@ providers: [
|
||||
...
|
||||
```
|
||||
|
||||
:::tip
|
||||
If you think your custom provider might be useful to others, we encourage you to open a PR and add it to the built-in list so others can discover it much more easily! You only need to add three changes:
|
||||
1. Add your config: [`src/providers/{provider}.js`](https://github.com/nextauthjs/next-auth/tree/main/src/providers)
|
||||
2. Re-export your config: at [`src/providers/index.js`](https://github.com/nextauthjs/next-auth/blob/main/src/providers/index.js)
|
||||
3. Add provider documentation: [`www/docs/providers/{provider}.md`](https://github.com/nextauthjs/next-auth/tree/main/www/docs/providers)
|
||||
|
||||
You can look at the existing built-in providers for inspiration.
|
||||
:::
|
||||
|
||||
|
||||
### OAuth provider options
|
||||
|
||||
| Name | Description | Type | Required |
|
||||
|
||||
@@ -5,14 +5,14 @@ title: Contributors
|
||||
|
||||
## Core Team
|
||||
|
||||
* <a href="https://github.com/iaincollins">Iain Collins</a>
|
||||
* <a href="https://github.com/LoriKarikari">Lori Karikari</a>
|
||||
* <a href="https://github.com/ndom91">Nico Domino</a>
|
||||
* <a href="https://github.com/Fumler">Fredrik Pettersen</a>
|
||||
* <a href="https://github.com/geraldnolan">Gerald Nolan</a>
|
||||
* <a href="https://github.com/lluia">Lluis Agusti</a>
|
||||
* <a href="https://github.com/JeffersonBledsoe">Jefferson Bledsoe</a>
|
||||
* <a href="https://github.com/balazsorban44">Balázs Orbán</a>
|
||||
* [Iain Collins](https://github.com/iaincollins)
|
||||
* [Lori Karikari](https://github.com/LoriKarikari)
|
||||
* [Nico Domino](https://github.com/ndom91)
|
||||
* [Fredrik Pettersen](https://github.com/Fumler)
|
||||
* [Gerald Nolan](https://github.com/geraldnolan)
|
||||
* [Lluis Agusti](https://github.com/lluia)
|
||||
* [Jefferson Bledsoe](https://github.com/JeffersonBledsoe)
|
||||
* [Balázs Orbán](https://github.com/sponsors/balazsorban44)
|
||||
|
||||
_Special thanks to Lori Karikari for creating most of the providers, to Nico Domino for creating this site, to Fredrik Pettersen for creating the Prisma adapter, to Gerald Nolan for adding support for Sign in with Apple, to Lluis Agusti for work to add TypeScript definitions and to Jefferson Bledsoe for working on automating testing._
|
||||
|
||||
|
||||
@@ -133,7 +133,7 @@ The `getProviders()` method returns the list of providers currently configured f
|
||||
|
||||
It calls `/api/auth/providers` and returns a list of the currently configured authentication providers.
|
||||
|
||||
It can be use useful if you are creating a dynamic custom sign in page.
|
||||
It can be useful if you are creating a dynamic custom sign in page.
|
||||
|
||||
---
|
||||
|
||||
@@ -150,7 +150,7 @@ export default async (req, res) => {
|
||||
```
|
||||
|
||||
:::note
|
||||
Unlike `getSession()` and `getCsrfToken()`, when calling `getSession()` server side, you don't need to pass anything, just as calling it client side.
|
||||
Unlike `getSession()` and `getCsrfToken()`, when calling `getProviders()` server side, you don't need to pass anything, just as calling it client side.
|
||||
:::
|
||||
|
||||
---
|
||||
@@ -210,6 +210,35 @@ e.g.
|
||||
|
||||
The URL must be considered valid by the [redirect callback handler](/configuration/callbacks#redirect). By default it requires the URL to be an absolute URL at the same hostname, or else it will redirect to the homepage. You can define your own redirect callback to allow other URLs, including supporting relative URLs.
|
||||
|
||||
#### Using the redirect: false option
|
||||
|
||||
When you use the `credentials` provider, you might not want the user to redirect to an error page if an error occurs, so you can handle any errors (like wrong credentials given by the user) on the same page. For that, you can pass `redirect: false` in the second parameter object. `signIn` then will return a Promise, that resolves to the following:
|
||||
|
||||
```ts
|
||||
{
|
||||
/**
|
||||
* Will be different error codes,
|
||||
* depending on the type of error.
|
||||
*/
|
||||
error: string | undefined
|
||||
/**
|
||||
* HTTP status code,
|
||||
* hints the kind of error that happened.
|
||||
*/
|
||||
status: number
|
||||
/**
|
||||
* `true` if the signin was successful
|
||||
*/
|
||||
ok: boolean
|
||||
/**
|
||||
* `null` if there was an error,
|
||||
* otherwise the url the user
|
||||
* should have been redirected to.
|
||||
*/
|
||||
url: string | null
|
||||
}
|
||||
```
|
||||
|
||||
#### Additional params
|
||||
|
||||
It is also possible to pass additional parameters to the `/authorize` endpoint through the third argument of `signIn()`.
|
||||
@@ -256,6 +285,16 @@ e.g. `signOut({ callbackUrl: 'http://localhost:3000/foo' })`
|
||||
|
||||
The URL must be considered valid by the [redirect callback handler](/configuration/callbacks#redirect). By default this means it must be an absolute URL at the same hostname (or else it will default to the homepage); you can define your own custom redirect callback to allow other URLs, including supporting relative URLs.
|
||||
|
||||
#### Using the redirect: false option
|
||||
|
||||
If you pass `redirect: false` to `signOut`, the page will not reload. The session will be deleted, and the `useSession` hook is notified, so any indication about the user will be shown as logged out automatically. It can give a very nice experience for the user.
|
||||
|
||||
:::tip
|
||||
If you need to redirect to another page but you want to avoid a page reload, you can try:
|
||||
`const data = await signOut({redirect: false, callbackUrl: "/foo"})`
|
||||
where `data.url` is the validated url you can redirect the user to without any flicker by using Next.js's `useRouter().push(data.url)`
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Provider
|
||||
|
||||
@@ -9,8 +9,6 @@ https://developer.atlassian.com/cloud/jira/platform/oauth-2-authorization-code-g
|
||||
|
||||
## Example
|
||||
|
||||
For Jira Platform API access:
|
||||
|
||||
```js
|
||||
import Providers from `next-auth/providers`
|
||||
...
|
||||
@@ -18,8 +16,7 @@ providers: [
|
||||
Providers.Atlassian({
|
||||
clientId: process.env.ATLASSIAN_CLIENT_ID,
|
||||
clientSecret: process.env.ATLASSIAN_CLIENT_SECRET,
|
||||
scope:
|
||||
'write:jira-work read:jira-work read:jira-user offline_access read:me',
|
||||
scope: 'write:jira-work read:jira-work read:jira-user offline_access read:me'
|
||||
})
|
||||
]
|
||||
...
|
||||
@@ -33,7 +30,7 @@ providers: [
|
||||
An app can be created at https://developer.atlassian.com/apps/
|
||||
:::
|
||||
|
||||
Under "Apis and features" side menu, configure the following for the "OAuth 2.0 (3LO)"
|
||||
Under "Apis and features" in the side menu, configure the following for "OAuth 2.0 (3LO)":
|
||||
|
||||
- Redirect URL
|
||||
- http://localhost:3000/api/auth/callback/atlassian
|
||||
|
||||
@@ -12,7 +12,23 @@ https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-c
|
||||
https://docs.microsoft.com/en-us/azure/active-directory-b2c/tutorial-create-tenant
|
||||
|
||||
## Example
|
||||
- In https://portal.azure.com/ -> Azure Active Directory create a new App Registration.
|
||||
- Make sure to remember / copy
|
||||
- Application (client) ID
|
||||
- Directory (tenant) ID
|
||||
- When asked for a redirection URL, use http://localhost:3000/api/auth/callback/azure-ad-b2c
|
||||
- Create a new secret and remember / copy its value immediately, it will disappear.
|
||||
|
||||
In `.env.local` create the follwing entries:
|
||||
|
||||
```
|
||||
AZURE_CLIENT_ID=<copy Application (client) ID here>
|
||||
AZURE_CLIENT_SECRET=<copy generated secret value here>
|
||||
AZURE_TENANT_ID=<copy the tenant id here>
|
||||
```
|
||||
|
||||
In `pages/api/auth/[...nextauth].js` find or add the AZURE entries:
|
||||
|
||||
```js
|
||||
import Providers from 'next-auth/providers';
|
||||
...
|
||||
@@ -25,4 +41,5 @@ providers: [
|
||||
}),
|
||||
]
|
||||
...
|
||||
|
||||
```
|
||||
|
||||
57
www/docs/providers/eveonline.md
Normal file
57
www/docs/providers/eveonline.md
Normal file
@@ -0,0 +1,57 @@
|
||||
---
|
||||
id: eveonline
|
||||
title: EVE Online
|
||||
---
|
||||
|
||||
## Documentation
|
||||
|
||||
https://developers.eveonline.com/blog/article/sso-to-authenticated-calls
|
||||
|
||||
## Configuration
|
||||
|
||||
https://developers.eveonline.com/
|
||||
|
||||
## Example
|
||||
|
||||
```js
|
||||
import Providers from `next-auth/providers`
|
||||
...
|
||||
providers: [
|
||||
Providers.EVEOnline({
|
||||
clientId: process.env.EVE_CLIENT_ID,
|
||||
clientSecret: process.env.EVE_CLIENT_SECRET
|
||||
})
|
||||
]
|
||||
...
|
||||
```
|
||||
|
||||
:::tip When creating your application, make sure to select `Authentication Only` as the connection type.
|
||||
|
||||
:::tip If using JWT for the session, you can add the `CharacterID` to the JWT token and session. Example:
|
||||
|
||||
```js
|
||||
...
|
||||
options: {
|
||||
jwt: {
|
||||
secret: process.env.JWT_SECRET,
|
||||
},
|
||||
callbacks: {
|
||||
jwt: async (token, user, account, profile, isNewUser) => {
|
||||
if (profile) {
|
||||
token = {
|
||||
...token,
|
||||
id: profile.CharacterID,
|
||||
}
|
||||
}
|
||||
return token;
|
||||
},
|
||||
session: async (session, token) => {
|
||||
if (token) {
|
||||
session.user.id = token.id;
|
||||
}
|
||||
return session;
|
||||
}
|
||||
}
|
||||
}
|
||||
...
|
||||
```
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
id: vk
|
||||
title: vk.com
|
||||
title: VK
|
||||
---
|
||||
|
||||
## Documentation
|
||||
|
||||
@@ -55,13 +55,13 @@ However, it should not be enabled against production databases as it may cause d
|
||||
|
||||
## Prisma Adapter
|
||||
|
||||
You can also use NextAuth.js with [Prisma](https://www.prisma.io/docs/).
|
||||
You can also use NextAuth.js with the experimental adapter for [Prisma 2](https://www.prisma.io/docs/).
|
||||
|
||||
To use this adapter, you need to install Prisma Client and Prisma CLI:
|
||||
|
||||
```
|
||||
npm i @prisma/client
|
||||
npm add -D @prisma/cli
|
||||
npm install @prisma/client
|
||||
npm install prisma --save-dev
|
||||
```
|
||||
|
||||
Configure your NextAuth.js to use the Prisma adapter:
|
||||
@@ -91,7 +91,7 @@ While Prisma includes an experimental feature in the migration command that is a
|
||||
|
||||
### Prisma Schema
|
||||
|
||||
Create a `schema.prisma` file similar to this one:
|
||||
Create a schema file in `prisma/schema.prisma` similar to this one:
|
||||
|
||||
```json title="schema.prisma"
|
||||
generator client {
|
||||
@@ -179,12 +179,22 @@ datasource db {
|
||||
|
||||
### Generate Client
|
||||
|
||||
Once you have saved your schema, you can run the Prisma CLI to generate the Prisma Client:
|
||||
Once you have saved your schema, use the Prisma CLI to generate the Prisma Client:
|
||||
|
||||
```
|
||||
npx @prisma/cli generate
|
||||
npx prisma generate
|
||||
```
|
||||
|
||||
To configure you database to use the new schema (i.e. create tables and columns) use the `primsa migrate` command:
|
||||
|
||||
```
|
||||
npx prisma migrate dev --preview-feature
|
||||
```
|
||||
|
||||
To generate a schema in this way with the above example code, you will need to specify your datbase connection string in the environment variable `DATABASE_URL`. You can do this by setting it in a `.env` file at the root of your project.
|
||||
|
||||
As this feature is experimental in Prisma, it is behind a feature flag. You should check your database schema manually after using this option. See the [Prisma documentation](https://www.prisma.io/docs/) for information on how to use `prisma migrate`.
|
||||
|
||||
### Custom Models
|
||||
|
||||
You can add properties to the schema and map them to any database column names you wish, but you should not change the base properties or types defined in the example schema.
|
||||
|
||||
@@ -68,7 +68,7 @@ module.exports = {
|
||||
to: '/contributors'
|
||||
},
|
||||
{
|
||||
label: 'Canary docs',
|
||||
label: 'Canary documentation',
|
||||
to: 'https://next-auth-git-canary.nextauthjs.vercel.app/'
|
||||
}
|
||||
]
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
"name": "next-auth-docs",
|
||||
"version": "0.1.1",
|
||||
"scripts": {
|
||||
"start": "docusaurus start",
|
||||
"build": "docusaurus build",
|
||||
"start": "npm run generate-providers && docusaurus start",
|
||||
"build": "npm run generate-providers && docusaurus build",
|
||||
"swizzle": "docusaurus swizzle",
|
||||
"deploy": "docusaurus deploy",
|
||||
"lint": "standard",
|
||||
"lint:fix": "standard --fix"
|
||||
"lint:fix": "standard --fix",
|
||||
"generate-providers": "node ./scripts/generate-providers.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "^2.0.0-alpha.66",
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
{
|
||||
"apple": "Apple",
|
||||
"atlassian": "Atlassian",
|
||||
"auth0": "Auth0",
|
||||
"azure-ad-b2c": "Azure Active Directory B2C",
|
||||
"basecamp": "Basecamp",
|
||||
"battle.net": "Battle.net",
|
||||
"box": "Box",
|
||||
"bungie": "Bungie",
|
||||
"cognito": "Amazon Cognito",
|
||||
"credentials": "Credentials",
|
||||
"discord": "Discord",
|
||||
"email": "Email",
|
||||
"facebook": "Facebook",
|
||||
"foursquare": "Foursquare",
|
||||
"fusionauth": "FusionAuth",
|
||||
"github": "GitHub",
|
||||
"gitlab": "GitLab",
|
||||
"google": "Google",
|
||||
"identity-server4": "IdentityServer4",
|
||||
"line": "LINE",
|
||||
"linkedin": "LinkedIn",
|
||||
"mailru": "Mail.ru",
|
||||
"medium": "Medium",
|
||||
"netlify": "Netlify",
|
||||
"okta": "Okta",
|
||||
"reddit": "Reddit",
|
||||
"salesforce": "Salesforce",
|
||||
"slack": "Slack",
|
||||
"spotify": "Spotify",
|
||||
"strava": "Strava",
|
||||
"twitch": "Twitch",
|
||||
"twitter": "Twitter",
|
||||
"vk": "VK",
|
||||
"yandex": "Yandex"
|
||||
}
|
||||
15
www/scripts/generate-providers.js
Normal file
15
www/scripts/generate-providers.js
Normal file
@@ -0,0 +1,15 @@
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
|
||||
const providersPath = path.join(process.cwd(), '/docs/providers')
|
||||
|
||||
const files = fs.readdirSync(providersPath, 'utf8')
|
||||
|
||||
const result = files.reduce((acc, file) => {
|
||||
const provider = fs.readFileSync(path.join(providersPath, file), 'utf8')
|
||||
const { id, title } = provider.match(/id: (?<id>.+)\ntitle: (?<title>.+)\n/).groups
|
||||
acc[id] = title
|
||||
return acc
|
||||
}, {})
|
||||
|
||||
fs.writeFileSync(path.join(process.cwd(), 'providers.json'), JSON.stringify(result, null, 2))
|
||||
Reference in New Issue
Block a user