mirror of
https://github.com/SrIzan10/next-auth.git
synced 2026-05-01 10:55:20 +00:00
Compare commits
24 Commits
next-auth@
...
next-auth@
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
58e30a6af6 | ||
|
|
b7ff987baf | ||
|
|
782812a52b | ||
|
|
32f2a0cea3 | ||
|
|
3343ef18b2 | ||
|
|
6280fe9e10 | ||
|
|
52a2bf3e28 | ||
|
|
180c6252d9 | ||
|
|
362e981e6d | ||
|
|
5198eb19f7 | ||
|
|
0210cfccf3 | ||
|
|
e90925bea0 | ||
|
|
27a0b70d87 | ||
|
|
c676e93d8a | ||
|
|
f498e9cd0a | ||
|
|
2f3396d376 | ||
|
|
e62f879ebd | ||
|
|
f67959eb04 | ||
|
|
060953dacf | ||
|
|
30ad639d16 | ||
|
|
777da4302d | ||
|
|
733fd5f234 | ||
|
|
a787efc6be | ||
|
|
261968b9bb |
1
.github/ISSUE_TEMPLATE/3_bug_adapter.yml
vendored
1
.github/ISSUE_TEMPLATE/3_bug_adapter.yml
vendored
@@ -33,6 +33,7 @@ body:
|
||||
- "@next-auth/sequelize-adapter"
|
||||
- "@next-auth/typeorm-legacy-adapter"
|
||||
- "@next-auth/upstash-redis-adapter"
|
||||
- "@next-auth/xata-adapter"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
|
||||
3
.github/issue-labeler.yml
vendored
3
.github/issue-labeler.yml
vendored
@@ -35,3 +35,6 @@ typeorm-legacy:
|
||||
|
||||
upstash-redis:
|
||||
- "@next-auth/upstash-redis-adapter"
|
||||
|
||||
xata:
|
||||
- "@next-auth/xata-adapter"
|
||||
|
||||
3
.github/pr-labeler.yml
vendored
3
.github/pr-labeler.yml
vendored
@@ -48,6 +48,9 @@ typeorm-legacy:
|
||||
upstash-redis:
|
||||
- packages/adapter-upstash-redis/**
|
||||
|
||||
xata:
|
||||
- packages/adapter-xata/**
|
||||
|
||||
core:
|
||||
- packages/next-auth/src/**/*
|
||||
|
||||
|
||||
6
.github/workflows/release.yml
vendored
6
.github/workflows/release.yml
vendored
@@ -25,7 +25,7 @@ jobs:
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 18
|
||||
cache: "pnpm"
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
@@ -59,7 +59,7 @@ jobs:
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 18
|
||||
cache: "pnpm"
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
@@ -89,7 +89,7 @@ jobs:
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 18
|
||||
cache: "pnpm"
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
@@ -41,7 +41,7 @@ corepack enable pnpm
|
||||
|
||||
(Now, if you run `pnpm --version`, it should print the same verion as the `packageManager` property in the [`package.json` file](https://github.com/nextauthjs/next-auth/blob/main/package.json))
|
||||
|
||||
3. Install packages. Developing requires Node.js v16:
|
||||
3. Install packages. Developing requires Node.js v18:
|
||||
|
||||
```sh
|
||||
pnpm install
|
||||
|
||||
12
apps/dev/app/layout.tsx
Normal file
12
apps/dev/app/layout.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<html>
|
||||
<head></head>
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
6
apps/dev/app/server-component/page.tsx
Normal file
6
apps/dev/app/server-component/page.tsx
Normal file
@@ -0,0 +1,6 @@
|
||||
import { unstable_getServerSession } from "next-auth/next"
|
||||
|
||||
export default async function Page() {
|
||||
const session = await unstable_getServerSession()
|
||||
return <pre>{JSON.stringify(session, null, 2)}</pre>
|
||||
}
|
||||
@@ -17,9 +17,7 @@ export default function Footer() {
|
||||
<a href="https://github.com/nextauthjs/next-auth-example">GitHub</a>
|
||||
</li>
|
||||
<li className={styles.navItem}>
|
||||
<Link href="/policy">
|
||||
<a>Policy</a>
|
||||
</Link>
|
||||
<Link href="/policy">Policy</Link>
|
||||
</li>
|
||||
<li className={styles.navItem}>
|
||||
<em>{packageJSON.version}</em>
|
||||
|
||||
@@ -64,49 +64,31 @@ export default function Header() {
|
||||
<nav>
|
||||
<ul className={styles.navItems}>
|
||||
<li className={styles.navItem}>
|
||||
<Link href="/">
|
||||
<a>Home</a>
|
||||
</Link>
|
||||
<Link href="/">Home</Link>
|
||||
</li>
|
||||
<li className={styles.navItem}>
|
||||
<Link href="/client">
|
||||
<a>Client</a>
|
||||
</Link>
|
||||
<Link href="/client">Client</Link>
|
||||
</li>
|
||||
<li className={styles.navItem}>
|
||||
<Link href="/server">
|
||||
<a>Server</a>
|
||||
</Link>
|
||||
<Link href="/server">Server</Link>
|
||||
</li>
|
||||
<li className={styles.navItem}>
|
||||
<Link href="/protected">
|
||||
<a>Protected</a>
|
||||
</Link>
|
||||
<Link href="/protected">Protected</Link>
|
||||
</li>
|
||||
<li className={styles.navItem}>
|
||||
<Link href="/protected-ssr">
|
||||
<a>Protected(SSR)</a>
|
||||
</Link>
|
||||
<Link href="/protected-ssr">Protected(SSR)</Link>
|
||||
</li>
|
||||
<li className={styles.navItem}>
|
||||
<Link href="/api-example">
|
||||
<a>API</a>
|
||||
</Link>
|
||||
<Link href="/api-example">API</Link>
|
||||
</li>
|
||||
<li className={styles.navItem}>
|
||||
<Link href="/credentials">
|
||||
<a>Credentials</a>
|
||||
</Link>
|
||||
<Link href="/credentials">Credentials</Link>
|
||||
</li>
|
||||
<li className={styles.navItem}>
|
||||
<Link href="/email">
|
||||
<a>Email</a>
|
||||
</Link>
|
||||
<Link href="/email">Email</Link>
|
||||
</li>
|
||||
<li className={styles.navItem}>
|
||||
<Link href="/middleware-protected">
|
||||
<a>Middleware protected</a>
|
||||
</Link>
|
||||
<Link href="/middleware-protected">Middleware protected</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
@@ -4,5 +4,6 @@ module.exports = {
|
||||
config.experiments = { ...config.experiments, topLevelAwait: true }
|
||||
return config
|
||||
},
|
||||
experimental: { appDir: true },
|
||||
typescript: { ignoreBuildErrors: true },
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
"@next-auth/typeorm-legacy-adapter": "workspace:*",
|
||||
"@prisma/client": "^3",
|
||||
"faunadb": "^4",
|
||||
"next": "12.3.1",
|
||||
"next": "13.0.2",
|
||||
"next-auth": "workspace:*",
|
||||
"nodemailer": "^6",
|
||||
"react": "^18",
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// This is an example of how to access a session from an API route
|
||||
import { unstable_getServerSession } from "next-auth/next"
|
||||
import { authOptions } from '../auth/[...nextauth]';
|
||||
import { authOptions } from "../auth/[...nextauth]"
|
||||
|
||||
export default async (req, res) => {
|
||||
const session = await unstable_getServerSession(req, res, authOptions)
|
||||
res.send(JSON.stringify(session, null, 2))
|
||||
res.json(session)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": false,
|
||||
@@ -15,7 +19,20 @@
|
||||
"incremental": true,
|
||||
"jsx": "preserve",
|
||||
"baseUrl": ".",
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
]
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
||||
"exclude": ["node_modules", "jest.config.js"]
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"jest.config.js"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -17,9 +17,7 @@ export default function Footer() {
|
||||
<a href="https://github.com/nextauthjs/next-auth-example">GitHub</a>
|
||||
</li>
|
||||
<li className={styles.navItem}>
|
||||
<Link href="/policy">
|
||||
<a>Policy</a>
|
||||
</Link>
|
||||
<Link href="/policy">Policy</Link>
|
||||
</li>
|
||||
<li className={styles.navItem}>
|
||||
<em>next-auth@{packageJSON.dependencies["next-auth"]}</em>
|
||||
|
||||
@@ -67,39 +67,25 @@ export default function Header() {
|
||||
<nav>
|
||||
<ul className={styles.navItems}>
|
||||
<li className={styles.navItem}>
|
||||
<Link href="/">
|
||||
<a>Home</a>
|
||||
</Link>
|
||||
<Link href="/">Home</Link>
|
||||
</li>
|
||||
<li className={styles.navItem}>
|
||||
<Link href="/client">
|
||||
<a>Client</a>
|
||||
</Link>
|
||||
<Link href="/client">Client</Link>
|
||||
</li>
|
||||
<li className={styles.navItem}>
|
||||
<Link href="/server">
|
||||
<a>Server</a>
|
||||
</Link>
|
||||
<Link href="/server">Server</Link>
|
||||
</li>
|
||||
<li className={styles.navItem}>
|
||||
<Link href="/protected">
|
||||
<a>Protected</a>
|
||||
</Link>
|
||||
<Link href="/protected">Protected</Link>
|
||||
</li>
|
||||
<li className={styles.navItem}>
|
||||
<Link href="/api-example">
|
||||
<a>API</a>
|
||||
</Link>
|
||||
<Link href="/api-example">API</Link>
|
||||
</li>
|
||||
<li className={styles.navItem}>
|
||||
<Link href="/admin">
|
||||
<a>Admin</a>
|
||||
</Link>
|
||||
<Link href="/admin">Admin</Link>
|
||||
</li>
|
||||
<li className={styles.navItem}>
|
||||
<Link href="/me">
|
||||
<a>Me</a>
|
||||
</Link>
|
||||
<Link href="/me">Me</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
import Header from "./header"
|
||||
import Footer from "./footer"
|
||||
import type { ReactChildren } from "react"
|
||||
import type { ReactNode } from "react"
|
||||
|
||||
interface Props {
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
export default function Layout({ children }: Props) {
|
||||
export default function Layout({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
|
||||
@@ -13,13 +13,12 @@ export default function ServerSidePage({ session }: { session: Session }) {
|
||||
<h1>Server Side Rendering</h1>
|
||||
<p>
|
||||
This page uses the <strong>unstable_getServerSession()</strong> method
|
||||
in <strong>unstable_getServerSideProps()</strong>.
|
||||
in <strong>getServerSideProps()</strong>.
|
||||
</p>
|
||||
<p>
|
||||
Using <strong>unstable_getServerSession()</strong> in{" "}
|
||||
<strong>unstable_getServerSideProps()</strong> is the recommended
|
||||
approach if you need to support Server Side Rendering with
|
||||
authentication.
|
||||
<strong>getServerSideProps()</strong> is the recommended approach if you
|
||||
need to support Server Side Rendering with authentication.
|
||||
</p>
|
||||
<p>
|
||||
The advantage of Server Side Rendering is this page does not require
|
||||
|
||||
12
apps/playground-nuxt/.editorconfig
Normal file
12
apps/playground-nuxt/.editorconfig
Normal file
@@ -0,0 +1,12 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
4
apps/playground-nuxt/.eslintignore
Normal file
4
apps/playground-nuxt/.eslintignore
Normal file
@@ -0,0 +1,4 @@
|
||||
dist
|
||||
node_modules
|
||||
tsconfig.json
|
||||
package.json
|
||||
10
apps/playground-nuxt/.eslintrc
Normal file
10
apps/playground-nuxt/.eslintrc
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": [
|
||||
"@nuxtjs/eslint-config-typescript"
|
||||
],
|
||||
"rules": {
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"off"
|
||||
]
|
||||
}
|
||||
}
|
||||
52
apps/playground-nuxt/.gitignore
vendored
Normal file
52
apps/playground-nuxt/.gitignore
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
# Dependencies
|
||||
node_modules
|
||||
|
||||
# Logs
|
||||
*.log*
|
||||
|
||||
# Temp directories
|
||||
.temp
|
||||
.tmp
|
||||
.cache
|
||||
|
||||
# Yarn
|
||||
**/.yarn/cache
|
||||
**/.yarn/*state*
|
||||
|
||||
# Generated dirs
|
||||
dist
|
||||
|
||||
# Nuxt
|
||||
.nuxt
|
||||
.output
|
||||
.vercel_build_output
|
||||
.build-*
|
||||
.env
|
||||
.netlify
|
||||
|
||||
# Env
|
||||
.env
|
||||
|
||||
# Testing
|
||||
reports
|
||||
coverage
|
||||
*.lcov
|
||||
.nyc_output
|
||||
|
||||
# VSCode
|
||||
.vscode
|
||||
|
||||
# Intellij idea
|
||||
*.iml
|
||||
.idea
|
||||
|
||||
# OSX
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
.vercel
|
||||
1
apps/playground-nuxt/.nuxtrc
Normal file
1
apps/playground-nuxt/.nuxtrc
Normal file
@@ -0,0 +1 @@
|
||||
imports.autoImport=false
|
||||
108
apps/playground-nuxt/README.md
Normal file
108
apps/playground-nuxt/README.md
Normal file
@@ -0,0 +1,108 @@
|
||||
# NextAuth + Nuxt 3 Playground
|
||||
|
||||
NextAuth.js is committed to bringing easy authentication to other frameworks. [#2294](https://github.com/nextauthjs/next-auth/issues/2294)
|
||||
|
||||
Nuxt 3 support with NextAuth.js is currently experimental. This directory contains a minimal, proof-of-concept application. Parts of this is expected to be abstracted away into a package like` @next-auth/nuxt.`
|
||||
|
||||
This package uses Nuxt's [module starter](https://github.com/nuxt/starter/tree/module).
|
||||
|
||||
Demo: https://next-auth-nuxt-demo.vercel.app
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Add the module to the modules section of `nuxt.config.ts`:
|
||||
|
||||
```ts
|
||||
export default defineNuxtConfig({
|
||||
// temporary module name.
|
||||
modules: ['next-auth-nuxt'],
|
||||
// https://v3.nuxtjs.org/migration/runtime-config#runtime-config
|
||||
runtimeConfig: {
|
||||
secret: process.env.NEXTAUTH_SECRET
|
||||
github: {
|
||||
clientId: process.env.GITHUB_CLIENT_ID,
|
||||
clientSecret: process.env.GITHUB_CLIENT_SECRET
|
||||
}
|
||||
},
|
||||
// https://v3.nuxtjs.org/guide/concepts/esm#aliasing-libraries
|
||||
// Fix for GithubProvider (or whichever provider you choose) is not a function error in Vite
|
||||
alias: {
|
||||
'next-auth/providers/github': 'node_modules/next-auth/providers/github.js'
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Add API route
|
||||
|
||||
To add `NextAuth.js` to a project create a file called `[...].ts` in `server/api/auth`. This contains the dynamic route handler for NextAuth.js which will also contain all of your global NextAuth.js configurations.
|
||||
|
||||
```ts
|
||||
// ~/server/api/auth/[...].ts
|
||||
import { NextAuthNuxtHandler } from 'next-auth-nuxt/handler'
|
||||
import GithubProvider from 'next-auth/providers/github'
|
||||
|
||||
const runtimeConfig = useRuntimeConfig()
|
||||
|
||||
export const authOptions = {
|
||||
secret: runtimeConfig.secret,
|
||||
providers: [
|
||||
GithubProvider({
|
||||
clientId: runtimeConfig.github.clientId,
|
||||
clientSecret: runtimeConfig.github.clientSecret
|
||||
}),
|
||||
],
|
||||
}
|
||||
|
||||
export default NextAuthNuxtHandler(authOptions)
|
||||
```
|
||||
|
||||
All requests to `/api/auth/*` (`signIn`, `callback`, `signOut`, etc.) will automatically be handled by NextAuth.js.
|
||||
|
||||
### Frontend - Add Vue Composable
|
||||
|
||||
The `useSession()` Vue Composable is the easiest way to check if someone is signed in.
|
||||
|
||||
```html
|
||||
<script setup lang="ts">
|
||||
const { data: session } = useSession()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="session">
|
||||
Signed in as {{ session.user.email }} <br />
|
||||
<button @click="signOut">Sign out</button>
|
||||
</div>
|
||||
<div v-else>
|
||||
Not signed in <br />
|
||||
<button @click="signIn">Sign in</button>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
### Backend - API Route
|
||||
|
||||
To protect an API Route, you can use the `getServerSession()` method.
|
||||
|
||||
```ts
|
||||
import { getServerSession } from 'next-auth-nuxt/handler'
|
||||
import { authOptions } from '~/server/api/auth/[...]'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const session = await getServerSession(event, authOptions)
|
||||
|
||||
if (session) {
|
||||
return {
|
||||
content: 'This is protected content. You can access this content because you are signed in.'
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
error: 'You must be signed in to view the protected content on this page.'
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
- Run `pnpm dev:generate` to generate type stubs.
|
||||
- Use `pnpm dev` to start `playground` in development mode.
|
||||
1
apps/playground-nuxt/client.d.ts
vendored
Normal file
1
apps/playground-nuxt/client.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export * from './dist/runtime/client'
|
||||
1
apps/playground-nuxt/handler.d.ts
vendored
Normal file
1
apps/playground-nuxt/handler.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export * from './dist/runtime/server/handler'
|
||||
49
apps/playground-nuxt/package.json
Normal file
49
apps/playground-nuxt/package.json
Normal file
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"name": "next-auth-nuxt",
|
||||
"type": "module",
|
||||
"version": "0.0.0",
|
||||
"packageManager": "pnpm@7.1.1",
|
||||
"license": "MIT",
|
||||
"main": "./dist/module.cjs",
|
||||
"types": "./dist/types.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/module.mjs",
|
||||
"require": "./dist/module.cjs"
|
||||
},
|
||||
"./handler": {
|
||||
"import": "./dist/runtime/server/handler.mjs",
|
||||
"types": "./dist/runtime/server/handler.d.ts"
|
||||
},
|
||||
"./client": {
|
||||
"import": "./dist/runtime/client/index.mjs",
|
||||
"types": "./dist/runtime/client/index.d.ts"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"handler.d.ts",
|
||||
"client.d.ts"
|
||||
],
|
||||
"scripts": {
|
||||
"prepack": "nuxt-module-build",
|
||||
"dev": "pnpm prepack && nuxi dev playground",
|
||||
"dev:build": "nuxi build playground",
|
||||
"dev:build:vercel": "NITRO_PRESET=vercel nuxi build playground",
|
||||
"dev:prepare": "nuxt-module-build --stub && nuxi prepare playground"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nuxt/kit": "^3.0.0-rc.13",
|
||||
"h3": "^0.8.6",
|
||||
"next-auth": "^4.16.2",
|
||||
"pathe": "^0.3.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nuxt/module-builder": "^0.2.0",
|
||||
"@nuxt/schema": "^3.0.0-rc.12",
|
||||
"@nuxtjs/eslint-config-typescript": "^11.0.0",
|
||||
"eslint": "^8.26.0",
|
||||
"nuxt": "^3.0.0-rc.13",
|
||||
"next-auth-nuxt": "workspace:*"
|
||||
}
|
||||
}
|
||||
4
apps/playground-nuxt/playground/.env.example
Normal file
4
apps/playground-nuxt/playground/.env.example
Normal file
@@ -0,0 +1,4 @@
|
||||
GITHUB_CLIENT_ID=
|
||||
GITHUB_CLIENT_SECRET=
|
||||
NEXTAUTH_URL=
|
||||
NEXTAUTH_SECRET=
|
||||
40
apps/playground-nuxt/playground/app.vue
Normal file
40
apps/playground-nuxt/playground/app.vue
Normal file
@@ -0,0 +1,40 @@
|
||||
<template>
|
||||
<div>
|
||||
<Header />
|
||||
<NuxtPage />
|
||||
<Footer />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans, sans-serif, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
|
||||
padding: 0 1rem 1rem 1rem;
|
||||
max-width: 680px;
|
||||
margin: 0 auto;
|
||||
background: #fff;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,8 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>Access Denied</h1>
|
||||
<p>
|
||||
<a href="/api/auth/signin">You must be signed in to view this page</a>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
30
apps/playground-nuxt/playground/components/Footer.vue
Normal file
30
apps/playground-nuxt/playground/components/Footer.vue
Normal file
@@ -0,0 +1,30 @@
|
||||
<template>
|
||||
<footer class="fotter">
|
||||
<hr>
|
||||
<ul class="navItems">
|
||||
<li class="navItem">
|
||||
<a href="https://github.com/nextauthjs/next-auth/tree/main/apps/playground-nuxt">Demo GitHub</a>
|
||||
</li>
|
||||
<li class="navItem">
|
||||
<a href="https://next-auth.js.org">Next.js Documentation</a>
|
||||
</li>
|
||||
</ul>
|
||||
</footer>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.footer {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.navItems {
|
||||
margin-bottom: 1rem;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.navItem {
|
||||
display: inline-block;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
</style>
|
||||
155
apps/playground-nuxt/playground/components/Header.vue
Normal file
155
apps/playground-nuxt/playground/components/Header.vue
Normal file
@@ -0,0 +1,155 @@
|
||||
<script setup lang="ts">
|
||||
import { useSession, signIn, signOut, computed } from '#imports'
|
||||
|
||||
const { data: session, status } = useSession()
|
||||
const loading = computed(() => status.value === 'loading')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<header>
|
||||
<div class="signedInStatus">
|
||||
<p :class="['nojs-show', !session && loading ? 'loading' : 'loaded']">
|
||||
<template v-if="session">
|
||||
<span v-if="session.user?.image" :style="{ backgroundImage: `url(${session.user.image})` }" class="avatar" />
|
||||
<span class="signedInText">
|
||||
<small>Signed in as</small><br>
|
||||
<strong>{{ session.user?.email || session.user?.name }}</strong>
|
||||
</span>
|
||||
<a href="/api/auth/signout" class="button" @click.prevent="signOut">Sign out</a>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span class="notSignedInText">You are not signed in</span>
|
||||
<a href="/api/auth/signin" class="buttonPrimary" @click.prevent="signIn">Sign in</a>
|
||||
</template>
|
||||
</p>
|
||||
</div>
|
||||
<nav>
|
||||
<ul class="navItems">
|
||||
<li class="navItem">
|
||||
<NuxtLink to="/">
|
||||
Home
|
||||
</NuxtLink>
|
||||
</li>
|
||||
<li class="navItem">
|
||||
<NuxtLink to="/client">
|
||||
Client
|
||||
</NuxtLink>
|
||||
</li>
|
||||
<li class="navItem">
|
||||
<NuxtLink to="/server">
|
||||
Server
|
||||
</NuxtLink>
|
||||
</li>
|
||||
<li class="navItem">
|
||||
<NuxtLink to="/protected">
|
||||
Protected
|
||||
</NuxtLink>
|
||||
</li>
|
||||
<li class="navItem">
|
||||
<NuxtLink to="/api-example">
|
||||
API
|
||||
</NuxtLink>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.nojs-show {
|
||||
opacity: 1;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
</style>
|
||||
20
apps/playground-nuxt/playground/nuxt.config.ts
Normal file
20
apps/playground-nuxt/playground/nuxt.config.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import MyModule from '../src/module'
|
||||
|
||||
export default defineNuxtConfig({
|
||||
modules: [
|
||||
MyModule
|
||||
],
|
||||
// https://v3.nuxtjs.org/migration/runtime-config#runtime-config
|
||||
runtimeConfig: {
|
||||
secret: process.env.NEXTAUTH_SECRET,
|
||||
github: {
|
||||
clientId: process.env.GITHUB_CLIENT_ID,
|
||||
clientSecret: process.env.GITHUB_CLIENT_SECRET
|
||||
}
|
||||
},
|
||||
// https://v3.nuxtjs.org/guide/concepts/esm#aliasing-libraries
|
||||
// Fix for GithubProvider is not a function error in Vite
|
||||
alias: {
|
||||
'next-auth/providers/github': 'node_modules/next-auth/providers/github.js'
|
||||
}
|
||||
})
|
||||
4
apps/playground-nuxt/playground/package.json
Normal file
4
apps/playground-nuxt/playground/package.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "playground",
|
||||
"private": true
|
||||
}
|
||||
15
apps/playground-nuxt/playground/pages/api-example.vue
Normal file
15
apps/playground-nuxt/playground/pages/api-example.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<div>
|
||||
<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" />
|
||||
</div>
|
||||
</template>
|
||||
18
apps/playground-nuxt/playground/pages/client.vue
Normal file
18
apps/playground-nuxt/playground/pages/client.vue
Normal file
@@ -0,0 +1,18 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>Client Side Rendering</h1>
|
||||
<p>
|
||||
This page uses the <strong>useSession()</strong> Vue Composable in the <strong><Header/></strong> component.
|
||||
</p>
|
||||
<p>
|
||||
The <strong>useSession()</strong> Vue Composable is 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 a provided session via <strong>Vue Plugin</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>
|
||||
</div>
|
||||
</template>
|
||||
8
apps/playground-nuxt/playground/pages/index.vue
Normal file
8
apps/playground-nuxt/playground/pages/index.vue
Normal file
@@ -0,0 +1,8 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>Nuxt 3 + NextAuth.js Example</h1>
|
||||
<p>
|
||||
This is an example site to demonstrate how to use <a href="https://v3.nuxtjs.org/">Nuxt 3</a> with <a href="https://next-auth.js.org">NextAuth.js</a> for authentication.
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
19
apps/playground-nuxt/playground/pages/protected.vue
Normal file
19
apps/playground-nuxt/playground/pages/protected.vue
Normal file
@@ -0,0 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
import { useSession, useFetch, useLazyFetch } from '#imports'
|
||||
import AccessDenied from '~/components/AccessDenied.vue'
|
||||
|
||||
const { data: session } = useSession()
|
||||
const { data } = await useLazyFetch('/api/examples/protected', {
|
||||
server: false
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<AccessDenied v-if="!session" />
|
||||
<template v-else>
|
||||
<h1>Protected Page</h1>
|
||||
<p><strong>{{ data?.content || "\u00a0" }}</strong></p>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
24
apps/playground-nuxt/playground/pages/server.vue
Normal file
24
apps/playground-nuxt/playground/pages/server.vue
Normal file
@@ -0,0 +1,24 @@
|
||||
<script setup lang="ts">
|
||||
import { useFetch } from '#imports'
|
||||
|
||||
await useFetch('/api/examples/session')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h1>Server Side Rendering</h1>
|
||||
<p>
|
||||
This page uses the <strong>getServerSession()</strong> method inside an api route and is fetched using the <strong>useFetch()</strong> composable.
|
||||
</p>
|
||||
<p>
|
||||
Using <strong>getServerSession()</strong> is the recommended approach if you need to
|
||||
support Server Side Rendering with authentication.
|
||||
</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>
|
||||
</div>
|
||||
</template>
|
||||
17
apps/playground-nuxt/playground/server/api/auth/[...].ts
Normal file
17
apps/playground-nuxt/playground/server/api/auth/[...].ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { NextAuthNuxtHandler } from 'next-auth-nuxt/handler'
|
||||
import GithubProvider from 'next-auth/providers/github'
|
||||
import type { NextAuthOptions } from 'next-auth'
|
||||
|
||||
const runtimeConfig = useRuntimeConfig()
|
||||
|
||||
export const authOptions: NextAuthOptions = {
|
||||
secret: runtimeConfig.secret,
|
||||
providers: [
|
||||
GithubProvider({
|
||||
clientId: runtimeConfig.github.clientId,
|
||||
clientSecret: runtimeConfig.github.clientSecret
|
||||
})
|
||||
]
|
||||
}
|
||||
|
||||
export default NextAuthNuxtHandler(authOptions)
|
||||
10
apps/playground-nuxt/playground/server/api/examples/jwt.ts
Normal file
10
apps/playground-nuxt/playground/server/api/examples/jwt.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { getToken } from 'next-auth/jwt'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
// @ts-expect-error: cookies property is not present in h3
|
||||
event.req.cookies = parseCookies(event)
|
||||
const token = await getToken({
|
||||
req: event.req
|
||||
})
|
||||
return token
|
||||
})
|
||||
@@ -0,0 +1,16 @@
|
||||
import { getServerSession } from 'next-auth-nuxt/handler'
|
||||
import { authOptions } from '../auth/[...]'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const session = await getServerSession(event, authOptions)
|
||||
|
||||
if (session) {
|
||||
return {
|
||||
content: 'This is protected content. You can access this content because you are signed in.'
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
error: 'You must be signed in to view the protected content on this page.'
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,7 @@
|
||||
import { getServerSession } from 'next-auth-nuxt/handler'
|
||||
import { authOptions } from '../auth/[...]'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const session = await getServerSession(event, authOptions)
|
||||
return session
|
||||
})
|
||||
6386
apps/playground-nuxt/pnpm-lock.yaml
generated
Normal file
6386
apps/playground-nuxt/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
2
apps/playground-nuxt/pnpm-workspace.yaml
Normal file
2
apps/playground-nuxt/pnpm-workspace.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
packages:
|
||||
- playground
|
||||
40
apps/playground-nuxt/src/module.ts
Normal file
40
apps/playground-nuxt/src/module.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { fileURLToPath } from 'url'
|
||||
import { addImports, addPlugin, defineNuxtModule, extendViteConfig } from '@nuxt/kit'
|
||||
import { resolve } from 'pathe'
|
||||
|
||||
export interface ModuleOptions {
|
||||
}
|
||||
|
||||
export default defineNuxtModule<ModuleOptions>({
|
||||
meta: {
|
||||
name: 'next-auth-nuxt',
|
||||
configKey: 'auth'
|
||||
},
|
||||
defaults: {
|
||||
},
|
||||
async setup (_options, nuxt) {
|
||||
const runtimeDir = fileURLToPath(new URL('./runtime', import.meta.url))
|
||||
nuxt.options.build.transpile.push(runtimeDir)
|
||||
|
||||
addPlugin(resolve(runtimeDir, 'plugin.client'))
|
||||
|
||||
// Composables are auto-imported in client.
|
||||
const client = resolve(runtimeDir, 'client')
|
||||
await addImports([
|
||||
{ name: 'getSession', from: client },
|
||||
{ name: 'getCsrfToken', from: client },
|
||||
{ name: 'getProviders', from: client },
|
||||
{ name: 'signIn', from: client },
|
||||
{ name: 'signOut', from: client },
|
||||
{ name: 'useSession', from: client }
|
||||
])
|
||||
|
||||
// We can safely expose this to client.
|
||||
extendViteConfig((config) => {
|
||||
config.define = config.define || {}
|
||||
config.define['process.env.NEXTAUTH_URL'] = JSON.stringify(process.env.NEXTAUTH_URL)
|
||||
config.define['process.env.NEXTAUTH_URL_INTERNAL'] = JSON.stringify(process.env.NEXTAUTH_URL_INTERNAL)
|
||||
config.define['process.env.VERCEL_URL'] = JSON.stringify(process.env.VERCEL_URL)
|
||||
})
|
||||
}
|
||||
})
|
||||
369
apps/playground-nuxt/src/runtime/client/index.ts
Normal file
369
apps/playground-nuxt/src/runtime/client/index.ts
Normal file
@@ -0,0 +1,369 @@
|
||||
import type { NextAuthClientConfig } from 'next-auth/client/_utils'
|
||||
import type { Plugin, Ref } from 'vue'
|
||||
import { ref, reactive, computed, inject, toRefs } from 'vue'
|
||||
import { BroadcastChannel, apiBaseUrl, fetchData, now } from 'next-auth/client/_utils'
|
||||
import type { Session } from 'next-auth'
|
||||
import type {
|
||||
BuiltInProviderType,
|
||||
RedirectableProviderType
|
||||
} from 'next-auth/providers'
|
||||
import type { H3EventContext } from 'h3'
|
||||
import parseUrl from '../lib/parse-url'
|
||||
import _logger, { proxyLogger } from '../lib/logger'
|
||||
import type {
|
||||
ClientSafeProvider,
|
||||
LiteralUnion,
|
||||
SessionProviderProps,
|
||||
SignInAuthorizationParams,
|
||||
SignInOptions,
|
||||
SignInResponse,
|
||||
SignOutParams,
|
||||
SignOutResponse
|
||||
} from '../types'
|
||||
|
||||
// This behaviour mirrors the default behaviour for getting the site name that
|
||||
// happens server side in server/index.js
|
||||
// 1. An empty value is legitimate when the code is being invoked client side as
|
||||
// relative URLs are valid in that context and so defaults to empty.
|
||||
// 2. When invoked server side the value is picked up from an environment
|
||||
// variable and defaults to 'http://localhost:3000'.
|
||||
const __NEXTAUTH: NextAuthClientConfig = {
|
||||
baseUrl: parseUrl(process.env.NEXTAUTH_URL ?? process.env.VERCEL_URL).origin,
|
||||
basePath: parseUrl(process.env.NEXTAUTH_URL).path,
|
||||
baseUrlServer: parseUrl(
|
||||
process.env.NEXTAUTH_URL_INTERNAL ??
|
||||
process.env.NEXTAUTH_URL ??
|
||||
process.env.VERCEL_URL
|
||||
).origin,
|
||||
basePathServer: parseUrl(
|
||||
process.env.NEXTAUTH_URL_INTERNAL ?? process.env.NEXTAUTH_URL
|
||||
).path,
|
||||
_lastSync: 0,
|
||||
_session: undefined,
|
||||
_getSession: () => {}
|
||||
}
|
||||
|
||||
export interface CtxOrReq {
|
||||
req?: H3EventContext['req']
|
||||
event?: { req: H3EventContext['req'] }
|
||||
}
|
||||
|
||||
export type GetSessionParams = CtxOrReq & {
|
||||
event?: 'storage' | 'timer' | 'hidden' | string
|
||||
triggerEvent?: boolean
|
||||
broadcast?: boolean
|
||||
}
|
||||
|
||||
const logger = proxyLogger(_logger, __NEXTAUTH.basePath)
|
||||
|
||||
const broadcast = BroadcastChannel()
|
||||
|
||||
function isServer () {
|
||||
return (process as any).server
|
||||
}
|
||||
|
||||
export async function getSession (params?: GetSessionParams) {
|
||||
const session = await fetchData<Session>(
|
||||
'session',
|
||||
__NEXTAUTH,
|
||||
logger,
|
||||
params
|
||||
)
|
||||
if (params?.broadcast ?? true) { broadcast.post({ event: 'session', data: { trigger: 'getSession' } }) }
|
||||
|
||||
return session
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current Cross Site Request Forgery Token (CSRF Token)
|
||||
* required to make POST requests (e.g. for signing in and signing out).
|
||||
* You likely only need to use this if you are not using the built-in
|
||||
* `signIn()` and `signOut()` methods.
|
||||
*
|
||||
* [Documentation](https://next-auth.js.org/getting-started/client#getcsrftoken)
|
||||
*/
|
||||
export async function getCsrfToken (params?: CtxOrReq) {
|
||||
const response = await fetchData<{ csrfToken: string }>(
|
||||
'csrf',
|
||||
__NEXTAUTH,
|
||||
logger,
|
||||
params
|
||||
)
|
||||
return response?.csrfToken
|
||||
}
|
||||
|
||||
/**
|
||||
* It calls `/api/auth/providers` and returns
|
||||
* a list of the currently configured authentication providers.
|
||||
* It can be useful if you are creating a dynamic custom sign in page.
|
||||
*
|
||||
* [Documentation](https://next-auth.js.org/getting-started/client#getproviders)
|
||||
*/
|
||||
export async function getProviders () {
|
||||
return await fetchData<
|
||||
Record<LiteralUnion<BuiltInProviderType>, ClientSafeProvider>
|
||||
>('providers', __NEXTAUTH, logger)
|
||||
}
|
||||
|
||||
/**
|
||||
* Client-side method to initiate a signin flow
|
||||
* or send the user to the signin page listing all possible providers.
|
||||
* Automatically adds the CSRF token to the request.
|
||||
*
|
||||
* [Documentation](https://next-auth.js.org/getting-started/client#signin)
|
||||
*/
|
||||
export async function signIn<
|
||||
P extends RedirectableProviderType | undefined = undefined,
|
||||
> (
|
||||
provider?: LiteralUnion<BuiltInProviderType>,
|
||||
options?: SignInOptions,
|
||||
authorizationParams?: SignInAuthorizationParams
|
||||
): Promise<
|
||||
P extends RedirectableProviderType ? SignInResponse | undefined : undefined
|
||||
> {
|
||||
const { callbackUrl = window.location.href, redirect = true } = options ?? {}
|
||||
|
||||
const baseUrl = apiBaseUrl(__NEXTAUTH)
|
||||
const providers = await getProviders()
|
||||
|
||||
if (!providers) {
|
||||
window.location.href = `${baseUrl}/error`
|
||||
return
|
||||
}
|
||||
|
||||
if (!provider || !(provider in providers)) {
|
||||
window.location.href = `${baseUrl}/signin?${new URLSearchParams({
|
||||
callbackUrl
|
||||
})}`
|
||||
return
|
||||
}
|
||||
|
||||
const isCredentials = providers[provider].type === 'credentials'
|
||||
const isEmail = providers[provider].type === 'email'
|
||||
const isSupportingReturn = isCredentials || isEmail
|
||||
|
||||
const signInUrl = `${baseUrl}/${
|
||||
isCredentials ? 'callback' : 'signin'
|
||||
}/${provider}`
|
||||
|
||||
const _signInUrl = `${signInUrl}?${new URLSearchParams(authorizationParams)}`
|
||||
|
||||
const res = await fetch(_signInUrl, {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
// @ts-expect-error: Internal
|
||||
body: new URLSearchParams({
|
||||
...options,
|
||||
csrfToken: await getCsrfToken(),
|
||||
callbackUrl,
|
||||
json: true
|
||||
})
|
||||
})
|
||||
|
||||
const data = await res.json()
|
||||
|
||||
if (redirect || !isSupportingReturn) {
|
||||
const url = data.url ?? callbackUrl
|
||||
window.location.href = url
|
||||
// If url contains a hash, the browser does not reload the page. We reload manually
|
||||
if (url.includes('#')) { window.location.reload() }
|
||||
return
|
||||
}
|
||||
|
||||
const error = new URL(data.url).searchParams.get('error')
|
||||
|
||||
if (res.ok) { await __NEXTAUTH._getSession({ event: 'storage' }) }
|
||||
|
||||
return {
|
||||
error,
|
||||
status: res.status,
|
||||
ok: res.ok,
|
||||
url: error ? null : data.url
|
||||
} as any
|
||||
}
|
||||
|
||||
/**
|
||||
* Signs the user out, by removing the session cookie.
|
||||
* Automatically adds the CSRF token to the request.
|
||||
*
|
||||
* [Documentation](https://next-auth.js.org/getting-started/client#signout)
|
||||
*/
|
||||
export async function signOut<R extends boolean = true> (
|
||||
options?: SignOutParams<R>
|
||||
): Promise<R extends true ? undefined : SignOutResponse> {
|
||||
const { callbackUrl = window.location.href } = options ?? {}
|
||||
const baseUrl = apiBaseUrl(__NEXTAUTH)
|
||||
const fetchOptions = {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
// @ts-expect-error: Internal
|
||||
body: new URLSearchParams({
|
||||
csrfToken: await getCsrfToken(),
|
||||
callbackUrl,
|
||||
json: true
|
||||
})
|
||||
}
|
||||
const res = await fetch(`${baseUrl}/signout`, fetchOptions)
|
||||
const data = await res.json()
|
||||
|
||||
broadcast.post({ event: 'session', data: { trigger: 'signout' } })
|
||||
|
||||
if (options?.redirect ?? true) {
|
||||
const url = data.url ?? callbackUrl
|
||||
window.location.href = url
|
||||
// If url contains a hash, the browser does not reload the page. We reload manually
|
||||
if (url.includes('#')) { window.location.reload() }
|
||||
// @ts-expect-error: Internal
|
||||
return
|
||||
}
|
||||
|
||||
await __NEXTAUTH._getSession({ event: 'storage' })
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
export function SessionProviderPlugin (options: SessionProviderProps): Plugin {
|
||||
return {
|
||||
install (app) {
|
||||
const { basePath } = options
|
||||
|
||||
if (basePath) { __NEXTAUTH.basePath = basePath }
|
||||
|
||||
/**
|
||||
* If session was `null`, there was an attempt to fetch it,
|
||||
* but it failed, but we still treat it as a valid initial value.
|
||||
*/
|
||||
const hasInitialSession = options.session !== undefined
|
||||
|
||||
/** If session was passed, initialize as already synced */
|
||||
__NEXTAUTH._lastSync = hasInitialSession ? now() : 0
|
||||
|
||||
if (hasInitialSession) { __NEXTAUTH._session = options.session }
|
||||
|
||||
const session = ref(options.session)
|
||||
|
||||
/** If session was passed, initialize as not loading */
|
||||
const loading = ref(!hasInitialSession)
|
||||
|
||||
__NEXTAUTH._getSession = async ({ event } = {}) => {
|
||||
try {
|
||||
const storageEvent = event === 'storage'
|
||||
// We should always update if we don't have a client session yet
|
||||
// or if there are events from other tabs/windows
|
||||
if (storageEvent || __NEXTAUTH._session === undefined) {
|
||||
__NEXTAUTH._lastSync = now()
|
||||
__NEXTAUTH._session = await getSession({
|
||||
broadcast: !storageEvent
|
||||
})
|
||||
session.value = __NEXTAUTH._session
|
||||
return
|
||||
}
|
||||
|
||||
if (
|
||||
// If there is no time defined for when a session should be considered
|
||||
// stale, then it's okay to use the value we have until an event is
|
||||
// triggered which updates it
|
||||
!event ||
|
||||
// If the client doesn't have a session then we don't need to call
|
||||
// the server to check if it does (if they have signed in via another
|
||||
// tab or window that will come through as a "stroage" event
|
||||
// event anyway)
|
||||
__NEXTAUTH._session === null ||
|
||||
// Bail out early if the client session is not stale yet
|
||||
now() < __NEXTAUTH._lastSync
|
||||
) { return }
|
||||
|
||||
// An event or session staleness occurred, update the client session.
|
||||
__NEXTAUTH._lastSync = now()
|
||||
__NEXTAUTH._session = await getSession()
|
||||
session.value = __NEXTAUTH._session
|
||||
} catch (error) {
|
||||
logger.error('CLIENT_SESSION_ERROR', error as Error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
__NEXTAUTH._getSession()
|
||||
|
||||
const { refetchOnWindowFocus = true } = options
|
||||
|
||||
// Listen for when the page is visible, if the user switches tabs
|
||||
// and makes our tab visible again, re-fetch the session, but only if
|
||||
// this feature is not disabled.
|
||||
const visibilityHandler = () => {
|
||||
if (refetchOnWindowFocus && document.visibilityState === 'visible') { __NEXTAUTH._getSession({ event: 'visibilitychange' }) }
|
||||
}
|
||||
|
||||
document.addEventListener('visibilitychange', visibilityHandler, false)
|
||||
|
||||
const unsubscribeFromBroadcast = broadcast.receive(() =>
|
||||
__NEXTAUTH._getSession({ event: 'storage' })
|
||||
)
|
||||
|
||||
const { refetchInterval } = options
|
||||
let refetchIntervalTimer: NodeJS.Timer
|
||||
|
||||
if (refetchInterval) {
|
||||
refetchIntervalTimer = setInterval(() => {
|
||||
if (__NEXTAUTH._session) { __NEXTAUTH._getSession({ event: 'poll' }) }
|
||||
}, refetchInterval * 1000)
|
||||
}
|
||||
|
||||
const originalUnmount = app.unmount
|
||||
app.unmount = function nextAuthUnmount () {
|
||||
document.removeEventListener('visibilitychange', visibilityHandler, false)
|
||||
unsubscribeFromBroadcast?.()
|
||||
clearInterval(refetchIntervalTimer)
|
||||
__NEXTAUTH._lastSync = 0
|
||||
__NEXTAUTH._session = undefined
|
||||
__NEXTAUTH._getSession = () => {}
|
||||
originalUnmount()
|
||||
}
|
||||
|
||||
const status = computed(() => loading.value ? 'loading' : session.value ? 'authenticated' : 'unauthenticated')
|
||||
const value = reactive({
|
||||
data: session,
|
||||
status
|
||||
})
|
||||
|
||||
app.provide('SessionKey', value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Vue Composable that gives you access
|
||||
* to the logged in user's session data.
|
||||
*
|
||||
* [Documentation](https://next-auth.js.org/getting-started/client#usesession)
|
||||
*/
|
||||
export function useSession (): {
|
||||
data: Ref<SessionProviderProps['session']>;
|
||||
status: Ref<string>;
|
||||
} {
|
||||
if (typeof window === 'undefined') {
|
||||
return {
|
||||
data: ref(null),
|
||||
status: ref('loading')
|
||||
}
|
||||
}
|
||||
|
||||
const value = inject<{
|
||||
data: SessionProviderProps['session']
|
||||
status: string
|
||||
}>('SessionKey')
|
||||
if (!value) {
|
||||
throw new Error('Could not resolve provided session value')
|
||||
}
|
||||
const { data, status } = toRefs(value)
|
||||
|
||||
return {
|
||||
data,
|
||||
status
|
||||
}
|
||||
}
|
||||
115
apps/playground-nuxt/src/runtime/lib/errors.ts
Normal file
115
apps/playground-nuxt/src/runtime/lib/errors.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import type { Adapter } from 'next-auth/adapters'
|
||||
import type { EventCallbacks, LoggerInstance } from 'next-auth'
|
||||
|
||||
/**
|
||||
* Same as the default `Error`, but it is JSON serializable.
|
||||
* @source https://iaincollins.medium.com/error-handling-in-javascript-a6172ccdf9af
|
||||
*/
|
||||
export class UnknownError extends Error {
|
||||
code: string
|
||||
constructor (error: Error | string) {
|
||||
super((error as Error)?.message ?? error)
|
||||
this.name = 'UnknownError'
|
||||
this.code = (error as any).code
|
||||
if (error instanceof Error) { this.stack = error.stack }
|
||||
}
|
||||
|
||||
toJSON () {
|
||||
return {
|
||||
name: this.name,
|
||||
message: this.message,
|
||||
stack: this.stack
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class OAuthCallbackError extends UnknownError {
|
||||
name = 'OAuthCallbackError'
|
||||
}
|
||||
|
||||
/**
|
||||
* Thrown when an Email address is already associated with an account
|
||||
* but the user is trying an OAuth account that is not linked to it.
|
||||
*/
|
||||
export class AccountNotLinkedError extends UnknownError {
|
||||
name = 'AccountNotLinkedError'
|
||||
}
|
||||
|
||||
export class MissingAPIRoute extends UnknownError {
|
||||
name = 'MissingAPIRouteError'
|
||||
code = 'MISSING_NEXTAUTH_API_ROUTE_ERROR'
|
||||
}
|
||||
|
||||
export class MissingSecret extends UnknownError {
|
||||
name = 'MissingSecretError'
|
||||
code = 'NO_SECRET'
|
||||
}
|
||||
|
||||
export class MissingAuthorize extends UnknownError {
|
||||
name = 'MissingAuthorizeError'
|
||||
code = 'CALLBACK_CREDENTIALS_HANDLER_ERROR'
|
||||
}
|
||||
|
||||
export class MissingAdapter extends UnknownError {
|
||||
name = 'MissingAdapterError'
|
||||
code = 'EMAIL_REQUIRES_ADAPTER_ERROR'
|
||||
}
|
||||
|
||||
export class UnsupportedStrategy extends UnknownError {
|
||||
name = 'UnsupportedStrategyError'
|
||||
code = 'CALLBACK_CREDENTIALS_JWT_ERROR'
|
||||
}
|
||||
|
||||
type Method = (...args: any[]) => Promise<any>
|
||||
|
||||
export function upperSnake (s: string) {
|
||||
return s.replace(/([A-Z])/g, '_$1').toUpperCase()
|
||||
}
|
||||
|
||||
export function capitalize (s: string) {
|
||||
return `${s[0].toUpperCase()}${s.slice(1)}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps an object of methods and adds error handling.
|
||||
*/
|
||||
export function eventsErrorHandler (
|
||||
methods: Partial<EventCallbacks>,
|
||||
logger: LoggerInstance
|
||||
): Partial<EventCallbacks> {
|
||||
return Object.keys(methods).reduce<any>((acc, name) => {
|
||||
acc[name] = async (...args: any[]) => {
|
||||
try {
|
||||
const method: Method = methods[name as keyof Method]
|
||||
return await method(...args)
|
||||
} catch (e) {
|
||||
logger.error(`${upperSnake(name)}_EVENT_ERROR`, e as Error)
|
||||
}
|
||||
}
|
||||
return acc
|
||||
}, {})
|
||||
}
|
||||
|
||||
/** Handles adapter induced errors. */
|
||||
export function adapterErrorHandler (
|
||||
adapter: Adapter | undefined,
|
||||
logger: LoggerInstance
|
||||
): Adapter | undefined {
|
||||
if (!adapter) { return }
|
||||
|
||||
return Object.keys(adapter).reduce<any>((acc, name) => {
|
||||
acc[name] = async (...args: any[]) => {
|
||||
try {
|
||||
logger.debug(`adapter_${name}`, { args })
|
||||
const method: Method = adapter[name as keyof Method]
|
||||
return await method(...args)
|
||||
} catch (error) {
|
||||
logger.error(`adapter_error_${name}`, error as Error)
|
||||
const e = new UnknownError(error as Error)
|
||||
e.name = `${capitalize(name)}Error`
|
||||
throw e
|
||||
}
|
||||
}
|
||||
return acc
|
||||
}, {})
|
||||
}
|
||||
113
apps/playground-nuxt/src/runtime/lib/logger.ts
Normal file
113
apps/playground-nuxt/src/runtime/lib/logger.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
import { UnknownError } from './errors'
|
||||
|
||||
// TODO: better typing
|
||||
/** Makes sure that error is always serializable */
|
||||
function formatError (o: unknown): unknown {
|
||||
if (o instanceof Error && !(o instanceof UnknownError)) { return { message: o.message, stack: o.stack, name: o.name } }
|
||||
|
||||
if (hasErrorProperty(o)) {
|
||||
o.error = formatError(o.error) as Error
|
||||
o.message = o.message ?? o.error.message
|
||||
}
|
||||
return o
|
||||
}
|
||||
|
||||
function hasErrorProperty (
|
||||
x: unknown
|
||||
): x is { error: Error; [key: string]: unknown } {
|
||||
return !!(x as any)?.error
|
||||
}
|
||||
|
||||
export type WarningCode =
|
||||
| 'NEXTAUTH_URL'
|
||||
| 'NO_SECRET'
|
||||
| 'TWITTER_OAUTH_2_BETA'
|
||||
| 'DEBUG_ENABLED'
|
||||
|
||||
/**
|
||||
* Override any of the methods, and the rest will use the default logger.
|
||||
*
|
||||
* [Documentation](https://next-auth.js.org/configuration/options#logger)
|
||||
*/
|
||||
export interface LoggerInstance extends Record<string, Function> {
|
||||
warn: (code: WarningCode) => void
|
||||
error: (
|
||||
code: string,
|
||||
/**
|
||||
* Either an instance of (JSON serializable) Error
|
||||
* or an object that contains some debug information.
|
||||
* (Error is still available through `metadata.error`)
|
||||
*/
|
||||
metadata: Error | { error: Error; [key: string]: unknown }
|
||||
) => void
|
||||
debug: (code: string, metadata: unknown) => void
|
||||
}
|
||||
|
||||
const _logger: LoggerInstance = {
|
||||
error (code, metadata) {
|
||||
metadata = formatError(metadata) as Error
|
||||
console.error(
|
||||
`[next-auth][error][${code}]`,
|
||||
`\nhttps://next-auth.js.org/errors#${code.toLowerCase()}`,
|
||||
metadata.message,
|
||||
metadata
|
||||
)
|
||||
},
|
||||
warn (code) {
|
||||
console.warn(
|
||||
`[next-auth][warn][${code}]`,
|
||||
`\nhttps://next-auth.js.org/warnings#${code.toLowerCase()}`
|
||||
)
|
||||
},
|
||||
debug (code, metadata) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`[next-auth][debug][${code}]`, metadata)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the built-in logger with user's implementation.
|
||||
* Any `undefined` level will use the default logger.
|
||||
*/
|
||||
export function setLogger (
|
||||
newLogger: Partial<LoggerInstance> = {},
|
||||
debug?: boolean
|
||||
) {
|
||||
// Turn off debug logging if `debug` isn't set to `true`
|
||||
if (!debug) { _logger.debug = () => {} }
|
||||
|
||||
if (newLogger.error) { _logger.error = newLogger.error }
|
||||
if (newLogger.warn) { _logger.warn = newLogger.warn }
|
||||
if (newLogger.debug) { _logger.debug = newLogger.debug }
|
||||
}
|
||||
|
||||
export default _logger
|
||||
|
||||
/** Serializes client-side log messages and sends them to the server */
|
||||
export function proxyLogger (
|
||||
logger: LoggerInstance = _logger,
|
||||
basePath?: string
|
||||
): LoggerInstance {
|
||||
try {
|
||||
if (typeof window === 'undefined') { return logger }
|
||||
|
||||
const clientLogger: Record<string, unknown> = {}
|
||||
for (const level in logger) {
|
||||
clientLogger[level] = (code: string, metadata: Error) => {
|
||||
_logger[level](code, metadata) // Logs to console
|
||||
|
||||
if (level === 'error') {
|
||||
metadata = formatError(metadata) as Error
|
||||
}(metadata as any).client = true
|
||||
const url = `${basePath}/_log`
|
||||
const body = new URLSearchParams({ level, code, ...(metadata as any) })
|
||||
if (navigator.sendBeacon) { return navigator.sendBeacon(url, body) }
|
||||
|
||||
return fetch(url, { method: 'POST', body, keepalive: true })
|
||||
}
|
||||
}
|
||||
return clientLogger as unknown as LoggerInstance
|
||||
} catch {
|
||||
return _logger
|
||||
}
|
||||
}
|
||||
34
apps/playground-nuxt/src/runtime/lib/parse-url.ts
Normal file
34
apps/playground-nuxt/src/runtime/lib/parse-url.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
export interface InternalUrl {
|
||||
/** @default "http://localhost:3000" */
|
||||
origin: string
|
||||
/** @default "localhost:3000" */
|
||||
host: string
|
||||
/** @default "/api/auth" */
|
||||
path: string
|
||||
/** @default "http://localhost:3000/api/auth" */
|
||||
base: string
|
||||
/** @default "http://localhost:3000/api/auth" */
|
||||
toString: () => string
|
||||
}
|
||||
|
||||
/** Returns an `URL` like object to make requests/redirects from server-side */
|
||||
export default function parseUrl (url?: string): InternalUrl {
|
||||
const defaultUrl = new URL('http://localhost:3000/api/auth')
|
||||
|
||||
if (url && !url.startsWith('http')) { url = `https://${url}` }
|
||||
|
||||
const _url = new URL(url ?? defaultUrl)
|
||||
const path = (_url.pathname === '/' ? defaultUrl.pathname : _url.pathname)
|
||||
// Remove trailing slash
|
||||
.replace(/\/$/, '')
|
||||
|
||||
const base = `${_url.origin}${path}`
|
||||
|
||||
return {
|
||||
origin: _url.origin,
|
||||
host: _url.host,
|
||||
path,
|
||||
base,
|
||||
toString: () => base
|
||||
}
|
||||
}
|
||||
7
apps/playground-nuxt/src/runtime/plugin.client.ts
Normal file
7
apps/playground-nuxt/src/runtime/plugin.client.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
// @ts-expect-error: Nuxt auto-import
|
||||
import { defineNuxtPlugin } from '#app'
|
||||
import { SessionProviderPlugin } from './client'
|
||||
|
||||
export default defineNuxtPlugin((nuxtApp) => {
|
||||
nuxtApp.vueApp.use(SessionProviderPlugin({}))
|
||||
})
|
||||
93
apps/playground-nuxt/src/runtime/server/handler.ts
Normal file
93
apps/playground-nuxt/src/runtime/server/handler.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import type { NextAuthAction, NextAuthOptions, Session } from 'next-auth'
|
||||
import type { RequestInternal } from 'next-auth/core'
|
||||
import { NextAuthHandler } from 'next-auth/core'
|
||||
import {
|
||||
appendHeader,
|
||||
defineEventHandler,
|
||||
isMethod,
|
||||
sendRedirect,
|
||||
setCookie,
|
||||
readBody,
|
||||
parseCookies,
|
||||
getQuery
|
||||
} from 'h3'
|
||||
import type { H3Event } from 'h3'
|
||||
|
||||
export function NextAuthNuxtHandler (options: NextAuthOptions) {
|
||||
return defineEventHandler(async (event) => {
|
||||
// Catch-all route params in Nuxt goes to the underscore property
|
||||
const nextauth = event.context.params._.split('/')
|
||||
|
||||
const req: RequestInternal | Request = {
|
||||
host: process.env.NEXTAUTH_URL,
|
||||
body: undefined,
|
||||
query: getQuery(event),
|
||||
headers: event.req.headers,
|
||||
method: event.req.method,
|
||||
cookies: parseCookies(event),
|
||||
action: nextauth[0] as NextAuthAction,
|
||||
providerId: nextauth[1],
|
||||
error: nextauth[1]
|
||||
}
|
||||
|
||||
if (isMethod(event, 'POST')) {
|
||||
req.body = await readBody(event)
|
||||
}
|
||||
|
||||
const response = await NextAuthHandler({
|
||||
req,
|
||||
options
|
||||
})
|
||||
|
||||
const { headers, cookies, body, redirect, status = 200 } = response
|
||||
event.res.statusCode = status
|
||||
|
||||
headers?.forEach((header) => {
|
||||
appendHeader(event, header.key, header.value)
|
||||
})
|
||||
|
||||
cookies?.forEach((cookie) => {
|
||||
setCookie(event, cookie.name, cookie.value, cookie.options)
|
||||
})
|
||||
|
||||
if (redirect) {
|
||||
if (isMethod(event, 'POST')) {
|
||||
const body = await readBody(event)
|
||||
if (body?.json !== 'true') { await sendRedirect(event, redirect, 302) }
|
||||
|
||||
return {
|
||||
url: redirect
|
||||
}
|
||||
} else {
|
||||
await sendRedirect(event, redirect, 302)
|
||||
}
|
||||
}
|
||||
|
||||
return body
|
||||
})
|
||||
}
|
||||
|
||||
export async function getServerSession (
|
||||
event: H3Event,
|
||||
options: NextAuthOptions
|
||||
): Promise<Session | null> {
|
||||
options.secret = process.env.NEXTAUTH_SECRET
|
||||
|
||||
const session = await NextAuthHandler<Session>({
|
||||
req: {
|
||||
host: process.env.NEXTAUTH_URL,
|
||||
action: 'session',
|
||||
method: 'GET',
|
||||
cookies: parseCookies(event),
|
||||
headers: event.req.headers
|
||||
},
|
||||
options
|
||||
})
|
||||
|
||||
const { body } = session
|
||||
|
||||
if (body && Object.keys(body).length) {
|
||||
return body
|
||||
}
|
||||
return null
|
||||
}
|
||||
78
apps/playground-nuxt/src/runtime/types.ts
Normal file
78
apps/playground-nuxt/src/runtime/types.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import type { Session } from 'next-auth'
|
||||
import type { BuiltInProviderType, ProviderType } from 'next-auth/providers'
|
||||
|
||||
export interface UseSessionOptions<R extends boolean> {
|
||||
required: R
|
||||
/** Defaults to `signIn` */
|
||||
onUnauthenticated?: () => void
|
||||
}
|
||||
|
||||
/**
|
||||
* Util type that matches some strings literally, but allows any other string as well.
|
||||
* @source https://github.com/microsoft/TypeScript/issues/29729#issuecomment-832522611
|
||||
*/
|
||||
export type LiteralUnion<T extends U, U = string> =
|
||||
| T
|
||||
| (U & Record<never, never>)
|
||||
|
||||
export interface ClientSafeProvider {
|
||||
id: LiteralUnion<BuiltInProviderType>
|
||||
name: string
|
||||
type: ProviderType
|
||||
signinUrl: string
|
||||
callbackUrl: string
|
||||
}
|
||||
|
||||
export interface SignInOptions extends Record<string, unknown> {
|
||||
/**
|
||||
* Defaults to the current URL.
|
||||
* @docs https://next-auth.js.org/getting-started/client#specifying-a-callbackurl
|
||||
*/
|
||||
callbackUrl?: string
|
||||
/** @docs https://next-auth.js.org/getting-started/client#using-the-redirect-false-option */
|
||||
redirect?: boolean
|
||||
}
|
||||
|
||||
export interface SignInResponse {
|
||||
error: string | undefined
|
||||
status: number
|
||||
ok: boolean
|
||||
url: string | null
|
||||
}
|
||||
|
||||
/** Match `inputType` of `new URLSearchParams(inputType)` */
|
||||
export type SignInAuthorizationParams =
|
||||
| string
|
||||
| string[][]
|
||||
| Record<string, string>
|
||||
| URLSearchParams
|
||||
|
||||
/** @docs https://next-auth.js.org/getting-started/client#using-the-redirect-false-option-1 */
|
||||
export interface SignOutResponse {
|
||||
url: string
|
||||
}
|
||||
|
||||
export interface SignOutParams<R extends boolean = true> {
|
||||
/** @docs https://next-auth.js.org/getting-started/client#specifying-a-callbackurl-1 */
|
||||
callbackUrl?: string
|
||||
/** @docs https://next-auth.js.org/getting-started/client#using-the-redirect-false-option-1 */
|
||||
redirect?: R
|
||||
}
|
||||
|
||||
/** @docs: https://next-auth.js.org/getting-started/client#options */
|
||||
export interface SessionProviderProps {
|
||||
// children: React.ReactNode
|
||||
session?: Session | null
|
||||
baseUrl?: string
|
||||
basePath?: string
|
||||
/**
|
||||
* A time interval (in seconds) after which the session will be re-fetched.
|
||||
* If set to `0` (default), the session is not polled.
|
||||
*/
|
||||
refetchInterval?: number
|
||||
/**
|
||||
* `SessionProvider` automatically refetches the session when the user switches between windows.
|
||||
* This option activates this behaviour if set to `true` (default).
|
||||
*/
|
||||
refetchOnWindowFocus?: boolean
|
||||
}
|
||||
4
apps/playground-nuxt/tsconfig.json
Normal file
4
apps/playground-nuxt/tsconfig.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
// https://v3.nuxtjs.org/concepts/typescript
|
||||
"extends": "./playground/.nuxt/tsconfig.json"
|
||||
}
|
||||
@@ -11,6 +11,7 @@ When using a database, you can still use JWT for session handling for fast acces
|
||||
|
||||
We have a list of official adapters that are distributed as their own packages under the `@next-auth/{name}-adapter` namespace. Their source code is available in their various adapters package directories at [`nextauthjs/next-auth`](https://github.com/nextauthjs/next-auth/tree/main/packages).
|
||||
|
||||
- [`xata`](./xata)
|
||||
- [`prisma`](./prisma)
|
||||
- [`fauna`](./fauna)
|
||||
- [`dynamodb`](./dynamodb)
|
||||
|
||||
242
docs/docs/adapters/xata.md
Normal file
242
docs/docs/adapters/xata.md
Normal file
@@ -0,0 +1,242 @@
|
||||
---
|
||||
id: xata
|
||||
title: Xata
|
||||
---
|
||||
|
||||
# Xata
|
||||
|
||||
This adapter allows using next-auth with Xata as a database to store users, sessions, and more. The preferred way to create a Xata project and use Xata databases is using the [Xata Command Line Interface (CLI)](https://docs.xata.io/cli/getting-started). The CLI allows generating a `XataClient` that will help you work with Xata in a safe way, and that this adapter depends on.
|
||||
|
||||
<!-- @todo add GIFs -->
|
||||
|
||||
## Getting Started
|
||||
|
||||
Let's first make sure we have everything installed and configured. We're going to need:
|
||||
|
||||
- next-auth + adapter
|
||||
- the Xata CLI
|
||||
- to configure the CLI
|
||||
|
||||
We can do this like so:
|
||||
|
||||
```bash npm2yarn2pnpm
|
||||
# Install next-auth + adapter
|
||||
npm install next-auth @next-auth/xata-adapter
|
||||
|
||||
# Install the Xata CLI globally if you don't already have it
|
||||
npm install --location=global @xata.io/cli
|
||||
|
||||
# Login
|
||||
xata auth login
|
||||
```
|
||||
|
||||
Now that we're ready, let's create a new Xata project using our next-auth schema that the Xata adapter can work with. To do that, copy and paste this schema file into your project's directory:
|
||||
|
||||
```json title="schema.json"
|
||||
{
|
||||
"formatVersion": "",
|
||||
"tables": [
|
||||
{
|
||||
"name": "nextauth_users",
|
||||
"columns": [
|
||||
{
|
||||
"name": "email",
|
||||
"type": "email"
|
||||
},
|
||||
{
|
||||
"name": "emailVerified",
|
||||
"type": "datetime"
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "image",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "nextauth_accounts",
|
||||
"columns": [
|
||||
{
|
||||
"name": "user",
|
||||
"type": "link",
|
||||
"link": {
|
||||
"table": "nextauth_users"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "type",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "provider",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "providerAccountId",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "refresh_token",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "access_token",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "expires_at",
|
||||
"type": "int"
|
||||
},
|
||||
{
|
||||
"name": "token_type",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "scope",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "id_token",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"name": "session_state",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "nextauth_verificationTokens",
|
||||
"columns": [
|
||||
{
|
||||
"name": "identifier",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "token",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "expires",
|
||||
"type": "datetime"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "nextauth_users_accounts",
|
||||
"columns": [
|
||||
{
|
||||
"name": "user",
|
||||
"type": "link",
|
||||
"link": {
|
||||
"table": "nextauth_users"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "account",
|
||||
"type": "link",
|
||||
"link": {
|
||||
"table": "nextauth_accounts"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "nextauth_users_sessions",
|
||||
"columns": [
|
||||
{
|
||||
"name": "user",
|
||||
"type": "link",
|
||||
"link": {
|
||||
"table": "nextauth_users"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "session",
|
||||
"type": "link",
|
||||
"link": {
|
||||
"table": "nextauth_sessions"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "nextauth_sessions",
|
||||
"columns": [
|
||||
{
|
||||
"name": "sessionToken",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "expires",
|
||||
"type": "datetime"
|
||||
},
|
||||
{
|
||||
"name": "user",
|
||||
"type": "link",
|
||||
"link": {
|
||||
"table": "nextauth_users"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Now, run the following command:
|
||||
|
||||
```bash
|
||||
xata init --schema=./path/to/your/schema.json
|
||||
```
|
||||
|
||||
The CLI will walk you through a setup process where you choose a [workspace](https://docs.xata.io/concepts/workspaces) (kind of like a GitHub org or a Vercel team) and an appropriate database. We recommend using a fresh database for this, as we'll augment it with tables that next-auth needs.
|
||||
|
||||
Once you're done, you can continue using next-auth in your project as expected, like creating a `./pages/api/auth/[...nextauth]` route.
|
||||
|
||||
```typescript title="pages/api/auth/[...nextauth].ts"
|
||||
import NextAuth from "next-auth"
|
||||
import GoogleProvider from "next-auth/providers/google"
|
||||
|
||||
const client = new XataClient()
|
||||
|
||||
export default NextAuth({
|
||||
providers: [
|
||||
GoogleProvider({
|
||||
clientId: process.env.GOOGLE_CLIENT_ID,
|
||||
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
|
||||
}),
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
Now to Xata-fy this route, let's add the Xata client and adapter:
|
||||
|
||||
```diff
|
||||
import NextAuth from "next-auth"
|
||||
import GoogleProvider from "next-auth/providers/google"
|
||||
+import { XataAdapter } from "@next-auth/xata-adapter"
|
||||
+import { XataClient } from "../../../xata" // or wherever you've chosen to create the client
|
||||
|
||||
+const client = new XataClient()
|
||||
|
||||
export default NextAuth({
|
||||
+ adapter: XataAdapter(client),
|
||||
providers: [
|
||||
GoogleProvider({
|
||||
clientId: process.env.GOOGLE_CLIENT_ID,
|
||||
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
|
||||
}),
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
This fully sets up your next-auth site to work with Xata.
|
||||
|
||||
## Contributing
|
||||
|
||||
This is an open-source project created by humans, and as such, might have a few issues. If you experience any of these, we recommend [opening issues](https://github.com/nextauthjs/next-auth/issues/new?assignees=&labels=triage&template=1_bug_framework.yml&title=Issue%20on%20Xata%20adapter&description=I%20experienced%20this%20issue:\n##%20Reproduction%20Steps:\n\n-) that can help us solve problems and build reliable software.
|
||||
@@ -24,7 +24,7 @@ export const authOptions: NextAuthOptions = {
|
||||
export default NextAuth(authOptions);
|
||||
```
|
||||
|
||||
In `getServerSideProps`:
|
||||
### In `getServerSideProps`:
|
||||
```js
|
||||
import { authOptions } from 'pages/api/auth/[...nextauth]'
|
||||
import { unstable_getServerSession } from "next-auth/next"
|
||||
@@ -48,7 +48,8 @@ export async function getServerSideProps(context) {
|
||||
}
|
||||
}
|
||||
```
|
||||
In API routes:
|
||||
|
||||
### In API Routes:
|
||||
```js
|
||||
import { authOptions } from 'pages/api/auth/[...nextauth]'
|
||||
import { unstable_getServerSession } from "next-auth/next"
|
||||
@@ -68,6 +69,23 @@ export async function handler(req, res) {
|
||||
}
|
||||
```
|
||||
|
||||
### In `app/` directory:
|
||||
|
||||
You can also use `unstable_getServerSession` in Next.js' server components:
|
||||
|
||||
```tsx
|
||||
import { unstable_getServerSession } from "next-auth/next"
|
||||
|
||||
export default async function Page() {
|
||||
const session = await unstable_getServerSession()
|
||||
return <pre>{JSON.stringify(session, null, 2)}</pre>
|
||||
}
|
||||
```
|
||||
|
||||
:::warning
|
||||
Currently, the underlying Next.js `cookies()` method does [only provides read access](https://beta.nextjs.org/docs/api-reference/cookies) to the request cookies. This means that the `expires` value is stripped away from `session` in Server Components. Furthermore, there is a hard expiry on sessions, after which the user will be required to sign in again. (The default expiry is 30 days).
|
||||
:::
|
||||
|
||||
## Middleware
|
||||
|
||||
You can use a Next.js Middleware with NextAuth.js to protect your site.
|
||||
|
||||
@@ -83,7 +83,7 @@ TWITTER_SECRET=YOUR_TWITTER_CLIENT_SECRET
|
||||
4. Now you can add the provider settings to the NextAuth.js options object. You can add as many OAuth providers as you like, as you can see `providers` is an array.
|
||||
|
||||
```js title="pages/api/auth/[...nextauth].js"
|
||||
import TwitterProvider from "next-auth/providers/"
|
||||
import TwitterProvider from "next-auth/providers/twitter"
|
||||
...
|
||||
providers: [
|
||||
TwitterProvider({
|
||||
@@ -281,7 +281,7 @@ An advanced option, hopefully you won't need it in most cases. `next-auth` uses
|
||||
|
||||
### `allowDangerousEmailAccountLinking` option
|
||||
|
||||
Normally, when you sign in with an OAuth provider and another account with the same email address already exists, the accounts are not linked automatically. Automatic account linking on sign in is not secure between arbitrary providers and is disabled by default (see our [Security FAQ](https://next-auth.js.org/faq#security)). However, it may be desirable to allow automatic account linking if you trust that the provider involved has securely verified the email address associated with the account. Just set `allowDangerousEmailAccountLinking: true` in your provider configuration to enable automatic account linking.
|
||||
Normally, when you sign in with an OAuth provider and another account with the same email address already exists, the accounts are not linked automatically. Automatic account linking on sign in is not secure between arbitrary providers and is disabled by default (see our [Security FAQ](https://next-auth.js.org/faq#security)). However, it may be desirable to allow automatic account linking if you trust that the provider involved has securely verified the email address associated with the account. Just set `allowDangerousEmailAccountLinking: true` in your provider configuration to enable automatic account linking.
|
||||
|
||||
## Using a custom provider
|
||||
|
||||
@@ -425,10 +425,11 @@ GoogleProvider({
|
||||
|
||||
If you think your custom provider might be useful to others, we encourage you to open a PR and add it to the built-in list so others can discover it much more easily!
|
||||
|
||||
You only need to add two changes:
|
||||
You only need to add three changes:
|
||||
|
||||
1. Add your config: [`src/providers/{provider}.ts`](https://github.com/nextauthjs/next-auth/tree/main/packages/next-auth/src/providers)<br />
|
||||
• make sure you use a named default export, like this: `export default function YourProvider`
|
||||
2. Add provider documentation: [`/docs/providers/{provider}.md`](https://github.com/nextauthjs/next-auth/tree/main/docs/docs/providers)
|
||||
3. Add the new provider name to the `Provider type` dropdown options in [`the provider issue template`](<[http](https://github.com/nextauthjs/next-auth/edit/main/.github/ISSUE_TEMPLATE/2_bug_provider.yml)>)
|
||||
|
||||
That's it! 🎉 Others will be able to discover and use this provider much more easily now!
|
||||
|
||||
@@ -44,8 +44,8 @@ providers: [
|
||||
CredentialsProvider({
|
||||
// The name to display on the sign in form (e.g. "Sign in with...")
|
||||
name: "Credentials",
|
||||
// The credentials is used to generate a suitable form on the sign in page.
|
||||
// You can specify whatever fields you are expecting to be submitted.
|
||||
// `credentials` is used to generate a form on the sign in page.
|
||||
// You can specify which fields should be submitted, by adding keys to the `credentials` object.
|
||||
// e.g. domain, username, password, 2FA token, etc.
|
||||
// You can pass any HTML attribute to the <input> tag through the object.
|
||||
credentials: {
|
||||
@@ -54,7 +54,7 @@ providers: [
|
||||
},
|
||||
async authorize(credentials, req) {
|
||||
// Add logic here to look up the user from the credentials supplied
|
||||
const user = { id: 1, name: "J Smith", email: "jsmith@example.com" }
|
||||
const user = { id: "1", name: "J Smith", email: "jsmith@example.com" }
|
||||
|
||||
if (user) {
|
||||
// Any object returned will be saved in `user` property of the JWT
|
||||
|
||||
@@ -3,7 +3,7 @@ id: corporate-proxy
|
||||
title: Add support for HTTP Proxy
|
||||
--
|
||||
|
||||
Using NextAuth.js behind a corporate proxy is not supported out of the box. This is due to the fact that the underlying library we use, [`openid-client`](https://npm.im/openid-client), uses the built-in Node.js `http` / `https` libraries, which do not support proxys by default. (See: [`http` docs](https://nodejs.org/dist/latest-v16.x/docs/api/http.html), [`https` docs](https://nodejs.org/dist/latest-v16.x/docs/api/https.html)).
|
||||
Using NextAuth.js behind a corporate proxy is not supported out of the box. This is due to the fact that the underlying library we use, [`openid-client`](https://npm.im/openid-client), uses the built-in Node.js `http` / `https` libraries, which do not support proxys by default. (See: [`http` docs](https://nodejs.org/dist/latest-v18.x/docs/api/http.html), [`https` docs](https://nodejs.org/dist/latest-v18.x/docs/api/https.html)).
|
||||
|
||||
Therefore, we'll need to an additional proxy agent to the http client, such as `https-proxy-agent`. `openid-client` allows the user to set an `agent` for requests ([Source](https://github.com/panva/node-openid-client/blob/main/docs/README.md#customizing-individual-http-requests).
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# Corporate proxy
|
||||
|
||||
Using NextAuth.js behind a corporate proxy is not supported out of the box. This is due to the fact that the underlying library we use, [`openid-client`](https://npm.im/openid-client) which uses the built-in Node.js `http` / `https` libraries, and those do not support proxys by default:
|
||||
- [`http` docs](https://nodejs.org/dist/latest-v16.x/docs/api/http.html)
|
||||
- [`https` docs](https://nodejs.org/dist/latest-v16.x/docs/api/https.html)
|
||||
- [`http` docs](https://nodejs.org/dist/latest-v18.x/docs/api/http.html)
|
||||
- [`https` docs](https://nodejs.org/dist/latest-v18.x/docs/api/https.html)
|
||||
|
||||
Therefore, we'll need to add an additional proxy agent to the http client, such as `https-proxy-agent`.
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ export const authOptions: NextAuthOptions = {
|
||||
export default NextAuth(authOptions);
|
||||
```
|
||||
|
||||
In `getServerSideProps`:
|
||||
### In `getServerSideProps`:
|
||||
```js
|
||||
import { authOptions } from 'pages/api/[...nextauth]'
|
||||
import { unstable_getServerSession } from "next-auth/next"
|
||||
@@ -48,7 +48,8 @@ export async function getServerSideProps(context) {
|
||||
}
|
||||
}
|
||||
```
|
||||
In API routes:
|
||||
|
||||
### In API routes:
|
||||
```js
|
||||
import { authOptions } from 'pages/api/[...nextauth]'
|
||||
import { unstable_getServerSession } from "next-auth/next"
|
||||
@@ -68,6 +69,23 @@ export async function handler(req, res) {
|
||||
}
|
||||
```
|
||||
|
||||
### In `app/` directory:
|
||||
|
||||
You can also use `unstable_getServerSession` in Next.js' server components:
|
||||
|
||||
```tsx
|
||||
import { unstable_getServerSession } from "next-auth/next"
|
||||
|
||||
export default async function Page() {
|
||||
const session = await unstable_getServerSession()
|
||||
return <pre>{JSON.stringify(session, null, 2)}</pre>
|
||||
}
|
||||
```
|
||||
|
||||
:::warning
|
||||
Currently, the underlying Next.js `cookies()` method does [only provides read access](https://beta.nextjs.org/docs/api-reference/cookies) to the request cookies. This means that the `expires` value is stripped away from `session` in Server Components. Furthermore, there is a hard expiry on sessions, after which the user will be required to sign in again. (The default expiry is 30 days).
|
||||
:::
|
||||
|
||||
## Middleware
|
||||
|
||||
You can use a Next.js Middleware with NextAuth.js to protect your site.
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
"typescript": "4.8.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.19.0 || ^14.15.0 || ^16.13.0"
|
||||
"node": "^12.19.0 || ^14.15.0 || ^16.13.0 || ^18.12.0"
|
||||
},
|
||||
"prettier": {
|
||||
"semi": false,
|
||||
|
||||
249
packages/adapter-xata/README.md
Normal file
249
packages/adapter-xata/README.md
Normal file
@@ -0,0 +1,249 @@
|
||||
<p align="center">
|
||||
<br/>
|
||||
<a href="https://next-auth.js.org" target="_blank"><img height="64px" src="https://next-auth.js.org/img/logo/logo-sm.png" /></a> <img height="64px" src="logo.svg" />
|
||||
<h3 align="center"><b>Xata Adapter</b> - NextAuth.js</h3>
|
||||
<p align="center">
|
||||
Think data, not databases.
|
||||
</p>
|
||||
<p align="center" style="align: center;">
|
||||
<img src="https://github.com/nextauthjs/next-auth/actions/workflows/release.yml/badge.svg?branch=main" alt="CI Test" />
|
||||
<a href="https://www.npmjs.com/package/@next-auth/xata-adapter" target="_blank"><img src="https://img.shields.io/bundlephobia/minzip/@next-auth/xata-adapter/next" alt="Bundle Size"/></a>
|
||||
<a href="https://www.npmjs.com/package/@next-auth/xata-adapter" target="_blank"><img src="https://img.shields.io/npm/v/@next-auth/xata-adapter/next" alt="@next-auth/xata-adapter Version" /></a>
|
||||
</p>
|
||||
</p>
|
||||
|
||||
This adapter allows using next-auth with Xata as a database to store users, sessions, and more. The preferred way to create a Xata project and use Xata databases is using the [Xata Command Line Interface (CLI)](https://docs.xata.io/cli/getting-started). The CLI allows generating a `XataClient` that will help you work with Xata in a safe way, and that this adapter depends on.
|
||||
|
||||
<!-- @todo add GIFs -->
|
||||
|
||||
## Getting Started
|
||||
|
||||
Let's first make sure we have everything installed and configured. We're going to need:
|
||||
|
||||
- next-auth + adapter
|
||||
- the Xata CLI
|
||||
- to configure the CLI
|
||||
|
||||
We can do this like so:
|
||||
|
||||
```bash npm2yarn2pnpm
|
||||
# Install next-auth + adapter
|
||||
npm install next-auth @next-auth/xata-adapter
|
||||
|
||||
# Install the Xata CLI globally if you don't already have it
|
||||
npm install --location=global @xata.io/cli
|
||||
|
||||
# Login
|
||||
xata auth login
|
||||
```
|
||||
|
||||
Now that we're ready, let's create a new Xata project using our next-auth schema that the Xata adapter can work with. To do that, copy and paste this schema file into your project's directory:
|
||||
|
||||
```json title="schema.json"
|
||||
{
|
||||
"formatVersion": "",
|
||||
"tables": [
|
||||
{
|
||||
"name": "nextauth_users",
|
||||
"columns": [
|
||||
{
|
||||
"name": "email",
|
||||
"type": "email"
|
||||
},
|
||||
{
|
||||
"name": "emailVerified",
|
||||
"type": "datetime"
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "image",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "nextauth_accounts",
|
||||
"columns": [
|
||||
{
|
||||
"name": "user",
|
||||
"type": "link",
|
||||
"link": {
|
||||
"table": "nextauth_users"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "type",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "provider",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "providerAccountId",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "refresh_token",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "access_token",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "expires_at",
|
||||
"type": "int"
|
||||
},
|
||||
{
|
||||
"name": "token_type",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "scope",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "id_token",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"name": "session_state",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "nextauth_verificationTokens",
|
||||
"columns": [
|
||||
{
|
||||
"name": "identifier",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "token",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "expires",
|
||||
"type": "datetime"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "nextauth_users_accounts",
|
||||
"columns": [
|
||||
{
|
||||
"name": "user",
|
||||
"type": "link",
|
||||
"link": {
|
||||
"table": "nextauth_users"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "account",
|
||||
"type": "link",
|
||||
"link": {
|
||||
"table": "nextauth_accounts"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "nextauth_users_sessions",
|
||||
"columns": [
|
||||
{
|
||||
"name": "user",
|
||||
"type": "link",
|
||||
"link": {
|
||||
"table": "nextauth_users"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "session",
|
||||
"type": "link",
|
||||
"link": {
|
||||
"table": "nextauth_sessions"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "nextauth_sessions",
|
||||
"columns": [
|
||||
{
|
||||
"name": "sessionToken",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "expires",
|
||||
"type": "datetime"
|
||||
},
|
||||
{
|
||||
"name": "user",
|
||||
"type": "link",
|
||||
"link": {
|
||||
"table": "nextauth_users"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Now, run the following command:
|
||||
|
||||
```bash
|
||||
xata init --schema=./path/to/your/schema.json
|
||||
```
|
||||
|
||||
The CLI will walk you through a setup process where you choose a [workspace](https://docs.xata.io/concepts/workspaces) (kind of like a GitHub org or a Vercel team) and an appropriate database. We recommend using a fresh database for this, as we'll augment it with tables that next-auth needs.
|
||||
|
||||
Once you're done, you can continue using next-auth in your project as expected, like creating a `./pages/api/auth/[...nextauth]` route.
|
||||
|
||||
```typescript title="pages/api/auth/[...nextauth].ts"
|
||||
import NextAuth from "next-auth"
|
||||
import GoogleProvider from "next-auth/providers/google"
|
||||
|
||||
const client = new XataClient()
|
||||
|
||||
export default NextAuth({
|
||||
providers: [
|
||||
GoogleProvider({
|
||||
clientId: process.env.GOOGLE_CLIENT_ID,
|
||||
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
|
||||
}),
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
Now to Xata-fy this route, let's add the Xata client and adapter:
|
||||
|
||||
```diff
|
||||
import NextAuth from "next-auth"
|
||||
import GoogleProvider from "next-auth/providers/google"
|
||||
+import { XataAdapter } from "@next-auth/xata-adapter"
|
||||
+import { XataClient } from "../../../xata" // or wherever you've chosen to create the client
|
||||
|
||||
+const client = new XataClient()
|
||||
|
||||
export default NextAuth({
|
||||
+ adapter: XataAdapter(client),
|
||||
providers: [
|
||||
GoogleProvider({
|
||||
clientId: process.env.GOOGLE_CLIENT_ID,
|
||||
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
|
||||
}),
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
This fully sets up your next-auth site to work with Xata.
|
||||
|
||||
## Contributing
|
||||
|
||||
This is an open-source project created by humans, and as such, might have a few issues. If you experience any of these, we recommend [opening issues](https://github.com/nextauthjs/next-auth/issues/new?assignees=&labels=triage&template=1_bug_framework.yml&title=Issue%20on%20Xata%20adapter&description=I%20experienced%20this%20issue:\n##%20Reproduction%20Steps:\n\n-) that can help us solve problems and build reliable software.
|
||||
6
packages/adapter-xata/logo.svg
Normal file
6
packages/adapter-xata/logo.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg width="461" height="467" viewBox="0 0 461 467" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M389.431 465.611C421.673 433.443 443.246 393.222 449.403 353.796C455.56 314.369 445.797 278.966 422.262 255.376L300.691 376.663L389.431 465.611Z" fill="#06D6A0"/>
|
||||
<path d="M71.5738 466.306C39.331 434.138 17.7582 393.917 11.6011 354.49C5.44395 315.064 15.2069 279.661 38.7422 256.07L160.314 377.358L71.5738 466.306Z" fill="#FFBB00"/>
|
||||
<path d="M0.673946 122.184C0.727229 167.729 18.8709 211.387 51.1137 243.554L51.1168 243.551L172.673 364.823C204.84 332.58 222.882 288.88 222.829 243.335C222.775 197.79 204.632 154.132 172.389 121.964L172.386 121.967L50.8296 0.695312C18.6622 32.9381 0.620662 76.6388 0.673946 122.184Z" fill="#FF0075"/>
|
||||
<path d="M460.329 121.488C460.276 167.033 442.132 210.692 409.89 242.859L409.886 242.855L288.33 364.127C256.163 331.884 238.121 288.183 238.175 242.638C238.228 197.094 256.372 153.435 288.614 121.268L288.618 121.271L410.174 0C442.341 32.2428 460.383 75.9435 460.329 121.488Z" fill="#0076FF"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
46
packages/adapter-xata/package.json
Normal file
46
packages/adapter-xata/package.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"name": "@next-auth/xata-adapter",
|
||||
"version": "0.2.0",
|
||||
"description": "Xata adapter for next-auth.",
|
||||
"homepage": "https://next-auth.js.org",
|
||||
"repository": "https://github.com/nextauthjs/next-auth",
|
||||
"bugs": {
|
||||
"url": "https://github.com/nextauthjs/next-auth/issues"
|
||||
},
|
||||
"author": "Tejas Kumar",
|
||||
"main": "dist/index.js",
|
||||
"license": "ISC",
|
||||
"keywords": [
|
||||
"next-auth",
|
||||
"next.js",
|
||||
"oauth",
|
||||
"xata"
|
||||
],
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"test": "jest"
|
||||
},
|
||||
"files": [
|
||||
"README.md",
|
||||
"dist"
|
||||
],
|
||||
"peerDependencies": {
|
||||
"@xata.io/client": ">=0.13.0",
|
||||
"next-auth": "^4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@next-auth/adapter-test": "workspace:^0.0.0",
|
||||
"@next-auth/tsconfig": "workspace:^0.0.0",
|
||||
"jest": "^27.4.3",
|
||||
"next-auth": "workspace:*",
|
||||
"@xata.io/client": "^0.13.0",
|
||||
"typescript": "^4.7.4"
|
||||
},
|
||||
"jest": {
|
||||
"preset": "@next-auth/adapter-test/jest"
|
||||
}
|
||||
}
|
||||
162
packages/adapter-xata/src/index.ts
Normal file
162
packages/adapter-xata/src/index.ts
Normal file
@@ -0,0 +1,162 @@
|
||||
import type { Adapter } from "next-auth/adapters"
|
||||
|
||||
import type { XataClient } from "./xata"
|
||||
|
||||
export function XataAdapter(client: XataClient): Adapter {
|
||||
return {
|
||||
async createUser(user) {
|
||||
const newUser = await client.db.nextauth_users.create(user)
|
||||
return newUser
|
||||
},
|
||||
async getUser(id) {
|
||||
const user = await client.db.nextauth_users.filter({ id }).getFirst()
|
||||
return user ?? null
|
||||
},
|
||||
async getUserByEmail(email) {
|
||||
const user = await client.db.nextauth_users.filter({ email }).getFirst()
|
||||
return user ?? null
|
||||
},
|
||||
async getUserByAccount({ providerAccountId, provider }) {
|
||||
const result = await client.db.nextauth_users_accounts
|
||||
.select(["user.*"])
|
||||
.filter({
|
||||
"account.providerAccountId": providerAccountId,
|
||||
"account.provider": provider,
|
||||
})
|
||||
.getFirst()
|
||||
const user = result?.user
|
||||
return user ?? null
|
||||
},
|
||||
async updateUser(user) {
|
||||
const result = await client.db.nextauth_users.update(user.id!, user)
|
||||
return (
|
||||
result ?? {
|
||||
...user,
|
||||
id: user.id!,
|
||||
emailVerified: user.emailVerified ?? null,
|
||||
}
|
||||
)
|
||||
},
|
||||
async deleteUser(id) {
|
||||
return await client.db.nextauth_users.delete(id)
|
||||
},
|
||||
async linkAccount(initialAccount) {
|
||||
const { userId, ...account } = initialAccount
|
||||
const newXataAccount = await client.db.nextauth_accounts.create({
|
||||
...account,
|
||||
user: { id: userId },
|
||||
})
|
||||
await client.db.nextauth_users_accounts.create({
|
||||
user: { id: userId },
|
||||
account: { id: newXataAccount.id },
|
||||
})
|
||||
},
|
||||
async unlinkAccount({ providerAccountId, provider }) {
|
||||
/**
|
||||
* @todo refactor this when we support DELETE WHERE.
|
||||
*/
|
||||
const connectedAccount = await client.db.nextauth_users_accounts
|
||||
.filter({
|
||||
"account.providerAccountId": providerAccountId,
|
||||
"account.provider": provider,
|
||||
})
|
||||
.getFirst()
|
||||
|
||||
if (!connectedAccount) {
|
||||
return
|
||||
}
|
||||
return await client.db.nextauth_users_accounts.delete(connectedAccount.id)
|
||||
},
|
||||
async createSession(initialSession) {
|
||||
const { userId, ...session } = initialSession
|
||||
const newXataSession = await client.db.nextauth_sessions.create({
|
||||
...session,
|
||||
user: { id: userId },
|
||||
})
|
||||
await client.db.nextauth_users_sessions.create({
|
||||
user: { id: userId },
|
||||
session: { id: newXataSession.id },
|
||||
})
|
||||
return { ...session, ...newXataSession, userId }
|
||||
},
|
||||
async getSessionAndUser(sessionToken) {
|
||||
const result = await client.db.nextauth_users_sessions
|
||||
.select(["user.*", "session.*"])
|
||||
.filter({ "session.sessionToken": sessionToken })
|
||||
.getFirst()
|
||||
if (!result?.session || !result?.user) {
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
session: {
|
||||
...result.session,
|
||||
sessionToken: result.session.sessionToken!,
|
||||
expires: result.session.expires!,
|
||||
userId: result.user.id,
|
||||
},
|
||||
user: {
|
||||
...result.user,
|
||||
emailVerified: result.user.emailVerified ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
async updateSession({ sessionToken, ...data }) {
|
||||
const session = await client.db.nextauth_sessions
|
||||
.filter({ sessionToken })
|
||||
.getFirst()
|
||||
if (!session) {
|
||||
return null
|
||||
}
|
||||
|
||||
await client.db.nextauth_sessions.update({ ...session, ...data })
|
||||
return {
|
||||
...session,
|
||||
sessionToken,
|
||||
userId: data.userId!,
|
||||
expires: data.expires!,
|
||||
}
|
||||
},
|
||||
|
||||
async deleteSession(sessionToken) {
|
||||
/**
|
||||
* @todo refactor this when we support DELETE WHERE.
|
||||
*/
|
||||
const session = await client.db.nextauth_sessions
|
||||
.filter({ sessionToken })
|
||||
.getFirst()
|
||||
if (!session) {
|
||||
return
|
||||
}
|
||||
const connectedSession = await client.db.nextauth_users_sessions
|
||||
.filter({ "session.sessionToken": sessionToken })
|
||||
.getFirst()
|
||||
if (!connectedSession) {
|
||||
return
|
||||
}
|
||||
await client.db.nextauth_sessions.delete(session.id)
|
||||
await client.db.nextauth_users_sessions.delete(connectedSession.id)
|
||||
},
|
||||
async createVerificationToken(token) {
|
||||
await client.db.nextauth_verificationTokens.create({
|
||||
expires: token.expires,
|
||||
identifier: token.identifier,
|
||||
token: token.token,
|
||||
})
|
||||
return token
|
||||
},
|
||||
async useVerificationToken(token) {
|
||||
/**
|
||||
* @todo refactor this when we support DELETE WHERE.
|
||||
*/
|
||||
const xataToken = await client.db.nextauth_verificationTokens
|
||||
.filter({ identifier: token.identifier, token: token.token })
|
||||
.getFirst()
|
||||
if (!xataToken) {
|
||||
return null
|
||||
}
|
||||
await client.db.nextauth_verificationTokens.delete(xataToken.id)
|
||||
return { ...token, expires: new Date() }
|
||||
},
|
||||
}
|
||||
}
|
||||
93
packages/adapter-xata/src/xata.ts
Normal file
93
packages/adapter-xata/src/xata.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* This file is auto-generated from Xata and corresponds
|
||||
* to the database types in the Xata database. Please do not
|
||||
* augment by hand.
|
||||
*/
|
||||
import {
|
||||
buildClient,
|
||||
BaseClientOptions,
|
||||
XataRecord,
|
||||
ClientConstructor,
|
||||
} from "@xata.io/client"
|
||||
|
||||
export interface NextauthUser {
|
||||
email?: string | null
|
||||
emailVerified?: Date | null
|
||||
name?: string | null
|
||||
image?: string | null
|
||||
}
|
||||
|
||||
export type NextauthUserRecord = NextauthUser & XataRecord
|
||||
|
||||
export interface NextauthAccount {
|
||||
user?: NextauthUserRecord | null
|
||||
type?: string | null
|
||||
provider?: string | null
|
||||
providerAccountId?: string | null
|
||||
refresh_token?: string | null
|
||||
access_token?: string | null
|
||||
expires_at?: number | null
|
||||
token_type?: string | null
|
||||
scope?: string | null
|
||||
id_token?: string | null
|
||||
session_state?: string | null
|
||||
}
|
||||
|
||||
export type NextauthAccountRecord = NextauthAccount & XataRecord
|
||||
|
||||
export interface NextauthVerificationToken {
|
||||
identifier?: string | null
|
||||
token?: string | null
|
||||
expires?: Date | null
|
||||
}
|
||||
|
||||
export type NextauthVerificationTokenRecord = NextauthVerificationToken &
|
||||
XataRecord
|
||||
|
||||
export interface NextauthUsersAccount {
|
||||
user?: NextauthUserRecord | null
|
||||
account?: NextauthAccountRecord | null
|
||||
}
|
||||
|
||||
export type NextauthUsersAccountRecord = NextauthUsersAccount & XataRecord
|
||||
|
||||
export interface NextauthUsersSession {
|
||||
user?: NextauthUserRecord | null
|
||||
session?: NextauthSessionRecord | null
|
||||
}
|
||||
|
||||
export type NextauthUsersSessionRecord = NextauthUsersSession & XataRecord
|
||||
|
||||
export interface NextauthSession {
|
||||
sessionToken?: string | null
|
||||
expires?: Date | null
|
||||
user?: NextauthUserRecord | null
|
||||
}
|
||||
|
||||
export type NextauthSessionRecord = NextauthSession & XataRecord
|
||||
|
||||
export type DatabaseSchema = {
|
||||
nextauth_users: NextauthUser
|
||||
nextauth_accounts: NextauthAccount
|
||||
nextauth_verificationTokens: NextauthVerificationToken
|
||||
nextauth_users_accounts: NextauthUsersAccount
|
||||
nextauth_users_sessions: NextauthUsersSession
|
||||
nextauth_sessions: NextauthSession
|
||||
}
|
||||
|
||||
const tables = [
|
||||
"nextauth_users",
|
||||
"nextauth_accounts",
|
||||
"nextauth_verificationTokens",
|
||||
"nextauth_users_accounts",
|
||||
"nextauth_users_sessions",
|
||||
"nextauth_sessions",
|
||||
]
|
||||
|
||||
const DatabaseClient = buildClient() as ClientConstructor<any>
|
||||
|
||||
export class XataClient extends DatabaseClient<DatabaseSchema> {
|
||||
constructor(options?: BaseClientOptions) {
|
||||
super({ databaseURL: "", ...options }, tables)
|
||||
}
|
||||
}
|
||||
56
packages/adapter-xata/tests/index.test.ts
Normal file
56
packages/adapter-xata/tests/index.test.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import "isomorphic-fetch"
|
||||
import { runBasicTests } from "@next-auth/adapter-test"
|
||||
import "dotenv/config"
|
||||
import { XataClient } from "../src/xata"
|
||||
import { XataAdapter } from "../src"
|
||||
|
||||
if (!process.env.XATA_API_KEY) {
|
||||
test("Skipping XataAdapter tests, since required environment variables aren't available", () => {
|
||||
expect(true).toBe(true)
|
||||
})
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
if (process.env.CI) {
|
||||
// TODO: Fix this
|
||||
test('Skipping XataAdapter tests in CI because of "Request failed" errors. Should revisit', () => {
|
||||
expect(true).toBe(true)
|
||||
})
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
const client = new XataClient({
|
||||
apiKey: process.env.XATA_API_KEY,
|
||||
})
|
||||
|
||||
runBasicTests({
|
||||
adapter: XataAdapter(client),
|
||||
db: {
|
||||
async user(id: string) {
|
||||
const data = await client.db.nextauth_users.filter({ id }).getFirst()
|
||||
if (!data) return null
|
||||
return data
|
||||
},
|
||||
async account({ provider, providerAccountId }) {
|
||||
const data = await client.db.nextauth_accounts
|
||||
.filter({ provider, providerAccountId })
|
||||
.getFirst()
|
||||
if (!data) return null
|
||||
return data
|
||||
},
|
||||
async session(sessionToken) {
|
||||
const data = await client.db.nextauth_sessions
|
||||
.filter({ sessionToken })
|
||||
.getFirst()
|
||||
if (!data) return null
|
||||
return data
|
||||
},
|
||||
async verificationToken(where) {
|
||||
const data = await client.db.nextauth_verificationTokens
|
||||
.filter(where)
|
||||
.getFirst()
|
||||
if (!data) return null
|
||||
return data
|
||||
},
|
||||
},
|
||||
})
|
||||
9
packages/adapter-xata/tsconfig.json
Normal file
9
packages/adapter-xata/tsconfig.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "@next-auth/tsconfig/tsconfig.adapters.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "dist"
|
||||
},
|
||||
"include": ["."],
|
||||
"exclude": ["tests", "dist", "jest.config.js"]
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "next-auth",
|
||||
"version": "4.15.0",
|
||||
"version": "4.16.4",
|
||||
"description": "Authentication for Next.js",
|
||||
"homepage": "https://next-auth.js.org",
|
||||
"repository": "https://github.com/nextauthjs/next-auth.git",
|
||||
@@ -78,7 +78,7 @@
|
||||
"uuid": "^8.3.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"next": "^12.2.5",
|
||||
"next": "^12.2.5 || ^13",
|
||||
"nodemailer": "^6.6.5",
|
||||
"react": "^17.0.2 || ^18",
|
||||
"react-dom": "^17.0.2 || ^18"
|
||||
@@ -120,7 +120,7 @@
|
||||
"jest-environment-jsdom": "^28.1.1",
|
||||
"jest-watch-typeahead": "^1.1.0",
|
||||
"msw": "^0.42.3",
|
||||
"next": "12.3.1",
|
||||
"next": "13.0.2",
|
||||
"postcss": "^8.4.14",
|
||||
"postcss-cli": "^9.1.0",
|
||||
"postcss-nested": "^5.0.6",
|
||||
@@ -129,6 +129,6 @@
|
||||
"whatwg-fetch": "^3.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.19.0 || ^14.15.0 || ^16.13.0"
|
||||
"node": "^12.19.0 || ^14.15.0 || ^16.13.0 || ^18.12.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -58,10 +58,8 @@ async function toInternalRequest(
|
||||
const url = new URL(req.url)
|
||||
// TODO: handle custom paths?
|
||||
const nextauth = url.pathname.split("/").slice(3)
|
||||
const headers = Object.fromEntries(req.headers.entries())
|
||||
const query: Record<string, any> = Object.fromEntries(
|
||||
url.searchParams.entries()
|
||||
)
|
||||
const headers = Object.fromEntries(req.headers)
|
||||
const query: Record<string, any> = Object.fromEntries(url.searchParams)
|
||||
query.nextauth = nextauth
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { IncomingHttpHeaders } from "http"
|
||||
import type { CookiesOptions } from "../.."
|
||||
import type { CookieOption, LoggerInstance, SessionStrategy } from "../types"
|
||||
import type { NextRequest } from "next/server"
|
||||
import type { NextApiRequest } from "next"
|
||||
|
||||
// Uncomment to recalculate the estimated size
|
||||
// of an empty session cookie
|
||||
@@ -128,10 +129,10 @@ export class SessionStore {
|
||||
|
||||
constructor(
|
||||
option: CookieOption,
|
||||
req: {
|
||||
cookies?: Partial<Record<string, string> | Map<string, string>>
|
||||
headers?: Headers | IncomingHttpHeaders | Record<string, string>
|
||||
},
|
||||
req: Partial<{
|
||||
cookies: NextRequest["cookies"] | NextApiRequest["cookies"]
|
||||
headers: NextRequest["headers"] | NextApiRequest["headers"]
|
||||
}>,
|
||||
logger: LoggerInstance | Console
|
||||
) {
|
||||
this.#logger = logger
|
||||
@@ -140,7 +141,14 @@ export class SessionStore {
|
||||
const { cookies } = req
|
||||
const { name: cookieName } = option
|
||||
|
||||
if (cookies instanceof Map) {
|
||||
if (typeof cookies?.getAll === "function") {
|
||||
// Next.js ^v13.0.1 (Edge Env)
|
||||
for (const { name, value } of cookies.getAll()) {
|
||||
if (name.startsWith(cookieName)) {
|
||||
this.#chunks[name] = value
|
||||
}
|
||||
}
|
||||
} else if (cookies instanceof Map) {
|
||||
for (const name of cookies.keys()) {
|
||||
if (name.startsWith(cookieName)) this.#chunks[name] = cookies.get(name)
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ export default async function getAuthorizationUrl({
|
||||
|
||||
if (typeof provider.authorization === "string") {
|
||||
const parsedUrl = new URL(provider.authorization)
|
||||
const parsedParams = Object.fromEntries(parsedUrl.searchParams.entries())
|
||||
const parsedParams = Object.fromEntries(parsedUrl.searchParams)
|
||||
params = { ...params, ...parsedParams }
|
||||
} else {
|
||||
params = { ...params, ...provider.authorization?.params }
|
||||
|
||||
@@ -84,6 +84,7 @@ function NextAuth(
|
||||
export default NextAuth
|
||||
|
||||
let experimentalWarningShown = false
|
||||
let experimentalRSCWarningShown = false
|
||||
export async function unstable_getServerSession(
|
||||
...args:
|
||||
| [
|
||||
@@ -92,6 +93,8 @@ export async function unstable_getServerSession(
|
||||
NextAuthOptions
|
||||
]
|
||||
| [NextApiRequest, NextApiResponse, NextAuthOptions]
|
||||
| [NextAuthOptions]
|
||||
| []
|
||||
): Promise<Session | null> {
|
||||
if (!experimentalWarningShown && process.env.NODE_ENV !== "production") {
|
||||
console.warn(
|
||||
@@ -103,7 +106,40 @@ export async function unstable_getServerSession(
|
||||
experimentalWarningShown = true
|
||||
}
|
||||
|
||||
const [req, res, options] = args
|
||||
const isRSC = args.length === 0 || args.length === 1
|
||||
if (
|
||||
!experimentalRSCWarningShown &&
|
||||
isRSC &&
|
||||
process.env.NODE_ENV !== "production"
|
||||
) {
|
||||
console.warn(
|
||||
"[next-auth][warn][EXPERIMENTAL_API]",
|
||||
"\n`unstable_getServerSession` is used in a React Server Component.",
|
||||
`\nhttps://next-auth.js.org/configuration/nextjs#unstable_getServerSession}`,
|
||||
`\nhttps://next-auth.js.org/warnings#EXPERIMENTAL_API`
|
||||
)
|
||||
experimentalRSCWarningShown = true
|
||||
}
|
||||
|
||||
let req, res, options: NextAuthOptions
|
||||
if (isRSC) {
|
||||
options = args[0] ?? { providers: [] }
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const { headers, cookies } = require("next/headers")
|
||||
req = {
|
||||
headers: Object.fromEntries(headers() as Headers),
|
||||
cookies: Object.fromEntries(
|
||||
cookies()
|
||||
.getAll()
|
||||
.map((c) => [c.name, c.value])
|
||||
),
|
||||
}
|
||||
res = { getHeader() {}, setCookie() {}, setHeader() {} }
|
||||
} else {
|
||||
req = args[0]
|
||||
res = args[1]
|
||||
options = args[2]
|
||||
}
|
||||
|
||||
options.secret = options.secret ?? process.env.NEXTAUTH_SECRET
|
||||
|
||||
@@ -123,7 +159,11 @@ export async function unstable_getServerSession(
|
||||
cookies?.forEach((cookie) => setCookie(res, cookie))
|
||||
|
||||
if (body && typeof body !== "string" && Object.keys(body).length) {
|
||||
if (status === 200) return body as Session
|
||||
if (status === 200) {
|
||||
// @ts-expect-error
|
||||
if (isRSC) delete body.expires
|
||||
return body as Session
|
||||
}
|
||||
throw new Error((body as any).message)
|
||||
}
|
||||
|
||||
|
||||
@@ -184,7 +184,7 @@ export type WithAuthArgs =
|
||||
* [Documentation](https://next-auth.js.org/configuration/nextjs#middleware)
|
||||
*/
|
||||
export function withAuth(...args: WithAuthArgs) {
|
||||
if (!args.length || args[0] instanceof NextRequest) {
|
||||
if (!args.length || args[0] instanceof Request) {
|
||||
// @ts-expect-error
|
||||
return handleMiddleware(...args)
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ export type SessionContextValue<R extends boolean = false> = R extends true
|
||||
| { data: Session; status: "authenticated" }
|
||||
| { data: null; status: "unauthenticated" | "loading" }
|
||||
|
||||
export const SessionContext = React.createContext<
|
||||
export const SessionContext = React.createContext?.<
|
||||
SessionContextValue | undefined
|
||||
>(undefined)
|
||||
|
||||
@@ -106,6 +106,10 @@ export const SessionContext = React.createContext<
|
||||
* [Documentation](https://next-auth.js.org/getting-started/client#usesession)
|
||||
*/
|
||||
export function useSession<R extends boolean>(options?: UseSessionOptions<R>) {
|
||||
if (!SessionContext) {
|
||||
throw new Error("React Context is unavailable in Server Components")
|
||||
}
|
||||
|
||||
// @ts-expect-error Satisfy TS if branch on line below
|
||||
const value: SessionContextValue<R> = React.useContext(SessionContext)
|
||||
if (!value && process.env.NODE_ENV !== "production") {
|
||||
@@ -322,6 +326,10 @@ export async function signOut<R extends boolean = true>(
|
||||
* [Documentation](https://next-auth.js.org/getting-started/client#sessionprovider)
|
||||
*/
|
||||
export function SessionProvider(props: SessionProviderProps) {
|
||||
if (!SessionContext) {
|
||||
throw new Error("React Context is unavailable in Server Components")
|
||||
}
|
||||
|
||||
const { children, basePath, refetchInterval, refetchWhenOffline } = props
|
||||
|
||||
if (basePath) __NEXTAUTH.basePath = basePath
|
||||
|
||||
441
pnpm-lock.yaml
generated
441
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user