Compare commits

...

31 Commits

Author SHA1 Message Date
GitHub Actions
25388de027 chore(release): bump package version(s) [skip ci] 2023-04-18 17:45:29 +00:00
Balázs Orbán
ad77e1c2b7 chore: trigger CI 2023-04-18 19:31:51 +02:00
Balázs Orbán
cd654c3001 chore: trigger CI 2023-04-18 19:09:53 +02:00
Balázs Orbán
6f9ca4143d fix: detect origin when instanceof Request check fails (#7303) 2023-04-18 17:46:49 +01:00
Balázs Orbán
e97b27414a Merge branch 'main' of github.com:nextauthjs/next-auth 2023-04-17 11:41:02 +02:00
Balázs Orbán
9018939ee7 docs: clean up databases intro page
#7221
2023-04-17 11:40:59 +02:00
Raul
c2fc41b44d chore: fix "Contributing guide" link (#7279) 2023-04-17 10:36:23 +01:00
Chris Hayes
01d7eb4feb docs: Remove --save from install command (#7277)
Remove --save from install command

--save is no longer needed on npm install.
2023-04-17 10:35:48 +01:00
Balázs Orbán
2388c20cc6 Merge branch 'main' of github.com:nextauthjs/next-auth 2023-04-17 11:32:26 +02:00
Balázs Orbán
9a1bef9e72 chore: skip adapters in docs dev by default 2023-04-17 11:32:23 +02:00
Balázs Orbán
35a72d2273 chore: update typedoc dependencies 2023-04-17 11:32:09 +02:00
Abdulaziz Askaraliev
5f1b75a7a2 fix(providers): fix type definition and docs for yandex (#7170)
Co-authored-by: Thang Vu <hi@thvu.dev>
2023-04-16 14:47:06 +07:00
Thang Vu
fa58065951 chore: move next-auth from v4 to main (#7265) 2023-04-15 17:02:46 +01:00
Balázs Orbán
b31f2af66c feat: misc improvements (#7228)
* tweak types, fix typos

* filter non-oauth files when generating provider types

* allow implicit config invoke

* remove workaround for multiple cookie settings in Next.js

* feat: return `null` when session does not exist

* error on missing checks when configured
2023-04-12 11:40:55 +01:00
Prana Adiwira
71bb6f2590 fix(providers): Use the proper check for Reddit (#7224)
Reddit expects the `state` parameter

https://github.com/reddit-archive/reddit/wiki/OAuth2#authorization
2023-04-12 11:37:31 +01:00
Balázs Orbán
6c07331cc5 chore: upgrade turbo 2023-04-06 12:58:10 +02:00
Saurav Maheshkar
c8ef94b2be chore: move prettier and eslint configs under package.json (#7145) 2023-04-06 12:57:16 +02:00
jakzo
75a59fbd92 chore(docs): fix dynamodb typo (#7130)
fix: typo
2023-04-06 12:57:09 +02:00
Balázs Orbán
3dd47b0735 docs(example): remove unstable_ prefix 2023-03-31 05:01:58 +02:00
Balázs Orbán
4dc1d421f8 docs: mention client in OAuth config options
Related issue #7114
2023-03-30 18:34:30 +02:00
Balázs Orbán
99ca67f1cf docs: fix typo 2023-03-28 13:59:08 +02:00
Balázs Orbán
a087df8494 docs: fix some links 2023-03-28 13:47:53 +02:00
Sai Srikar Dumpeti
1aa4994de6 docs: respect color scheme (#7076) 2023-03-28 04:06:21 +02:00
Alan Hoskins
88023f69b9 fix(docs): remove extra install (#7081) 2023-03-27 15:47:32 +02:00
Alan Hoskins
b02057a72d fix(docs): fix broken links links (#7083)
Co-authored-by: Alan Hoskins <ahoskins@knowland.com>
2023-03-27 15:46:43 +02:00
Balázs Orbán
400da8c766 fix(providers): mention Email Address as required for Azure B2C
closes #7071
2023-03-27 15:44:23 +02:00
Andres Rodriguez
b48104801b chore(provider): added svg for Reddit (#7050)
Added svg for Reddit

Co-authored-by: Nico Domino <yo@ndo.dev>
2023-03-27 09:36:47 +02:00
Balázs Orbán
ccbbc800d2 docs: rephrase buttons on landing page 2023-03-27 02:06:33 +02:00
Abdulaziz Askaraliev
d7888263ca fix(providers): update Yandex to TypeScript (#7054)
* fix(providers): yandex add typescript.

* fix(providers): yandex add avatar to scope

* fix(providers): Yandex - add types & avatar scope

* fix(providers): Yandex - permissions list

* Apply suggestions from code review

* Apply suggestions from code review

* docs(provider): added comments for

* revert yandex.ts from next-auth/providers/

* fix(providers): yandex fix typo

* revert

* Update [...nextauth].ts

* Update yandex.ts

* Update yandex.ts

* Update [...nextauth].ts

---------

Co-authored-by: Balázs Orbán <info@balazsorban.com>
2023-03-27 00:38:06 +01:00
Balázs Orbán
47d3151410 Merge branch 'main' of github.com:nextauthjs/next-auth 2023-03-27 01:32:54 +02:00
Balázs Orbán
7d264860ab chore: package builds as docs#dev task dependencies 2023-03-27 01:32:50 +02:00
251 changed files with 4583 additions and 3102 deletions

View File

@@ -1,70 +0,0 @@
.eslintrc.js
.cache-loader
.DS_Store
.pnpm-debug.log
.turbo
.vscode/generated*
/_work
/actions-runner
node_modules
patches
pnpm-lock.yaml
.github/actions/issue-validator/index.mjs
*.cjs
*.js
*.d.ts
*.d.ts.map
.svelte-kit
.next
.nuxt
# --------------- Docs ---------------
.docusaurus
build
docs/docs/reference/core
docs/docs/reference/sveltekit
static
# --------------- Packages ---------------
coverage
dist
# @auth/core
packages/core/src/providers/oauth-types.ts
packages/core/src/lib/pages/styles.ts
# @auth/sveltekit
packages/frameworks-sveltekit/package
packages/frameworks-sveltekit/vite.config.{js,ts}.timestamp-*
# next-auth
packages/next-auth/src/providers/oauth-types.ts
packages/next-auth/css/index.css
# Adapters
.branches
db.sqlite
dev.db
dynamodblocal-bin
firebase-debug.log
firestore-debug.log
migrations
test.schema.gql
# --------------- Apps ---------------
# Examples should have their own Prettier config since they are templates too
apps/example-sveltekit
# Development app
apps
# --------------- Tests ---------------
# TODO: these should be linted
packages/**/*test*

View File

@@ -1,75 +0,0 @@
// @ts-check
/** @type {import("eslint").ESLint.ConfigData} */
module.exports = {
env: { browser: true, es2022: true, node: true },
extends: ["eslint:recommended", "prettier"],
overrides: [
{
files: ["*.ts", "*.tsx"],
parser: "@typescript-eslint/parser",
parserOptions: {
project: ["./packages/**/tsconfig.json", "./apps/**/tsconfig.json"],
},
settings: { react: { version: "18" } },
extends: [
"plugin:react/recommended",
"plugin:react/jsx-runtime",
"standard-with-typescript",
"prettier",
],
rules: {
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/method-signature-style": "off",
"@typescript-eslint/naming-convention": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/restrict-template-expressions": "off",
"@typescript-eslint/strict-boolean-expressions": "off",
"react/prop-types": "off",
"react/no-unescaped-entities": "off",
},
},
{
files: ["*.test.ts", "*.test.js"],
extends: ["plugin:jest/recommended"],
env: { jest: true },
},
{
files: ["docs/**"],
plugins: ["@docusaurus"],
extends: ["plugin:@docusaurus/recommended"],
},
{
// TODO: Expand to all packages
files: ["packages/{core,sveltekit}/*.ts"],
plugins: ["jsdoc"],
extends: ["plugin:jsdoc/recommended"],
rules: {
"jsdoc/require-param": "off",
"jsdoc/require-returns": "off",
"jsdoc/require-jsdoc": [
"warn",
{ publicOnly: true, enableFixer: false },
],
"jsdoc/no-multi-asterisks": ["warn", { allowWhitespace: true }],
"jsdoc/tag-lines": "off",
},
},
{
files: ["packages/frameworks-sveltekit"],
plugins: ["svelte3"],
overrides: [{ files: ["*.svelte"], processor: "svelte3/svelte3" }],
settings: {
"svelte3/typescript": () => require("typescript"),
},
parserOptions: { sourceType: "module", ecmaVersion: 2020 },
env: { browser: true, es2017: true, node: true },
},
],
parserOptions: {
sourceType: "module",
ecmaVersion: "latest",
ecmaFeatures: { jsx: true },
},
root: true,
}

1
.gitignore vendored
View File

@@ -43,6 +43,7 @@ packages/*/*.d.ts.map
apps/dev/src/css
apps/dev/prisma/migrations
apps/dev/typeorm
apps/dev/nextjs-2
# VS
/.vs/slnx.sqlite-journal

View File

@@ -1,22 +0,0 @@
// @ts-check
/** @type {import("prettier").Config} */
module.exports = {
semi: false,
singleQuote: false,
overrides: [
{
files: [
"apps/dev/nextjs/pages/api/auth/[...nextauth].ts",
"docs/{sidebars,docusaurus.config}.js",
],
options: { printWidth: 150 },
},
{
files: ["**/*package.json"],
options: {
trailingComma: "none",
},
},
],
}

View File

@@ -0,0 +1,58 @@
# Rename file to .env.local (or .env) and populate values
# to be able to run the dev app
NEXTAUTH_URL=http://localhost:3000
# You can use `openssl rand -hex 32` or
# https://generate-secret.vercel.app/32 to generate a secret.
# Note: Changing a secret may invalidate existing sessions
# and/or verification tokens.
NEXTAUTH_SECRET=secret
AUTH0_ID=
AUTH0_SECRET=
AUTH0_ISSUER=
KEYCLOAK_ID=
KEYCLOAK_SECRET=
KEYCLOAK_ISSUER=
IDS4_ID=
IDS4_SECRET=
IDS4_ISSUER=
GITHUB_ID=
GITHUB_SECRET=
TWITCH_ID=
TWITCH_SECRET=
TWITTER_ID=
TWITTER_SECRET=
LINE_ID=
LINE_SECRET=
TRAKT_ID=
TRAKT_SECRET=
# Example configuration for a Gmail account (will need SMTP enabled)
EMAIL_SERVER=smtps://user@gmail.com:password@smtp.gmail.com:465
EMAIL_FROM=user@gmail.com
# Note: If using with Prisma adapter, you need to use a `.env`
# file rather than a `.env.local` file to configure env vars.
# Postgres: DATABASE_URL=postgres://nextauth:password@127.0.0.1:5432/nextauth?synchronize=true
# MySQL: DATABASE_URL=mysql://nextauth:password@127.0.0.1:3306/nextauth?synchronize=true
# MongoDB: DATABASE_URL=mongodb://nextauth:password@127.0.0.1:27017/nextauth?synchronize=true
DATABASE_URL=
WIKIMEDIA_ID=
WIKIMEDIA_SECRET=
# Supabase Example Configuration
# Supabase Example Configuration
# NEXT_PUBLIC_SUPABASE_URL=http://localhost:54321
# SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSJ9.vI9obAHOGyVVKa3pD--kJlyxp-Z2zV9UUMAhKpNLAcU
# SUPABASE_JWT_SECRET=super-secret-jwt-token-with-at-least-32-characters-long
# NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24ifQ.625_WdcF3KHqz5amU0x2X5WWHP-OEs_4qj0ssLNHzTs

View File

@@ -0,0 +1,4 @@
{
"typescript.tsdk": "../../node_modules/.pnpm/typescript@4.8.4/node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true
}

View File

@@ -0,0 +1,6 @@
# NextAuth.js Development App
This folder contains a Next.js app using NextAuth.js for local development. See the following section on how to start:
[Setting up local environment
](https://github.com/nextauthjs/next-auth/blob/main/CONTRIBUTING.md#setting-up-local-environment)

View File

@@ -0,0 +1,14 @@
import NextAuth, { type NextAuthOptions } from "next-auth"
import GitHub from "next-auth/providers/github"
export const authOptions: NextAuthOptions = {
providers: [
GitHub({
clientId: process.env.GITHUB_ID,
clientSecret: process.env.GITHUB_SECRET,
}),
],
}
const handler = NextAuth(authOptions)
export { handler as GET, handler as POST }

View File

@@ -0,0 +1,12 @@
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html>
<head></head>
<body>{children}</body>
</html>
)
}

View File

@@ -0,0 +1,6 @@
import { getServerSession } from "next-auth/next"
export default async function Page() {
const session = await getServerSession()
return <pre>{JSON.stringify(session, null, 2)}</pre>
}

View File

@@ -0,0 +1,20 @@
import { signIn } from "next-auth/react"
export default function AccessDenied() {
return (
<>
<h1>Access Denied</h1>
<p>
<a
href="/api/auth/signin"
onClick={(e) => {
e.preventDefault()
signIn()
}}
>
You must be signed in to view this page
</a>
</p>
</>
)
}

View File

@@ -0,0 +1,28 @@
import Link from "next/link"
import styles from "./footer.module.css"
import packageJSON from "package.json"
export default function Footer() {
return (
<footer className={styles.footer}>
<hr />
<ul className={styles.navItems}>
<li className={styles.navItem}>
<a href="https://next-auth.js.org">Documentation</a>
</li>
<li className={styles.navItem}>
<a href="https://www.npmjs.com/package/next-auth">NPM</a>
</li>
<li className={styles.navItem}>
<a href="https://github.com/nextauthjs/next-auth-example">GitHub</a>
</li>
<li className={styles.navItem}>
<Link href="/policy">Policy</Link>
</li>
<li className={styles.navItem}>
<em>{packageJSON.version}</em>
</li>
</ul>
</footer>
)
}

View File

@@ -0,0 +1,14 @@
.footer {
margin-top: 2rem;
}
.navItems {
margin-bottom: 1rem;
padding: 0;
list-style: none;
}
.navItem {
display: inline-block;
margin-right: 1rem;
}

View File

@@ -0,0 +1,103 @@
import Link from "next/link"
import { signIn, signOut, useSession } from "next-auth/react"
import styles from "./header.module.css"
// The approach used in this component shows how to built a sign in and sign out
// component that works on pages which support both client and server side
// rendering, and avoids any flash incorrect content on initial page load.
export default function Header() {
const { data: session, status } = useSession()
return (
<header>
<noscript>
<style>{".nojs-show { opacity: 1; top: 0; }"}</style>
</noscript>
<div className={styles.signedInStatus}>
<p
className={`nojs-show ${
!session && status === "loading" ? styles.loading : styles.loaded
}`}
>
{!session && (
<>
<span className={styles.notSignedInText}>
You are not signed in
</span>
<a
href="/api/auth/signin"
className={styles.buttonPrimary}
onClick={(e) => {
e.preventDefault()
signIn()
}}
>
Sign in
</a>
</>
)}
{session && (
<>
{session.user.image && (
<img src={session.user.image} className={styles.avatar} />
)}
<span className={styles.signedInText}>
<small>Signed in as</small>
<br />
<strong>{session.user.email} </strong>
{session.user.name ? `(${session.user.name})` : null}
</span>
<a
href="/api/auth/signout"
className={styles.button}
onClick={(e) => {
e.preventDefault()
signOut()
}}
>
Sign out
</a>
</>
)}
</p>
</div>
<nav>
<ul className={styles.navItems}>
<li className={styles.navItem}>
<Link href="/">Home</Link>
</li>
<li className={styles.navItem}>
<Link href="/client">Client</Link>
</li>
<li className={styles.navItem}>
<Link href="/server">Server</Link>
</li>
<li className={styles.navItem}>
<Link href="/protected">Protected</Link>
</li>
<li className={styles.navItem}>
<Link href="/protected-ssr">Protected(SSR)</Link>
</li>
<li className={styles.navItem}>
<Link href="/api-example">API</Link>
</li>
<li className={styles.navItem}>
<Link href="/credentials">Credentials</Link>
</li>
<li className={styles.navItem}>
<Link href="/email">Email</Link>
</li>
<li className={styles.navItem}>
<Link href="/middleware-protected">Middleware protected</Link>
</li>
<li className={styles.navItem}>
<Link href="/supabase-client-rls">Supabase RLS</Link>
</li>
<li className={styles.navItem}>
<Link href="/supabase-ssr">Supabase RLS(SSR)</Link>
</li>
</ul>
</nav>
</header>
)
}

View File

@@ -0,0 +1,92 @@
/* Set min-height to avoid page reflow while session loading */
.signedInStatus {
display: block;
min-height: 4rem;
width: 100%;
}
.loading,
.loaded {
position: relative;
top: 0;
opacity: 1;
overflow: hidden;
border-radius: 0 0 .6rem .6rem;
padding: .6rem 1rem;
margin: 0;
background-color: rgba(0,0,0,.05);
transition: all 0.2s ease-in;
}
.loading {
top: -2rem;
opacity: 0;
}
.signedInText,
.notSignedInText {
position: absolute;
padding-top: .8rem;
left: 1rem;
right: 6.5rem;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
display: inherit;
z-index: 1;
line-height: 1.3rem;
}
.signedInText {
padding-top: 0rem;
left: 4.6rem;
}
.avatar {
border-radius: 2rem;
float: left;
height: 2.8rem;
width: 2.8rem;
background-color: white;
background-size: cover;
background-repeat: no-repeat;
}
.button,
.buttonPrimary {
float: right;
margin-right: -.4rem;
font-weight: 500;
border-radius: .3rem;
cursor: pointer;
font-size: 1rem;
line-height: 1.4rem;
padding: .7rem .8rem;
position: relative;
z-index: 10;
background-color: transparent;
color: #555;
}
.buttonPrimary {
background-color: #346df1;
border-color: #346df1;
color: #fff;
text-decoration: none;
padding: .7rem 1.4rem;
}
.buttonPrimary:hover {
box-shadow: inset 0 0 5rem rgba(0,0,0,0.2)
}
.navItems {
margin-bottom: 2rem;
padding: 0;
list-style: none;
}
.navItem {
display: inline-block;
margin-right: 1rem;
}

View File

@@ -0,0 +1,14 @@
import Header from 'components/header'
import Footer from 'components/footer'
export default function Layout ({ children }) {
return (
<>
<Header />
<main>
{children}
</main>
<Footer />
</>
)
}

View File

@@ -0,0 +1,45 @@
export { default } from "next-auth/middleware"
export const config = { matcher: ["/middleware-protected"] }
// Other ways to use this middleware
// import withAuth from "next-auth/middleware"
// import { withAuth } from "next-auth/middleware"
// export function middleware(req, ev) {
// return withAuth(req)
// }
// export function middleware(req, ev) {
// return withAuth(req, ev)
// }
// export function middleware(req, ev) {
// return withAuth(req, {
// callbacks: {
// authorized: ({ token }) => !!token,
// },
// })
// }
// export default withAuth(function middleware(req, ev) {
// console.log(req.nextauth.token)
// })
// export default withAuth(
// function middleware(req, ev) {
// console.log(req, ev)
// },
// {
// callbacks: {
// authorized: ({ token }) => token.name === "Balázs Orbán",
// },
// }
// )
// export default withAuth({
// callbacks: {
// authorized: ({ token }) => !!token,
// },
// })

6
apps/dev/nextjs-v4/next-env.d.ts vendored Normal file
View File

@@ -0,0 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
/// <reference types="next/navigation-types/compat/navigation" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.

View File

@@ -0,0 +1,9 @@
/** @type {import("next").NextConfig} */
module.exports = {
webpack(config) {
config.experiments = { ...config.experiments, topLevelAwait: true }
return config
},
experimental: { appDir: true },
typescript: { ignoreBuildErrors: true },
}

View File

@@ -0,0 +1,40 @@
{
"name": "next-auth-app-v4",
"version": "1.0.0",
"description": "NextAuth.js Developer app",
"private": true,
"scripts": {
"clean": "rm -rf .next",
"dev": "next dev",
"lint": "next lint",
"build": "next build",
"start": "next start",
"email": "fake-smtp-server",
"start:email": "pnpm email"
},
"license": "ISC",
"dependencies": {
"@next-auth/fauna-adapter": "workspace:*",
"@next-auth/prisma-adapter": "workspace:*",
"@next-auth/supabase-adapter": "workspace:*",
"@next-auth/typeorm-legacy-adapter": "workspace:*",
"@prisma/client": "^3",
"@supabase/supabase-js": "^2.0.5",
"faunadb": "^4",
"next": "13.3.0",
"next-auth": "workspace:*",
"nodemailer": "^6",
"react": "^18",
"react-dom": "^18"
},
"devDependencies": {
"@types/jsonwebtoken": "^8.5.5",
"@types/react": "^18.0.15",
"@types/react-dom": "^18.0.6",
"fake-smtp-server": "^0.8.0",
"pg": "^8.7.3",
"prisma": "^3",
"sqlite3": "^5.0.8",
"typeorm": "0.3.7"
}
}

View File

@@ -0,0 +1,10 @@
import { SessionProvider } from "next-auth/react"
import "./styles.css"
export default function App({ Component, pageProps }) {
return (
<SessionProvider session={pageProps.session}>
<Component {...pageProps} />
</SessionProvider>
)
}

View File

@@ -0,0 +1,17 @@
import Layout from '../components/layout'
export default function Page () {
return (
<Layout>
<h1>API Example</h1>
<p>The examples below show responses from the example API endpoints.</p>
<p><em>You must be signed in to see responses.</em></p>
<h2>Session</h2>
<p>/api/examples/session</p>
<iframe src='/api/examples/session' />
<h2>JSON Web Token</h2>
<p>/api/examples/jwt</p>
<iframe src='/api/examples/jwt' />
</Layout>
)
}

View File

@@ -0,0 +1,132 @@
import NextAuth, { NextAuthOptions } from "next-auth"
// Providers
import Apple from "next-auth/providers/apple"
import Auth0 from "next-auth/providers/auth0"
import AzureAD from "next-auth/providers/azure-ad"
import AzureB2C from "next-auth/providers/azure-ad-b2c"
import BoxyHQSAML from "next-auth/providers/boxyhq-saml"
// import Cognito from "next-auth/providers/cognito"
import Credentials from "next-auth/providers/credentials"
import Discord from "next-auth/providers/discord"
import DuendeIDS6 from "next-auth/providers/duende-identity-server6"
// import Email from "next-auth/providers/email"
import Facebook from "next-auth/providers/facebook"
import Foursquare from "next-auth/providers/foursquare"
import Freshbooks from "next-auth/providers/freshbooks"
import GitHub from "next-auth/providers/github"
import Gitlab from "next-auth/providers/gitlab"
import Google from "next-auth/providers/google"
// import IDS4 from "next-auth/providers/identity-server4"
import Instagram from "next-auth/providers/instagram"
// import Keycloak from "next-auth/providers/keycloak"
import Line from "next-auth/providers/line"
import LinkedIn from "next-auth/providers/linkedin"
import Mailchimp from "next-auth/providers/mailchimp"
// import Okta from "next-auth/providers/okta"
import Osu from "next-auth/providers/osu"
import Patreon from "next-auth/providers/patreon"
import Slack from "next-auth/providers/slack"
import Spotify from "next-auth/providers/spotify"
import Trakt from "next-auth/providers/trakt"
import Twitch from "next-auth/providers/twitch"
import Twitter 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"
// // Prisma
// import { PrismaClient } from "@prisma/client"
// import { PrismaAdapter } from "@next-auth/prisma-adapter"
// const client = globalThis.prisma || new PrismaClient()
// if (process.env.NODE_ENV !== "production") globalThis.prisma = client
// const adapter = PrismaAdapter(client)
// // Fauna
// import { Client as FaunaClient } from "faunadb"
// import { FaunaAdapter } from "@next-auth/fauna-adapter"
// const opts = { secret: process.env.FAUNA_SECRET, domain: process.env.FAUNA_DOMAIN }
// const client = globalThis.fauna || new FaunaClient(opts)
// if (process.env.NODE_ENV !== "production") globalThis.fauna = client
// const adapter = FaunaAdapter(client)
// // 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,
// })
// // Supabase
// import { SupabaseAdapter } from "@next-auth/supabase-adapter"
// const adapter = SupabaseAdapter({
// url: process.env.NEXT_PUBLIC_SUPABASE_URL,
// secret: process.env.SUPABASE_SERVICE_ROLE_KEY,
// })
export const authOptions: NextAuthOptions = {
// adapter,
// debug: process.env.NODE_ENV !== "production",
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", id: "1", foo: "" }
},
}),
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({ 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) {
// TODO:
// authOptions.providers.unshift(
// // NOTE: You can start a fake e-mail server with `pnpm email`
// // and then go to `http://localhost:1080` in the browser
// Email({ server: "smtp://127.0.0.1:1025?tls.rejectUnauthorized=false" })
// )
}
export default NextAuth(authOptions)

View File

@@ -0,0 +1,7 @@
// This is an example of how to read a JSON Web Token from an API route
import { getToken } from "next-auth/jwt"
export default async (req, res) => {
const token = await getToken({ req })
res.send(JSON.stringify(token, null, 2))
}

View File

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

View File

@@ -0,0 +1,8 @@
// This is an example of how to access a session from an API route
import { getServerSession } from "next-auth/next"
import { authOptions } from "../auth/[...nextauth]"
export default async (req, res) => {
const session = await getServerSession(req, res, authOptions)
res.json(session)
}

View File

@@ -0,0 +1,30 @@
// This is an example of how to query data from Supabase with RLS.
// Learn more about Row Levele Security (RLS): https://supabase.com/docs/guides/auth/row-level-security
import { getServerSession } from "next-auth/next"
import { authOptions } from "../auth/[...nextauth]"
import { createClient } from "@supabase/supabase-js"
export default async (req, res) => {
const session = await getServerSession(req, res, authOptions)
if (!session)
return res.send(JSON.stringify({ error: "No session!" }, null, 2))
const { supabaseAccessToken } = session
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
{
global: {
headers: {
Authorization: `Bearer ${supabaseAccessToken}`,
},
},
}
)
// Now you can query with RLS enabled.
const { data, error } = await supabase.from("users").select("*")
res.send(JSON.stringify({ supabaseAccessToken, data, error }, null, 2))
}

View File

@@ -0,0 +1,22 @@
import Layout from '../components/layout'
export default function Page () {
return (
<Layout>
<h1>Client Side Rendering</h1>
<p>
This page uses the <strong>useSession()</strong> React Hook in the <strong>&lt;/Header&gt;</strong> component.
</p>
<p>
The <strong>useSession()</strong> React Hook easy to use and allows pages to render very quickly.
</p>
<p>
The advantage of this approach is that session state is shared between pages by using the <strong>Provider</strong> in <strong>_app.js</strong> so
that navigation between pages using <strong>useSession()</strong> is very fast.
</p>
<p>
The disadvantage of <strong>useSession()</strong> is that it requires client side JavaScript.
</p>
</Layout>
)
}

View File

@@ -0,0 +1,67 @@
// eslint-disable-next-line no-use-before-define
import * as React from "react"
import { signIn, signOut, useSession } from "next-auth/react"
import Layout from "components/layout"
export default function Page() {
const [response, setResponse] = React.useState(null)
const handleLogin = (options) => async () => {
if (options.redirect) {
return signIn("credentials", options)
}
const response = await signIn("credentials", options)
setResponse(response)
}
const handleLogout = (options) => async () => {
if (options.redirect) {
return signOut(options)
}
const response = await signOut(options)
setResponse(response)
}
const { data: session } = useSession()
if (session) {
return (
<Layout>
<h1>Test different flows for Credentials logout</h1>
<span className="spacing">Default:</span>
<button onClick={handleLogout({ redirect: true })}>Logout</button>
<br />
<span className="spacing">No redirect:</span>
<button onClick={handleLogout({ redirect: false })}>Logout</button>
<br />
<p>Response:</p>
<pre style={{ background: "#eee", padding: 16 }}>
{JSON.stringify(response, null, 2)}
</pre>
</Layout>
)
}
return (
<Layout>
<h1>Test different flows for Credentials login</h1>
<span className="spacing">Default:</span>
<button onClick={handleLogin({ redirect: true, password: "password" })}>
Login
</button>
<br />
<span className="spacing">No redirect:</span>
<button onClick={handleLogin({ redirect: false, password: "password" })}>
Login
</button>
<br />
<span className="spacing">No redirect, wrong password:</span>
<button onClick={handleLogin({ redirect: false, password: "" })}>
Login
</button>
<p>Response:</p>
<pre style={{ background: "#eee", padding: 16 }}>
{JSON.stringify(response, null, 2)}
</pre>
</Layout>
)
}

View File

@@ -0,0 +1,80 @@
// eslint-disable-next-line no-use-before-define
import * as React from "react"
import { signIn, signOut, useSession } from "next-auth/react"
import Layout from "components/layout"
export default function Page() {
const [response, setResponse] = React.useState(null)
const [email, setEmail] = React.useState("")
const handleChange = (event) => {
setEmail(event.target.value)
}
const handleLogin = (options) => async (event) => {
event.preventDefault()
if (options.redirect) {
return signIn("email", options)
}
const response = await signIn("email", options)
setResponse(response)
}
const handleLogout = (options) => async (event) => {
if (options.redirect) {
return signOut(options)
}
const response = await signOut(options)
setResponse(response)
}
const { data: session } = useSession()
if (session) {
return (
<Layout>
<h1>Test different flows for Email logout</h1>
<span className="spacing">Default:</span>
<button onClick={handleLogout({ redirect: true })}>Logout</button>
<br />
<span className="spacing">No redirect:</span>
<button onClick={handleLogout({ redirect: false })}>Logout</button>
<br />
<p>Response:</p>
<pre style={{ background: "#eee", padding: 16 }}>
{JSON.stringify(response, null, 2)}
</pre>
</Layout>
)
}
return (
<Layout>
<h1>Test different flows for Email login</h1>
<label className="spacing">
Email address:{" "}
<input
type="text"
id="email"
name="email"
value={email}
onChange={handleChange}
/>
</label>
<br />
<form onSubmit={handleLogin({ redirect: true, email })}>
<span className="spacing">Default:</span>
<button type="submit">Sign in with Email</button>
</form>
<form onSubmit={handleLogin({ redirect: false, email })}>
<span className="spacing">No redirect:</span>
<button type="submit">Sign in with Email</button>
</form>
<p>Response:</p>
<pre style={{ background: "#eee", padding: 16 }}>
{JSON.stringify(response, null, 2)}
</pre>
</Layout>
)
}

View File

@@ -0,0 +1,12 @@
import Layout from 'components/layout'
export default function Page () {
return (
<Layout>
<h1>NextAuth.js Example</h1>
<p>
This is an example site to demonstrate how to use <a href='https://next-auth.js.org'>NextAuth.js</a> for authentication.
</p>
</Layout>
)
}

View File

@@ -0,0 +1,9 @@
import Layout from "components/layout"
export default function Page() {
return (
<Layout>
<h1>Page protected by Middleware</h1>
</Layout>
)
}

View File

@@ -0,0 +1,30 @@
import Layout from '../components/layout'
export default function Page () {
return (
<Layout>
<p>
This is an example site to demonstrate how to use <a href='https://next-auth.js.org'>NextAuth.js</a> for authentication.
</p>
<h2>Terms of Service</h2>
<p>
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
</p>
<h2>Privacy Policy</h2>
<p>
This site uses JSON Web Tokens and an in-memory database which resets every ~2 hours.
</p>
<p>
Data provided to this site is exclusively used to support signing in
and is not passed to any third party services, other than via SMTP or OAuth for the
purposes of authentication.
</p>
</Layout>
)
}

View File

@@ -0,0 +1,48 @@
// This is an example of how to protect content using server rendering
import { getServerSession } from "next-auth/next"
import { authOptions } from "./api/auth/[...nextauth]"
import Layout from "../components/layout"
import AccessDenied from "../components/access-denied"
export default function Page({ content, session }) {
// If no session exists, display access denied message
if (!session) {
return (
<Layout>
<AccessDenied />
</Layout>
)
}
// If session exists, display content
return (
<Layout>
<h1>Protected Page</h1>
<p>
<strong>{content}</strong>
</p>
</Layout>
)
}
export async function getServerSideProps(context) {
const session = await getServerSession(context.req, context.res, authOptions)
let content = null
if (session) {
const hostname = process.env.NEXTAUTH_URL || "http://localhost:3000"
const options = { headers: { cookie: context.req.headers.cookie } }
const res = await fetch(`${hostname}/api/examples/protected`, options)
const json = await res.json()
if (json.content) {
content = json.content
}
}
return {
props: {
session,
content,
},
}
}

View File

@@ -0,0 +1,35 @@
import { useState, useEffect } from "react"
import { useSession } from "next-auth/react"
import Layout from "../components/layout"
export default function Page() {
const { status } = useSession({
required: true,
})
const [content, setContent] = useState()
// Fetch content from protected route
useEffect(() => {
if (status === "loading") return
const fetchData = async () => {
const res = await fetch("/api/examples/protected")
const json = await res.json()
if (json.content) {
setContent(json.content)
}
}
fetchData()
}, [status])
if (status === "loading") return <Layout>Loading...</Layout>
// If session exists, display content
return (
<Layout>
<h1>Protected Page</h1>
<p>
<strong>{content}</strong>
</p>
</Layout>
)
}

View File

@@ -0,0 +1,46 @@
import { getServerSession } from "next-auth/next"
import Layout from "../components/layout"
import { authOptions } from "./api/auth/[...nextauth]"
export default function Page() {
// As this page uses Server Side Rendering, the `session` will be already
// populated on render without needing to go through a loading stage.
// This is possible because of the shared context configured in `_app.js` that
// is used by `useSession()`.
return (
<Layout>
<h1>Server Side Rendering</h1>
<p>
This page uses the <strong>getServerSession()</strong> method in{" "}
<strong>getServerSideProps()</strong>.
</p>
<p>
Using <strong>getServerSession()</strong> in{" "}
<strong>getServerSideProps()</strong> is currently the recommended
approach, although the API may still change, if you need to support
Server Side Rendering with authentication.
</p>
<p>
Using <strong>getSession()</strong> is still recommended on the client.
</p>
<p>
The advantage of Server Side Rendering is this page does not require
client side JavaScript.
</p>
<p>
The disadvantage of Server Side Rendering is that this page is slower to
render.
</p>
</Layout>
)
}
// Export the `session` prop to use sessions with Server Side Rendering
export async function getServerSideProps(context) {
return {
props: {
session: await getServerSession(context.req, context.res, authOptions),
},
}
}

View File

@@ -0,0 +1,32 @@
body {
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
"Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif,
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
padding: 0 1rem 1rem 1rem;
max-width: 680px;
margin: 0 auto;
background: #fff;
color: var(--color-text);
}
li,
p {
line-height: 1.5rem;
}
a {
font-weight: 500;
}
hr {
border: 1px solid #ddd;
}
iframe {
background: #ccc;
border: 1px solid #ccc;
height: 10rem;
width: 100%;
border-radius: .5rem;
filter: invert(1);
}

View File

@@ -0,0 +1,49 @@
import Layout from "../components/layout"
import { useState, useEffect } from "react"
import { useSession } from "next-auth/react"
import { createClient } from "@supabase/supabase-js"
export default function Page() {
const { data: session } = useSession()
const [data, setData] = useState(null)
useEffect(() => {
if (session) {
console.log(session)
// User is logged in, let's fetch their data.
const { supabaseAccessToken } = session
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
{
global: {
headers: { Authorization: `Bearer ${supabaseAccessToken}` },
},
}
)
// Fetch data with RLS enabled.
supabase
.from("users")
.select("*")
.then(({ data }) => setData(data))
}
}, [session])
return (
<Layout>
<h1>Fetch Data from Supabase with RLS</h1>
<h2>Client-side data fetching with RLS:</h2>
<pre>{JSON.stringify(data, null, 2)}</pre>
<h2>API Example</h2>
<p>
You can also use Supabase in API routes. See the code in the
`/pages/api/examples/supabase-rls.js` file.
</p>
<p>
<em>You must be signed in to see responses.</em>
</p>
<p>/api/examples/supabase-rls</p>
<iframe src="/api/examples/supabase-rls" />
</Layout>
)
}

View File

@@ -0,0 +1,64 @@
// This is an example of how to protect content using server rendering
// and fetching data from Supabase with RLS enabled.
import { getServerSession } from "next-auth/next"
import { authOptions } from "./api/auth/[...nextauth]"
import { createClient } from "@supabase/supabase-js"
import Layout from "../components/layout"
import AccessDenied from "../components/access-denied"
export default function Page({ data, session }) {
// If no session exists, display access denied message
if (!session) {
return (
<Layout>
<AccessDenied />
</Layout>
)
}
// If session exists, display content
return (
<Layout>
<h1>Protected Page</h1>
<p>Data fetched during SSR from Supabase with RSL enabled:</p>
<pre>{JSON.stringify(data, null, 2)}</pre>
</Layout>
)
}
export async function getServerSideProps(context) {
const session = await getServerSession(context.req, context.res, authOptions)
if (!session)
return {
props: {
session,
data: null,
error: "No session",
},
}
const { supabaseAccessToken } = session
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
{
global: {
headers: {
Authorization: `Bearer ${supabaseAccessToken}`,
},
},
}
)
// Now you can query with RLS enabled.
const { data, error } = await supabase.from("users").select("*")
return {
props: {
session,
data,
error,
},
}
}

View File

@@ -0,0 +1,60 @@
-- CreateTable
CREATE TABLE "Account" (
"id" TEXT NOT NULL PRIMARY KEY,
"userId" TEXT NOT NULL,
"type" TEXT NOT NULL,
"provider" TEXT NOT NULL,
"providerAccountId" TEXT NOT NULL,
"refresh_token" TEXT,
"access_token" TEXT,
"expires_at" INTEGER,
"token_type" TEXT,
"scope" TEXT,
"id_token" TEXT,
"session_state" TEXT,
"oauth_token_secret" TEXT,
"oauth_token" TEXT,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
CONSTRAINT "Account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "Session" (
"id" TEXT NOT NULL PRIMARY KEY,
"sessionToken" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"expires" DATETIME NOT NULL,
CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "User" (
"id" TEXT NOT NULL PRIMARY KEY,
"name" TEXT,
"email" TEXT,
"emailVerified" DATETIME,
"image" TEXT
);
-- CreateTable
CREATE TABLE "VerificationToken" (
"identifier" TEXT NOT NULL,
"token" TEXT NOT NULL,
"expires" DATETIME NOT NULL
);
-- CreateIndex
CREATE UNIQUE INDEX "Account_provider_providerAccountId_key" ON "Account"("provider", "providerAccountId");
-- CreateIndex
CREATE UNIQUE INDEX "Session_sessionToken_key" ON "Session"("sessionToken");
-- CreateIndex
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
-- CreateIndex
CREATE UNIQUE INDEX "VerificationToken_token_key" ON "VerificationToken"("token");
-- CreateIndex
CREATE UNIQUE INDEX "VerificationToken_identifier_token_key" ON "VerificationToken"("identifier", "token");

View File

@@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (i.e. Git)
provider = "sqlite"

View File

@@ -0,0 +1,57 @@
datasource db {
provider = "sqlite"
url = "file:./dev.db"
}
generator client {
provider = "prisma-client-js"
}
model Account {
id String @id @default(cuid())
userId String
type String
provider String
providerAccountId String
refresh_token String?
access_token String?
expires_at Int?
token_type String?
scope String?
id_token String?
session_state String?
oauth_token_secret String?
oauth_token String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id])
@@unique([provider, providerAccountId])
}
model Session {
id String @id @default(cuid())
sessionToken String @unique
userId String
expires DateTime
user User @relation(fields: [userId], references: [id])
}
model User {
id String @id @default(cuid())
name String?
email String? @unique
emailVerified DateTime?
image String?
accounts Account[]
sessions Session[]
}
model VerificationToken {
identifier String
token String @unique
expires DateTime
@@unique([identifier, token])
}

View File

@@ -0,0 +1,39 @@
{
"compilerOptions": {
"target": "esnext",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"strict": false,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"incremental": true,
"jsx": "preserve",
"baseUrl": ".",
"plugins": [
{
"name": "next"
}
],
"strictNullChecks": true
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts"
],
"exclude": [
"node_modules",
"jest.config.js"
]
}

20
apps/dev/nextjs-v4/types/nextauth.d.ts vendored Normal file
View File

@@ -0,0 +1,20 @@
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import NextAuth from "next-auth"
declare module "next-auth" {
/**
* Returned by `useSession`, `getSession` and received as a prop on the `SessionProvider` React Context
*/
interface Session {
// A JWT which can be used as Authorization header with supabase-js for RLS.
supabaseAccessToken?: string
user: {
/** The user's postal address. */
address: string
} & User
}
interface User {
foo: string
}
}

View File

@@ -52,6 +52,10 @@ TWITTER_SECRET=
WIKIMEDIA_ID=
WIKIMEDIA_SECRET=
# Yandex OAuth. new app -> https://oauth.yandex.com/client/new/id
YANDEX_ID=
YANDEX_SECRET=
# Example configuration for a Gmail account (will need SMTP enabled)
EMAIL_SERVER=smtps://user@gmail.com:password@smtp.gmail.com:465
EMAIL_FROM=user@gmail.com

View File

@@ -34,6 +34,7 @@ import Spotify from "@auth/core/providers/spotify"
import Trakt from "@auth/core/providers/trakt"
import Twitch from "@auth/core/providers/twitch"
import Twitter from "@auth/core/providers/twitter"
import Yandex from "@auth/core/providers/yandex"
import Vk from "@auth/core/providers/vk"
import Wikimedia from "@auth/core/providers/wikimedia"
import WorkOS from "@auth/core/providers/workos"
@@ -120,6 +121,7 @@ export const authConfig: AuthConfig = {
Twitch({ clientId: process.env.TWITCH_ID, clientSecret: process.env.TWITCH_SECRET }),
Twitter({ clientId: process.env.TWITTER_ID, clientSecret: process.env.TWITTER_SECRET }),
// TwitterLegacy({ clientId: process.env.TWITTER_LEGACY_ID, clientSecret: process.env.TWITTER_LEGACY_SECRET }),
Yandex({ clientId: process.env.YANDEX_ID, clientSecret: process.env.YANDEX_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 }),

View File

@@ -1,4 +1,4 @@
import { unstable_getServerSession } from "next-auth/next"
import { getServerSession } from "next-auth/next"
import { authOptions } from "./api/auth/[...nextauth]"
import Layout from "../components/layout"
@@ -38,7 +38,7 @@ export default function ServerSidePage() {
export async function getServerSideProps(context: GetServerSidePropsContext) {
return {
props: {
session: await unstable_getServerSession(
session: await getServerSession(
context.req,
context.res,
authOptions

View File

@@ -46,7 +46,7 @@ export default NextAuth({
```
:::note
Check the [Credentials Provider options](/reference/providers/credentials) for further customization
Check the [Credentials Provider options](/reference/core/providers_credentials) for further customization
:::
Note that we only need to define an `authorize` method that is in charge of receiving the credentials inserted by the user and call the authorization service.

View File

@@ -4,15 +4,11 @@ title: Databases
Auth.js offers multiple database adapters. Check our guides on:
- [using a database adapter](/guides/adapters/using-a-database-adapter)
- [creating your own](/guides/adapters/creating-a-database-adapter)
> As of **v4** Auth.js no longer ships with an adapter included by default. If you would like to persist any information, you need to install one of the many available adapters yourself. See the individual adapter documentation pages for more details.
- [Using a database adapter](/guides/adapters/using-a-database-adapter)
- [Creating your own](/guides/adapters/creating-a-database-adapter)
To learn more about databases in Auth.js and how they are used, check out [databases in the FAQ](/concepts/faq#databases).
---
## How to use a database
See the [documentation for adapters](/reference/adapters/overview) for more information on advanced configuration, including how to use Auth.js with other databases using a [custom adapter](/guides/adapters/creating-a-database-adapter).
See the [documentation for adapters](/reference/adapters) for more information on advanced configuration, including how to use Auth.js with other databases using a [custom adapter](/guides/adapters/creating-a-database-adapter).

View File

@@ -66,7 +66,7 @@ Nice! We're getting there. Now we need to read supply this values as the configu
```ts title="pages/api/auth/[...nextauth].ts"
import NextAuth from "next-auth"
import EmailProvider from "next-auth/providers/email"
import Email from "next-auth/providers/email"
export default NextAuth({
providers: [

View File

@@ -239,7 +239,7 @@ Introduced in https://github.com/nextauthjs/next-auth/releases/tag/v4.0.0-next.1
## `nodemailer`
Like `typeorm` and `prisma`, [`nodemailer`](https://npmjs.com/package/nodemailer) is no longer included as a dependency by default. If you are using the Email provider you must install it in your project manually, or use any other Email library in the [`sendVerificationRequest`](/reference/providers/email) callback. This reduces bundle size for those not actually using the Email provider. Remember, when using the Email provider, it is mandatory to also use a database adapter due to the fact that verification tokens need to be persisted longer term for the magic link functionality to work.
Like `typeorm` and `prisma`, [`nodemailer`](https://npmjs.com/package/nodemailer) is no longer included as a dependency by default. If you are using the Email provider you must install it in your project manually, or use any other Email library in the [`sendVerificationRequest`](/guides/providers/email) callback. This reduces bundle size for those not actually using the Email provider. Remember, when using the Email provider, it is mandatory to also use a database adapter due to the fact that verification tokens need to be persisted longer term for the magic link functionality to work.
Introduced in https://github.com/nextauthjs/next-auth/releases/tag/v4.0.0-next.2

View File

@@ -17,7 +17,7 @@ The functionality provided for credentials-based authentication is intentionally
The **Credentials Provider** comes with a set of default options:
- [Credentials Provider options](/reference/providers/credentials)
- [Credentials Provider options](/reference/core/providers_credentials)
You can override any of the options to suit your own use case.

View File

@@ -23,7 +23,7 @@ The Email Provider can be used with both JSON Web Tokens and database sessions,
The **Email Provider** comes with a set of default options:
- [Email Provider options](/reference/providers/email)
- [Email Provider options](/guides/providers/email)
You can override any of the options to suit your own use case.

View File

@@ -52,7 +52,7 @@ Using a Auth.js / NextAuth.js adapter you can connect to any database service or
<a href="/reference/adapter/typeorm" class="adapter-card">
<img src="/img/adapters/typeorm.png" width="30" />
<h4 class="adapter-card__title">TypeORM Adapter</h4>
</a>
</a>
<a href="/reference/adapter/upstash-redis" class="adapter-card">
<img src="/img/adapters/upstash-redis.svg" width="30" />
<h4 class="adapter-card__title">Upstash Adapter</h4>
@@ -133,7 +133,7 @@ If a user first signs in with OAuth then their email address is automatically po
This provides a way to contact users and for users to maintain access to their account and sign in using email in the event they are unable to sign in with the OAuth provider in future (if the [Email Provider](/getting-started/email-tutorial) is configured).
:::
User creation in the database is automatic, and happens when the user is logging in for the first time with a provider. The default data saved is `id`, `name`, `email` and `image`. You can add more profile data by returning extra fields in your [OAuth provider's `profile()`](/reference/providers/oauth) callback.
User creation in the database is automatic, and happens when the user is logging in for the first time with a provider. The default data saved is `id`, `name`, `email` and `image`. You can add more profile data by returning extra fields in your [OAuth provider](/guides/providers/custom-provider)'s [`profile()`](/reference/core/providers#profile) callback.
### Account
@@ -174,7 +174,7 @@ A single User can have multiple open Verification Tokens (e.g. to sign in to dif
It has been designed to be extendable for other verification purposes in the future (e.g. 2FA / short codes).
:::note
Auth.js makes sure that every token is usable only once, and by default has a short (1 day, can be configured by [`maxAge`](/reference/providers/email)) lifetime. If your user did not manage to finish the sign-in flow in time, they will have to start the sign-in process again.
Auth.js makes sure that every token is usable only once, and by default has a short (1 day, can be configured by [`maxAge`](/guides/providers/email)) lifetime. If your user did not manage to finish the sign-in flow in time, they will have to start the sign-in process again.
:::
:::tip
@@ -188,4 +188,4 @@ Auth.js / NextAuth.js uses `camelCase` for its own database rows, while respecti
## TypeScript
Check out the [`@auth/core/adapters` API Reference](/reference/core/adapters) documentation.
Check out the [`@auth/core/adapters` API Reference](/reference/core/adapters) documentation.

View File

@@ -261,32 +261,36 @@ const docusaurusConfig = {
},
},
],
typedocAdapter("Dgraph"),
typedocAdapter("DynamoDB"),
typedocAdapter("Fauna"),
typedocAdapter("Firebase"),
typedocAdapter("Mikro ORM"),
typedocAdapter("MongoDB"),
typedocAdapter("Neo4j"),
typedocAdapter("PouchDB"),
typedocAdapter("Prisma"),
[
"docusaurus-plugin-typedoc",
{
...typedocConfig,
id: "typeorm",
plugin: [require.resolve("./typedoc-mdn-links")],
watch: process.env.TYPEDOC_WATCH,
entryPoints: [`../packages/adapter-typeorm-legacy/src/index.ts`],
tsconfig: `../packages/adapter-typeorm-legacy/tsconfig.json`,
out: `reference/adapter/typeorm`,
sidebar: { indexLabel: "TypeORM" },
},
],
typedocAdapter("Sequelize"),
typedocAdapter("Supabase"),
typedocAdapter("Upstash Redis"),
typedocAdapter("Xata"),
...(process.env.TYPEDOC_SKIP_ADAPTERS
? []
: [
typedocAdapter("Dgraph"),
typedocAdapter("DynamoDB"),
typedocAdapter("Fauna"),
typedocAdapter("Firebase"),
typedocAdapter("Mikro ORM"),
typedocAdapter("MongoDB"),
typedocAdapter("Neo4j"),
typedocAdapter("PouchDB"),
typedocAdapter("Prisma"),
[
"docusaurus-plugin-typedoc",
{
...typedocConfig,
id: "typeorm",
plugin: [require.resolve("./typedoc-mdn-links")],
watch: process.env.TYPEDOC_WATCH,
entryPoints: [`../packages/adapter-typeorm-legacy/src/index.ts`],
tsconfig: `../packages/adapter-typeorm-legacy/tsconfig.json`,
out: `reference/adapter/typeorm`,
sidebar: { indexLabel: "TypeORM" },
},
],
typedocAdapter("Sequelize"),
typedocAdapter("Supabase"),
typedocAdapter("Upstash Redis"),
typedocAdapter("Xata"),
]),
],
}

View File

@@ -34,9 +34,9 @@
"@docusaurus/theme-common": "2.3.1",
"@docusaurus/theme-mermaid": "2.3.1",
"@docusaurus/types": "2.3.1",
"docusaurus-plugin-typedoc": "1.0.0-next.2",
"typedoc": "^0.23.28",
"typedoc-plugin-markdown": "4.0.0-next.3"
"docusaurus-plugin-typedoc": "1.0.0-next.5",
"typedoc": "^0.24.4",
"typedoc-plugin-markdown": "4.0.0-next.6"
},
"browserslist": {
"production": [

View File

@@ -46,27 +46,31 @@ module.exports = {
},
],
},
{
type: "category",
label: "Database Adapters",
link: { type: "doc", id: "reference/adapters/index" },
items: [
{ type: "doc", id: "reference/adapter/dgraph/index" },
{ type: "doc", id: "reference/adapter/dynamodb/index" },
{ type: "doc", id: "reference/adapter/fauna/index" },
{ type: "doc", id: "reference/adapter/firebase/index" },
{ type: "doc", id: "reference/adapter/mikro-orm/index" },
{ type: "doc", id: "reference/adapter/mongodb/index" },
{ type: "doc", id: "reference/adapter/neo4j/index" },
{ type: "doc", id: "reference/adapter/pouchdb/index" },
{ type: "doc", id: "reference/adapter/prisma/index" },
{ type: "doc", id: "reference/adapter/sequelize/index" },
{ type: "doc", id: "reference/adapter/supabase/index" },
{ type: "doc", id: "reference/adapter/typeorm/index" },
{ type: "doc", id: "reference/adapter/upstash-redis/index" },
{ type: "doc", id: "reference/adapter/xata/index" },
],
},
...(process.env.TYPEDOC_SKIP_ADAPTERS
? []
: [
{
type: "category",
label: "Database Adapters",
link: { type: "doc", id: "reference/adapters/index" },
items: [
{ type: "doc", id: "reference/adapter/dgraph/index" },
{ type: "doc", id: "reference/adapter/dynamodb/index" },
{ type: "doc", id: "reference/adapter/fauna/index" },
{ type: "doc", id: "reference/adapter/firebase/index" },
{ type: "doc", id: "reference/adapter/mikro-orm/index" },
{ type: "doc", id: "reference/adapter/mongodb/index" },
{ type: "doc", id: "reference/adapter/neo4j/index" },
{ type: "doc", id: "reference/adapter/pouchdb/index" },
{ type: "doc", id: "reference/adapter/prisma/index" },
{ type: "doc", id: "reference/adapter/sequelize/index" },
{ type: "doc", id: "reference/adapter/supabase/index" },
{ type: "doc", id: "reference/adapter/typeorm/index" },
{ type: "doc", id: "reference/adapter/upstash-redis/index" },
{ type: "doc", id: "reference/adapter/xata/index" },
],
},
]),
"reference/warnings",
],
conceptsSidebar: [

View File

@@ -25,6 +25,10 @@
transition: 0.2s background-color ease-in-out;
}
html[data-theme="dark"] .adapter-card {
color: #f5f5f5;
}
.adapter-card:hover {
text-decoration: none;
color: black;

View File

@@ -135,7 +135,7 @@ export default function Home() {
)}
href="https://next-auth-example.vercel.app"
>
Live Demo (Next.js)
Next.js Demo
</a>
<a
className={classnames(
@@ -144,7 +144,7 @@ export default function Home() {
)}
href="https://sveltekit-auth-example.vercel.app"
>
Live Demo (SvelteKit)
SvelteKit Demo
</a>
<a
className={classnames(
@@ -153,7 +153,7 @@ export default function Home() {
)}
href="https://auth-solid.vercel.app"
>
Live Demo (SolidStart)
SolidStart Demo
</a>
<Link
className={classnames(

6
docs/static/img/providers/reddit.svg vendored Normal file
View File

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" height="32" width="32">
<g>
<circle fill="#FF4500" cx="10" cy="10" r="10"/>
<path fill="#FFF" d="M16.67,10A1.46,1.46,0,0,0,14.2,9a7.12,7.12,0,0,0-3.85-1.23L11,4.65,13.14,5.1a1,1,0,1,0,.13-0.61L10.82,4a0.31,0.31,0,0,0-.37.24L9.71,7.71a7.14,7.14,0,0,0-3.9,1.23A1.46,1.46,0,1,0,4.2,11.33a2.87,2.87,0,0,0,0,.44c0,2.24,2.61,4.06,5.83,4.06s5.83-1.82,5.83-4.06a2.87,2.87,0,0,0,0-.44A1.46,1.46,0,0,0,16.67,10Zm-10,1a1,1,0,1,1,1,1A1,1,0,0,1,6.67,11Zm5.81,2.75a3.84,3.84,0,0,1-2.47.77,3.84,3.84,0,0,1-2.47-.77,0.27,0.27,0,0,1,.38-0.38A3.27,3.27,0,0,0,10,14a3.28,3.28,0,0,0,2.09-.61A0.27,0.27,0,1,1,12.48,13.79Zm-0.18-1.71a1,1,0,1,1,1-1A1,1,0,0,1,12.29,12.08Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 736 B

1
docs/static/img/providers/yandex.svg vendored Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0,0,256,256" width="480px" height="480px" fill-rule="nonzero"><g transform="translate(32,32) scale(0.75,0.75)"><g fill-opacity="0" fill="#ff0000" fill-rule="nonzero" stroke="none" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="10" stroke-dasharray="" stroke-dashoffset="0" font-family="none" font-weight="none" font-size="none" text-anchor="none" style="mix-blend-mode: normal"><path d="M-42.66667,298.66667v-341.33333h341.33333v341.33333z" id="bgRectangle"></path></g><g fill="#ff0000" fill-rule="nonzero" stroke="none" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="10" stroke-dasharray="" stroke-dashoffset="0" font-family="none" font-weight="none" font-size="none" text-anchor="none" style="mix-blend-mode: normal"><path d="M128,292.57143c-90.89029,0 -164.57143,-73.68114 -164.57143,-164.57143v0c0,-90.89029 73.68114,-164.57143 164.57143,-164.57143v0c90.89029,0 164.57143,73.68114 164.57143,164.57143v0c0,90.89029 -73.68114,164.57143 -164.57143,164.57143z" id="shape"></path></g><g fill="#ffffff" fill-rule="nonzero" stroke="none" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="10" stroke-dasharray="" stroke-dashoffset="0" font-family="none" font-weight="none" font-size="none" text-anchor="none" style="mix-blend-mode: normal"><g transform="scale(5.33333,5.33333)"><path d="M21.413,47.315c1.685,0.076 3.206,-0.076 4.891,0c-0.383,-4.097 0.719,-8.451 0.297,-12.544c-0.118,-1.142 0.076,-2.779 0.171,-3.924c0.102,-1.229 1.069,-3.553 1.533,-4.696c3.541,-8.731 5.77,-15.742 8.924,-24.62c-1.548,-0.029 -3.702,0.029 -5.25,0c-2.934,7.419 -5.846,15.183 -8.022,22.859c-3.659,-6.121 -5.157,-12.182 -7.055,-18.386c-0.13,-0.424 -0.284,-0.885 -0.66,-1.12c-0.247,-0.154 -0.549,-0.183 -0.839,-0.205c-1.56,-0.118 -3.126,-0.149 -4.689,-0.09c3.636,7.742 6.793,16.44 9.267,24.628c0.411,1.36 0.806,2.729 1.013,4.134c0.218,1.48 0.447,2.718 0.453,4.213c0.009,2.887 -0.044,6.865 -0.034,9.751z"></path><path d="M37.228,1.033c-1.75,-0.028 -3.5,0.028 -5.25,0c-0.238,-0.004 -0.401,0.161 -0.482,0.367c-2.823,7.142 -5.525,14.344 -7.685,21.717c-2.11,-3.802 -3.592,-7.875 -4.862,-12.033c-0.335,-1.095 -0.659,-2.193 -0.986,-3.291c-0.27,-0.904 -0.439,-2.011 -0.95,-2.815c-0.522,-0.821 -1.483,-0.798 -2.35,-0.848c-1.315,-0.076 -2.633,-0.086 -3.95,-0.041c-0.401,0.014 -0.593,0.409 -0.432,0.752c2.538,5.416 4.725,10.992 6.675,16.645c1.884,5.463 3.992,11.069 3.99,16.917c0,2.97 -0.043,5.941 -0.034,8.911c0.001,0.199 0.102,0.33 0.234,0.406c0.072,0.051 0.156,0.09 0.266,0.094c1.631,0.063 3.261,-0.063 4.891,0c0.253,0.01 0.523,-0.238 0.5,-0.5c-0.29,-3.347 0.333,-6.672 0.39,-10.014c0.026,-1.503 -0.185,-2.988 -0.088,-4.49c0.082,-1.276 0.2,-2.483 0.603,-3.701c0.908,-2.743 2.12,-5.396 3.142,-8.098c1.122,-2.965 2.185,-5.951 3.229,-8.944c1.21,-3.467 2.4,-6.941 3.629,-10.402c0.114,-0.314 -0.175,-0.627 -0.48,-0.632zM30.286,19.685c-1.051,2.814 -2.242,5.576 -3.274,8.396c-0.426,1.163 -0.695,2.274 -0.806,3.505c-0.061,0.682 -0.122,1.366 -0.138,2.051c-0.018,0.764 0.083,1.516 0.114,2.278c0.148,3.64 -0.621,7.255 -0.403,10.893c-1.288,-0.021 -2.575,0.037 -3.862,0.015c0.012,-5.353 0.438,-10.808 -0.961,-16.031c-1.566,-5.848 -3.647,-11.615 -5.86,-17.245c-1.123,-2.857 -2.331,-5.681 -3.617,-8.468c0.98,-0.018 1.96,-0.013 2.939,0.038c0.445,0.023 1.19,-0.067 1.573,0.2c0.355,0.247 0.446,0.872 0.561,1.249c0.612,2.017 1.195,4.043 1.828,6.053c1.316,4.174 2.906,8.253 5.146,12.025c0.22,0.371 0.796,0.295 0.914,-0.119c2.174,-7.647 4.961,-15.101 7.878,-22.492c1.403,0.014 2.805,-0.007 4.208,0c-2.082,5.883 -4.056,11.805 -6.24,17.652z"></path></g></g></g></svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@@ -9,6 +9,7 @@
"excludeProtected": true,
"hideHierarchy": true,
"gitRevision": "main",
"groupByReflections": false,
"hideBreadcrumbs": true,
"hideGenerator": true,
"kindSortOrder": [
@@ -36,11 +37,11 @@
"SetSignature"
],
"readme": "none",
"reflectionsWithOwnFile": "none",
"sort": [
"kind",
"static-first",
"required-first",
"alphabetical"
],
"symbolsWithOwnFile": "none"
]
}

View File

@@ -11,8 +11,11 @@
"clean": "turbo run clean --no-cache",
"dev:db": "turbo run dev --parallel --continue --filter=next-auth-app...",
"dev": "turbo run dev --parallel --continue --filter=next-auth-app... --filter=!./packages/adapter-*",
"dev-v4:db": "turbo run dev --parallel --continue --filter=next-auth-app-v4...",
"dev-v4": "turbo run dev --parallel --continue --filter=next-auth-app-v4... --filter=!./packages/adapter-*",
"dev:kit": "turbo run dev --parallel --continue --filter=sveltekit-auth-app...",
"dev:docs": "turbo run dev --filter=docs",
"dev:docs": "TYPEDOC_SKIP_ADAPTERS=1 turbo run dev --filter=docs",
"dev:docs:adapters": "turbo run dev --filter=docs",
"email": "cd apps/dev/nextjs && pnpm email",
"eslint": "eslint --cache .",
"lint": "prettier --check .",
@@ -40,7 +43,7 @@
"eslint-plugin-svelte3": "^4.0.0",
"prettier": "2.8.1",
"prettier-plugin-svelte": "^2.8.1",
"turbo": "1.6.3",
"turbo": "1.8.8",
"typescript": "4.9.4"
},
"engines": {
@@ -61,5 +64,205 @@
"patchedDependencies": {
"@balazsorban/monorepo-release@0.1.8": "patches/@balazsorban__monorepo-release@0.1.8.patch"
}
},
"eslintIgnore": [
".eslintrc.js",
".cache-loader",
".DS_Store",
".pnpm-debug.log",
".turbo",
".vscode/generated*",
"/_work",
"/actions-runner",
"node_modules",
"patches",
"pnpm-lock.yaml",
".github/actions/issue-validator/index.mjs",
"*.cjs",
"*.js",
"*.d.ts",
"*.d.ts.map",
".svelte-kit",
".next",
".nuxt",
".docusaurus",
"build",
"docs/docs/reference/core",
"docs/docs/reference/sveltekit",
"static",
"coverage",
"dist",
"packages/core/src/providers/oauth-types.ts",
"packages/core/src/lib/pages/styles.ts",
"packages/frameworks-sveltekit/package",
"packages/frameworks-sveltekit/vite.config.{js,ts}.timestamp-*",
"packages/next-auth/src/providers/oauth-types.ts",
"packages/next-auth/css/index.css",
".branches",
"db.sqlite",
"dev.db",
"dynamodblocal-bin",
"firebase-debug.log",
"firestore-debug.log",
"migrations",
"test.schema.gql",
"apps/example-sveltekit",
"apps",
"packages/**/*test*"
],
"eslintConfig": {
"env": {
"browser": true,
"es2021": true,
"node": true
},
"extends": [
"eslint:recommended",
"prettier"
],
"overrides": [
{
"files": [
"*.ts",
"*.tsx"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": [
"./packages/**/tsconfig.json",
"./apps/**/tsconfig.json"
]
},
"settings": {
"react": {
"version": "18"
}
},
"extends": [
"plugin:react/recommended",
"plugin:react/jsx-runtime",
"standard-with-typescript",
"prettier"
],
"rules": {
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/method-signature-style": "off",
"@typescript-eslint/naming-convention": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/restrict-template-expressions": "off",
"@typescript-eslint/strict-boolean-expressions": "off",
"react/prop-types": "off",
"react/no-unescaped-entities": "off"
}
},
{
"files": [
"*.test.ts",
"*.test.js"
],
"extends": [
"plugin:jest/recommended"
],
"env": {
"jest": true
}
},
{
"files": [
"docs/**"
],
"plugins": [
"@docusaurus"
],
"extends": [
"plugin:@docusaurus/recommended"
]
},
{
"files": [
"packages/{core,sveltekit}/*.ts"
],
"plugins": [
"jsdoc"
],
"extends": [
"plugin:jsdoc/recommended"
],
"rules": {
"jsdoc/require-param": "off",
"jsdoc/require-returns": "off",
"jsdoc/require-jsdoc": [
"warn",
{
"publicOnly": true,
"enableFixer": false
}
],
"jsdoc/no-multi-asterisks": [
"warn",
{
"allowWhitespace": true
}
],
"jsdoc/tag-lines": "off"
}
},
{
"files": [
"packages/frameworks-sveltekit"
],
"plugins": [
"svelte3"
],
"overrides": [
{
"files": [
"*.svelte"
],
"processor": "svelte3/svelte3"
}
],
"parserOptions": {
"sourceType": "module",
"ecmaVersion": 2020
},
"env": {
"browser": true,
"es2017": true,
"node": true
}
}
],
"parserOptions": {
"sourceType": "module",
"ecmaVersion": "latest",
"ecmaFeatures": {
"jsx": true
}
},
"root": true
},
"prettier": {
"semi": false,
"singleQuote": false,
"overrides": [
{
"files": [
"apps/dev/nextjs/pages/api/auth/[...nextauth].ts",
"docs/{sidebars,docusaurus.config}.js"
],
"options": {
"printWidth": 150
}
},
{
"files": [
"**/*package.json"
],
"options": {
"trailingComma": "none"
}
}
]
}
}

View File

@@ -0,0 +1 @@
//registry.npmjs.org/:_authToken=${NPM_TOKEN}

View File

@@ -1,6 +1,6 @@
{
"name": "@next-auth/dgraph-adapter",
"version": "1.0.5",
"version": "1.0.6",
"description": "Dgraph adapter for next-auth.",
"homepage": "https://authjs.dev",
"repository": "https://github.com/nextauthjs/next-auth",

View File

@@ -0,0 +1 @@
//registry.npmjs.org/:_authToken=${NPM_TOKEN}

View File

@@ -1,7 +1,7 @@
{
"name": "@next-auth/dynamodb-adapter",
"repository": "https://github.com/nextauthjs/next-auth",
"version": "3.0.1",
"version": "3.0.2",
"description": "AWS DynamoDB adapter for next-auth.",
"keywords": [
"next-auth",
@@ -59,4 +59,4 @@
"dependencies": {
"uuid": "^9.0.0"
}
}
}

View File

@@ -9,10 +9,10 @@
* ## Installation
*
* ```bash npm2yarn2pnpm
* npm install next-auth @next-auth/dyanamodb-adapter
* npm install next-auth @next-auth/dynamodb-adapter
* ```
*
* @module @next-auth/dyanamodb-adapter
* @module @next-auth/dynamodb-adapter
*/
import { v4 as uuid } from "uuid"

View File

@@ -0,0 +1 @@
//registry.npmjs.org/:_authToken=${NPM_TOKEN}

View File

@@ -1,6 +1,6 @@
{
"name": "@next-auth/mongodb-adapter",
"version": "1.1.1",
"version": "1.1.2",
"description": "mongoDB adapter for next-auth.",
"homepage": "https://authjs.dev",
"repository": "https://github.com/nextauthjs/next-auth",
@@ -44,4 +44,4 @@
"jest": {
"preset": "@next-auth/adapter-test/jest"
}
}
}

View File

@@ -0,0 +1 @@
//registry.npmjs.org/:_authToken=${NPM_TOKEN}

View File

@@ -1,6 +1,6 @@
{
"name": "@next-auth/pouchdb-adapter",
"version": "0.1.6",
"version": "1.0.0",
"description": "PouchDB adapter for next-auth.",
"homepage": "https://authjs.dev",
"repository": "https://github.com/nextauthjs/next-auth",
@@ -58,4 +58,4 @@
"jest": {
"preset": "@next-auth/adapter-test/jest"
}
}
}

View File

@@ -0,0 +1 @@
//registry.npmjs.org/:_authToken=${NPM_TOKEN}

View File

@@ -1,6 +1,6 @@
{
"name": "@next-auth/prisma-adapter",
"version": "1.0.5",
"version": "1.0.6",
"description": "Prisma adapter for next-auth.",
"homepage": "https://authjs.dev",
"repository": "https://github.com/nextauthjs/next-auth",
@@ -52,4 +52,4 @@
"jest": {
"preset": "@next-auth/adapter-test/jest"
}
}
}

View File

@@ -0,0 +1 @@
//registry.npmjs.org/:_authToken=${NPM_TOKEN}

View File

@@ -1,6 +1,6 @@
{
"name": "@next-auth/sequelize-adapter",
"version": "1.0.7",
"version": "1.0.8",
"description": "Sequelize adapter for next-auth.",
"homepage": "https://authjs.dev",
"repository": "https://github.com/nextauthjs/next-auth",
@@ -42,4 +42,4 @@
"jest": {
"preset": "@next-auth/adapter-test/jest"
}
}
}

View File

@@ -9,7 +9,7 @@
* ## Installation
*
* ```bash npm2yarn2pnpm
* npm install install next-auth @next-auth/sequelize-adapter sequelize
* npm install next-auth @next-auth/sequelize-adapter sequelize
* ```
*
* @module @next-auth/sequelize-adapter

View File

@@ -0,0 +1 @@
//registry.npmjs.org/:_authToken=${NPM_TOKEN}

View File

@@ -1,6 +1,6 @@
{
"name": "@next-auth/typeorm-legacy-adapter",
"version": "2.0.1",
"version": "2.0.2",
"description": "TypeORM (legacy) adapter for next-auth.",
"homepage": "https://authjs.dev",
"repository": "https://github.com/nextauthjs/next-auth",
@@ -76,4 +76,4 @@
"jest": {
"preset": "@next-auth/adapter-test/jest"
}
}
}

1
packages/core/.npmrc Normal file
View File

@@ -0,0 +1 @@
//registry.npmjs.org/:_authToken=${NPM_TOKEN}

View File

@@ -1,6 +1,6 @@
{
"name": "@auth/core",
"version": "0.5.1",
"version": "0.6.0",
"description": "Authentication for the Web.",
"keywords": [
"authentication",

View File

@@ -5,10 +5,11 @@ const providersPath = join(process.cwd(), "src/providers")
const files = readdirSync(providersPath, "utf8")
const nonOAuthFile = ["oauth-types", "oauth", "index", "email", "credentials"]
const providers = files.map((file) => {
const strippedProviderName = file.substring(0, file.indexOf("."))
return `"${strippedProviderName}"`
}).filter((provider) => provider !== '"oauth-types"' && provider !== '"index"')
}).filter((provider) => !nonOAuthFile.includes(provider.replace(/"/g, '')))
const result = `
// THIS FILE IS AUTOGENERATED. DO NOT EDIT.

View File

@@ -1,6 +1,5 @@
interface ErrorCause extends Record<string, unknown> {}
/** @internal */
export class AuthError extends Error {
constructor(message: string | Error | ErrorCause, cause?: ErrorCause) {
if (message instanceof Error) {
@@ -91,7 +90,7 @@ export class InvalidCallbackUrl extends AuthError {}
export class InvalidEndpoints extends AuthError {}
/** @todo */
export class InvalidState extends AuthError {}
export class InvalidCheck extends AuthError {}
/** @todo */
export class JWTSessionError extends AuthError {}

View File

@@ -48,9 +48,10 @@ const DEFAULT_MAX_AGE = 30 * 24 * 60 * 60 // 30 days
const now = () => (Date.now() / 1000) | 0
/** Issues a JWT. By default, the JWT is encrypted using "A256GCM". */
export async function encode(params: JWTEncodeParams) {
export async function encode<Payload = JWT>(params: JWTEncodeParams<Payload>) {
const { token = {}, secret, maxAge = DEFAULT_MAX_AGE } = params
const encryptionSecret = await getDerivedEncryptionKey(secret)
// @ts-expect-error `jose` allows any object as payload.
return await new EncryptJWT(token)
.setProtectedHeader({ alg: "dir", enc: "A256GCM" })
.setIssuedAt()
@@ -60,14 +61,16 @@ export async function encode(params: JWTEncodeParams) {
}
/** Decodes a Auth.js issued JWT. */
export async function decode(params: JWTDecodeParams): Promise<JWT | null> {
export async function decode<Payload = JWT>(
params: JWTDecodeParams
): Promise<Payload | null> {
const { token, secret } = params
if (!token) return null
const encryptionSecret = await getDerivedEncryptionKey(secret)
const { payload } = await jwtDecrypt(token, encryptionSecret, {
clockTolerance: 15,
})
return payload
return payload as Payload
}
export interface GetTokenParams<R extends boolean = false> {
@@ -179,9 +182,9 @@ export interface DefaultJWT extends Record<string, unknown> {
*/
export interface JWT extends Record<string, unknown>, DefaultJWT {}
export interface JWTEncodeParams {
export interface JWTEncodeParams<Payload = JWT> {
/** The JWT payload. */
token?: JWT
token?: Payload
/** The secret used to encode the Auth.js issued JWT. */
secret: string
/**

View File

@@ -101,7 +101,8 @@ export function assertConfig(
)
}
for (const provider of options.providers) {
for (const p of options.providers) {
const provider = typeof p === "function" ? p() : p
if (
(provider.type === "oauth" || provider.type === "oidc") &&
!(provider.issuer ?? provider.options?.issuer)
@@ -127,7 +128,7 @@ export function assertConfig(
if (hasCredentials) {
const dbStrategy = options.session?.strategy === "database"
const onlyCredentials = !options.providers.some(
(p) => p.type !== "credentials"
(p) => (typeof p === "function" ? p() : p).type !== "credentials"
)
if (dbStrategy && onlyCredentials) {
return new UnsupportedStrategy(
@@ -135,9 +136,10 @@ export function assertConfig(
)
}
const credentialsNoAuthorize = options.providers.some(
(p) => p.type === "credentials" && !p.authorize
)
const credentialsNoAuthorize = options.providers.some((p) => {
const provider = typeof p === "function" ? p() : p
return provider.type === "credentials" && !provider.authorize
})
if (credentialsNoAuthorize) {
return new MissingAuthorize(
"Must define an authorize() handler to use credentials authentication provider"

View File

@@ -11,6 +11,7 @@ import type {
ResponseInternal,
} from "../types.js"
/** @internal */
export async function AuthInternal<
Body extends string | Record<string, any> | any[]
>(

View File

@@ -73,7 +73,7 @@ export async function handleOAuth(
const state = await checks.state.use(cookies, resCookies, options)
const parameters = o.validateAuthResponse(
const codeGrantParams = o.validateAuthResponse(
as,
client,
new URLSearchParams(query),
@@ -81,36 +81,22 @@ export async function handleOAuth(
)
/** https://www.rfc-editor.org/rfc/rfc6749#section-4.1.2.1 */
if (o.isOAuth2Error(parameters)) {
if (o.isOAuth2Error(codeGrantParams)) {
logger.debug("OAuthCallbackError", {
providerId: provider.id,
...parameters,
...codeGrantParams,
})
throw new OAuthCallbackError(parameters.error)
throw new OAuthCallbackError(codeGrantParams.error)
}
const codeVerifier = await checks.pkce.use(
cookies?.[options.cookies.pkceCodeVerifier.name],
options
)
if (codeVerifier) resCookies.push(codeVerifier.cookie)
// TODO:
const nonce = await checks.nonce.use(
cookies?.[options.cookies.nonce.name],
options
)
if (nonce && provider.type === "oidc") {
resCookies.push(nonce.cookie)
}
const codeVerifier = await checks.pkce.use(cookies, resCookies, options)
let codeGrantResponse = await o.authorizationCodeGrantRequest(
as,
client,
parameters,
codeGrantParams,
provider.callbackUrl,
codeVerifier?.codeVerifier ?? "auth" // TODO: review fallback code verifier
codeVerifier ?? "auth" // TODO: review fallback code verifier
)
if (provider.token?.conform) {
@@ -131,11 +117,12 @@ export async function handleOAuth(
let tokens: TokenSet
if (provider.type === "oidc") {
const nonce = await checks.nonce.use(cookies, resCookies, options)
const result = await o.processAuthorizationCodeOpenIDResponse(
as,
client,
codeGrantResponse,
nonce?.value ?? o.expectNoNonce
nonce ?? o.expectNoNonce
)
if (o.isOAuth2Error(result)) {

View File

@@ -1,14 +1,17 @@
import * as o from "oauth4webapi"
import * as jwt from "../../jwt.js"
import { InvalidCheck } from "../../errors.js"
import { encode, decode } from "../../jwt.js"
import type {
CookiesOptions,
InternalOptions,
RequestInternal,
CookiesOptions,
} from "../../types.js"
import type { Cookie } from "../cookie.js"
import { InvalidState } from "../../errors.js"
interface CheckPayload {
value: string
}
/** Returns a signed cookie. */
export async function signCookie(
@@ -25,7 +28,11 @@ export async function signCookie(
expires.setTime(expires.getTime() + maxAge * 1000)
return {
name: cookies[type].name,
value: await jwt.encode({ ...options.jwt, maxAge, token: { value } }),
value: await encode<CheckPayload>({
...options.jwt,
maxAge,
token: { value },
}),
options: { ...cookies[type].options, expires },
}
}
@@ -44,34 +51,43 @@ export const pkce = {
)
return { cookie, value }
},
/**
* Returns code_verifier if provider uses PKCE,
* Returns code_verifier if the provider is configured to use PKCE,
* and clears the container cookie afterwards.
* An error is thrown if the code_verifier is missing or invalid.
* @see https://www.rfc-editor.org/rfc/rfc7636
* @see https://danielfett.de/2020/05/16/pkce-vs-nonce-equivalent-or-not/#pkce
*/
async use(
codeVerifier: string | undefined,
cookies: RequestInternal["cookies"],
resCookies: Cookie[],
options: InternalOptions<"oauth">
): Promise<{ codeVerifier: string; cookie: Cookie } | undefined> {
const { cookies, provider } = options
): Promise<string | undefined> {
const { provider } = options
if (!provider?.checks?.includes("pkce") || !codeVerifier) {
return
}
if (!provider?.checks?.includes("pkce")) return
const pkce = (await jwt.decode({
const codeVerifier = cookies?.[options.cookies.pkceCodeVerifier.name]
if (!codeVerifier)
throw new InvalidCheck("PKCE code_verifier cookie was missing.")
const value = await decode<CheckPayload>({
...options.jwt,
token: codeVerifier,
})) as any
})
return {
codeVerifier: pkce?.value ?? undefined,
cookie: {
name: cookies.pkceCodeVerifier.name,
value: "",
options: { ...cookies.pkceCodeVerifier.options, maxAge: 0 },
},
}
if (!value?.value)
throw new InvalidCheck("PKCE code_verifier value could not be parsed.")
// Clear the pkce code verifier cookie after use
resCookies.push({
name: options.cookies.pkceCodeVerifier.name,
value: "",
options: { ...options.cookies.pkceCodeVerifier.options, maxAge: 0 },
})
return value.value
},
}
@@ -86,26 +102,29 @@ export const state = {
return { cookie, value }
},
/**
* Returns state from the saved cookie
* if the provider supports states,
* Returns state if the provider is configured to use state,
* and clears the container cookie afterwards.
* An error is thrown if the state is missing or invalid.
* @see https://www.rfc-editor.org/rfc/rfc6749#section-10.12
* @see https://www.rfc-editor.org/rfc/rfc6749#section-4.1.1
*/
async use(
cookies: RequestInternal["cookies"],
resCookies: Cookie[],
options: InternalOptions<"oauth">
): Promise<string | undefined> {
const { provider, jwt } = options
const { provider } = options
if (!provider.checks.includes("state")) return
const state = cookies?.[options.cookies.state.name]
if (!state) throw new InvalidState("State was missing from the cookies.")
if (!state) throw new InvalidCheck("State cookie was missing.")
// IDEA: Let the user do something with the returned state
const value = (await jwt.decode({ ...options.jwt, token: state })) as any
const value = await decode<CheckPayload>({ ...options.jwt, token: state })
if (!value?.value) throw new InvalidState("Could not parse state cookie.")
if (!value?.value)
throw new InvalidCheck("State value could not be parsed.")
// Clear the state cookie after use
resCookies.push({
@@ -128,28 +147,36 @@ export const nonce = {
return { cookie, value }
},
/**
* Returns nonce from if the provider supports nonce,
* Returns nonce if the provider is configured to use nonce,
* and clears the container cookie afterwards.
* An error is thrown if the nonce is missing or invalid.
* @see https://openid.net/specs/openid-connect-core-1_0.html#NonceNotes
* @see https://danielfett.de/2020/05/16/pkce-vs-nonce-equivalent-or-not/#nonce
*/
async use(
nonce: string | undefined,
cookies: RequestInternal["cookies"],
resCookies: Cookie[],
options: InternalOptions<"oauth">
): Promise<{ value: string; cookie: Cookie } | undefined> {
const { cookies, provider } = options
): Promise<string | undefined> {
const { provider } = options
if (!provider?.checks?.includes("nonce") || !nonce) {
return
}
if (!provider?.checks?.includes("nonce")) return
const value = (await jwt.decode({ ...options.jwt, token: nonce })) as any
const nonce = cookies?.[options.cookies.nonce.name]
if (!nonce) throw new InvalidCheck("Nonce cookie was missing.")
return {
value: value?.value ?? undefined,
cookie: {
name: cookies.nonce.name,
value: "",
options: { ...cookies.nonce.options, maxAge: 0 },
},
}
const value = await decode<CheckPayload>({ ...options.jwt, token: nonce })
if (!value?.value)
throw new InvalidCheck("Nonce value could not be parsed.")
// Clear the nonce cookie after use
resCookies.push({
name: options.cookies.nonce.name,
value: "",
options: { ...options.cookies.nonce.options, maxAge: 0 },
})
return value.value
},
}

View File

@@ -23,7 +23,8 @@ export default function parseProviders(params: {
} {
const { url, providerId } = params
const providers = params.providers.map((provider) => {
const providers = params.providers.map((p) => {
const provider = typeof p === "function" ? p() : p
const { options: userOptions, ...defaults } = provider
const id = (userOptions?.id ?? defaults.id) as string

View File

@@ -139,7 +139,6 @@ export async function callback(params: {
})
}
// @ts-expect-error
await events.signIn?.({ user, account, profile, isNewUser })
// Handle first logins on new accounts

View File

@@ -9,7 +9,7 @@ import type { SessionStore } from "../cookie.js"
export async function session(
sessionStore: SessionStore,
options: InternalOptions
): Promise<ResponseInternal<Session | {}>> {
): Promise<ResponseInternal<Session | null>> {
const {
adapter,
jwt,
@@ -19,8 +19,8 @@ export async function session(
session: { strategy: sessionStrategy, maxAge: sessionMaxAge },
} = options
const response: ResponseInternal<Session | {}> = {
body: {},
const response: ResponseInternal<Session | null> = {
body: null,
headers: { "Content-Type": "application/json" },
cookies: [],
}

View File

@@ -78,7 +78,6 @@ export function toResponse(res: ResponseInternal): Response {
const cookieHeader = serialize(name, value, options)
if (headers.has("Set-Cookie")) headers.append("Set-Cookie", cookieHeader)
else headers.set("Set-Cookie", cookieHeader)
// headers.set("Set-Cookie", cookieHeader) // TODO: Remove. Seems to be a bug with Headers in the runtime
})
let body = res.body

View File

@@ -44,7 +44,7 @@ export interface AzureADB2CProfile {
* 2. [App Registration](https://docs.microsoft.com/en-us/azure/active-directory-b2c/tutorial-register-applications)
* 3. [User Flow](https://docs.microsoft.com/en-us/azure/active-directory-b2c/tutorial-create-user-flows)
*
* For the step "User attributes and token claims" you might want to set the following:
* For the step "User attributes and token claims" set the following:
*
* - Collect attribute:
* - Email Address

View File

@@ -39,7 +39,7 @@ export interface EmailConfig extends CommonProviderOptions {
* @default 86400
*/
maxAge?: number
/** [Documentation](https://authjs.dev/reference/providers/email#customizing-emails) */
/** [Documentation](https://authjs.dev/guides/providers/email#customizing-emails) */
sendVerificationRequest: (
params: SendVerificationRequestParams
) => Awaitable<void>
@@ -55,7 +55,7 @@ export interface EmailConfig extends CommonProviderOptions {
* }
* })
* ```
* [Documentation](https://authjs.dev/reference/providers/email#customizing-the-verification-token)
* [Documentation](https://authjs.dev/guides/providers/email#customizing-the-verification-token)
*/
generateVerificationToken?: () => Awaitable<string>
/** If defined, it is used to hash the verification token when saving to the database . */
@@ -72,7 +72,7 @@ export interface EmailConfig extends CommonProviderOptions {
* By default, we treat email addresses as all lower case,
* but you can override this function to change this behavior.
*
* [Documentation](https://authjs.dev/reference/providers/email#normalizing-the-e-mail-address) | [RFC 2821](https://tools.ietf.org/html/rfc2821) | [Email syntax](https://en.wikipedia.org/wiki/Email_address#Syntax)
* [Documentation](https://authjs.dev/guides/providers/email#normalizing-the-e-mail-address) | [RFC 2821](https://tools.ietf.org/html/rfc2821) | [Email syntax](https://en.wikipedia.org/wiki/Email_address#Syntax)
*/
normalizeIdentifier?: (identifier: string) => string
}

View File

@@ -44,6 +44,12 @@ export interface CommonProviderOptions {
type: ProviderType
}
interface InternalProviderOptions {
/** Used to deep merge user-provided config with the default config
*/
options?: Record<string, unknown>
}
/**
* Must be a supported authentication provider config:
* - {@link OAuthConfig}
@@ -57,17 +63,14 @@ export interface CommonProviderOptions {
* @see [Credentials guide](https://authjs.dev/guides/providers/credentials)
*/
export type Provider<P extends Profile = Profile> = (
| OIDCConfig<P>
| OAuth2Config<P>
| EmailConfig
| CredentialsConfig
) & {
/**
* Used to deep merge user-provided config with the default config
* @internal
*/
options: Record<string, unknown>
}
| ((OIDCConfig<P> | OAuth2Config<P> | EmailConfig | CredentialsConfig) &
InternalProviderOptions)
| ((
...args: any
) => (OAuth2Config<P> | OIDCConfig<P> | EmailConfig | CredentialsConfig) &
InternalProviderOptions)
) &
InternalProviderOptions
export type BuiltInProviders = Record<
OAuthProviderType,

View File

@@ -19,7 +19,7 @@ type UrlParams = Record<string, unknown>
type EndpointRequest<C, R, P> = (
context: C & {
/** Provider is passed for convenience, ans also contains the `callbackUrl`. */
/** Provider is passed for convenience, and also contains the `callbackUrl`. */
provider: OAuthConfigInternal<P> & {
signinUrl: string
callbackUrl: string
@@ -150,6 +150,10 @@ export interface OAuth2Config<Profile>
checks?: Array<"pkce" | "state" | "none" | "nonce">
clientId?: string
clientSecret?: string
/**
* Pass overrides to the underlying OAuth library.
* See [`oauth4webapi` client](https://github.com/panva/oauth4webapi/blob/main/docs/interfaces/Client.md) for details.
*/
client?: Partial<Client>
style?: OAuthProviderButtonStyles
/**
@@ -179,7 +183,6 @@ export type OAuthEndpointType = "authorization" | "token" | "userinfo"
/**
* We parsed `authorization`, `token` and `userinfo`
* to always contain a valid `URL`, with the params
* @internal
*/
export type OAuthConfigInternal<Profile> = Omit<
OAuthConfig<Profile>,
@@ -189,6 +192,7 @@ export type OAuthConfigInternal<Profile> = Omit<
token?: {
url: URL
request?: TokenEndpointHandler["request"]
/** @internal */
conform?: TokenEndpointHandler["conform"]
}
userinfo?: { url: URL; request?: UserinfoEndpointHandler["request"] }

View File

@@ -7,6 +7,12 @@ export default function Reddit(options) {
authorization: "https://www.reddit.com/api/v1/authorize?scope=identity",
token: "https://www.reddit.com/api/v1/access_token",
userinfo: "https://oauth.reddit.com/api/v1/me",
checks: ["state"],
style: {
logo: "/reddit.svg",
bg: "#fff",
text: "#000",
},
options,
}
}

View File

@@ -1,23 +0,0 @@
/** @type {import(".").OAuthProvider} */
export default function Yandex(options) {
return {
id: "yandex",
name: "Yandex",
type: "oauth",
authorization:
"https://oauth.yandex.ru/authorize?scope=login:email+login:info",
token: "https://oauth.yandex.ru/token",
userinfo: "https://login.yandex.ru/info?format=json",
profile(profile) {
return {
id: profile.id,
name: profile.real_name,
email: profile.default_email,
image: profile.is_avatar_empty
? null
: `https://avatars.yandex.net/get-yapic/${profile.default_avatar_id}/islands-200`,
}
},
options,
}
}

View File

@@ -0,0 +1,156 @@
/**
* <div style={{backgroundColor: "#ffcc00", display: "flex", justifyContent: "space-between", color: "#000", padding: 16}}>
* <span>Built-in <b>Yandex</b> integration.</span>
* <a href="https://github.com">
* <img style={{display: "block"}} src="https://authjs.dev/img/providers/yandex.svg" height="48" width="48"/>
* </a>
* </div>
*
* ---
* @module providers/yandex
*/
import { OAuthConfig, OAuthUserConfig } from "."
/**
* @see [Getting information about the user](https://yandex.com/dev/id/doc/en/user-information)
* @see [Access to email address](https://yandex.com/dev/id/doc/en/user-information#email-access)
* @see [Access to the user's profile picture](https://yandex.com/dev/id/doc/en/user-information#avatar-access)
* @see [Access to the date of birth](https://yandex.com/dev/id/doc/en/user-information#birthday-access)
* @see [Access to login, first name, last name, and gender](https://yandex.com/dev/id/doc/en/user-information#name-access)
* @see [Access to the phone number](https://yandex.com/dev/id/doc/en/user-information#phone-access)
*/
export interface YandexProfile {
/** User's Yandex login. */
login: string
/** Yandex user's unique ID. */
id: string
/**
* The ID of the app the OAuth token in the request was issued for.
* Available in the [app properties](https://oauth.yandex.com/). To open properties, click the app name.
*/
client_id: string
/** Authorized Yandex user ID. It is formed on the Yandex side based on the `client_id` and `user_id` pair. */
psuid: string
/** An array of the user's email addresses. Currently only includes the default email address. */
emails?: string[]
/** The default email address for contacting the user. */
default_email?: string
/**
* Indicates that the stub (profile picture that is automatically assigned when registering in Yandex)
* ID is specified in the `default_avatar_id` field.
*/
is_avatar_empty?: boolean
/**
* ID of the Yandex user's profile picture.
* Format for downloading user avatars: `https://avatars.yandex.net/get-yapic/<default_avatar_id>/<size>`
* @example "https://avatars.yandex.net/get-yapic/31804/BYkogAC6AoB17bN1HKRFAyKiM4-1/islands-200"
* Available sizes:
* `islands-small`: 28×28 pixels.
* `islands-34`: 34×34 pixels.
* `islands-middle`: 42×42 pixels.
* `islands-50`: 50×50 pixels.
* `islands-retina-small`: 56×56 pixels.
* `islands-68`: 68×68 pixels.
* `islands-75`: 75×75 pixels.
* `islands-retina-middle`: 84×84 pixels.
* `islands-retina-50`: 100×100 pixels.
* `islands-200`: 200×200 pixels.
*/
default_avatar_id?: string
/**
* The user's date of birth in YYYY-MM-DD format.
* Unknown elements of the date are filled in with zeros, such as: `0000-12-23`.
* If the user's date of birth is unknow, birthday will be `null`
*/
birthday?: string | null
first_name?: string
last_name?: string
display_name?: string
/**
* The first and last name that the user specified in Yandex ID.
* Non-Latin characters of the first and last names are presented in Unicode format.
*/
real_name?: string
/** User's gender. `null` Stands for unknown or unspecified gender. Will be `undefined` if not provided by Yandex. */
sex?: "male" | "female" | null
/**
* The default phone number for contacting the user.
* The API can exclude the user's phone number from the response at its discretion.
* The field contains the following parameters:
* id: Phone number ID.
* number: The user's phone number.
*/
default_phone?: { id: number; number: string }
}
/**
* Add Yandex login to your page
*
* ## Example
*
* ```ts
* import { Auth } from "@auth/core"
* import Yandex from "@auth/core/providers/yandex"
*
* const request = new Request("https://example.com")
* const response = await Auth(request, {
* providers: [Yandex({ clientId: "", clientSecret: "" })],
* })
* ```
*
* ## Resources
*
* @see [Yandex - Creating an OAuth app](https://yandex.com/dev/id/doc/en/register-client#create)
* @see [Yandex - Manage OAuth apps](https://oauth.yandex.com/)
* @see [Yandex - OAuth documentation](https://yandex.com/dev/id/doc/en/)
* @see [Learn more about OAuth](https://authjs.dev/concepts/oauth)
* @see [Source code](https://github.com/nextauthjs/next-auth/blob/main/packages/core/src/providers/yandex.ts)
*
*:::tip
* The Yandex provider comes with a [default configuration](https://github.com/nextauthjs/next-auth/blob/main/packages/core/src/providers/yandex.ts).
* To override the defaults for your use case, check out [customizing a built-in OAuth provider](https://authjs.dev/guides/providers/custom-provider#override-default-options).
* :::
*
* :::info **Disclaimer**
* If you think you found a bug in the default configuration, you can [open an issue](https://authjs.dev/new/provider-issue).
*
* Auth.js strictly adheres to the specification and it cannot take responsibility for any deviation from
* the spec by the provider. You can open an issue, but if the problem is non-compliance with the spec,
* we might not pursue a resolution. You can ask for more help in [Discussions](https://authjs.dev/new/github-discussions).
* :::
*/
export default function Yandex(
options: OAuthUserConfig<YandexProfile>
): OAuthConfig<YandexProfile> {
return {
id: "yandex",
name: "Yandex",
type: "oauth",
/** @see [Data access](https://yandex.com/dev/id/doc/en/register-client#access) */
authorization:
"https://oauth.yandex.ru/authorize?scope=login:info+login:email+login:avatar",
token: "https://oauth.yandex.ru/token",
userinfo: "https://login.yandex.ru/info?format=json",
profile(profile) {
return {
id: profile.id,
name: profile.display_name ?? profile.real_name ?? profile.first_name,
email: profile.default_email ?? profile.emails?.[0] ?? null,
image:
!profile.is_avatar_empty && profile.default_avatar_id
? `https://avatars.yandex.net/get-yapic/${profile.default_avatar_id}/islands-200`
: null,
}
},
style: {
logo: "/yandex.svg",
logoDark: "/yandex.svg",
bg: "#ffcc00",
text: "#000",
bgDark: "#ffcc00",
textDark: "#000",
},
options,
}
}

View File

@@ -121,10 +121,10 @@ export interface Account extends Partial<OpenIDTokenEndpointResponse> {
/** The OAuth profile returned from your provider */
export interface Profile {
sub?: string
name?: string
email?: string
image?: string
sub?: string | null
name?: string | null
email?: string | null
image?: string | null
}
/** [Documentation](https://authjs.dev/guides/basics/callbacks) */
@@ -406,7 +406,7 @@ export interface RequestInternal {
/** @internal */
export interface ResponseInternal<
Body extends string | Record<string, any> | any[] = any
Body extends string | Record<string, any> | any[] | null = any
> {
status?: number
headers?: Headers | HeadersInit

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