Compare commits

...

72 Commits

Author SHA1 Message Date
Balázs Orbán
6590993fdc chore(release): bump package version(s) [skip ci] 2022-07-21 16:35:03 +02:00
Balázs Orbán
0ea96796b2 fix: improve logger (#4970)
* fix: add debug warning, only show warnings once

* fix: prefer `debug` for details

* remove url

* test: fix tests

* Update docs/docs/errors.md

Co-authored-by: Thang Vu <31528554+ThangHuuVu@users.noreply.github.com>

* Update callback.ts

Co-authored-by: Thang Vu <31528554+ThangHuuVu@users.noreply.github.com>
2022-07-21 16:00:16 +02:00
Misha Kaletsky
8ec940bd6a docs: highlight archiving of next-auth/react-query (#4964)
* docs: highlight archiving of next-auth/react-query

project is read-only and author said users should just copy-paste the implementation: https://github.com/nextauthjs/react-query/issues/7#issuecomment-923099050

* Update docs/docs/getting-started/client.md

Co-authored-by: Balázs Orbán <info@balazsorban.com>

* Update docs/docs/getting-started/client.md

Co-authored-by: Balázs Orbán <info@balazsorban.com>

Co-authored-by: Balázs Orbán <info@balazsorban.com>
2022-07-21 13:59:46 +02:00
Balázs Orbán
e3bcdf83f1 chore: update lock file 2022-07-20 03:18:02 +02:00
Balázs Orbán
4084297334 chore(release): extract release script to its own package 2022-07-20 03:08:03 +02:00
Balázs Orbán
c9827960b1 chore(release): read packages dynamically 2022-07-20 00:36:49 +02:00
Balázs Orbán
946a825865 chore: fix adapters PR auto-labeler 2022-07-19 23:55:03 +02:00
Balázs Orbán
c57d8c997e fix(adapters): set correct peer dependency version of next-auth (#4950)
* fix(adapters): set correct peer dependency version of `next-auth`

* fix fauna peer dependency
2022-07-19 23:46:45 +02:00
Balázs Orbán
e2b92bf04f chore: add newlines to PR comment 2022-07-19 17:45:55 +02:00
Balázs Orbán
8bff050e4e chore(release): bump version [skip ci] 2022-07-19 17:30:40 +02:00
Thang Vu
1a79a1a612 docs: FAQ framework-agnostic & session sharing (#4962)
Split a FAQ into two parts:
Before:
- Can I use NextAuth.js with a website that does not use Next.js?

After:
- Can I use NextAuth.js with a framework different than Next.js?
- Can session generated by NextAuth.js be used by another website?
2022-07-19 17:29:54 +02:00
Balázs Orbán
b7065a602f chore: correct Middleware logic in Next.js example 2022-07-19 17:16:51 +02:00
Balázs Orbán
61b92ec1b6 chore: revert type assertion 2022-07-19 16:57:16 +02:00
Balázs Orbán
282f7ab340 fix(ts): fix Middleware internal type 2022-07-19 16:46:13 +02:00
Balázs Orbán
4f56e414b0 chore: simplify dev app 2022-07-19 16:42:21 +02:00
Yoann Fleury
2725d07eb7 fix(providers): migrate GitLab provider to TS (#4929) 2022-07-17 04:47:15 +02:00
Balázs Orbán
5a8b029523 docs: clarify getToken + secret in example
ref #4954
2022-07-17 04:44:11 +02:00
Balázs Orbán
f62a985848 docs: clarify getToken and secret
Ref: #4954
2022-07-17 04:39:47 +02:00
Balázs Orbán
edd6fb5989 Merge branch 'main' of github.com:nextauthjs/next-auth 2022-07-17 04:29:49 +02:00
Balázs Orbán
fb60554a62 chore(ts): explicitly set next path in next-auth 2022-07-17 04:29:44 +02:00
cobbvanth
9784dfb631 docs: Remove import of "useSession" from wrong location (#4952)
This tutorial snipped erroneously imports useSession from "next-auth/next", when it actually resides in "next-auth/react".
2022-07-16 23:06:00 +02:00
Balázs Orbán
4ff836a8cf test(providers): add fetch polyfill to redis test runs 2022-07-16 17:39:32 +02:00
Balázs Orbán
042955eaaa fix(providers): allow issuer in Azure AD B2C 2022-07-16 14:20:31 +02:00
Balázs Orbán
82e107c0e7 chore: improve dev app DX 2022-07-16 14:19:33 +02:00
dependabot[bot]
f7050347e8 chore(deps-dev): bump svelte from 3.46.4 to 3.49.0 in /apps/playground-sveltekit (#4947)
chore(deps-dev): bump svelte in /apps/playground-sveltekit

Bumps [svelte](https://github.com/sveltejs/svelte) from 3.46.4 to 3.49.0.
- [Release notes](https://github.com/sveltejs/svelte/releases)
- [Changelog](https://github.com/sveltejs/svelte/blob/master/CHANGELOG.md)
- [Commits](https://github.com/sveltejs/svelte/compare/v3.46.4...v3.49.0)

---
updated-dependencies:
- dependency-name: svelte
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-15 23:17:21 +02:00
Balázs Orbán
c56abbd745 chore: update CODEOWNERS (#4941)
* chore: update codeowners

* reorganize
2022-07-15 12:35:54 +02:00
Thang Vu
3f6d99e8df chore: add Thang to contributor (#4944) 2022-07-15 12:34:36 +02:00
Balázs Orbán
46eedee3c8 fix(ts): remove TS workaround for withAuth (#4926)
* fix(ts): improve Middleware types

* docs: remove TS workaround for Middleware

* ignore lint

* simplify
2022-07-15 04:39:15 +02:00
Balázs Orbán
bb664a27da fix(providers): typo in GitHub provider scope (#4938) 2022-07-15 04:38:42 +02:00
Balázs Orbán
a14fbea0b5 chore: add TODO comment for next major 2022-07-15 02:41:09 +02:00
Balázs Orbán
4705632c6b chore: add TODO comment for next major version 2022-07-15 02:38:59 +02:00
Balázs Orbán
2296471f02 chore: add pnpm to experimental release comment 2022-07-15 02:20:06 +02:00
S. Suzuki
8853000fd5 fix(ts): handleMiddleware return type can be NextMiddlewareResult (#4818)
Co-authored-by: Lluis Agusti <hi@llu.lu>
2022-07-14 00:20:19 +02:00
Balázs Orbán
70ffa6592f chore(release): bump package version(s) [skip ci] 2022-07-13 14:16:44 +02:00
mshd
3666e438a3 refactor(providers): convert GitHub provider to TypeScript (#4908)
* rewrite github provider in typescript

* rename md js endings

* Update packages/next-auth/src/providers/github.ts

Co-authored-by: Balázs Orbán <info@balazsorban.com>

* add eslint disable next line

Co-authored-by: Balázs Orbán <info@balazsorban.com>
2022-07-13 14:07:24 +02:00
Aliaksei Tayanouski
cdf467eba1 fix(providers): fix VK provider and convert to TS (#3709)
* renamed vk provider file extension to .ts

* lint issue fix

* vk provider fix

* formatting fix

* remove user_id from tokens

* Update src/providers/vk.ts

Co-authored-by: Balázs Orbán <info@balazsorban.com>

* Update src/providers/vk.ts

Co-authored-by: Balázs Orbán <info@balazsorban.com>

* user interface additions

Co-authored-by: Balázs Orbán <info@balazsorban.com>
2022-07-13 14:04:55 +02:00
Balázs Orbán
374dc30f9f chore: bump pnpm version requirement 2022-07-13 14:01:25 +02:00
Balázs Orbán
d9534d807d chore: tweak release script 2022-07-13 13:59:45 +02:00
Balázs Orbán
f4c7401a5d chore: fix release script text 2022-07-13 13:09:08 +02:00
Balázs Orbán
2baa0c30c1 chore: bump turbo 2022-07-13 13:08:58 +02:00
Sagar Gajare
839b9108ea docs: provider link fixed (#4913) 2022-07-13 01:39:13 +02:00
Juho Tapio
0bf955a63d docs: a few typos in CONTRIBUTING.md (#4910)
* docs: fixed a typo

* docs: update link to scripts index

* docks: more typos

* docs: added a comma
2022-07-12 17:58:54 +02:00
Balázs Orbán
83a974d455 docs: update Middleware docs
closes #4867
2022-07-12 17:19:23 +02:00
Nico Domino
8f54b8f729 docs: upgrade docusaurus-plugin-npm2yarn2pnpm (#4909) 2022-07-12 16:52:21 +02:00
nkg
1b91282402 fix(ts): use correct type for nodemailer config in the EmailProvider (#4097)
Co-authored-by: Thang Vu <31528554+ThangHuuVu@users.noreply.github.com>
2022-07-12 02:50:09 +02:00
Ryan Barr
c2a9ab3023 fix(providers): convert Discord Provider to TypeScript (#4170)
* chore(providers): Add typings for Discord Provider

* Update DiscordProfile interface to extend Record.
2022-07-12 02:43:56 +02:00
Chancellor Clark
5bd00f6ff1 feat(adapters): update Firebase adapter to use new API (#3873)
Ports and refactors `@next-auth/firebase-adapter` to use the new Adapter API. Ported from this PR: nextauthjs/adapters#183

BREAKING CHANGE:

- Renames `FirebaseAdapter` export to `FirestoreAdpater`
- This adapter now requires firebase v9+
2022-07-12 01:45:06 +02:00
William Crutchfield
af3c2dd33d fix(ts): typo in Azure Active Directory Provider (#4895) 2022-07-11 18:24:09 +02:00
John Brunton
709edc5153 fix(adapters): use correct peer dependency in Sequelize adapter (#4880) 2022-07-11 18:23:04 +02:00
Balázs Orbán
fa3ea37ebc chore: comment out adapter in dev app 2022-07-11 18:20:53 +02:00
Yoann Fleury
6a364f0353 docs: update CONTRIBUTING for pnpm instead of yarn (#4888)
* docs: update CONTRIBUTING for pnpm instead of yarn

* dev:app -> dev

Co-authored-by: Thang Vu <31528554+ThangHuuVu@users.noreply.github.com>
2022-07-11 10:38:30 +07:00
mshd
c22d613774 feat(providers): Add Wikimedia Oauth Provider (#4813)
* Add Wikimedia Oauth Provider

* add docs

* Update wikimedia.md

* Update wikimedia.md

* Update wikimedia.md

Co-authored-by: Nico Domino <yo@ndo.dev>
2022-07-10 19:38:01 +02:00
Nico Domino
9efafcd36c chore: fix dev app URL parse error (#4887) 2022-07-10 14:30:10 +02:00
Misha Kaletsky
e317b16cd2 docs: getToken link (#4884)
fix(docs): getToken link

without `/` it's considered a relative link so goes to https://next-auth.js.org/configuration/tutorials/securing-pages-and-api-routes#using-gettoken which doesn't exist.
2022-07-09 22:46:38 +02:00
Rudy Boutte
2edc79ed2b fix(docs): options page link (#4876) 2022-07-09 12:56:20 +02:00
Balázs Orbán
637dda9966 chore: tweak release script 2022-07-08 19:55:08 +02:00
Balázs Orbán
10bb32c479 chore(adapters): fix MikroORM adapter dependencies 2022-07-08 19:36:24 +02:00
Balázs Orbán
89e25568b1 chore: accept --dry-run flag in release script 2022-07-08 19:31:04 +02:00
Balázs Orbán
88ad25a16b feat(adapters): update typeorm support (#4844)
We haven't kept up with the recent TypeORM changes, and since they are still <1, it's likely that users kept upgrading, even if there were breaking changes.

BREAKING CHANGE:

[`typeorm`](https://github.com/typeorm/typeorm) is still in active development and has not yet published a stable release. Because of this, you can expect breaking changes in minor versions. This release of the adapter expects `typeorm@0.3.7` and is not validated against previous or future releases.

Run `npm i typeorm@latest` and make sure to read the [release notes](https://github.com/typeorm/typeorm/releases) for breaking changes in TypeORM
2022-07-08 19:26:59 +02:00
Luiz Bett
c1f7ce3436 fix(ts): make colorScheme optional (#4868)
Documentation says if it is not passed it will be "auto", but it is mandatory on this type.
2022-07-08 19:24:30 +02:00
sean-nicholas
c59a4e04d1 fix(middleware): allow secret as option in Middleware (#4846)
*  provide secret via config

* 🐛 make secret optional

* 📝 docs for middleware and env var

* 📝  recommendation at the end of paragraph

Co-authored-by: Balázs Orbán <info@balazsorban.com>
2022-07-07 14:14:21 +02:00
Joshua Grant
3c210d961b feat(providers): add Duende IdentityServer 6 (#4850)
* add duende identity server 6 provider

* Update docs/versioned_docs/version-v3/providers/duende-identity-server6.md

Co-authored-by: Balázs Orbán <info@balazsorban.com>

* Update docs/versioned_docs/version-v3/providers/duende-identity-server6.md

Co-authored-by: Balázs Orbán <info@balazsorban.com>

* Update packages/next-auth/src/providers/duende-identity-server6.ts

Co-authored-by: Balázs Orbán <info@balazsorban.com>

* Update packages/next-auth/src/providers/duende-identity-server6.ts

Co-authored-by: Balázs Orbán <info@balazsorban.com>

* Update docs/versioned_docs/version-v3/providers/duende-identity-server6.md

Co-authored-by: Balázs Orbán <info@balazsorban.com>

* Update apps/dev/pages/api/auth/[...nextauth].ts

Co-authored-by: Balázs Orbán <info@balazsorban.com>

* Update docs/versioned_docs/version-v3/providers/duende-identity-server6.md

Co-authored-by: Balázs Orbán <info@balazsorban.com>

* Update docs/versioned_docs/version-v3/providers/duende-identity-server6.md

Co-authored-by: Balázs Orbán <info@balazsorban.com>

* Update packages/next-auth/src/providers/duende-identity-server6.ts

Co-authored-by: Balázs Orbán <info@balazsorban.com>

Co-authored-by: Joshua <joshua.grant@tempcover.com>
Co-authored-by: Balázs Orbán <info@balazsorban.com>
2022-07-07 14:12:35 +02:00
Balázs Orbán
9457593038 chore: update Next.js example, bump dependencies 2022-07-07 11:31:17 +02:00
dependabot[bot]
5081d25f5c chore(deps): bump next-auth in /apps/playground-sveltekit (#4859)
Bumps [next-auth](https://github.com/nextauthjs/next-auth) from 4.5.0 to 4.9.0.
- [Release notes](https://github.com/nextauthjs/next-auth/releases)
- [Changelog](https://github.com/nextauthjs/next-auth/blob/main/CHANGELOG.md)
- [Commits](https://github.com/nextauthjs/next-auth/compare/next-auth@v4.5.0...next-auth@v4.9.0)

---
updated-dependencies:
- dependency-name: next-auth
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-07 00:34:27 +02:00
Rudy Boutte
384edbab3b docs: Added documentation on how to use a custom base path in NextAuth (#4858) 2022-07-07 00:34:12 +02:00
Balázs Orbán
2adfadefdc chore: bump version 2022-07-06 11:51:37 +02:00
Balázs Orbán
32fa01f939 chore: re-add GITHUB_TOKEN 2022-07-06 11:44:37 +02:00
Balázs Orbán
ae834f1e08 feat(providers): allow styling e-mail through theme option (#4841)
* fix(core): move email handling

* fix: don' use `replaceAll`

* feat(providers): re-use `theme` for e-mail

* docs: mention `theme` option for email

* fix: don't render user e-mail in the email HTML body

* docs: add missing comma

* refactor: fix lint

* refactor: fix lint
2022-07-05 16:02:04 +02:00
Nico Domino
4d4c276627 docs: replace npm2yarn with npm2yarn2pnpm docusaurus plugin (#4805)
* feat: rm npm2yarn add npm2yarn2pnpm plugin

* fix: pnpm-lock.yaml

* chore: test change

* fix: update npm2yarn2pnpm usage

* fix: package.json mistake packages added

* fix: pnpm-lock.yaml

* fix: test debug output

* fix: named import npm2yarn2pnpm

* chore: rm debug:true

* fix: change require statement

* Update pnpm-lock.yaml

* Update pnpm-lock.yaml

* Update pnpm-lock.yaml

* Update pnpm-lock.yaml

* Delete pnpm-lock.yaml

* add pnpm-lock.yaml

Co-authored-by: Balázs Orbán <info@balazsorban.com>
2022-07-05 11:56:44 +02:00
Junichi Sato
f4c0d5ab5d docs: Correct grammatical error (#4836) 2022-07-05 00:25:46 -04:00
Nico Domino
01cd6b0f7b docs: fix unstable_getServerSession arguments (#4815)
* chore(docs): fix unstable_getServerSession arguments

* chore: add authOptions import
2022-07-03 23:27:37 +02:00
Nico Domino
993c0f46b0 fix: show experimental api warning only in dev and only once (#4816)
Co-authored-by: Lluis Agusti <hi@llu.lu>
2022-07-02 21:00:11 +02:00
126 changed files with 2905 additions and 2666 deletions

15
.github/CODEOWNERS vendored
View File

@@ -1,4 +1,11 @@
/types/ @balazsorban44 @lluia
/docs/ @balazsorban44 @ndom91
/adapters/ @balazsorban44 @ndom91
/__tests__/ @lluia
# Learn how to add code owners here:
# https://help.github.com/en/articles/about-code-owners
* @balazsorban44
.github @ThangHuuVu
/apps/ @lluia @ndom91 @ThangHuuVu
/docs/ @lluia @ndom91
/packages/ @ThangHuuVu
/packages/adapter-*/ @ndom91
/**/*test* @lluia
/**/*type* @lluia

View File

@@ -10,7 +10,7 @@ providers:
adapters:
- packages/next-auth/src/adapters.ts
- packages/*-adapter/**
- packages/adapter-*/**
dgraph:
- packages/adapter-dgraph/**

View File

@@ -4,5 +4,5 @@ outputs:
version:
description: "npm package version"
runs:
using: "node12"
using: "node16"
main: "index.js"

View File

@@ -21,7 +21,7 @@ jobs:
- name: Install pnpm
uses: pnpm/action-setup@v2.2.1
with:
version: 6.32.8
version: 7.5.1
- name: Setup Node
uses: actions/setup-node@v3
with:
@@ -55,7 +55,7 @@ jobs:
- name: Install pnpm
uses: pnpm/action-setup@v2.2.1
with:
version: 6.32.8
version: 7.5.1
- name: Setup Node
uses: actions/setup-node@v3
with:
@@ -70,6 +70,7 @@ jobs:
pnpm release
env:
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
NPM_TOKEN_PKG: ${{ secrets.NPM_TOKEN_PKG }}
NPM_TOKEN_ORG: ${{ secrets.NPM_TOKEN_ORG }}
release-pr:
@@ -84,7 +85,7 @@ jobs:
- name: Install pnpm
uses: pnpm/action-setup@v2.2.1
with:
version: 6.32.8
version: 7.5.1
- name: Setup Node
uses: actions/setup-node@v3
with:
@@ -107,7 +108,11 @@ jobs:
- name: Comment version on PR
uses: NejcZdovc/comment-pr@v1
with:
message: "🎉 Experimental release [published on npm](https://www.npmjs.com/package/next-auth/v/${{ env.VERSION }})!\n\n```sh\nnpm i next-auth@${{ env.VERSION }}\n```\n```sh\nyarn add next-auth@${{ env.VERSION }}\n```"
message:
"🎉 Experimental release [published 📦️ on npm](https://npmjs.com/package/next-auth/v/${{ env.VERSION }})!\n \
```sh\npnpm add next-auth@${{ env.VERSION }}\n```\n \
```sh\nyarn add next-auth@${{ env.VERSION }}\n```\n \
```sh\nnpm i next-auth@${{ env.VERSION }}\n```"
env:
VERSION: ${{ steps.determine-version.outputs.version }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

1
.gitignore vendored
View File

@@ -44,6 +44,7 @@ packages/next-auth/middleware.js
# Development app
apps/dev/src/css
apps/dev/prisma/migrations
apps/dev/typeorm
# VS
/.vs/slnx.sqlite-journal

View File

@@ -17,7 +17,7 @@ Anyone can be a contributor. Either you found a typo, or you have an awesome fea
- The latest changes are always in `main`, so please make your Pull Request against that branch.
- Pull Requests should be raised for any change
- Pull Requests need approval of a [core contributor](https://next-auth.js.org/contributors#core-team) before merging
- We use ESLint/Prettier for linting/formatting, so please run `yarn 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 use ESLint/Prettier for linting/formatting, so please run `pnpm 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
@@ -37,7 +37,7 @@ cd next-auth
1. Install packages. Developing requires Node.js v16:
```sh
yarn
pnpm install
```
3. Populate `.env.local`:
@@ -55,7 +55,7 @@ cp .env.local.example .env.local
4. Start the developer application/server:
```sh
yarn dev:app
pnpm dev
```
Your developer application will be available on `http://localhost:3000`
@@ -65,7 +65,7 @@ If you need an example project to link to, you can use [next-auth-example](https
#### Hot reloading
When running `yarn dev:app`, you start a Next.js developer 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.
When running `pnpm dev`, you start a Next.js developer server on `http://localhost:3000`, which includes hot reloading out-of-the-box. Make changes on any of the files in `src` and see the changes immediately.
> NOTE: When working on CSS, you will have to manually refresh the page after changes. The reason for this is our pages using CSS are server-side rendered (using API routes). (Improving this through a PR is very welcome!)
@@ -75,7 +75,7 @@ When running `yarn dev:app`, you start a Next.js developer server on `http://loc
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`!)
1. Add your config: [`src/providers/{provider}.js`](https://github.com/nextauthjs/next-auth/tree/main/packages/next-auth/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!
@@ -88,13 +88,13 @@ If you would like to contribute to an existing database adapter or help create a
#### Testing
Tests can be run with `yarn test`.
Tests can be run with `pnpm test`.
Automated tests are currently crude and limited in functionality, but improvements are in development.
## For maintainers
We use [a custom script](https://github.com/nextauthjs/next-auth/tree/main/scripts/index.ts) together with [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0) to automate releases. This makes the maintenance process easier and less error-prone. Please study the "Conventional Commits" site to understand how to write a good commit message.
We use [a custom script](https://github.com/nextauthjs/next-auth/blob/main/scripts/release/index.ts) together with [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0) to automate releases. This makes the maintenance process easier and less error-prone. Please study the "Conventional Commits" site to understand how to write a good commit message.
When accepting Pull Requests, make sure the following:
@@ -103,9 +103,9 @@ When accepting Pull Requests, make sure the following:
- Rewrite the commit message to conform to the `Conventional Commits` style.
- Using `fix` releases a patch (x.x.1)
- Using `feat` releases a minor (x.1.x)
- Using `feat` when `BREAKING CHANGE` is present in the commit messgae releases a major (1.x.x)
- Using `feat` when `BREAKING CHANGE` is present in the commit message releases a major (1.x.x)
- 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.)
### Skipping a release
If a commit contains `[skip release]` in their message will be excluded from the commit analysis and won't participate in the release type determination. This is useful, if the PR being merged should not trigger a new `npm` release.
If a commit contains `[skip release]` in their message, it will be excluded from the commit analysis and won't participate in the release type determination. This is useful, if the PR being merged should not trigger a new `npm` release.

View File

@@ -47,6 +47,5 @@ EMAIL_FROM=user@gmail.com
# MongoDB: DATABASE_URL=mongodb://nextauth:password@127.0.0.1:27017/nextauth?synchronize=true
DATABASE_URL=
BOXYHQSAML_ISSUER="https://jackson-demo.boxyhq.com"
BOXYHQSAML_ID="tenant=boxyhq.com&product=saml-demo.boxyhq.com"
BOXYHQSAML_SECRET="dummy"
WIKIMEDIA_ID=
WIKIMEDIA_SECRET=

View File

@@ -30,12 +30,11 @@ export const config = { matcher: ["/middleware-protected"] }
// export default withAuth(
// function middleware(req, ev) {
// console.log(req, ev)
// return undefined // NOTE: `NextMiddleware` should allow returning `void`
// },
// {
// callbacks: {
// authorized: ({ token }) => token.name === "Balázs Orbán",
// }
// },
// }
// )

View File

@@ -16,21 +16,25 @@
},
"license": "ISC",
"dependencies": {
"@next-auth/fauna-adapter": "^1",
"@next-auth/prisma-adapter": "^1",
"@next-auth/fauna-adapter": "workspace:*",
"@next-auth/prisma-adapter": "workspace:*",
"@next-auth/typeorm-legacy-adapter": "workspace:*",
"@prisma/client": "^3",
"faunadb": "^4",
"next": "12.1.7-canary.51",
"next": "12.2.0",
"nodemailer": "^6",
"react": "^18",
"react-dom": "^18"
},
"devDependencies": {
"@types/react": "^18",
"@types/react-dom": "^18",
"@types/react": "^18.0.15",
"@types/react-dom": "^18.0.6",
"concurrently": "^7",
"cpx": "^1.5.0",
"fake-smtp-server": "^0.8.0",
"prisma": "^3"
"pg": "^8.7.3",
"prisma": "^3",
"sqlite3": "^5.0.8",
"typeorm": "0.3.7"
}
}

View File

@@ -1,218 +1,134 @@
import NextAuth, { NextAuthOptions } from "next-auth"
// import EmailProvider from "next-auth/providers/email"
import GitHubProvider from "next-auth/providers/github"
import Auth0Provider from "next-auth/providers/auth0"
import KeycloakProvider from "next-auth/providers/keycloak"
import TwitterProvider, {
TwitterLegacy as TwitterLegacyProvider,
} from "next-auth/providers/twitter"
import CredentialsProvider from "next-auth/providers/credentials"
import IDS4Provider from "next-auth/providers/identity-server4"
import Twitch from "next-auth/providers/twitch"
import GoogleProvider from "next-auth/providers/google"
import FacebookProvider from "next-auth/providers/facebook"
import FoursquareProvider from "next-auth/providers/foursquare"
// import FreshbooksProvider from "next-auth/providers/freshbooks"
import GitlabProvider from "next-auth/providers/gitlab"
import InstagramProvider from "next-auth/providers/instagram"
import LineProvider from "next-auth/providers/line"
import LinkedInProvider from "next-auth/providers/linkedin"
import MailchimpProvider from "next-auth/providers/mailchimp"
import DiscordProvider from "next-auth/providers/discord"
import AzureADProvider from "next-auth/providers/azure-ad"
import SpotifyProvider from "next-auth/providers/spotify"
import CognitoProvider from "next-auth/providers/cognito"
import SlackProvider from "next-auth/providers/slack"
import Okta from "next-auth/providers/okta"
import NextAuth from "next-auth"
import type { NextAuthOptions } from "next-auth"
// Providers
import Apple from "next-auth/providers/apple"
import Auth0 from "next-auth/providers/auth0"
import AzureAD from "next-auth/providers/azure-ad"
import AzureB2C from "next-auth/providers/azure-ad-b2c"
import OsuProvider from "next-auth/providers/osu"
import AppleProvider from "next-auth/providers/apple"
import PatreonProvider from "next-auth/providers/patreon"
import TraktProvider from "next-auth/providers/trakt"
import WorkOSProvider from "next-auth/providers/workos"
import BoxyHQSAMLProvider from "next-auth/providers/boxyhq-saml"
import BoxyHQSAML from "next-auth/providers/boxyhq-saml"
import Cognito from "next-auth/providers/cognito"
import Credentials from "next-auth/providers/credentials"
import Discord from "next-auth/providers/discord"
import DuendeIDS6 from "next-auth/providers/duende-identity-server6"
import Email from "next-auth/providers/email"
import Facebook from "next-auth/providers/facebook"
import Foursquare from "next-auth/providers/foursquare"
import Freshbooks from "next-auth/providers/freshbooks"
import GitHub from "next-auth/providers/github"
import Gitlab from "next-auth/providers/gitlab"
import Google from "next-auth/providers/google"
import IDS4 from "next-auth/providers/identity-server4"
import Instagram from "next-auth/providers/instagram"
import Keycloak from "next-auth/providers/keycloak"
import Line from "next-auth/providers/line"
import LinkedIn from "next-auth/providers/linkedin"
import Mailchimp from "next-auth/providers/mailchimp"
import Okta from "next-auth/providers/okta"
import Osu from "next-auth/providers/osu"
import Patreon from "next-auth/providers/patreon"
import Slack from "next-auth/providers/slack"
import Spotify from "next-auth/providers/spotify"
import Trakt from "next-auth/providers/trakt"
import Twitch from "next-auth/providers/twitch"
import Twitter, { TwitterLegacy } from "next-auth/providers/twitter"
import Vk from "next-auth/providers/vk"
import Wikimedia from "next-auth/providers/wikimedia"
import WorkOS from "next-auth/providers/workos"
// import { PrismaAdapter } from "@next-auth/prisma-adapter"
// import { PrismaClient } from "@prisma/client"
// const prisma = new PrismaClient()
// const adapter = PrismaAdapter(prisma)
// Adapters
import { PrismaClient } from "@prisma/client"
import { PrismaAdapter } from "@next-auth/prisma-adapter"
import { Client as FaunaClient } from "faunadb"
import { FaunaAdapter } from "@next-auth/fauna-adapter"
import { TypeORMLegacyAdapter } from "@next-auth/typeorm-legacy-adapter"
// import { Client as FaunaClient } from "faunadb"
// import { FaunaAdapter } from "@next-auth/fauna-adapter"
// const client = new FaunaClient({
// secret: process.env.FAUNA_SECRET,
// domain: process.env.FAUNA_DOMAIN,
// })
// const adapter = FaunaAdapter(client)
export const authOptions: NextAuthOptions = {
// adapter,
providers: [
// E-mail
// Start fake e-mail server with `npm run start:email`
// EmailProvider({
// server: {
// host: "127.0.0.1",
// auth: null,
// secure: false,
// port: 1025,
// tls: { rejectUnauthorized: false },
// },
// }),
// Credentials
CredentialsProvider({
name: "Credentials",
credentials: {
password: { label: "Password", type: "password" },
},
async authorize(credentials) {
if (credentials.password === "pw") {
return {
name: "Fill Murray",
email: "bill@fillmurray.com",
image: "https://www.fillmurray.com/64/64",
}
}
return null
},
}),
// OAuth 1
// TwitterLegacyProvider({
// clientId: process.env.TWITTER_LEGACY_ID,
// clientSecret: process.env.TWITTER_LEGACY_SECRET,
// }),
// OAuth 2 / OIDC
TwitterProvider({
// Opt-in to the new Twitter API for now. Should be default in the future.
version: "2.0",
clientId: process.env.TWITTER_ID,
clientSecret: process.env.TWITTER_SECRET,
}),
GitHubProvider({
clientId: process.env.GITHUB_ID,
clientSecret: process.env.GITHUB_SECRET,
}),
Auth0Provider({
clientId: process.env.AUTH0_ID,
clientSecret: process.env.AUTH0_SECRET,
issuer: process.env.AUTH0_ISSUER,
}),
KeycloakProvider({
clientId: process.env.KEYCLOAK_ID,
clientSecret: process.env.KEYCLOAK_SECRET,
issuer: process.env.KEYCLOAK_ISSUER,
}),
Twitch({
clientId: process.env.TWITCH_ID,
clientSecret: process.env.TWITCH_SECRET,
}),
GoogleProvider({
clientId: process.env.GOOGLE_ID,
clientSecret: process.env.GOOGLE_SECRET,
}),
FacebookProvider({
clientId: process.env.FACEBOOK_ID,
clientSecret: process.env.FACEBOOK_SECRET,
}),
FoursquareProvider({
clientId: process.env.FOURSQUARE_ID,
clientSecret: process.env.FOURSQUARE_SECRET,
}),
// FreshbooksProvider({
// clientId: process.env.FRESHBOOKS_ID,
// clientSecret: process.env.FRESHBOOKS_SECRET,
// }),
GitlabProvider({
clientId: process.env.GITLAB_ID,
clientSecret: process.env.GITLAB_SECRET,
}),
InstagramProvider({
clientId: process.env.INSTAGRAM_ID,
clientSecret: process.env.INSTAGRAM_SECRET,
}),
LineProvider({
clientId: process.env.LINE_ID,
clientSecret: process.env.LINE_SECRET,
}),
LinkedInProvider({
clientId: process.env.LINKEDIN_ID,
clientSecret: process.env.LINKEDIN_SECRET,
}),
MailchimpProvider({
clientId: process.env.MAILCHIMP_ID,
clientSecret: process.env.MAILCHIMP_SECRET,
}),
IDS4Provider({
clientId: process.env.IDS4_ID,
clientSecret: process.env.IDS4_SECRET,
issuer: process.env.IDS4_ISSUER,
}),
DiscordProvider({
clientId: process.env.DISCORD_ID,
clientSecret: process.env.DISCORD_SECRET,
}),
AzureADProvider({
clientId: process.env.AZURE_AD_CLIENT_ID,
clientSecret: process.env.AZURE_AD_CLIENT_SECRET,
tenantId: process.env.AZURE_AD_TENANT_ID,
profilePhotoSize: 48,
}),
SpotifyProvider({
clientId: process.env.SPOTIFY_ID,
clientSecret: process.env.SPOTIFY_SECRET,
}),
CognitoProvider({
clientId: process.env.COGNITO_ID,
clientSecret: process.env.COGNITO_SECRET,
issuer: process.env.COGNITO_ISSUER,
}),
Okta({
clientId: process.env.OKTA_ID,
clientSecret: process.env.OKTA_SECRET,
issuer: process.env.OKTA_ISSUER,
}),
SlackProvider({
clientId: process.env.SLACK_ID,
clientSecret: process.env.SLACK_SECRET,
}),
AzureB2C({
clientId: process.env.AZURE_B2C_ID,
clientSecret: process.env.AZURE_B2C_SECRET,
tenantId: process.env.AZURE_B2C_TENANT_ID,
primaryUserFlow: process.env.AZURE_B2C_PRIMARY_USER_FLOW,
}),
OsuProvider({
clientId: process.env.OSU_CLIENT_ID,
clientSecret: process.env.OSU_CLIENT_SECRET,
}),
AppleProvider({
clientId: process.env.APPLE_ID,
clientSecret: process.env.APPLE_SECRET,
}),
PatreonProvider({
clientId: process.env.PATREON_ID,
clientSecret: process.env.PATREON_SECRET,
}),
TraktProvider({
clientId: process.env.TRAKT_ID,
clientSecret: process.env.TRAKT_SECRET,
}),
WorkOSProvider({
clientId: process.env.WORKOS_ID,
clientSecret: process.env.WORKOS_SECRET,
}),
BoxyHQSAMLProvider({
issuer: process.env.BOXYHQSAML_ISSUER,
clientId: process.env.BOXYHQSAML_ID,
clientSecret: process.env.BOXYHQSAML_SECRET,
}),
],
debug: true,
theme: {
colorScheme: "auto",
logo: "https://next-auth.js.org/img/logo/logo-sm.png",
brandColor: "#1786fb",
// Add an adapter you want to test here.
const adapters = {
prisma() {
const client = globalThis.prisma || new PrismaClient()
if (process.env.NODE_ENV !== "production") globalThis.prisma = client
return PrismaAdapter(client)
},
typeorm() {
return TypeORMLegacyAdapter({
type: "sqlite",
name: "next-auth-test-memory",
database: "./typeorm/dev.db",
synchronize: true,
})
},
fauna() {
const client =
globalThis.fauna ||
new FaunaClient({
secret: process.env.FAUNA_SECRET,
domain: process.env.FAUNA_DOMAIN,
})
if (process.env.NODE_ENV !== "production") global.fauna = client
return FaunaAdapter(client)
},
noop() {
return undefined
},
}
export const authOptions: NextAuthOptions = {
adapter: adapters.noop(),
debug: true,
theme: {
logo: "https://next-auth.js.org/img/logo/logo-sm.png",
brandColor: "#1786fb",
},
providers: [
Credentials({
credentials: { password: { label: "Password", type: "password" } },
async authorize(credentials) {
if (credentials.password !== "pw") return null
return { name: "Fill Murray", email: "bill@fillmurray.com", image: "https://www.fillmurray.com/64/64" }
},
}),
Apple({ clientId: process.env.APPLE_ID, clientSecret: process.env.APPLE_SECRET }),
Auth0({ clientId: process.env.AUTH0_ID, clientSecret: process.env.AUTH0_SECRET, issuer: process.env.AUTH0_ISSUER }),
AzureAD({ clientId: process.env.AZURE_AD_CLIENT_ID, clientSecret: process.env.AZURE_AD_CLIENT_SECRET, tenantId: process.env.AZURE_AD_TENANT_ID }),
AzureB2C({ clientId: process.env.AZURE_B2C_ID, clientSecret: process.env.AZURE_B2C_SECRET, issuer: process.env.AZURE_B2C_ISSUER }),
BoxyHQSAML({ issuer: "https://jackson-demo.boxyhq.com", clientId: "tenant=boxyhq.com&product=saml-demo.boxyhq.com", clientSecret: "dummy" }),
Cognito({ clientId: process.env.COGNITO_ID, clientSecret: process.env.COGNITO_SECRET, issuer: process.env.COGNITO_ISSUER }),
Discord({ clientId: process.env.DISCORD_ID, clientSecret: process.env.DISCORD_SECRET }),
DuendeIDS6({ clientId: "interactive.confidential", clientSecret: "secret", issuer: "https://demo.duendesoftware.com" }),
Facebook({ clientId: process.env.FACEBOOK_ID, clientSecret: process.env.FACEBOOK_SECRET }),
Foursquare({ clientId: process.env.FOURSQUARE_ID, clientSecret: process.env.FOURSQUARE_SECRET }),
Freshbooks({ clientId: process.env.FRESHBOOKS_ID, clientSecret: process.env.FRESHBOOKS_SECRET }),
GitHub({ clientId: process.env.GITHUB_ID, clientSecret: process.env.GITHUB_SECRET }),
Gitlab({ clientId: process.env.GITLAB_ID, clientSecret: process.env.GITLAB_SECRET }),
Google({ clientId: process.env.GOOGLE_ID, clientSecret: process.env.GOOGLE_SECRET }),
IDS4({ clientId: process.env.IDS4_ID, clientSecret: process.env.IDS4_SECRET, issuer: process.env.IDS4_ISSUER }),
Instagram({ clientId: process.env.INSTAGRAM_ID, clientSecret: process.env.INSTAGRAM_SECRET }),
Keycloak({ clientId: process.env.KEYCLOAK_ID, clientSecret: process.env.KEYCLOAK_SECRET, issuer: process.env.KEYCLOAK_ISSUER }),
Line({ clientId: process.env.LINE_ID, clientSecret: process.env.LINE_SECRET }),
LinkedIn({ clientId: process.env.LINKEDIN_ID, clientSecret: process.env.LINKEDIN_SECRET }),
Mailchimp({ clientId: process.env.MAILCHIMP_ID, clientSecret: process.env.MAILCHIMP_SECRET }),
Okta({ clientId: process.env.OKTA_ID, clientSecret: process.env.OKTA_SECRET, issuer: process.env.OKTA_ISSUER }),
Osu({ clientId: process.env.OSU_CLIENT_ID, clientSecret: process.env.OSU_CLIENT_SECRET }),
Patreon({ clientId: process.env.PATREON_ID, clientSecret: process.env.PATREON_SECRET }),
Slack({ clientId: process.env.SLACK_ID, clientSecret: process.env.SLACK_SECRET }),
Spotify({ clientId: process.env.SPOTIFY_ID, clientSecret: process.env.SPOTIFY_SECRET }),
Trakt({ clientId: process.env.TRAKT_ID, clientSecret: process.env.TRAKT_SECRET }),
Twitch({ clientId: process.env.TWITCH_ID, clientSecret: process.env.TWITCH_SECRET }),
Twitter({ version: "2.0", clientId: process.env.TWITTER_ID, clientSecret: process.env.TWITTER_SECRET }),
TwitterLegacy({ clientId: process.env.TWITTER_LEGACY_ID, clientSecret: process.env.TWITTER_LEGACY_SECRET }),
Vk({ clientId: process.env.VK_ID, clientSecret: process.env.VK_SECRET }),
Wikimedia({ clientId: process.env.WIKIMEDIA_ID, clientSecret: process.env.WIKIMEDIA_SECRET }),
WorkOS({ clientId: process.env.WORKOS_ID, clientSecret: process.env.WORKOS_SECRET }),
],
}
if (authOptions.adapter) {
authOptions.providers.unshift(
// NOTE: You can start a fake e-mail server with `pnpm email`
// and then go to `http://localhost:1080` in the browser
Email({ server: "smtp://127.0.0.1:1025?tls.rejectUnauthorized=false" })
)
}
export default NextAuth(authOptions)

View File

@@ -2,6 +2,6 @@
import { getToken } from "next-auth/jwt"
export default async (req, res) => {
const token = await getToken({ req, secret: process.env.SECRET })
const token = await getToken({ req })
res.send(JSON.stringify(token, null, 2))
}

View File

@@ -3,9 +3,14 @@ import { withAuth } from "next-auth/middleware"
// More on how NextAuth.js middleware works: https://next-auth.js.org/configuration/nextjs#middleware
export default withAuth({
callbacks: {
authorized: ({ req, token }) =>
// /admin requires admin role, but /me only requires the user to be logged in.
req.nextUrl.pathname !== "/admin" || token?.userRole === "admin",
authorized({ req, token }) {
// `/admin` requires admin role
if (req.nextUrl.pathname === "/admin") {
return token?.userRole === "admin"
}
// `/me` only requires the user to be logged in
return !!token
},
},
})

View File

@@ -1,19 +1,15 @@
{
"name": "next-auth-example",
"version": "0.0.0",
"private": true,
"description": "An example project for NextAuth.js",
"description": "An example project for NextAuth.js with Next.js",
"repository": "https://github.com/nextauthjs/next-auth-example.git",
"bugs": {
"url": "https://github.com/nextauthjs/next-auth/issues"
},
"homepage": "https://next-auth-example.vercel.app",
"main": "",
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start",
"types": "tsc --noEmit"
"start": "next start"
},
"author": "Iain Collins <me@iaincollins.com>",
"contributors": [
@@ -21,20 +17,16 @@
"Nico Domino <yo@ndo.dev>",
"Lluis Agusti <hi@llu.lu>"
],
"license": "ISC",
"dependencies": {
"next": "12.1.7-canary.51",
"next": "latest",
"next-auth": "latest",
"nodemailer": "^6",
"react": "^18",
"react-dom": "^18"
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@types/node": "^17",
"@types/react": "^18",
"@types/react": "^18.0.15",
"typescript": "^4"
},
"prettier": {
"semi": false
}
}

View File

@@ -1,7 +1,8 @@
import { SessionProvider } from "next-auth/react"
import type { AppProps } from "next/app"
import "./styles.css"
import type { AppProps } from "next/app"
// Use of the <SessionProvider> is mandatory to allow components that call
// `useSession()` anywhere in your application to access the `session` object.
export default function App({ Component, pageProps }: AppProps) {

View File

@@ -1,10 +1,14 @@
// This is an example of how to read a JSON Web Token from an API route
import { getToken } from "next-auth/jwt"
import type { NextApiRequest, NextApiResponse } from "next"
const secret = process.env.NEXTAUTH_SECRET
export default async (req: NextApiRequest, res: NextApiResponse) => {
const token = await getToken({ req, secret })
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
// If you don't have the NEXTAUTH_SECRET environment variable set,
// you will have to pass your secret as `secret` to `getToken`
const token = await getToken({ req })
res.send(JSON.stringify(token, null, 2))
}

View File

@@ -1,19 +1,23 @@
// This is an example of to protect an API route
import { unstable_getServerSession } from "next-auth/next"
import { authOptions } from "../auth/[...nextauth]"
import type { NextApiRequest, NextApiResponse } from "next"
export default async (req: NextApiRequest, res: NextApiResponse) => {
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const session = await unstable_getServerSession(req, res, authOptions)
if (session) {
res.send({
return res.send({
content:
"This is protected content. You can access this content because you are signed in.",
})
} else {
res.send({
error:
"You must be signed in to view the protected content on this page.",
})
}
res.send({
error: "You must be signed in to view the protected content on this page.",
})
}

View File

@@ -1,8 +1,13 @@
// This is an example of how to access a session from an API route
import { getSession } from "next-auth/react"
import { unstable_getServerSession } from "next-auth"
import { authOptions } from "../auth/[...nextauth]"
import type { NextApiRequest, NextApiResponse } from "next"
export default async (req: NextApiRequest, res: NextApiResponse) => {
const session = await getSession({ req })
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const session = await unstable_getServerSession(req, res, authOptions)
res.send(JSON.stringify(session, null, 2))
}

View File

@@ -1,12 +1,13 @@
import { unstable_getServerSession } from "next-auth/next"
import { authOptions } from "./api/auth/[...nextauth]"
import Layout from "../components/layout"
import type { NextPageContext } from "next"
export default function ServerSidePage({ session }) {
import type { GetServerSidePropsContext } from "next"
import type { Session } from "next-auth"
export default function ServerSidePage({ session }: { session: Session }) {
// As this page uses Server Side Rendering, the `session` will be already
// populated on render without needing to go through a loading stage.
return (
<Layout>
<h1>Server Side Rendering</h1>
@@ -28,15 +29,20 @@ export default function ServerSidePage({ session }) {
The disadvantage of Server Side Rendering is that this page is slower to
render.
</p>
<pre>{JSON.stringify(session, null, 2)}</pre>
</Layout>
)
}
// Export the `session` prop to use sessions with Server Side Rendering
export async function getServerSideProps(context: NextPageContext) {
export async function getServerSideProps(context: GetServerSidePropsContext) {
return {
props: {
session: await unstable_getServerSession(context.req, context.res, authOptions),
session: await unstable_getServerSession(
context.req,
context.res,
authOptions
),
},
}
}

View File

@@ -21,7 +21,7 @@
"eslint-plugin-svelte3": "^3.2.1",
"prettier": "^2.5.1",
"prettier-plugin-svelte": "^2.5.0",
"svelte": "^3.44.0",
"svelte": "^3.49.0",
"svelte-check": "^2.2.6",
"svelte-preprocess": "^4.10.1",
"tslib": "^2.3.1",

View File

@@ -1232,10 +1232,10 @@ natural-compare@^1.4.0:
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=
next-auth@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/next-auth/-/next-auth-4.5.0.tgz#2df57287fddc705b8971c88c60bad44a89ac6dd1"
integrity sha512-B6gYRIbqtj8nlDsx3y2Ruwp/mvZnItPs7VUULY43QYw+M9xtDPIM9EBZ3ryd/wNYA3MDteBJlzGm/ivseXcmJA==
"next-auth@workspace:*":
version "4.9.0"
resolved "https://registry.yarnpkg.com/next-auth/-/next-auth-4.9.0.tgz#0d8cabcb22a976744131a2e68d5f08756f322593"
integrity sha512-/4S5dFeyNg2nXlD7g/Sh5A4WZWnUMDpEf8x/x+gzmAf5cAY2SjDM6sLk9u4XRmsndsxQpIMWDw03sUTAD+Yzog==
dependencies:
"@babel/runtime" "^7.16.3"
"@panva/hkdf" "^1.0.1"
@@ -1617,10 +1617,10 @@ svelte-preprocess@^4.0.0, svelte-preprocess@^4.10.1:
sorcery "^0.10.0"
strip-indent "^3.0.0"
svelte@^3.44.0:
version "3.46.4"
resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.46.4.tgz#0c46bc4a3e20a2617a1b7dc43a722f9d6c084a38"
integrity sha512-qKJzw6DpA33CIa+C/rGp4AUdSfii0DOTCzj/2YpSKKayw5WGSS624Et9L1nU1k2OVRS9vaENQXp2CVZNU+xvIg==
svelte@^3.49.0:
version "3.49.0"
resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.49.0.tgz#5baee3c672306de1070c3b7888fc2204e36a4029"
integrity sha512-+lmjic1pApJWDfPCpUUTc1m8azDqYCG1JN9YEngrx/hUyIcFJo6VZhj0A1Ai0wqoHcEIuQy+e9tk+4uDgdtsFA==
table@^6.0.9:
version "6.8.0"

View File

@@ -11,7 +11,7 @@ This is the Dgraph Adapter for [`next-auth`](https://next-auth.js.org).
1. Install the necessary packages
```bash npm2yarn
```bash npm2yarn2pnpm
npm install next-auth @next-auth/dgraph-adapter
```
@@ -226,22 +226,22 @@ database you must customize next-auth `encode` and `decode` functions, as the de
further customize the jwt with roles if you want to implement [`RBAC logic`](https://dgraph.io/docs/graphql/authorization/directive/#role-based-access-control).
```js
import * as jwt from "jsonwebtoken";
import * as jwt from "jsonwebtoken"
export default NextAuth({
session: {
strategy: "jwt"
strategy: "jwt",
},
jwt: {
secret: process.env.SECRET,
encode: async ({ secret, token }) => {
return jwt.sign({...token, userId: token.id}, secret, {
return jwt.sign({ ...token, userId: token.id }, secret, {
algorithm: "HS256",
expiresIn: 30 * 24 * 60 * 60, // 30 days
});
})
},
decode: async ({ secret, token }) => {
return jwt.verify(token, secret, { algorithms: ["HS256"] });
}
return jwt.verify(token, secret, { algorithms: ["HS256"] })
},
},
})
```

View File

@@ -15,7 +15,7 @@ You can find the full schema in the table structure section below.
1. Install `next-auth` and `@next-auth/dynamodb-adapter`
```bash npm2yarn
```bash npm2yarn2pnpm
npm install next-auth @next-auth/dynamodb-adapter
```

View File

@@ -13,7 +13,7 @@ You can find the Fauna schema and seed information in the docs at [next-auth.js.
1. Install the necessary packages
```bash npm2yarn
```bash npm2yarn2pnpm
npm install next-auth @next-auth/fauna-adapter faunadb
```

View File

@@ -5,18 +5,14 @@ title: Firebase
# Firebase
:::warning
This adapter is still experimental and does not work with NextAuth.js 4 or newer. If you would like to help out upgrading it, please visit [this PR](https://github.com/nextauthjs/next-auth/pull/3873)
:::
This is the Firebase Adapter for [`next-auth`](https://next-auth.js.org). This package can only be used in conjunction with the primary `next-auth` package. It is not a standalone package.
This is the Firebase (Firestore) Adapter for [`next-auth`](https://next-auth.js.org). This package can only be used in conjunction with the primary `next-auth` package. It is not a standalone package.
## Getting Started
1. Install the necessary packages
```bash npm2yarn
npm install next-auth @next-auth/firebase-adapter@experimental
```bash npm2yarn2pnpm
npm install next-auth @next-auth/firebase-adapter
```
2. Add this adapter to your `pages/api/auth/[...nextauth].js` next-auth configuration object.
@@ -24,14 +20,7 @@ npm install next-auth @next-auth/firebase-adapter@experimental
```javascript title="pages/api/auth/[...nextauth].js"
import NextAuth from "next-auth"
import GoogleProvider from "next-auth/providers/google"
import { FirebaseAdapter } from "@next-auth/firebase-adapter"
import firebase from "firebase/app"
import "firebase/firestore"
const firestore = (
firebase.apps[0] ?? firebase.initializeApp(/* your config */)
).firestore()
import { FirestoreAdapter } from "@next-auth/firebase-adapter"
// For more information on each option (and a full list of options) go to
// https://next-auth.js.org/configuration/options
@@ -43,9 +32,19 @@ export default NextAuth({
clientSecret: process.env.GOOGLE_SECRET,
}),
],
adapter: FirebaseAdapter(firestore),
...
})
adapter: FirestoreAdapter({
apiKey: process.env.FIREBASE_API_KEY,
appId: process.env.FIREBASE_APP_ID,
authDomain: process.env.FIREBASE_AUTH_DOMAIN,
databaseURL: process.env.FIREBASE_DATABASE_URL,
projectId: process.env.FIREBASE_PROJECT_ID,
storageBucket: process.env.FIREBASE_STORAGE_BUCKET,
messagingSenderId: process.env.FIREBASE_MESSAGING_SENDER_ID,
// Optional emulator config (see below for options)
emulator: {},
}),
// ...
});
```
## Options
@@ -69,6 +68,21 @@ const firebaseConfig = {
See [firebase.google.com/docs/web/setup](https://firebase.google.com/docs/web/setup) for more details.
You can optionally pass in emulator options to automatically connect to your local Firebase emulator.
```js
FirestoreAdapter({
// ...
// Passing in an enable object will enable the emulator
emulator: {
// Optional host, defaults to `localhost`
host: 'localhost',
// Optional port, defaults to `3001`
port: 3001,
},
}),
```
:::tip **From Firebase**
**Caution**: We do not recommend manually modifying an app's Firebase config file or object. If you initialize an app with invalid or missing values for any of these required "Firebase options", then your end users may experience serious issues.

View File

@@ -5,7 +5,7 @@ title: MikroORM
To use this Adapter, you need to install Mikro ORM, the driver that suits your database, and the separate `@next-auth/mikro-orm-adapter` package:
```bash npm2yarn
```bash npm2yarn2pnpm
npm install next-auth @next-auth/mikro-orm-adapter @mikro-orm/core @mikro-orm/[YOUR DRIVER]
```

View File

@@ -11,7 +11,7 @@ The MongoDB adapter does not handle connections automatically, so you will have
1. Install the necessary packages
```bash npm2yarn
```bash npm2yarn2pnpm
npm install next-auth @next-auth/mongodb-adapter mongodb
```

View File

@@ -11,7 +11,7 @@ This is the Neo4j Adapter for [`next-auth`](https://next-auth.js.org). This pack
1. Install the necessary packages
```bash npm2yarn
```bash npm2yarn2pnpm
npm install next-auth @next-auth/neo4j-adapter neo4j-driver
```

View File

@@ -19,7 +19,7 @@ Depending on your architecture you can use PouchDB's http adapter to reach any d
1. Install `next-auth` and `@next-auth/pouchdb-adapter`
```bash npm2yarn
```bash npm2yarn2pnpm
npm install next-auth @next-auth/pouchdb-adapter
```

View File

@@ -7,7 +7,7 @@ title: Prisma
To use this Adapter, you need to install Prisma Client, Prisma CLI, and the separate `@next-auth/prisma-adapter` package:
```bash npm2yarn
```bash npm2yarn2pnpm
npm install next-auth @prisma/client @next-auth/prisma-adapter
npm install prisma --save-dev
```

View File

@@ -11,7 +11,7 @@ This is the Sequelize Adapter for [`next-auth`](https://next-auth.js.org).
1. Install the necessary packages
```bash npm2yarn
```bash npm2yarn2pnpm
npm install next-auth @next-auth/sequelize-adapter sequelize
```

View File

@@ -5,21 +5,25 @@ title: TypeORM
# TypeORM
This Adapter is used to support SQL-flavored databases (like SQLite, MySQL, MSSQL, MariaDB, CockroachDB, etc.) through [TypeORM](https://typeorm.io), and mostly kept around for legacy reasons. (See the warning below.)
This Adapter is used to support SQL-flavored databases (like SQLite, MySQL, MSSQL, MariaDB, CockroachDB, etc.) through [TypeORM](https://typeorm.io).
:::note
If you previously used this Adapter with MongoDB, check out the [MongoDB Adapter](/adapters/mongodb) instead.
:::
:::warning
:::note
In the future, we might split up this adapter to support single flavors of SQL for easier maintenance and reduced bundle size.
:::
## Usage
:::warning
[`typeorm`](https://github.com/typeorm/typeorm) is still in active development and has not yet published a stable release. Because of this, you can expect breaking changes in minor versions. This adapter expects `typeorm@0.3.7` and is not validated against previous or future releases.
:::
To use this Adapter, you need to install the following packages:
```bash npm2yarn
```bash npm2yarn2pnpm
npm install next-auth @next-auth/typeorm-legacy-adapter typeorm
```
@@ -36,7 +40,7 @@ export default NextAuth({
})
```
`TypeORMLegacyAdapter` takes either a connection string, or a [`ConnectionOptions`](https://github.com/typeorm/typeorm/blob/master/docs/connection-options.md) object as its first parameter.
`TypeORMLegacyAdapter` takes either a connection string, or a [`DataSourceOptions`](https://github.com/typeorm/typeorm/blob/master/docs/data-source-options.md) object as its first parameter.
## Custom models
@@ -217,9 +221,9 @@ For example, you can add the naming convention option to the connection object i
import NextAuth from "next-auth"
import { TypeORMLegacyAdapter } from "@next-auth/typeorm-legacy-adapter"
import { SnakeNamingStrategy } from 'typeorm-naming-strategies'
import { ConnectionOptions } from "typeorm"
const connection: ConnectionOptions = {
export default NextAuth({
adapter: TypeORMLegacyAdapter({
type: "mysql",
host: "localhost",
port: 3306,
@@ -227,10 +231,7 @@ const connection: ConnectionOptions = {
password: "test",
database: "test",
namingStrategy: new SnakeNamingStrategy()
}
export default NextAuth({
adapter: TypeORMLegacyAdapter(connection),
}),
...
})
```

View File

@@ -7,7 +7,7 @@ title: Upstash Redis
To use this Adapter, you need to install `@upstash/redis` and `@next-auth/upstash-redis-adapter` package:
```bash npm2yarn
```bash npm2yarn2pnpm
npm install @upstash/redis @next-auth/upstash-redis-adapter
```

View File

@@ -8,7 +8,7 @@ This feature is experimental and may be removed or changed in the future.
When calling from server-side i.e. in API routes or in `getServerSideProps`, we recommend using this function instead of `getSession` to retrieve the `session` object. This method is especially useful when you are using NextAuth.js with a database. This method can _drastically_ reduce response time when used over `getSession` server-side, due to avoiding an extra `fetch` to an API Route (this is generally [not recommended in Next.js](https://nextjs.org/docs/basic-features/data-fetching/get-server-side-props#getserversideprops-or-api-routes)). In addition, `unstable_getServerSession` will correctly update the cookie expiry time and update the session content if `callbacks.jwt` or `callbacks.session` changed something.
Otherwise, if you only want to get the session token, see [`getToken`](tutorials/securing-pages-and-api-routes#using-gettoken).
Otherwise, if you only want to get the session token, see [`getToken`](/tutorials/securing-pages-and-api-routes#using-gettoken).
`unstable_getServerSession` requires passing the same object you would pass to `NextAuth` when initializing NextAuth.js. To do so, you can export your NextAuth.js options in the following way:
@@ -80,10 +80,11 @@ You can get the `withAuth` middleware function from `next-auth/middleware` eithe
### Prerequisites
You must set the [`NEXTAUTH_SECRET`](/configuration/options#nextauth_secret) environment variable when using this middleware. If you are using the [`secret` option](/configuration/options#secret) this value must match.
You must set the same secret in the middleware that you use in NextAuth. The easiest way is to set the [`NEXTAUTH_SECRET`](/configuration/options#nextauth_secret) environment variable. It will be picked up by both the [NextAuth config](/configuration/options#options), as well as the middleware config.
**We strongly recommend** replacing the `secret` value completely with this `NEXTAUTH_SECRET` environment variable. This environment variable will be picked up by both the [NextAuth config](/configuration/options#options), as well as the middleware config.
Alternatively, you can provide the secret using the [`secret`](#secret) option in the middleware config.
**We strongly recommend** replacing the `secret` value completely with this `NEXTAUTH_SECRET` environment variable.
### Basic usage
@@ -149,6 +150,22 @@ See the documentation for the [pages option](/configuration/pages) for more info
---
### `secret`
- **Required**: _No_
#### Description
The same `secret` used in the [NextAuth config](/configuration/options#options).
#### Example (default value)
```js
secret: process.env.NEXTAUTH_SECRET
```
---
### Advanced usage
NextAuth.js Middleware is very flexible, there are multiple ways to use it.
@@ -160,13 +177,11 @@ If you do not define the options, NextAuth.js will use the default values for th
#### wrap middleware
```ts title="middleware.ts"
import type { NextRequest } from "next/server"
import type { JWT } from "next-auth/jwt"
import { withAuth } from "next-auth/middleware"
export default withAuth(
// `withAuth` can augment your Request with the user's token.
function middleware(req: NextRequest & { nextauth: { token: JWT | null } }) {
// `withAuth` augments your `Request` with the user's token.
function middleware(req) {
console.log(req.nextauth.token)
},
{

View File

@@ -13,19 +13,22 @@ When deploying to production, set the `NEXTAUTH_URL` environment variable to the
NEXTAUTH_URL=https://example.com
```
If your Next.js application uses a custom base path, specify the route to the API endpoint in full.
If your Next.js application uses a custom base path, specify the route to the API endpoint in full. More informations about the usage of custom base path [here](/getting-started/client#custom-base-path).
_e.g. `NEXTAUTH_URL=https://example.com/custom-route/api/auth`_
:::tip
When you're using a custom base path, you will need to pass the `basePath` page prop to the `<SessionProvider>`. More informations [here](/getting-started/client#custom-base-path).
:::
:::note
Using [System Environment Variables](https://vercel.com/docs/concepts/projects/environment-variables#system-environment-variables) we automatically detect when you deploy to [Vercel](https://vercel.com) so you don't have to define this variable. Make sure **Automatically expose System Environment Variables** is checked in your Project Settings.
:::
### NEXTAUTH_SECRET
Used to encrypt the NextAuth.js JWT, and to hash [email verification tokens](/adapters/models#verification-token). This is the default value for the [`secret`](/configuration/options#secret) option. The `secret` option might be removed in the future in favor of this.
Used to encrypt the NextAuth.js JWT, and to hash [email verification tokens](/adapters/models#verification-token). This is the default value for the `secret` option in [NextAuth](/configuration/options#secret) and [Middleware](/configuration/nextjs#secret).
If you are using [Middleware](/configuration/nextjs#prerequisites) this environment variable must be set.
### NEXTAUTH_URL_INTERNAL
@@ -366,11 +369,14 @@ Changes the color scheme theme of [pages](/configuration/pages) as well as allow
In addition, you can define a logo URL in `theme.logo` which will be rendered above the main card in the default signin/signout/error/verify-request pages, as well as a `theme.brandColor` which will affect the accent color of these pages.
The sign-in button's background color will match the `brandColor` and defaults to `"#346df1"`. The text color is `#fff` by default, but if your brand color gives a weak contrast, correct it with the `buttonText` color option.
```js
theme: {
colorScheme: "auto", // "auto" | "dark" | "light"
brandColor: "", // Hex color code
logo: "" // Absolute URL to image
logo: "", // Absolute URL to image
buttonText: "" // Hex color code
}
```

View File

@@ -350,7 +350,7 @@ providers: [
## Built-in providers
NextAuth.js comes with a set of built-in providers. You can find them [here](https://github.com/nextauthjs/next-auth/tree/main/src/providers). Each built-in provider has its own documentation page:
NextAuth.js comes with a set of built-in providers. You can find them [here](https://github.com/nextauthjs/next-auth/tree/main/packages/next-auth/src/providers). Each built-in provider has its own documentation page:
<div className="provider-name-list">
{Object.entries(require("../../../providers.json"))

View File

@@ -11,6 +11,7 @@ Without these people, the project could not have become one of the most used aut
- [Balázs Orbán](https://github.com/balazsorban44) - **Lead Maintainer**
- [Nico Domino](https://github.com/ndom91) - Maintainer (Documentation, Core)
- [Lluis Agusti](https://github.com/lluia) - Maintainer (Documentation, Testing, TypeScript)
- [Thang Huu Vu](https://github.com/ThangHuuVu) - Maintainer (Core, TypeScript)
## Special thanks

View File

@@ -61,17 +61,20 @@ There should also be further details logged when this occurs, such as the error
### Signin / Callback
#### GET_AUTHORIZATION_URL_ERROR
This error can occur when we cannot get the OAuth v1 request token and generate the authorization URL.
Please double check your OAuth v1 provider settings, especially the OAuth token and OAuth token secret.
#### SIGNIN_OAUTH_ERROR
This error can occur in one of a few places, first during the redirect to the authorization URL of the provider. Next, in the signin flow while creating the PKCE code verifier. Finally, during the generation of the CSRF Token hash in the internal state during signin.
This error occurs during the redirection to the authorization URL of the OAuth provider. Possible causes:
Please check your OAuth provider settings and make sure your URLs and other options are correctly set on the provider side.
1. Cookie handling
Either PKCE code verifier or the generation of the CSRF token hash in the internal state failed.
If set, check your [`cookies` configuration](/configuration/options#cookies), and make sure the browser is not blocking/restricting cookies.
2. OAuth misconfiguration
Please check your OAuth provider and make sure your URLs and other options are correctly set.
If you are using an OAuth v1 provider, check your OAuth v1 provider settings, especially the OAuth token and OAuth token secret.
#### CALLBACK_OAUTH_ERROR
@@ -151,12 +154,6 @@ This error occurs when there was an issue deleting the session from the database
### Other
#### SEND_VERIFICATION_EMAIL_ERROR
This error occurs when the Email Authentication Provider is unable to send an email.
Check your mail server configuration.
#### MISSING_NEXTAUTH_API_ROUTE_ERROR
This error happens when `[...nextauth].js` file is not found inside `pages/api/auth`.

View File

@@ -63,17 +63,32 @@ _If you use a custom credentials provider user accounts will not be persisted in
<details>
<summary>
<h3 style={{display:"inline-block"}}>Can I use NextAuth.js with a website that does not use Next.js?</h3>
<h3 style={{display:"inline-block"}}>Can I use NextAuth.js with a framework different than Next.js?</h3>
</summary>
<p>
NextAuth.js is designed for use with Next.js and Serverless.
NextAuth.js was originally designed for use with Next.js and Serverless. However, today you could use the NextAuth.js core with any other framework. Checkout the examples for <a href="https://github.com/nextauthjs/next-auth/tree/main/apps/example-gatsby" target="_blank">Gatsby</a> and <a href="https://github.com/nextauthjs/next-auth/tree/main/apps/playground-sveltekit" target="_blank">SvelteKit</a>. If you would add another integration with other frameworks, feel free to work on it and send a pull request. Make sure to check if there's any on-going work before open a new issue.
If you are using a different framework for your website, you can create a website that handles sign in with Next.js and then access those sessions on a website that does not use Next.js as long as the websites are on the same domain.
</p>
</details>
If you use NextAuth.js on a website with a different subdomain then the rest of your website (e.g. `auth.example.com` vs `www.example.com`) you will need to set a custom cookie domain policy for the Session Token cookie. (See also: [Cookies](/configuration/options#cookies))
<details>
<summary>
<h3 style={{display:"inline-block"}}>Can session generated by NextAuth.js be used by another website?</h3>
</summary>
<p>
NextAuth.js does not currently support automatically signing into sites on different top level domains (e.g. `www.example.com` vs `www.example.org`) using a single session.
**Same domain**: you can create a website that handles sign-in with NextAuth.js and then access those sessions on a website that does not use NextAuth.js as long as the websites are on the same domain.
**Same root domain, different subdomains**: If you use NextAuth.js on a website with a different subdomain than the rest of your website (e.g. `auth.example.com` vs. `www.example.com`) you will need to set a custom cookie domain policy for the Session Token cookie. (See also: [Cookies](/configuration/options#cookies)).
:::warning
Changing the default cookies domain policy is advanced and can lead to security issues if done correctly. Make sure you're aware of the security implication before proceeding.
:::
A working example can be found at <a href="https://github.com/vercel/examples/tree/main/solutions/subdomain-auth" target="_blank">this example repo</a>.
**Different root domains**: NextAuth.js does not currently support automatically signing into sites on different top-level domains (e.g. `www.example.com` vs. `www.example.org`) using a single session.
</p>
</details>

View File

@@ -148,13 +148,9 @@ Because of how `_app` is written, it won't unnecessarily contact the `/api/auth/
More information can be found in the following [GitHub Issue](https://github.com/nextauthjs/next-auth/issues/1210).
### NextAuth.js + React-Query
### NextAuth.js + React Query
There is also an alternative client-side API library based upon [`react-query`](https://www.npmjs.com/package/react-query) available under [`nextauthjs/react-query`](https://github.com/nextauthjs/react-query).
If you use `react-query` in your project already, you can leverage it with NextAuth.js to handle the client-side session management for you as well. This replaces NextAuth.js's native `useSession` and `SessionProvider` from `next-auth/react`.
See repository [`README`](https://github.com/nextauthjs/react-query) for more details.
You can create your own session management solution using data fetching libraries like [React Query](https://tanstack.com/query/v4/docs/adapters/react-query) or [SWR](https://swr.vercel.app). You can use the [original implementation of `@next-auth/react-query`](https://github.com/nextauthjs/react-query) and look at the [`next-auth/react` source code](https://github.com/nextauthjs/next-auth/blob/main/packages/next-auth/src/react/index.tsx) as a starting point.
---
@@ -427,13 +423,14 @@ This only works on pages where you provide the correct `pageProps`, however. Thi
```js title="pages/index.js"
import { unstable_getServerSession } from "next-auth/next"
import { authOptions } from './api/auth/[...nextauth]'
...
export async function getServerSideProps(ctx) {
export async function getServerSideProps({ req, res }) {
return {
props: {
session: await unstable_getServerSession(ctx)
session: await unstable_getServerSession(req, res, authOptions)
}
}
}
@@ -505,3 +502,29 @@ However, if it was set to `false`, it stops re-fetching the session and the comp
:::note
See [**the Next.js documentation**](https://nextjs.org/docs/advanced-features/custom-app) for more information on **\_app.js** in Next.js applications.
:::
### Custom base path
When your Next.js application uses a custom base path, set the `NEXTAUTH_URL` environment variable to the route to the API endpoint in full - as in the example below and as explained [here](/configuration/options#nextauth_url).
Also, make sure to pass the `basePath` page prop to the `<SessionProvider>` as in the example below so your custom base path is fully configured and used by NextAuth.js.
#### Example
In this example, the custom base path used is `/custom-route`.
```
NEXTAUTH_URL=https://example.com/custom-route/api/auth
```
```jsx title="pages/_app.js"
import { SessionProvider } from "next-auth/react"
export default function App({
Component,
pageProps: { session, ...pageProps },
}) {
return (
<SessionProvider session={session} basePath="/custom-route/api/auth">
<Component {...pageProps} />
</SessionProvider>
)
}
```

View File

@@ -97,6 +97,7 @@ To protect an API Route, you can use the [`unstable_getServerSession()`](/config
```javascript title="pages/api/restricted.js" showLineNumbers
import { unstable_getServerSession } from "next-auth/next"
import { authOptions } from "./api/auth/[...nextauth]"
export default async (req, res) => {
const session = await unstable_getServerSession(req, res, authOptions)

View File

@@ -13,7 +13,7 @@ We encourage users to try it out and report any and all issues they come across.
You can upgrade to the new version by running:
```bash npm2yarn
```bash npm2yarn2pnpm
npm install next-auth
```

View File

@@ -124,67 +124,74 @@ providers: [
The following code shows the complete source for the built-in `sendVerificationRequest()` method:
```js
import nodemailer from "nodemailer"
import { createTransport } from "nodemailer"
async function sendVerificationRequest({
identifier: email,
url,
provider: { server, from },
}) {
async function sendVerificationRequest(params) {
const { identifier, url, provider, theme } = params
const { host } = new URL(url)
const transport = nodemailer.createTransport(server)
await transport.sendMail({
to: email,
from,
// NOTE: You are not required to use `nodemailer`, use whatever you want.
const transport = createTransport(provider.server)
const result = await transport.sendMail({
to: identifier,
from: provider.from,
subject: `Sign in to ${host}`,
text: text({ url, host }),
html: html({ url, host, email }),
html: html({ url, host, theme }),
})
const failed = result.rejected.concat(result.pending).filter(Boolean)
if (failed.length) {
throw new Error(`Email(s) (${failed.join(", ")}) could not be sent`)
}
}
// Email HTML body
function html({ url, host, email }: Record<"url" | "host" | "email", string>) {
// Insert invisible space into domains and email address to prevent both the
// email address and the domain from being turned into a hyperlink by email
// clients like Outlook and Apple mail, as this is confusing because it seems
// like they are supposed to click on their email address to sign in.
const escapedEmail = `${email.replace(/\./g, "&#8203;.")}`
const escapedHost = `${host.replace(/\./g, "&#8203;.")}`
/**
* Email HTML body
* Insert invisible space into domains 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 it to sign in.
*
* @note We don't add the email address to avoid needing to escape it, if you do, remember to sanitize it!
*/
function html(params: { url: string; host: string; theme: Theme }) {
const { url, host, theme } = params
// Some simple styling options
const backgroundColor = "#f9f9f9"
const textColor = "#444444"
const mainBackgroundColor = "#ffffff"
const buttonBackgroundColor = "#346df1"
const buttonBorderColor = "#346df1"
const buttonTextColor = "#ffffff"
const escapedHost = host.replace(/\./g, "&#8203;.")
const brandColor = theme.brandColor || "#346df1"
const color = {
background: "#f9f9f9",
text: "#444",
mainBackground: "#fff",
buttonBackground: brandColor,
buttonBorder: brandColor,
buttonText: theme.buttonText || "#fff",
}
return `
<body style="background: ${backgroundColor};">
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<body style="background: ${color.background};">
<table width="100%" border="0" cellspacing="20" cellpadding="0"
style="background: ${color.mainBackground}; max-width: 600px; margin: auto; border-radius: 10px;">
<tr>
<td align="center" style="padding: 10px 0px 20px 0px; font-size: 22px; font-family: Helvetica, Arial, sans-serif; color: ${textColor};">
<strong>${escapedHost}</strong>
</td>
</tr>
</table>
<table width="100%" border="0" cellspacing="20" cellpadding="0" style="background: ${mainBackgroundColor}; max-width: 600px; margin: auto; border-radius: 10px;">
<tr>
<td align="center" style="padding: 10px 0px 0px 0px; font-size: 18px; font-family: Helvetica, Arial, sans-serif; color: ${textColor};">
Sign in as <strong>${escapedEmail}</strong>
<td align="center"
style="padding: 10px 0px; font-size: 22px; font-family: Helvetica, Arial, sans-serif; color: ${color.text};">
Sign in to <strong>${escapedHost}</strong>
</td>
</tr>
<tr>
<td align="center" style="padding: 20px 0;">
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center" style="border-radius: 5px;" bgcolor="${buttonBackgroundColor}"><a href="${url}" target="_blank" style="font-size: 18px; font-family: Helvetica, Arial, sans-serif; color: ${buttonTextColor}; text-decoration: none; border-radius: 5px; padding: 10px 20px; border: 1px solid ${buttonBorderColor}; display: inline-block; font-weight: bold;">Sign in</a></td>
<td align="center" style="border-radius: 5px;" bgcolor="${color.buttonBackground}"><a href="${url}"
target="_blank"
style="font-size: 18px; font-family: Helvetica, Arial, sans-serif; color: ${color.buttonText}; text-decoration: none; border-radius: 5px; padding: 10px 20px; border: 1px solid ${color.buttonBorder}; display: inline-block; font-weight: bold;">Sign
in</a></td>
</tr>
</table>
</td>
</tr>
<tr>
<td align="center" style="padding: 0px 0px 10px 0px; font-size: 16px; line-height: 22px; font-family: Helvetica, Arial, sans-serif; color: ${textColor};">
<td align="center"
style="padding: 0px 0px 10px 0px; font-size: 16px; line-height: 22px; font-family: Helvetica, Arial, sans-serif; color: ${color.text};">
If you did not request this email you can safely ignore it.
</td>
</tr>
@@ -193,8 +200,8 @@ function html({ url, host, email }: Record<"url" | "host" | "email", string>) {
`
}
// Email Text body (fallback for email clients that don't render HTML, e.g. feature phones)
function text({ url, host }: Record<"url" | "host", string>) {
/** Email Text body (fallback for email clients that don't render HTML, e.g. feature phones) */
function text({ url, host }: { url: string; host: string }) {
return `Sign in to ${host}\n${url}\n\n`
}
```

View File

@@ -19,7 +19,7 @@ https://github.com/settings/apps
The **GitHub Provider** comes with a set of default options:
- [GitHub Provider options](https://github.com/nextauthjs/next-auth/blob/main/packages/next-auth/src/providers/github.js)
- [GitHub Provider options](https://github.com/nextauthjs/next-auth/blob/main/packages/next-auth/src/providers/github.ts)
You can override any of the options to suit your own use case.

View File

@@ -5,7 +5,7 @@ title: Overview
Authentication Providers in **NextAuth.js** are services that can be used to sign in a user.
There's four ways a user can be signed in:
There are four ways a user can be signed in:
- [Using a built-in OAuth Provider](/configuration/providers/oauth) (e.g Github, Twitter, Google, etc...)
- [Using a custom OAuth Provider](/configuration/providers/oauth#using-a-custom-provider)

View File

@@ -0,0 +1,50 @@
---
id: wikimedia
title: Wikimedia
---
## Documentation
https://www.mediawiki.org/wiki/Extension:OAuth
This provider also supports all Wikimedia projects:
- Wikipedia
- Wikidata
- Wikibooks
- Wiktionary
- etc..
Please be aware that Wikimedia accounts do not have to have an associated email address. So you may want to add check if the user has an email address before allowing them to login.
## Configuration
1. Go to and accept the Consumer Registration doc: https://meta.wikimedia.org/wiki/Special:OAuthConsumerRegistration
2. Request a new OAuth 2.0 consumer to get the `clientId` and `clientSecret`: https://meta.wikimedia.org/wiki/Special:OAuthConsumerRegistration/propose/oauth2
2a. Add the following redirect URL into the console `http://<your-next-app-url>/api/auth/callback/wikimedia`
2b. Do not check the box next to `This consumer is only for [your username]`
2c. Unless you explicitly need a larger scope, feel free to select the radio button labelled `User identity verification only - no ability to read pages or act on the users behalf.`
After registration, you can initally test your application only with your own Wikimedia account. You may have to wait several days for the application to be approved for it to be used by everyone.
## Options
The **Wikimedia Provider** comes with a set of default options:
- [Wikimedia Provider options](https://github.com/nextauthjs/next-auth/blob/main/packages/next-auth/src/providers/wikimedia.ts)
You can override any of the options to suit your own use case.
## Example
```js
import WikimediaProvider from "next-auth/providers/wikimedia";
...
providers: [
WikimediaProvider({
clientId: process.env.WIKIMEDIA_CLIENT_ID,
clientSecret: process.env.WIKIMEDIA_CLIENT_SECRET
})
]
...
```

View File

@@ -7,7 +7,7 @@ NextAuth.js provides the ability to setup a [custom Credential provider](/config
You will need an additional dependency, `ldapjs`, which you can install by running
```bash npm2yarn
```bash npm2yarn2pnpm
npm install ldapjs
```

View File

@@ -44,7 +44,7 @@ export default function Page() {
With NextAuth.js 4.2.0 and Next.js 12, you can now protect your pages via the middleware pattern more easily. If you would like to protect all pages, you can create a `_middleware.js` file in your root `pages` directory which looks like this.
```js title="/pages/_middleware.js"
```js title="/middleware.js"
export { default } from "next-auth/middleware"
```
@@ -61,7 +61,9 @@ You can protect server side rendered pages using the `unstable_getServerSession`
You need to add this to every server rendered page you want to protect. Be aware, `unstable_getServerSession` takes slightly different arguments than the method it is replacing, `getSession`.
```js title="pages/server-side-example.js"
import { useSession, unstable_getServerSession } from "next-auth/next"
import { unstable_getServerSession } from "next-auth/next"
import { authOptions } from "./api/auth/[...nextauth]"
import { useSession } from "next-auth/react"
export default function Page() {
const { data: session } = useSession()
@@ -120,6 +122,7 @@ You can protect API routes using the `unstable_getServerSession()` method.
```js title="pages/api/get-session-example.js"
import { unstable_getServerSession } from "next-auth/next"
import { authOptions } from "./api/auth/[...nextauth]"
export default async (req, res) => {
const session = await unstable_getServerSession(req, res, authOptions)
@@ -142,10 +145,9 @@ If you are using JSON Web Tokens you can use the `getToken()` helper to access t
// This is an example of how to read a JSON Web Token from an API route
import { getToken } from "next-auth/jwt"
const secret = process.env.SECRET
export default async (req, res) => {
const token = await getToken({ req, secret })
// If you don't have NEXTAUTH_SECRET set, you will have to pass your secret as `secret` to `getToken`
const token = await getToken({ req })
if (token) {
// Signed in
console.log("JSON Web Token", JSON.stringify(token, null, 2))

View File

@@ -9,7 +9,7 @@ To test an implementation of NextAuth.js, we encourage you to use [Cypress](http
To get started, install the dependencies:
```bash npm2yarn
```bash npm2yarn2pnpm
npm install --save-dev cypress cypress-social-logins @testing-library/cypress
```

View File

@@ -37,6 +37,12 @@ Twitter OAuth 2.0 is currently in beta as certain changes might still be necessa
Some APIs are still experimental; they may be changed or removed in the future. Use at your own risk.
#### DEBUG_ENABLED
You have enabled the `debug` option. It is meant for development only, to help you catch issues in your authentication flow and you should consider removing this option when deploying to production. One way of only allowing debugging while not in production is to set `debug: process.env.NODE_ENV !== "production"`, so you can commit this without needing to change the value.
If you want to log debug messages during production anyway, we recommend setting the [`logger` option](/configuration/options#logger) with proper sanitization of potentially sensitive user information.
## Adapter
### ADAPTER_TYPEORM_UPDATING_ENTITIES

View File

@@ -155,9 +155,9 @@ module.exports = {
showLastUpdateAuthor: true,
showLastUpdateTime: true,
remarkPlugins: [
require("@sapphire/docusaurus-plugin-npm2yarn2pnpm").npm2yarn2pnpm,
require("remark-github"),
require("mdx-mermaid"),
[require("@docusaurus/remark-plugin-npm2yarn"), { sync: true }],
],
versions: {
current: {

View File

@@ -21,9 +21,9 @@
"dependencies": {
"@docusaurus/core": "^2.0.0-beta.21",
"@docusaurus/preset-classic": "^2.0.0-beta.21",
"@docusaurus/remark-plugin-npm2yarn": "^2.0.0-beta.21",
"@docusaurus/theme-common": "2.0.0-beta.21",
"@mdx-js/react": "1.6.22",
"@sapphire/docusaurus-plugin-npm2yarn2pnpm": "1.1.3",
"classnames": "^2.3.1",
"mdx-mermaid": "^1.2.2",
"mermaid": "^9.0.1",

View File

@@ -181,7 +181,7 @@ If you think your custom provider might be useful to others, we encourage you to
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)<br />
1. Add your config: [`src/providers/{provider}.js`](https://github.com/nextauthjs/next-auth/tree/main/packages/next-auth/src/providers)<br />
• make sure you use a named default export, like this: `export default function YourProvider`
2. Add provider documentation: [`www/docs/providers/{provider}.md`](https://github.com/nextauthjs/next-auth/tree/ead715219a5d7a6e882a6ba27fa56b03954d062d/www/docs/providers)
3. Add it to our [provider types](https://github.com/nextauthjs/next-auth/blob/ead715219a5d7a6e882a6ba27fa56b03954d062d/types/providers.d.ts) (for TS projects)<br />

View File

@@ -0,0 +1,53 @@
---
id: duende-identityserver6
title: DuendeIdentityServer6
---
## Documentation
https://docs.duendesoftware.com/identityserver/v6
## Options
The **DuendeIdentityServer6 Provider** comes with a set of default options:
- [DuendeIdentityServer6 Provider options](https://github.com/nextauthjs/next-auth/tree/main/packages/next-auth/src/providers/duende-identity-server6.ts)
You can override any of the options to suit your own use case.
## Example
```js
import DuendeIDS6Provider from "next-auth/providers/duende-identity-server6"
...
providers: [
DuendeIDS6Provider({
clientId: process.env.DUENDE_IDS6_ID,
clientSecret: process.env.DUENDE_IDS6_SECRET,
issuer: process.env.DUENDE_IDS6_ISSUER,
})
]
...
```
## Demo IdentityServer
The configuration below is for the demo server at https://demo.duendesoftware.com/
If you want to try it out, you can copy and paste the configuration below.
You can sign in to the demo service with either <b>bob/bob</b> or <b>alice/alice</b>.
```js
import DuendeIDS6Provider from "next-auth/providers/duende-identity-server6"
...
providers: [
DuendeIDS6Provider({
clientId: "interactive.confidential",
clientSecret: "secret",
issuer: "https://demo.duendesoftware.com",
})
]
...
```

View File

@@ -15,7 +15,7 @@ https://github.com/settings/apps
The **Github Provider** comes with a set of default options:
- [Github Provider options](https://github.com/nextauthjs/next-auth/blob/main/src/providers/github.js)
- [Github Provider options](https://github.com/nextauthjs/next-auth/blob/main/src/providers/github.ts)
You can override any of the options to suit your own use case.

View File

@@ -54,6 +54,6 @@ providers: [
clientSecret: "secret",
protection: "pkce"
})
}
]
...
```

View File

@@ -11,16 +11,16 @@
"test": "turbo run test --concurrency=1 --filter=!@next-auth/pouchdb-adapter --filter=!next-auth-* --filter=[HEAD^1]",
"setup": "turbo run setup",
"dev": "pnpm dev:app",
"email": "cd apps/dev && pnpm email",
"dev:app": "turbo run dev --parallel --no-deps --no-cache --filter=next-auth-app",
"dev:docs": "turbo run dev --parallel --no-deps --no-cache --filter=next-auth-docs",
"version:pr": "node ./config/version-pr",
"release": "ts-node scripts/release"
"release": "release"
},
"devDependencies": {
"@actions/core": "^1.6.0",
"@commitlint/parse": "16.0.0",
"@balazsorban/monorepo-release": "0.0.4",
"@types/node": "^17.0.25",
"@types/semver": "7.3.9",
"@typescript-eslint/eslint-plugin": "^5.10.2",
"@typescript-eslint/parser": "^4.33.0",
"eslint": "^7.32.0",
@@ -30,14 +30,10 @@
"eslint-plugin-jest": "^25.3.0",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^6.0.0",
"git-log-parser": "1.2.0",
"husky": "^7.0.4",
"prettier": "2.4.1",
"pretty-quick": "^3.1.2",
"semver": "7.3.5",
"stream-to-array": "2.3.0",
"ts-node": "10.5.0",
"turbo": "^1.2.5",
"turbo": "1.3.1",
"typescript": "^4.5.2"
},
"engines": {
@@ -46,7 +42,15 @@
},
"prettier": {
"semi": false,
"singleQuote": false
"singleQuote": false,
"overrides": [
{
"files": "apps/dev/pages/api/auth/[...nextauth].ts",
"options": {
"printWidth": 150
}
}
]
},
"eslintConfig": {
"parser": "@typescript-eslint/parser",
@@ -97,7 +101,7 @@
"**/tests",
"**/__tests__"
],
"packageManager": "pnpm@6.32.8",
"packageManager": "pnpm@7.5.1",
"funding": [
{
"type": "github",

View File

@@ -1,6 +1,6 @@
{
"name": "@next-auth/dgraph-adapter",
"version": "1.0.3",
"version": "1.0.4",
"description": "Dgraph adapter for next-auth.",
"homepage": "https://next-auth.js.org",
"repository": "https://github.com/nextauthjs/next-auth",
@@ -31,7 +31,7 @@
},
"peerDependencies": {
"jsonwebtoken": "^8.5.1",
"next-auth": "workspace:*"
"next-auth": "^4"
},
"devDependencies": {
"@next-auth/adapter-test": "workspace:^0.0.0",
@@ -50,4 +50,4 @@
"jest": {
"preset": "@next-auth/adapter-test/jest"
}
}
}

View File

@@ -1,7 +1,7 @@
{
"name": "@next-auth/dynamodb-adapter",
"repository": "https://github.com/nextauthjs/next-auth",
"version": "1.0.3",
"version": "1.0.4",
"description": "AWS DynamoDB adapter for next-auth.",
"keywords": [
"next-auth",
@@ -32,7 +32,7 @@
"license": "ISC",
"peerDependencies": {
"@aws-sdk/lib-dynamodb": "^3.36.1",
"next-auth": "workspace:*"
"next-auth": "^4"
},
"devDependencies": {
"@aws-sdk/client-dynamodb": "^3.36.1",
@@ -43,4 +43,4 @@
"jest": "^27.4.3",
"next-auth": "workspace:*"
}
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@next-auth/fauna-adapter",
"version": "1.0.3",
"version": "1.0.4",
"description": "Fauna Adapter for NextAuth",
"homepage": "https://next-auth.js.org",
"repository": "https://github.com/nextauthjs/next-auth",
@@ -41,7 +41,7 @@
},
"peerDependencies": {
"faunadb": "^4.3.0",
"next-auth": "workspace:*"
"next-auth": "^4"
},
"devDependencies": {
"@fauna-labs/fauna-schema-migrate": "^2.1.3",
@@ -54,4 +54,4 @@
"jest": {
"preset": "@next-auth/adapter-test/jest"
}
}
}

View File

@@ -32,14 +32,13 @@ npm install next-auth @next-auth/firebase-adapter
```js
import NextAuth from "next-auth"
import Providers from "next-auth/providers"
import { FirebaseAdapter } from "@next-auth/firebase-adapter"
import { FirestoreAdapter } from "@next-auth/firebase-adapter"
import firebase from "firebase/app"
import "firebase/firestore"
import { initializeApp } from "firebase/app";
import { getFirestore } from "firebase/firestore"
const firestore = (
firebase.apps[0] ?? firebase.initializeApp(/* your config */)
).firestore()
const app = initializeApp({ projectId: "next-auth-test" });
const firestore = getFirestore(app);
// For more information on each option (and a full list of options) go to
// https://next-auth.js.org/configuration/options
@@ -51,7 +50,7 @@ export default NextAuth({
clientSecret: process.env.GOOGLE_SECRET,
}),
],
adapter: FirebaseAdapter(firestore),
adapter: FirestoreAdapter(firestore),
...
})
```

View File

@@ -1 +1 @@
module.exports = require("@next-auth/adapter-test/jest.config")
module.exports = require("@next-auth/adapter-test/jest/jest-preset")

View File

@@ -1,6 +1,6 @@
{
"name": "@next-auth/firebase-adapter",
"version": "0.1.3",
"version": "1.0.1",
"description": "Firebase adapter for next-auth.",
"homepage": "https://next-auth.js.org",
"repository": "https://github.com/nextauthjs/next-auth",
@@ -28,19 +28,19 @@
"access": "public"
},
"scripts": {
"build:wip": "tsc",
"test:wip": "FIRESTORE_EMULATOR_HOST=localhost:8080 firebase emulators:exec --only firestore --project next-auth-test jest"
"build": "tsc",
"test": "FIRESTORE_EMULATOR_HOST=localhost:8080 firebase emulators:exec --only firestore --project next-auth-test jest"
},
"peerDependencies": {
"firebase": "^8.6.2",
"next-auth": "workspace:*"
"firebase": "^9.7.0",
"next-auth": "^4"
},
"devDependencies": {
"@next-auth/adapter-test": "workspace:^0.0.0",
"@next-auth/tsconfig": "workspace:^0.0.0",
"firebase": "^8.6.2",
"firebase-tools": "^9.11.0",
"firebase": "^9.7.0",
"firebase-tools": "^10.7.2",
"jest": "^27.4.3",
"next-auth": "workspace:*"
}
}
}

View File

@@ -0,0 +1,58 @@
import { Timestamp } from "firebase/firestore"
import type {
FirestoreDataConverter,
QueryDocumentSnapshot,
WithFieldValue,
} from "firebase/firestore"
const isTimestamp = (value: unknown): value is Timestamp =>
typeof value === "object" && value !== null && value instanceof Timestamp
interface GetConverterOptions {
excludeId?: boolean
}
export const getConverter = <Document extends Record<string, unknown>>(
options?: GetConverterOptions
): FirestoreDataConverter<Document> => ({
// `PartialWithFieldValue` implicitly types `object` as `any`, so we want to explicitly type it
toFirestore(object: WithFieldValue<Document>) {
const document: Record<string, unknown> = {}
Object.keys(object).forEach((key) => {
if (object[key] !== undefined) {
document[key] = object[key]
}
})
return document
},
// We need to explicitly type `snapshot` since it uses `DocumentData` for generic type
fromFirestore(snapshot: QueryDocumentSnapshot<Document>) {
if (!snapshot.exists()) {
return snapshot
}
let document: Document = snapshot.data()
if (!options?.excludeId) {
document = {
...document,
id: snapshot.id,
}
}
for (const key in document) {
const value = document[key]
if (isTimestamp(value)) {
document = {
...document,
[key]: value.toDate(),
}
}
}
return document
},
})

View File

@@ -1,283 +1,289 @@
import type firebase from "firebase"
import { createHash, randomBytes } from "crypto"
import { Adapter } from "next-auth/adapters"
import { initializeApp } from "firebase/app"
import type { FirebaseOptions } from "firebase/app"
import {
querySnapshotToObject,
docSnapshotToObject,
stripUndefined,
} from "./utils"
import { Profile, Session, User } from "next-auth"
addDoc,
collection,
deleteDoc,
doc,
getDoc,
getDocs,
getFirestore,
limit,
query,
runTransaction,
setDoc,
where,
connectFirestoreEmulator,
} from "firebase/firestore"
import type { Account } from "next-auth"
import type {
Adapter,
AdapterSession,
AdapterUser,
VerificationToken,
} from "next-auth/adapters"
interface FirebaseVerificationRequest {
id: string
identifier: string
token: string
expires: Date
import { getConverter } from "./converter"
type IndexableObject = Record<string, unknown>
export interface FirestoreAdapterOptions {
emulator?: {
host?: string
port?: number
}
}
export type FirebaseSession = Session & {
id: string
expires: Date
}
export function FirestoreAdapter({
emulator,
...firebaseOptions
}: FirebaseOptions & FirestoreAdapterOptions): Adapter {
const firebaseApp = initializeApp(firebaseOptions)
const db = getFirestore(firebaseApp)
if (emulator) {
connectFirestoreEmulator(
db,
emulator?.host ?? "localhost",
emulator?.port ?? 3001
)
}
const Users = collection(db, "users").withConverter(
getConverter<AdapterUser>()
)
const Sessions = collection(db, "sessions").withConverter(
getConverter<AdapterSession & IndexableObject>()
)
const Accounts = collection(db, "accounts").withConverter(
getConverter<Account>()
)
const VerificationTokens = collection(db, "verificationTokens").withConverter(
getConverter<VerificationToken & IndexableObject>({ excludeId: true })
)
// @ts-expect-error
export const FirebaseAdapter: Adapter<
firebase.firestore.Firestore,
never,
User & { id: string },
Profile,
FirebaseSession
> = (client) => {
return {
async getAdapter({ session, secret, ...appOptions }) {
const sessionMaxAge = session.maxAge * 1000 // default is 30 days
const sessionUpdateAge = session.updateAge * 1000 // default is 1 day
/**
* @todo Move this to core package
* @todo Use bcrypt or a more secure method
*/
const hashToken = (token: string) =>
createHash("sha256").update(`${token}${secret}`).digest("hex")
async createUser(newUser) {
const userRef = await addDoc(Users, newUser)
const userSnapshot = await getDoc(userRef)
return {
displayName: "FIREBASE",
async createUser(profile) {
const userRef = await client.collection("users").add(
stripUndefined({
name: profile.name,
email: profile.email,
image: profile.image,
emailVerified: profile.emailVerified ?? null,
})
)
const snapshot = await userRef.get()
const user = docSnapshotToObject(snapshot)
return user
},
async getUser(id) {
const snapshot = await client.collection("users").doc(id).get()
const user = docSnapshotToObject(snapshot)
return user
},
async getUserByEmail(email) {
if (!email) return null
const snapshot = await client
.collection("users")
.where("email", "==", email)
.limit(1)
.get()
const user = querySnapshotToObject(snapshot)
return user
},
async getUserByProviderAccountId(providerId, providerAccountId) {
const accountSnapshot = await client
.collection("accounts")
.where("providerId", "==", providerId)
.where("providerAccountId", "==", providerAccountId)
.limit(1)
.get()
if (accountSnapshot.empty) return null
const userId = accountSnapshot.docs[0].data().userId
const userSnapshot = await client
.collection("users")
.doc(userId)
.get()
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
return { ...userSnapshot.data(), id: userSnapshot.id } as any
},
async updateUser(user) {
await client
.collection("users")
.doc(user.id)
.update(stripUndefined(user))
return user
},
async deleteUser(userId) {
await client.collection("users").doc(userId).delete()
},
async linkAccount(
userId,
providerId,
providerType,
providerAccountId,
refreshToken,
accessToken,
accessTokenExpires
) {
const accountRef = await client.collection("accounts").add(
stripUndefined({
userId,
providerId,
providerType,
providerAccountId,
refreshToken,
accessToken,
accessTokenExpires,
})
)
const accountSnapshot = await accountRef.get()
const account = docSnapshotToObject(accountSnapshot)
return account
},
async unlinkAccount(userId, providerId, providerAccountId) {
const snapshot = await client
.collection("accounts")
.where("userId", "==", userId)
.where("providerId", "==", providerId)
.where("providerAccountId", "==", providerAccountId)
.limit(1)
.get()
const accountId = snapshot.docs[0].id
await client.collection("accounts").doc(accountId).delete()
},
async createSession(user) {
const sessionRef = await client.collection("sessions").add({
userId: user.id,
expires: new Date(Date.now() + sessionMaxAge),
sessionToken: randomBytes(32).toString("hex"),
accessToken: randomBytes(32).toString("hex"),
})
const snapshot = await sessionRef.get()
const session = docSnapshotToObject(snapshot)
return session
},
async getSession(sessionToken) {
const snapshot = await client
.collection("sessions")
.where("sessionToken", "==", sessionToken)
.limit(1)
.get()
const session = querySnapshotToObject<FirebaseSession>(snapshot)
if (!session) return null
// if the session has expired
if (session.expires < new Date()) {
// delete the session
await client.collection("sessions").doc(session.id).delete()
return null
}
// return already existing session
return session
},
async updateSession(session, force) {
if (
!force &&
Number(session.expires) - sessionMaxAge + sessionUpdateAge >
Date.now()
) {
return null
}
// Update the item in the database
await client
.collection("sessions")
.doc(session.id)
.update({
expires: new Date(Date.now() + sessionMaxAge),
})
return session
},
async deleteSession(sessionToken) {
const snapshot = await client
.collection("sessions")
.where("sessionToken", "==", sessionToken)
.limit(1)
.get()
const session = querySnapshotToObject<FirebaseSession>(snapshot)
if (!session) return
await client.collection("sessions").doc(session.id).delete()
},
async createVerificationRequest(identifier, url, token, _, provider) {
const verificationRequestRef = await client
.collection("verificationRequests")
.add({
identifier,
token: hashToken(token),
expires: new Date(Date.now() + provider.maxAge * 1000),
})
// With the verificationCallback on a provider, you can send an email, or queue
// an email to be sent, or perform some other action (e.g. send a text message)
await provider.sendVerificationRequest({
identifier,
url,
token,
baseUrl: appOptions.baseUrl,
provider,
})
const snapshot = await verificationRequestRef.get()
return docSnapshotToObject<FirebaseVerificationRequest>(snapshot)
},
async getVerificationRequest(identifier, token) {
const snapshot = await client
.collection("verificationRequests")
.where("token", "==", hashToken(token))
.where("identifier", "==", identifier)
.limit(1)
.get()
const verificationRequest =
querySnapshotToObject<FirebaseVerificationRequest>(snapshot)
if (!verificationRequest) return null
if (verificationRequest.expires < new Date()) {
// Delete verification entry so it cannot be used again
await client
.collection("verificationRequests")
.doc(verificationRequest.id)
.delete()
return null
}
return verificationRequest
},
async deleteVerificationRequest(identifier, token) {
const snapshot = await client
.collection("verificationRequests")
.where("token", "==", hashToken(token))
.where("identifier", "==", identifier)
.limit(1)
.get()
const verificationRequest =
querySnapshotToObject<FirebaseVerificationRequest>(snapshot)
if (!verificationRequest) return null
await client
.collection("verificationRequests")
.doc(verificationRequest.id)
.delete()
},
if (userSnapshot.exists() && Users.converter) {
return Users.converter.fromFirestore(userSnapshot)
}
throw new Error("[createUser] Failed to create user")
},
async getUser(id) {
const userSnapshot = await getDoc(doc(Users, id))
if (userSnapshot.exists() && Users.converter) {
return Users.converter.fromFirestore(userSnapshot)
}
return null
},
async getUserByEmail(email) {
const userQuery = query(Users, where("email", "==", email), limit(1))
const userSnapshots = await getDocs(userQuery)
const userSnpashot = userSnapshots.docs[0]
if (userSnpashot?.exists() && Users.converter) {
return Users.converter.fromFirestore(userSnpashot)
}
return null
},
async getUserByAccount({ provider, providerAccountId }) {
const accountQuery = query(
Accounts,
where("provider", "==", provider),
where("providerAccountId", "==", providerAccountId),
limit(1)
)
const accountSnapshots = await getDocs(accountQuery)
const accountSnapshot = accountSnapshots.docs[0]
if (accountSnapshot?.exists()) {
const { userId } = accountSnapshot.data()
const userDoc = await getDoc(doc(Users, userId))
if (userDoc.exists() && Users.converter) {
return Users.converter.fromFirestore(userDoc)
}
}
return null
},
async updateUser(partialUser) {
const userRef = doc(Users, partialUser.id)
await setDoc(userRef, partialUser, { merge: true })
const userSnapshot = await getDoc(userRef)
if (userSnapshot.exists() && Users.converter) {
return Users.converter.fromFirestore(userSnapshot)
}
throw new Error("[updateUser] Failed to update user")
},
async deleteUser(userId) {
const userRef = doc(Users, userId)
const accountsQuery = query(Accounts, where("userId", "==", userId))
const sessionsQuery = query(Sessions, where("userId", "==", userId))
// TODO: May be better to use events instead of transactions?
await runTransaction(db, async (transaction) => {
const accounts = await getDocs(accountsQuery)
const sessions = await getDocs(sessionsQuery)
transaction.delete(userRef)
accounts.forEach((account) => transaction.delete(account.ref))
sessions.forEach((session) => transaction.delete(session.ref))
})
},
async linkAccount(account) {
const accountRef = await addDoc(Accounts, account)
const accountSnapshot = await getDoc(accountRef)
if (accountSnapshot.exists() && Accounts.converter) {
return Accounts.converter.fromFirestore(accountSnapshot)
}
},
async unlinkAccount({ provider, providerAccountId }) {
const accountQuery = query(
Accounts,
where("provider", "==", provider),
where("providerAccountId", "==", providerAccountId),
limit(1)
)
const accountSnapshots = await getDocs(accountQuery)
const accountSnapshot = accountSnapshots.docs[0]
if (accountSnapshot?.exists()) {
await deleteDoc(accountSnapshot.ref)
}
},
async createSession(session) {
const sessionRef = await addDoc(Sessions, session)
const sessionSnapshot = await getDoc(sessionRef)
if (sessionSnapshot.exists() && Sessions.converter) {
return Sessions.converter.fromFirestore(sessionSnapshot)
}
throw new Error("[createSession] Failed to create session")
},
async getSessionAndUser(sessionToken) {
const sessionQuery = query(
Sessions,
where("sessionToken", "==", sessionToken),
limit(1)
)
const sessionSnapshots = await getDocs(sessionQuery)
const sessionSnapshot = sessionSnapshots.docs[0]
if (sessionSnapshot?.exists() && Sessions.converter) {
const session = Sessions.converter.fromFirestore(sessionSnapshot)
const userDoc = await getDoc(doc(Users, session.userId))
if (userDoc.exists() && Users.converter) {
const user = Users.converter.fromFirestore(userDoc)
return { session, user }
}
}
return null
},
async updateSession(partialSession) {
const sessionQuery = query(
Sessions,
where("sessionToken", "==", partialSession.sessionToken),
limit(1)
)
const sessionSnapshots = await getDocs(sessionQuery)
const sessionSnapshot = sessionSnapshots.docs[0]
if (sessionSnapshot?.exists()) {
await setDoc(sessionSnapshot.ref, partialSession, { merge: true })
const sessionDoc = await getDoc(sessionSnapshot.ref)
if (sessionDoc?.exists() && Sessions.converter) {
const session = Sessions.converter.fromFirestore(sessionDoc)
return session
}
}
return null
},
async deleteSession(sessionToken) {
const sessionQuery = query(
Sessions,
where("sessionToken", "==", sessionToken),
limit(1)
)
const sessionSnapshots = await getDocs(sessionQuery)
const sessionSnapshot = sessionSnapshots.docs[0]
if (sessionSnapshot?.exists()) {
await deleteDoc(sessionSnapshot.ref)
}
},
async createVerificationToken(verificationToken) {
const verificationTokenRef = await addDoc(
VerificationTokens,
verificationToken
)
const verificationTokenSnapshot = await getDoc(verificationTokenRef)
if (verificationTokenSnapshot.exists() && VerificationTokens.converter) {
const {
id,
...verificationToken
} = VerificationTokens.converter.fromFirestore(
verificationTokenSnapshot
)
return verificationToken
}
},
async useVerificationToken({ identifier, token }) {
const verificationTokensQuery = query(
VerificationTokens,
where("identifier", "==", identifier),
where("token", "==", token),
limit(1)
)
const verificationTokenSnapshots = await getDocs(verificationTokensQuery)
const verificationTokenSnapshot = verificationTokenSnapshots.docs[0]
if (verificationTokenSnapshot?.exists() && VerificationTokens.converter) {
await deleteDoc(verificationTokenSnapshot.ref)
const {
id,
...verificationToken
} = VerificationTokens.converter.fromFirestore(
verificationTokenSnapshot
)
return verificationToken
}
return null
},
}
}

View File

@@ -1,40 +0,0 @@
import type firebase from "firebase"
/**
* Takes in a snapshot and returns all of its `data()`,
* as well as `id` and `createdAt` and `updatedAt` `Date`
*/
export function docSnapshotToObject<T>(
snapshot: firebase.firestore.DocumentSnapshot<firebase.firestore.DocumentData>
): T | null {
if (!snapshot.exists) {
return null
}
const data: any = snapshot.data()
if (data.expires) {
data.expires = data.expires.toDate()
}
return { id: snapshot.id, ...data }
}
export function querySnapshotToObject<T>(
snapshot: firebase.firestore.QuerySnapshot<firebase.firestore.DocumentData>
): T | null {
if (snapshot.empty) {
return null
}
const doc = snapshot.docs[0]
const data: any = doc.data()
if (data.expires) {
data.expires = data.expires.toDate()
}
return { id: doc.id, ...data }
}
/** Firebase does not like `undefined` values */
export function stripUndefined(obj: any) {
return Object.fromEntries(
Object.entries(obj).filter(([, value]) => typeof value !== "undefined")
)
}

View File

@@ -1,67 +1,77 @@
import { runBasicTests } from "@next-auth/adapter-test"
import { FirebaseAdapter } from "../src"
import { docSnapshotToObject, querySnapshotToObject } from "../src/utils"
import { FirestoreAdapter } from "../src"
import firebase from "firebase/app"
import "firebase/firestore"
import { getFirestore, connectFirestoreEmulator, terminate, collection, query, where, limit, getDocs, getDoc, doc } from "firebase/firestore"
import { initializeApp } from "firebase/app";
import { getConverter } from "../src/converter";
import type { AdapterSession, AdapterUser, VerificationToken } from "next-auth/adapters";
import type { Account } from "next-auth";
const firestore = (
firebase.apps[0] ?? firebase.initializeApp({ projectId: "next-auth-test" })
).firestore()
firestore.useEmulator("localhost", 8080)
const app = initializeApp({ projectId: "next-auth-test" });
const firestore = getFirestore(app);
connectFirestoreEmulator(firestore, 'localhost', 8080);
type IndexableObject = Record<string, unknown>;
const Users = collection(firestore, 'users').withConverter(getConverter<AdapterUser>());
const Sessions = collection(firestore, 'sessions').withConverter(getConverter<AdapterSession & IndexableObject>());
const Accounts = collection(firestore, 'accounts').withConverter(getConverter<Account>());
const VerificationTokens = collection(firestore, 'verificationTokens').withConverter(getConverter<VerificationToken & IndexableObject>({ excludeId: true }));
runBasicTests({
adapter: FirebaseAdapter(firestore),
adapter: FirestoreAdapter({ projectId: "next-auth-test" }),
db: {
async disconnect() {
await firestore.terminate()
await terminate(firestore);
},
async session(sessionToken) {
const snapshot = await firestore
.collection("sessions")
.where("sessionToken", "==", sessionToken)
.limit(1)
.get()
return querySnapshotToObject(snapshot)
},
async expireSession(sessionToken, expires) {
const snapshot = await firestore
.collection("sessions")
.where("sessionToken", "==", sessionToken)
.limit(1)
.get()
const snapshotQuery = query(Sessions, where("sessionToken", "==", sessionToken), limit(1));
const snapshots = await getDocs(snapshotQuery);
const snapshot = snapshots.docs[0];
if (snapshot.empty) {
console.error(sessionToken, expires)
throw new Error("Could not expire session")
if (snapshot?.exists() && Sessions.converter) {
const session = Sessions.converter.fromFirestore(snapshot);
return session;
}
return await firestore
.collection("sessions")
.doc(snapshot.docs[0].id)
.update({ expires })
return null;
},
async user(id) {
const snapshot = await firestore.collection("users").doc(id).get()
return docSnapshotToObject(snapshot)
const snapshot = await getDoc(doc(Users, id));
if (snapshot?.exists() && Users.converter) {
const user = Users.converter.fromFirestore(snapshot);
return user;
}
return null;
},
async account(providerId, providerAccountId) {
const snapshot = await firestore
.collection("accounts")
.where("providerId", "==", providerId)
.where("providerAccountId", "==", providerAccountId)
.limit(1)
.get()
return querySnapshotToObject(snapshot)
async account({ provider, providerAccountId }) {
const snapshotQuery = query(Accounts, where("provider", "==", provider), where("providerAccountId", "==", providerAccountId), limit(1));
const snapshots = await getDocs(snapshotQuery);
const snapshot = snapshots.docs[0];
if (snapshot?.exists() && Accounts.converter) {
const account = Accounts.converter.fromFirestore(snapshot);
return account;
}
return null;
},
async verificationRequest(identifier, token) {
const snapshot = await firestore
.collection("verificationRequests")
.where("identifier", "==", identifier)
.where("token", "==", token)
.limit(1)
.get()
return querySnapshotToObject(snapshot)
async verificationToken({ identifier, token }) {
const snapshotQuery = query(VerificationTokens, where("identifier", "==", identifier), where("token", "==", token), limit(1));
const snapshots = await getDocs(snapshotQuery);
const snapshot = snapshots.docs[0];
if (snapshot?.exists() && VerificationTokens.converter) {
const verificationToken = VerificationTokens.converter.fromFirestore(snapshot);
return verificationToken;
}
},
},
})

View File

@@ -1,8 +1,10 @@
{
"extends": "@next-auth/tsconfig/base.json",
"extends": "@next-auth/tsconfig/adapters.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "dist"
"outDir": "dist",
"strict": true,
"noUncheckedIndexedAccess": true
},
"exclude": ["tests", "dist", "jest.config.js"]
}

View File

@@ -33,17 +33,21 @@
],
"peerDependencies": {
"@mikro-orm/core": "^5.0.2",
"next-auth": "workspace:*"
"next-auth": "^4"
},
"devDependencies": {
"@mikro-orm/core": "^5.0.2",
"@mikro-orm/sqlite": "^5.0.2",
"@next-auth/adapter-test": "workspace:^0.0.0",
"@next-auth/tsconfig": "workspace:^0.0.0",
"@types/uuid": "^8.3.3",
"jest": "^27.4.3",
"next-auth": "workspace:*"
},
"jest": {
"preset": "@next-auth/adapter-test/jest"
},
"dependencies": {
"uuid": "^8.3.2"
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@next-auth/mongodb-adapter",
"version": "1.0.3",
"version": "1.0.4",
"description": "mongoDB adapter for next-auth.",
"homepage": "https://next-auth.js.org",
"repository": "https://github.com/nextauthjs/next-auth",
@@ -32,7 +32,7 @@
],
"peerDependencies": {
"mongodb": "^4.1.1",
"next-auth": "workspace:*"
"next-auth": "^4"
},
"devDependencies": {
"@next-auth/adapter-test": "workspace:^0.0.0",
@@ -44,4 +44,4 @@
"jest": {
"preset": "@next-auth/adapter-test/jest"
}
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@next-auth/neo4j-adapter",
"version": "1.0.3",
"version": "1.0.4",
"description": "neo4j adapter for next-auth.",
"homepage": "https://next-auth.js.org",
"repository": "https://github.com/nextauthjs/next-auth",
@@ -34,7 +34,7 @@
],
"peerDependencies": {
"neo4j-driver": "^4.0.0",
"next-auth": "workspace:*"
"next-auth": "^4"
},
"devDependencies": {
"@next-auth/adapter-test": "workspace:^0.0.0",
@@ -50,4 +50,4 @@
"jest": {
"preset": "@next-auth/adapter-test/jest"
}
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@next-auth/pouchdb-adapter",
"version": "0.1.3",
"version": "0.1.4",
"description": "PouchDB adapter for next-auth.",
"homepage": "https://next-auth.js.org",
"repository": "https://github.com/nextauthjs/next-auth",
@@ -30,7 +30,7 @@
"dist"
],
"peerDependencies": {
"next-auth": "workspace:*",
"next-auth": "^3",
"pouchdb": "^7.2.2",
"pouchdb-find": "^7.2.2"
},
@@ -51,4 +51,4 @@
"jest": {
"preset": "@next-auth/adapter-test/jest"
}
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@next-auth/prisma-adapter",
"version": "1.0.3",
"version": "1.0.4",
"description": "Prisma adapter for next-auth.",
"homepage": "https://next-auth.js.org",
"repository": "https://github.com/nextauthjs/next-auth",
@@ -37,7 +37,7 @@
],
"peerDependencies": {
"@prisma/client": ">=2.26.0 || >=3",
"next-auth": "workspace:*"
"next-auth": "^4"
},
"devDependencies": {
"@next-auth/adapter-test": "workspace:^0.0.0",
@@ -51,4 +51,4 @@
"jest": {
"preset": "@next-auth/adapter-test/jest"
}
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@next-auth/sequelize-adapter",
"version": "1.0.4",
"version": "1.0.5",
"description": "Sequelize adapter for next-auth.",
"homepage": "https://next-auth.js.org",
"repository": "https://github.com/nextauthjs/next-auth",
@@ -29,7 +29,7 @@
"dist"
],
"peerDependencies": {
"next-auth": "workspace:*",
"next-auth": "^4",
"sequelize": "^6.6.5"
},
"devDependencies": {

View File

@@ -1,7 +1,13 @@
const swcConfig = {
jsc: {
parser: { syntax: "typescript", decorators: true },
transform: { legacyDecorator: true, decoratorMetadata: true },
},
}
module.exports = {
transform: {
".(ts|tsx)$": "@swc/jest",
".(js|jsx)$": "@swc/jest", // jest's default
".(ts|tsx)$": ["@swc/jest", swcConfig],
".(js|jsx)$": ["@swc/jest", swcConfig],
},
transformIgnorePatterns: ["[/\\\\]node_modules[/\\\\].+\\.(js|jsx)$"],
moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"],

View File

@@ -1,6 +1,6 @@
{
"name": "@next-auth/typeorm-legacy-adapter",
"version": "1.0.3",
"version": "2.0.0",
"description": "TypeORM (legacy) adapter for next-auth.",
"homepage": "https://next-auth.js.org",
"repository": "https://github.com/nextauthjs/next-auth",
@@ -37,24 +37,25 @@
"sqlite": "tests/sqlite/test.sh"
},
"devDependencies": {
"@next-auth/adapter-test": "workspace:^0.0.0",
"@next-auth/tsconfig": "workspace:^0.0.0",
"@next-auth/adapter-test": "workspace:*",
"@next-auth/tsconfig": "workspace:*",
"jest": "^27.4.3",
"mssql": "^7.2.1",
"mysql": "^2.18.1",
"next-auth": "workspace:*",
"pg": "^8.7.1",
"sqlite3": "^5.0.2",
"typeorm": "^0.2.37",
"typeorm-naming-strategies": "^2.0.0"
"pg": "^8.7.3",
"sqlite3": "^5.0.8",
"typeorm": "0.3.7",
"typeorm-naming-strategies": "^4.1.0",
"typescript": "^4.7.4"
},
"peerDependencies": {
"mssql": "^6.2.1 || 7",
"mysql": "^2.18.1",
"next-auth": "workspace:*",
"next-auth": "^4",
"pg": "^8.2.1",
"sqlite3": "^5.0.2",
"typeorm": "^0.2.31"
"typeorm": "0.3.7"
},
"peerDependenciesMeta": {
"mysql": {

View File

@@ -1,13 +1,8 @@
import { Adapter, AdapterSession, AdapterUser } from "next-auth/adapters"
import {
Connection,
ConnectionOptions,
EntityManager,
getConnectionManager,
} from "typeorm"
import { Account } from "next-auth"
import type { Adapter, AdapterSession, AdapterUser } from "next-auth/adapters"
import { DataSourceOptions, DataSource, EntityManager } from "typeorm"
import type { Account } from "next-auth"
import * as defaultEntities from "./entities"
import { parseConnectionConfig, updateConnectionEntities } from "./utils"
import { parseDataSourceConfig, updateConnectionEntities } from "./utils"
export const entities = defaultEntities
@@ -17,43 +12,40 @@ export interface TypeORMLegacyAdapterOptions {
entities?: Entities
}
let _connection: Connection
let _dataSource: DataSource | undefined
export async function getManager(options: {
connection: string | ConnectionOptions
dataSource: string | DataSourceOptions
entities: Entities
}): Promise<EntityManager> {
const { connection, entities } = options
const { dataSource, entities } = options
const config = {
...parseConnectionConfig(connection),
...parseDataSourceConfig(dataSource),
entities: Object.values(entities),
}
const connectionManager = getConnectionManager()
if (!_dataSource) _dataSource = new DataSource(config)
if (connectionManager.has(config.name ?? "default")) {
_connection = connectionManager.get(config.name ?? "default")
const manager = _dataSource?.manager
if (_connection.isConnected) return _connection.manager
if (process.env.NODE_ENV !== "production") {
await updateConnectionEntities(_connection, config.entities)
}
} else {
_connection = await connectionManager.create(config).connect()
if (!manager.connection.isInitialized) {
await manager.connection.initialize()
}
return _connection.manager
if (process.env.NODE_ENV !== "production") {
await updateConnectionEntities(_dataSource, config.entities)
}
return manager
}
export function TypeORMLegacyAdapter(
connection: string | ConnectionOptions,
dataSource: string | DataSourceOptions,
options?: TypeORMLegacyAdapterOptions
): Adapter {
const entities = options?.entities
const c = {
connection,
dataSource,
entities: {
UserEntity: entities?.UserEntity ?? defaultEntities.UserEntity,
SessionEntity: entities?.SessionEntity ?? defaultEntities.SessionEntity,
@@ -82,14 +74,14 @@ export function TypeORMLegacyAdapter(
// @ts-expect-error
async getUser(id) {
const m = await getManager(c)
const user = await m.findOne("UserEntity", { id })
const user = await m.findOne("UserEntity", { where: { id } })
if (!user) return null
return { ...user }
},
// @ts-expect-error
async getUserByEmail(email) {
const m = await getManager(c)
const user = await m.findOne("UserEntity", { email })
const user = await m.findOne("UserEntity", { where: { email } })
if (!user) return null
return { ...user }
},
@@ -97,8 +89,7 @@ export function TypeORMLegacyAdapter(
const m = await getManager(c)
const account = await m.findOne<Account & { user: AdapterUser }>(
"AccountEntity",
provider_providerAccountId,
{ relations: ["user"] }
{ where: provider_providerAccountId, relations: ["user"] }
)
if (!account) return null
return account.user ?? null
@@ -136,7 +127,7 @@ export function TypeORMLegacyAdapter(
const m = await getManager(c)
const sessionAndUser = await m.findOne<
AdapterSession & { user: AdapterUser }
>("SessionEntity", { sessionToken }, { relations: ["user"] })
>("SessionEntity", { where: { sessionToken }, relations: ["user"] })
if (!sessionAndUser) return null
const { user, ...session } = sessionAndUser
@@ -162,10 +153,9 @@ export function TypeORMLegacyAdapter(
// @ts-expect-error
async useVerificationToken(identifier_token) {
const m = await getManager(c)
const verificationToken = await m.findOne(
"VerificationTokenEntity",
identifier_token
)
const verificationToken = await m.findOne("VerificationTokenEntity", {
where: identifier_token,
})
if (!verificationToken) {
return null
}

View File

@@ -1,10 +1,10 @@
import { Connection, ConnectionOptions } from "typeorm"
import type { DataSource, DataSourceOptions } from "typeorm"
import * as defaultEntities from "./entities"
/** Ensure configOrString is normalized to an object. */
export function parseConnectionConfig(
configOrString: string | ConnectionOptions
): ConnectionOptions {
export function parseDataSourceConfig(
configOrString: string | DataSourceOptions
): DataSourceOptions {
if (typeof configOrString !== "string") {
return {
...configOrString,
@@ -89,22 +89,22 @@ function entitiesChanged(
}
export async function updateConnectionEntities(
connection: Connection,
dataSource: DataSource,
entities: any[]
) {
if (!entitiesChanged(connection.options.entities, entities)) return
if (!entitiesChanged(dataSource.entityMetadatas, entities)) return
// @ts-expect-error
connection.options.entities = entities
dataSource.entityMetadatas = entities
// @ts-expect-error
connection.buildMetadatas()
dataSource.buildMetadatas()
if (connection.options.synchronize !== false) {
if (dataSource.options.synchronize !== false) {
console.warn(
"[next-auth][warn][adapter_typeorm_updating_entities]",
"\nhttps://next-auth.js.org/warnings#adapter_typeorm_updating_entities"
)
await connection.synchronize()
await dataSource.synchronize()
}
}

View File

@@ -1,46 +1,49 @@
import { ConnectionManager, ConnectionOptions } from "typeorm"
import { TestOptions } from "@next-auth/adapter-test"
import { DataSource } from "typeorm"
import type { DataSourceOptions } from "typeorm"
import type { TestOptions } from "@next-auth/adapter-test"
import * as defaultEntities from "../src/entities"
import { parseConnectionConfig } from "../src/utils"
import { parseDataSourceConfig } from "../src/utils"
export { defaultEntities }
console.warn = jest.fn()
/** Set up Test Database */
export function db(
config: string | ConnectionOptions,
config: string | DataSourceOptions,
entities: typeof defaultEntities = defaultEntities
): TestOptions["db"] {
const connection = new ConnectionManager().create({
...parseConnectionConfig(config),
const connection = new DataSource({
...parseDataSourceConfig(config),
entities: Object.values(entities),
})
}).manager.connection
const m = connection.manager
return {
connect: async () => await connection.connect(),
disconnect: async () => await connection.close(),
connect: async () => await connection.initialize(),
disconnect: async () => await connection.destroy(),
async user(id) {
const user = await m.findOne(entities.UserEntity, id)
const user = await m.findOne(entities.UserEntity, { where: { id } })
return user ?? null
},
async account(provider_providerAccountId) {
const account = await m.findOne(
entities.AccountEntity,
provider_providerAccountId
)
const account = await m.findOne(entities.AccountEntity, {
where: provider_providerAccountId,
})
return account ?? null
},
async session(sessionToken) {
const session = await m.findOne(entities.SessionEntity, { sessionToken })
const session = await m.findOne(entities.SessionEntity, {
where: { sessionToken },
})
return session ?? null
},
async verificationToken(token_identifier) {
const verificationToken = await m.findOne(
entities.VerificationTokenEntity,
token_identifier
{ where: token_identifier }
)
if (!verificationToken) return null
// @ts-expect-error
const { id: _, ...rest } = verificationToken
return rest
},

View File

@@ -1,11 +1,9 @@
// eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error
// @ts-ignore
import { parseConnectionString } from "../src/lib/config"
import { parseDataSourceConfig } from "../src/utils"
const connectionString = "mysql://root:password@localhost:3306/next-auth"
test("could parse connection string", () => {
expect(parseConnectionString(connectionString)).toEqual(
expect(parseDataSourceConfig(connectionString)).toEqual(
expect.objectContaining({
type: "mysql",
host: "localhost",

View File

@@ -3,9 +3,9 @@ import { TypeORMLegacyAdapter } from "../../src"
import { db } from "../helpers"
import { SnakeNamingStrategy } from "typeorm-naming-strategies"
import type { ConnectionOptions } from "typeorm"
import type { DataSourceOptions } from "typeorm"
const sqliteConfig: ConnectionOptions = {
const sqliteConfig: DataSourceOptions = {
type: "sqlite" as const,
name: "next-auth-test-memory",
database: "./tests/sqlite/dev.db",

View File

@@ -1,6 +1,6 @@
{
"name": "@next-auth/upstash-redis-adapter",
"version": "3.0.0",
"version": "3.0.1",
"description": "Upstash adapter for next-auth. It uses Upstash's connectionless (HTTP based) Redis client.",
"homepage": "https://next-auth.js.org",
"repository": "https://github.com/nextauthjs/next-auth",
@@ -31,7 +31,7 @@
],
"peerDependencies": {
"@upstash/redis": "^1.0.1",
"next-auth": "workspace:*"
"next-auth": "^4"
},
"devDependencies": {
"@next-auth/adapter-test": "workspace:^0.0.0",
@@ -39,6 +39,7 @@
"@types/uuid": "^8.3.3",
"@upstash/redis": "^1.0.1",
"dotenv": "^10.0.0",
"isomorphic-fetch": "3.0.0",
"jest": "^27.4.3",
"next-auth": "workspace:*"
},
@@ -48,4 +49,4 @@
"jest": {
"preset": "@next-auth/adapter-test/jest"
}
}
}

View File

@@ -1,3 +1,4 @@
import "isomorphic-fetch"
import { Redis } from "@upstash/redis"
import { runBasicTests } from "@next-auth/adapter-test"
import { hydrateDates, UpstashRedisAdapter } from "../src"
@@ -7,41 +8,42 @@ if (!process.env.UPSTASH_REDIS_URL || !process.env.UPSTASH_REDIS_KEY) {
test("Skipping UpstashRedisAdapter tests, since required environment variables aren't available", () => {
expect(true).toBe(true)
})
} else {
const client = new Redis({
url: process.env.UPSTASH_REDIS_URL,
token: process.env.UPSTASH_REDIS_KEY,
})
runBasicTests({
adapter: UpstashRedisAdapter(client, { baseKeyPrefix: "testApp:" }),
db: {
async user(id: string) {
const data = await client.get<object>(`testApp:user:${id}`)
if (!data) return null
return hydrateDates(data)
},
async account({ provider, providerAccountId }) {
const data = await client.get<object>(
`testApp:user:account:${provider}:${providerAccountId}`
)
if (!data) return null
return hydrateDates(data)
},
async session(sessionToken) {
const data = await client.get<object>(
`testApp:user:session:${sessionToken}`
)
if (!data) return null
return hydrateDates(data)
},
async verificationToken(where) {
const data = await client.get<object>(
`testApp:user:token:${where.identifier}`
)
if (!data) return null
return hydrateDates(data)
},
},
})
process.exit(0)
}
const client = new Redis({
url: process.env.UPSTASH_REDIS_URL,
token: process.env.UPSTASH_REDIS_KEY,
})
runBasicTests({
adapter: UpstashRedisAdapter(client, { baseKeyPrefix: "testApp:" }),
db: {
async user(id: string) {
const data = await client.get<object>(`testApp:user:${id}`)
if (!data) return null
return hydrateDates(data)
},
async account({ provider, providerAccountId }) {
const data = await client.get<object>(
`testApp:user:account:${provider}:${providerAccountId}`
)
if (!data) return null
return hydrateDates(data)
},
async session(sessionToken) {
const data = await client.get<object>(
`testApp:user:session:${sessionToken}`
)
if (!data) return null
return hydrateDates(data)
},
async verificationToken(where) {
const data = await client.get<object>(
`testApp:user:token:${where.identifier}`
)
if (!data) return null
return hydrateDates(data)
},
},
})

View File

@@ -1,6 +1,6 @@
{
"name": "next-auth",
"version": "4.8.0",
"version": "4.10.2",
"description": "Authentication for Next.js",
"homepage": "https://next-auth.js.org",
"repository": "https://github.com/nextauthjs/next-auth.git",
@@ -8,7 +8,8 @@
"contributors": [
"Balázs Orbán <info@balazsorban.com>",
"Nico Domino <yo@ndo.dev>",
"Lluis Agusti <hi@llu.lu>"
"Lluis Agusti <hi@llu.lu>",
"Thang Huu Vu <thvu@hey.com>"
],
"main": "index.js",
"module": "index.js",
@@ -107,8 +108,8 @@
"@types/node": "^17.0.42",
"@types/nodemailer": "^6.4.4",
"@types/oauth": "^0.9.1",
"@types/react": "^18.0.2",
"@types/react-dom": "^18.0.5",
"@types/react": "^18.0.15",
"@types/react-dom": "^18.0.6",
"autoprefixer": "^10.4.7",
"babel-plugin-jsx-pragmatic": "^1.0.2",
"babel-preset-preact": "^2.0.0",
@@ -117,7 +118,7 @@
"jest-environment-jsdom": "^28.1.1",
"jest-watch-typeahead": "^1.1.0",
"msw": "^0.42.3",
"next": "12.1.7-canary.51",
"next": "12.2.0",
"postcss": "^8.4.14",
"postcss-cli": "^9.1.0",
"postcss-nested": "^5.0.6",
@@ -138,4 +139,4 @@
"**/tests",
"**/__tests__"
]
}
}

View File

@@ -79,7 +79,7 @@ test("when the fetch fails it'll throw a client fetch error", async () => {
await waitFor(() => {
expect(logger.error).toHaveBeenCalledTimes(1)
expect(logger.error).toBeCalledWith("CLIENT_FETCH_ERROR", {
path: "csrf",
url: "/api/auth/csrf",
error: new SyntaxError("Unexpected token s in JSON at position 0"),
})
})

View File

@@ -57,7 +57,7 @@ test("when failing to fetch the providers, it'll log the error", async () => {
await waitFor(() => {
expect(logger.error).toHaveBeenCalledTimes(1)
expect(logger.error).toBeCalledWith("CLIENT_FETCH_ERROR", {
path: "providers",
url: "/api/auth/providers",
error: new SyntaxError("Unexpected token s in JSON at position 0"),
})
})

View File

@@ -71,7 +71,7 @@ test("if there's an error fetching the session, it should log it", async () => {
await waitFor(() => {
expect(logger.error).toHaveBeenCalledTimes(1)
expect(logger.error).toBeCalledWith("CLIENT_FETCH_ERROR", {
path: "session",
url: "/api/auth/session",
error: new SyntaxError("Unexpected token S in JSON at position 0"),
})
})

View File

@@ -256,7 +256,7 @@ test("when it fails to fetch the providers, it redirected back to signin page",
expect(logger.error).toHaveBeenCalledTimes(1)
expect(logger.error).toBeCalledWith("CLIENT_FETCH_ERROR", {
error: "Error when retrieving providers",
path: "providers",
url: "/api/auth/providers",
})
})
})

View File

@@ -35,20 +35,17 @@ export async function fetchData<T = any>(
logger: LoggerInstance,
{ ctx, req = ctx?.req }: CtxOrReq = {}
): Promise<T | null> {
const url = `${apiBaseUrl(__NEXTAUTH)}/${path}`
try {
const options = req?.headers.cookie
? { headers: { cookie: req.headers.cookie } }
: {}
const res = await fetch(`${apiBaseUrl(__NEXTAUTH)}/${path}`, options)
const res = await fetch(url, options)
const data = await res.json()
if (!res.ok) throw data
return Object.keys(data).length > 0 ? data : null // Return null if data empty
} catch (error) {
logger.error("CLIENT_FETCH_ERROR", {
error: error as Error,
path,
...(req ? { header: req.headers } : {}),
})
logger.error("CLIENT_FETCH_ERROR", { error: error as Error, url })
return null
}
}

View File

@@ -90,8 +90,8 @@ export async function NextAuthHandler<
const assertionResult = assertConfig({ options: userOptions, req })
if (typeof assertionResult === "string") {
logger.warn(assertionResult)
if (Array.isArray(assertionResult)) {
assertionResult.forEach(logger.warn)
} else if (assertionResult instanceof Error) {
// Bail out early if there's an error in the user config
const { pages, theme } = userOptions

View File

@@ -3,7 +3,7 @@ import logger from "../utils/logger"
import parseUrl from "../utils/parse-url"
import { adapterErrorHandler, eventsErrorHandler } from "./errors"
import parseProviders from "./lib/providers"
import createSecret from "./lib/utils"
import { createSecret } from "./lib/utils"
import * as cookie from "./lib/cookie"
import * as jwt from "../jwt"
import { defaultCallbacks } from "./lib/default-callbacks"
@@ -62,6 +62,7 @@ export async function init({
colorScheme: "auto",
logo: "",
brandColor: "",
buttonText: "",
},
// Custom options override defaults
...userOptions,

View File

@@ -9,8 +9,9 @@ import {
import parseUrl from "../../utils/parse-url"
import { defaultCookies } from "./cookie"
import type { NextAuthHandlerParams, RequestInternal } from ".."
import type { RequestInternal } from ".."
import type { WarningCode } from "../../utils/logger"
import type { NextAuthOptions } from "../types"
type ConfigError =
| MissingAPIRoute
@@ -19,7 +20,7 @@ type ConfigError =
| MissingAuthorize
| MissingAdapter
let twitterWarned = false
let warned = false
function isValidHttpUrl(url: string, baseUrl: string) {
try {
@@ -37,13 +38,28 @@ function isValidHttpUrl(url: string, baseUrl: string) {
*
* REVIEW: Make some of these and corresponding docs less Next.js specific?
*/
export function assertConfig(
params: NextAuthHandlerParams & {
req: RequestInternal
}
): ConfigError | WarningCode | undefined {
export function assertConfig(params: {
options: NextAuthOptions
req: RequestInternal
}): ConfigError | WarningCode[] {
const { options, req } = params
const warnings: WarningCode[] = []
if (!warned) {
if (!req.host) warnings.push("NEXTAUTH_URL")
// TODO: Make this throw an error in next major. This will also get rid of `NODE_ENV`
if (!options.secret && process.env.NODE_ENV !== "production")
warnings.push("NO_SECRET")
if (options.debug) warnings.push("DEBUG_ENABLED")
}
if (!options.secret && process.env.NODE_ENV === "production") {
return new MissingSecret("Please define a `secret` in production.")
}
// req.query isn't defined when asserting `unstable_getServerSession` for example
if (!req.query?.nextauth && !req.action) {
return new MissingAPIRoute(
@@ -51,14 +67,6 @@ export function assertConfig(
)
}
if (!options.secret) {
if (process.env.NODE_ENV === "production") {
return new MissingSecret("Please define a `secret` in production.")
} else {
return "NO_SECRET"
}
}
const callbackUrlParam = req.query?.callbackUrl as string | undefined
const url = parseUrl(req.host)
@@ -69,9 +77,6 @@ export function assertConfig(
)
}
// This is below the callbackUrlParam check because it would obscure the error
if (!req.host) return "NEXTAUTH_URL"
const { callbackUrl: defaultCallbackUrl } = defaultCookies(
options.useSecureCookies ?? url.base.startsWith("https://")
)
@@ -119,8 +124,10 @@ export function assertConfig(
return new MissingAdapter("E-mail login requires an adapter.")
}
if (!twitterWarned && hasTwitterOAuth2) {
twitterWarned = true
return "TWITTER_OAUTH_2_BETA"
if (!warned) {
if (hasTwitterOAuth2) warnings.push("TWITTER_OAUTH_2_BETA")
warned = true
}
return warnings
}

View File

@@ -9,9 +9,8 @@ import type { InternalOptions } from "../../types"
export default async function email(
identifier: string,
options: InternalOptions<"email">
) {
const { url, adapter, provider, logger, callbackUrl } = options
): Promise<string> {
const { url, adapter, provider, callbackUrl, theme } = options
// Generate token
const token =
(await provider.generateVerificationToken?.()) ??
@@ -34,21 +33,18 @@ export default async function email(
const params = new URLSearchParams({ callbackUrl, token, email: identifier })
const _url = `${url}/callback/${provider.id}?${params}`
try {
// Send to user
await provider.sendVerificationRequest({
identifier,
token,
expires,
url: _url,
provider,
})
} catch (error) {
logger.error("SEND_VERIFICATION_EMAIL_ERROR", {
identifier,
url,
error: error as Error,
})
throw new Error("SEND_VERIFICATION_EMAIL_ERROR")
}
// Send to user
await provider.sendVerificationRequest({
identifier,
token,
expires,
url: _url,
provider,
theme,
})
return `${url}/verify-request?${new URLSearchParams({
provider: provider.id,
type: provider.type,
})}`
}

View File

@@ -14,66 +14,63 @@ import type { Cookie } from "../cookie"
*
* [OAuth 2](https://www.oauth.com/oauth2-servers/authorization/the-authorization-request/) | [OAuth 1](https://oauth.net/core/1.0a/#auth_step2)
*/
export default async function getAuthorizationUrl(params: {
export default async function getAuthorizationUrl({
options,
query,
}: {
options: InternalOptions<"oauth">
query: RequestInternal["query"]
}) {
const { options, query } = params
const { logger, provider } = options
try {
let params: any = {}
let params: any = {}
if (typeof provider.authorization === "string") {
const parsedUrl = new URL(provider.authorization)
const parsedParams = Object.fromEntries(parsedUrl.searchParams.entries())
params = { ...params, ...parsedParams }
} else {
params = { ...params, ...provider.authorization?.params }
}
params = { ...params, ...query }
// Handle OAuth v1.x
if (provider.version?.startsWith("1.")) {
const client = oAuth1Client(options)
const tokens = (await client.getOAuthRequestToken(params)) as any
const url = `${
// @ts-expect-error
provider.authorization?.url ?? provider.authorization
}?${new URLSearchParams({
oauth_token: tokens.oauth_token,
oauth_token_secret: tokens.oauth_token_secret,
...tokens.params,
})}`
logger.debug("GET_AUTHORIZATION_URL", { url })
return { redirect: url }
}
const client = await openidClient(options)
const authorizationParams: AuthorizationParameters = params
const cookies: Cookie[] = []
const state = await createState(options)
if (state) {
authorizationParams.state = state.value
cookies.push(state.cookie)
}
const pkce = await createPKCE(options)
if (pkce) {
authorizationParams.code_challenge = pkce.code_challenge
authorizationParams.code_challenge_method = pkce.code_challenge_method
cookies.push(pkce.cookie)
}
const url = client.authorizationUrl(authorizationParams)
logger.debug("GET_AUTHORIZATION_URL", { url, cookies })
return { redirect: url, cookies }
} catch (error) {
logger.error("GET_AUTHORIZATION_URL_ERROR", error as Error)
throw error
if (typeof provider.authorization === "string") {
const parsedUrl = new URL(provider.authorization)
const parsedParams = Object.fromEntries(parsedUrl.searchParams.entries())
params = { ...params, ...parsedParams }
} else {
params = { ...params, ...provider.authorization?.params }
}
params = { ...params, ...query }
// Handle OAuth v1.x
if (provider.version?.startsWith("1.")) {
const client = oAuth1Client(options)
const tokens = (await client.getOAuthRequestToken(params)) as any
const url = `${
// @ts-expect-error
provider.authorization?.url ?? provider.authorization
}?${new URLSearchParams({
oauth_token: tokens.oauth_token,
oauth_token_secret: tokens.oauth_token_secret,
...tokens.params,
})}`
logger.debug("GET_AUTHORIZATION_URL", { url, provider })
return { redirect: url }
}
const client = await openidClient(options)
const authorizationParams: AuthorizationParameters = params
const cookies: Cookie[] = []
const state = await createState(options)
if (state) {
authorizationParams.state = state.value
cookies.push(state.cookie)
}
const pkce = await createPKCE(options)
if (pkce) {
authorizationParams.code_challenge = pkce.code_challenge
authorizationParams.code_challenge_method = pkce.code_challenge_method
cookies.push(pkce.cookie)
}
const url = client.authorizationUrl(authorizationParams)
logger.debug("GET_AUTHORIZATION_URL", { url, cookies, provider })
return { redirect: url, cookies }
}

View File

@@ -28,9 +28,9 @@ export default async function oAuthCallback(params: {
logger.error("OAUTH_CALLBACK_HANDLER_ERROR", {
error,
error_description: query?.error_description,
body,
providerId: provider.id,
})
logger.debug("OAUTH_CALLBACK_HANDLER_ERROR", { body })
throw error
}
@@ -199,10 +199,7 @@ async function getProfile({
// all providers, so we return an empty object; the user should then be
// redirected back to the sign up page. We log the error to help developers
// who might be trying to debug this when configuring a new provider.
logger.error("OAUTH_PARSE_PROFILE_ERROR", {
error: error as Error,
OAuthProfile,
})
logger.error("OAUTH_PARSE_PROFILE_ERROR", error as Error)
return {
profile: null,
account: null,

View File

@@ -28,7 +28,7 @@ export function hashToken(token: string, options: InternalOptions<"email">) {
* If no secret option is specified then it creates one on the fly
* based on options passed here. If options contains unique data, such as
* OAuth provider secrets and database credentials it should be sufficent. If no secret provided in production, we throw an error. */
export default function createSecret(params: {
export function createSecret(params: {
userOptions: NextAuthOptions
url: InternalUrl
}) {
@@ -36,6 +36,7 @@ export default function createSecret(params: {
return (
userOptions.secret ??
// TODO: Remove falling back to default secret, and error in dev if one isn't provided
createHash("sha256")
.update(JSON.stringify({ ...url, ...userOptions }))
.digest("hex")

View File

@@ -26,7 +26,10 @@ export default async function signin(params: {
const response = await getAuthorizationUrl({ options, query })
return response
} catch (error) {
logger.error("SIGNIN_OAUTH_ERROR", { error: error as Error, provider })
logger.error("SIGNIN_OAUTH_ERROR", {
error: error as Error,
providerId: provider.id,
})
return { redirect: `${url}/error?error=OAuthSignin` }
}
} else if (provider.type === "email") {
@@ -37,19 +40,10 @@ export default async function signin(params: {
* it solves. We treat email addresses as all lower case. If anyone
* complains about this we can make strict RFC 2821 compliance an option.
*/
let email = body?.email?.toLowerCase()
const email = body?.email?.toLowerCase()
if (!email) return { redirect: `${url}/error?error=EmailSignin` }
email = email
.split(",")[0]
.trim()
.replaceAll("&", "&amp;")
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;")
.replaceAll('"', "&quot;")
.replaceAll("'", "&#x27;")
// Verified in `assertConfig`
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const { getUserByEmail } = adapter!
@@ -88,18 +82,15 @@ export default async function signin(params: {
}
try {
await emailSignin(email, options)
const redirect = await emailSignin(email, options)
return { redirect }
} catch (error) {
logger.error("SIGNIN_EMAIL_ERROR", error as Error)
logger.error("SIGNIN_EMAIL_ERROR", {
error: error as Error,
providerId: provider.id,
})
return { redirect: `${url}/error?error=EmailSignin` }
}
const params = new URLSearchParams({
provider: provider.id,
type: provider.type,
})
return { redirect: `${url}/verify-request?${params}` }
}
return { redirect: `${url}/signin` }
}

View File

@@ -214,9 +214,10 @@ export interface NextAuthOptions {
* [Pages](https://next-auth.js.org/configuration/pages)
*/
export interface Theme {
colorScheme: "auto" | "dark" | "light"
colorScheme?: "auto" | "dark" | "light"
logo?: string
brandColor?: string
buttonText?: string
}
/**

View File

@@ -83,20 +83,28 @@ function NextAuth(
export default NextAuth
let experimentalWarningShown = false
export async function unstable_getServerSession(
...args:
| [GetServerSidePropsContext['req'], GetServerSidePropsContext['res'], NextAuthOptions]
| [
GetServerSidePropsContext["req"],
GetServerSidePropsContext["res"],
NextAuthOptions
]
| [NextApiRequest, NextApiResponse, NextAuthOptions]
): Promise<Session | null> {
console.warn(
"[next-auth][warn][EXPERIMENTAL_API]",
"\n`unstable_getServerSession` is experimental and may be removed or changed in the future, as the name suggested.",
`\nhttps://next-auth.js.org/configuration/nextjs#unstable_getServerSession}`,
`\nhttps://next-auth.js.org/warnings#EXPERIMENTAL_API`
if (!experimentalWarningShown && process.env.NODE_ENV !== "production") {
console.warn(
"[next-auth][warn][EXPERIMENTAL_API]",
"\n`unstable_getServerSession` is experimental and may be removed or changed in the future, as the name suggested.",
`\nhttps://next-auth.js.org/configuration/nextjs#unstable_getServerSession}`,
`\nhttps://next-auth.js.org/warnings#EXPERIMENTAL_API`
)
experimentalWarningShown = true
}
const [req, res, options] = args
const [req, res, options] = args;
options.secret = options.secret ?? process.env.NEXTAUTH_SECRET
const session = await NextAuthHandler<Session | {}>({

View File

@@ -84,12 +84,22 @@ export interface NextAuthMiddlewareOptions {
*/
authorized?: AuthorizedCallback
}
/**
* The same `secret` used in the `NextAuth` configuration.
* Defaults to the `NEXTAUTH_SECRET` environment variable.
*/
secret?: string
}
// TODO: `NextMiddleware` should allow returning `void`
// Simplify when https://github.com/vercel/next.js/pull/38625 is merged.
type NextMiddlewareResult = ReturnType<NextMiddleware> | void // eslint-disable-line @typescript-eslint/no-invalid-void-type
async function handleMiddleware(
req: NextRequest,
options: NextAuthMiddlewareOptions | undefined,
onSuccess?: (token: JWT | null) => Promise<any>
onSuccess?: (token: JWT | null) => Promise<NextMiddlewareResult>
) {
const signInPage = options?.pages?.signIn ?? "/api/auth/signin"
const errorPage = options?.pages?.error ?? "/api/auth/error"
@@ -102,7 +112,8 @@ async function handleMiddleware(
return
}
if (!process.env.NEXTAUTH_SECRET) {
const secret = options?.secret ?? process.env.NEXTAUTH_SECRET
if (!secret) {
console.error(
`[next-auth][error][NO_SECRET]`,
`\nhttps://next-auth.js.org/errors#no_secret`
@@ -118,6 +129,7 @@ async function handleMiddleware(
req,
decode: options?.jwt?.decode,
cookieName: options?.cookies?.sessionToken?.name,
secret,
})
const isAuthorized =
@@ -135,12 +147,21 @@ async function handleMiddleware(
return NextResponse.redirect(signInUrl)
}
export interface NextRequestWithAuth extends NextRequest {
nextauth: { token: JWT | null }
}
export type NextMiddlewareWithAuth = (
request: NextRequestWithAuth,
event: NextFetchEvent
) => NextMiddlewareResult | Promise<NextMiddlewareResult>
export type WithAuthArgs =
| [NextRequest]
| [NextRequest, NextFetchEvent]
| [NextRequest, NextAuthMiddlewareOptions]
| [NextMiddleware]
| [NextMiddleware, NextAuthMiddlewareOptions]
| [NextRequestWithAuth]
| [NextRequestWithAuth, NextFetchEvent]
| [NextRequestWithAuth, NextAuthMiddlewareOptions]
| [NextMiddlewareWithAuth]
| [NextMiddlewareWithAuth, NextAuthMiddlewareOptions]
| [NextAuthMiddlewareOptions]
| []
@@ -168,9 +189,9 @@ export function withAuth(...args: WithAuthArgs) {
if (typeof args[0] === "function") {
const middleware = args[0]
const options = args[1] as NextAuthMiddlewareOptions | undefined
return async (...args: Parameters<NextMiddleware>) =>
return async (...args: Parameters<NextMiddlewareWithAuth>) =>
await handleMiddleware(args[0], options, async (token) => {
;(args[0] as any).nextauth = { token }
args[0].nextauth = { token }
return await middleware(...args)
})
}

View File

@@ -19,16 +19,19 @@ export interface AzureB2CProfile extends Record<string, any> {
export default function AzureADB2C<P extends AzureB2CProfile>(
options: OAuthUserConfig<P> & {
primaryUserFlow: string
tenantId: string
primaryUserFlow?: string
tenantId?: string
}
): OAuthConfig<P> {
const { tenantId, primaryUserFlow } = options
const issuer =
options.issuer ??
`https://${tenantId}.b2clogin.com/${tenantId}.onmicrosoft.com/${primaryUserFlow}/v2.0`
return {
id: "azure-ad-b2c",
name: "Azure Active Directory B2C",
type: "oauth",
wellKnown: `https://${tenantId}.b2clogin.com/${tenantId}.onmicrosoft.com/${primaryUserFlow}/v2.0/.well-known/openid-configuration`,
wellKnown: `${issuer}/.well-known/openid-configuration`,
idToken: true,
profile(profile) {
return {

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