mirror of
https://github.com/SrIzan10/next-auth.git
synced 2026-05-01 10:55:20 +00:00
Compare commits
24 Commits
@next-auth
...
next-auth@
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8bff050e4e | ||
|
|
1a79a1a612 | ||
|
|
b7065a602f | ||
|
|
61b92ec1b6 | ||
|
|
282f7ab340 | ||
|
|
4f56e414b0 | ||
|
|
2725d07eb7 | ||
|
|
5a8b029523 | ||
|
|
f62a985848 | ||
|
|
edd6fb5989 | ||
|
|
fb60554a62 | ||
|
|
9784dfb631 | ||
|
|
4ff836a8cf | ||
|
|
042955eaaa | ||
|
|
82e107c0e7 | ||
|
|
f7050347e8 | ||
|
|
c56abbd745 | ||
|
|
3f6d99e8df | ||
|
|
46eedee3c8 | ||
|
|
bb664a27da | ||
|
|
a14fbea0b5 | ||
|
|
4705632c6b | ||
|
|
2296471f02 | ||
|
|
8853000fd5 |
15
.github/CODEOWNERS
vendored
15
.github/CODEOWNERS
vendored
@@ -1,4 +1,11 @@
|
|||||||
/types/ @balazsorban44 @lluia
|
# Learn how to add code owners here:
|
||||||
/docs/ @balazsorban44 @ndom91
|
# https://help.github.com/en/articles/about-code-owners
|
||||||
/adapters/ @balazsorban44 @ndom91
|
|
||||||
/__tests__/ @lluia
|
* @balazsorban44
|
||||||
|
.github @ThangHuuVu
|
||||||
|
/apps/ @lluia @ndom91 @ThangHuuVu
|
||||||
|
/docs/ @lluia @ndom91
|
||||||
|
/packages/ @ThangHuuVu
|
||||||
|
/packages/adapter-*/ @ndom91
|
||||||
|
/**/*test* @lluia
|
||||||
|
/**/*type* @lluia
|
||||||
2
.github/version-pr/action.yml
vendored
2
.github/version-pr/action.yml
vendored
@@ -4,5 +4,5 @@ outputs:
|
|||||||
version:
|
version:
|
||||||
description: "npm package version"
|
description: "npm package version"
|
||||||
runs:
|
runs:
|
||||||
using: "node12"
|
using: "node16"
|
||||||
main: "index.js"
|
main: "index.js"
|
||||||
|
|||||||
6
.github/workflows/release.yml
vendored
6
.github/workflows/release.yml
vendored
@@ -108,7 +108,11 @@ jobs:
|
|||||||
- name: Comment version on PR
|
- name: Comment version on PR
|
||||||
uses: NejcZdovc/comment-pr@v1
|
uses: NejcZdovc/comment-pr@v1
|
||||||
with:
|
with:
|
||||||
message: "🎉 Experimental release [published on npm](https://www.npmjs.com/package/next-auth/v/${{ env.VERSION }})!\n\n```sh\nnpm i next-auth@${{ env.VERSION }}\n```\n```sh\nyarn add next-auth@${{ env.VERSION }}\n```"
|
message:
|
||||||
|
"🎉 Experimental release [published 📦️ on npm](https://npmjs.com/package/next-auth/v/${{ env.VERSION }})! \
|
||||||
|
```sh\npnpm add next-auth@${{ env.VERSION }}\n``` \
|
||||||
|
```sh\nyarn add next-auth@${{ env.VERSION }}\n``` \
|
||||||
|
```sh\nnpm i next-auth@${{ env.VERSION }}\n```"
|
||||||
env:
|
env:
|
||||||
VERSION: ${{ steps.determine-version.outputs.version }}
|
VERSION: ${{ steps.determine-version.outputs.version }}
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
@@ -47,9 +47,5 @@ EMAIL_FROM=user@gmail.com
|
|||||||
# MongoDB: DATABASE_URL=mongodb://nextauth:password@127.0.0.1:27017/nextauth?synchronize=true
|
# MongoDB: DATABASE_URL=mongodb://nextauth:password@127.0.0.1:27017/nextauth?synchronize=true
|
||||||
DATABASE_URL=
|
DATABASE_URL=
|
||||||
|
|
||||||
BOXYHQSAML_ISSUER="https://jackson-demo.boxyhq.com"
|
|
||||||
BOXYHQSAML_ID="tenant=boxyhq.com&product=saml-demo.boxyhq.com"
|
|
||||||
BOXYHQSAML_SECRET="dummy"
|
|
||||||
|
|
||||||
WIKIMEDIA_ID=
|
WIKIMEDIA_ID=
|
||||||
WIKIMEDIA_SECRET=
|
WIKIMEDIA_SECRET=
|
||||||
@@ -30,12 +30,11 @@ export const config = { matcher: ["/middleware-protected"] }
|
|||||||
// export default withAuth(
|
// export default withAuth(
|
||||||
// function middleware(req, ev) {
|
// function middleware(req, ev) {
|
||||||
// console.log(req, ev)
|
// console.log(req, ev)
|
||||||
// return undefined // NOTE: `NextMiddleware` should allow returning `void`
|
|
||||||
// },
|
// },
|
||||||
// {
|
// {
|
||||||
// callbacks: {
|
// callbacks: {
|
||||||
// authorized: ({ token }) => token.name === "Balázs Orbán",
|
// authorized: ({ token }) => token.name === "Balázs Orbán",
|
||||||
// }
|
// },
|
||||||
// }
|
// }
|
||||||
// )
|
// )
|
||||||
|
|
||||||
|
|||||||
@@ -1,251 +1,134 @@
|
|||||||
import NextAuth, { NextAuthOptions } from "next-auth"
|
import NextAuth from "next-auth"
|
||||||
// import EmailProvider from "next-auth/providers/email"
|
import type { NextAuthOptions } from "next-auth"
|
||||||
import GitHubProvider from "next-auth/providers/github"
|
|
||||||
import Auth0Provider from "next-auth/providers/auth0"
|
// Providers
|
||||||
import KeycloakProvider from "next-auth/providers/keycloak"
|
import Apple from "next-auth/providers/apple"
|
||||||
import TwitterProvider, {
|
import Auth0 from "next-auth/providers/auth0"
|
||||||
// TwitterLegacy as TwitterLegacyProvider,
|
import AzureAD from "next-auth/providers/azure-ad"
|
||||||
} from "next-auth/providers/twitter"
|
|
||||||
import CredentialsProvider from "next-auth/providers/credentials"
|
|
||||||
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 GoogleProvider from "next-auth/providers/google"
|
|
||||||
import FacebookProvider from "next-auth/providers/facebook"
|
|
||||||
import FoursquareProvider from "next-auth/providers/foursquare"
|
|
||||||
// import FreshbooksProvider from "next-auth/providers/freshbooks"
|
|
||||||
import GitlabProvider from "next-auth/providers/gitlab"
|
|
||||||
import InstagramProvider from "next-auth/providers/instagram"
|
|
||||||
import LineProvider from "next-auth/providers/line"
|
|
||||||
import LinkedInProvider from "next-auth/providers/linkedin"
|
|
||||||
import MailchimpProvider from "next-auth/providers/mailchimp"
|
|
||||||
import DiscordProvider from "next-auth/providers/discord"
|
|
||||||
import AzureADProvider from "next-auth/providers/azure-ad"
|
|
||||||
import SpotifyProvider from "next-auth/providers/spotify"
|
|
||||||
import CognitoProvider from "next-auth/providers/cognito"
|
|
||||||
import SlackProvider from "next-auth/providers/slack"
|
|
||||||
import Okta from "next-auth/providers/okta"
|
|
||||||
import AzureB2C from "next-auth/providers/azure-ad-b2c"
|
import AzureB2C from "next-auth/providers/azure-ad-b2c"
|
||||||
import OsuProvider from "next-auth/providers/osu"
|
import BoxyHQSAML from "next-auth/providers/boxyhq-saml"
|
||||||
import AppleProvider from "next-auth/providers/apple"
|
import Cognito from "next-auth/providers/cognito"
|
||||||
import PatreonProvider from "next-auth/providers/patreon"
|
import Credentials from "next-auth/providers/credentials"
|
||||||
import TraktProvider from "next-auth/providers/trakt"
|
import Discord from "next-auth/providers/discord"
|
||||||
import WorkOSProvider from "next-auth/providers/workos"
|
import DuendeIDS6 from "next-auth/providers/duende-identity-server6"
|
||||||
import BoxyHQSAMLProvider from "next-auth/providers/boxyhq-saml"
|
import Email from "next-auth/providers/email"
|
||||||
import WikimediaProvider from "next-auth/providers/wikimedia"
|
import Facebook from "next-auth/providers/facebook"
|
||||||
import VkProvider from "next-auth/providers/vk"
|
import Foursquare from "next-auth/providers/foursquare"
|
||||||
|
import Freshbooks from "next-auth/providers/freshbooks"
|
||||||
|
import GitHub from "next-auth/providers/github"
|
||||||
|
import Gitlab from "next-auth/providers/gitlab"
|
||||||
|
import Google from "next-auth/providers/google"
|
||||||
|
import IDS4 from "next-auth/providers/identity-server4"
|
||||||
|
import Instagram from "next-auth/providers/instagram"
|
||||||
|
import Keycloak from "next-auth/providers/keycloak"
|
||||||
|
import Line from "next-auth/providers/line"
|
||||||
|
import LinkedIn from "next-auth/providers/linkedin"
|
||||||
|
import Mailchimp from "next-auth/providers/mailchimp"
|
||||||
|
import Okta from "next-auth/providers/okta"
|
||||||
|
import Osu from "next-auth/providers/osu"
|
||||||
|
import Patreon from "next-auth/providers/patreon"
|
||||||
|
import Slack from "next-auth/providers/slack"
|
||||||
|
import Spotify from "next-auth/providers/spotify"
|
||||||
|
import Trakt from "next-auth/providers/trakt"
|
||||||
|
import Twitch from "next-auth/providers/twitch"
|
||||||
|
import Twitter, { TwitterLegacy } from "next-auth/providers/twitter"
|
||||||
|
import Vk from "next-auth/providers/vk"
|
||||||
|
import Wikimedia from "next-auth/providers/wikimedia"
|
||||||
|
import WorkOS from "next-auth/providers/workos"
|
||||||
|
|
||||||
// TypeORM
|
// Adapters
|
||||||
// import { TypeORMLegacyAdapter } from "@next-auth/typeorm-legacy-adapter"
|
import { PrismaClient } from "@prisma/client"
|
||||||
// const adapter = TypeORMLegacyAdapter({
|
import { PrismaAdapter } from "@next-auth/prisma-adapter"
|
||||||
// type: "sqlite",
|
import { Client as FaunaClient } from "faunadb"
|
||||||
// name: "next-auth-test-memory",
|
import { FaunaAdapter } from "@next-auth/fauna-adapter"
|
||||||
// database: "./typeorm/dev.db",
|
import { TypeORMLegacyAdapter } from "@next-auth/typeorm-legacy-adapter"
|
||||||
// synchronize: true,
|
|
||||||
// })
|
|
||||||
|
|
||||||
// // Prisma
|
// Add an adapter you want to test here.
|
||||||
// import { PrismaAdapter } from "@next-auth/prisma-adapter"
|
const adapters = {
|
||||||
// import { PrismaClient } from "@prisma/client"
|
prisma() {
|
||||||
// const prisma = new PrismaClient()
|
const client = globalThis.prisma || new PrismaClient()
|
||||||
// const adapter = PrismaAdapter(prisma)
|
if (process.env.NODE_ENV !== "production") globalThis.prisma = client
|
||||||
|
return PrismaAdapter(client)
|
||||||
// // Fauna
|
},
|
||||||
// import { Client as FaunaClient } from "faunadb"
|
typeorm() {
|
||||||
// import { FaunaAdapter } from "@next-auth/fauna-adapter"
|
return TypeORMLegacyAdapter({
|
||||||
// const client = new FaunaClient({
|
type: "sqlite",
|
||||||
// secret: process.env.FAUNA_SECRET,
|
name: "next-auth-test-memory",
|
||||||
// domain: process.env.FAUNA_DOMAIN,
|
database: "./typeorm/dev.db",
|
||||||
// })
|
synchronize: true,
|
||||||
// const adapter = FaunaAdapter(client)
|
})
|
||||||
|
},
|
||||||
// // Dummy
|
fauna() {
|
||||||
// const adapter: any = {
|
const client =
|
||||||
// getUserByEmail: (email) => ({ id: "1", email, emailVerified: null }),
|
globalThis.fauna ||
|
||||||
// createVerificationToken: (token) => token,
|
new FaunaClient({
|
||||||
// }
|
secret: process.env.FAUNA_SECRET,
|
||||||
|
domain: process.env.FAUNA_DOMAIN,
|
||||||
export const authOptions: NextAuthOptions = {
|
})
|
||||||
// adapter,
|
if (process.env.NODE_ENV !== "production") global.fauna = client
|
||||||
providers: [
|
return FaunaAdapter(client)
|
||||||
// E-mail
|
},
|
||||||
// Start fake e-mail server with `npm run start:email`
|
noop() {
|
||||||
// EmailProvider({
|
return undefined
|
||||||
// server: {
|
|
||||||
// host: "127.0.0.1",
|
|
||||||
// auth: null,
|
|
||||||
// secure: false,
|
|
||||||
// port: 1025,
|
|
||||||
// tls: { rejectUnauthorized: false },
|
|
||||||
// },
|
|
||||||
// }),
|
|
||||||
// Credentials
|
|
||||||
CredentialsProvider({
|
|
||||||
name: "Credentials",
|
|
||||||
credentials: {
|
|
||||||
password: { label: "Password", type: "password" },
|
|
||||||
},
|
|
||||||
async authorize(credentials) {
|
|
||||||
if (credentials.password === "pw") {
|
|
||||||
return {
|
|
||||||
name: "Fill Murray",
|
|
||||||
email: "bill@fillmurray.com",
|
|
||||||
image: "https://www.fillmurray.com/64/64",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
// OAuth 1
|
|
||||||
// TwitterLegacyProvider({
|
|
||||||
// clientId: process.env.TWITTER_LEGACY_ID,
|
|
||||||
// clientSecret: process.env.TWITTER_LEGACY_SECRET,
|
|
||||||
// }),
|
|
||||||
// OAuth 2 / OIDC
|
|
||||||
TwitterProvider({
|
|
||||||
// Opt-in to the new Twitter API for now. Should be default in the future.
|
|
||||||
version: "2.0",
|
|
||||||
clientId: process.env.TWITTER_ID,
|
|
||||||
clientSecret: process.env.TWITTER_SECRET,
|
|
||||||
}),
|
|
||||||
GitHubProvider({
|
|
||||||
clientId: process.env.GITHUB_ID,
|
|
||||||
clientSecret: process.env.GITHUB_SECRET,
|
|
||||||
}),
|
|
||||||
Auth0Provider({
|
|
||||||
clientId: process.env.AUTH0_ID,
|
|
||||||
clientSecret: process.env.AUTH0_SECRET,
|
|
||||||
issuer: process.env.AUTH0_ISSUER,
|
|
||||||
}),
|
|
||||||
KeycloakProvider({
|
|
||||||
clientId: process.env.KEYCLOAK_ID,
|
|
||||||
clientSecret: process.env.KEYCLOAK_SECRET,
|
|
||||||
issuer: process.env.KEYCLOAK_ISSUER,
|
|
||||||
}),
|
|
||||||
Twitch({
|
|
||||||
clientId: process.env.TWITCH_ID,
|
|
||||||
clientSecret: process.env.TWITCH_SECRET,
|
|
||||||
}),
|
|
||||||
GoogleProvider({
|
|
||||||
clientId: process.env.GOOGLE_ID,
|
|
||||||
clientSecret: process.env.GOOGLE_SECRET,
|
|
||||||
}),
|
|
||||||
FacebookProvider({
|
|
||||||
clientId: process.env.FACEBOOK_ID,
|
|
||||||
clientSecret: process.env.FACEBOOK_SECRET,
|
|
||||||
}),
|
|
||||||
FoursquareProvider({
|
|
||||||
clientId: process.env.FOURSQUARE_ID,
|
|
||||||
clientSecret: process.env.FOURSQUARE_SECRET,
|
|
||||||
}),
|
|
||||||
// FreshbooksProvider({
|
|
||||||
// clientId: process.env.FRESHBOOKS_ID,
|
|
||||||
// clientSecret: process.env.FRESHBOOKS_SECRET,
|
|
||||||
// }),
|
|
||||||
GitlabProvider({
|
|
||||||
clientId: process.env.GITLAB_ID,
|
|
||||||
clientSecret: process.env.GITLAB_SECRET,
|
|
||||||
}),
|
|
||||||
InstagramProvider({
|
|
||||||
clientId: process.env.INSTAGRAM_ID,
|
|
||||||
clientSecret: process.env.INSTAGRAM_SECRET,
|
|
||||||
}),
|
|
||||||
LineProvider({
|
|
||||||
clientId: process.env.LINE_ID,
|
|
||||||
clientSecret: process.env.LINE_SECRET,
|
|
||||||
}),
|
|
||||||
LinkedInProvider({
|
|
||||||
clientId: process.env.LINKEDIN_ID,
|
|
||||||
clientSecret: process.env.LINKEDIN_SECRET,
|
|
||||||
}),
|
|
||||||
MailchimpProvider({
|
|
||||||
clientId: process.env.MAILCHIMP_ID,
|
|
||||||
clientSecret: process.env.MAILCHIMP_SECRET,
|
|
||||||
}),
|
|
||||||
IDS4Provider({
|
|
||||||
clientId: process.env.IDS4_ID,
|
|
||||||
clientSecret: process.env.IDS4_SECRET,
|
|
||||||
issuer: process.env.IDS4_ISSUER,
|
|
||||||
}),
|
|
||||||
DuendeIDS6Provider({
|
|
||||||
clientId: "interactive.confidential",
|
|
||||||
clientSecret: "secret",
|
|
||||||
issuer: "https://demo.duendesoftware.com",
|
|
||||||
}),
|
|
||||||
DiscordProvider({
|
|
||||||
clientId: process.env.DISCORD_ID,
|
|
||||||
clientSecret: process.env.DISCORD_SECRET,
|
|
||||||
}),
|
|
||||||
AzureADProvider({
|
|
||||||
clientId: process.env.AZURE_AD_CLIENT_ID,
|
|
||||||
clientSecret: process.env.AZURE_AD_CLIENT_SECRET,
|
|
||||||
tenantId: process.env.AZURE_AD_TENANT_ID,
|
|
||||||
profilePhotoSize: 48,
|
|
||||||
}),
|
|
||||||
SpotifyProvider({
|
|
||||||
clientId: process.env.SPOTIFY_ID,
|
|
||||||
clientSecret: process.env.SPOTIFY_SECRET,
|
|
||||||
}),
|
|
||||||
CognitoProvider({
|
|
||||||
clientId: process.env.COGNITO_ID,
|
|
||||||
clientSecret: process.env.COGNITO_SECRET,
|
|
||||||
issuer: process.env.COGNITO_ISSUER,
|
|
||||||
}),
|
|
||||||
Okta({
|
|
||||||
clientId: process.env.OKTA_ID,
|
|
||||||
clientSecret: process.env.OKTA_SECRET,
|
|
||||||
issuer: process.env.OKTA_ISSUER,
|
|
||||||
}),
|
|
||||||
SlackProvider({
|
|
||||||
clientId: process.env.SLACK_ID,
|
|
||||||
clientSecret: process.env.SLACK_SECRET,
|
|
||||||
}),
|
|
||||||
AzureB2C({
|
|
||||||
clientId: process.env.AZURE_B2C_ID,
|
|
||||||
clientSecret: process.env.AZURE_B2C_SECRET,
|
|
||||||
tenantId: process.env.AZURE_B2C_TENANT_ID,
|
|
||||||
primaryUserFlow: process.env.AZURE_B2C_PRIMARY_USER_FLOW,
|
|
||||||
}),
|
|
||||||
OsuProvider({
|
|
||||||
clientId: process.env.OSU_CLIENT_ID,
|
|
||||||
clientSecret: process.env.OSU_CLIENT_SECRET,
|
|
||||||
}),
|
|
||||||
AppleProvider({
|
|
||||||
clientId: process.env.APPLE_ID,
|
|
||||||
clientSecret: process.env.APPLE_SECRET,
|
|
||||||
}),
|
|
||||||
PatreonProvider({
|
|
||||||
clientId: process.env.PATREON_ID,
|
|
||||||
clientSecret: process.env.PATREON_SECRET,
|
|
||||||
}),
|
|
||||||
TraktProvider({
|
|
||||||
clientId: process.env.TRAKT_ID,
|
|
||||||
clientSecret: process.env.TRAKT_SECRET,
|
|
||||||
}),
|
|
||||||
WorkOSProvider({
|
|
||||||
clientId: process.env.WORKOS_ID,
|
|
||||||
clientSecret: process.env.WORKOS_SECRET,
|
|
||||||
}),
|
|
||||||
BoxyHQSAMLProvider({
|
|
||||||
issuer: process.env.BOXYHQSAML_ISSUER ?? "https://example.com",
|
|
||||||
clientId: process.env.BOXYHQSAML_ID,
|
|
||||||
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,
|
|
||||||
theme: {
|
|
||||||
colorScheme: "auto",
|
|
||||||
logo: "https://next-auth.js.org/img/logo/logo-sm.png",
|
|
||||||
brandColor: "#1786fb",
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const authOptions: NextAuthOptions = {
|
||||||
|
adapter: adapters.noop(),
|
||||||
|
debug: true,
|
||||||
|
theme: {
|
||||||
|
logo: "https://next-auth.js.org/img/logo/logo-sm.png",
|
||||||
|
brandColor: "#1786fb",
|
||||||
|
},
|
||||||
|
providers: [
|
||||||
|
Credentials({
|
||||||
|
credentials: { password: { label: "Password", type: "password" } },
|
||||||
|
async authorize(credentials) {
|
||||||
|
if (credentials.password !== "pw") return null
|
||||||
|
return { name: "Fill Murray", email: "bill@fillmurray.com", image: "https://www.fillmurray.com/64/64" }
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
Apple({ clientId: process.env.APPLE_ID, clientSecret: process.env.APPLE_SECRET }),
|
||||||
|
Auth0({ clientId: process.env.AUTH0_ID, clientSecret: process.env.AUTH0_SECRET, issuer: process.env.AUTH0_ISSUER }),
|
||||||
|
AzureAD({ clientId: process.env.AZURE_AD_CLIENT_ID, clientSecret: process.env.AZURE_AD_CLIENT_SECRET, tenantId: process.env.AZURE_AD_TENANT_ID }),
|
||||||
|
AzureB2C({ clientId: process.env.AZURE_B2C_ID, clientSecret: process.env.AZURE_B2C_SECRET, issuer: process.env.AZURE_B2C_ISSUER }),
|
||||||
|
BoxyHQSAML({ issuer: "https://jackson-demo.boxyhq.com", clientId: "tenant=boxyhq.com&product=saml-demo.boxyhq.com", clientSecret: "dummy" }),
|
||||||
|
Cognito({ clientId: process.env.COGNITO_ID, clientSecret: process.env.COGNITO_SECRET, issuer: process.env.COGNITO_ISSUER }),
|
||||||
|
Discord({ clientId: process.env.DISCORD_ID, clientSecret: process.env.DISCORD_SECRET }),
|
||||||
|
DuendeIDS6({ clientId: "interactive.confidential", clientSecret: "secret", issuer: "https://demo.duendesoftware.com" }),
|
||||||
|
Facebook({ clientId: process.env.FACEBOOK_ID, clientSecret: process.env.FACEBOOK_SECRET }),
|
||||||
|
Foursquare({ clientId: process.env.FOURSQUARE_ID, clientSecret: process.env.FOURSQUARE_SECRET }),
|
||||||
|
Freshbooks({ clientId: process.env.FRESHBOOKS_ID, clientSecret: process.env.FRESHBOOKS_SECRET }),
|
||||||
|
GitHub({ clientId: process.env.GITHUB_ID, clientSecret: process.env.GITHUB_SECRET }),
|
||||||
|
Gitlab({ clientId: process.env.GITLAB_ID, clientSecret: process.env.GITLAB_SECRET }),
|
||||||
|
Google({ clientId: process.env.GOOGLE_ID, clientSecret: process.env.GOOGLE_SECRET }),
|
||||||
|
IDS4({ clientId: process.env.IDS4_ID, clientSecret: process.env.IDS4_SECRET, issuer: process.env.IDS4_ISSUER }),
|
||||||
|
Instagram({ clientId: process.env.INSTAGRAM_ID, clientSecret: process.env.INSTAGRAM_SECRET }),
|
||||||
|
Keycloak({ clientId: process.env.KEYCLOAK_ID, clientSecret: process.env.KEYCLOAK_SECRET, issuer: process.env.KEYCLOAK_ISSUER }),
|
||||||
|
Line({ clientId: process.env.LINE_ID, clientSecret: process.env.LINE_SECRET }),
|
||||||
|
LinkedIn({ clientId: process.env.LINKEDIN_ID, clientSecret: process.env.LINKEDIN_SECRET }),
|
||||||
|
Mailchimp({ clientId: process.env.MAILCHIMP_ID, clientSecret: process.env.MAILCHIMP_SECRET }),
|
||||||
|
Okta({ clientId: process.env.OKTA_ID, clientSecret: process.env.OKTA_SECRET, issuer: process.env.OKTA_ISSUER }),
|
||||||
|
Osu({ clientId: process.env.OSU_CLIENT_ID, clientSecret: process.env.OSU_CLIENT_SECRET }),
|
||||||
|
Patreon({ clientId: process.env.PATREON_ID, clientSecret: process.env.PATREON_SECRET }),
|
||||||
|
Slack({ clientId: process.env.SLACK_ID, clientSecret: process.env.SLACK_SECRET }),
|
||||||
|
Spotify({ clientId: process.env.SPOTIFY_ID, clientSecret: process.env.SPOTIFY_SECRET }),
|
||||||
|
Trakt({ clientId: process.env.TRAKT_ID, clientSecret: process.env.TRAKT_SECRET }),
|
||||||
|
Twitch({ clientId: process.env.TWITCH_ID, clientSecret: process.env.TWITCH_SECRET }),
|
||||||
|
Twitter({ version: "2.0", clientId: process.env.TWITTER_ID, clientSecret: process.env.TWITTER_SECRET }),
|
||||||
|
TwitterLegacy({ clientId: process.env.TWITTER_LEGACY_ID, clientSecret: process.env.TWITTER_LEGACY_SECRET }),
|
||||||
|
Vk({ clientId: process.env.VK_ID, clientSecret: process.env.VK_SECRET }),
|
||||||
|
Wikimedia({ clientId: process.env.WIKIMEDIA_ID, clientSecret: process.env.WIKIMEDIA_SECRET }),
|
||||||
|
WorkOS({ clientId: process.env.WORKOS_ID, clientSecret: process.env.WORKOS_SECRET }),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authOptions.adapter) {
|
||||||
|
authOptions.providers.unshift(
|
||||||
|
// NOTE: You can start a fake e-mail server with `pnpm email`
|
||||||
|
// and then go to `http://localhost:1080` in the browser
|
||||||
|
Email({ server: "smtp://127.0.0.1:1025?tls.rejectUnauthorized=false" })
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export default NextAuth(authOptions)
|
export default NextAuth(authOptions)
|
||||||
|
|||||||
@@ -2,6 +2,6 @@
|
|||||||
import { getToken } from "next-auth/jwt"
|
import { getToken } from "next-auth/jwt"
|
||||||
|
|
||||||
export default async (req, res) => {
|
export default async (req, res) => {
|
||||||
const token = await getToken({ req, secret: process.env.SECRET })
|
const token = await getToken({ req })
|
||||||
res.send(JSON.stringify(token, null, 2))
|
res.send(JSON.stringify(token, null, 2))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,14 @@ import { withAuth } from "next-auth/middleware"
|
|||||||
// More on how NextAuth.js middleware works: https://next-auth.js.org/configuration/nextjs#middleware
|
// More on how NextAuth.js middleware works: https://next-auth.js.org/configuration/nextjs#middleware
|
||||||
export default withAuth({
|
export default withAuth({
|
||||||
callbacks: {
|
callbacks: {
|
||||||
authorized: ({ req, token }) =>
|
authorized({ req, token }) {
|
||||||
// /admin requires admin role, but /me only requires the user to be logged in.
|
// `/admin` requires admin role
|
||||||
req.nextUrl.pathname !== "/admin" || token?.userRole === "admin",
|
if (req.nextUrl.pathname === "/admin") {
|
||||||
|
return token?.userRole === "admin"
|
||||||
|
}
|
||||||
|
// `/me` only requires the user to be logged in
|
||||||
|
return !!token
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -3,12 +3,12 @@ import { getToken } from "next-auth/jwt"
|
|||||||
|
|
||||||
import type { NextApiRequest, NextApiResponse } from "next"
|
import type { NextApiRequest, NextApiResponse } from "next"
|
||||||
|
|
||||||
const secret = process.env.NEXTAUTH_SECRET
|
|
||||||
|
|
||||||
export default async function handler(
|
export default async function handler(
|
||||||
req: NextApiRequest,
|
req: NextApiRequest,
|
||||||
res: NextApiResponse
|
res: NextApiResponse
|
||||||
) {
|
) {
|
||||||
const token = await getToken({ req, secret })
|
// If you don't have the NEXTAUTH_SECRET environment variable set,
|
||||||
|
// you will have to pass your secret as `secret` to `getToken`
|
||||||
|
const token = await getToken({ req })
|
||||||
res.send(JSON.stringify(token, null, 2))
|
res.send(JSON.stringify(token, null, 2))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
"eslint-plugin-svelte3": "^3.2.1",
|
"eslint-plugin-svelte3": "^3.2.1",
|
||||||
"prettier": "^2.5.1",
|
"prettier": "^2.5.1",
|
||||||
"prettier-plugin-svelte": "^2.5.0",
|
"prettier-plugin-svelte": "^2.5.0",
|
||||||
"svelte": "^3.44.0",
|
"svelte": "^3.49.0",
|
||||||
"svelte-check": "^2.2.6",
|
"svelte-check": "^2.2.6",
|
||||||
"svelte-preprocess": "^4.10.1",
|
"svelte-preprocess": "^4.10.1",
|
||||||
"tslib": "^2.3.1",
|
"tslib": "^2.3.1",
|
||||||
|
|||||||
@@ -1617,10 +1617,10 @@ svelte-preprocess@^4.0.0, svelte-preprocess@^4.10.1:
|
|||||||
sorcery "^0.10.0"
|
sorcery "^0.10.0"
|
||||||
strip-indent "^3.0.0"
|
strip-indent "^3.0.0"
|
||||||
|
|
||||||
svelte@^3.44.0:
|
svelte@^3.49.0:
|
||||||
version "3.46.4"
|
version "3.49.0"
|
||||||
resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.46.4.tgz#0c46bc4a3e20a2617a1b7dc43a722f9d6c084a38"
|
resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.49.0.tgz#5baee3c672306de1070c3b7888fc2204e36a4029"
|
||||||
integrity sha512-qKJzw6DpA33CIa+C/rGp4AUdSfii0DOTCzj/2YpSKKayw5WGSS624Et9L1nU1k2OVRS9vaENQXp2CVZNU+xvIg==
|
integrity sha512-+lmjic1pApJWDfPCpUUTc1m8azDqYCG1JN9YEngrx/hUyIcFJo6VZhj0A1Ai0wqoHcEIuQy+e9tk+4uDgdtsFA==
|
||||||
|
|
||||||
table@^6.0.9:
|
table@^6.0.9:
|
||||||
version "6.8.0"
|
version "6.8.0"
|
||||||
|
|||||||
@@ -177,13 +177,11 @@ If you do not define the options, NextAuth.js will use the default values for th
|
|||||||
#### wrap middleware
|
#### wrap middleware
|
||||||
|
|
||||||
```ts title="middleware.ts"
|
```ts title="middleware.ts"
|
||||||
import type { NextRequest } from "next/server"
|
|
||||||
import type { JWT } from "next-auth/jwt"
|
|
||||||
import { withAuth } from "next-auth/middleware"
|
import { withAuth } from "next-auth/middleware"
|
||||||
|
|
||||||
export default withAuth(
|
export default withAuth(
|
||||||
// `withAuth` can augment your Request with the user's token.
|
// `withAuth` augments your `Request` with the user's token.
|
||||||
function middleware(req: NextRequest & { nextauth: { token: JWT | null } }) {
|
function middleware(req) {
|
||||||
console.log(req.nextauth.token)
|
console.log(req.nextauth.token)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ Without these people, the project could not have become one of the most used aut
|
|||||||
- [Balázs Orbán](https://github.com/balazsorban44) - **Lead Maintainer**
|
- [Balázs Orbán](https://github.com/balazsorban44) - **Lead Maintainer**
|
||||||
- [Nico Domino](https://github.com/ndom91) - Maintainer (Documentation, Core)
|
- [Nico Domino](https://github.com/ndom91) - Maintainer (Documentation, Core)
|
||||||
- [Lluis Agusti](https://github.com/lluia) - Maintainer (Documentation, Testing, TypeScript)
|
- [Lluis Agusti](https://github.com/lluia) - Maintainer (Documentation, Testing, TypeScript)
|
||||||
|
- [Thang Huu Vu](https://github.com/ThangHuuVu) - Maintainer (Core, TypeScript)
|
||||||
|
|
||||||
## Special thanks
|
## Special thanks
|
||||||
|
|
||||||
|
|||||||
@@ -63,17 +63,32 @@ _If you use a custom credentials provider user accounts will not be persisted in
|
|||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>
|
<summary>
|
||||||
<h3 style={{display:"inline-block"}}>Can I use NextAuth.js with a website that does not use Next.js?</h3>
|
<h3 style={{display:"inline-block"}}>Can I use NextAuth.js with a framework different than Next.js?</h3>
|
||||||
</summary>
|
</summary>
|
||||||
<p>
|
<p>
|
||||||
|
|
||||||
NextAuth.js is designed for use with Next.js and Serverless.
|
NextAuth.js was originally designed for use with Next.js and Serverless. However, today you could use the NextAuth.js core with any other framework. Checkout the examples for <a href="https://github.com/nextauthjs/next-auth/tree/main/apps/example-gatsby" target="_blank">Gatsby</a> and <a href="https://github.com/nextauthjs/next-auth/tree/main/apps/playground-sveltekit" target="_blank">SvelteKit</a>. If you would add another integration with other frameworks, feel free to work on it and send a pull request. Make sure to check if there's any on-going work before open a new issue.
|
||||||
|
|
||||||
If you are using a different framework for your website, you can create a website that handles sign in with Next.js and then access those sessions on a website that does not use Next.js as long as the websites are on the same domain.
|
</p>
|
||||||
|
</details>
|
||||||
|
|
||||||
If you use NextAuth.js on a website with a different subdomain then the rest of your website (e.g. `auth.example.com` vs `www.example.com`) you will need to set a custom cookie domain policy for the Session Token cookie. (See also: [Cookies](/configuration/options#cookies))
|
<details>
|
||||||
|
<summary>
|
||||||
|
<h3 style={{display:"inline-block"}}>Can session generated by NextAuth.js be used by another website?</h3>
|
||||||
|
</summary>
|
||||||
|
<p>
|
||||||
|
|
||||||
NextAuth.js does not currently support automatically signing into sites on different top level domains (e.g. `www.example.com` vs `www.example.org`) using a single session.
|
**Same domain**: you can create a website that handles sign-in with NextAuth.js and then access those sessions on a website that does not use NextAuth.js as long as the websites are on the same domain.
|
||||||
|
|
||||||
|
**Same root domain, different subdomains**: If you use NextAuth.js on a website with a different subdomain than the rest of your website (e.g. `auth.example.com` vs. `www.example.com`) you will need to set a custom cookie domain policy for the Session Token cookie. (See also: [Cookies](/configuration/options#cookies)).
|
||||||
|
|
||||||
|
:::warning
|
||||||
|
Changing the default cookies domain policy is advanced and can lead to security issues if done correctly. Make sure you're aware of the security implication before proceeding.
|
||||||
|
:::
|
||||||
|
|
||||||
|
A working example can be found at <a href="https://github.com/vercel/examples/tree/main/solutions/subdomain-auth" target="_blank">this example repo</a>.
|
||||||
|
|
||||||
|
**Different root domains**: NextAuth.js does not currently support automatically signing into sites on different top-level domains (e.g. `www.example.com` vs. `www.example.org`) using a single session.
|
||||||
|
|
||||||
</p>
|
</p>
|
||||||
</details>
|
</details>
|
||||||
|
|||||||
@@ -61,8 +61,9 @@ You can protect server side rendered pages using the `unstable_getServerSession`
|
|||||||
You need to add this to every server rendered page you want to protect. Be aware, `unstable_getServerSession` takes slightly different arguments than the method it is replacing, `getSession`.
|
You need to add this to every server rendered page you want to protect. Be aware, `unstable_getServerSession` takes slightly different arguments than the method it is replacing, `getSession`.
|
||||||
|
|
||||||
```js title="pages/server-side-example.js"
|
```js title="pages/server-side-example.js"
|
||||||
import { useSession, unstable_getServerSession } from "next-auth/next"
|
import { unstable_getServerSession } from "next-auth/next"
|
||||||
import { authOptions } from "./api/auth/[...nextauth]"
|
import { authOptions } from "./api/auth/[...nextauth]"
|
||||||
|
import { useSession } from "next-auth/react"
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const { data: session } = useSession()
|
const { data: session } = useSession()
|
||||||
@@ -144,10 +145,9 @@ If you are using JSON Web Tokens you can use the `getToken()` helper to access t
|
|||||||
// This is an example of how to read a JSON Web Token from an API route
|
// 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"
|
||||||
|
|
||||||
const secret = process.env.SECRET
|
|
||||||
|
|
||||||
export default async (req, res) => {
|
export default async (req, res) => {
|
||||||
const token = await getToken({ req, secret })
|
// If you don't have NEXTAUTH_SECRET set, you will have to pass your secret as `secret` to `getToken`
|
||||||
|
const token = await getToken({ req })
|
||||||
if (token) {
|
if (token) {
|
||||||
// Signed in
|
// Signed in
|
||||||
console.log("JSON Web Token", JSON.stringify(token, null, 2))
|
console.log("JSON Web Token", JSON.stringify(token, null, 2))
|
||||||
|
|||||||
11
package.json
11
package.json
@@ -11,6 +11,7 @@
|
|||||||
"test": "turbo run test --concurrency=1 --filter=!@next-auth/pouchdb-adapter --filter=!next-auth-* --filter=[HEAD^1]",
|
"test": "turbo run test --concurrency=1 --filter=!@next-auth/pouchdb-adapter --filter=!next-auth-* --filter=[HEAD^1]",
|
||||||
"setup": "turbo run setup",
|
"setup": "turbo run setup",
|
||||||
"dev": "pnpm dev:app",
|
"dev": "pnpm dev:app",
|
||||||
|
"email": "cd apps/dev && pnpm email",
|
||||||
"dev:app": "turbo run dev --parallel --no-deps --no-cache --filter=next-auth-app",
|
"dev:app": "turbo run dev --parallel --no-deps --no-cache --filter=next-auth-app",
|
||||||
"dev:docs": "turbo run dev --parallel --no-deps --no-cache --filter=next-auth-docs",
|
"dev:docs": "turbo run dev --parallel --no-deps --no-cache --filter=next-auth-docs",
|
||||||
"version:pr": "node ./config/version-pr",
|
"version:pr": "node ./config/version-pr",
|
||||||
@@ -47,7 +48,15 @@
|
|||||||
},
|
},
|
||||||
"prettier": {
|
"prettier": {
|
||||||
"semi": false,
|
"semi": false,
|
||||||
"singleQuote": false
|
"singleQuote": false,
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": "apps/dev/pages/api/auth/[...nextauth].ts",
|
||||||
|
"options": {
|
||||||
|
"printWidth": 150
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"parser": "@typescript-eslint/parser",
|
"parser": "@typescript-eslint/parser",
|
||||||
|
|||||||
@@ -39,6 +39,7 @@
|
|||||||
"@types/uuid": "^8.3.3",
|
"@types/uuid": "^8.3.3",
|
||||||
"@upstash/redis": "^1.0.1",
|
"@upstash/redis": "^1.0.1",
|
||||||
"dotenv": "^10.0.0",
|
"dotenv": "^10.0.0",
|
||||||
|
"isomorphic-fetch": "3.0.0",
|
||||||
"jest": "^27.4.3",
|
"jest": "^27.4.3",
|
||||||
"next-auth": "workspace:*"
|
"next-auth": "workspace:*"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import "isomorphic-fetch"
|
||||||
import { Redis } from "@upstash/redis"
|
import { Redis } from "@upstash/redis"
|
||||||
import { runBasicTests } from "@next-auth/adapter-test"
|
import { runBasicTests } from "@next-auth/adapter-test"
|
||||||
import { hydrateDates, UpstashRedisAdapter } from "../src"
|
import { hydrateDates, UpstashRedisAdapter } from "../src"
|
||||||
@@ -7,41 +8,42 @@ if (!process.env.UPSTASH_REDIS_URL || !process.env.UPSTASH_REDIS_KEY) {
|
|||||||
test("Skipping UpstashRedisAdapter tests, since required environment variables aren't available", () => {
|
test("Skipping UpstashRedisAdapter tests, since required environment variables aren't available", () => {
|
||||||
expect(true).toBe(true)
|
expect(true).toBe(true)
|
||||||
})
|
})
|
||||||
} else {
|
process.exit(0)
|
||||||
const client = new Redis({
|
|
||||||
url: process.env.UPSTASH_REDIS_URL,
|
|
||||||
token: process.env.UPSTASH_REDIS_KEY,
|
|
||||||
})
|
|
||||||
|
|
||||||
runBasicTests({
|
|
||||||
adapter: UpstashRedisAdapter(client, { baseKeyPrefix: "testApp:" }),
|
|
||||||
db: {
|
|
||||||
async user(id: string) {
|
|
||||||
const data = await client.get<object>(`testApp:user:${id}`)
|
|
||||||
if (!data) return null
|
|
||||||
return hydrateDates(data)
|
|
||||||
},
|
|
||||||
async account({ provider, providerAccountId }) {
|
|
||||||
const data = await client.get<object>(
|
|
||||||
`testApp:user:account:${provider}:${providerAccountId}`
|
|
||||||
)
|
|
||||||
if (!data) return null
|
|
||||||
return hydrateDates(data)
|
|
||||||
},
|
|
||||||
async session(sessionToken) {
|
|
||||||
const data = await client.get<object>(
|
|
||||||
`testApp:user:session:${sessionToken}`
|
|
||||||
)
|
|
||||||
if (!data) return null
|
|
||||||
return hydrateDates(data)
|
|
||||||
},
|
|
||||||
async verificationToken(where) {
|
|
||||||
const data = await client.get<object>(
|
|
||||||
`testApp:user:token:${where.identifier}`
|
|
||||||
)
|
|
||||||
if (!data) return null
|
|
||||||
return hydrateDates(data)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const client = new Redis({
|
||||||
|
url: process.env.UPSTASH_REDIS_URL,
|
||||||
|
token: process.env.UPSTASH_REDIS_KEY,
|
||||||
|
})
|
||||||
|
|
||||||
|
runBasicTests({
|
||||||
|
adapter: UpstashRedisAdapter(client, { baseKeyPrefix: "testApp:" }),
|
||||||
|
db: {
|
||||||
|
async user(id: string) {
|
||||||
|
const data = await client.get<object>(`testApp:user:${id}`)
|
||||||
|
if (!data) return null
|
||||||
|
return hydrateDates(data)
|
||||||
|
},
|
||||||
|
async account({ provider, providerAccountId }) {
|
||||||
|
const data = await client.get<object>(
|
||||||
|
`testApp:user:account:${provider}:${providerAccountId}`
|
||||||
|
)
|
||||||
|
if (!data) return null
|
||||||
|
return hydrateDates(data)
|
||||||
|
},
|
||||||
|
async session(sessionToken) {
|
||||||
|
const data = await client.get<object>(
|
||||||
|
`testApp:user:session:${sessionToken}`
|
||||||
|
)
|
||||||
|
if (!data) return null
|
||||||
|
return hydrateDates(data)
|
||||||
|
},
|
||||||
|
async verificationToken(where) {
|
||||||
|
const data = await client.get<object>(
|
||||||
|
`testApp:user:token:${where.identifier}`
|
||||||
|
)
|
||||||
|
if (!data) return null
|
||||||
|
return hydrateDates(data)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "next-auth",
|
"name": "next-auth",
|
||||||
"version": "4.10.0",
|
"version": "4.10.1",
|
||||||
"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",
|
||||||
@@ -8,7 +8,8 @@
|
|||||||
"contributors": [
|
"contributors": [
|
||||||
"Balázs Orbán <info@balazsorban.com>",
|
"Balázs Orbán <info@balazsorban.com>",
|
||||||
"Nico Domino <yo@ndo.dev>",
|
"Nico Domino <yo@ndo.dev>",
|
||||||
"Lluis Agusti <hi@llu.lu>"
|
"Lluis Agusti <hi@llu.lu>",
|
||||||
|
"Thang Huu Vu <thvu@hey.com>"
|
||||||
],
|
],
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"module": "index.js",
|
"module": "index.js",
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import logger from "../utils/logger"
|
|||||||
import parseUrl from "../utils/parse-url"
|
import parseUrl from "../utils/parse-url"
|
||||||
import { adapterErrorHandler, eventsErrorHandler } from "./errors"
|
import { adapterErrorHandler, eventsErrorHandler } from "./errors"
|
||||||
import parseProviders from "./lib/providers"
|
import parseProviders from "./lib/providers"
|
||||||
import createSecret from "./lib/utils"
|
import { createSecret } from "./lib/utils"
|
||||||
import * as cookie from "./lib/cookie"
|
import * as cookie from "./lib/cookie"
|
||||||
import * as jwt from "../jwt"
|
import * as jwt from "../jwt"
|
||||||
import { defaultCallbacks } from "./lib/default-callbacks"
|
import { defaultCallbacks } from "./lib/default-callbacks"
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export function hashToken(token: string, options: InternalOptions<"email">) {
|
|||||||
* If no secret option is specified then it creates one on the fly
|
* If no secret option is specified then it creates one on the fly
|
||||||
* based on options passed here. If options contains unique data, such as
|
* based on options passed here. If options contains unique data, such as
|
||||||
* OAuth provider secrets and database credentials it should be sufficent. If no secret provided in production, we throw an error. */
|
* OAuth provider secrets and database credentials it should be sufficent. If no secret provided in production, we throw an error. */
|
||||||
export default function createSecret(params: {
|
export function createSecret(params: {
|
||||||
userOptions: NextAuthOptions
|
userOptions: NextAuthOptions
|
||||||
url: InternalUrl
|
url: InternalUrl
|
||||||
}) {
|
}) {
|
||||||
@@ -36,6 +36,7 @@ export default function createSecret(params: {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
userOptions.secret ??
|
userOptions.secret ??
|
||||||
|
// TODO: Remove falling back to default secret, and error in dev if one isn't provided
|
||||||
createHash("sha256")
|
createHash("sha256")
|
||||||
.update(JSON.stringify({ ...url, ...userOptions }))
|
.update(JSON.stringify({ ...url, ...userOptions }))
|
||||||
.digest("hex")
|
.digest("hex")
|
||||||
|
|||||||
@@ -92,10 +92,14 @@ export interface NextAuthMiddlewareOptions {
|
|||||||
secret?: string
|
secret?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: `NextMiddleware` should allow returning `void`
|
||||||
|
// Simplify when https://github.com/vercel/next.js/pull/38625 is merged.
|
||||||
|
type NextMiddlewareResult = ReturnType<NextMiddleware> | void // eslint-disable-line @typescript-eslint/no-invalid-void-type
|
||||||
|
|
||||||
async function handleMiddleware(
|
async function handleMiddleware(
|
||||||
req: NextRequest,
|
req: NextRequest,
|
||||||
options: NextAuthMiddlewareOptions | undefined,
|
options: NextAuthMiddlewareOptions | undefined,
|
||||||
onSuccess?: (token: JWT | null) => Promise<any>
|
onSuccess?: (token: JWT | null) => Promise<NextMiddlewareResult>
|
||||||
) {
|
) {
|
||||||
const signInPage = options?.pages?.signIn ?? "/api/auth/signin"
|
const signInPage = options?.pages?.signIn ?? "/api/auth/signin"
|
||||||
const errorPage = options?.pages?.error ?? "/api/auth/error"
|
const errorPage = options?.pages?.error ?? "/api/auth/error"
|
||||||
@@ -143,12 +147,21 @@ async function handleMiddleware(
|
|||||||
return NextResponse.redirect(signInUrl)
|
return NextResponse.redirect(signInUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface NextRequestWithAuth extends NextRequest {
|
||||||
|
nextauth: { token: JWT | null }
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NextMiddlewareWithAuth = (
|
||||||
|
request: NextRequestWithAuth,
|
||||||
|
event: NextFetchEvent
|
||||||
|
) => NextMiddlewareResult | Promise<NextMiddlewareResult>
|
||||||
|
|
||||||
export type WithAuthArgs =
|
export type WithAuthArgs =
|
||||||
| [NextRequest]
|
| [NextRequestWithAuth]
|
||||||
| [NextRequest, NextFetchEvent]
|
| [NextRequestWithAuth, NextFetchEvent]
|
||||||
| [NextRequest, NextAuthMiddlewareOptions]
|
| [NextRequestWithAuth, NextAuthMiddlewareOptions]
|
||||||
| [NextMiddleware]
|
| [NextMiddlewareWithAuth]
|
||||||
| [NextMiddleware, NextAuthMiddlewareOptions]
|
| [NextMiddlewareWithAuth, NextAuthMiddlewareOptions]
|
||||||
| [NextAuthMiddlewareOptions]
|
| [NextAuthMiddlewareOptions]
|
||||||
| []
|
| []
|
||||||
|
|
||||||
@@ -176,9 +189,9 @@ export function withAuth(...args: WithAuthArgs) {
|
|||||||
if (typeof args[0] === "function") {
|
if (typeof args[0] === "function") {
|
||||||
const middleware = args[0]
|
const middleware = args[0]
|
||||||
const options = args[1] as NextAuthMiddlewareOptions | undefined
|
const options = args[1] as NextAuthMiddlewareOptions | undefined
|
||||||
return async (...args: Parameters<NextMiddleware>) =>
|
return async (...args: Parameters<NextMiddlewareWithAuth>) =>
|
||||||
await handleMiddleware(args[0], options, async (token) => {
|
await handleMiddleware(args[0], options, async (token) => {
|
||||||
;(args[0] as any).nextauth = { token }
|
args[0].nextauth = { token }
|
||||||
return await middleware(...args)
|
return await middleware(...args)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,16 +19,19 @@ export interface AzureB2CProfile extends Record<string, any> {
|
|||||||
|
|
||||||
export default function AzureADB2C<P extends AzureB2CProfile>(
|
export default function AzureADB2C<P extends AzureB2CProfile>(
|
||||||
options: OAuthUserConfig<P> & {
|
options: OAuthUserConfig<P> & {
|
||||||
primaryUserFlow: string
|
primaryUserFlow?: string
|
||||||
tenantId: string
|
tenantId?: string
|
||||||
}
|
}
|
||||||
): OAuthConfig<P> {
|
): OAuthConfig<P> {
|
||||||
const { tenantId, primaryUserFlow } = options
|
const { tenantId, primaryUserFlow } = options
|
||||||
|
const issuer =
|
||||||
|
options.issuer ??
|
||||||
|
`https://${tenantId}.b2clogin.com/${tenantId}.onmicrosoft.com/${primaryUserFlow}/v2.0`
|
||||||
return {
|
return {
|
||||||
id: "azure-ad-b2c",
|
id: "azure-ad-b2c",
|
||||||
name: "Azure Active Directory B2C",
|
name: "Azure Active Directory B2C",
|
||||||
type: "oauth",
|
type: "oauth",
|
||||||
wellKnown: `https://${tenantId}.b2clogin.com/${tenantId}.onmicrosoft.com/${primaryUserFlow}/v2.0/.well-known/openid-configuration`,
|
wellKnown: `${issuer}/.well-known/openid-configuration`,
|
||||||
idToken: true,
|
idToken: true,
|
||||||
profile(profile) {
|
profile(profile) {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import type { OAuthConfig, OAuthUserConfig } from "."
|
import type { OAuthConfig, OAuthUserConfig } from "."
|
||||||
|
|
||||||
/**
|
/** @see https://docs.github.com/en/rest/users/users#get-the-authenticated-user */
|
||||||
* Source https://docs.github.com/en/rest/users/users#get-the-authenticated-user
|
|
||||||
*/
|
|
||||||
export interface GithubProfile extends Record<string, any> {
|
export interface GithubProfile extends Record<string, any> {
|
||||||
login: string
|
login: string
|
||||||
id: number
|
id: number
|
||||||
@@ -55,7 +53,7 @@ export interface GithubEmail extends Record<string, any> {
|
|||||||
email: string
|
email: string
|
||||||
primary: boolean
|
primary: boolean
|
||||||
verified: boolean
|
verified: boolean
|
||||||
visibility: string | null
|
visibility: "public" | "private"
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Github<P extends GithubProfile>(
|
export default function Github<P extends GithubProfile>(
|
||||||
@@ -67,29 +65,25 @@ export default function Github<P extends GithubProfile>(
|
|||||||
type: "oauth",
|
type: "oauth",
|
||||||
authorization: {
|
authorization: {
|
||||||
url: "https://github.com/login/oauth/authorize",
|
url: "https://github.com/login/oauth/authorize",
|
||||||
params: { scope: "read:user+user:email" },
|
params: { scope: "read:user user:email" },
|
||||||
},
|
},
|
||||||
token: "https://github.com/login/oauth/access_token",
|
token: "https://github.com/login/oauth/access_token",
|
||||||
userinfo: {
|
userinfo: {
|
||||||
url: "https://api.github.com/user",
|
url: "https://api.github.com/user",
|
||||||
async request({ client, tokens }) {
|
async request({ client, tokens }) {
|
||||||
// Get base profile
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
const profile = await client.userinfo(tokens.access_token!)
|
const profile = await client.userinfo(tokens.access_token!)
|
||||||
|
|
||||||
// If user has email hidden, get their primary email from the GitHub API
|
|
||||||
if (!profile.email) {
|
if (!profile.email) {
|
||||||
const emails: GithubEmail[] = await (
|
// If the user does not have a public email, get another via the GitHub API
|
||||||
await fetch("https://api.github.com/user/emails", {
|
// See https://docs.github.com/en/rest/users/emails#list-public-email-addresses-for-the-authenticated-user
|
||||||
headers: { Authorization: `token ${tokens.access_token}` },
|
const res = await fetch("https://api.github.com/user/emails", {
|
||||||
})
|
headers: { Authorization: `token ${tokens.access_token}` },
|
||||||
).json()
|
})
|
||||||
|
|
||||||
if (emails?.length > 0) {
|
if (res.ok) {
|
||||||
// Get primary email
|
const emails: GithubEmail[] = await res.json()
|
||||||
profile.email = emails.find((email) => email.primary)?.email
|
profile.email = (emails.find((e) => e.primary) ?? emails[0]).email
|
||||||
// And if for some reason it doesn't exist, just use the first
|
|
||||||
if (!profile.email) profile.email = emails[0].email
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
/** @type {import("src/providers").OAuthProvider} */
|
|
||||||
/** @type {import(".").OAuthProvider} */
|
|
||||||
export default function GitLab(options) {
|
|
||||||
return {
|
|
||||||
id: "gitlab",
|
|
||||||
name: "GitLab",
|
|
||||||
type: "oauth",
|
|
||||||
authorization: "https://gitlab.com/oauth/authorize?scope=read_user",
|
|
||||||
token: "https://gitlab.com/oauth/token",
|
|
||||||
userinfo: "https://gitlab.com/api/v4/user",
|
|
||||||
checks: ["pkce", "state"],
|
|
||||||
profile(profile) {
|
|
||||||
return {
|
|
||||||
id: profile.id,
|
|
||||||
name: profile.username,
|
|
||||||
email: profile.email,
|
|
||||||
image: profile.avatar_url,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
options,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
72
packages/next-auth/src/providers/gitlab.ts
Normal file
72
packages/next-auth/src/providers/gitlab.ts
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import type { OAuthConfig, OAuthUserConfig } from "."
|
||||||
|
|
||||||
|
export interface GitLabProfile extends Record<string, any> {
|
||||||
|
id: number
|
||||||
|
username: string
|
||||||
|
email: string
|
||||||
|
name: string
|
||||||
|
state: string
|
||||||
|
avatar_url: string
|
||||||
|
web_url: string
|
||||||
|
created_at: string
|
||||||
|
bio: string
|
||||||
|
location?: string
|
||||||
|
public_email: string
|
||||||
|
skype: string
|
||||||
|
linkedin: string
|
||||||
|
twitter: string
|
||||||
|
website_url: string
|
||||||
|
organization: string
|
||||||
|
job_title: string
|
||||||
|
pronouns: string
|
||||||
|
bot: boolean
|
||||||
|
work_information?: string
|
||||||
|
followers: number
|
||||||
|
following: number
|
||||||
|
local_time: string
|
||||||
|
last_sign_in_at: string
|
||||||
|
confirmed_at: string
|
||||||
|
theme_id: number
|
||||||
|
last_activity_on: string
|
||||||
|
color_scheme_id: number
|
||||||
|
projects_limit: number
|
||||||
|
current_sign_in_at: string
|
||||||
|
identities: Array<{
|
||||||
|
provider: string
|
||||||
|
extern_uid: string
|
||||||
|
}>
|
||||||
|
can_create_group: boolean
|
||||||
|
can_create_project: boolean
|
||||||
|
two_factor_enabled: boolean
|
||||||
|
external: boolean
|
||||||
|
private_profile: boolean
|
||||||
|
commit_email: string
|
||||||
|
shared_runners_minutes_limit: number
|
||||||
|
extra_shared_runners_minutes_limit: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function GitLab<P extends GitLabProfile>(
|
||||||
|
options: OAuthUserConfig<P>
|
||||||
|
): OAuthConfig<P> {
|
||||||
|
return {
|
||||||
|
id: "gitlab",
|
||||||
|
name: "GitLab",
|
||||||
|
type: "oauth",
|
||||||
|
authorization: {
|
||||||
|
url: "https://gitlab.com/oauth/authorize",
|
||||||
|
params: { scope: "read_user" },
|
||||||
|
},
|
||||||
|
token: "https://gitlab.com/oauth/token",
|
||||||
|
userinfo: "https://gitlab.com/api/v4/user",
|
||||||
|
checks: ["pkce", "state"],
|
||||||
|
profile(profile) {
|
||||||
|
return {
|
||||||
|
id: profile.id.toString(),
|
||||||
|
name: profile.name ?? profile.username,
|
||||||
|
email: profile.email,
|
||||||
|
image: profile.avatar_url,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
options,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -175,7 +175,11 @@ export async function getProviders() {
|
|||||||
export async function signIn<
|
export async function signIn<
|
||||||
P extends RedirectableProviderType | undefined = undefined
|
P extends RedirectableProviderType | undefined = undefined
|
||||||
>(
|
>(
|
||||||
provider?: LiteralUnion<P extends RedirectableProviderType ? P | BuiltInProviderType : BuiltInProviderType>,
|
provider?: LiteralUnion<
|
||||||
|
P extends RedirectableProviderType
|
||||||
|
? P | BuiltInProviderType
|
||||||
|
: BuiltInProviderType
|
||||||
|
>,
|
||||||
options?: SignInOptions,
|
options?: SignInOptions,
|
||||||
authorizationParams?: SignInAuthorizationParams
|
authorizationParams?: SignInAuthorizationParams
|
||||||
): Promise<
|
): Promise<
|
||||||
@@ -224,6 +228,7 @@ export async function signIn<
|
|||||||
|
|
||||||
const data = await res.json()
|
const data = await res.json()
|
||||||
|
|
||||||
|
// TODO: Do not redirect for Credentials and Email providers by default in next major
|
||||||
if (redirect || !isSupportingReturn) {
|
if (redirect || !isSupportingReturn) {
|
||||||
const url = data.url ?? callbackUrl
|
const url = data.url ?? callbackUrl
|
||||||
window.location.href = url
|
window.location.href = url
|
||||||
|
|||||||
@@ -15,7 +15,9 @@
|
|||||||
"skipDefaultLibCheck": true,
|
"skipDefaultLibCheck": true,
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"outDir": ".",
|
"outDir": ".",
|
||||||
|
"paths": {
|
||||||
|
"next": ["node_modules/next"]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"exclude": ["./*.js", "./*.d.ts", "config", "**/__tests__", "tests"]
|
"exclude": ["./*.js", "./*.d.ts", "config", "**/__tests__", "tests"]
|
||||||
}
|
}
|
||||||
|
|||||||
2
pnpm-lock.yaml
generated
2
pnpm-lock.yaml
generated
@@ -389,6 +389,7 @@ importers:
|
|||||||
'@types/uuid': ^8.3.3
|
'@types/uuid': ^8.3.3
|
||||||
'@upstash/redis': ^1.0.1
|
'@upstash/redis': ^1.0.1
|
||||||
dotenv: ^10.0.0
|
dotenv: ^10.0.0
|
||||||
|
isomorphic-fetch: 3.0.0
|
||||||
jest: ^27.4.3
|
jest: ^27.4.3
|
||||||
next-auth: workspace:*
|
next-auth: workspace:*
|
||||||
uuid: ^8.3.2
|
uuid: ^8.3.2
|
||||||
@@ -400,6 +401,7 @@ importers:
|
|||||||
'@types/uuid': 8.3.4
|
'@types/uuid': 8.3.4
|
||||||
'@upstash/redis': 1.7.0
|
'@upstash/redis': 1.7.0
|
||||||
dotenv: 10.0.0
|
dotenv: 10.0.0
|
||||||
|
isomorphic-fetch: 3.0.0
|
||||||
jest: 27.5.1
|
jest: 27.5.1
|
||||||
next-auth: link:../next-auth
|
next-auth: link:../next-auth
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user