mirror of
https://github.com/SrIzan10/next-auth.git
synced 2026-05-01 10:55:20 +00:00
Compare commits
1 Commits
@next-auth
...
next-auth@
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10bdea24b4 |
7
.github/workflows/release.yml
vendored
7
.github/workflows/release.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
|||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v2.2.1
|
uses: pnpm/action-setup@v2.2.1
|
||||||
with:
|
with:
|
||||||
version: 7.5.1
|
version: 6.32.8
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
@@ -55,7 +55,7 @@ jobs:
|
|||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v2.2.1
|
uses: pnpm/action-setup@v2.2.1
|
||||||
with:
|
with:
|
||||||
version: 7.5.1
|
version: 6.32.8
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
@@ -70,7 +70,6 @@ jobs:
|
|||||||
pnpm release
|
pnpm release
|
||||||
env:
|
env:
|
||||||
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||||
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
|
||||||
NPM_TOKEN_PKG: ${{ secrets.NPM_TOKEN_PKG }}
|
NPM_TOKEN_PKG: ${{ secrets.NPM_TOKEN_PKG }}
|
||||||
NPM_TOKEN_ORG: ${{ secrets.NPM_TOKEN_ORG }}
|
NPM_TOKEN_ORG: ${{ secrets.NPM_TOKEN_ORG }}
|
||||||
release-pr:
|
release-pr:
|
||||||
@@ -85,7 +84,7 @@ jobs:
|
|||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v2.2.1
|
uses: pnpm/action-setup@v2.2.1
|
||||||
with:
|
with:
|
||||||
version: 7.5.1
|
version: 6.32.8
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -44,7 +44,6 @@ packages/next-auth/middleware.js
|
|||||||
# Development app
|
# Development app
|
||||||
apps/dev/src/css
|
apps/dev/src/css
|
||||||
apps/dev/prisma/migrations
|
apps/dev/prisma/migrations
|
||||||
apps/dev/typeorm
|
|
||||||
|
|
||||||
# VS
|
# VS
|
||||||
/.vs/slnx.sqlite-journal
|
/.vs/slnx.sqlite-journal
|
||||||
|
|||||||
@@ -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.
|
- 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 should be raised for any change
|
||||||
- Pull Requests need approval of a [core contributor](https://next-auth.js.org/contributors#core-team) before merging
|
- 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 `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 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 encourage you to test your changes, and if you have the opportunity, please make those tests part of the Pull Request
|
- 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
|
- 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:
|
1. Install packages. Developing requires Node.js v16:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
pnpm install
|
yarn
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Populate `.env.local`:
|
3. Populate `.env.local`:
|
||||||
@@ -55,7 +55,7 @@ cp .env.local.example .env.local
|
|||||||
4. Start the developer application/server:
|
4. Start the developer application/server:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
pnpm dev
|
yarn dev:app
|
||||||
```
|
```
|
||||||
Your developer application will be available on `http://localhost:3000`
|
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
|
#### Hot reloading
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
> 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!)
|
> 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 `pnpm dev`, you start a Next.js developer server on `http://localho
|
|||||||
|
|
||||||
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:
|
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/packages/next-auth/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/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)
|
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!
|
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
|
#### Testing
|
||||||
|
|
||||||
Tests can be run with `pnpm test`.
|
Tests can be run with `yarn test`.
|
||||||
|
|
||||||
Automated tests are currently crude and limited in functionality, but improvements are in development.
|
Automated tests are currently crude and limited in functionality, but improvements are in development.
|
||||||
|
|
||||||
## For maintainers
|
## For maintainers
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
When accepting Pull Requests, make sure the following:
|
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.
|
- Rewrite the commit message to conform to the `Conventional Commits` style.
|
||||||
- Using `fix` releases a patch (x.x.1)
|
- Using `fix` releases a patch (x.x.1)
|
||||||
- Using `feat` releases a minor (x.1.x)
|
- Using `feat` releases a minor (x.1.x)
|
||||||
- Using `feat` when `BREAKING CHANGE` is present in the commit message releases a major (1.x.x)
|
- Using `feat` when `BREAKING CHANGE` is present in the commit messgae 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.)
|
- 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
|
### Skipping a 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.
|
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.
|
||||||
@@ -50,6 +50,3 @@ DATABASE_URL=
|
|||||||
BOXYHQSAML_ISSUER="https://jackson-demo.boxyhq.com"
|
BOXYHQSAML_ISSUER="https://jackson-demo.boxyhq.com"
|
||||||
BOXYHQSAML_ID="tenant=boxyhq.com&product=saml-demo.boxyhq.com"
|
BOXYHQSAML_ID="tenant=boxyhq.com&product=saml-demo.boxyhq.com"
|
||||||
BOXYHQSAML_SECRET="dummy"
|
BOXYHQSAML_SECRET="dummy"
|
||||||
|
|
||||||
WIKIMEDIA_ID=
|
|
||||||
WIKIMEDIA_SECRET=
|
|
||||||
@@ -16,25 +16,21 @@
|
|||||||
},
|
},
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@next-auth/fauna-adapter": "workspace:*",
|
"@next-auth/fauna-adapter": "^1",
|
||||||
"@next-auth/prisma-adapter": "workspace:*",
|
"@next-auth/prisma-adapter": "^1",
|
||||||
"@next-auth/typeorm-legacy-adapter": "workspace:*",
|
|
||||||
"@prisma/client": "^3",
|
"@prisma/client": "^3",
|
||||||
"faunadb": "^4",
|
"faunadb": "^4",
|
||||||
"next": "12.2.0",
|
"next": "12.1.7-canary.51",
|
||||||
"nodemailer": "^6",
|
"nodemailer": "^6",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-dom": "^18"
|
"react-dom": "^18"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/react": "^18.0.15",
|
"@types/react": "^18",
|
||||||
"@types/react-dom": "^18.0.6",
|
"@types/react-dom": "^18",
|
||||||
"concurrently": "^7",
|
"concurrently": "^7",
|
||||||
"cpx": "^1.5.0",
|
"cpx": "^1.5.0",
|
||||||
"fake-smtp-server": "^0.8.0",
|
"fake-smtp-server": "^0.8.0",
|
||||||
"pg": "^8.7.3",
|
"prisma": "^3"
|
||||||
"prisma": "^3",
|
|
||||||
"sqlite3": "^5.0.8",
|
|
||||||
"typeorm": "0.3.7"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,10 @@ import GitHubProvider from "next-auth/providers/github"
|
|||||||
import Auth0Provider from "next-auth/providers/auth0"
|
import Auth0Provider from "next-auth/providers/auth0"
|
||||||
import KeycloakProvider from "next-auth/providers/keycloak"
|
import KeycloakProvider from "next-auth/providers/keycloak"
|
||||||
import TwitterProvider, {
|
import TwitterProvider, {
|
||||||
// TwitterLegacy as TwitterLegacyProvider,
|
TwitterLegacy as TwitterLegacyProvider,
|
||||||
} from "next-auth/providers/twitter"
|
} from "next-auth/providers/twitter"
|
||||||
import CredentialsProvider from "next-auth/providers/credentials"
|
import CredentialsProvider from "next-auth/providers/credentials"
|
||||||
import IDS4Provider from "next-auth/providers/identity-server4"
|
import IDS4Provider from "next-auth/providers/identity-server4"
|
||||||
import DuendeIDS6Provider from "next-auth/providers/duende-identity-server6"
|
|
||||||
import Twitch from "next-auth/providers/twitch"
|
import Twitch from "next-auth/providers/twitch"
|
||||||
import GoogleProvider from "next-auth/providers/google"
|
import GoogleProvider from "next-auth/providers/google"
|
||||||
import FacebookProvider from "next-auth/providers/facebook"
|
import FacebookProvider from "next-auth/providers/facebook"
|
||||||
@@ -32,39 +31,20 @@ import PatreonProvider from "next-auth/providers/patreon"
|
|||||||
import TraktProvider from "next-auth/providers/trakt"
|
import TraktProvider from "next-auth/providers/trakt"
|
||||||
import WorkOSProvider from "next-auth/providers/workos"
|
import WorkOSProvider from "next-auth/providers/workos"
|
||||||
import BoxyHQSAMLProvider from "next-auth/providers/boxyhq-saml"
|
import BoxyHQSAMLProvider from "next-auth/providers/boxyhq-saml"
|
||||||
import WikimediaProvider from "next-auth/providers/wikimedia"
|
|
||||||
import VkProvider from "next-auth/providers/vk"
|
|
||||||
|
|
||||||
// TypeORM
|
|
||||||
// import { TypeORMLegacyAdapter } from "@next-auth/typeorm-legacy-adapter"
|
|
||||||
// const adapter = TypeORMLegacyAdapter({
|
|
||||||
// type: "sqlite",
|
|
||||||
// name: "next-auth-test-memory",
|
|
||||||
// database: "./typeorm/dev.db",
|
|
||||||
// synchronize: true,
|
|
||||||
// })
|
|
||||||
|
|
||||||
// // Prisma
|
|
||||||
// import { PrismaAdapter } from "@next-auth/prisma-adapter"
|
// import { PrismaAdapter } from "@next-auth/prisma-adapter"
|
||||||
// import { PrismaClient } from "@prisma/client"
|
// import { PrismaClient } from "@prisma/client"
|
||||||
// const prisma = new PrismaClient()
|
// const prisma = new PrismaClient()
|
||||||
// const adapter = PrismaAdapter(prisma)
|
// const adapter = PrismaAdapter(prisma)
|
||||||
|
|
||||||
// // Fauna
|
|
||||||
// import { Client as FaunaClient } from "faunadb"
|
// import { Client as FaunaClient } from "faunadb"
|
||||||
// import { FaunaAdapter } from "@next-auth/fauna-adapter"
|
// import { FaunaAdapter } from "@next-auth/fauna-adapter"
|
||||||
|
|
||||||
// const client = new FaunaClient({
|
// const client = new FaunaClient({
|
||||||
// secret: process.env.FAUNA_SECRET,
|
// secret: process.env.FAUNA_SECRET,
|
||||||
// domain: process.env.FAUNA_DOMAIN,
|
// domain: process.env.FAUNA_DOMAIN,
|
||||||
// })
|
// })
|
||||||
// const adapter = FaunaAdapter(client)
|
// const adapter = FaunaAdapter(client)
|
||||||
|
|
||||||
// // Dummy
|
|
||||||
// const adapter: any = {
|
|
||||||
// getUserByEmail: (email) => ({ id: "1", email, emailVerified: null }),
|
|
||||||
// createVerificationToken: (token) => token,
|
|
||||||
// }
|
|
||||||
|
|
||||||
export const authOptions: NextAuthOptions = {
|
export const authOptions: NextAuthOptions = {
|
||||||
// adapter,
|
// adapter,
|
||||||
providers: [
|
providers: [
|
||||||
@@ -167,11 +147,6 @@ export const authOptions: NextAuthOptions = {
|
|||||||
clientSecret: process.env.IDS4_SECRET,
|
clientSecret: process.env.IDS4_SECRET,
|
||||||
issuer: process.env.IDS4_ISSUER,
|
issuer: process.env.IDS4_ISSUER,
|
||||||
}),
|
}),
|
||||||
DuendeIDS6Provider({
|
|
||||||
clientId: "interactive.confidential",
|
|
||||||
clientSecret: "secret",
|
|
||||||
issuer: "https://demo.duendesoftware.com",
|
|
||||||
}),
|
|
||||||
DiscordProvider({
|
DiscordProvider({
|
||||||
clientId: process.env.DISCORD_ID,
|
clientId: process.env.DISCORD_ID,
|
||||||
clientSecret: process.env.DISCORD_SECRET,
|
clientSecret: process.env.DISCORD_SECRET,
|
||||||
@@ -227,18 +202,10 @@ export const authOptions: NextAuthOptions = {
|
|||||||
clientSecret: process.env.WORKOS_SECRET,
|
clientSecret: process.env.WORKOS_SECRET,
|
||||||
}),
|
}),
|
||||||
BoxyHQSAMLProvider({
|
BoxyHQSAMLProvider({
|
||||||
issuer: process.env.BOXYHQSAML_ISSUER ?? "https://example.com",
|
issuer: process.env.BOXYHQSAML_ISSUER,
|
||||||
clientId: process.env.BOXYHQSAML_ID,
|
clientId: process.env.BOXYHQSAML_ID,
|
||||||
clientSecret: process.env.BOXYHQSAML_SECRET,
|
clientSecret: process.env.BOXYHQSAML_SECRET,
|
||||||
}),
|
}),
|
||||||
WikimediaProvider({
|
|
||||||
clientId: process.env.WIKIMEDIA_ID,
|
|
||||||
clientSecret: process.env.WIKIMEDIA_SECRET,
|
|
||||||
}),
|
|
||||||
VkProvider({
|
|
||||||
clientId: process.env.VK_ID,
|
|
||||||
clientSecret: process.env.VK_SECRET
|
|
||||||
}),
|
|
||||||
],
|
],
|
||||||
debug: true,
|
debug: true,
|
||||||
theme: {
|
theme: {
|
||||||
|
|||||||
@@ -1,15 +1,19 @@
|
|||||||
{
|
{
|
||||||
|
"name": "next-auth-example",
|
||||||
|
"version": "0.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "An example project for NextAuth.js with Next.js",
|
"description": "An example project for NextAuth.js",
|
||||||
"repository": "https://github.com/nextauthjs/next-auth-example.git",
|
"repository": "https://github.com/nextauthjs/next-auth-example.git",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/nextauthjs/next-auth/issues"
|
"url": "https://github.com/nextauthjs/next-auth/issues"
|
||||||
},
|
},
|
||||||
"homepage": "https://next-auth-example.vercel.app",
|
"homepage": "https://next-auth-example.vercel.app",
|
||||||
|
"main": "",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next",
|
"dev": "next",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start"
|
"start": "next start",
|
||||||
|
"types": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"author": "Iain Collins <me@iaincollins.com>",
|
"author": "Iain Collins <me@iaincollins.com>",
|
||||||
"contributors": [
|
"contributors": [
|
||||||
@@ -17,16 +21,20 @@
|
|||||||
"Nico Domino <yo@ndo.dev>",
|
"Nico Domino <yo@ndo.dev>",
|
||||||
"Lluis Agusti <hi@llu.lu>"
|
"Lluis Agusti <hi@llu.lu>"
|
||||||
],
|
],
|
||||||
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"next": "latest",
|
"next": "12.1.7-canary.51",
|
||||||
"next-auth": "latest",
|
"next-auth": "latest",
|
||||||
"nodemailer": "^6",
|
"nodemailer": "^6",
|
||||||
"react": "^18.2.0",
|
"react": "^18",
|
||||||
"react-dom": "^18.2.0"
|
"react-dom": "^18"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^17",
|
"@types/node": "^17",
|
||||||
"@types/react": "^18.0.15",
|
"@types/react": "^18",
|
||||||
"typescript": "^4"
|
"typescript": "^4"
|
||||||
|
},
|
||||||
|
"prettier": {
|
||||||
|
"semi": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { SessionProvider } from "next-auth/react"
|
import { SessionProvider } from "next-auth/react"
|
||||||
import "./styles.css"
|
|
||||||
|
|
||||||
import type { AppProps } from "next/app"
|
import type { AppProps } from "next/app"
|
||||||
|
import "./styles.css"
|
||||||
|
|
||||||
// Use of the <SessionProvider> is mandatory to allow components that call
|
// Use of the <SessionProvider> is mandatory to allow components that call
|
||||||
// `useSession()` anywhere in your application to access the `session` object.
|
// `useSession()` anywhere in your application to access the `session` object.
|
||||||
|
|||||||
@@ -1,14 +1,10 @@
|
|||||||
// This is an example of how to read a JSON Web Token from an API route
|
// This is an example of how to read a JSON Web Token from an API route
|
||||||
import { getToken } from "next-auth/jwt"
|
import { getToken } from "next-auth/jwt"
|
||||||
|
|
||||||
import type { NextApiRequest, NextApiResponse } from "next"
|
import type { NextApiRequest, NextApiResponse } from "next"
|
||||||
|
|
||||||
const secret = process.env.NEXTAUTH_SECRET
|
const secret = process.env.NEXTAUTH_SECRET
|
||||||
|
|
||||||
export default async function handler(
|
export default async (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
req: NextApiRequest,
|
|
||||||
res: NextApiResponse
|
|
||||||
) {
|
|
||||||
const token = await getToken({ req, secret })
|
const token = await getToken({ req, secret })
|
||||||
res.send(JSON.stringify(token, null, 2))
|
res.send(JSON.stringify(token, null, 2))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,19 @@
|
|||||||
// This is an example of to protect an API route
|
// This is an example of to protect an API route
|
||||||
import { unstable_getServerSession } from "next-auth/next"
|
import { unstable_getServerSession } from "next-auth/next"
|
||||||
import { authOptions } from "../auth/[...nextauth]"
|
|
||||||
|
|
||||||
import type { NextApiRequest, NextApiResponse } from "next"
|
import type { NextApiRequest, NextApiResponse } from "next"
|
||||||
|
|
||||||
export default async function handler(
|
export default async (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
req: NextApiRequest,
|
|
||||||
res: NextApiResponse
|
|
||||||
) {
|
|
||||||
const session = await unstable_getServerSession(req, res, authOptions)
|
const session = await unstable_getServerSession(req, res, authOptions)
|
||||||
|
|
||||||
if (session) {
|
if (session) {
|
||||||
return res.send({
|
res.send({
|
||||||
content:
|
content:
|
||||||
"This is protected content. You can access this content because you are signed in.",
|
"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.",
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,8 @@
|
|||||||
// This is an example of how to access a session from an API route
|
// This is an example of how to access a session from an API route
|
||||||
import { unstable_getServerSession } from "next-auth"
|
import { getSession } from "next-auth/react"
|
||||||
import { authOptions } from "../auth/[...nextauth]"
|
|
||||||
|
|
||||||
import type { NextApiRequest, NextApiResponse } from "next"
|
import type { NextApiRequest, NextApiResponse } from "next"
|
||||||
|
|
||||||
export default async function handler(
|
export default async (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
req: NextApiRequest,
|
const session = await getSession({ req })
|
||||||
res: NextApiResponse
|
|
||||||
) {
|
|
||||||
const session = await unstable_getServerSession(req, res, authOptions)
|
|
||||||
res.send(JSON.stringify(session, null, 2))
|
res.send(JSON.stringify(session, null, 2))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
import { unstable_getServerSession } from "next-auth/next"
|
import { unstable_getServerSession } from "next-auth/next"
|
||||||
import { authOptions } from "./api/auth/[...nextauth]"
|
import { authOptions } from "./api/auth/[...nextauth]"
|
||||||
import Layout from "../components/layout"
|
import Layout from "../components/layout"
|
||||||
|
import type { NextPageContext } from "next"
|
||||||
|
|
||||||
import type { GetServerSidePropsContext } from "next"
|
export default function ServerSidePage({ session }) {
|
||||||
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
|
// As this page uses Server Side Rendering, the `session` will be already
|
||||||
// populated on render without needing to go through a loading stage.
|
// populated on render without needing to go through a loading stage.
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<Layout>
|
||||||
<h1>Server Side Rendering</h1>
|
<h1>Server Side Rendering</h1>
|
||||||
@@ -29,20 +28,15 @@ export default function ServerSidePage({ session }: { session: Session }) {
|
|||||||
The disadvantage of Server Side Rendering is that this page is slower to
|
The disadvantage of Server Side Rendering is that this page is slower to
|
||||||
render.
|
render.
|
||||||
</p>
|
</p>
|
||||||
<pre>{JSON.stringify(session, null, 2)}</pre>
|
|
||||||
</Layout>
|
</Layout>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Export the `session` prop to use sessions with Server Side Rendering
|
// Export the `session` prop to use sessions with Server Side Rendering
|
||||||
export async function getServerSideProps(context: GetServerSidePropsContext) {
|
export async function getServerSideProps(context: NextPageContext) {
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
session: await unstable_getServerSession(
|
session: await unstable_getServerSession(context.req, context.res, authOptions),
|
||||||
context.req,
|
|
||||||
context.res,
|
|
||||||
authOptions
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1232,10 +1232,10 @@ natural-compare@^1.4.0:
|
|||||||
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
|
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
|
||||||
integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=
|
integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=
|
||||||
|
|
||||||
"next-auth@workspace:*":
|
next-auth@^4.5.0:
|
||||||
version "4.9.0"
|
version "4.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/next-auth/-/next-auth-4.9.0.tgz#0d8cabcb22a976744131a2e68d5f08756f322593"
|
resolved "https://registry.yarnpkg.com/next-auth/-/next-auth-4.5.0.tgz#2df57287fddc705b8971c88c60bad44a89ac6dd1"
|
||||||
integrity sha512-/4S5dFeyNg2nXlD7g/Sh5A4WZWnUMDpEf8x/x+gzmAf5cAY2SjDM6sLk9u4XRmsndsxQpIMWDw03sUTAD+Yzog==
|
integrity sha512-B6gYRIbqtj8nlDsx3y2Ruwp/mvZnItPs7VUULY43QYw+M9xtDPIM9EBZ3ryd/wNYA3MDteBJlzGm/ivseXcmJA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.16.3"
|
"@babel/runtime" "^7.16.3"
|
||||||
"@panva/hkdf" "^1.0.1"
|
"@panva/hkdf" "^1.0.1"
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ This is the Dgraph Adapter for [`next-auth`](https://next-auth.js.org).
|
|||||||
|
|
||||||
1. Install the necessary packages
|
1. Install the necessary packages
|
||||||
|
|
||||||
```bash npm2yarn2pnpm
|
```bash npm2yarn
|
||||||
npm install next-auth @next-auth/dgraph-adapter
|
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).
|
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
|
```js
|
||||||
import * as jwt from "jsonwebtoken"
|
import * as jwt from "jsonwebtoken";
|
||||||
export default NextAuth({
|
export default NextAuth({
|
||||||
session: {
|
session: {
|
||||||
strategy: "jwt",
|
strategy: "jwt"
|
||||||
},
|
},
|
||||||
jwt: {
|
jwt: {
|
||||||
secret: process.env.SECRET,
|
secret: process.env.SECRET,
|
||||||
encode: async ({ secret, token }) => {
|
encode: async ({ secret, token }) => {
|
||||||
return jwt.sign({ ...token, userId: token.id }, secret, {
|
return jwt.sign({...token, userId: token.id}, secret, {
|
||||||
algorithm: "HS256",
|
algorithm: "HS256",
|
||||||
expiresIn: 30 * 24 * 60 * 60, // 30 days
|
expiresIn: 30 * 24 * 60 * 60, // 30 days
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
decode: async ({ secret, token }) => {
|
decode: async ({ secret, token }) => {
|
||||||
return jwt.verify(token, secret, { algorithms: ["HS256"] })
|
return jwt.verify(token, secret, { algorithms: ["HS256"] });
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -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`
|
1. Install `next-auth` and `@next-auth/dynamodb-adapter`
|
||||||
|
|
||||||
```bash npm2yarn2pnpm
|
```bash npm2yarn
|
||||||
npm install next-auth @next-auth/dynamodb-adapter
|
npm install next-auth @next-auth/dynamodb-adapter
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
1. Install the necessary packages
|
||||||
|
|
||||||
```bash npm2yarn2pnpm
|
```bash npm2yarn
|
||||||
npm install next-auth @next-auth/fauna-adapter faunadb
|
npm install next-auth @next-auth/fauna-adapter faunadb
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -5,14 +5,18 @@ title: Firebase
|
|||||||
|
|
||||||
# Firebase
|
# Firebase
|
||||||
|
|
||||||
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.
|
:::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.
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
1. Install the necessary packages
|
1. Install the necessary packages
|
||||||
|
|
||||||
```bash npm2yarn2pnpm
|
```bash npm2yarn
|
||||||
npm install next-auth @next-auth/firebase-adapter
|
npm install next-auth @next-auth/firebase-adapter@experimental
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Add this adapter to your `pages/api/auth/[...nextauth].js` next-auth configuration object.
|
2. Add this adapter to your `pages/api/auth/[...nextauth].js` next-auth configuration object.
|
||||||
@@ -20,7 +24,14 @@ npm install next-auth @next-auth/firebase-adapter
|
|||||||
```javascript title="pages/api/auth/[...nextauth].js"
|
```javascript title="pages/api/auth/[...nextauth].js"
|
||||||
import NextAuth from "next-auth"
|
import NextAuth from "next-auth"
|
||||||
import GoogleProvider from "next-auth/providers/google"
|
import GoogleProvider from "next-auth/providers/google"
|
||||||
import { FirestoreAdapter } from "@next-auth/firebase-adapter"
|
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()
|
||||||
|
|
||||||
// For more information on each option (and a full list of options) go to
|
// For more information on each option (and a full list of options) go to
|
||||||
// https://next-auth.js.org/configuration/options
|
// https://next-auth.js.org/configuration/options
|
||||||
@@ -32,19 +43,9 @@ export default NextAuth({
|
|||||||
clientSecret: process.env.GOOGLE_SECRET,
|
clientSecret: process.env.GOOGLE_SECRET,
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
adapter: FirestoreAdapter({
|
adapter: FirebaseAdapter(firestore),
|
||||||
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
|
## Options
|
||||||
@@ -68,21 +69,6 @@ const firebaseConfig = {
|
|||||||
|
|
||||||
See [firebase.google.com/docs/web/setup](https://firebase.google.com/docs/web/setup) for more details.
|
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**
|
:::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.
|
**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.
|
||||||
|
|||||||
@@ -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:
|
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 npm2yarn2pnpm
|
```bash npm2yarn
|
||||||
npm install next-auth @next-auth/mikro-orm-adapter @mikro-orm/core @mikro-orm/[YOUR DRIVER]
|
npm install next-auth @next-auth/mikro-orm-adapter @mikro-orm/core @mikro-orm/[YOUR DRIVER]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ The MongoDB adapter does not handle connections automatically, so you will have
|
|||||||
|
|
||||||
1. Install the necessary packages
|
1. Install the necessary packages
|
||||||
|
|
||||||
```bash npm2yarn2pnpm
|
```bash npm2yarn
|
||||||
npm install next-auth @next-auth/mongodb-adapter mongodb
|
npm install next-auth @next-auth/mongodb-adapter mongodb
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ This is the Neo4j Adapter for [`next-auth`](https://next-auth.js.org). This pack
|
|||||||
|
|
||||||
1. Install the necessary packages
|
1. Install the necessary packages
|
||||||
|
|
||||||
```bash npm2yarn2pnpm
|
```bash npm2yarn
|
||||||
npm install next-auth @next-auth/neo4j-adapter neo4j-driver
|
npm install next-auth @next-auth/neo4j-adapter neo4j-driver
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -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`
|
1. Install `next-auth` and `@next-auth/pouchdb-adapter`
|
||||||
|
|
||||||
```bash npm2yarn2pnpm
|
```bash npm2yarn
|
||||||
npm install next-auth @next-auth/pouchdb-adapter
|
npm install next-auth @next-auth/pouchdb-adapter
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -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:
|
To use this Adapter, you need to install Prisma Client, Prisma CLI, and the separate `@next-auth/prisma-adapter` package:
|
||||||
|
|
||||||
```bash npm2yarn2pnpm
|
```bash npm2yarn
|
||||||
npm install next-auth @prisma/client @next-auth/prisma-adapter
|
npm install next-auth @prisma/client @next-auth/prisma-adapter
|
||||||
npm install prisma --save-dev
|
npm install prisma --save-dev
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ This is the Sequelize Adapter for [`next-auth`](https://next-auth.js.org).
|
|||||||
|
|
||||||
1. Install the necessary packages
|
1. Install the necessary packages
|
||||||
|
|
||||||
```bash npm2yarn2pnpm
|
```bash npm2yarn
|
||||||
npm install next-auth @next-auth/sequelize-adapter sequelize
|
npm install next-auth @next-auth/sequelize-adapter sequelize
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -5,25 +5,21 @@ title: TypeORM
|
|||||||
|
|
||||||
# TypeORM
|
# TypeORM
|
||||||
|
|
||||||
This Adapter is used to support SQL-flavored databases (like SQLite, MySQL, MSSQL, MariaDB, CockroachDB, etc.) through [TypeORM](https://typeorm.io).
|
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.)
|
||||||
|
|
||||||
:::note
|
:::note
|
||||||
If you previously used this Adapter with MongoDB, check out the [MongoDB Adapter](/adapters/mongodb) instead.
|
If you previously used this Adapter with MongoDB, check out the [MongoDB Adapter](/adapters/mongodb) instead.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
:::note
|
:::warning
|
||||||
In the future, we might split up this adapter to support single flavors of SQL for easier maintenance and reduced bundle size.
|
In the future, we might split up this adapter to support single flavors of SQL for easier maintenance and reduced bundle size.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
## Usage
|
## 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:
|
To use this Adapter, you need to install the following packages:
|
||||||
|
|
||||||
```bash npm2yarn2pnpm
|
```bash npm2yarn
|
||||||
npm install next-auth @next-auth/typeorm-legacy-adapter typeorm
|
npm install next-auth @next-auth/typeorm-legacy-adapter typeorm
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -40,7 +36,7 @@ export default NextAuth({
|
|||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
`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.
|
`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.
|
||||||
|
|
||||||
## Custom models
|
## Custom models
|
||||||
|
|
||||||
@@ -221,9 +217,9 @@ For example, you can add the naming convention option to the connection object i
|
|||||||
import NextAuth from "next-auth"
|
import NextAuth from "next-auth"
|
||||||
import { TypeORMLegacyAdapter } from "@next-auth/typeorm-legacy-adapter"
|
import { TypeORMLegacyAdapter } from "@next-auth/typeorm-legacy-adapter"
|
||||||
import { SnakeNamingStrategy } from 'typeorm-naming-strategies'
|
import { SnakeNamingStrategy } from 'typeorm-naming-strategies'
|
||||||
|
import { ConnectionOptions } from "typeorm"
|
||||||
|
|
||||||
export default NextAuth({
|
const connection: ConnectionOptions = {
|
||||||
adapter: TypeORMLegacyAdapter({
|
|
||||||
type: "mysql",
|
type: "mysql",
|
||||||
host: "localhost",
|
host: "localhost",
|
||||||
port: 3306,
|
port: 3306,
|
||||||
@@ -231,7 +227,10 @@ export default NextAuth({
|
|||||||
password: "test",
|
password: "test",
|
||||||
database: "test",
|
database: "test",
|
||||||
namingStrategy: new SnakeNamingStrategy()
|
namingStrategy: new SnakeNamingStrategy()
|
||||||
}),
|
}
|
||||||
|
|
||||||
|
export default NextAuth({
|
||||||
|
adapter: TypeORMLegacyAdapter(connection),
|
||||||
...
|
...
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ title: Upstash Redis
|
|||||||
|
|
||||||
To use this Adapter, you need to install `@upstash/redis` and `@next-auth/upstash-redis-adapter` package:
|
To use this Adapter, you need to install `@upstash/redis` and `@next-auth/upstash-redis-adapter` package:
|
||||||
|
|
||||||
```bash npm2yarn2pnpm
|
```bash npm2yarn
|
||||||
npm install @upstash/redis @next-auth/upstash-redis-adapter
|
npm install @upstash/redis @next-auth/upstash-redis-adapter
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
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:
|
`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,11 +80,10 @@ You can get the `withAuth` middleware function from `next-auth/middleware` eithe
|
|||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
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. This environment variable 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.
|
|
||||||
|
|
||||||
### Basic usage
|
### Basic usage
|
||||||
|
|
||||||
@@ -94,7 +93,7 @@ The most simple usage is when you want to require authentication for your entire
|
|||||||
export { default } from "next-auth/middleware"
|
export { default } from "next-auth/middleware"
|
||||||
```
|
```
|
||||||
|
|
||||||
That's it! Your application is now secured. 🎉
|
That's it! Your application is not secured. 🎉
|
||||||
|
|
||||||
If you only want to secure certain pages, export a `config` object with a `matcher`:
|
If you only want to secure certain pages, export a `config` object with a `matcher`:
|
||||||
|
|
||||||
@@ -150,22 +149,6 @@ 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
|
### Advanced usage
|
||||||
|
|
||||||
NextAuth.js Middleware is very flexible, there are multiple ways to use it.
|
NextAuth.js Middleware is very flexible, there are multiple ways to use it.
|
||||||
|
|||||||
@@ -13,22 +13,19 @@ When deploying to production, set the `NEXTAUTH_URL` environment variable to the
|
|||||||
NEXTAUTH_URL=https://example.com
|
NEXTAUTH_URL=https://example.com
|
||||||
```
|
```
|
||||||
|
|
||||||
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).
|
If your Next.js application uses a custom base path, specify the route to the API endpoint in full.
|
||||||
|
|
||||||
_e.g. `NEXTAUTH_URL=https://example.com/custom-route/api/auth`_
|
_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
|
:::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.
|
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
|
### 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` option in [NextAuth](/configuration/options#secret) and [Middleware](/configuration/nextjs#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.
|
||||||
|
|
||||||
|
If you are using [Middleware](/configuration/nextjs#prerequisites) this environment variable must be set.
|
||||||
|
|
||||||
### NEXTAUTH_URL_INTERNAL
|
### NEXTAUTH_URL_INTERNAL
|
||||||
|
|
||||||
@@ -369,14 +366,11 @@ 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.
|
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
|
```js
|
||||||
theme: {
|
theme: {
|
||||||
colorScheme: "auto", // "auto" | "dark" | "light"
|
colorScheme: "auto", // "auto" | "dark" | "light"
|
||||||
brandColor: "", // Hex color code
|
brandColor: "", // Hex color code
|
||||||
logo: "", // Absolute URL to image
|
logo: "" // Absolute URL to image
|
||||||
buttonText: "" // Hex color code
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -350,7 +350,7 @@ providers: [
|
|||||||
|
|
||||||
## Built-in 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/packages/next-auth/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/src/providers). Each built-in provider has its own documentation page:
|
||||||
|
|
||||||
<div className="provider-name-list">
|
<div className="provider-name-list">
|
||||||
{Object.entries(require("../../../providers.json"))
|
{Object.entries(require("../../../providers.json"))
|
||||||
|
|||||||
@@ -165,12 +165,6 @@ See repository [`README`](https://github.com/nextauthjs/react-query) for more de
|
|||||||
|
|
||||||
NextAuth.js provides a `getSession()` helper which should be called **client side only** to return the current active session.
|
NextAuth.js provides a `getSession()` helper which should be called **client side only** to return the current active session.
|
||||||
|
|
||||||
On the server side, **this is still available to use**, however, we recommend using `unstable_getServerSession` going forward. The idea behind this is to avoid an additional unnecessary `fetch` call on the server side. For more information, please check out [this issue](https://github.com/nextauthjs/next-auth/issues/1535).
|
|
||||||
|
|
||||||
:::note
|
|
||||||
The `unstable_getServerSession` only has the prefix `unstable_` at the moment, because the API may change in the future. There are no known bugs at the moment and it is safe to use. If you discover any issues, please do report them as a [GitHub Issue](https://github.com/nextauthjs/next-auth/issues) and we will patch them as soon as possible.
|
|
||||||
:::
|
|
||||||
|
|
||||||
This helper is helpful in case you want to read the session outside of the context of React.
|
This helper is helpful in case you want to read the session outside of the context of React.
|
||||||
|
|
||||||
When called, `getSession()` will send a request to `/api/auth/session` and returns a promise with a [session object](https://github.com/nextauthjs/next-auth/blob/main/packages/next-auth/src/core/types.ts#L407-L425), or `null` if no session exists.
|
When called, `getSession()` will send a request to `/api/auth/session` and returns a promise with a [session object](https://github.com/nextauthjs/next-auth/blob/main/packages/next-auth/src/core/types.ts#L407-L425), or `null` if no session exists.
|
||||||
@@ -427,14 +421,13 @@ This only works on pages where you provide the correct `pageProps`, however. Thi
|
|||||||
|
|
||||||
```js title="pages/index.js"
|
```js title="pages/index.js"
|
||||||
import { unstable_getServerSession } from "next-auth/next"
|
import { unstable_getServerSession } from "next-auth/next"
|
||||||
import { authOptions } from './api/auth/[...nextauth]'
|
|
||||||
|
|
||||||
...
|
...
|
||||||
|
|
||||||
export async function getServerSideProps({ req, res }) {
|
export async function getServerSideProps(ctx) {
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
session: await unstable_getServerSession(req, res, authOptions)
|
session: await unstable_getServerSession(ctx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -506,29 +499,3 @@ However, if it was set to `false`, it stops re-fetching the session and the comp
|
|||||||
:::note
|
:::note
|
||||||
See [**the Next.js documentation**](https://nextjs.org/docs/advanced-features/custom-app) for more information on **\_app.js** in Next.js applications.
|
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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
@@ -97,7 +97,6 @@ To protect an API Route, you can use the [`unstable_getServerSession()`](/config
|
|||||||
|
|
||||||
```javascript title="pages/api/restricted.js" showLineNumbers
|
```javascript title="pages/api/restricted.js" showLineNumbers
|
||||||
import { unstable_getServerSession } from "next-auth/next"
|
import { unstable_getServerSession } from "next-auth/next"
|
||||||
import { authOptions } from "./api/auth/[...nextauth]"
|
|
||||||
|
|
||||||
export default async (req, res) => {
|
export default async (req, res) => {
|
||||||
const session = await unstable_getServerSession(req, res, authOptions)
|
const session = await unstable_getServerSession(req, res, authOptions)
|
||||||
|
|||||||
@@ -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:
|
You can upgrade to the new version by running:
|
||||||
|
|
||||||
```bash npm2yarn2pnpm
|
```bash npm2yarn
|
||||||
npm install next-auth
|
npm install next-auth
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -124,74 +124,67 @@ providers: [
|
|||||||
The following code shows the complete source for the built-in `sendVerificationRequest()` method:
|
The following code shows the complete source for the built-in `sendVerificationRequest()` method:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
import { createTransport } from "nodemailer"
|
import nodemailer from "nodemailer"
|
||||||
|
|
||||||
async function sendVerificationRequest(params) {
|
async function sendVerificationRequest({
|
||||||
const { identifier, url, provider, theme } = params
|
identifier: email,
|
||||||
|
url,
|
||||||
|
provider: { server, from },
|
||||||
|
}) {
|
||||||
const { host } = new URL(url)
|
const { host } = new URL(url)
|
||||||
// NOTE: You are not required to use `nodemailer`, use whatever you want.
|
const transport = nodemailer.createTransport(server)
|
||||||
const transport = createTransport(provider.server)
|
await transport.sendMail({
|
||||||
const result = await transport.sendMail({
|
to: email,
|
||||||
to: identifier,
|
from,
|
||||||
from: provider.from,
|
|
||||||
subject: `Sign in to ${host}`,
|
subject: `Sign in to ${host}`,
|
||||||
text: text({ url, host }),
|
text: text({ url, host }),
|
||||||
html: html({ url, host, theme }),
|
html: html({ url, host, email }),
|
||||||
})
|
})
|
||||||
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
|
||||||
* Email HTML body
|
function html({ url, host, email }: Record<"url" | "host" | "email", string>) {
|
||||||
* Insert invisible space into domains from being turned into a hyperlink by email
|
// Insert invisible space into domains and email address to prevent both the
|
||||||
* clients like Outlook and Apple mail, as this is confusing because it seems
|
// email address and the domain from being turned into a hyperlink by email
|
||||||
* like they are supposed to click on it to sign in.
|
// 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.
|
||||||
* @note We don't add the email address to avoid needing to escape it, if you do, remember to sanitize it!
|
const escapedEmail = `${email.replace(/\./g, "​.")}`
|
||||||
*/
|
const escapedHost = `${host.replace(/\./g, "​.")}`
|
||||||
function html(params: { url: string; host: string; theme: Theme }) {
|
|
||||||
const { url, host, theme } = params
|
|
||||||
|
|
||||||
const escapedHost = host.replace(/\./g, "​.")
|
// Some simple styling options
|
||||||
|
const backgroundColor = "#f9f9f9"
|
||||||
const brandColor = theme.brandColor || "#346df1"
|
const textColor = "#444444"
|
||||||
const color = {
|
const mainBackgroundColor = "#ffffff"
|
||||||
background: "#f9f9f9",
|
const buttonBackgroundColor = "#346df1"
|
||||||
text: "#444",
|
const buttonBorderColor = "#346df1"
|
||||||
mainBackground: "#fff",
|
const buttonTextColor = "#ffffff"
|
||||||
buttonBackground: brandColor,
|
|
||||||
buttonBorder: brandColor,
|
|
||||||
buttonText: theme.buttonText || "#fff",
|
|
||||||
}
|
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<body style="background: ${color.background};">
|
<body style="background: ${backgroundColor};">
|
||||||
<table width="100%" border="0" cellspacing="20" cellpadding="0"
|
<table width="100%" border="0" cellspacing="0" cellpadding="0">
|
||||||
style="background: ${color.mainBackground}; max-width: 600px; margin: auto; border-radius: 10px;">
|
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center"
|
<td align="center" style="padding: 10px 0px 20px 0px; font-size: 22px; font-family: Helvetica, Arial, sans-serif; color: ${textColor};">
|
||||||
style="padding: 10px 0px; font-size: 22px; font-family: Helvetica, Arial, sans-serif; color: ${color.text};">
|
<strong>${escapedHost}</strong>
|
||||||
Sign in to <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>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center" style="padding: 20px 0;">
|
<td align="center" style="padding: 20px 0;">
|
||||||
<table border="0" cellspacing="0" cellpadding="0">
|
<table border="0" cellspacing="0" cellpadding="0">
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center" style="border-radius: 5px;" bgcolor="${color.buttonBackground}"><a href="${url}"
|
<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>
|
||||||
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>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center"
|
<td align="center" style="padding: 0px 0px 10px 0px; font-size: 16px; line-height: 22px; font-family: Helvetica, Arial, sans-serif; color: ${textColor};">
|
||||||
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.
|
If you did not request this email you can safely ignore it.
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -200,8 +193,8 @@ function html(params: { url: string; host: string; theme: Theme }) {
|
|||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Email Text body (fallback for email clients that don't render HTML, e.g. feature phones) */
|
// Email Text body (fallback for email clients that don't render HTML, e.g. feature phones)
|
||||||
function text({ url, host }: { url: string; host: string }) {
|
function text({ url, host }: Record<"url" | "host", string>) {
|
||||||
return `Sign in to ${host}\n${url}\n\n`
|
return `Sign in to ${host}\n${url}\n\n`
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ https://github.com/settings/apps
|
|||||||
|
|
||||||
The **GitHub Provider** comes with a set of default options:
|
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.ts)
|
- [GitHub Provider options](https://github.com/nextauthjs/next-auth/blob/main/packages/next-auth/src/providers/github.js)
|
||||||
|
|
||||||
You can override any of the options to suit your own use case.
|
You can override any of the options to suit your own use case.
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ title: Overview
|
|||||||
|
|
||||||
Authentication Providers in **NextAuth.js** are services that can be used to sign in a user.
|
Authentication Providers in **NextAuth.js** are services that can be used to sign in a user.
|
||||||
|
|
||||||
There are four ways a user can be signed in:
|
There's 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 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)
|
- [Using a custom OAuth Provider](/configuration/providers/oauth#using-a-custom-provider)
|
||||||
|
|||||||
@@ -1,50 +0,0 @@
|
|||||||
---
|
|
||||||
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
|
|
||||||
})
|
|
||||||
]
|
|
||||||
...
|
|
||||||
```
|
|
||||||
@@ -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
|
You will need an additional dependency, `ldapjs`, which you can install by running
|
||||||
|
|
||||||
```bash npm2yarn2pnpm
|
```bash npm2yarn
|
||||||
npm install ldapjs
|
npm install ldapjs
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
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="/middleware.js"
|
```js title="/pages/_middleware.js"
|
||||||
export { default } from "next-auth/middleware"
|
export { default } from "next-auth/middleware"
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -58,11 +58,10 @@ More details can be found [here](https://next-auth.js.org/configuration/nextjs#m
|
|||||||
|
|
||||||
You can protect server side rendered pages using the `unstable_getServerSession` method. This is different from the old `getSession()` method, in that it does not do an extra fetch out over the internet to confirm data from itself, increasing performance significantly.
|
You can protect server side rendered pages using the `unstable_getServerSession` method. This is different from the old `getSession()` method, in that it does not do an extra fetch out over the internet to confirm data from itself, increasing performance significantly.
|
||||||
|
|
||||||
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`.
|
You need to add this to every server rendered page you want to protect.
|
||||||
|
|
||||||
```js title="pages/server-side-example.js"
|
```js title="pages/server-side-example.js"
|
||||||
import { useSession, unstable_getServerSession } from "next-auth/next"
|
import { useSession, unstable_getServerSession } from "next-auth/next"
|
||||||
import { authOptions } from "./api/auth/[...nextauth]"
|
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const { data: session } = useSession()
|
const { data: session } = useSession()
|
||||||
@@ -83,11 +82,7 @@ export default function Page() {
|
|||||||
export async function getServerSideProps(context) {
|
export async function getServerSideProps(context) {
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
session: await unstable_getServerSession(
|
session: await unstable_getServerSession(context.req, context.res, authOptions),
|
||||||
context.req,
|
|
||||||
context.res,
|
|
||||||
authOptions
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -121,7 +116,6 @@ You can protect API routes using the `unstable_getServerSession()` method.
|
|||||||
|
|
||||||
```js title="pages/api/get-session-example.js"
|
```js title="pages/api/get-session-example.js"
|
||||||
import { unstable_getServerSession } from "next-auth/next"
|
import { unstable_getServerSession } from "next-auth/next"
|
||||||
import { authOptions } from "./api/auth/[...nextauth]"
|
|
||||||
|
|
||||||
export default async (req, res) => {
|
export default async (req, res) => {
|
||||||
const session = await unstable_getServerSession(req, res, authOptions)
|
const session = await unstable_getServerSession(req, res, authOptions)
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ To test an implementation of NextAuth.js, we encourage you to use [Cypress](http
|
|||||||
|
|
||||||
To get started, install the dependencies:
|
To get started, install the dependencies:
|
||||||
|
|
||||||
```bash npm2yarn2pnpm
|
```bash npm2yarn
|
||||||
npm install --save-dev cypress cypress-social-logins @testing-library/cypress
|
npm install --save-dev cypress cypress-social-logins @testing-library/cypress
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -155,9 +155,9 @@ module.exports = {
|
|||||||
showLastUpdateAuthor: true,
|
showLastUpdateAuthor: true,
|
||||||
showLastUpdateTime: true,
|
showLastUpdateTime: true,
|
||||||
remarkPlugins: [
|
remarkPlugins: [
|
||||||
require("@sapphire/docusaurus-plugin-npm2yarn2pnpm").npm2yarn2pnpm,
|
|
||||||
require("remark-github"),
|
require("remark-github"),
|
||||||
require("mdx-mermaid"),
|
require("mdx-mermaid"),
|
||||||
|
[require("@docusaurus/remark-plugin-npm2yarn"), { sync: true }],
|
||||||
],
|
],
|
||||||
versions: {
|
versions: {
|
||||||
current: {
|
current: {
|
||||||
|
|||||||
@@ -21,9 +21,9 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/core": "^2.0.0-beta.21",
|
"@docusaurus/core": "^2.0.0-beta.21",
|
||||||
"@docusaurus/preset-classic": "^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",
|
"@docusaurus/theme-common": "2.0.0-beta.21",
|
||||||
"@mdx-js/react": "1.6.22",
|
"@mdx-js/react": "1.6.22",
|
||||||
"@sapphire/docusaurus-plugin-npm2yarn2pnpm": "1.1.3",
|
|
||||||
"classnames": "^2.3.1",
|
"classnames": "^2.3.1",
|
||||||
"mdx-mermaid": "^1.2.2",
|
"mdx-mermaid": "^1.2.2",
|
||||||
"mermaid": "^9.0.1",
|
"mermaid": "^9.0.1",
|
||||||
|
|||||||
@@ -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:
|
You only need to add two changes:
|
||||||
|
|
||||||
1. Add your config: [`src/providers/{provider}.js`](https://github.com/nextauthjs/next-auth/tree/main/packages/next-auth/src/providers)<br />
|
1. Add your config: [`src/providers/{provider}.js`](https://github.com/nextauthjs/next-auth/tree/main/src/providers)<br />
|
||||||
• make sure you use a named default export, like this: `export default function YourProvider`
|
• 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)
|
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 />
|
3. Add it to our [provider types](https://github.com/nextauthjs/next-auth/blob/ead715219a5d7a6e882a6ba27fa56b03954d062d/types/providers.d.ts) (for TS projects)<br />
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
---
|
|
||||||
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",
|
|
||||||
})
|
|
||||||
]
|
|
||||||
...
|
|
||||||
```
|
|
||||||
@@ -15,7 +15,7 @@ https://github.com/settings/apps
|
|||||||
|
|
||||||
The **Github Provider** comes with a set of default options:
|
The **Github Provider** comes with a set of default options:
|
||||||
|
|
||||||
- [Github Provider options](https://github.com/nextauthjs/next-auth/blob/main/src/providers/github.ts)
|
- [Github Provider options](https://github.com/nextauthjs/next-auth/blob/main/src/providers/github.js)
|
||||||
|
|
||||||
You can override any of the options to suit your own use case.
|
You can override any of the options to suit your own use case.
|
||||||
|
|
||||||
|
|||||||
@@ -54,6 +54,6 @@ providers: [
|
|||||||
clientSecret: "secret",
|
clientSecret: "secret",
|
||||||
protection: "pkce"
|
protection: "pkce"
|
||||||
})
|
})
|
||||||
]
|
}
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -36,9 +36,8 @@
|
|||||||
"pretty-quick": "^3.1.2",
|
"pretty-quick": "^3.1.2",
|
||||||
"semver": "7.3.5",
|
"semver": "7.3.5",
|
||||||
"stream-to-array": "2.3.0",
|
"stream-to-array": "2.3.0",
|
||||||
"ts-node": "10.8.2",
|
"ts-node": "10.5.0",
|
||||||
"turbo": "1.3.1",
|
"turbo": "^1.2.5",
|
||||||
"type-fest": "2.16.0",
|
|
||||||
"typescript": "^4.5.2"
|
"typescript": "^4.5.2"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -98,7 +97,7 @@
|
|||||||
"**/tests",
|
"**/tests",
|
||||||
"**/__tests__"
|
"**/__tests__"
|
||||||
],
|
],
|
||||||
"packageManager": "pnpm@7.5.1",
|
"packageManager": "pnpm@6.32.8",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "github",
|
"type": "github",
|
||||||
|
|||||||
@@ -32,13 +32,14 @@ npm install next-auth @next-auth/firebase-adapter
|
|||||||
```js
|
```js
|
||||||
import NextAuth from "next-auth"
|
import NextAuth from "next-auth"
|
||||||
import Providers from "next-auth/providers"
|
import Providers from "next-auth/providers"
|
||||||
import { FirestoreAdapter } from "@next-auth/firebase-adapter"
|
import { FirebaseAdapter } from "@next-auth/firebase-adapter"
|
||||||
|
|
||||||
import { initializeApp } from "firebase/app";
|
import firebase from "firebase/app"
|
||||||
import { getFirestore } from "firebase/firestore"
|
import "firebase/firestore"
|
||||||
|
|
||||||
const app = initializeApp({ projectId: "next-auth-test" });
|
const firestore = (
|
||||||
const firestore = getFirestore(app);
|
firebase.apps[0] ?? firebase.initializeApp(/* your config */)
|
||||||
|
).firestore()
|
||||||
|
|
||||||
// For more information on each option (and a full list of options) go to
|
// For more information on each option (and a full list of options) go to
|
||||||
// https://next-auth.js.org/configuration/options
|
// https://next-auth.js.org/configuration/options
|
||||||
@@ -50,7 +51,7 @@ export default NextAuth({
|
|||||||
clientSecret: process.env.GOOGLE_SECRET,
|
clientSecret: process.env.GOOGLE_SECRET,
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
adapter: FirestoreAdapter(firestore),
|
adapter: FirebaseAdapter(firestore),
|
||||||
...
|
...
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
module.exports = require("@next-auth/adapter-test/jest/jest-preset")
|
module.exports = require("@next-auth/adapter-test/jest.config")
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@next-auth/firebase-adapter",
|
"name": "@next-auth/firebase-adapter",
|
||||||
"version": "1.0.0",
|
"version": "0.1.3",
|
||||||
"description": "Firebase adapter for next-auth.",
|
"description": "Firebase adapter for next-auth.",
|
||||||
"homepage": "https://next-auth.js.org",
|
"homepage": "https://next-auth.js.org",
|
||||||
"repository": "https://github.com/nextauthjs/next-auth",
|
"repository": "https://github.com/nextauthjs/next-auth",
|
||||||
@@ -28,18 +28,18 @@
|
|||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc",
|
"build:wip": "tsc",
|
||||||
"test": "FIRESTORE_EMULATOR_HOST=localhost:8080 firebase emulators:exec --only firestore --project next-auth-test jest"
|
"test:wip": "FIRESTORE_EMULATOR_HOST=localhost:8080 firebase emulators:exec --only firestore --project next-auth-test jest"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"firebase": "^9.7.0",
|
"firebase": "^8.6.2",
|
||||||
"next-auth": "workspace:*"
|
"next-auth": "workspace:*"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@next-auth/adapter-test": "workspace:^0.0.0",
|
"@next-auth/adapter-test": "workspace:^0.0.0",
|
||||||
"@next-auth/tsconfig": "workspace:^0.0.0",
|
"@next-auth/tsconfig": "workspace:^0.0.0",
|
||||||
"firebase": "^9.7.0",
|
"firebase": "^8.6.2",
|
||||||
"firebase-tools": "^10.7.2",
|
"firebase-tools": "^9.11.0",
|
||||||
"jest": "^27.4.3",
|
"jest": "^27.4.3",
|
||||||
"next-auth": "workspace:*"
|
"next-auth": "workspace:*"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,58 +0,0 @@
|
|||||||
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
|
|
||||||
},
|
|
||||||
})
|
|
||||||
@@ -1,289 +1,283 @@
|
|||||||
import { initializeApp } from "firebase/app"
|
import type firebase from "firebase"
|
||||||
import type { FirebaseOptions } from "firebase/app"
|
import { createHash, randomBytes } from "crypto"
|
||||||
|
import { Adapter } from "next-auth/adapters"
|
||||||
import {
|
import {
|
||||||
addDoc,
|
querySnapshotToObject,
|
||||||
collection,
|
docSnapshotToObject,
|
||||||
deleteDoc,
|
stripUndefined,
|
||||||
doc,
|
} from "./utils"
|
||||||
getDoc,
|
import { Profile, Session, User } from "next-auth"
|
||||||
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"
|
|
||||||
|
|
||||||
import { getConverter } from "./converter"
|
interface FirebaseVerificationRequest {
|
||||||
|
id: string
|
||||||
type IndexableObject = Record<string, unknown>
|
identifier: string
|
||||||
|
token: string
|
||||||
export interface FirestoreAdapterOptions {
|
expires: Date
|
||||||
emulator?: {
|
|
||||||
host?: string
|
|
||||||
port?: number
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function FirestoreAdapter({
|
export type FirebaseSession = Session & {
|
||||||
emulator,
|
id: string
|
||||||
...firebaseOptions
|
expires: Date
|
||||||
}: 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 {
|
return {
|
||||||
async createUser(newUser) {
|
async getAdapter({ session, secret, ...appOptions }) {
|
||||||
const userRef = await addDoc(Users, newUser)
|
const sessionMaxAge = session.maxAge * 1000 // default is 30 days
|
||||||
const userSnapshot = await getDoc(userRef)
|
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")
|
||||||
|
|
||||||
if (userSnapshot.exists() && Users.converter) {
|
return {
|
||||||
return Users.converter.fromFirestore(userSnapshot)
|
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
|
||||||
|
},
|
||||||
|
|
||||||
throw new Error("[createUser] Failed to create user")
|
async getUser(id) {
|
||||||
},
|
const snapshot = await client.collection("users").doc(id).get()
|
||||||
async getUser(id) {
|
const user = docSnapshotToObject(snapshot)
|
||||||
const userSnapshot = await getDoc(doc(Users, id))
|
return user
|
||||||
|
},
|
||||||
|
|
||||||
if (userSnapshot.exists() && Users.converter) {
|
async getUserByEmail(email) {
|
||||||
return Users.converter.fromFirestore(userSnapshot)
|
if (!email) return null
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
const snapshot = await client
|
||||||
},
|
.collection("users")
|
||||||
async getUserByEmail(email) {
|
.where("email", "==", email)
|
||||||
const userQuery = query(Users, where("email", "==", email), limit(1))
|
.limit(1)
|
||||||
const userSnapshots = await getDocs(userQuery)
|
.get()
|
||||||
const userSnpashot = userSnapshots.docs[0]
|
|
||||||
|
|
||||||
if (userSnpashot?.exists() && Users.converter) {
|
const user = querySnapshotToObject(snapshot)
|
||||||
return Users.converter.fromFirestore(userSnpashot)
|
return user
|
||||||
}
|
},
|
||||||
|
|
||||||
return null
|
async getUserByProviderAccountId(providerId, providerAccountId) {
|
||||||
},
|
const accountSnapshot = await client
|
||||||
async getUserByAccount({ provider, providerAccountId }) {
|
.collection("accounts")
|
||||||
const accountQuery = query(
|
.where("providerId", "==", providerId)
|
||||||
Accounts,
|
.where("providerAccountId", "==", providerAccountId)
|
||||||
where("provider", "==", provider),
|
.limit(1)
|
||||||
where("providerAccountId", "==", providerAccountId),
|
.get()
|
||||||
limit(1)
|
|
||||||
)
|
|
||||||
const accountSnapshots = await getDocs(accountQuery)
|
|
||||||
const accountSnapshot = accountSnapshots.docs[0]
|
|
||||||
|
|
||||||
if (accountSnapshot?.exists()) {
|
if (accountSnapshot.empty) return null
|
||||||
const { userId } = accountSnapshot.data()
|
|
||||||
const userDoc = await getDoc(doc(Users, userId))
|
|
||||||
|
|
||||||
if (userDoc.exists() && Users.converter) {
|
const userId = accountSnapshot.docs[0].data().userId
|
||||||
return Users.converter.fromFirestore(userDoc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
const userSnapshot = await client
|
||||||
},
|
.collection("users")
|
||||||
|
.doc(userId)
|
||||||
|
.get()
|
||||||
|
|
||||||
async updateUser(partialUser) {
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
||||||
const userRef = doc(Users, partialUser.id)
|
return { ...userSnapshot.data(), id: userSnapshot.id } as any
|
||||||
|
},
|
||||||
|
|
||||||
await setDoc(userRef, partialUser, { merge: true })
|
async updateUser(user) {
|
||||||
|
await client
|
||||||
|
.collection("users")
|
||||||
|
.doc(user.id)
|
||||||
|
.update(stripUndefined(user))
|
||||||
|
|
||||||
const userSnapshot = await getDoc(userRef)
|
return user
|
||||||
|
},
|
||||||
|
|
||||||
if (userSnapshot.exists() && Users.converter) {
|
async deleteUser(userId) {
|
||||||
return Users.converter.fromFirestore(userSnapshot)
|
await client.collection("users").doc(userId).delete()
|
||||||
}
|
},
|
||||||
|
|
||||||
throw new Error("[updateUser] Failed to update user")
|
async linkAccount(
|
||||||
},
|
userId,
|
||||||
|
providerId,
|
||||||
|
providerType,
|
||||||
|
providerAccountId,
|
||||||
|
refreshToken,
|
||||||
|
accessToken,
|
||||||
|
accessTokenExpires
|
||||||
|
) {
|
||||||
|
const accountRef = await client.collection("accounts").add(
|
||||||
|
stripUndefined({
|
||||||
|
userId,
|
||||||
|
providerId,
|
||||||
|
providerType,
|
||||||
|
providerAccountId,
|
||||||
|
refreshToken,
|
||||||
|
accessToken,
|
||||||
|
accessTokenExpires,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
async deleteUser(userId) {
|
const accountSnapshot = await accountRef.get()
|
||||||
const userRef = doc(Users, userId)
|
const account = docSnapshotToObject(accountSnapshot)
|
||||||
const accountsQuery = query(Accounts, where("userId", "==", userId))
|
return account
|
||||||
const sessionsQuery = query(Sessions, where("userId", "==", userId))
|
},
|
||||||
|
|
||||||
// TODO: May be better to use events instead of transactions?
|
async unlinkAccount(userId, providerId, providerAccountId) {
|
||||||
await runTransaction(db, async (transaction) => {
|
const snapshot = await client
|
||||||
const accounts = await getDocs(accountsQuery)
|
.collection("accounts")
|
||||||
const sessions = await getDocs(sessionsQuery)
|
.where("userId", "==", userId)
|
||||||
|
.where("providerId", "==", providerId)
|
||||||
|
.where("providerAccountId", "==", providerAccountId)
|
||||||
|
.limit(1)
|
||||||
|
.get()
|
||||||
|
|
||||||
transaction.delete(userRef)
|
const accountId = snapshot.docs[0].id
|
||||||
accounts.forEach((account) => transaction.delete(account.ref))
|
|
||||||
sessions.forEach((session) => transaction.delete(session.ref))
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
async linkAccount(account) {
|
await client.collection("accounts").doc(accountId).delete()
|
||||||
const accountRef = await addDoc(Accounts, account)
|
},
|
||||||
const accountSnapshot = await getDoc(accountRef)
|
|
||||||
|
|
||||||
if (accountSnapshot.exists() && Accounts.converter) {
|
async createSession(user) {
|
||||||
return Accounts.converter.fromFirestore(accountSnapshot)
|
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 unlinkAccount({ provider, providerAccountId }) {
|
async getSession(sessionToken) {
|
||||||
const accountQuery = query(
|
const snapshot = await client
|
||||||
Accounts,
|
.collection("sessions")
|
||||||
where("provider", "==", provider),
|
.where("sessionToken", "==", sessionToken)
|
||||||
where("providerAccountId", "==", providerAccountId),
|
.limit(1)
|
||||||
limit(1)
|
.get()
|
||||||
)
|
|
||||||
const accountSnapshots = await getDocs(accountQuery)
|
|
||||||
const accountSnapshot = accountSnapshots.docs[0]
|
|
||||||
|
|
||||||
if (accountSnapshot?.exists()) {
|
const session = querySnapshotToObject<FirebaseSession>(snapshot)
|
||||||
await deleteDoc(accountSnapshot.ref)
|
if (!session) return null
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async createSession(session) {
|
// if the session has expired
|
||||||
const sessionRef = await addDoc(Sessions, session)
|
if (session.expires < new Date()) {
|
||||||
const sessionSnapshot = await getDoc(sessionRef)
|
// delete the session
|
||||||
|
await client.collection("sessions").doc(session.id).delete()
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
// return already existing session
|
||||||
|
return session
|
||||||
|
},
|
||||||
|
|
||||||
if (sessionSnapshot.exists() && Sessions.converter) {
|
async updateSession(session, force) {
|
||||||
return Sessions.converter.fromFirestore(sessionSnapshot)
|
if (
|
||||||
}
|
!force &&
|
||||||
|
Number(session.expires) - sessionMaxAge + sessionUpdateAge >
|
||||||
|
Date.now()
|
||||||
|
) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
throw new Error("[createSession] Failed to create session")
|
// Update the item in the database
|
||||||
},
|
await client
|
||||||
|
.collection("sessions")
|
||||||
async getSessionAndUser(sessionToken) {
|
.doc(session.id)
|
||||||
const sessionQuery = query(
|
.update({
|
||||||
Sessions,
|
expires: new Date(Date.now() + sessionMaxAge),
|
||||||
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 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()
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
40
packages/adapter-firebase/src/utils.ts
Normal file
40
packages/adapter-firebase/src/utils.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
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")
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,77 +1,67 @@
|
|||||||
import { runBasicTests } from "@next-auth/adapter-test"
|
import { runBasicTests } from "@next-auth/adapter-test"
|
||||||
import { FirestoreAdapter } from "../src"
|
import { FirebaseAdapter } from "../src"
|
||||||
|
import { docSnapshotToObject, querySnapshotToObject } from "../src/utils"
|
||||||
|
|
||||||
import { getFirestore, connectFirestoreEmulator, terminate, collection, query, where, limit, getDocs, getDoc, doc } from "firebase/firestore"
|
import firebase from "firebase/app"
|
||||||
import { initializeApp } from "firebase/app";
|
import "firebase/firestore"
|
||||||
import { getConverter } from "../src/converter";
|
|
||||||
import type { AdapterSession, AdapterUser, VerificationToken } from "next-auth/adapters";
|
|
||||||
import type { Account } from "next-auth";
|
|
||||||
|
|
||||||
const app = initializeApp({ projectId: "next-auth-test" });
|
const firestore = (
|
||||||
const firestore = getFirestore(app);
|
firebase.apps[0] ?? firebase.initializeApp({ projectId: "next-auth-test" })
|
||||||
|
).firestore()
|
||||||
connectFirestoreEmulator(firestore, 'localhost', 8080);
|
firestore.useEmulator("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({
|
runBasicTests({
|
||||||
adapter: FirestoreAdapter({ projectId: "next-auth-test" }),
|
adapter: FirebaseAdapter(firestore),
|
||||||
db: {
|
db: {
|
||||||
async disconnect() {
|
async disconnect() {
|
||||||
await terminate(firestore);
|
await firestore.terminate()
|
||||||
},
|
},
|
||||||
async session(sessionToken) {
|
async session(sessionToken) {
|
||||||
const snapshotQuery = query(Sessions, where("sessionToken", "==", sessionToken), limit(1));
|
const snapshot = await firestore
|
||||||
const snapshots = await getDocs(snapshotQuery);
|
.collection("sessions")
|
||||||
const snapshot = snapshots.docs[0];
|
.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()
|
||||||
|
|
||||||
if (snapshot?.exists() && Sessions.converter) {
|
if (snapshot.empty) {
|
||||||
const session = Sessions.converter.fromFirestore(snapshot);
|
console.error(sessionToken, expires)
|
||||||
|
throw new Error("Could not expire session")
|
||||||
return session;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return await firestore
|
||||||
|
.collection("sessions")
|
||||||
|
.doc(snapshot.docs[0].id)
|
||||||
|
.update({ expires })
|
||||||
},
|
},
|
||||||
async user(id) {
|
async user(id) {
|
||||||
const snapshot = await getDoc(doc(Users, id));
|
const snapshot = await firestore.collection("users").doc(id).get()
|
||||||
|
return docSnapshotToObject(snapshot)
|
||||||
if (snapshot?.exists() && Users.converter) {
|
|
||||||
const user = Users.converter.fromFirestore(snapshot);
|
|
||||||
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
},
|
},
|
||||||
async account({ provider, providerAccountId }) {
|
async account(providerId, providerAccountId) {
|
||||||
const snapshotQuery = query(Accounts, where("provider", "==", provider), where("providerAccountId", "==", providerAccountId), limit(1));
|
const snapshot = await firestore
|
||||||
const snapshots = await getDocs(snapshotQuery);
|
.collection("accounts")
|
||||||
const snapshot = snapshots.docs[0];
|
.where("providerId", "==", providerId)
|
||||||
|
.where("providerAccountId", "==", providerAccountId)
|
||||||
if (snapshot?.exists() && Accounts.converter) {
|
.limit(1)
|
||||||
const account = Accounts.converter.fromFirestore(snapshot);
|
.get()
|
||||||
|
return querySnapshotToObject(snapshot)
|
||||||
return account;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
},
|
},
|
||||||
async verificationToken({ identifier, token }) {
|
async verificationRequest(identifier, token) {
|
||||||
const snapshotQuery = query(VerificationTokens, where("identifier", "==", identifier), where("token", "==", token), limit(1));
|
const snapshot = await firestore
|
||||||
const snapshots = await getDocs(snapshotQuery);
|
.collection("verificationRequests")
|
||||||
const snapshot = snapshots.docs[0];
|
.where("identifier", "==", identifier)
|
||||||
|
.where("token", "==", token)
|
||||||
if (snapshot?.exists() && VerificationTokens.converter) {
|
.limit(1)
|
||||||
const verificationToken = VerificationTokens.converter.fromFirestore(snapshot);
|
.get()
|
||||||
|
return querySnapshotToObject(snapshot)
|
||||||
return verificationToken;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
{
|
{
|
||||||
"extends": "@next-auth/tsconfig/adapters.json",
|
"extends": "@next-auth/tsconfig/base.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"rootDir": "src",
|
"rootDir": "src",
|
||||||
"outDir": "dist",
|
"outDir": "dist"
|
||||||
"strict": true,
|
|
||||||
"noUncheckedIndexedAccess": true
|
|
||||||
},
|
},
|
||||||
"exclude": ["tests", "dist", "jest.config.js"]
|
"exclude": ["tests", "dist", "jest.config.js"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,21 +33,17 @@
|
|||||||
],
|
],
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@mikro-orm/core": "^5.0.2",
|
"@mikro-orm/core": "^5.0.2",
|
||||||
"next-auth": "^4"
|
"next-auth": "workspace:*"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@mikro-orm/core": "^5.0.2",
|
"@mikro-orm/core": "^5.0.2",
|
||||||
"@mikro-orm/sqlite": "^5.0.2",
|
"@mikro-orm/sqlite": "^5.0.2",
|
||||||
"@next-auth/adapter-test": "workspace:^0.0.0",
|
"@next-auth/adapter-test": "workspace:^0.0.0",
|
||||||
"@next-auth/tsconfig": "workspace:^0.0.0",
|
"@next-auth/tsconfig": "workspace:^0.0.0",
|
||||||
"@types/uuid": "^8.3.3",
|
|
||||||
"jest": "^27.4.3",
|
"jest": "^27.4.3",
|
||||||
"next-auth": "workspace:*"
|
"next-auth": "workspace:*"
|
||||||
},
|
},
|
||||||
"jest": {
|
"jest": {
|
||||||
"preset": "@next-auth/adapter-test/jest"
|
"preset": "@next-auth/adapter-test/jest"
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"uuid": "^8.3.2"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@next-auth/sequelize-adapter",
|
"name": "@next-auth/sequelize-adapter",
|
||||||
"version": "1.0.5",
|
"version": "1.0.4",
|
||||||
"description": "Sequelize adapter for next-auth.",
|
"description": "Sequelize adapter for next-auth.",
|
||||||
"homepage": "https://next-auth.js.org",
|
"homepage": "https://next-auth.js.org",
|
||||||
"repository": "https://github.com/nextauthjs/next-auth",
|
"repository": "https://github.com/nextauthjs/next-auth",
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
"dist"
|
"dist"
|
||||||
],
|
],
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"next-auth": "^4",
|
"next-auth": "workspace:*",
|
||||||
"sequelize": "^6.6.5"
|
"sequelize": "^6.6.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -1,13 +1,7 @@
|
|||||||
const swcConfig = {
|
|
||||||
jsc: {
|
|
||||||
parser: { syntax: "typescript", decorators: true },
|
|
||||||
transform: { legacyDecorator: true, decoratorMetadata: true },
|
|
||||||
},
|
|
||||||
}
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
transform: {
|
transform: {
|
||||||
".(ts|tsx)$": ["@swc/jest", swcConfig],
|
".(ts|tsx)$": "@swc/jest",
|
||||||
".(js|jsx)$": ["@swc/jest", swcConfig],
|
".(js|jsx)$": "@swc/jest", // jest's default
|
||||||
},
|
},
|
||||||
transformIgnorePatterns: ["[/\\\\]node_modules[/\\\\].+\\.(js|jsx)$"],
|
transformIgnorePatterns: ["[/\\\\]node_modules[/\\\\].+\\.(js|jsx)$"],
|
||||||
moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"],
|
moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"],
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@next-auth/typeorm-legacy-adapter",
|
"name": "@next-auth/typeorm-legacy-adapter",
|
||||||
"version": "2.0.0",
|
"version": "1.0.3",
|
||||||
"description": "TypeORM (legacy) adapter for next-auth.",
|
"description": "TypeORM (legacy) adapter for next-auth.",
|
||||||
"homepage": "https://next-auth.js.org",
|
"homepage": "https://next-auth.js.org",
|
||||||
"repository": "https://github.com/nextauthjs/next-auth",
|
"repository": "https://github.com/nextauthjs/next-auth",
|
||||||
@@ -37,25 +37,24 @@
|
|||||||
"sqlite": "tests/sqlite/test.sh"
|
"sqlite": "tests/sqlite/test.sh"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@next-auth/adapter-test": "workspace:*",
|
"@next-auth/adapter-test": "workspace:^0.0.0",
|
||||||
"@next-auth/tsconfig": "workspace:*",
|
"@next-auth/tsconfig": "workspace:^0.0.0",
|
||||||
"jest": "^27.4.3",
|
"jest": "^27.4.3",
|
||||||
"mssql": "^7.2.1",
|
"mssql": "^7.2.1",
|
||||||
"mysql": "^2.18.1",
|
"mysql": "^2.18.1",
|
||||||
"next-auth": "workspace:*",
|
"next-auth": "workspace:*",
|
||||||
"pg": "^8.7.3",
|
"pg": "^8.7.1",
|
||||||
"sqlite3": "^5.0.8",
|
"sqlite3": "^5.0.2",
|
||||||
"typeorm": "0.3.7",
|
"typeorm": "^0.2.37",
|
||||||
"typeorm-naming-strategies": "^4.1.0",
|
"typeorm-naming-strategies": "^2.0.0"
|
||||||
"typescript": "^4.7.4"
|
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"mssql": "^6.2.1 || 7",
|
"mssql": "^6.2.1 || 7",
|
||||||
"mysql": "^2.18.1",
|
"mysql": "^2.18.1",
|
||||||
"next-auth": "^4",
|
"next-auth": "workspace:*",
|
||||||
"pg": "^8.2.1",
|
"pg": "^8.2.1",
|
||||||
"sqlite3": "^5.0.2",
|
"sqlite3": "^5.0.2",
|
||||||
"typeorm": "0.3.7"
|
"typeorm": "^0.2.31"
|
||||||
},
|
},
|
||||||
"peerDependenciesMeta": {
|
"peerDependenciesMeta": {
|
||||||
"mysql": {
|
"mysql": {
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
import type { Adapter, AdapterSession, AdapterUser } from "next-auth/adapters"
|
import { Adapter, AdapterSession, AdapterUser } from "next-auth/adapters"
|
||||||
import { DataSourceOptions, DataSource, EntityManager } from "typeorm"
|
import {
|
||||||
import type { Account } from "next-auth"
|
Connection,
|
||||||
|
ConnectionOptions,
|
||||||
|
EntityManager,
|
||||||
|
getConnectionManager,
|
||||||
|
} from "typeorm"
|
||||||
|
import { Account } from "next-auth"
|
||||||
import * as defaultEntities from "./entities"
|
import * as defaultEntities from "./entities"
|
||||||
import { parseDataSourceConfig, updateConnectionEntities } from "./utils"
|
import { parseConnectionConfig, updateConnectionEntities } from "./utils"
|
||||||
|
|
||||||
export const entities = defaultEntities
|
export const entities = defaultEntities
|
||||||
|
|
||||||
@@ -12,40 +17,43 @@ export interface TypeORMLegacyAdapterOptions {
|
|||||||
entities?: Entities
|
entities?: Entities
|
||||||
}
|
}
|
||||||
|
|
||||||
let _dataSource: DataSource | undefined
|
let _connection: Connection
|
||||||
|
|
||||||
export async function getManager(options: {
|
export async function getManager(options: {
|
||||||
dataSource: string | DataSourceOptions
|
connection: string | ConnectionOptions
|
||||||
entities: Entities
|
entities: Entities
|
||||||
}): Promise<EntityManager> {
|
}): Promise<EntityManager> {
|
||||||
const { dataSource, entities } = options
|
const { connection, entities } = options
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
...parseDataSourceConfig(dataSource),
|
...parseConnectionConfig(connection),
|
||||||
entities: Object.values(entities),
|
entities: Object.values(entities),
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_dataSource) _dataSource = new DataSource(config)
|
const connectionManager = getConnectionManager()
|
||||||
|
|
||||||
const manager = _dataSource?.manager
|
if (connectionManager.has(config.name ?? "default")) {
|
||||||
|
_connection = connectionManager.get(config.name ?? "default")
|
||||||
|
|
||||||
if (!manager.connection.isInitialized) {
|
if (_connection.isConnected) return _connection.manager
|
||||||
await manager.connection.initialize()
|
|
||||||
|
if (process.env.NODE_ENV !== "production") {
|
||||||
|
await updateConnectionEntities(_connection, config.entities)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_connection = await connectionManager.create(config).connect()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== "production") {
|
return _connection.manager
|
||||||
await updateConnectionEntities(_dataSource, config.entities)
|
|
||||||
}
|
|
||||||
return manager
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function TypeORMLegacyAdapter(
|
export function TypeORMLegacyAdapter(
|
||||||
dataSource: string | DataSourceOptions,
|
connection: string | ConnectionOptions,
|
||||||
options?: TypeORMLegacyAdapterOptions
|
options?: TypeORMLegacyAdapterOptions
|
||||||
): Adapter {
|
): Adapter {
|
||||||
const entities = options?.entities
|
const entities = options?.entities
|
||||||
const c = {
|
const c = {
|
||||||
dataSource,
|
connection,
|
||||||
entities: {
|
entities: {
|
||||||
UserEntity: entities?.UserEntity ?? defaultEntities.UserEntity,
|
UserEntity: entities?.UserEntity ?? defaultEntities.UserEntity,
|
||||||
SessionEntity: entities?.SessionEntity ?? defaultEntities.SessionEntity,
|
SessionEntity: entities?.SessionEntity ?? defaultEntities.SessionEntity,
|
||||||
@@ -74,14 +82,14 @@ export function TypeORMLegacyAdapter(
|
|||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
async getUser(id) {
|
async getUser(id) {
|
||||||
const m = await getManager(c)
|
const m = await getManager(c)
|
||||||
const user = await m.findOne("UserEntity", { where: { id } })
|
const user = await m.findOne("UserEntity", { id })
|
||||||
if (!user) return null
|
if (!user) return null
|
||||||
return { ...user }
|
return { ...user }
|
||||||
},
|
},
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
async getUserByEmail(email) {
|
async getUserByEmail(email) {
|
||||||
const m = await getManager(c)
|
const m = await getManager(c)
|
||||||
const user = await m.findOne("UserEntity", { where: { email } })
|
const user = await m.findOne("UserEntity", { email })
|
||||||
if (!user) return null
|
if (!user) return null
|
||||||
return { ...user }
|
return { ...user }
|
||||||
},
|
},
|
||||||
@@ -89,7 +97,8 @@ export function TypeORMLegacyAdapter(
|
|||||||
const m = await getManager(c)
|
const m = await getManager(c)
|
||||||
const account = await m.findOne<Account & { user: AdapterUser }>(
|
const account = await m.findOne<Account & { user: AdapterUser }>(
|
||||||
"AccountEntity",
|
"AccountEntity",
|
||||||
{ where: provider_providerAccountId, relations: ["user"] }
|
provider_providerAccountId,
|
||||||
|
{ relations: ["user"] }
|
||||||
)
|
)
|
||||||
if (!account) return null
|
if (!account) return null
|
||||||
return account.user ?? null
|
return account.user ?? null
|
||||||
@@ -127,7 +136,7 @@ export function TypeORMLegacyAdapter(
|
|||||||
const m = await getManager(c)
|
const m = await getManager(c)
|
||||||
const sessionAndUser = await m.findOne<
|
const sessionAndUser = await m.findOne<
|
||||||
AdapterSession & { user: AdapterUser }
|
AdapterSession & { user: AdapterUser }
|
||||||
>("SessionEntity", { where: { sessionToken }, relations: ["user"] })
|
>("SessionEntity", { sessionToken }, { relations: ["user"] })
|
||||||
|
|
||||||
if (!sessionAndUser) return null
|
if (!sessionAndUser) return null
|
||||||
const { user, ...session } = sessionAndUser
|
const { user, ...session } = sessionAndUser
|
||||||
@@ -153,9 +162,10 @@ export function TypeORMLegacyAdapter(
|
|||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
async useVerificationToken(identifier_token) {
|
async useVerificationToken(identifier_token) {
|
||||||
const m = await getManager(c)
|
const m = await getManager(c)
|
||||||
const verificationToken = await m.findOne("VerificationTokenEntity", {
|
const verificationToken = await m.findOne(
|
||||||
where: identifier_token,
|
"VerificationTokenEntity",
|
||||||
})
|
identifier_token
|
||||||
|
)
|
||||||
if (!verificationToken) {
|
if (!verificationToken) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import type { DataSource, DataSourceOptions } from "typeorm"
|
import { Connection, ConnectionOptions } from "typeorm"
|
||||||
import * as defaultEntities from "./entities"
|
import * as defaultEntities from "./entities"
|
||||||
|
|
||||||
/** Ensure configOrString is normalized to an object. */
|
/** Ensure configOrString is normalized to an object. */
|
||||||
export function parseDataSourceConfig(
|
export function parseConnectionConfig(
|
||||||
configOrString: string | DataSourceOptions
|
configOrString: string | ConnectionOptions
|
||||||
): DataSourceOptions {
|
): ConnectionOptions {
|
||||||
if (typeof configOrString !== "string") {
|
if (typeof configOrString !== "string") {
|
||||||
return {
|
return {
|
||||||
...configOrString,
|
...configOrString,
|
||||||
@@ -89,22 +89,22 @@ function entitiesChanged(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function updateConnectionEntities(
|
export async function updateConnectionEntities(
|
||||||
dataSource: DataSource,
|
connection: Connection,
|
||||||
entities: any[]
|
entities: any[]
|
||||||
) {
|
) {
|
||||||
if (!entitiesChanged(dataSource.entityMetadatas, entities)) return
|
if (!entitiesChanged(connection.options.entities, entities)) return
|
||||||
|
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
dataSource.entityMetadatas = entities
|
connection.options.entities = entities
|
||||||
|
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
dataSource.buildMetadatas()
|
connection.buildMetadatas()
|
||||||
|
|
||||||
if (dataSource.options.synchronize !== false) {
|
if (connection.options.synchronize !== false) {
|
||||||
console.warn(
|
console.warn(
|
||||||
"[next-auth][warn][adapter_typeorm_updating_entities]",
|
"[next-auth][warn][adapter_typeorm_updating_entities]",
|
||||||
"\nhttps://next-auth.js.org/warnings#adapter_typeorm_updating_entities"
|
"\nhttps://next-auth.js.org/warnings#adapter_typeorm_updating_entities"
|
||||||
)
|
)
|
||||||
await dataSource.synchronize()
|
await connection.synchronize()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,49 +1,46 @@
|
|||||||
import { DataSource } from "typeorm"
|
import { ConnectionManager, ConnectionOptions } from "typeorm"
|
||||||
import type { DataSourceOptions } from "typeorm"
|
import { TestOptions } from "@next-auth/adapter-test"
|
||||||
import type { TestOptions } from "@next-auth/adapter-test"
|
|
||||||
import * as defaultEntities from "../src/entities"
|
import * as defaultEntities from "../src/entities"
|
||||||
import { parseDataSourceConfig } from "../src/utils"
|
import { parseConnectionConfig } from "../src/utils"
|
||||||
|
|
||||||
export { defaultEntities }
|
export { defaultEntities }
|
||||||
|
|
||||||
console.warn = jest.fn()
|
|
||||||
|
|
||||||
/** Set up Test Database */
|
/** Set up Test Database */
|
||||||
export function db(
|
export function db(
|
||||||
config: string | DataSourceOptions,
|
config: string | ConnectionOptions,
|
||||||
entities: typeof defaultEntities = defaultEntities
|
entities: typeof defaultEntities = defaultEntities
|
||||||
): TestOptions["db"] {
|
): TestOptions["db"] {
|
||||||
const connection = new DataSource({
|
const connection = new ConnectionManager().create({
|
||||||
...parseDataSourceConfig(config),
|
...parseConnectionConfig(config),
|
||||||
entities: Object.values(entities),
|
entities: Object.values(entities),
|
||||||
}).manager.connection
|
})
|
||||||
|
|
||||||
const m = connection.manager
|
const m = connection.manager
|
||||||
return {
|
return {
|
||||||
connect: async () => await connection.initialize(),
|
connect: async () => await connection.connect(),
|
||||||
disconnect: async () => await connection.destroy(),
|
disconnect: async () => await connection.close(),
|
||||||
async user(id) {
|
async user(id) {
|
||||||
const user = await m.findOne(entities.UserEntity, { where: { id } })
|
const user = await m.findOne(entities.UserEntity, id)
|
||||||
return user ?? null
|
return user ?? null
|
||||||
},
|
},
|
||||||
async account(provider_providerAccountId) {
|
async account(provider_providerAccountId) {
|
||||||
const account = await m.findOne(entities.AccountEntity, {
|
const account = await m.findOne(
|
||||||
where: provider_providerAccountId,
|
entities.AccountEntity,
|
||||||
})
|
provider_providerAccountId
|
||||||
|
)
|
||||||
return account ?? null
|
return account ?? null
|
||||||
},
|
},
|
||||||
async session(sessionToken) {
|
async session(sessionToken) {
|
||||||
const session = await m.findOne(entities.SessionEntity, {
|
const session = await m.findOne(entities.SessionEntity, { sessionToken })
|
||||||
where: { sessionToken },
|
|
||||||
})
|
|
||||||
return session ?? null
|
return session ?? null
|
||||||
},
|
},
|
||||||
async verificationToken(token_identifier) {
|
async verificationToken(token_identifier) {
|
||||||
const verificationToken = await m.findOne(
|
const verificationToken = await m.findOne(
|
||||||
entities.VerificationTokenEntity,
|
entities.VerificationTokenEntity,
|
||||||
{ where: token_identifier }
|
token_identifier
|
||||||
)
|
)
|
||||||
if (!verificationToken) return null
|
if (!verificationToken) return null
|
||||||
|
// @ts-expect-error
|
||||||
const { id: _, ...rest } = verificationToken
|
const { id: _, ...rest } = verificationToken
|
||||||
return rest
|
return rest
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import { parseDataSourceConfig } from "../src/utils"
|
// eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error
|
||||||
|
// @ts-ignore
|
||||||
|
import { parseConnectionString } from "../src/lib/config"
|
||||||
|
|
||||||
const connectionString = "mysql://root:password@localhost:3306/next-auth"
|
const connectionString = "mysql://root:password@localhost:3306/next-auth"
|
||||||
|
|
||||||
test("could parse connection string", () => {
|
test("could parse connection string", () => {
|
||||||
expect(parseDataSourceConfig(connectionString)).toEqual(
|
expect(parseConnectionString(connectionString)).toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
type: "mysql",
|
type: "mysql",
|
||||||
host: "localhost",
|
host: "localhost",
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ import { TypeORMLegacyAdapter } from "../../src"
|
|||||||
import { db } from "../helpers"
|
import { db } from "../helpers"
|
||||||
import { SnakeNamingStrategy } from "typeorm-naming-strategies"
|
import { SnakeNamingStrategy } from "typeorm-naming-strategies"
|
||||||
|
|
||||||
import type { DataSourceOptions } from "typeorm"
|
import type { ConnectionOptions } from "typeorm"
|
||||||
|
|
||||||
const sqliteConfig: DataSourceOptions = {
|
const sqliteConfig: ConnectionOptions = {
|
||||||
type: "sqlite" as const,
|
type: "sqlite" as const,
|
||||||
name: "next-auth-test-memory",
|
name: "next-auth-test-memory",
|
||||||
database: "./tests/sqlite/dev.db",
|
database: "./tests/sqlite/dev.db",
|
||||||
|
|||||||
@@ -166,10 +166,6 @@ export default function App({
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Security
|
|
||||||
|
|
||||||
If you think you have found a vulnerability (or not sure) in NextAuth.js or any of the related packages (i.e. Adapters), we ask you to have a read of our [Security Policy](https://github.com/nextauthjs/next-auth/blob/main/SECURITY.md) to reach out responsibly. Please do not open Pull Requests/Issues/Discussions before consulting with us.
|
|
||||||
|
|
||||||
## Acknowledgments
|
## Acknowledgments
|
||||||
|
|
||||||
[NextAuth.js is made possible thanks to all of its contributors.](https://next-auth.js.org/contributors)
|
[NextAuth.js is made possible thanks to all of its contributors.](https://next-auth.js.org/contributors)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "next-auth",
|
"name": "next-auth",
|
||||||
"version": "4.10.0",
|
"version": "4.7.0",
|
||||||
"description": "Authentication for Next.js",
|
"description": "Authentication for Next.js",
|
||||||
"homepage": "https://next-auth.js.org",
|
"homepage": "https://next-auth.js.org",
|
||||||
"repository": "https://github.com/nextauthjs/next-auth.git",
|
"repository": "https://github.com/nextauthjs/next-auth.git",
|
||||||
@@ -107,8 +107,8 @@
|
|||||||
"@types/node": "^17.0.42",
|
"@types/node": "^17.0.42",
|
||||||
"@types/nodemailer": "^6.4.4",
|
"@types/nodemailer": "^6.4.4",
|
||||||
"@types/oauth": "^0.9.1",
|
"@types/oauth": "^0.9.1",
|
||||||
"@types/react": "^18.0.15",
|
"@types/react": "^18.0.2",
|
||||||
"@types/react-dom": "^18.0.6",
|
"@types/react-dom": "^18.0.5",
|
||||||
"autoprefixer": "^10.4.7",
|
"autoprefixer": "^10.4.7",
|
||||||
"babel-plugin-jsx-pragmatic": "^1.0.2",
|
"babel-plugin-jsx-pragmatic": "^1.0.2",
|
||||||
"babel-preset-preact": "^2.0.0",
|
"babel-preset-preact": "^2.0.0",
|
||||||
@@ -117,7 +117,7 @@
|
|||||||
"jest-environment-jsdom": "^28.1.1",
|
"jest-environment-jsdom": "^28.1.1",
|
||||||
"jest-watch-typeahead": "^1.1.0",
|
"jest-watch-typeahead": "^1.1.0",
|
||||||
"msw": "^0.42.3",
|
"msw": "^0.42.3",
|
||||||
"next": "12.2.0",
|
"next": "12.1.7-canary.51",
|
||||||
"postcss": "^8.4.14",
|
"postcss": "^8.4.14",
|
||||||
"postcss-cli": "^9.1.0",
|
"postcss-cli": "^9.1.0",
|
||||||
"postcss-nested": "^5.0.6",
|
"postcss-nested": "^5.0.6",
|
||||||
@@ -138,4 +138,4 @@
|
|||||||
"**/tests",
|
"**/tests",
|
||||||
"**/__tests__"
|
"**/__tests__"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -9,7 +9,6 @@ import { SessionStore } from "./lib/cookie"
|
|||||||
import type { NextAuthAction, NextAuthOptions } from "./types"
|
import type { NextAuthAction, NextAuthOptions } from "./types"
|
||||||
import type { Cookie } from "./lib/cookie"
|
import type { Cookie } from "./lib/cookie"
|
||||||
import type { ErrorType } from "./pages/error"
|
import type { ErrorType } from "./pages/error"
|
||||||
import { parse as parseCookie } from "cookie"
|
|
||||||
|
|
||||||
export interface RequestInternal {
|
export interface RequestInternal {
|
||||||
/** @default "http://localhost:3000" */
|
/** @default "http://localhost:3000" */
|
||||||
@@ -69,7 +68,7 @@ async function toInternalRequest(
|
|||||||
method: req.method,
|
method: req.method,
|
||||||
headers,
|
headers,
|
||||||
body: await getBody(req),
|
body: await getBody(req),
|
||||||
cookies: parseCookie(req.headers.get("cookie") ?? ""),
|
cookies: {},
|
||||||
providerId: nextauth[1],
|
providerId: nextauth[1],
|
||||||
error: url.searchParams.get("error") ?? nextauth[1],
|
error: url.searchParams.get("error") ?? nextauth[1],
|
||||||
host: detectHost(headers["x-forwarded-host"] ?? headers.host),
|
host: detectHost(headers["x-forwarded-host"] ?? headers.host),
|
||||||
|
|||||||
@@ -62,7 +62,6 @@ export async function init({
|
|||||||
colorScheme: "auto",
|
colorScheme: "auto",
|
||||||
logo: "",
|
logo: "",
|
||||||
brandColor: "",
|
brandColor: "",
|
||||||
buttonText: "",
|
|
||||||
},
|
},
|
||||||
// Custom options override defaults
|
// Custom options override defaults
|
||||||
...userOptions,
|
...userOptions,
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ export default async function email(
|
|||||||
identifier: string,
|
identifier: string,
|
||||||
options: InternalOptions<"email">
|
options: InternalOptions<"email">
|
||||||
) {
|
) {
|
||||||
const { url, adapter, provider, logger, callbackUrl, theme } = options
|
const { url, adapter, provider, logger, callbackUrl } = options
|
||||||
|
|
||||||
// Generate token
|
// Generate token
|
||||||
const token =
|
const token =
|
||||||
@@ -42,7 +42,6 @@ export default async function email(
|
|||||||
expires,
|
expires,
|
||||||
url: _url,
|
url: _url,
|
||||||
provider,
|
provider,
|
||||||
theme,
|
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("SEND_VERIFICATION_EMAIL_ERROR", {
|
logger.error("SEND_VERIFICATION_EMAIL_ERROR", {
|
||||||
|
|||||||
@@ -30,16 +30,12 @@ export default async function signin(params: {
|
|||||||
return { redirect: `${url}/error?error=OAuthSignin` }
|
return { redirect: `${url}/error?error=OAuthSignin` }
|
||||||
}
|
}
|
||||||
} else if (provider.type === "email") {
|
} else if (provider.type === "email") {
|
||||||
/**
|
// Note: Technically the part of the email address local mailbox element
|
||||||
* @note Technically the part of the email address local mailbox element
|
// (everything before the @ symbol) should be treated as 'case sensitive'
|
||||||
* (everything before the @ symbol) should be treated as 'case sensitive'
|
// according to RFC 2821, but in practice this causes more problems than
|
||||||
* according to RFC 2821, but in practice this causes more problems than
|
// it solves. We treat email addresses as all lower case. If anyone
|
||||||
* it solves. We treat email addresses as all lower case. If anyone
|
// complains about this we can make strict RFC 2821 compliance an option.
|
||||||
* complains about this we can make strict RFC 2821 compliance an option.
|
const email = body?.email?.toLowerCase() ?? null
|
||||||
*/
|
|
||||||
const email = body?.email?.toLowerCase()
|
|
||||||
|
|
||||||
if (!email) return { redirect: `${url}/error?error=EmailSignin` }
|
|
||||||
|
|
||||||
// Verified in `assertConfig`
|
// Verified in `assertConfig`
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
|
|||||||
@@ -214,10 +214,9 @@ export interface NextAuthOptions {
|
|||||||
* [Pages](https://next-auth.js.org/configuration/pages)
|
* [Pages](https://next-auth.js.org/configuration/pages)
|
||||||
*/
|
*/
|
||||||
export interface Theme {
|
export interface Theme {
|
||||||
colorScheme?: "auto" | "dark" | "light"
|
colorScheme: "auto" | "dark" | "light"
|
||||||
logo?: string
|
logo?: string
|
||||||
brandColor?: string
|
brandColor?: string
|
||||||
buttonText?: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -83,28 +83,20 @@ function NextAuth(
|
|||||||
|
|
||||||
export default NextAuth
|
export default NextAuth
|
||||||
|
|
||||||
let experimentalWarningShown = false
|
|
||||||
export async function unstable_getServerSession(
|
export async function unstable_getServerSession(
|
||||||
...args:
|
...args:
|
||||||
| [
|
| [GetServerSidePropsContext['req'], GetServerSidePropsContext['res'], NextAuthOptions]
|
||||||
GetServerSidePropsContext["req"],
|
|
||||||
GetServerSidePropsContext["res"],
|
|
||||||
NextAuthOptions
|
|
||||||
]
|
|
||||||
| [NextApiRequest, NextApiResponse, NextAuthOptions]
|
| [NextApiRequest, NextApiResponse, NextAuthOptions]
|
||||||
): Promise<Session | null> {
|
): Promise<Session | null> {
|
||||||
if (!experimentalWarningShown && process.env.NODE_ENV !== "production") {
|
console.warn(
|
||||||
console.warn(
|
"[next-auth][warn][EXPERIMENTAL_API]",
|
||||||
"[next-auth][warn][EXPERIMENTAL_API]",
|
"\n`unstable_getServerSession` is experimental and may be removed or changed in the future, as the name suggested.",
|
||||||
"\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/configuration/nextjs#unstable_getServerSession}`,
|
`\nhttps://next-auth.js.org/warnings#EXPERIMENTAL_API`
|
||||||
`\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
|
options.secret = options.secret ?? process.env.NEXTAUTH_SECRET
|
||||||
|
|
||||||
const session = await NextAuthHandler<Session | {}>({
|
const session = await NextAuthHandler<Session | {}>({
|
||||||
|
|||||||
@@ -84,12 +84,6 @@ export interface NextAuthMiddlewareOptions {
|
|||||||
*/
|
*/
|
||||||
authorized?: AuthorizedCallback
|
authorized?: AuthorizedCallback
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* The same `secret` used in the `NextAuth` configuration.
|
|
||||||
* Defaults to the `NEXTAUTH_SECRET` environment variable.
|
|
||||||
*/
|
|
||||||
secret?: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleMiddleware(
|
async function handleMiddleware(
|
||||||
@@ -108,8 +102,7 @@ async function handleMiddleware(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const secret = options?.secret ?? process.env.NEXTAUTH_SECRET
|
if (!process.env.NEXTAUTH_SECRET) {
|
||||||
if (!secret) {
|
|
||||||
console.error(
|
console.error(
|
||||||
`[next-auth][error][NO_SECRET]`,
|
`[next-auth][error][NO_SECRET]`,
|
||||||
`\nhttps://next-auth.js.org/errors#no_secret`
|
`\nhttps://next-auth.js.org/errors#no_secret`
|
||||||
@@ -125,7 +118,6 @@ async function handleMiddleware(
|
|||||||
req,
|
req,
|
||||||
decode: options?.jwt?.decode,
|
decode: options?.jwt?.decode,
|
||||||
cookieName: options?.cookies?.sessionToken?.name,
|
cookieName: options?.cookies?.sessionToken?.name,
|
||||||
secret,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const isAuthorized =
|
const isAuthorized =
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type { OAuthConfig, OAuthUserConfig } from "."
|
|||||||
|
|
||||||
export interface AzureADProfile extends Record<string, any> {
|
export interface AzureADProfile extends Record<string, any> {
|
||||||
sub: string
|
sub: string
|
||||||
nickname: string
|
nicname: string
|
||||||
email: string
|
email: string
|
||||||
picture: string
|
picture: string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
import type { OAuthConfig, OAuthUserConfig } from "./oauth"
|
|
||||||
|
|
||||||
export interface DuendeISUser extends Record<string, any> {
|
|
||||||
email: string
|
|
||||||
id: string
|
|
||||||
name: string
|
|
||||||
verified: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function DuendeIdentityServer6<P extends DuendeISUser>(
|
|
||||||
options: OAuthUserConfig<P>
|
|
||||||
): OAuthConfig<P> {
|
|
||||||
return {
|
|
||||||
id: "duende-identityserver6",
|
|
||||||
name: "DuendeIdentityServer6",
|
|
||||||
type: "oauth",
|
|
||||||
wellKnown: `${options.issuer}/.well-known/openid-configuration`,
|
|
||||||
authorization: { params: { scope: "openid profile email" } },
|
|
||||||
checks: ["pkce", "state"],
|
|
||||||
idToken: true,
|
|
||||||
profile(profile) {
|
|
||||||
return {
|
|
||||||
id: profile.sub,
|
|
||||||
name: profile.name,
|
|
||||||
email: profile.email,
|
|
||||||
image: null,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
options,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +1,13 @@
|
|||||||
import { createTransport } from "nodemailer"
|
import { createTransport } from "nodemailer"
|
||||||
|
|
||||||
import type { CommonProviderOptions } from "."
|
import type { CommonProviderOptions } from "."
|
||||||
import type { Options as SMTPTransportOptions } from "nodemailer/lib/smtp-transport"
|
import type { Options as SMTPConnectionOptions } from "nodemailer/lib/smtp-connection"
|
||||||
import type { Awaitable } from ".."
|
import type { Awaitable } from ".."
|
||||||
import type { Theme } from "../core/types"
|
|
||||||
|
|
||||||
export interface SendVerificationRequestParams {
|
|
||||||
identifier: string
|
|
||||||
url: string
|
|
||||||
expires: Date
|
|
||||||
provider: EmailConfig
|
|
||||||
token: string
|
|
||||||
theme: Theme
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface EmailConfig extends CommonProviderOptions {
|
export interface EmailConfig extends CommonProviderOptions {
|
||||||
type: "email"
|
type: "email"
|
||||||
// TODO: Make use of https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html
|
// TODO: Make use of https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html
|
||||||
server: string | SMTPTransportOptions
|
server: string | SMTPConnectionOptions
|
||||||
/** @default "NextAuth <no-reply@example.com>" */
|
/** @default "NextAuth <no-reply@example.com>" */
|
||||||
from?: string
|
from?: string
|
||||||
/**
|
/**
|
||||||
@@ -26,10 +16,13 @@ export interface EmailConfig extends CommonProviderOptions {
|
|||||||
* @default 86400
|
* @default 86400
|
||||||
*/
|
*/
|
||||||
maxAge?: number
|
maxAge?: number
|
||||||
/** [Documentation](https://next-auth.js.org/providers/email#customizing-emails) */
|
sendVerificationRequest: (params: {
|
||||||
sendVerificationRequest: (
|
identifier: string
|
||||||
params: SendVerificationRequestParams
|
url: string
|
||||||
) => Awaitable<void>
|
expires: Date
|
||||||
|
provider: EmailConfig
|
||||||
|
token: string
|
||||||
|
}) => Awaitable<void>
|
||||||
/**
|
/**
|
||||||
* By default, we are generating a random verification token.
|
* By default, we are generating a random verification token.
|
||||||
* You can make it predictable or modify it as you like with this method.
|
* You can make it predictable or modify it as you like with this method.
|
||||||
@@ -63,81 +56,78 @@ export default function Email(options: EmailUserConfig): EmailConfig {
|
|||||||
type: "email",
|
type: "email",
|
||||||
name: "Email",
|
name: "Email",
|
||||||
// Server can be an SMTP connection string or a nodemailer config object
|
// Server can be an SMTP connection string or a nodemailer config object
|
||||||
server: { host: "localhost", port: 25, auth: { user: "", pass: "" } },
|
server: {
|
||||||
|
host: "localhost",
|
||||||
|
port: 25,
|
||||||
|
auth: {
|
||||||
|
user: "",
|
||||||
|
pass: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
from: "NextAuth <no-reply@example.com>",
|
from: "NextAuth <no-reply@example.com>",
|
||||||
maxAge: 24 * 60 * 60,
|
maxAge: 24 * 60 * 60,
|
||||||
async sendVerificationRequest(params) {
|
async sendVerificationRequest({
|
||||||
const { identifier, url, provider, theme } = params
|
identifier: email,
|
||||||
|
url,
|
||||||
|
provider: { server, from },
|
||||||
|
}) {
|
||||||
const { host } = new URL(url)
|
const { host } = new URL(url)
|
||||||
const transport = createTransport(provider.server)
|
const transport = createTransport(server)
|
||||||
const result = await transport.sendMail({
|
await transport.sendMail({
|
||||||
to: identifier,
|
to: email,
|
||||||
from: provider.from,
|
from,
|
||||||
subject: `Sign in to ${host}`,
|
subject: `Sign in to ${host}`,
|
||||||
text: text({ url, host }),
|
text: text({ url, host }),
|
||||||
html: html({ url, host, theme }),
|
html: html({ url, host, email }),
|
||||||
})
|
})
|
||||||
const failed = result.rejected.concat(result.pending).filter(Boolean)
|
|
||||||
if (failed.length) {
|
|
||||||
throw new Error(`Email(s) (${failed.join(", ")}) could not be sent`)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
options,
|
options,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Email HTML body
|
||||||
* Email HTML body
|
function html({ url, host, email }: Record<"url" | "host" | "email", string>) {
|
||||||
* Insert invisible space into domains from being turned into a hyperlink by email
|
// Insert invisible space into domains and email address to prevent both the
|
||||||
* clients like Outlook and Apple mail, as this is confusing because it seems
|
// email address and the domain from being turned into a hyperlink by email
|
||||||
* like they are supposed to click on it to sign in.
|
// 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.
|
||||||
* @note We don't add the email address to avoid needing to escape it, if you do, remember to sanitize it!
|
const escapedEmail = `${email.replace(/\./g, "​.")}`
|
||||||
*/
|
const escapedHost = `${host.replace(/\./g, "​.")}`
|
||||||
function html(params: { url: string; host: string; theme: Theme }) {
|
|
||||||
const { url, host, theme } = params
|
|
||||||
|
|
||||||
const escapedHost = host.replace(/\./g, "​.")
|
// Some simple styling options
|
||||||
|
const backgroundColor = "#f9f9f9"
|
||||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
const textColor = "#444444"
|
||||||
const brandColor = theme.brandColor || "#346df1"
|
const mainBackgroundColor = "#ffffff"
|
||||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
const buttonBackgroundColor = "#346df1"
|
||||||
const buttonText = theme.buttonText || "#fff"
|
const buttonBorderColor = "#346df1"
|
||||||
|
const buttonTextColor = "#ffffff"
|
||||||
const color = {
|
|
||||||
background: "#f9f9f9",
|
|
||||||
text: "#444",
|
|
||||||
mainBackground: "#fff",
|
|
||||||
buttonBackground: brandColor,
|
|
||||||
buttonBorder: brandColor,
|
|
||||||
buttonText,
|
|
||||||
}
|
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<body style="background: ${color.background};">
|
<body style="background: ${backgroundColor};">
|
||||||
<table width="100%" border="0" cellspacing="20" cellpadding="0"
|
<table width="100%" border="0" cellspacing="0" cellpadding="0">
|
||||||
style="background: ${color.mainBackground}; max-width: 600px; margin: auto; border-radius: 10px;">
|
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center"
|
<td align="center" style="padding: 10px 0px 20px 0px; font-size: 22px; font-family: Helvetica, Arial, sans-serif; color: ${textColor};">
|
||||||
style="padding: 10px 0px; font-size: 22px; font-family: Helvetica, Arial, sans-serif; color: ${color.text};">
|
<strong>${escapedHost}</strong>
|
||||||
Sign in to <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>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center" style="padding: 20px 0;">
|
<td align="center" style="padding: 20px 0;">
|
||||||
<table border="0" cellspacing="0" cellpadding="0">
|
<table border="0" cellspacing="0" cellpadding="0">
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center" style="border-radius: 5px;" bgcolor="${color.buttonBackground}"><a href="${url}"
|
<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>
|
||||||
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>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center"
|
<td align="center" style="padding: 0px 0px 10px 0px; font-size: 16px; line-height: 22px; font-family: Helvetica, Arial, sans-serif; color: ${textColor};">
|
||||||
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.
|
If you did not request this email you can safely ignore it.
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -146,7 +136,7 @@ function html(params: { url: string; host: string; theme: Theme }) {
|
|||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Email Text body (fallback for email clients that don't render HTML, e.g. feature phones) */
|
// Email Text body (fallback for email clients that don't render HTML, e.g. feature phones)
|
||||||
function text({ url, host }: { url: string; host: string }) {
|
function text({ url, host }: Record<"url" | "host", string>) {
|
||||||
return `Sign in to ${host}\n${url}\n\n`
|
return `Sign in to ${host}\n${url}\n\n`
|
||||||
}
|
}
|
||||||
|
|||||||
44
packages/next-auth/src/providers/github.js
Normal file
44
packages/next-auth/src/providers/github.js
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
/** @type {import(".").OAuthProvider} */
|
||||||
|
export default function GitHub(options) {
|
||||||
|
return {
|
||||||
|
id: "github",
|
||||||
|
name: "GitHub",
|
||||||
|
type: "oauth",
|
||||||
|
authorization: "https://github.com/login/oauth/authorize?scope=read:user+user:email",
|
||||||
|
token: "https://github.com/login/oauth/access_token",
|
||||||
|
userinfo: {
|
||||||
|
url: "https://api.github.com/user",
|
||||||
|
async request({ client, tokens }) {
|
||||||
|
// Get base profile
|
||||||
|
const profile = await client.userinfo(tokens)
|
||||||
|
|
||||||
|
// If user has email hidden, get their primary email from the GitHub API
|
||||||
|
if (!profile.email) {
|
||||||
|
const emails = await (
|
||||||
|
await fetch("https://api.github.com/user/emails", {
|
||||||
|
headers: { Authorization: `token ${tokens.access_token}` },
|
||||||
|
})
|
||||||
|
).json()
|
||||||
|
|
||||||
|
if (emails?.length > 0) {
|
||||||
|
// Get primary email
|
||||||
|
profile.email = emails.find(email => email.primary)?.email;
|
||||||
|
// And if for some reason it doesn't exist, just use the first
|
||||||
|
if (!profile.email) profile.email = emails[0].email;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return profile
|
||||||
|
},
|
||||||
|
},
|
||||||
|
profile(profile) {
|
||||||
|
return {
|
||||||
|
id: profile.id.toString(),
|
||||||
|
name: profile.name || profile.login,
|
||||||
|
email: profile.email,
|
||||||
|
image: profile.avatar_url,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
options,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
import type { OAuthConfig, OAuthUserConfig } from "."
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Source https://docs.github.com/en/rest/users/users#get-the-authenticated-user
|
|
||||||
*/
|
|
||||||
export interface GithubProfile extends Record<string, any> {
|
|
||||||
login: string
|
|
||||||
id: number
|
|
||||||
node_id: string
|
|
||||||
avatar_url: string
|
|
||||||
gravatar_id: string | null
|
|
||||||
url: string
|
|
||||||
html_url: string
|
|
||||||
followers_url: string
|
|
||||||
following_url: string
|
|
||||||
gists_url: string
|
|
||||||
starred_url: string
|
|
||||||
subscriptions_url: string
|
|
||||||
organizations_url: string
|
|
||||||
repos_url: string
|
|
||||||
events_url: string
|
|
||||||
received_events_url: string
|
|
||||||
type: string
|
|
||||||
site_admin: boolean
|
|
||||||
name: string | null
|
|
||||||
company: string | null
|
|
||||||
blog: string | null
|
|
||||||
location: string | null
|
|
||||||
email: string | null
|
|
||||||
hireable: boolean | null
|
|
||||||
bio: string | null
|
|
||||||
twitter_username?: string | null
|
|
||||||
public_repos: number
|
|
||||||
public_gists: number
|
|
||||||
followers: number
|
|
||||||
following: number
|
|
||||||
created_at: string
|
|
||||||
updated_at: string
|
|
||||||
private_gists?: number
|
|
||||||
total_private_repos?: number
|
|
||||||
owned_private_repos?: number
|
|
||||||
disk_usage?: number
|
|
||||||
suspended_at?: string | null
|
|
||||||
collaborators?: number
|
|
||||||
two_factor_authentication: boolean
|
|
||||||
plan?: {
|
|
||||||
collaborators: number
|
|
||||||
name: string
|
|
||||||
space: number
|
|
||||||
private_repos: number
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GithubEmail extends Record<string, any> {
|
|
||||||
email: string
|
|
||||||
primary: boolean
|
|
||||||
verified: boolean
|
|
||||||
visibility: string | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Github<P extends GithubProfile>(
|
|
||||||
options: OAuthUserConfig<P>
|
|
||||||
): OAuthConfig<P> {
|
|
||||||
return {
|
|
||||||
id: "github",
|
|
||||||
name: "GitHub",
|
|
||||||
type: "oauth",
|
|
||||||
authorization: {
|
|
||||||
url: "https://github.com/login/oauth/authorize",
|
|
||||||
params: { scope: "read:user+user:email" },
|
|
||||||
},
|
|
||||||
token: "https://github.com/login/oauth/access_token",
|
|
||||||
userinfo: {
|
|
||||||
url: "https://api.github.com/user",
|
|
||||||
async request({ client, tokens }) {
|
|
||||||
// Get base profile
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
||||||
const profile = await client.userinfo(tokens.access_token!)
|
|
||||||
|
|
||||||
// If user has email hidden, get their primary email from the GitHub API
|
|
||||||
if (!profile.email) {
|
|
||||||
const emails: GithubEmail[] = await (
|
|
||||||
await fetch("https://api.github.com/user/emails", {
|
|
||||||
headers: { Authorization: `token ${tokens.access_token}` },
|
|
||||||
})
|
|
||||||
).json()
|
|
||||||
|
|
||||||
if (emails?.length > 0) {
|
|
||||||
// Get primary email
|
|
||||||
profile.email = emails.find((email) => email.primary)?.email
|
|
||||||
// And if for some reason it doesn't exist, just use the first
|
|
||||||
if (!profile.email) profile.email = emails[0].email
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return profile
|
|
||||||
},
|
|
||||||
},
|
|
||||||
profile(profile) {
|
|
||||||
return {
|
|
||||||
id: profile.id.toString(),
|
|
||||||
name: profile.name ?? profile.login,
|
|
||||||
email: profile.email,
|
|
||||||
image: profile.avatar_url,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
options,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
23
packages/next-auth/src/providers/vk.js
Normal file
23
packages/next-auth/src/providers/vk.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
/** @type {import(".").OAuthProvider} */
|
||||||
|
export default function VK(options) {
|
||||||
|
const apiVersion = "5.126" // https://vk.com/dev/versions
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: "vk",
|
||||||
|
name: "VK",
|
||||||
|
type: "oauth",
|
||||||
|
authorization: `https://oauth.vk.com/authorize?scope=email&v=${apiVersion}`,
|
||||||
|
token: `https://oauth.vk.com/access_token?v=${apiVersion}`,
|
||||||
|
userinfo: `https://api.vk.com/method/users.get?fields=photo_100&v=${apiVersion}`,
|
||||||
|
profile(result) {
|
||||||
|
const profile = result.response?.[0] ?? {}
|
||||||
|
return {
|
||||||
|
id: profile.id,
|
||||||
|
name: [profile.first_name, profile.last_name].filter(Boolean).join(" "),
|
||||||
|
email: profile.email,
|
||||||
|
image: profile.photo_100,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
options,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,285 +0,0 @@
|
|||||||
import type { OAuthConfig, OAuthUserConfig } from "."
|
|
||||||
|
|
||||||
export interface VkProfile {
|
|
||||||
// https://dev.vk.com/reference/objects/user
|
|
||||||
response: Array<{
|
|
||||||
id: number
|
|
||||||
first_name: string
|
|
||||||
last_name: string
|
|
||||||
photo_100: string
|
|
||||||
can_access_closed: boolean
|
|
||||||
is_closed: boolean
|
|
||||||
deactivated?: string
|
|
||||||
sex?: 0 | 1 | 2
|
|
||||||
screen_name?: string
|
|
||||||
photo_50?: string
|
|
||||||
online?: 0 | 1
|
|
||||||
online_mobile?: 0 | 1
|
|
||||||
online_app?: number
|
|
||||||
verified?: 0 | 1
|
|
||||||
trending?: 0 | 1
|
|
||||||
friend_status?: 0 | 1 | 2 | 3
|
|
||||||
first_name_nom?: string
|
|
||||||
first_name_gen?: string
|
|
||||||
first_name_dat?: string
|
|
||||||
first_name_acc?: string
|
|
||||||
first_name_ins?: string
|
|
||||||
first_name_abl?: string
|
|
||||||
last_name_nom?: string
|
|
||||||
last_name_gen?: string
|
|
||||||
last_name_dat?: string
|
|
||||||
last_name_acc?: string
|
|
||||||
last_name_ins?: string
|
|
||||||
last_name_abl?: string
|
|
||||||
nickname?: string
|
|
||||||
maiden_name?: string
|
|
||||||
domain?: string
|
|
||||||
bdate?: string
|
|
||||||
city?: {
|
|
||||||
id: number
|
|
||||||
title: string
|
|
||||||
}
|
|
||||||
country?: {
|
|
||||||
id: number
|
|
||||||
title: string
|
|
||||||
}
|
|
||||||
timezone?: number
|
|
||||||
photo_200?: string
|
|
||||||
photo_max?: string
|
|
||||||
photo_200_orig?: string
|
|
||||||
photo_400_orig?: string
|
|
||||||
photo_max_orig?: string
|
|
||||||
photo_id?: string
|
|
||||||
has_photo?: 0 | 1
|
|
||||||
has_mobile?: 0 | 1
|
|
||||||
is_friend?: 0 | 1
|
|
||||||
can_post?: 0 | 1
|
|
||||||
can_see_all_posts?: 0 | 1
|
|
||||||
can_see_audio?: 0 | 1
|
|
||||||
connections?: {
|
|
||||||
facebook?: string
|
|
||||||
skype?: string
|
|
||||||
twitter?: string
|
|
||||||
livejournal?: string
|
|
||||||
instagram?: string
|
|
||||||
}
|
|
||||||
photo_400?: string
|
|
||||||
wall_default?: 'owner' | 'all'
|
|
||||||
interests?: string
|
|
||||||
books?: string
|
|
||||||
tv?: string
|
|
||||||
quotes?: string
|
|
||||||
about?: string
|
|
||||||
games?: string
|
|
||||||
movies?: string
|
|
||||||
activities?: string
|
|
||||||
music?: string
|
|
||||||
can_write_private_message?: 0 | 1
|
|
||||||
can_send_friend_request?: 0 | 1
|
|
||||||
contacts?: {
|
|
||||||
mobile_phone?: string
|
|
||||||
home_phone?: string
|
|
||||||
}
|
|
||||||
site?: string
|
|
||||||
status_audio?: {
|
|
||||||
access_key?: string
|
|
||||||
artist: string
|
|
||||||
id: number
|
|
||||||
owner_id: number
|
|
||||||
title: string
|
|
||||||
url?: string
|
|
||||||
duration: number
|
|
||||||
date?: number
|
|
||||||
album_id?: number
|
|
||||||
genre_id?: number
|
|
||||||
performer?: string
|
|
||||||
}
|
|
||||||
status?: string
|
|
||||||
last_seen?: {
|
|
||||||
platform?: 1 | 2 | 3 | 4 | 5 | 6 | 7
|
|
||||||
time?: number
|
|
||||||
}
|
|
||||||
exports?: {
|
|
||||||
facebook?: number
|
|
||||||
livejournal?: number
|
|
||||||
twitter?: number
|
|
||||||
instagram?: number
|
|
||||||
}
|
|
||||||
crop_photo?: {
|
|
||||||
photo: {
|
|
||||||
access_key?: string
|
|
||||||
album_id: number
|
|
||||||
date: number
|
|
||||||
height?: number
|
|
||||||
id: number
|
|
||||||
images?: Array<{
|
|
||||||
height?: number
|
|
||||||
type?: 's' | 'm' | 'x' | 'l' | 'o' | 'p' | 'q' | 'r' | 'y' | 'z' | 'w'
|
|
||||||
url?: string
|
|
||||||
width?: number
|
|
||||||
}>
|
|
||||||
lat?: number
|
|
||||||
long?: number
|
|
||||||
owner_id: number
|
|
||||||
photo_256?: string
|
|
||||||
can_comment?: 0 | 1
|
|
||||||
place?: string
|
|
||||||
post_id?: number
|
|
||||||
sizes?: Array<{
|
|
||||||
height: number
|
|
||||||
url: string
|
|
||||||
src?: string
|
|
||||||
type: 's' | 'm' | 'x' | 'o' | 'p' | 'q' | 'r' | 'k' | 'l' | 'y' | 'z' | 'c' | 'w' | 'a' | 'b' | 'e' | 'i' | 'd' | 'j' | 'temp' | 'h' | 'g' | 'n' | 'f' | 'max'
|
|
||||||
width: number
|
|
||||||
}>
|
|
||||||
text?: string
|
|
||||||
user_id?: number
|
|
||||||
width?: number
|
|
||||||
has_tags: boolean
|
|
||||||
}
|
|
||||||
crop: {
|
|
||||||
x: number
|
|
||||||
y: number
|
|
||||||
x2: number
|
|
||||||
y2: number
|
|
||||||
}
|
|
||||||
rect: {
|
|
||||||
x: number
|
|
||||||
y: number
|
|
||||||
x2: number
|
|
||||||
y2: number
|
|
||||||
}
|
|
||||||
}
|
|
||||||
followers_count?: number
|
|
||||||
blacklisted?: 0 | 1
|
|
||||||
blacklisted_by_me?: 0 | 1
|
|
||||||
is_favorite?: 0 | 1
|
|
||||||
is_hidden_from_feed?: 0 | 1
|
|
||||||
common_count?: number
|
|
||||||
occupation?: {
|
|
||||||
id?: number
|
|
||||||
name?: string
|
|
||||||
type?: 'work' | 'school' | 'university'
|
|
||||||
}
|
|
||||||
career?: {
|
|
||||||
group_id?: number
|
|
||||||
company?: string
|
|
||||||
country_id?: number
|
|
||||||
city_id?: number
|
|
||||||
city_name?: string
|
|
||||||
from?: number
|
|
||||||
until?: number
|
|
||||||
position?: string
|
|
||||||
}
|
|
||||||
military?: {
|
|
||||||
country_id: number
|
|
||||||
from?: number
|
|
||||||
unit: string
|
|
||||||
unit_id: number
|
|
||||||
until?: number
|
|
||||||
}
|
|
||||||
education?: {
|
|
||||||
university?: number
|
|
||||||
university_name?: string
|
|
||||||
faculty?: number
|
|
||||||
faculty_name?: string
|
|
||||||
graduation?: number
|
|
||||||
}
|
|
||||||
home_town?: string
|
|
||||||
relation?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8
|
|
||||||
relation_partner?: {
|
|
||||||
deactivated?: string
|
|
||||||
first_name: string
|
|
||||||
hidden?: number
|
|
||||||
id: number
|
|
||||||
last_name: string
|
|
||||||
can_access_closed?: boolean
|
|
||||||
is_closed?: boolean
|
|
||||||
}
|
|
||||||
personal?: {
|
|
||||||
alcohol?: 1 | 2 | 3 | 4 | 5
|
|
||||||
inspired_by?: string
|
|
||||||
langs?: string[]
|
|
||||||
life_main?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8
|
|
||||||
people_main?: 1 | 2 | 3 | 4 | 5 | 6
|
|
||||||
political?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
|
|
||||||
religion?: string
|
|
||||||
smoking?: 1 | 2 | 3 | 4 | 5
|
|
||||||
}
|
|
||||||
universities?: Array<{
|
|
||||||
chair?: number
|
|
||||||
chair_name?: string
|
|
||||||
city?: number
|
|
||||||
country?: number
|
|
||||||
education_form?: string
|
|
||||||
education_status?: string
|
|
||||||
faculty?: number
|
|
||||||
faculty_name?: string
|
|
||||||
graduation?: number
|
|
||||||
id?: number
|
|
||||||
name?: string
|
|
||||||
university_group_id?: number
|
|
||||||
}>
|
|
||||||
schools?: Array<{
|
|
||||||
city?: number
|
|
||||||
class?: string
|
|
||||||
country?: number
|
|
||||||
id?: string
|
|
||||||
name?: string
|
|
||||||
type?: number
|
|
||||||
type_str?: string
|
|
||||||
year_from?: number
|
|
||||||
year_graduated?: number
|
|
||||||
year_to?: number
|
|
||||||
speciality?: string
|
|
||||||
}>
|
|
||||||
relatives?: Array<{
|
|
||||||
id?: number
|
|
||||||
name?: string
|
|
||||||
type: 'parent' | 'child' | 'grandparent' | 'grandchild' | 'sibling'
|
|
||||||
}>
|
|
||||||
counters?: {
|
|
||||||
albums?: number
|
|
||||||
videos?: number
|
|
||||||
audios?: number
|
|
||||||
photos?: number
|
|
||||||
notes?: number
|
|
||||||
friends?: number
|
|
||||||
groups?: number
|
|
||||||
online_friends?: number
|
|
||||||
mutual_friends?: number
|
|
||||||
user_videos?: number
|
|
||||||
followers?: number
|
|
||||||
pages?: number
|
|
||||||
}
|
|
||||||
is_no_index?: 0 | 1
|
|
||||||
}>
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function VK<
|
|
||||||
P extends Record<string, any> = VkProfile
|
|
||||||
>(options: OAuthUserConfig<P>): OAuthConfig<P> {
|
|
||||||
const apiVersion = "5.131" // https://vk.com/dev/versions
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: "vk",
|
|
||||||
name: "VK",
|
|
||||||
type: "oauth",
|
|
||||||
authorization: `https://oauth.vk.com/authorize?scope=email&v=${apiVersion}`,
|
|
||||||
client: {
|
|
||||||
token_endpoint_auth_method: "client_secret_post",
|
|
||||||
},
|
|
||||||
token: `https://oauth.vk.com/access_token?v=${apiVersion}`,
|
|
||||||
userinfo: `https://api.vk.com/method/users.get?fields=photo_100&v=${apiVersion}`,
|
|
||||||
profile(result: P) {
|
|
||||||
const profile = result.response?.[0] ?? {}
|
|
||||||
return {
|
|
||||||
id: profile.id,
|
|
||||||
name: [profile.first_name, profile.last_name].filter(Boolean).join(" "),
|
|
||||||
email: null,
|
|
||||||
image: profile.photo_100,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
options,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,185 +0,0 @@
|
|||||||
import type { OAuthConfig, OAuthUserConfig } from "."
|
|
||||||
|
|
||||||
export type WikimediaGroup =
|
|
||||||
| "*"
|
|
||||||
| "user"
|
|
||||||
| "autoconfirmed"
|
|
||||||
| "extendedconfirmed"
|
|
||||||
| "bot"
|
|
||||||
| "sysop"
|
|
||||||
| "bureaucrat"
|
|
||||||
| "steward"
|
|
||||||
| "accountcreator"
|
|
||||||
| "import"
|
|
||||||
| "transwiki"
|
|
||||||
| "ipblock-exempt"
|
|
||||||
| "oversight"
|
|
||||||
| "rollbacker"
|
|
||||||
| "propertycreator"
|
|
||||||
| "wikidata-staff"
|
|
||||||
| "flood"
|
|
||||||
| "translationadmin"
|
|
||||||
| "confirmed"
|
|
||||||
| "flow-bot"
|
|
||||||
| "checkuser"
|
|
||||||
|
|
||||||
export type WikimediaGrant =
|
|
||||||
| "basic"
|
|
||||||
| "blockusers"
|
|
||||||
| "checkuser"
|
|
||||||
| "createaccount"
|
|
||||||
| "delete"
|
|
||||||
| "editinterface"
|
|
||||||
| "editmycssjs"
|
|
||||||
| "editmyoptions"
|
|
||||||
| "editmywatchlist"
|
|
||||||
| "editpage"
|
|
||||||
| "editprotected"
|
|
||||||
| "editsiteconfig"
|
|
||||||
| "globalblock"
|
|
||||||
| "highvolume"
|
|
||||||
| "import"
|
|
||||||
| "mergehistory"
|
|
||||||
| "oath"
|
|
||||||
| "oversight"
|
|
||||||
| "patrol"
|
|
||||||
| "privateinfo"
|
|
||||||
| "protect"
|
|
||||||
| "rollback"
|
|
||||||
| "sendemail"
|
|
||||||
| "shortenurls"
|
|
||||||
| "uploadfile"
|
|
||||||
| "viewdeleted"
|
|
||||||
| "viewmywatchlist"
|
|
||||||
|
|
||||||
export type WikimediaRight =
|
|
||||||
| "abusefilter-log"
|
|
||||||
| "apihighlimits"
|
|
||||||
| "applychangetags"
|
|
||||||
| "autoconfirmed"
|
|
||||||
| "autopatrol"
|
|
||||||
| "autoreview"
|
|
||||||
| "bigdelete"
|
|
||||||
| "block"
|
|
||||||
| "blockemail"
|
|
||||||
| "bot"
|
|
||||||
| "browsearchive"
|
|
||||||
| "changetags"
|
|
||||||
| "checkuser"
|
|
||||||
| "checkuser-log"
|
|
||||||
| "createaccount"
|
|
||||||
| "createpage"
|
|
||||||
| "createpagemainns"
|
|
||||||
| "createtalk"
|
|
||||||
| "delete"
|
|
||||||
| "delete-redirect"
|
|
||||||
| "deletedhistory"
|
|
||||||
| "deletedtext"
|
|
||||||
| "deletelogentry"
|
|
||||||
| "deleterevision"
|
|
||||||
| "edit"
|
|
||||||
| "edit-legal"
|
|
||||||
| "editinterface"
|
|
||||||
| "editmyoptions"
|
|
||||||
| "editmyusercss"
|
|
||||||
| "editmyuserjs"
|
|
||||||
| "editmyuserjson"
|
|
||||||
| "editmywatchlist"
|
|
||||||
| "editprotected"
|
|
||||||
| "editsemiprotected"
|
|
||||||
| "editsitecss"
|
|
||||||
| "editsitejs"
|
|
||||||
| "editsitejson"
|
|
||||||
| "editusercss"
|
|
||||||
| "edituserjs"
|
|
||||||
| "edituserjson"
|
|
||||||
| "globalblock"
|
|
||||||
| "import"
|
|
||||||
| "importupload"
|
|
||||||
| "ipblock-exempt"
|
|
||||||
| "item-merge"
|
|
||||||
| "item-redirect"
|
|
||||||
| "item-term"
|
|
||||||
| "markbotedits"
|
|
||||||
| "massmessage"
|
|
||||||
| "mergehistory"
|
|
||||||
| "minoredit"
|
|
||||||
| "move"
|
|
||||||
| "move-subpages"
|
|
||||||
| "movefile"
|
|
||||||
| "movestable"
|
|
||||||
| "mwoauth-authonlyprivate"
|
|
||||||
| "nominornewtalk"
|
|
||||||
| "noratelimit"
|
|
||||||
| "nuke"
|
|
||||||
| "patrol"
|
|
||||||
| "patrolmarks"
|
|
||||||
| "property-create"
|
|
||||||
| "property-term"
|
|
||||||
| "protect"
|
|
||||||
| "purge"
|
|
||||||
| "read"
|
|
||||||
| "reupload"
|
|
||||||
| "reupload-own"
|
|
||||||
| "reupload-shared"
|
|
||||||
| "rollback"
|
|
||||||
| "sendemail"
|
|
||||||
| "skipcaptcha"
|
|
||||||
| "suppressionlog"
|
|
||||||
| "tboverride"
|
|
||||||
| "templateeditor"
|
|
||||||
| "torunblocked"
|
|
||||||
| "transcode-reset"
|
|
||||||
| "translate"
|
|
||||||
| "undelete"
|
|
||||||
| "unwatchedpages"
|
|
||||||
| "upload"
|
|
||||||
| "upload_by_url"
|
|
||||||
| "viewmywatchlist"
|
|
||||||
| "viewsuppressed"
|
|
||||||
| "writeapi"
|
|
||||||
|
|
||||||
export interface WikimediaProfile extends Record<string, any> {
|
|
||||||
sub: string
|
|
||||||
username: string
|
|
||||||
editcount: number
|
|
||||||
confirmed_email: boolean
|
|
||||||
blocked: boolean
|
|
||||||
registered: string
|
|
||||||
groups: WikimediaGroup[]
|
|
||||||
rights: WikimediaRight[]
|
|
||||||
grants: WikimediaGrant[]
|
|
||||||
realname: string
|
|
||||||
email: string
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wikimedia OAuth2 provider.
|
|
||||||
* All Wikimedia wikis are supported. Wikipedia, Wikidata, etc...
|
|
||||||
*
|
|
||||||
* (Register)[https://meta.wikimedia.org/wiki/Special:OAuthConsumerRegistration]
|
|
||||||
* (Documentation)[https://www.mediawiki.org/wiki/Extension:OAuth]
|
|
||||||
*/
|
|
||||||
export default function Wikimedia<P extends WikimediaProfile>(
|
|
||||||
options: OAuthUserConfig<P>
|
|
||||||
): OAuthConfig<P> {
|
|
||||||
return {
|
|
||||||
id: "wikimedia",
|
|
||||||
name: "Wikimedia",
|
|
||||||
type: "oauth",
|
|
||||||
token: "https://meta.wikimedia.org/w/rest.php/oauth2/access_token",
|
|
||||||
userinfo: "https://meta.wikimedia.org/w/rest.php/oauth2/resource/profile",
|
|
||||||
authorization: {
|
|
||||||
url: "https://meta.wikimedia.org/w/rest.php/oauth2/authorize",
|
|
||||||
params: { scope: "" },
|
|
||||||
},
|
|
||||||
profile(profile) {
|
|
||||||
return {
|
|
||||||
id: profile.sub,
|
|
||||||
name: profile.username,
|
|
||||||
email: profile.email,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
options,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -50,19 +50,4 @@ describe("Treat secret correctly", () => {
|
|||||||
expect(logger.error).toBeCalledTimes(1)
|
expect(logger.error).toBeCalledTimes(1)
|
||||||
expect(logger.error).toBeCalledWith("NO_SECRET", expect.any(MissingSecret))
|
expect(logger.error).toBeCalledWith("NO_SECRET", expect.any(MissingSecret))
|
||||||
})
|
})
|
||||||
|
|
||||||
it("Only logs warning once and in development", async () => {
|
|
||||||
// Expect console.warn to NOT be called due to NODE_ENV=production
|
|
||||||
await unstable_getServerSession(req, res, { providers: [], logger })
|
|
||||||
expect(console.warn).toBeCalledTimes(0)
|
|
||||||
|
|
||||||
// Expect console.warn to be called ONCE due to NODE_ENV=development
|
|
||||||
process.env.NODE_ENV = "development"
|
|
||||||
await unstable_getServerSession(req, res, { providers: [], logger })
|
|
||||||
expect(console.warn).toBeCalledTimes(1)
|
|
||||||
|
|
||||||
// Expect console.warn to be still only be called ONCE
|
|
||||||
await unstable_getServerSession(req, res, { providers: [], logger })
|
|
||||||
expect(console.warn).toBeCalledTimes(1)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { createHash } from "crypto"
|
|
||||||
import type { LoggerInstance, NextAuthOptions } from "../src"
|
import type { LoggerInstance, NextAuthOptions } from "../src"
|
||||||
import { NextAuthHandler } from "../src/core"
|
import { NextAuthHandler } from "../src/core"
|
||||||
|
|
||||||
@@ -8,16 +7,17 @@ export const mockLogger: () => LoggerInstance = () => ({
|
|||||||
debug: jest.fn(() => {}),
|
debug: jest.fn(() => {}),
|
||||||
})
|
})
|
||||||
|
|
||||||
interface HandlerOptions {
|
|
||||||
prod?: boolean
|
|
||||||
path?: string
|
|
||||||
params?: URLSearchParams | Record<string, string>
|
|
||||||
requestInit?: RequestInit
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function handler(
|
export async function handler(
|
||||||
options: NextAuthOptions,
|
options: NextAuthOptions,
|
||||||
{ prod, path, params, requestInit }: HandlerOptions
|
{
|
||||||
|
prod,
|
||||||
|
path,
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
prod?: boolean
|
||||||
|
path?: string
|
||||||
|
params?: URLSearchParams | Record<string, string>
|
||||||
|
}
|
||||||
) {
|
) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
if (prod) process.env.NODE_ENV = "production"
|
if (prod) process.env.NODE_ENV = "production"
|
||||||
@@ -27,7 +27,11 @@ export async function handler(
|
|||||||
params ?? {}
|
params ?? {}
|
||||||
)}`
|
)}`
|
||||||
)
|
)
|
||||||
const req = new Request(url, { headers: { host: "" }, ...requestInit })
|
const req = new Request(url, {
|
||||||
|
headers: {
|
||||||
|
host: "",
|
||||||
|
},
|
||||||
|
})
|
||||||
const logger = mockLogger()
|
const logger = mockLogger()
|
||||||
const response = await NextAuthHandler({
|
const response = await NextAuthHandler({
|
||||||
req,
|
req,
|
||||||
@@ -45,14 +49,3 @@ export async function handler(
|
|||||||
log: logger,
|
log: logger,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createCSRF() {
|
|
||||||
const secret = "secret"
|
|
||||||
const value = "csrf"
|
|
||||||
const token = createHash("sha256").update(`${value}${secret}`).digest("hex")
|
|
||||||
|
|
||||||
return {
|
|
||||||
secret,
|
|
||||||
csrf: { value, token, cookie: `next-auth.csrf-token=${value}|${token}` },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
1502
pnpm-lock.yaml
generated
1502
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,13 +1,11 @@
|
|||||||
import type {
|
import type { Commit, GroupedCommits, PackageToRelease } from "./types"
|
||||||
Commit,
|
|
||||||
GroupedCommits as GrouppedCommits,
|
|
||||||
PackageToRelease,
|
|
||||||
} from "./types"
|
|
||||||
|
|
||||||
import { debug, pkgJson, execSync } from "./utils"
|
import { debug, pkgJson, execSync } from "./utils"
|
||||||
import semver from "semver"
|
import semver from "semver"
|
||||||
import parseCommit from "@commitlint/parse"
|
import parseCommit from "@commitlint/parse"
|
||||||
|
// @ts-ignore
|
||||||
import gitLog from "git-log-parser"
|
import gitLog from "git-log-parser"
|
||||||
|
// @ts-ignore
|
||||||
import streamToArray from "stream-to-array"
|
import streamToArray from "stream-to-array"
|
||||||
|
|
||||||
export async function analyze(options: {
|
export async function analyze(options: {
|
||||||
@@ -16,12 +14,14 @@ export async function analyze(options: {
|
|||||||
BREAKING_COMMIT_MSG: string
|
BREAKING_COMMIT_MSG: string
|
||||||
RELEASE_COMMIT_MSG: string
|
RELEASE_COMMIT_MSG: string
|
||||||
RELEASE_COMMIT_TYPES: string[]
|
RELEASE_COMMIT_TYPES: string[]
|
||||||
|
SKIP_RELEASE_MSG: string
|
||||||
}): Promise<PackageToRelease[]> {
|
}): Promise<PackageToRelease[]> {
|
||||||
const {
|
const {
|
||||||
packages,
|
packages,
|
||||||
BREAKING_COMMIT_MSG,
|
BREAKING_COMMIT_MSG,
|
||||||
RELEASE_COMMIT_MSG,
|
RELEASE_COMMIT_MSG,
|
||||||
RELEASE_COMMIT_TYPES,
|
RELEASE_COMMIT_TYPES,
|
||||||
|
SKIP_RELEASE_MSG,
|
||||||
} = options
|
} = options
|
||||||
|
|
||||||
const packageFolders = Object.values(options.packages)
|
const packageFolders = Object.values(options.packages)
|
||||||
@@ -65,7 +65,12 @@ export async function analyze(options: {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const lastCommit = commitsSinceLatestTag[0]
|
const lastCommit = commitsSinceLatestTag[0]
|
||||||
|
if (lastCommit?.parsed.raw.includes(SKIP_RELEASE_MSG)) {
|
||||||
|
console.log(
|
||||||
|
`Last commit contained ${SKIP_RELEASE_MSG}, skipping release...`
|
||||||
|
)
|
||||||
|
return []
|
||||||
|
}
|
||||||
if (lastCommit?.parsed.raw === RELEASE_COMMIT_MSG) {
|
if (lastCommit?.parsed.raw === RELEASE_COMMIT_MSG) {
|
||||||
debug("Already released...")
|
debug("Already released...")
|
||||||
return []
|
return []
|
||||||
@@ -127,7 +132,7 @@ export async function analyze(options: {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return acc
|
return acc
|
||||||
}, {} as Record<string, GrouppedCommits>)
|
}, {} as Record<string, GroupedCommits>)
|
||||||
|
|
||||||
if (packagesNeedRelease.length) {
|
if (packagesNeedRelease.length) {
|
||||||
console.log(
|
console.log(
|
||||||
|
|||||||
@@ -17,12 +17,9 @@ export const config = {
|
|||||||
"@next-auth/typeorm-legacy-adapter": "packages/adapter-typeorm-legacy",
|
"@next-auth/typeorm-legacy-adapter": "packages/adapter-typeorm-legacy",
|
||||||
},
|
},
|
||||||
rootDir: process.cwd(),
|
rootDir: process.cwd(),
|
||||||
BREAKING_COMMIT_MSG: "BREAKING CHANGE:",
|
RELEASE_COMMIT_MSG: "chore(release): bump version",
|
||||||
RELEASE_COMMIT_MSG: "chore(release): bump package version(s) [skip ci]",
|
BREAKING_COMMIT_MSG: "BREAKING CHANGE",
|
||||||
|
SKIP_RELEASE_MSG: "[skip release]",
|
||||||
RELEASE_COMMIT_TYPES: ["feat", "fix"],
|
RELEASE_COMMIT_TYPES: ["feat", "fix"],
|
||||||
dryRun:
|
dryRun: !process.env.CI || !!process.env.DRY_RUN,
|
||||||
!process.env.CI ||
|
|
||||||
!!process.env.DRY_RUN ||
|
|
||||||
process.argv.includes("--dry-run"),
|
|
||||||
verbose: !!process.env.VERBOSE || process.argv.includes("--verbose"),
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ async function run() {
|
|||||||
JSON.stringify(
|
JSON.stringify(
|
||||||
{
|
{
|
||||||
...p,
|
...p,
|
||||||
commits: `${p.commits.features.length} feature(s), ${p.commits.bugfixes.length} bugfixe(s), ${p.commits.other.length} other(s) and ${p.commits.breaking.length} breaking change(s)`,
|
commits: `${p.commits.features.length} feature(s), ${p.commits.bugfixes.length} bugfixe(s), ${p.commits.other.length} othe(r) and ${p.commits.breaking.length} breaking change(s)`,
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
2
|
2
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { createHash } from "crypto"
|
||||||
import type { Commit, PackageToRelease } from "./types"
|
import type { Commit, PackageToRelease } from "./types"
|
||||||
|
|
||||||
import { debug, pkgJson, execSync } from "./utils"
|
import { debug, pkgJson, execSync } from "./utils"
|
||||||
@@ -14,11 +15,11 @@ export async function publish(options: {
|
|||||||
for await (const pkg of packages) {
|
for await (const pkg of packages) {
|
||||||
if (dryRun) {
|
if (dryRun) {
|
||||||
console.log(
|
console.log(
|
||||||
`Dry run, \`npm publish\` would have released package \`${pkg.name}\` with version "${pkg.newVersion}".`
|
`Dry run, npm publish for package ${pkg.name} will show the wrong version (${pkg.oldVersion}). In normal run, it would be ${pkg.newVersion}`
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
console.log(
|
console.log(
|
||||||
`Writing version "${pkg.newVersion}" to package.json for package \`${pkg.name}\``
|
`Writing version ${pkg.newVersion} to package.json for package ${pkg.name}`
|
||||||
)
|
)
|
||||||
await pkgJson.update(pkg.path, { version: pkg.newVersion })
|
await pkgJson.update(pkg.path, { version: pkg.newVersion })
|
||||||
console.log("package.json file has been written, publishing...")
|
console.log("package.json file has been written, publishing...")
|
||||||
@@ -34,10 +35,8 @@ export async function publish(options: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (dryRun) {
|
if (dryRun) {
|
||||||
console.log(
|
console.log(`Dry run, skip npm publish for package ${pkg.name}...`)
|
||||||
`Dry run, skip \`npm publish\` for package \`${pkg.name}\`...\n`
|
npmPublish += " --dry-run"
|
||||||
)
|
|
||||||
npmPublish += " --dry-run --silent"
|
|
||||||
} else {
|
} else {
|
||||||
execSync(
|
execSync(
|
||||||
"echo '//registry.npmjs.org/:_authToken=${NPM_TOKEN}' > .npmrc",
|
"echo '//registry.npmjs.org/:_authToken=${NPM_TOKEN}' > .npmrc",
|
||||||
@@ -59,15 +58,13 @@ export async function publish(options: {
|
|||||||
const { name, oldVersion, newVersion } = pkg
|
const { name, oldVersion, newVersion } = pkg
|
||||||
const gitTag = `${name}@v${newVersion}`
|
const gitTag = `${name}@v${newVersion}`
|
||||||
|
|
||||||
console.log(
|
console.log(`${name} ${oldVersion} -> ${newVersion}`)
|
||||||
`\n\n-------------------------------\n${name} ${oldVersion} -> ${newVersion}`
|
|
||||||
)
|
|
||||||
|
|
||||||
const changelog = createChangelog(pkg)
|
const changelog = createChangelog(pkg)
|
||||||
debug("Changelog generated", changelog)
|
debug(`Generated changelog for package ${name}`, changelog)
|
||||||
|
|
||||||
if (dryRun) {
|
if (dryRun) {
|
||||||
console.log(`Dry run, skip git tag/release notes for package \`${name}\``)
|
console.log(`Dry run, skip git tag/release notes for package ${name}`)
|
||||||
} else {
|
} else {
|
||||||
console.log(`Creating git tag...`)
|
console.log(`Creating git tag...`)
|
||||||
execSync(`git tag ${gitTag}`)
|
execSync(`git tag ${gitTag}`)
|
||||||
@@ -86,7 +83,7 @@ function createChangelog(pkg: PackageToRelease) {
|
|||||||
const {
|
const {
|
||||||
commits: { features, breaking, bugfixes, other },
|
commits: { features, breaking, bugfixes, other },
|
||||||
} = pkg
|
} = pkg
|
||||||
console.log(`Creating changelog for package \`${pkg.name}\`...`)
|
console.log(`Creating changelog for package ${pkg.name}...`)
|
||||||
|
|
||||||
let changelog = ``
|
let changelog = ``
|
||||||
changelog += listGroup("Features", features)
|
changelog += listGroup("Features", features)
|
||||||
|
|||||||
@@ -1,12 +1,21 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ESNext",
|
"target": "ES2018",
|
||||||
"module": "NodeNext",
|
"module": "commonjs",
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"strict": true,
|
||||||
|
"noImplicitAny": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"skipDefaultLibCheck": true
|
"checkJs": true,
|
||||||
|
"resolveJsonModule": true
|
||||||
},
|
},
|
||||||
"ts-node": {
|
"ts-node": {
|
||||||
"swc": true
|
"transpileOnly": true,
|
||||||
|
"files": true,
|
||||||
|
"compilerOptions": {
|
||||||
|
"sourceMap": true,
|
||||||
|
"inlineSources": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import type { PackageJson } from "type-fest"
|
|||||||
import fs from "node:fs/promises"
|
import fs from "node:fs/promises"
|
||||||
import path from "node:path"
|
import path from "node:path"
|
||||||
import { execSync as nodeExecSync } from "node:child_process"
|
import { execSync as nodeExecSync } from "node:child_process"
|
||||||
import { config } from "./config"
|
|
||||||
|
|
||||||
async function read(directory: string): Promise<PackageJson> {
|
async function read(directory: string): Promise<PackageJson> {
|
||||||
const content = await fs.readFile(
|
const content = await fs.readFile(
|
||||||
@@ -29,7 +28,7 @@ async function update(
|
|||||||
export const pkgJson = { read, update }
|
export const pkgJson = { read, update }
|
||||||
|
|
||||||
export function debug(...args: any[]): void {
|
export function debug(...args: any[]): void {
|
||||||
if (!config.verbose) return
|
if (!process.env.DEBUG) return
|
||||||
const [first, ...rest] = args
|
const [first, ...rest] = args
|
||||||
console.log(`\n[debug] ${first}\n`, ...rest, "\n")
|
console.log(`\n[debug] ${first}\n`, ...rest, "\n")
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user