mirror of
https://github.com/SrIzan10/next-auth.git
synced 2026-05-01 10:55:20 +00:00
Compare commits
37 Commits
@auth/core
...
@auth/fire
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ff3a7392fb | ||
|
|
e1ba0c948e | ||
|
|
304575581b | ||
|
|
5133892784 | ||
|
|
a767456e36 | ||
|
|
b277e937e2 | ||
|
|
59b2847274 | ||
|
|
e32fb16b17 | ||
|
|
45e721c3f7 | ||
|
|
de8ad4f5af | ||
|
|
9462b8ffb4 | ||
|
|
1cf0eeace6 | ||
|
|
7cf0074417 | ||
|
|
899098ccc4 | ||
|
|
67c29039c7 | ||
|
|
e9ad688a5a | ||
|
|
8f8067a23a | ||
|
|
8629e16255 | ||
|
|
bfa0d910d7 | ||
|
|
cff0d61e07 | ||
|
|
41c24542b5 | ||
|
|
77a439b2a2 | ||
|
|
95eb8aaf69 | ||
|
|
559842fe02 | ||
|
|
ce7a49910e | ||
|
|
e895f42302 | ||
|
|
db2ace585d | ||
|
|
c9fc84ee82 | ||
|
|
77933b23f0 | ||
|
|
cbbe27102e | ||
|
|
e274c51807 | ||
|
|
2b3836d945 | ||
|
|
b729f8af0b | ||
|
|
9f54222c0e | ||
|
|
a5ac491cb8 | ||
|
|
a96dcdbca3 | ||
|
|
bec01a82ea |
2
.github/ISSUE_TEMPLATE/1_bug_framework.yml
vendored
2
.github/ISSUE_TEMPLATE/1_bug_framework.yml
vendored
@@ -1,6 +1,6 @@
|
||||
name: Bug report
|
||||
description: Report an issue so we can improve
|
||||
labels: [triage]
|
||||
labels: [triage, bug]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/2_bug_provider.yml
vendored
2
.github/ISSUE_TEMPLATE/2_bug_provider.yml
vendored
@@ -1,6 +1,6 @@
|
||||
name: Bug report (Provider)
|
||||
description: Create a provider-specific report
|
||||
labels: [triage, providers]
|
||||
labels: [triage, bug, providers]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
|
||||
5
.github/ISSUE_TEMPLATE/3_bug_adapter.yml
vendored
5
.github/ISSUE_TEMPLATE/3_bug_adapter.yml
vendored
@@ -1,6 +1,6 @@
|
||||
name: Bug report (Adapter)
|
||||
description: Create an adapter-specific report
|
||||
labels: [triage, adapters]
|
||||
labels: [triage, bug, adapters]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
@@ -21,6 +21,9 @@ body:
|
||||
multiple: true
|
||||
options:
|
||||
- "Custom adapter"
|
||||
- "@auth/azure-tables-adapter"
|
||||
- "@auth/edgedb-adapter"
|
||||
- "@auth/d1-adapter"
|
||||
- "@auth/dgraph-adapter"
|
||||
- "@auth/drizzle-adapter"
|
||||
- "@auth/dynamodb-adapter"
|
||||
|
||||
5
.github/pr-labeler.yml
vendored
5
.github/pr-labeler.yml
vendored
@@ -1,7 +1,11 @@
|
||||
# https://github.com/actions/labeler#create-githublabeleryml
|
||||
adapters: ["packages/core/src/adapters.ts", "packages/adapter-*/**/*"]
|
||||
core: ["packages/core/src/**/*"]
|
||||
azure-tables: ["packages/adapter-azure-tables/**/*"]
|
||||
edgedb: ["packages/adapter-edgedb/**/*"]
|
||||
d1: ["packages/adapter-d1/**/*"]
|
||||
dgraph: ["packages/adapter-dgraph/**/*"]
|
||||
drizzle: ["packages/adapter-drizzle/**/*"]
|
||||
documentation: ["packages/docs/docs/**/*"]
|
||||
dynamodb: ["packages/adapter-dynamodb/**/*"]
|
||||
examples: ["apps/examples/**/*"]
|
||||
@@ -21,6 +25,7 @@ providers: ["packages/core/src/providers/**/*"]
|
||||
sequelize: ["packages/adapter-sequelize/**/*"]
|
||||
solidjs: ["packages/frameworks-solid-start/**/*"]
|
||||
supabase: ["packages/adapter-supabase/**/*"]
|
||||
surrealdb: ["packages/adapter-surrealdb/**/*"]
|
||||
svelte: ["packages/frameworks-sveltekit/**/*"]
|
||||
test: ["**test**/*"]
|
||||
typeorm: ["packages/adapter-typeorm/**/*"]
|
||||
|
||||
2
.github/version-pr/action.yml
vendored
2
.github/version-pr/action.yml
vendored
@@ -4,5 +4,5 @@ outputs:
|
||||
version:
|
||||
description: "npm package version"
|
||||
runs:
|
||||
using: "node16"
|
||||
using: "node20"
|
||||
main: "index.js"
|
||||
|
||||
10
.github/workflows/release.yml
vendored
10
.github/workflows/release.yml
vendored
@@ -39,6 +39,7 @@ on:
|
||||
options:
|
||||
- "core"
|
||||
- "frameworks-nextjs"
|
||||
- "adapter-edgedb"
|
||||
- "adapter-dgraph"
|
||||
- "adapter-drizzle"
|
||||
- "adapter-dynamodb"
|
||||
@@ -118,22 +119,21 @@ jobs:
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.GH_PAT_CLASSIC }}
|
||||
# Please upvote https://github.com/orgs/community/discussions/13836
|
||||
token: ${{ secrets.GH_PAT }}
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2.2.4
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
cache: "pnpm"
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
- name: Publish to npm and GitHub
|
||||
run: pnpm release
|
||||
env:
|
||||
# Use GH_PAT when this is fixed:
|
||||
# https://github.com/github/roadmap/issues/622
|
||||
GITHUB_TOKEN: ${{ secrets.GH_PAT_CLASSIC }}
|
||||
# Please upvote https://github.com/orgs/community/discussions/13836
|
||||
GITHUB_TOKEN: ${{ secrets.GH_PAT }}
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
release-pr:
|
||||
name: Publish PR
|
||||
|
||||
3
.github/workflows/triage.yml
vendored
3
.github/workflows/triage.yml
vendored
@@ -14,10 +14,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Nissuer
|
||||
uses: balazsorban44/nissuer@1.3.5
|
||||
uses: balazsorban44/nissuer@1.5.0
|
||||
with:
|
||||
label-area-prefix: ""
|
||||
label-area-section: "[Provider|Adapter] type(.*)### Environment"
|
||||
label-comments: '{ "incomplete": ".github/invalid-reproduction.md" }'
|
||||
reproduction-link-section: "### Reproduction URL(.*)### Describe the issue"
|
||||
reproduction-invalid-label: "invalid reproduction"
|
||||
reproduction-issue-labels: "bug"
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
# Rename file to .env.local (or .env) and populate values
|
||||
# to be able to run the dev app
|
||||
|
||||
NEXTAUTH_URL=http://localhost:3000
|
||||
|
||||
# You can use `openssl rand -hex 32` or
|
||||
# https://generate-secret.vercel.app/32 to generate a secret.
|
||||
# Note: Changing a secret may invalidate existing sessions
|
||||
# and/or verification tokens.
|
||||
NEXTAUTH_SECRET=secret
|
||||
|
||||
AUTH0_ID=
|
||||
AUTH0_SECRET=
|
||||
AUTH0_ISSUER=
|
||||
|
||||
DESCOPE_ID=
|
||||
DESCOPE_SECRET=
|
||||
|
||||
KEYCLOAK_ID=
|
||||
KEYCLOAK_SECRET=
|
||||
KEYCLOAK_ISSUER=
|
||||
|
||||
IDS4_ID=
|
||||
IDS4_SECRET=
|
||||
IDS4_ISSUER=
|
||||
|
||||
GITHUB_ID=
|
||||
GITHUB_SECRET=
|
||||
|
||||
TWITCH_ID=
|
||||
TWITCH_SECRET=
|
||||
|
||||
TWITTER_ID=
|
||||
TWITTER_SECRET=
|
||||
|
||||
LINE_ID=
|
||||
LINE_SECRET=
|
||||
|
||||
TRAKT_ID=
|
||||
TRAKT_SECRET=
|
||||
|
||||
# Example configuration for a Gmail account (will need SMTP enabled)
|
||||
EMAIL_SERVER=smtps://user@gmail.com:password@smtp.gmail.com:465
|
||||
EMAIL_FROM=user@gmail.com
|
||||
|
||||
# Note: If using with Prisma adapter, you need to use a `.env`
|
||||
# file rather than a `.env.local` file to configure env vars.
|
||||
# Postgres: DATABASE_URL=postgres://nextauth:password@127.0.0.1:5432/nextauth?synchronize=true
|
||||
# MySQL: DATABASE_URL=mysql://nextauth:password@127.0.0.1:3306/nextauth?synchronize=true
|
||||
# MongoDB: DATABASE_URL=mongodb://nextauth:password@127.0.0.1:27017/nextauth?synchronize=true
|
||||
DATABASE_URL=
|
||||
|
||||
WIKIMEDIA_ID=
|
||||
WIKIMEDIA_SECRET=
|
||||
|
||||
# Supabase Example Configuration
|
||||
# Supabase Example Configuration
|
||||
# NEXT_PUBLIC_SUPABASE_URL=http://localhost:54321
|
||||
# SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSJ9.vI9obAHOGyVVKa3pD--kJlyxp-Z2zV9UUMAhKpNLAcU
|
||||
# SUPABASE_JWT_SECRET=super-secret-jwt-token-with-at-least-32-characters-long
|
||||
# NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24ifQ.625_WdcF3KHqz5amU0x2X5WWHP-OEs_4qj0ssLNHzTs
|
||||
4
apps/dev/nextjs-v4/.vscode/settings.json
vendored
4
apps/dev/nextjs-v4/.vscode/settings.json
vendored
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"typescript.tsdk": "../../node_modules/.pnpm/typescript@4.8.4/node_modules/typescript/lib",
|
||||
"typescript.enablePromptUseWorkspaceTsdk": true
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
# NextAuth.js Development App
|
||||
|
||||
This folder contains a Next.js app using NextAuth.js for local development. See the following section on how to start:
|
||||
|
||||
[Setting up local environment
|
||||
](https://github.com/nextauthjs/.github/blob/main/CONTRIBUTING.md#setting-up-local-environment)
|
||||
@@ -1,14 +0,0 @@
|
||||
import NextAuth, { type NextAuthOptions } from "next-auth"
|
||||
import GitHub from "next-auth/providers/github"
|
||||
|
||||
export const authOptions: NextAuthOptions = {
|
||||
providers: [
|
||||
GitHub({
|
||||
clientId: process.env.GITHUB_ID,
|
||||
clientSecret: process.env.GITHUB_SECRET,
|
||||
}),
|
||||
],
|
||||
}
|
||||
|
||||
const handler = NextAuth(authOptions)
|
||||
export { handler as GET, handler as POST }
|
||||
@@ -1,12 +0,0 @@
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<html>
|
||||
<head></head>
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
import { getServerSession } from "next-auth/next"
|
||||
|
||||
export default async function Page() {
|
||||
const session = await getServerSession()
|
||||
return <pre>{JSON.stringify(session, null, 2)}</pre>
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
import { signIn } from "next-auth/react"
|
||||
|
||||
export default function AccessDenied() {
|
||||
return (
|
||||
<>
|
||||
<h1>Access Denied</h1>
|
||||
<p>
|
||||
<a
|
||||
href="/api/auth/signin"
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
signIn()
|
||||
}}
|
||||
>
|
||||
You must be signed in to view this page
|
||||
</a>
|
||||
</p>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
import Link from "next/link"
|
||||
import styles from "./footer.module.css"
|
||||
import packageJSON from "package.json"
|
||||
|
||||
export default function Footer() {
|
||||
return (
|
||||
<footer className={styles.footer}>
|
||||
<hr />
|
||||
<ul className={styles.navItems}>
|
||||
<li className={styles.navItem}>
|
||||
<a href="https://next-auth.js.org">Documentation</a>
|
||||
</li>
|
||||
<li className={styles.navItem}>
|
||||
<a href="https://www.npmjs.com/package/next-auth">NPM</a>
|
||||
</li>
|
||||
<li className={styles.navItem}>
|
||||
<a href="https://github.com/nextauthjs/next-auth-example">GitHub</a>
|
||||
</li>
|
||||
<li className={styles.navItem}>
|
||||
<Link href="/policy">Policy</Link>
|
||||
</li>
|
||||
<li className={styles.navItem}>
|
||||
<em>{packageJSON.version}</em>
|
||||
</li>
|
||||
</ul>
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
.footer {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.navItems {
|
||||
margin-bottom: 1rem;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.navItem {
|
||||
display: inline-block;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
import Link from "next/link"
|
||||
import { signIn, signOut, useSession } from "next-auth/react"
|
||||
import styles from "./header.module.css"
|
||||
|
||||
// The approach used in this component shows how to built a sign in and sign out
|
||||
// component that works on pages which support both client and server side
|
||||
// rendering, and avoids any flash incorrect content on initial page load.
|
||||
export default function Header() {
|
||||
const { data: session, status } = useSession()
|
||||
|
||||
return (
|
||||
<header>
|
||||
<noscript>
|
||||
<style>{".nojs-show { opacity: 1; top: 0; }"}</style>
|
||||
</noscript>
|
||||
<div className={styles.signedInStatus}>
|
||||
<p
|
||||
className={`nojs-show ${
|
||||
!session && status === "loading" ? styles.loading : styles.loaded
|
||||
}`}
|
||||
>
|
||||
{!session && (
|
||||
<>
|
||||
<span className={styles.notSignedInText}>
|
||||
You are not signed in
|
||||
</span>
|
||||
<a
|
||||
href="/api/auth/signin"
|
||||
className={styles.buttonPrimary}
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
signIn()
|
||||
}}
|
||||
>
|
||||
Sign in
|
||||
</a>
|
||||
</>
|
||||
)}
|
||||
{session && (
|
||||
<>
|
||||
{session.user.image && (
|
||||
<img src={session.user.image} className={styles.avatar} />
|
||||
)}
|
||||
<span className={styles.signedInText}>
|
||||
<small>Signed in as</small>
|
||||
<br />
|
||||
<strong>{session.user.email} </strong>
|
||||
{session.user.name ? `(${session.user.name})` : null}
|
||||
</span>
|
||||
<a
|
||||
href="/api/auth/signout"
|
||||
className={styles.button}
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
signOut()
|
||||
}}
|
||||
>
|
||||
Sign out
|
||||
</a>
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<nav>
|
||||
<ul className={styles.navItems}>
|
||||
<li className={styles.navItem}>
|
||||
<Link href="/">Home</Link>
|
||||
</li>
|
||||
<li className={styles.navItem}>
|
||||
<Link href="/client">Client</Link>
|
||||
</li>
|
||||
<li className={styles.navItem}>
|
||||
<Link href="/server">Server</Link>
|
||||
</li>
|
||||
<li className={styles.navItem}>
|
||||
<Link href="/protected">Protected</Link>
|
||||
</li>
|
||||
<li className={styles.navItem}>
|
||||
<Link href="/protected-ssr">Protected(SSR)</Link>
|
||||
</li>
|
||||
<li className={styles.navItem}>
|
||||
<Link href="/api-example">API</Link>
|
||||
</li>
|
||||
<li className={styles.navItem}>
|
||||
<Link href="/credentials">Credentials</Link>
|
||||
</li>
|
||||
<li className={styles.navItem}>
|
||||
<Link href="/email">Email</Link>
|
||||
</li>
|
||||
<li className={styles.navItem}>
|
||||
<Link href="/middleware-protected">Middleware protected</Link>
|
||||
</li>
|
||||
<li className={styles.navItem}>
|
||||
<Link href="/supabase-client-rls">Supabase RLS</Link>
|
||||
</li>
|
||||
<li className={styles.navItem}>
|
||||
<Link href="/supabase-ssr">Supabase RLS(SSR)</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
/* Set min-height to avoid page reflow while session loading */
|
||||
.signedInStatus {
|
||||
display: block;
|
||||
min-height: 4rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.loading,
|
||||
.loaded {
|
||||
position: relative;
|
||||
top: 0;
|
||||
opacity: 1;
|
||||
overflow: hidden;
|
||||
border-radius: 0 0 .6rem .6rem;
|
||||
padding: .6rem 1rem;
|
||||
margin: 0;
|
||||
background-color: rgba(0,0,0,.05);
|
||||
transition: all 0.2s ease-in;
|
||||
}
|
||||
|
||||
.loading {
|
||||
top: -2rem;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.signedInText,
|
||||
.notSignedInText {
|
||||
position: absolute;
|
||||
padding-top: .8rem;
|
||||
left: 1rem;
|
||||
right: 6.5rem;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
display: inherit;
|
||||
z-index: 1;
|
||||
line-height: 1.3rem;
|
||||
}
|
||||
|
||||
.signedInText {
|
||||
padding-top: 0rem;
|
||||
left: 4.6rem;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
border-radius: 2rem;
|
||||
float: left;
|
||||
height: 2.8rem;
|
||||
width: 2.8rem;
|
||||
background-color: white;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.button,
|
||||
.buttonPrimary {
|
||||
float: right;
|
||||
margin-right: -.4rem;
|
||||
font-weight: 500;
|
||||
border-radius: .3rem;
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
line-height: 1.4rem;
|
||||
padding: .7rem .8rem;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
background-color: transparent;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.buttonPrimary {
|
||||
background-color: #346df1;
|
||||
border-color: #346df1;
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
padding: .7rem 1.4rem;
|
||||
}
|
||||
|
||||
.buttonPrimary:hover {
|
||||
box-shadow: inset 0 0 5rem rgba(0,0,0,0.2)
|
||||
}
|
||||
|
||||
.navItems {
|
||||
margin-bottom: 2rem;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.navItem {
|
||||
display: inline-block;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import Header from 'components/header'
|
||||
import Footer from 'components/footer'
|
||||
|
||||
export default function Layout ({ children }) {
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<main>
|
||||
{children}
|
||||
</main>
|
||||
<Footer />
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
export { default } from "next-auth/middleware"
|
||||
|
||||
export const config = { matcher: ["/middleware-protected"] }
|
||||
|
||||
// Other ways to use this middleware
|
||||
|
||||
// import withAuth from "next-auth/middleware"
|
||||
// import { withAuth } from "next-auth/middleware"
|
||||
|
||||
// export function middleware(req, ev) {
|
||||
// return withAuth(req)
|
||||
// }
|
||||
|
||||
// export function middleware(req, ev) {
|
||||
// return withAuth(req, ev)
|
||||
// }
|
||||
|
||||
// export function middleware(req, ev) {
|
||||
// return withAuth(req, {
|
||||
// callbacks: {
|
||||
// authorized: ({ token }) => !!token,
|
||||
// },
|
||||
// })
|
||||
// }
|
||||
|
||||
// export default withAuth(function middleware(req, ev) {
|
||||
// console.log(req.nextauth.token)
|
||||
// })
|
||||
|
||||
// export default withAuth(
|
||||
// function middleware(req, ev) {
|
||||
// console.log(req, ev)
|
||||
// },
|
||||
// {
|
||||
// callbacks: {
|
||||
// authorized: ({ token }) => token.name === "Balázs Orbán",
|
||||
// },
|
||||
// }
|
||||
// )
|
||||
|
||||
// export default withAuth({
|
||||
// callbacks: {
|
||||
// authorized: ({ token }) => !!token,
|
||||
// },
|
||||
// })
|
||||
6
apps/dev/nextjs-v4/next-env.d.ts
vendored
6
apps/dev/nextjs-v4/next-env.d.ts
vendored
@@ -1,6 +0,0 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
/// <reference types="next/navigation-types/compat/navigation" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
||||
@@ -1,9 +0,0 @@
|
||||
/** @type {import("next").NextConfig} */
|
||||
module.exports = {
|
||||
webpack(config) {
|
||||
config.experiments = { ...config.experiments, topLevelAwait: true }
|
||||
return config
|
||||
},
|
||||
experimental: { appDir: true },
|
||||
typescript: { ignoreBuildErrors: true },
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
{
|
||||
"name": "next-auth-app-v4",
|
||||
"version": "1.0.0",
|
||||
"description": "NextAuth.js Developer app",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"clean": "rm -rf .next",
|
||||
"dev": "next dev",
|
||||
"lint": "next lint",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"email": "fake-smtp-server",
|
||||
"start:email": "pnpm email"
|
||||
},
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@auth/fauna-adapter": "workspace:*",
|
||||
"@auth/prisma-adapter": "workspace:*",
|
||||
"@auth/supabase-adapter": "workspace:*",
|
||||
"@auth/typeorm-adapter": "workspace:*",
|
||||
"@prisma/client": "^3",
|
||||
"@supabase/supabase-js": "^2.0.5",
|
||||
"faunadb": "^4",
|
||||
"next": "13.3.0",
|
||||
"next-auth": "workspace:*",
|
||||
"nodemailer": "^6",
|
||||
"react": "^18",
|
||||
"react-dom": "^18"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jsonwebtoken": "^8.5.5",
|
||||
"@types/react": "^18.0.37",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"fake-smtp-server": "^0.8.0",
|
||||
"pg": "^8.7.3",
|
||||
"prisma": "^3",
|
||||
"sqlite3": "^5.0.8",
|
||||
"typeorm": "0.3.7"
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
import { SessionProvider } from "next-auth/react"
|
||||
import "./styles.css"
|
||||
|
||||
export default function App({ Component, pageProps }) {
|
||||
return (
|
||||
<SessionProvider session={pageProps.session}>
|
||||
<Component {...pageProps} />
|
||||
</SessionProvider>
|
||||
)
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
import Layout from '../components/layout'
|
||||
|
||||
export default function Page () {
|
||||
return (
|
||||
<Layout>
|
||||
<h1>API Example</h1>
|
||||
<p>The examples below show responses from the example API endpoints.</p>
|
||||
<p><em>You must be signed in to see responses.</em></p>
|
||||
<h2>Session</h2>
|
||||
<p>/api/examples/session</p>
|
||||
<iframe src='/api/examples/session' />
|
||||
<h2>JSON Web Token</h2>
|
||||
<p>/api/examples/jwt</p>
|
||||
<iframe src='/api/examples/jwt' />
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
@@ -1,217 +0,0 @@
|
||||
import NextAuth, { NextAuthOptions } from "next-auth"
|
||||
|
||||
// Providers
|
||||
import Apple from "next-auth/providers/apple"
|
||||
import Auth0 from "next-auth/providers/auth0"
|
||||
import AzureAD from "next-auth/providers/azure-ad"
|
||||
import AzureB2C from "next-auth/providers/azure-ad-b2c"
|
||||
import BoxyHQSAML from "next-auth/providers/boxyhq-saml"
|
||||
// import Cognito from "next-auth/providers/cognito"
|
||||
import Credentials from "next-auth/providers/credentials"
|
||||
import Discord from "next-auth/providers/discord"
|
||||
import DuendeIDS6 from "next-auth/providers/duende-identity-server6"
|
||||
// import Email from "next-auth/providers/email"
|
||||
import Facebook from "next-auth/providers/facebook"
|
||||
import Foursquare from "next-auth/providers/foursquare"
|
||||
import Freshbooks from "next-auth/providers/freshbooks"
|
||||
import GitHub from "next-auth/providers/github"
|
||||
import Gitlab from "next-auth/providers/gitlab"
|
||||
import Google from "next-auth/providers/google"
|
||||
// import IDS4 from "next-auth/providers/identity-server4"
|
||||
import Instagram from "next-auth/providers/instagram"
|
||||
// import Keycloak from "next-auth/providers/keycloak"
|
||||
import Line from "next-auth/providers/line"
|
||||
import LinkedIn from "next-auth/providers/linkedin"
|
||||
import Mailchimp from "next-auth/providers/mailchimp"
|
||||
// import Okta from "next-auth/providers/okta"
|
||||
import Osu from "next-auth/providers/osu"
|
||||
import Patreon from "next-auth/providers/patreon"
|
||||
import Slack from "next-auth/providers/slack"
|
||||
import Spotify from "next-auth/providers/spotify"
|
||||
import Trakt from "next-auth/providers/trakt"
|
||||
import Twitch from "next-auth/providers/twitch"
|
||||
import Twitter from "next-auth/providers/twitter"
|
||||
import Vk from "next-auth/providers/vk"
|
||||
import Wikimedia from "next-auth/providers/wikimedia"
|
||||
import WorkOS from "next-auth/providers/workos"
|
||||
|
||||
// // Prisma
|
||||
// import { PrismaClient } from "@prisma/client"
|
||||
// import { PrismaAdapter } from "@auth/prisma-adapter"
|
||||
// const client = globalThis.prisma || new PrismaClient()
|
||||
// if (process.env.NODE_ENV !== "production") globalThis.prisma = client
|
||||
// const adapter = PrismaAdapter(client)
|
||||
|
||||
// // Fauna
|
||||
// import { Client as FaunaClient } from "faunadb"
|
||||
// import { FaunaAdapter } from "@auth/fauna-adapter"
|
||||
// const opts = { secret: process.env.FAUNA_SECRET, domain: process.env.FAUNA_DOMAIN }
|
||||
// const client = globalThis.fauna || new FaunaClient(opts)
|
||||
// if (process.env.NODE_ENV !== "production") globalThis.fauna = client
|
||||
// const adapter = FaunaAdapter(client)
|
||||
|
||||
// // TypeORM
|
||||
// import { TypeORMAdapter } from "@auth/typeorm-adapter"
|
||||
// const adapter = TypeORMAdapter({
|
||||
// type: "sqlite",
|
||||
// name: "next-auth-test-memory",
|
||||
// database: "./typeorm/dev.db",
|
||||
// synchronize: true,
|
||||
// })
|
||||
|
||||
// // Supabase
|
||||
// import { SupabaseAdapter } from "@auth/supabase-adapter"
|
||||
// const adapter = SupabaseAdapter({
|
||||
// url: process.env.NEXT_PUBLIC_SUPABASE_URL,
|
||||
// secret: process.env.SUPABASE_SERVICE_ROLE_KEY,
|
||||
// })
|
||||
|
||||
export const authOptions: NextAuthOptions = {
|
||||
// adapter,
|
||||
// debug: process.env.NODE_ENV !== "production",
|
||||
theme: {
|
||||
logo: "https://next-auth.js.org/img/logo/logo-sm.png",
|
||||
brandColor: "#1786fb",
|
||||
},
|
||||
providers: [
|
||||
Credentials({
|
||||
credentials: { password: { label: "Password", type: "password" } },
|
||||
async authorize(credentials) {
|
||||
if (credentials.password !== "pw") return null
|
||||
return {
|
||||
name: "Fill Murray",
|
||||
email: "bill@fillmurray.com",
|
||||
image: "https://www.fillmurray.com/64/64",
|
||||
id: "1",
|
||||
foo: "",
|
||||
}
|
||||
},
|
||||
}),
|
||||
Apple({
|
||||
clientId: process.env.APPLE_ID,
|
||||
clientSecret: process.env.APPLE_SECRET,
|
||||
}),
|
||||
Auth0({
|
||||
clientId: process.env.AUTH0_ID,
|
||||
clientSecret: process.env.AUTH0_SECRET,
|
||||
issuer: process.env.AUTH0_ISSUER,
|
||||
}),
|
||||
AzureAD({
|
||||
clientId: process.env.AZURE_AD_CLIENT_ID,
|
||||
clientSecret: process.env.AZURE_AD_CLIENT_SECRET,
|
||||
tenantId: process.env.AZURE_AD_TENANT_ID,
|
||||
}),
|
||||
AzureB2C({
|
||||
clientId: process.env.AZURE_B2C_ID,
|
||||
clientSecret: process.env.AZURE_B2C_SECRET,
|
||||
issuer: process.env.AZURE_B2C_ISSUER,
|
||||
}),
|
||||
BoxyHQSAML({
|
||||
issuer: "https://jackson-demo.boxyhq.com",
|
||||
clientId: "tenant=boxyhq.com&product=saml-demo.boxyhq.com",
|
||||
clientSecret: "dummy",
|
||||
}),
|
||||
// Cognito({ clientId: process.env.COGNITO_ID, clientSecret: process.env.COGNITO_SECRET, issuer: process.env.COGNITO_ISSUER }),
|
||||
Discord({
|
||||
clientId: process.env.DISCORD_ID,
|
||||
clientSecret: process.env.DISCORD_SECRET,
|
||||
}),
|
||||
DuendeIDS6({
|
||||
clientId: "interactive.confidential",
|
||||
clientSecret: "secret",
|
||||
issuer: "https://demo.duendesoftware.com",
|
||||
}),
|
||||
Facebook({
|
||||
clientId: process.env.FACEBOOK_ID,
|
||||
clientSecret: process.env.FACEBOOK_SECRET,
|
||||
}),
|
||||
Foursquare({
|
||||
clientId: process.env.FOURSQUARE_ID,
|
||||
clientSecret: process.env.FOURSQUARE_SECRET,
|
||||
}),
|
||||
Freshbooks({
|
||||
clientId: process.env.FRESHBOOKS_ID,
|
||||
clientSecret: process.env.FRESHBOOKS_SECRET,
|
||||
}),
|
||||
GitHub({
|
||||
clientId: process.env.GITHUB_ID,
|
||||
clientSecret: process.env.GITHUB_SECRET,
|
||||
}),
|
||||
Gitlab({
|
||||
clientId: process.env.GITLAB_ID,
|
||||
clientSecret: process.env.GITLAB_SECRET,
|
||||
}),
|
||||
Google({
|
||||
clientId: process.env.GOOGLE_ID,
|
||||
clientSecret: process.env.GOOGLE_SECRET,
|
||||
}),
|
||||
// IDS4({ clientId: process.env.IDS4_ID, clientSecret: process.env.IDS4_SECRET, issuer: process.env.IDS4_ISSUER }),
|
||||
Instagram({
|
||||
clientId: process.env.INSTAGRAM_ID,
|
||||
clientSecret: process.env.INSTAGRAM_SECRET,
|
||||
}),
|
||||
// Keycloak({ clientId: process.env.KEYCLOAK_ID, clientSecret: process.env.KEYCLOAK_SECRET, issuer: process.env.KEYCLOAK_ISSUER }),
|
||||
Line({
|
||||
clientId: process.env.LINE_ID,
|
||||
clientSecret: process.env.LINE_SECRET,
|
||||
}),
|
||||
LinkedIn({
|
||||
clientId: process.env.LINKEDIN_ID,
|
||||
clientSecret: process.env.LINKEDIN_SECRET,
|
||||
}),
|
||||
Mailchimp({
|
||||
clientId: process.env.MAILCHIMP_ID,
|
||||
clientSecret: process.env.MAILCHIMP_SECRET,
|
||||
}),
|
||||
// Okta({ clientId: process.env.OKTA_ID, clientSecret: process.env.OKTA_SECRET, issuer: process.env.OKTA_ISSUER }),
|
||||
Osu({
|
||||
clientId: process.env.OSU_CLIENT_ID,
|
||||
clientSecret: process.env.OSU_CLIENT_SECRET,
|
||||
}),
|
||||
Patreon({
|
||||
clientId: process.env.PATREON_ID,
|
||||
clientSecret: process.env.PATREON_SECRET,
|
||||
}),
|
||||
Slack({
|
||||
clientId: process.env.SLACK_ID,
|
||||
clientSecret: process.env.SLACK_SECRET,
|
||||
}),
|
||||
Spotify({
|
||||
clientId: process.env.SPOTIFY_ID,
|
||||
clientSecret: process.env.SPOTIFY_SECRET,
|
||||
}),
|
||||
Trakt({
|
||||
clientId: process.env.TRAKT_ID,
|
||||
clientSecret: process.env.TRAKT_SECRET,
|
||||
}),
|
||||
Twitch({
|
||||
clientId: process.env.TWITCH_ID,
|
||||
clientSecret: process.env.TWITCH_SECRET,
|
||||
}),
|
||||
Twitter({
|
||||
clientId: process.env.TWITTER_ID,
|
||||
clientSecret: process.env.TWITTER_SECRET,
|
||||
}),
|
||||
// TwitterLegacy({ clientId: process.env.TWITTER_LEGACY_ID, clientSecret: process.env.TWITTER_LEGACY_SECRET }),
|
||||
Vk({ clientId: process.env.VK_ID, clientSecret: process.env.VK_SECRET }),
|
||||
Wikimedia({
|
||||
clientId: process.env.WIKIMEDIA_ID,
|
||||
clientSecret: process.env.WIKIMEDIA_SECRET,
|
||||
}),
|
||||
WorkOS({
|
||||
clientId: process.env.WORKOS_ID,
|
||||
clientSecret: process.env.WORKOS_SECRET,
|
||||
}),
|
||||
],
|
||||
}
|
||||
|
||||
if (authOptions.adapter) {
|
||||
// TODO:
|
||||
// authOptions.providers.unshift(
|
||||
// // NOTE: You can start a fake e-mail server with `pnpm email`
|
||||
// // and then go to `http://localhost:1080` in the browser
|
||||
// Email({ server: "smtp://127.0.0.1:1025?tls.rejectUnauthorized=false" })
|
||||
// )
|
||||
}
|
||||
|
||||
export default NextAuth(authOptions)
|
||||
@@ -1,7 +0,0 @@
|
||||
// This is an example of how to read a JSON Web Token from an API route
|
||||
import { getToken } from "next-auth/jwt"
|
||||
|
||||
export default async (req, res) => {
|
||||
const token = await getToken({ req })
|
||||
res.send(JSON.stringify(token, null, 2))
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
// This is an example of to protect an API route
|
||||
import { getServerSession } from "next-auth/next"
|
||||
import { authOptions } from "../auth/[...nextauth]"
|
||||
|
||||
export default async (req, res) => {
|
||||
const session = await getServerSession(req, res, authOptions)
|
||||
|
||||
if (session) {
|
||||
res.send({
|
||||
content:
|
||||
"This is protected content. You can access this content because you are signed in.",
|
||||
session,
|
||||
})
|
||||
} else {
|
||||
res.send({
|
||||
error: "You must be sign in to view the protected content on this page.",
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
// This is an example of how to access a session from an API route
|
||||
import { getServerSession } from "next-auth/next"
|
||||
import { authOptions } from "../auth/[...nextauth]"
|
||||
|
||||
export default async (req, res) => {
|
||||
const session = await getServerSession(req, res, authOptions)
|
||||
res.json(session)
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
// This is an example of how to query data from Supabase with RLS.
|
||||
// Learn more about Row Levele Security (RLS): https://supabase.com/docs/guides/auth/row-level-security
|
||||
import { getServerSession } from "next-auth/next"
|
||||
import { authOptions } from "../auth/[...nextauth]"
|
||||
import { createClient } from "@supabase/supabase-js"
|
||||
|
||||
export default async (req, res) => {
|
||||
const session = await getServerSession(req, res, authOptions)
|
||||
|
||||
if (!session)
|
||||
return res.send(JSON.stringify({ error: "No session!" }, null, 2))
|
||||
|
||||
const { supabaseAccessToken } = session
|
||||
|
||||
const supabase = createClient(
|
||||
process.env.NEXT_PUBLIC_SUPABASE_URL,
|
||||
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
|
||||
{
|
||||
global: {
|
||||
headers: {
|
||||
Authorization: `Bearer ${supabaseAccessToken}`,
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
// Now you can query with RLS enabled.
|
||||
const { data, error } = await supabase.from("users").select("*")
|
||||
|
||||
res.send(JSON.stringify({ supabaseAccessToken, data, error }, null, 2))
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import Layout from '../components/layout'
|
||||
|
||||
export default function Page () {
|
||||
return (
|
||||
<Layout>
|
||||
<h1>Client Side Rendering</h1>
|
||||
<p>
|
||||
This page uses the <strong>useSession()</strong> React Hook in the <strong></Header></strong> component.
|
||||
</p>
|
||||
<p>
|
||||
The <strong>useSession()</strong> React Hook easy to use and allows pages to render very quickly.
|
||||
</p>
|
||||
<p>
|
||||
The advantage of this approach is that session state is shared between pages by using the <strong>Provider</strong> in <strong>_app.js</strong> so
|
||||
that navigation between pages using <strong>useSession()</strong> is very fast.
|
||||
</p>
|
||||
<p>
|
||||
The disadvantage of <strong>useSession()</strong> is that it requires client side JavaScript.
|
||||
</p>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
import * as React from "react"
|
||||
import { signIn, signOut, useSession } from "next-auth/react"
|
||||
import Layout from "components/layout"
|
||||
|
||||
export default function Page() {
|
||||
const [response, setResponse] = React.useState(null)
|
||||
const handleLogin = (options) => async () => {
|
||||
if (options.redirect) {
|
||||
return signIn("credentials", options)
|
||||
}
|
||||
const response = await signIn("credentials", options)
|
||||
setResponse(response)
|
||||
}
|
||||
|
||||
const handleLogout = (options) => async () => {
|
||||
if (options.redirect) {
|
||||
return signOut(options)
|
||||
}
|
||||
const response = await signOut(options)
|
||||
setResponse(response)
|
||||
}
|
||||
|
||||
const { data: session } = useSession()
|
||||
|
||||
if (session) {
|
||||
return (
|
||||
<Layout>
|
||||
<h1>Test different flows for Credentials logout</h1>
|
||||
<span className="spacing">Default:</span>
|
||||
<button onClick={handleLogout({ redirect: true })}>Logout</button>
|
||||
<br />
|
||||
<span className="spacing">No redirect:</span>
|
||||
<button onClick={handleLogout({ redirect: false })}>Logout</button>
|
||||
<br />
|
||||
<p>Response:</p>
|
||||
<pre style={{ background: "#eee", padding: 16 }}>
|
||||
{JSON.stringify(response, null, 2)}
|
||||
</pre>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<h1>Test different flows for Credentials login</h1>
|
||||
<span className="spacing">Default:</span>
|
||||
<button onClick={handleLogin({ redirect: true, password: "password" })}>
|
||||
Login
|
||||
</button>
|
||||
<br />
|
||||
<span className="spacing">No redirect:</span>
|
||||
<button onClick={handleLogin({ redirect: false, password: "password" })}>
|
||||
Login
|
||||
</button>
|
||||
<br />
|
||||
<span className="spacing">No redirect, wrong password:</span>
|
||||
<button onClick={handleLogin({ redirect: false, password: "" })}>
|
||||
Login
|
||||
</button>
|
||||
<p>Response:</p>
|
||||
<pre style={{ background: "#eee", padding: 16 }}>
|
||||
{JSON.stringify(response, null, 2)}
|
||||
</pre>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
import * as React from "react"
|
||||
import { signIn, signOut, useSession } from "next-auth/react"
|
||||
import Layout from "components/layout"
|
||||
|
||||
export default function Page() {
|
||||
const [response, setResponse] = React.useState(null)
|
||||
const [email, setEmail] = React.useState("")
|
||||
|
||||
const handleChange = (event) => {
|
||||
setEmail(event.target.value)
|
||||
}
|
||||
|
||||
const handleLogin = (options) => async (event) => {
|
||||
event.preventDefault()
|
||||
|
||||
if (options.redirect) {
|
||||
return signIn("email", options)
|
||||
}
|
||||
const response = await signIn("email", options)
|
||||
setResponse(response)
|
||||
}
|
||||
|
||||
const handleLogout = (options) => async (event) => {
|
||||
if (options.redirect) {
|
||||
return signOut(options)
|
||||
}
|
||||
const response = await signOut(options)
|
||||
setResponse(response)
|
||||
}
|
||||
|
||||
const { data: session } = useSession()
|
||||
|
||||
if (session) {
|
||||
return (
|
||||
<Layout>
|
||||
<h1>Test different flows for Email logout</h1>
|
||||
<span className="spacing">Default:</span>
|
||||
<button onClick={handleLogout({ redirect: true })}>Logout</button>
|
||||
<br />
|
||||
<span className="spacing">No redirect:</span>
|
||||
<button onClick={handleLogout({ redirect: false })}>Logout</button>
|
||||
<br />
|
||||
<p>Response:</p>
|
||||
<pre style={{ background: "#eee", padding: 16 }}>
|
||||
{JSON.stringify(response, null, 2)}
|
||||
</pre>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<h1>Test different flows for Email login</h1>
|
||||
<label className="spacing">
|
||||
Email address:{" "}
|
||||
<input
|
||||
type="text"
|
||||
id="email"
|
||||
name="email"
|
||||
value={email}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</label>
|
||||
<br />
|
||||
<form onSubmit={handleLogin({ redirect: true, email })}>
|
||||
<span className="spacing">Default:</span>
|
||||
<button type="submit">Sign in with Email</button>
|
||||
</form>
|
||||
<form onSubmit={handleLogin({ redirect: false, email })}>
|
||||
<span className="spacing">No redirect:</span>
|
||||
<button type="submit">Sign in with Email</button>
|
||||
</form>
|
||||
<p>Response:</p>
|
||||
<pre style={{ background: "#eee", padding: 16 }}>
|
||||
{JSON.stringify(response, null, 2)}
|
||||
</pre>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import Layout from 'components/layout'
|
||||
|
||||
export default function Page () {
|
||||
return (
|
||||
<Layout>
|
||||
<h1>NextAuth.js Example</h1>
|
||||
<p>
|
||||
This is an example site to demonstrate how to use <a href='https://next-auth.js.org'>NextAuth.js</a> for authentication.
|
||||
</p>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import Layout from "components/layout"
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<Layout>
|
||||
<h1>Page protected by Middleware</h1>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
import Layout from '../components/layout'
|
||||
|
||||
export default function Page () {
|
||||
return (
|
||||
<Layout>
|
||||
<p>
|
||||
This is an example site to demonstrate how to use <a href='https://next-auth.js.org'>NextAuth.js</a> for authentication.
|
||||
</p>
|
||||
<h2>Terms of Service</h2>
|
||||
<p>
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
</p>
|
||||
<h2>Privacy Policy</h2>
|
||||
<p>
|
||||
This site uses JSON Web Tokens and an in-memory database which resets every ~2 hours.
|
||||
</p>
|
||||
<p>
|
||||
Data provided to this site is exclusively used to support signing in
|
||||
and is not passed to any third party services, other than via SMTP or OAuth for the
|
||||
purposes of authentication.
|
||||
</p>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
// This is an example of how to protect content using server rendering
|
||||
import { getServerSession } from "next-auth/next"
|
||||
import { authOptions } from "./api/auth/[...nextauth]"
|
||||
import Layout from "../components/layout"
|
||||
import AccessDenied from "../components/access-denied"
|
||||
|
||||
export default function Page({ content, session }) {
|
||||
// If no session exists, display access denied message
|
||||
if (!session) {
|
||||
return (
|
||||
<Layout>
|
||||
<AccessDenied />
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
// If session exists, display content
|
||||
return (
|
||||
<Layout>
|
||||
<h1>Protected Page</h1>
|
||||
<p>
|
||||
<strong>{content}</strong>
|
||||
</p>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
export async function getServerSideProps(context) {
|
||||
const session = await getServerSession(context.req, context.res, authOptions)
|
||||
let content = null
|
||||
|
||||
if (session) {
|
||||
const hostname = process.env.NEXTAUTH_URL || "http://localhost:3000"
|
||||
const options = { headers: { cookie: context.req.headers.cookie } }
|
||||
const res = await fetch(`${hostname}/api/examples/protected`, options)
|
||||
const json = await res.json()
|
||||
if (json.content) {
|
||||
content = json.content
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
props: {
|
||||
session,
|
||||
content,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
import { useState, useEffect } from "react"
|
||||
import { useSession } from "next-auth/react"
|
||||
import Layout from "../components/layout"
|
||||
|
||||
export default function Page() {
|
||||
const { status } = useSession({
|
||||
required: true,
|
||||
})
|
||||
const [content, setContent] = useState()
|
||||
|
||||
// Fetch content from protected route
|
||||
useEffect(() => {
|
||||
if (status === "loading") return
|
||||
const fetchData = async () => {
|
||||
const res = await fetch("/api/examples/protected")
|
||||
const json = await res.json()
|
||||
if (json.content) {
|
||||
setContent(json.content)
|
||||
}
|
||||
}
|
||||
fetchData()
|
||||
}, [status])
|
||||
|
||||
if (status === "loading") return <Layout>Loading...</Layout>
|
||||
|
||||
// If session exists, display content
|
||||
return (
|
||||
<Layout>
|
||||
<h1>Protected Page</h1>
|
||||
<p>
|
||||
<strong>{content}</strong>
|
||||
</p>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
import { getServerSession } from "next-auth/next"
|
||||
import Layout from "../components/layout"
|
||||
import { authOptions } from "./api/auth/[...nextauth]"
|
||||
|
||||
export default function Page() {
|
||||
// As this page uses Server Side Rendering, the `session` will be already
|
||||
// populated on render without needing to go through a loading stage.
|
||||
// This is possible because of the shared context configured in `_app.js` that
|
||||
// is used by `useSession()`.
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<h1>Server Side Rendering</h1>
|
||||
<p>
|
||||
This page uses the <strong>getServerSession()</strong> method in{" "}
|
||||
<strong>getServerSideProps()</strong>.
|
||||
</p>
|
||||
<p>
|
||||
Using <strong>getServerSession()</strong> in{" "}
|
||||
<strong>getServerSideProps()</strong> is currently the recommended
|
||||
approach, although the API may still change, if you need to support
|
||||
Server Side Rendering with authentication.
|
||||
</p>
|
||||
<p>
|
||||
Using <strong>getSession()</strong> is still recommended on the client.
|
||||
</p>
|
||||
<p>
|
||||
The advantage of Server Side Rendering is this page does not require
|
||||
client side JavaScript.
|
||||
</p>
|
||||
<p>
|
||||
The disadvantage of Server Side Rendering is that this page is slower to
|
||||
render.
|
||||
</p>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
// Export the `session` prop to use sessions with Server Side Rendering
|
||||
export async function getServerSideProps(context) {
|
||||
return {
|
||||
props: {
|
||||
session: await getServerSession(context.req, context.res, authOptions),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
body {
|
||||
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
|
||||
"Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif,
|
||||
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
padding: 0 1rem 1rem 1rem;
|
||||
max-width: 680px;
|
||||
margin: 0 auto;
|
||||
background: #fff;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
li,
|
||||
p {
|
||||
line-height: 1.5rem;
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
iframe {
|
||||
background: #ccc;
|
||||
border: 1px solid #ccc;
|
||||
height: 10rem;
|
||||
width: 100%;
|
||||
border-radius: .5rem;
|
||||
filter: invert(1);
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
import Layout from "../components/layout"
|
||||
import { useState, useEffect } from "react"
|
||||
import { useSession } from "next-auth/react"
|
||||
import { createClient } from "@supabase/supabase-js"
|
||||
|
||||
export default function Page() {
|
||||
const { data: session } = useSession()
|
||||
const [data, setData] = useState(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (session) {
|
||||
console.log(session)
|
||||
// User is logged in, let's fetch their data.
|
||||
const { supabaseAccessToken } = session
|
||||
const supabase = createClient(
|
||||
process.env.NEXT_PUBLIC_SUPABASE_URL,
|
||||
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
|
||||
{
|
||||
global: {
|
||||
headers: { Authorization: `Bearer ${supabaseAccessToken}` },
|
||||
},
|
||||
}
|
||||
)
|
||||
// Fetch data with RLS enabled.
|
||||
supabase
|
||||
.from("users")
|
||||
.select("*")
|
||||
.then(({ data }) => setData(data))
|
||||
}
|
||||
}, [session])
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<h1>Fetch Data from Supabase with RLS</h1>
|
||||
<h2>Client-side data fetching with RLS:</h2>
|
||||
<pre>{JSON.stringify(data, null, 2)}</pre>
|
||||
<h2>API Example</h2>
|
||||
<p>
|
||||
You can also use Supabase in API routes. See the code in the
|
||||
`/pages/api/examples/supabase-rls.js` file.
|
||||
</p>
|
||||
<p>
|
||||
<em>You must be signed in to see responses.</em>
|
||||
</p>
|
||||
<p>/api/examples/supabase-rls</p>
|
||||
<iframe src="/api/examples/supabase-rls" />
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
// This is an example of how to protect content using server rendering
|
||||
// and fetching data from Supabase with RLS enabled.
|
||||
import { getServerSession } from "next-auth/next"
|
||||
import { authOptions } from "./api/auth/[...nextauth]"
|
||||
import { createClient } from "@supabase/supabase-js"
|
||||
import Layout from "../components/layout"
|
||||
import AccessDenied from "../components/access-denied"
|
||||
|
||||
export default function Page({ data, session }) {
|
||||
// If no session exists, display access denied message
|
||||
if (!session) {
|
||||
return (
|
||||
<Layout>
|
||||
<AccessDenied />
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
// If session exists, display content
|
||||
return (
|
||||
<Layout>
|
||||
<h1>Protected Page</h1>
|
||||
<p>Data fetched during SSR from Supabase with RSL enabled:</p>
|
||||
<pre>{JSON.stringify(data, null, 2)}</pre>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
export async function getServerSideProps(context) {
|
||||
const session = await getServerSession(context.req, context.res, authOptions)
|
||||
|
||||
if (!session)
|
||||
return {
|
||||
props: {
|
||||
session,
|
||||
data: null,
|
||||
error: "No session",
|
||||
},
|
||||
}
|
||||
|
||||
const { supabaseAccessToken } = session
|
||||
|
||||
const supabase = createClient(
|
||||
process.env.NEXT_PUBLIC_SUPABASE_URL,
|
||||
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
|
||||
{
|
||||
global: {
|
||||
headers: {
|
||||
Authorization: `Bearer ${supabaseAccessToken}`,
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
// Now you can query with RLS enabled.
|
||||
const { data, error } = await supabase.from("users").select("*")
|
||||
|
||||
return {
|
||||
props: {
|
||||
session,
|
||||
data,
|
||||
error,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "Account" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"userId" TEXT NOT NULL,
|
||||
"type" TEXT NOT NULL,
|
||||
"provider" TEXT NOT NULL,
|
||||
"providerAccountId" TEXT NOT NULL,
|
||||
"refresh_token" TEXT,
|
||||
"access_token" TEXT,
|
||||
"expires_at" INTEGER,
|
||||
"token_type" TEXT,
|
||||
"scope" TEXT,
|
||||
"id_token" TEXT,
|
||||
"session_state" TEXT,
|
||||
"oauth_token_secret" TEXT,
|
||||
"oauth_token" TEXT,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "Account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Session" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"sessionToken" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"expires" DATETIME NOT NULL,
|
||||
CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "User" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"name" TEXT,
|
||||
"email" TEXT,
|
||||
"emailVerified" DATETIME,
|
||||
"image" TEXT
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "VerificationToken" (
|
||||
"identifier" TEXT NOT NULL,
|
||||
"token" TEXT NOT NULL,
|
||||
"expires" DATETIME NOT NULL
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Account_provider_providerAccountId_key" ON "Account"("provider", "providerAccountId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Session_sessionToken_key" ON "Session"("sessionToken");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "VerificationToken_token_key" ON "VerificationToken"("token");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "VerificationToken_identifier_token_key" ON "VerificationToken"("identifier", "token");
|
||||
@@ -1,3 +0,0 @@
|
||||
# Please do not edit this file manually
|
||||
# It should be added in your version-control system (i.e. Git)
|
||||
provider = "sqlite"
|
||||
@@ -1,57 +0,0 @@
|
||||
datasource db {
|
||||
provider = "sqlite"
|
||||
url = "file:./dev.db"
|
||||
}
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
model Account {
|
||||
id String @id @default(cuid())
|
||||
userId String
|
||||
type String
|
||||
provider String
|
||||
providerAccountId String
|
||||
refresh_token String?
|
||||
access_token String?
|
||||
expires_at Int?
|
||||
token_type String?
|
||||
scope String?
|
||||
id_token String?
|
||||
session_state String?
|
||||
oauth_token_secret String?
|
||||
oauth_token String?
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
|
||||
@@unique([provider, providerAccountId])
|
||||
}
|
||||
|
||||
model Session {
|
||||
id String @id @default(cuid())
|
||||
sessionToken String @unique
|
||||
userId String
|
||||
expires DateTime
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
}
|
||||
|
||||
model User {
|
||||
id String @id @default(cuid())
|
||||
name String?
|
||||
email String? @unique
|
||||
emailVerified DateTime?
|
||||
image String?
|
||||
accounts Account[]
|
||||
sessions Session[]
|
||||
}
|
||||
|
||||
model VerificationToken {
|
||||
identifier String
|
||||
token String @unique
|
||||
expires DateTime
|
||||
|
||||
@@unique([identifier, token])
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": false,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"incremental": true,
|
||||
"jsx": "preserve",
|
||||
"baseUrl": ".",
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"strictNullChecks": true
|
||||
},
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"jest.config.js"
|
||||
]
|
||||
}
|
||||
20
apps/dev/nextjs-v4/types/nextauth.d.ts
vendored
20
apps/dev/nextjs-v4/types/nextauth.d.ts
vendored
@@ -1,20 +0,0 @@
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
import NextAuth from "next-auth"
|
||||
|
||||
declare module "next-auth" {
|
||||
/**
|
||||
* Returned by `useSession`, `getSession` and received as a prop on the `SessionProvider` React Context
|
||||
*/
|
||||
interface Session {
|
||||
// A JWT which can be used as Authorization header with supabase-js for RLS.
|
||||
supabaseAccessToken?: string
|
||||
user: {
|
||||
/** The user's postal address. */
|
||||
address: string
|
||||
} & User
|
||||
}
|
||||
|
||||
interface User {
|
||||
foo: string
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,6 @@
|
||||
"@supabase/supabase-js": "^2.0.5",
|
||||
"faunadb": "^4",
|
||||
"next": "13.4.0",
|
||||
"next-auth": "workspace:*",
|
||||
"nodemailer": "^6",
|
||||
"react": "^18",
|
||||
"react-dom": "^18"
|
||||
@@ -41,6 +40,6 @@
|
||||
"pg": "^8.7.3",
|
||||
"prisma": "^3",
|
||||
"sqlite3": "^5.0.8",
|
||||
"typeorm": "0.3.7"
|
||||
"typeorm": "0.3.17"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ export const config = {
|
||||
Box({ clientId: process.env.AUTH_BOX_ID, clientSecret: process.env.AUTH_BOX_SECRET }),
|
||||
BoxyHQSAML({ clientId: process.env.AUTH_BOXYHQ_ID, clientSecret: process.env.AUTH_BOXYHQ_SECRET, issuer: process.env.AUTH_BOXYHQ_ISSUER }),
|
||||
Bungie({ clientId: process.env.AUTH_BUNGIE_ID, clientSecret: process.env.AUTH_BUNGIE_SECRET }),
|
||||
Cognito({ clientId: process.env.AUTH_COGNITO_ID, clientSecret: process.env.AUTH_COGNITO_SECRET }),
|
||||
Cognito({ clientId: process.env.AUTH_COGNITO_ID, clientSecret: process.env.AUTH_COGNITO_SECRET, issuer: process.env.AUTH_COGNITO_ISSUER }),
|
||||
Coinbase({ clientId: process.env.AUTH_COINBASE_ID, clientSecret: process.env.AUTH_COINBASE_SECRET }),
|
||||
Discord({ clientId: process.env.AUTH_DISCORD_ID, clientSecret: process.env.AUTH_DISCORD_SECRET }),
|
||||
Dropbox({ clientId: process.env.AUTH_DROPBOX_ID, clientSecret: process.env.AUTH_DROPBOX_SECRET }),
|
||||
@@ -182,6 +182,7 @@ declare global {
|
||||
AUTH_BUNGIE_ID: string
|
||||
AUTH_BUNGIE_SECRET: string
|
||||
AUTH_COGNITO_ID: string
|
||||
AUTH_COGNITO_ISSUER: string
|
||||
AUTH_COGNITO_SECRET: string
|
||||
AUTH_COINBASE_ID: string
|
||||
AUTH_COINBASE_SECRET: string
|
||||
|
||||
@@ -114,61 +114,9 @@ Yes! Check out the [TypeScript docs](/getting-started/typescript)
|
||||
|
||||
---
|
||||
|
||||
## Databases
|
||||
## Session strategies
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
<h3 style={{display: "inline-block"}}>What databases are supported by Auth.js?</h3>
|
||||
</summary>
|
||||
<p>
|
||||
|
||||
Auth.js can be used with MySQL, Postgres, MongoDB, SQLite and compatible databases (e.g. MariaDB, Amazon Aurora, Amazon DocumentDB…) or with no database.
|
||||
|
||||
It also provides an Adapter API which allows you to connect it to any database.
|
||||
|
||||
</p>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
<h3 style={{display: "inline-block"}}>What does Auth.js use databases for?</h3>
|
||||
</summary>
|
||||
<p>
|
||||
|
||||
Databases in Auth.js are used for persisting users, OAuth accounts, email sign-in tokens and sessions.
|
||||
|
||||
Specifying a database is optional if you don't need to persist user data or support email sign-in. If you don't specify a database then JSON Web Tokens will be enabled for session storage and used to store session data.
|
||||
|
||||
If you are using a database with Auth.js, you can still explicitly enable JSON Web Tokens for sessions (instead of using database sessions).
|
||||
|
||||
</p>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
<h3 style={{display: "inline-block"}}>Should I use a database?</h3>
|
||||
</summary>
|
||||
<p>
|
||||
|
||||
- Using Auth.js without a database works well for internal tools - where you need to control who can sign in, but when you do not need to create user accounts for them in your application.
|
||||
|
||||
- Using Auth.js with a database is usually a better approach for a consumer-facing application where you need to persist accounts (e.g. for billing, to contact customers, etc).
|
||||
|
||||
</p>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
<h3 style={{display: "inline-block"}}>What database should I use?</h3>
|
||||
</summary>
|
||||
<p>
|
||||
|
||||
Managed database solutions for MySQL, Postgres and MongoDB (and compatible databases) are well supported by cloud providers such as Amazon, Google, Microsoft and Atlas.
|
||||
|
||||
If you are deploying directly to a particular cloud platform you may also want to consider serverless database offerings they have (e.g. [Amazon Aurora Serverless on AWS](https://aws.amazon.com/rds/aurora/serverless/)).
|
||||
|
||||
</p>
|
||||
</details>
|
||||
Check out the [Session strategies page](/concepts/session-strategies) to learn more.
|
||||
|
||||
---
|
||||
|
||||
@@ -257,99 +205,3 @@ Ultimately if your request is not accepted or is not actively in development, yo
|
||||
</p>
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
## JSON Web Tokens
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
<h3>Does Auth.js use JSON Web Tokens?</h3>
|
||||
</summary>
|
||||
<p>
|
||||
|
||||
Auth.js by default uses JSON Web Tokens for saving the user's session. However, if you use a [database adapter](/guides/adapters/using-a-database-adapter), the database will be used to persist the user's session. You can force the usage of JWT when using a database [through the configuration options](/reference/configuration/auth-config#session). Since v4 all our JWTs are now encrypted by default with A256GCM.
|
||||
|
||||
</p>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
<h3>What are the advantages of JSON Web Tokens?</h3>
|
||||
</summary>
|
||||
<p>
|
||||
|
||||
JSON Web Tokens can be used for session tokens, but are also used for lots of other things, such as sending signed objects between services in authentication flows.
|
||||
|
||||
- Advantages of using a JWT as a session token include that they do not require a database to store sessions, this can be faster and cheaper to run and easier to scale.
|
||||
|
||||
- JSON Web Tokens in Auth.js are secured using cryptographic encryption (JWE) to store the included information directly in a JWT session token. You may then use the token to pass information between services and APIs on the same domain without having to contact a database to verify the included information.
|
||||
|
||||
- You can use JWT to securely store information you do not mind the client knowing even without encryption, as the JWT is stored in a server-readable-only cookie so data in the JWT is not accessible to third-party JavaScript running on your site.
|
||||
|
||||
</p>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
<h3>What are the disadvantages of JSON Web Tokens?</h3>
|
||||
</summary>
|
||||
<p>
|
||||
|
||||
- It's difficult to invalidate a JSON Web Token - doing so requires maintaining a server-side blocklist of the tokens (at least until they expire) and checking every token against the list every time a token is presented.
|
||||
|
||||
Shorter session expiry times are used when using JSON Web Tokens as session tokens to allow sessions to be invalidated sooner and simplify this problem.
|
||||
|
||||
Auth.js client includes advanced features to mitigate the downsides of using shorter session expiry times on the user experience, including automatic session token rotation, optionally sending keep-alive messages to prevent short-lived sessions from expiring if there is a window or tab opened, background re-validation, and automatic tab/window syncing that keeps sessions in sync across windows any time session state changes or a window or tab gains or loses focus.
|
||||
|
||||
- As with database session tokens, JSON Web Tokens are limited in the amount of data you can store in them. There is typically a limit of around 4096 bytes per cookie, though the exact limit varies between browsers, proxies and hosting services. If you want to support most browsers, then do not exceed 4096 bytes per cookie. If you want to save more data, you will need to persist your sessions in a database (Source: [browsercookielimits.iain.guru](http://browsercookielimits.iain.guru/))
|
||||
|
||||
The more data you try to store in a token and the more other cookies you set, the closer you will come to this limit. Auth.js uses cookie chunking so that cookies over the 4kb limit get split and reassembled upon parsing. However, since this data needs to be transmitted on every request, in case you wish to store more than ~4 KB of data you're probably at the point where you want to store a unique ID in the token and persist the data elsewhere (e.g. in a server-side key/value store).
|
||||
|
||||
- Data stored in an encrypted JSON Web Token (JWE) may be compromised at some point.
|
||||
|
||||
Even if appropriately configured, information stored in an encrypted JWT should not be assumed to be impossible to decrypt at some point - e.g. due to the discovery of a defect or advances in technology.
|
||||
|
||||
Avoid storing any data in a token that might be problematic if it were to be decrypted in the future.
|
||||
|
||||
- If you do not explicitly specify a secret for Auth.js, existing sessions will be invalidated any time your Auth.js configuration changes, as Auth.js will default to an auto-generated secret. Since v4 this only impacts development and generating a secret is required in production.
|
||||
|
||||
</p>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
<h3>Are JSON Web Tokens secure?</h3>
|
||||
</summary>
|
||||
<p>
|
||||
|
||||
By default, tokens are encrypted (JWE).
|
||||
|
||||
You can specify other valid algorithms - [as specified in RFC 7518](https://tools.ietf.org/html/rfc7517) - with either a secret (for symmetric encryption) or a public/private key pair (for asymmetric encryption).
|
||||
|
||||
Using explicit public/private keys for signing is strongly recommended.
|
||||
|
||||
</p>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
<h3>What signing and encryption standards does Auth.js support?</h3>
|
||||
</summary>
|
||||
<p>
|
||||
|
||||
Auth.js includes a largely complete implementation of JSON Object Signing and Encryption (JOSE):
|
||||
|
||||
- [RFC 7515 - JSON Web Signature (JWS)](https://tools.ietf.org/html/rfc7515)
|
||||
- [RFC 7516 - JSON Web Encryption (JWE)](https://tools.ietf.org/html/rfc7516)
|
||||
- [RFC 7517 - JSON Web Key (JWK)](https://tools.ietf.org/html/rfc7517)
|
||||
- [RFC 7518 - JSON Web Algorithms (JWA)](https://tools.ietf.org/html/rfc7518)
|
||||
- [RFC 7519 - JSON Web Token (JWT)](https://tools.ietf.org/html/rfc7519)
|
||||
|
||||
This incorporates support for:
|
||||
|
||||
- [RFC 7638 - JSON Web Key Thumbprint](https://tools.ietf.org/html/rfc7638)
|
||||
- [RFC 7787 - JSON JWS Unencoded Payload Option](https://tools.ietf.org/html/rfc7797)
|
||||
- [RFC 8037 - CFRG Elliptic Curve ECDH and Signatures](https://tools.ietf.org/html/rfc8037)
|
||||
|
||||
</p>
|
||||
</details>
|
||||
|
||||
49
docs/docs/concepts/session-strategies.md
Normal file
49
docs/docs/concepts/session-strategies.md
Normal file
@@ -0,0 +1,49 @@
|
||||
---
|
||||
title: Session strategies
|
||||
---
|
||||
|
||||
When a user logs into your application, you usually want them to not need to log in for some time. This is called a session. Auth.js libraries support different session strategies, which are described below.
|
||||
|
||||
:::note
|
||||
Both strategies have advantages and disadvantages which you have to evaluate based on your requirements
|
||||
:::
|
||||
|
||||
Check out the [`session.strategy`](/reference/core#session) option to see how you can configure the session strategy of your Auth.js library.
|
||||
|
||||
## JWT
|
||||
|
||||
Auth.js libraries can create sessions using [JSON Web Tokens (JWT)](https://datatracker.ietf.org/doc/html/rfc7519). This is the default session strategy for Auth.js libraries. When a user signs in, a JWT is created in a `HttpOnly` cookie. Making the cookie HttpOnly prevents JavaScript from accessing it client-side (`document.cookie`), which makes it harder for attackers to steal the value. In addition, the JWT is encrypted with a secret key only known to the server. So even if an attacker were to steal the JWT from the cookie, they would not be able to decrypt it. Combined with a short expiration time, this makes JWTs a secure way to create sessions.
|
||||
|
||||
When a user signs out, the JWT is deleted from the cookies, and the session is destroyed.
|
||||
|
||||
### Advantages
|
||||
|
||||
- JWTs as a session do not require a database to store sessions, this can be faster and cheaper to run and easier to scale.
|
||||
- Retrieving a JWT session can always run on the Edge.
|
||||
- Using this strategy requires fewer resources as you don't need to manage an extra database/service.
|
||||
- You may then use the created token to pass information between services and APIs on the same domain without having to contact a database to verify the included information.
|
||||
- You can use JWT to securely store information without exposing it to third-party JavaScript running on your site.
|
||||
|
||||
### Disadvantages
|
||||
|
||||
- Expiring a JSON Web Token before its encoded expiry is not possible - doing so requires maintaining a server-side blocklist of invalidated tokens (at least until they truly expire) and checking every token against the list every time a token is presented. Auth.js **will** destroy the cookie, but if the user has the JWT saved elsewhere, it will be valid (the server will accept it) until it expires. (Shorter session expiry times are used when using JSON Web Tokens as session tokens to allow sessions to be invalidated sooner and simplify this problem.)
|
||||
- Auth.js clients enable advanced features to mitigate the downsides of using shorter session expiry times on the user experience, including automatic session token rotation, optionally sending keep-alive messages (session polling) to prevent short-lived sessions from expiring if there is a window or tab open, background re-validation, and automatic tab/window syncing that keeps sessions in sync across windows any time session state changes or a window or tab gains or loses focus.
|
||||
- As with database session tokens, JSON Web Tokens are limited in the amount of data you can store in them. There is typically a limit of around 4096 bytes per cookie, though the exact limit varies between browsers. The more data you try to store in a token and the more other cookies you set, the closer you will come to this limit. Auth.js libraries implement session cookie chunking so that cookies over the 4kb limit will get split and reassembled upon parsing. However since this data needs to be transmitted on every request, you need to be aware of how much data you want to transfer using this technique.
|
||||
- Even if appropriately configured, information stored in an encrypted JWT should not be assumed to be impossible to decrypt at some point - e.g. due to the discovery of a defect or advances in technology. Data stored in an encrypted JSON Web Token (JWE) _may_ be compromised at some point. The recommendation is to generate a [secret](/reference/core#secret) with high entropy.
|
||||
|
||||
## Database
|
||||
|
||||
Alternatively, to a JWT session strategy, Auth.js libraries also support database sessions. In this case, instead of saving a JWT with user data after signing in, Auth.js libraries will create a session in your database. A session ID is then saved in a `HttpOnly` cookie. This is similar to the JWT session strategy, but instead of saving the user data in the cookie, it only stores an obscure value pointing to the session in the database. So whenever you will try to access the user session, you will query the database for the data.
|
||||
|
||||
When a user signs out, the session is deleted from the database, and the session ID is deleted from the cookies.
|
||||
|
||||
### Advantages
|
||||
|
||||
- Database sessions can be at any time modified server-side, so you can implement features that might be more difficult - but not impossible - using the JWT strategy, etc.: "sign out everywhere", or limiting concurrent logins
|
||||
- Auth.js has no opinion on the type of database you are using, we have a big list of [official database adapters](/reference/adapters), but you can [implement your own](guides/adapters/creating-a-database-adapter) as well
|
||||
|
||||
### Disadvantages
|
||||
|
||||
- Database sessions need a roundtrip to your database, so they might be slower on scale unless your connections/databases are accommodated for it
|
||||
- Many database adapters are not yet compatible with the Edge, which would allow faster and cheaper session retrieval
|
||||
- Setting up a database takes more effort and requires extra services to manage compared to the stateless JWT strategy
|
||||
@@ -5,7 +5,7 @@ displayed_sidebar: null
|
||||
|
||||
## Core team
|
||||
|
||||
Without these people, the project could not have become one of the most used authentication library in its category.
|
||||
Without these people, the project could not have become one of the most used authentication libraries in its category.
|
||||
|
||||
- [Balázs Orbán](https://github.com/balazsorban44) - **Lead Maintainer**
|
||||
- [Thang Vu](https://github.com/ThangHuuVu) - Maintainer (Core)
|
||||
@@ -14,8 +14,9 @@ Without these people, the project could not have become one of the most used aut
|
||||
|
||||
## Special thanks
|
||||
|
||||
Special thanks to Lori Karikari for creating most of the original provider configurations to Fredrik Pettersen for creating the original Prisma Adapter, to Gerald Nolan for adding support for Sign in with Apple, and to Jefferson Bledsoe for working on original testing automations.
|
||||
Special thanks to Filip Skokan for their feedback and high-quality OAuth libraries that we build on, Lori Karikari for creating most of the original provider configurations, Fredrik Pettersen for creating the original Prisma Adapter, Gerald Nolan for adding support for Sign in with Apple, and Jefferson Bledsoe for working on original testing automation.
|
||||
|
||||
- [Filip Skokan](https://github.com/panva)
|
||||
- [Lori Karikari](https://github.com/LoriKarikari)
|
||||
- [Fredrik Pettersen](https://github.com/Fumler)
|
||||
- [Gerald Nolan](https://github.com/geraldnolan)
|
||||
@@ -25,7 +26,7 @@ Special thanks to Lori Karikari for creating most of the original provider confi
|
||||
|
||||
Auth.js as it exists today has been possible thanks to the work of many individual contributors.
|
||||
|
||||
Thank you to the [dozens of individual contributors](https://github.com/nextauthjs/next-auth/graphs/contributors) who have help shaped Auth.js.
|
||||
Thank you to the [dozens of individual contributors](https://github.com/nextauthjs/next-auth/graphs/contributors) who have helped shape Auth.js.
|
||||
|
||||
## Open Collective
|
||||
|
||||
@@ -35,8 +36,10 @@ More information can be found at: https://opencollective.com/nextauth
|
||||
|
||||
## History
|
||||
|
||||
- Auth.js was originally developed by <a href="https://github.com/iaincollins">Iain Collins</a> in 2016 for Next.js.
|
||||
- NextAuth.js was originally developed by <a href="https://github.com/iaincollins">Iain Collins</a> in 2016 for Next.js.
|
||||
|
||||
- In 2020, Auth.js was rebuilt from the ground up to support Serverless, with support for MySQL, Postgres and MongoDB, JSON Web Tokens and built in support for over a dozen authentication providers.
|
||||
- In 2020, NextAuth.js was rebuilt from the ground up to support Serverless, with support for MySQL, Postgres and MongoDB, JSON Web Tokens and built-in support for over a dozen authentication providers.
|
||||
|
||||
- In 2021, efforts have started to move Auth.js to other frameworks and to support as many databases and providers as possible.
|
||||
- In 2021, efforts have started to move NextAuth.js to other frameworks and to support as many databases and providers as possible.
|
||||
|
||||
- In 2022, Auth.js was born which separated the core authentication logic from the Next.js framework and added support for any new frameworks.
|
||||
@@ -17,7 +17,7 @@ Continue to our tutorials to see how to use Auth.js for authentication:
|
||||
- [Setup with magic links](/getting-started/email-tutorial)
|
||||
- [Integrating with external auth](/getting-started/credentials-tutorial)
|
||||
|
||||
### Battery included
|
||||
### Features
|
||||
|
||||
- Built in support for 60+ popular services (Google, Facebook, Auth0, Apple…)
|
||||
- Built-in email/password-less/magic link
|
||||
|
||||
@@ -36,10 +36,6 @@ This tutorial assumes you have a Next.js application set up. If you don't, you c
|
||||
npm install next-auth
|
||||
```
|
||||
|
||||
:::info
|
||||
We are working on a new release of `next-auth` that will make it easier to set up Auth.js with Next.js. You can follow the development [on this PR](https://github.com/nextauthjs/next-auth/pull/7443)
|
||||
:::
|
||||
|
||||
### Creating the server config
|
||||
|
||||
Create the following [API route](https://nextjs.org/docs/api-routes/dynamic-api-routes#catch-all-api-routes) file. This route contains the necessary configuration for NextAuth.js, as well as the dynamic route handler:
|
||||
@@ -286,23 +282,24 @@ You can use the `$page.data.session` variable from anywhere on your page. Learn
|
||||
To protect your API Routes (blocking unauthorized access to resources), you can use `locals.getSessions()` just like in the layouts file to know whether a session exists or not:
|
||||
|
||||
```ts title="routes/api/movies/+server.ts"
|
||||
import { json, error } from "@sveltejs/kit";
|
||||
import type { RequestEvent } from "./$types";
|
||||
import { json, error } from "@sveltejs/kit"
|
||||
import type { RequestEvent } from "./$types"
|
||||
|
||||
export async function GET({ locals }: RequestEvent) {
|
||||
const session = await locals.getSession()
|
||||
if (!session?.user) {
|
||||
throw error(401, "You must sign in to view movies.");
|
||||
throw error(401, "You must sign in to view movies.")
|
||||
}
|
||||
|
||||
return json({
|
||||
movies: [
|
||||
{ title: "Alien vs Predator", id: 1 },
|
||||
{ title: "Reservoir Dogs", id: 2 },
|
||||
]
|
||||
],
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="solidstart" label="SolidStart">
|
||||
TODO: SolidStart
|
||||
@@ -451,7 +448,6 @@ Great! We're now ready to run our application locally. Start the Svelte app by r
|
||||
npm run vite dev
|
||||
```
|
||||
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="solidstart" label="SolidStart">
|
||||
TODO SolidStart
|
||||
|
||||
@@ -2,19 +2,16 @@
|
||||
title: Creating a database adapter
|
||||
---
|
||||
|
||||
Using a custom adapter you can connect to any database back-end or even several different databases. Official adapters created and maintained by our community can be found in the [adapters](https://github.com/nextauthjs/next-auth/tree/main/packages) packages. Feel free to add a custom adapter from your project to the repository, or even become a maintainer of a certain adapter. Custom adapters can still be created and used in a project without being added to the repository.
|
||||
Custom adapters allow you to integrate with any (even multiple) database/back-end service, even if we don't have an [official package](https://github.com/nextauthjs/next-auth/tree/main/packages) available yet. The only requirement is that your database can support the [models](/reference/adapters#models) that Auth.js expects.
|
||||
|
||||
## How to create an adapter
|
||||
|
||||
For more information about the data these methods need to manage see [models](/reference/adapters#models).
|
||||
|
||||
_See the code below for practical example._
|
||||
_See the code below for a practical example._
|
||||
|
||||
### Example code
|
||||
|
||||
```ts
|
||||
/** @return { import("next-auth/adapters").Adapter } */
|
||||
export default function MyAdapter(client, options = {}) {
|
||||
import type { Adapter } from '@auth/core/adapters'
|
||||
|
||||
export function MyAdapter(client, options = {}): Adapter {
|
||||
return {
|
||||
async createUser(user) {
|
||||
return
|
||||
@@ -64,7 +61,7 @@ export default function MyAdapter(client, options = {}) {
|
||||
|
||||
### Required methods
|
||||
|
||||
These methods are required for all sign in flows:
|
||||
These methods are required for all sign-in flows:
|
||||
|
||||
- `createUser`
|
||||
- `getUser`
|
||||
@@ -77,7 +74,7 @@ These methods are required for all sign in flows:
|
||||
- `deleteSession`
|
||||
- `updateUser`
|
||||
|
||||
These methods are required to support email / passwordless sign in:
|
||||
These methods are required to support email / passwordless sign-in:
|
||||
|
||||
- `createVerificationToken`
|
||||
- `useVerificationToken`
|
||||
@@ -88,3 +85,8 @@ These methods will be required in a future release, but are not yet invoked:
|
||||
|
||||
- `deleteUser`
|
||||
- `unlinkAccount`
|
||||
|
||||
### Useful resources
|
||||
|
||||
- [Official adapters' source code](https://github.com/nextauthjs/next-auth/tree/main/packages)
|
||||
- [`Adapter` interface](/reference/core/adapters#adapter)
|
||||
@@ -5,7 +5,7 @@ title: Using a database adapter
|
||||
An **Adapter** in Auth.js connects your application to whatever database or backend system you want to use to store data for users, their accounts, sessions, etc. Adapters are optional, unless you need to persist user information in your own database, or you want to implement certain flows. The [Email Provider](/getting-started/email-tutorial) requires an adapter to be able to save [Verification Tokens](/reference/adapters#verification-token).
|
||||
|
||||
:::tip
|
||||
When using a database, you can still use JWT for session handling for fast access. See the [`session.strategy`](/reference/configuration/auth-config#session) option. Read about the trade-offs of JWT in the [FAQ](/concepts/faq#json-web-tokens).
|
||||
When using a database, you can still use JWT for session handling for fast access. Learn more about [`session strategies`](/concepts/session-strategies) and their trade-offs.
|
||||
:::
|
||||
|
||||
We have a list of official adapters that are distributed as their own packages under the `@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):
|
||||
|
||||
@@ -5,6 +5,10 @@ title: Overview
|
||||
Using an Auth.js / NextAuth.js adapter you can connect to any database service or even several different services at the same time. The following listed official adapters are created and maintained by the community:
|
||||
|
||||
<div class="adapter-card-list">
|
||||
<a href="/reference/adapter/azure-tables" class="adapter-card">
|
||||
<img src="/img/adapters/azure-tables.svg" width="40" />
|
||||
<h4 class="adapter-card__title">Azure Table Storage Adapter</h4>
|
||||
</a>
|
||||
<a href="/reference/adapter/d1" class="adapter-card">
|
||||
<img src="/img/adapters/d1.svg" width="40" />
|
||||
<h4 class="adapter-card__title">D1 Adapter</h4>
|
||||
@@ -69,6 +73,10 @@ Using an Auth.js / NextAuth.js adapter you can connect to any database service o
|
||||
<img src="/img/adapters/supabase.svg" width="25" />
|
||||
<h4 class="adapter-card__title">Supabase Adapter</h4>
|
||||
</a>
|
||||
<a href="/reference/adapter/surrealdb" class="adapter-card">
|
||||
<img src="/img/adapters/surreal.png" width="25" />
|
||||
<h4 class="adapter-card__title">SurrealDB Adapter</h4>
|
||||
</a>
|
||||
<a href="/reference/adapter/typeorm" class="adapter-card">
|
||||
<img src="/img/adapters/typeorm.png" width="30" />
|
||||
<h4 class="adapter-card__title">TypeORM Adapter</h4>
|
||||
@@ -205,3 +213,7 @@ Auth.js / NextAuth.js uses `camelCase` for its database rows while respecting th
|
||||
## TypeScript
|
||||
|
||||
Check out the [`@auth/core/adapters` API Reference](/reference/core/adapters) documentation.
|
||||
|
||||
## Create a custom adapter
|
||||
|
||||
If you are using a database that we don't have an official adapter for, you can check out the [Creating a database adapter](/guides/adapters/creating-a-database-adapter) guide.
|
||||
@@ -6,10 +6,11 @@ This section of the documentation contains the API reference for all the officia
|
||||
|
||||
## Roadmap
|
||||
|
||||
Here are the _state_ of planned and released packages under the `@auth/*` scope. This is not an exhaustive list, but the set of packages that we would like to focus on, to begin with.
|
||||
Here are the _state_ of planned and released packages under the `@auth/*` and `@next-auth/*` scope, as well as `next-auth`. This is not an exhaustive list, but the set of packages that we would like to focus on, to begin with.
|
||||
|
||||
| Feature | Status |
|
||||
| ---------------------- | -------- |
|
||||
| `next-auth` | Release (stable). See [docs](https://next-auth.js.org) |
|
||||
| `@auth/*-adapter` | Released (stable). Fully compatible with `next-auth` and all `@auth/*` libraries. |
|
||||
| `@next-auth/*-adapter` | Maintenance has stopped. Update to `@auth/*-adapter`. See above. |
|
||||
| `@auth/core` | Released (experimental). |
|
||||
|
||||
@@ -30,14 +30,28 @@ function typedocAdapter(name) {
|
||||
entryPoints: [`../packages/adapter-${slug}/src/index.ts`],
|
||||
tsconfig: `../packages/adapter-${slug}/tsconfig.json`,
|
||||
out: `reference/adapter/${slug}`,
|
||||
sidebar: {
|
||||
indexLabel: name,
|
||||
},
|
||||
...typedocConfig,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
function typedocFramework(pkgDir, entrypoints) {
|
||||
const id = pkgDir.replace("frameworks-", "")
|
||||
return [
|
||||
"docusaurus-plugin-typedoc",
|
||||
{
|
||||
...typedocConfig,
|
||||
id: id,
|
||||
plugin: [require.resolve("./typedoc-mdn-links")],
|
||||
watch: process.env.TYPEDOC_WATCH,
|
||||
entryPoints: entrypoints.map((e) => `../packages/${pkgDir}/src/${e}`),
|
||||
tsconfig: `../packages/${pkgDir}/tsconfig.json`,
|
||||
out: `reference/${id === "next-auth" ? "nextjs" : id}`,
|
||||
skipIndexPage: true,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
/** @type {import("@docusaurus/types").Config} */
|
||||
const docusaurusConfig = {
|
||||
markdown: {
|
||||
@@ -225,12 +239,15 @@ const docusaurusConfig = {
|
||||
*/
|
||||
editUrl({ docPath }) {
|
||||
// TODO: support other packages, fix directory links like "providers"
|
||||
const base = "https://github.com/nextauthjs/next-auth/edit/main/packages"
|
||||
if (docPath.includes("reference/core")) {
|
||||
const file = docPath.split("reference/core/")[1].replace(".md", ".ts").replace("_", "/")
|
||||
const base = `https://github.com/nextauthjs/next-auth/edit/main/packages/core/src/${file}`
|
||||
return base
|
||||
return `${base}/core/src/${file}`
|
||||
} else if (docPath.includes("reference/adapter/")) {
|
||||
const file = docPath.split("reference/adapter/")[1].replace("index.md", "src/index.ts")
|
||||
return `${base}/adapter-${file}`
|
||||
}
|
||||
return "https://github.com/nextauthjs/next-auth/edit/main/docs"
|
||||
return `https://github.com/nextauthjs/next-auth/edit/main/docs/docs/${docPath}`
|
||||
},
|
||||
lastVersion: "current",
|
||||
showLastUpdateAuthor: true,
|
||||
@@ -241,6 +258,11 @@ const docusaurusConfig = {
|
||||
label: "experimental",
|
||||
},
|
||||
},
|
||||
async sidebarItemsGenerator({ defaultSidebarItemsGenerator, ...args }) {
|
||||
const sidebarItems = await defaultSidebarItemsGenerator(args)
|
||||
const sidebarIdsToOmit = ["reference/core/index", "reference/sveltekit/index", "reference/solidstart/index"]
|
||||
return sidebarItems.filter((sidebarItem) => !sidebarIdsToOmit.includes(sidebarItem.id))
|
||||
},
|
||||
},
|
||||
theme: {
|
||||
customCss: require.resolve("./src/css/index.css"),
|
||||
@@ -249,39 +271,12 @@ const docusaurusConfig = {
|
||||
],
|
||||
],
|
||||
plugins: [
|
||||
[
|
||||
"docusaurus-plugin-typedoc",
|
||||
{
|
||||
...typedocConfig,
|
||||
id: "core",
|
||||
plugin: [require.resolve("./typedoc-mdn-links")],
|
||||
watch: process.env.TYPEDOC_WATCH,
|
||||
entryPoints: ["index.ts", "adapters.ts", "errors.ts", "jwt.ts", "types.ts"].map((e) => `${coreSrc}/${e}`).concat(providers),
|
||||
tsconfig: "../packages/core/tsconfig.json",
|
||||
out: "reference/core",
|
||||
sidebar: {
|
||||
indexLabel: "index",
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
"docusaurus-plugin-typedoc",
|
||||
{
|
||||
...typedocConfig,
|
||||
id: "sveltekit",
|
||||
plugin: [require.resolve("./typedoc-mdn-links")],
|
||||
watch: process.env.TYPEDOC_WATCH,
|
||||
entryPoints: ["index.ts", "client.ts"].map((e) => `../packages/frameworks-sveltekit/src/lib/${e}`),
|
||||
tsconfig: "../packages/frameworks-sveltekit/tsconfig.json",
|
||||
out: "reference/sveltekit",
|
||||
sidebar: {
|
||||
indexLabel: "index",
|
||||
},
|
||||
},
|
||||
],
|
||||
typedocFramework("core", ["index.ts", "adapters.ts", "errors.ts", "jwt.ts", "types.ts"]),
|
||||
typedocFramework("frameworks-sveltekit", ["lib/index.ts", "lib/client.ts"]),
|
||||
...(process.env.TYPEDOC_SKIP_ADAPTERS
|
||||
? []
|
||||
: [
|
||||
typedocAdapter("Azure Tables"),
|
||||
typedocAdapter("D1"),
|
||||
typedocAdapter("EdgeDb"),
|
||||
typedocAdapter("Dgraph"),
|
||||
@@ -299,6 +294,7 @@ const docusaurusConfig = {
|
||||
typedocAdapter("TypeORM"),
|
||||
typedocAdapter("Sequelize"),
|
||||
typedocAdapter("Supabase"),
|
||||
typedocAdapter("SurrealDB"),
|
||||
typedocAdapter("Upstash Redis"),
|
||||
typedocAdapter("Xata"),
|
||||
]),
|
||||
|
||||
@@ -34,9 +34,9 @@
|
||||
"@docusaurus/theme-common": "2.4.1",
|
||||
"@docusaurus/theme-mermaid": "2.4.1",
|
||||
"@docusaurus/types": "2.4.1",
|
||||
"docusaurus-plugin-typedoc": "1.0.0-next.5",
|
||||
"docusaurus-plugin-typedoc": "1.0.0-next.13",
|
||||
"typedoc": "^0.24.8",
|
||||
"typedoc-plugin-markdown": "4.0.0-next.6"
|
||||
"typedoc-plugin-markdown": "4.0.0-next.16"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
|
||||
@@ -46,6 +46,8 @@ module.exports = {
|
||||
label: "Database Adapters",
|
||||
link: { type: "doc", id: "reference/adapters/index" },
|
||||
items: [
|
||||
{ type: "doc", id: "reference/adapter/azure-tables/index" },
|
||||
{ type: "doc", id: "reference/adapter/d1/index" },
|
||||
{ type: "doc", id: "reference/adapter/edgedb/index" },
|
||||
{ type: "doc", id: "reference/adapter/dgraph/index" },
|
||||
{ type: "doc", id: "reference/adapter/drizzle/index" },
|
||||
@@ -61,6 +63,7 @@ module.exports = {
|
||||
{ type: "doc", id: "reference/adapter/prisma/index" },
|
||||
{ type: "doc", id: "reference/adapter/sequelize/index" },
|
||||
{ type: "doc", id: "reference/adapter/supabase/index" },
|
||||
{ type: "doc", id: "reference/adapter/surrealdb/index" },
|
||||
{ type: "doc", id: "reference/adapter/typeorm/index" },
|
||||
{ type: "doc", id: "reference/adapter/upstash-redis/index" },
|
||||
{ type: "doc", id: "reference/adapter/xata/index" },
|
||||
|
||||
1
docs/static/img/adapters/azure-tables.svg
vendored
Normal file
1
docs/static/img/adapters/azure-tables.svg
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 91 81" fill="#fff" fill-rule="evenodd" stroke="#000" stroke-linecap="round" stroke-linejoin="round"><use xlink:href="#A" x=".5" y=".5"/><symbol id="A" overflow="visible"><path d="M67.678 0H22.559L0 40l22.559 40h44.881L90 40 67.678 0zM42.982 20.976H53.43v10.732H42.982V20.976zm0 13.415H53.43v10.732H42.982V34.39zm0 13.414H53.43v10.732H42.982V47.805zM29.921 20.976h10.448v10.732H29.921V20.976zm0 13.415h10.448v10.732H29.921V34.39zm0 13.414h10.448v10.732H29.921V47.805zM66.254 64.39H23.747V20.732h2.849v40.732h0 0 39.657v2.927zm.237-5.854H56.042V47.805h10.448v10.732zm0-13.414H56.042V34.39h10.448v10.732zm0-13.415H56.042V20.976h10.448v10.732z" fill="#0078d7" stroke="none"/></symbol></svg>
|
||||
|
After Width: | Height: | Size: 782 B |
BIN
docs/static/img/adapters/surreal.png
vendored
Normal file
BIN
docs/static/img/adapters/surreal.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 121 KiB |
@@ -3,14 +3,15 @@
|
||||
"cleanOutputDir": true,
|
||||
"disableSources": true,
|
||||
"excludeExternals": true,
|
||||
"excludeGroups": true,
|
||||
"excludeInternal": true,
|
||||
"excludeNotDocumented": true,
|
||||
"excludePrivate": true,
|
||||
"excludeProtected": true,
|
||||
"hideHierarchy": true,
|
||||
"gitRevision": "main",
|
||||
"groupByReflections": false,
|
||||
"hideBreadcrumbs": true,
|
||||
"hideHierarchy": true,
|
||||
"hideKindPrefix": true,
|
||||
"hideGenerator": true,
|
||||
"kindSortOrder": [
|
||||
"Function",
|
||||
@@ -36,8 +37,9 @@
|
||||
"GetSignature",
|
||||
"SetSignature"
|
||||
],
|
||||
"outputFileStrategy": "modules",
|
||||
"readme": "none",
|
||||
"reflectionsWithOwnFile": "none",
|
||||
"skipErrorChecking": true,
|
||||
"sort": [
|
||||
"kind",
|
||||
"static-first",
|
||||
|
||||
@@ -67,6 +67,16 @@
|
||||
"has": [{ "type": "host", "value": "solid-start.authjs.dev" }],
|
||||
"destination": "https://authjs.dev/reference/solid-start"
|
||||
},
|
||||
{
|
||||
"source": "/:path(.*)",
|
||||
"has": [{ "type": "host", "value": "nextjs.authjs.dev" }],
|
||||
"destination": "https://authjs.dev/reference/nextjs"
|
||||
},
|
||||
{
|
||||
"source": "/v5",
|
||||
"has": [{ "type": "host", "value": "nextjs.authjs.dev" }],
|
||||
"destination": "https://authjs.dev/guides/upgrade-to-v5"
|
||||
},
|
||||
{
|
||||
"source": "/:path(.*)",
|
||||
"has": [{ "type": "host", "value": "errors.authjs.dev" }],
|
||||
|
||||
@@ -26,9 +26,9 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@actions/core": "^1.10.0",
|
||||
"@balazsorban/monorepo-release": "0.1.8",
|
||||
"@balazsorban/monorepo-release": "0.2.4",
|
||||
"@types/jest": "^28.1.3",
|
||||
"@types/node": "^17.0.25",
|
||||
"@types/node": "^18.15.11",
|
||||
"@typescript-eslint/eslint-plugin": "5.47.0",
|
||||
"@typescript-eslint/parser": "5.47.0",
|
||||
"eslint": "8.30.0",
|
||||
@@ -47,7 +47,7 @@
|
||||
"typescript": "5.2.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^16.13.0 || ^18.12.0"
|
||||
"node": "^18.18.0 || ^20.8.0"
|
||||
},
|
||||
"packageManager": "pnpm@8.7.1",
|
||||
"funding": [
|
||||
@@ -62,7 +62,7 @@
|
||||
],
|
||||
"pnpm": {
|
||||
"patchedDependencies": {
|
||||
"@balazsorban/monorepo-release@0.1.8": "patches/@balazsorban__monorepo-release@0.1.8.patch"
|
||||
"@balazsorban/monorepo-release@0.2.4": "patches/@balazsorban__monorepo-release@0.2.4.patch"
|
||||
}
|
||||
},
|
||||
"eslintIgnore": [
|
||||
|
||||
26
packages/adapter-azure-tables/README.md
Normal file
26
packages/adapter-azure-tables/README.md
Normal file
@@ -0,0 +1,26 @@
|
||||
<p align="center">
|
||||
<br/>
|
||||
<a href="https://authjs.dev" target="_blank">
|
||||
<img height="64px" src="https://authjs.dev/img/logo/logo-sm.png" />
|
||||
</a>
|
||||
<a href="https://azure.microsoft.com/en-us/products/storage/tables" target="_blank">
|
||||
<img height="64px" src="https://authjs.dev/img/adapters/azure-tables.svg"/>
|
||||
</a>
|
||||
<h3 align="center"><b>Azure Table Storage Adapter</b> - NextAuth.js / Auth.js</a></h3>
|
||||
<p align="center" style="align: center;">
|
||||
<a href="https://npm.im/@auth/azure-tables-adapter">
|
||||
<img src="https://img.shields.io/badge/TypeScript-blue?style=flat-square" alt="TypeScript" />
|
||||
</a>
|
||||
<a href="https://npm.im/@auth/azure-tables-adapter">
|
||||
<img alt="npm" src="https://img.shields.io/npm/v/@auth/azure-tables-adapter?color=green&label=@auth/azure-tables-adapter&style=flat-square">
|
||||
</a>
|
||||
<a href="https://www.npmtrends.com/@auth/azure-tables-adapter">
|
||||
<img src="https://img.shields.io/npm/dm/@auth/azure-tables-adapter?label=%20downloads&style=flat-square" alt="Downloads" />
|
||||
</a>
|
||||
<a href="https://github.com/nextauthjs/next-auth/stargazers">
|
||||
<img src="https://img.shields.io/github/stars/nextauthjs/next-auth?style=flat-square" alt="Github Stars" />
|
||||
</a>
|
||||
</p>
|
||||
</p>
|
||||
|
||||
---
|
||||
59
packages/adapter-azure-tables/package.json
Normal file
59
packages/adapter-azure-tables/package.json
Normal file
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"name": "@auth/azure-tables-adapter",
|
||||
"version": "0.1.1",
|
||||
"description": "Azure Tables Storage adapter for next-auth.",
|
||||
"homepage": "https://authjs.dev",
|
||||
"repository": "https://github.com/nextauthjs/next-auth",
|
||||
"bugs": {
|
||||
"url": "https://github.com/nextauthjs/next-auth/issues"
|
||||
},
|
||||
"author": "Nikita Dmitrijev <nikitadmitry@gmail.com>",
|
||||
"contributors": [
|
||||
"Thang Huu Vu <hi@thvu.dev>"
|
||||
],
|
||||
"license": "ISC",
|
||||
"keywords": [
|
||||
"next-auth",
|
||||
"next.js",
|
||||
"oauth",
|
||||
"azure-tables",
|
||||
"adapter"
|
||||
],
|
||||
"type": "module",
|
||||
"types": "./index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./index.d.ts",
|
||||
"import": "./index.js"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"*.d.ts*",
|
||||
"*.js",
|
||||
"src"
|
||||
],
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "./tests/test.sh",
|
||||
"test:watch": "./tests/test.sh -w",
|
||||
"build": "tsc"
|
||||
},
|
||||
"dependencies": {
|
||||
"@auth/core": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@azure/data-tables": "^13.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@auth/adapter-test": "workspace:*",
|
||||
"@auth/tsconfig": "workspace:*",
|
||||
"jest": "^27.4.3",
|
||||
"@azure/data-tables": "^13.2.1"
|
||||
},
|
||||
"jest": {
|
||||
"preset": "@auth/adapter-test/jest"
|
||||
}
|
||||
}
|
||||
303
packages/adapter-azure-tables/src/index.ts
Normal file
303
packages/adapter-azure-tables/src/index.ts
Normal file
@@ -0,0 +1,303 @@
|
||||
/**
|
||||
* <div style={{display: "flex", justifyContent: "space-between", alignItems: "center", padding: 16}}>
|
||||
* <p style={{fontWeight: "normal"}}>An official <a href="https://azure.microsoft.com/en-us/products/storage/tables">Azure Table Storage</a> adapter for Auth.js / NextAuth.js.</p>
|
||||
* <a href="https://azure.microsoft.com/en-us/products/storage/tables">
|
||||
* <img style={{display: "block"}} src="/img/adapters/azure-tables.svg" width="48" />
|
||||
* </a>
|
||||
* </div>
|
||||
*
|
||||
* ## Installation
|
||||
*
|
||||
* ```bash npm2yarn2pnpm
|
||||
* npm install next-auth @auth/azure-tables-adapter
|
||||
* ```
|
||||
*
|
||||
* @module @auth/azure-tables-adapter
|
||||
*/
|
||||
|
||||
import type {
|
||||
Adapter,
|
||||
AdapterUser,
|
||||
AdapterAccount,
|
||||
AdapterSession,
|
||||
VerificationToken,
|
||||
} from "@auth/core/adapters"
|
||||
import {
|
||||
GetTableEntityResponse,
|
||||
TableClient,
|
||||
TableEntityResult,
|
||||
} from "@azure/data-tables"
|
||||
|
||||
globalThis.crypto ??= require("node:crypto").webcrypto
|
||||
|
||||
export const keys = {
|
||||
user: "user",
|
||||
userByEmail: "userByEmail",
|
||||
account: "account",
|
||||
accountByUserId: "accountByUserId",
|
||||
session: "session",
|
||||
sessionByUserId: "sessionByUserId",
|
||||
verificationToken: "verificationToken",
|
||||
}
|
||||
|
||||
export function withoutKeys<T>(
|
||||
entity: GetTableEntityResponse<TableEntityResult<T>>
|
||||
): T {
|
||||
delete entity.partitionKey
|
||||
delete entity.rowKey
|
||||
// @ts-expect-error
|
||||
delete entity.etag
|
||||
delete entity.timestamp
|
||||
// @ts-expect-error
|
||||
delete entity["odata.metadata"]
|
||||
|
||||
return entity
|
||||
}
|
||||
/**
|
||||
*
|
||||
* 1. Create a table for authentication data, `auth` in the example below.
|
||||
*
|
||||
* ```js title="auth.ts"
|
||||
* import type { AuthConfig } from "next-auth"
|
||||
* import { TableStorageAdapter } from "@next-auth/azure-tables-adapter"
|
||||
* import { AzureNamedKeyCredential, TableClient } from "@azure/data-tables"
|
||||
*
|
||||
* const credential = new AzureNamedKeyCredential(
|
||||
* process.env.AZURE_ACCOUNT,
|
||||
* process.env.AZURE_ACCESS_KEY
|
||||
* )
|
||||
* const authClient = new TableClient(
|
||||
* process.env.AZURE_TABLES_ENDPOINT,
|
||||
* "auth",
|
||||
* credential
|
||||
* )
|
||||
*
|
||||
* // For more information on each option (and a full list of options) go to
|
||||
* // https://authjs.dev/reference/configuration/auth-options
|
||||
* export default const authConfig = {
|
||||
* // https://authjs.dev/reference/providers/oauth-builtin
|
||||
* providers: [
|
||||
* // ...
|
||||
* ],
|
||||
* adapter: TableStorageAdapter(authClient),
|
||||
* // ...
|
||||
* } satisfies AuthConfig
|
||||
* ```
|
||||
*
|
||||
* Environment variable are as follows:
|
||||
*
|
||||
* ```
|
||||
* AZURE_ACCOUNT=storageaccountname
|
||||
* AZURE_ACCESS_KEY=longRandomKey
|
||||
* AZURE_TABLES_ENDPOINT=https://$AZURE_ACCOUNT.table.core.windows.net
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
export const TableStorageAdapter = (client: TableClient): Adapter => {
|
||||
return {
|
||||
async createUser(user) {
|
||||
const id = crypto.randomUUID()
|
||||
const newUser = {
|
||||
...user,
|
||||
id,
|
||||
}
|
||||
await Promise.all([
|
||||
client.createEntity({
|
||||
...newUser,
|
||||
partitionKey: keys.userByEmail,
|
||||
rowKey: user.email,
|
||||
}),
|
||||
client.createEntity({
|
||||
...newUser,
|
||||
partitionKey: keys.user,
|
||||
rowKey: id,
|
||||
}),
|
||||
])
|
||||
return newUser
|
||||
},
|
||||
async getUser(id: string) {
|
||||
try {
|
||||
const user = await client.getEntity<AdapterUser>(keys.user, id)
|
||||
return withoutKeys(user)
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
},
|
||||
async getUserByEmail(email) {
|
||||
try {
|
||||
const user = await client.getEntity<AdapterUser>(
|
||||
keys.userByEmail,
|
||||
email
|
||||
)
|
||||
return withoutKeys(user)
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
},
|
||||
async getUserByAccount({ providerAccountId, provider }) {
|
||||
try {
|
||||
const rowKey = `${providerAccountId}_${provider}`
|
||||
const account = await client.getEntity<AdapterAccount>(
|
||||
keys.account,
|
||||
rowKey
|
||||
)
|
||||
const user = await client.getEntity<AdapterUser>(
|
||||
keys.user,
|
||||
account.userId
|
||||
)
|
||||
return withoutKeys(user)
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
},
|
||||
async updateUser(user) {
|
||||
const _user = await client.getEntity<AdapterUser>(keys.user, user.id)
|
||||
const updatedUser = {
|
||||
...user,
|
||||
partitionKey: keys.user,
|
||||
rowKey: _user.id,
|
||||
}
|
||||
await client.updateEntity(updatedUser, "Merge")
|
||||
return { ..._user, ...updatedUser }
|
||||
},
|
||||
async deleteUser(userId) {
|
||||
try {
|
||||
const user = await client.getEntity<AdapterUser>(keys.user, userId)
|
||||
const { sessionToken } = await client.getEntity<AdapterSession>(
|
||||
keys.sessionByUserId,
|
||||
userId
|
||||
)
|
||||
const accounts = withoutKeys(
|
||||
await client.getEntity<AdapterAccount>(keys.accountByUserId, userId)
|
||||
)
|
||||
const deleteAccounts = Object.keys(accounts).map((property) =>
|
||||
client.deleteEntity(keys.account, `${accounts[property]}_${property}`)
|
||||
)
|
||||
await Promise.allSettled([
|
||||
client.deleteEntity(keys.userByEmail, user.email),
|
||||
client.deleteEntity(keys.user, userId),
|
||||
client.deleteEntity(keys.session, sessionToken),
|
||||
client.deleteEntity(keys.sessionByUserId, userId),
|
||||
...deleteAccounts,
|
||||
client.deleteEntity(keys.accountByUserId, userId),
|
||||
])
|
||||
return withoutKeys(user)
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
},
|
||||
async linkAccount(account) {
|
||||
try {
|
||||
await client.createEntity({
|
||||
...account,
|
||||
partitionKey: keys.account,
|
||||
rowKey: `${account.providerAccountId}_${account.provider}`,
|
||||
})
|
||||
await client.upsertEntity({
|
||||
partitionKey: keys.accountByUserId,
|
||||
rowKey: account.userId,
|
||||
[account.provider]: account.providerAccountId,
|
||||
})
|
||||
return account
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
},
|
||||
async unlinkAccount({ providerAccountId, provider }) {
|
||||
const rowKey = `${providerAccountId}_${provider}`
|
||||
const account = await client.getEntity<AdapterAccount>(
|
||||
keys.account,
|
||||
rowKey
|
||||
)
|
||||
await client.deleteEntity(keys.account, rowKey)
|
||||
await client.deleteEntity(keys.accountByUserId, account.userId)
|
||||
},
|
||||
async createSession(session) {
|
||||
await client.createEntity({
|
||||
...session,
|
||||
partitionKey: keys.session,
|
||||
rowKey: session.sessionToken,
|
||||
})
|
||||
await client.upsertEntity({
|
||||
partitionKey: keys.sessionByUserId,
|
||||
rowKey: session.userId,
|
||||
sessionToken: session.sessionToken,
|
||||
})
|
||||
return session
|
||||
},
|
||||
async getSessionAndUser(sessionToken) {
|
||||
try {
|
||||
const session = await client.getEntity<AdapterSession>(
|
||||
keys.session,
|
||||
sessionToken
|
||||
)
|
||||
if (session.expires.valueOf() < Date.now()) {
|
||||
await client.deleteEntity(keys.session, sessionToken)
|
||||
}
|
||||
const user = await client.getEntity<AdapterUser>(
|
||||
keys.user,
|
||||
session.userId
|
||||
)
|
||||
return {
|
||||
session: withoutKeys(session),
|
||||
user: withoutKeys(user),
|
||||
}
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
},
|
||||
async updateSession(session) {
|
||||
const _session = await client.getEntity<AdapterSession>(
|
||||
keys.session,
|
||||
session.sessionToken
|
||||
)
|
||||
const newSession = {
|
||||
expires: session.expires ?? _session.expires,
|
||||
}
|
||||
await client.updateEntity({
|
||||
...newSession,
|
||||
partitionKey: keys.session,
|
||||
rowKey: session.sessionToken,
|
||||
})
|
||||
return { ...withoutKeys(_session), ...newSession }
|
||||
},
|
||||
async deleteSession(sessionToken) {
|
||||
try {
|
||||
const session = await client.getEntity<AdapterSession>(
|
||||
keys.session,
|
||||
sessionToken
|
||||
)
|
||||
await Promise.allSettled([
|
||||
client.deleteEntity(keys.session, sessionToken),
|
||||
client.deleteEntity(keys.sessionByUserId, session.userId),
|
||||
])
|
||||
return withoutKeys(session)
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
},
|
||||
async createVerificationToken(token) {
|
||||
await client.createEntity({
|
||||
...token,
|
||||
partitionKey: keys.verificationToken,
|
||||
rowKey: token.token,
|
||||
})
|
||||
return token
|
||||
},
|
||||
async useVerificationToken({ identifier, token }) {
|
||||
try {
|
||||
const tokenEntity = await client.getEntity<VerificationToken>(
|
||||
keys.verificationToken,
|
||||
token
|
||||
)
|
||||
if (tokenEntity.identifier !== identifier) {
|
||||
return null
|
||||
}
|
||||
await client.deleteEntity(keys.verificationToken, token)
|
||||
return withoutKeys(tokenEntity)
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
90
packages/adapter-azure-tables/tests/index.test.ts
Normal file
90
packages/adapter-azure-tables/tests/index.test.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import { runBasicTests } from "@auth/adapter-test"
|
||||
import {
|
||||
AzureNamedKeyCredential,
|
||||
TableServiceClient,
|
||||
TableClient,
|
||||
} from "@azure/data-tables"
|
||||
import { keys, TableStorageAdapter, withoutKeys } from "../src"
|
||||
import type { AdapterUser, VerificationToken } from "@auth/core/adapters"
|
||||
|
||||
const testAccount = {
|
||||
// default constants used by a dev instance of azurite
|
||||
name: "devstoreaccount1",
|
||||
key: "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==",
|
||||
tableEndpoint: "http://127.0.0.1:10002/devstoreaccount1",
|
||||
}
|
||||
|
||||
const authTableName = "authTest"
|
||||
|
||||
const credential = new AzureNamedKeyCredential(
|
||||
testAccount.name,
|
||||
testAccount.key
|
||||
)
|
||||
|
||||
const authClient = new TableClient(
|
||||
testAccount.tableEndpoint,
|
||||
authTableName,
|
||||
credential,
|
||||
{ allowInsecureConnection: true }
|
||||
)
|
||||
|
||||
runBasicTests({
|
||||
adapter: TableStorageAdapter(authClient),
|
||||
db: {
|
||||
async connect() {
|
||||
const serviceClient = new TableServiceClient(
|
||||
testAccount.tableEndpoint,
|
||||
credential,
|
||||
{ allowInsecureConnection: true }
|
||||
)
|
||||
await serviceClient.createTable(authTableName)
|
||||
},
|
||||
async user(id) {
|
||||
try {
|
||||
const userById = await authClient.getEntity<AdapterUser>(keys.user, id)
|
||||
|
||||
return withoutKeys(userById)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
return null
|
||||
}
|
||||
},
|
||||
async account(provider_providerAccountId) {
|
||||
try {
|
||||
const account = await authClient.getEntity(
|
||||
keys.account,
|
||||
`${provider_providerAccountId.providerAccountId}_${provider_providerAccountId.provider}`
|
||||
)
|
||||
|
||||
return withoutKeys(account)
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
},
|
||||
async session(sessionToken) {
|
||||
try {
|
||||
const session = await authClient.getEntity(keys.session, sessionToken)
|
||||
|
||||
return withoutKeys(session)
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
},
|
||||
async verificationToken(identifier_token) {
|
||||
try {
|
||||
const verificationToken = await authClient.getEntity<VerificationToken>(
|
||||
keys.verificationToken,
|
||||
identifier_token.token
|
||||
)
|
||||
|
||||
if (verificationToken.identifier !== identifier_token.identifier) {
|
||||
return null
|
||||
}
|
||||
|
||||
return withoutKeys(verificationToken)
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
34
packages/adapter-azure-tables/tests/test.sh
Executable file
34
packages/adapter-azure-tables/tests/test.sh
Executable file
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
CONTAINER_NAME=next-auth-azure-tables-test
|
||||
|
||||
JEST_WATCH=false
|
||||
|
||||
# Is the watch flag passed to the script?
|
||||
while getopts w flag
|
||||
do
|
||||
case "${flag}" in
|
||||
w) JEST_WATCH=true;;
|
||||
*) continue;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Start db
|
||||
docker run -d -p 10002:10002 --name ${CONTAINER_NAME} mcr.microsoft.com/azure-storage/azurite azurite-table -l /workspace -d /workspace/debug.log --tableHost 0.0.0.0 --loose
|
||||
|
||||
echo "Waiting 3 sec for db to start..."
|
||||
sleep 3
|
||||
|
||||
if $JEST_WATCH; then
|
||||
# Run jest in watch mode
|
||||
npx jest tests --watch
|
||||
# Only stop the container after jest has been quit
|
||||
docker stop "${CONTAINER_NAME}"
|
||||
else
|
||||
# Always stop container, but exit with 1 when tests are failing
|
||||
if npx jest;then
|
||||
docker stop ${CONTAINER_NAME}
|
||||
else
|
||||
docker stop ${CONTAINER_NAME} && exit 1
|
||||
fi
|
||||
fi
|
||||
20
packages/adapter-azure-tables/tsconfig.json
Normal file
20
packages/adapter-azure-tables/tsconfig.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"extends": "@auth/tsconfig/tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"baseUrl": ".",
|
||||
"isolatedModules": true,
|
||||
"target": "ES2020",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "node",
|
||||
"outDir": ".",
|
||||
"rootDir": "src",
|
||||
"skipDefaultLibCheck": true,
|
||||
"strictNullChecks": true,
|
||||
"stripInternal": true,
|
||||
"declarationMap": true,
|
||||
"declaration": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["*.js", "*.d.ts"]
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@auth/d1-adapter",
|
||||
"version": "0.2.0",
|
||||
"version": "0.2.1",
|
||||
"description": "A Cloudflare D1 adapter for Auth.js",
|
||||
"homepage": "https://authjs.dev",
|
||||
"repository": "https://github.com/nextauthjs/next-auth",
|
||||
@@ -49,7 +49,7 @@
|
||||
"@auth/tsconfig": "workspace:*",
|
||||
"@cloudflare/workers-types": "^4.20230321.0",
|
||||
"@miniflare/d1": "^2.12.2",
|
||||
"better-sqlite3": "^7.0.0",
|
||||
"better-sqlite3": "^8.6.0",
|
||||
"jest": "^29.3.0"
|
||||
},
|
||||
"jest": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@auth/dgraph-adapter",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"description": "Dgraph adapter for Auth.js",
|
||||
"homepage": "https://authjs.dev",
|
||||
"repository": "https://github.com/nextauthjs/next-auth",
|
||||
@@ -57,4 +57,4 @@
|
||||
"jest": {
|
||||
"preset": "@auth/adapter-test/jest"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@auth/drizzle-adapter",
|
||||
"version": "0.3.2",
|
||||
"version": "0.3.3",
|
||||
"description": "Drizzle adapter for Auth.js.",
|
||||
"homepage": "https://authjs.dev",
|
||||
"repository": "https://github.com/nextauthjs/next-auth",
|
||||
@@ -52,9 +52,9 @@
|
||||
"@auth/tsconfig": "workspace:*",
|
||||
"@types/better-sqlite3": "^7.6.4",
|
||||
"@types/uuid": "^8.3.3",
|
||||
"better-sqlite3": "^8.4.0",
|
||||
"better-sqlite3": "^8.6.0",
|
||||
"drizzle-kit": "^0.19.5",
|
||||
"drizzle-orm": "^0.27.0",
|
||||
"drizzle-orm": "^0.28.6",
|
||||
"jest": "^27.4.3",
|
||||
"mysql2": "^3.2.0",
|
||||
"postgres": "^3.3.4"
|
||||
@@ -62,4 +62,4 @@
|
||||
"jest": {
|
||||
"preset": "@auth/adapter-test/jest"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@auth/dynamodb-adapter",
|
||||
"repository": "https://github.com/nextauthjs/next-auth",
|
||||
"version": "1.0.2",
|
||||
"version": "1.0.3",
|
||||
"description": "AWS DynamoDB adapter for next-auth.",
|
||||
"keywords": [
|
||||
"next-auth",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@auth/edgedb-adapter",
|
||||
"version": "0.2.0",
|
||||
"version": "0.2.1",
|
||||
"description": "EdgeDB adapter for next-auth.",
|
||||
"homepage": "https://authjs.dev",
|
||||
"repository": "https://github.com/nextauthjs/next-auth",
|
||||
@@ -57,4 +57,4 @@
|
||||
"jest": {
|
||||
"preset": "@auth/adapter-test/jest"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@auth/fauna-adapter",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"description": "Fauna Adapter for Auth.js",
|
||||
"homepage": "https://authjs.dev",
|
||||
"repository": "https://github.com/nextauthjs/next-auth",
|
||||
@@ -59,4 +59,4 @@
|
||||
"jest": {
|
||||
"preset": "@auth/adapter-test/jest"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@auth/firebase-adapter",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"description": "Firebase adapter for Auth.js",
|
||||
"homepage": "https://authjs.dev",
|
||||
"repository": "https://github.com/nextauthjs/next-auth",
|
||||
@@ -54,4 +54,4 @@
|
||||
"firebase-tools": "^11.16.1",
|
||||
"jest": "^29.3.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@auth/kysely-adapter",
|
||||
"version": "0.1.1",
|
||||
"version": "0.1.2",
|
||||
"description": "Kysely adapter for Auth.js",
|
||||
"homepage": "https://authjs.dev/reference/adapter/kysely",
|
||||
"repository": "https://github.com/nextauthjs/next-auth",
|
||||
@@ -44,7 +44,7 @@
|
||||
"@auth/tsconfig": "workspace:*",
|
||||
"@types/better-sqlite3": "^7.6.3",
|
||||
"@types/pg": "^8.6.5",
|
||||
"better-sqlite3": "^8.2.0",
|
||||
"better-sqlite3": "^8.6.0",
|
||||
"jest": "^27.4.3",
|
||||
"kysely": "^0.24.2",
|
||||
"mysql2": "^3.2.0",
|
||||
@@ -53,4 +53,4 @@
|
||||
"jest": {
|
||||
"preset": "@auth/adapter-test/jest"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@auth/mikro-orm-adapter",
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.2",
|
||||
"description": "MikroORM adapter for Auth.js",
|
||||
"homepage": "https://authjs.dev",
|
||||
"repository": "https://github.com/nextauthjs/next-auth",
|
||||
@@ -56,4 +56,4 @@
|
||||
"jest": {
|
||||
"preset": "@auth/adapter-test/jest"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@auth/mongodb-adapter",
|
||||
"version": "2.0.0",
|
||||
"version": "2.0.1",
|
||||
"description": "MongoDB adapter for Auth.js",
|
||||
"homepage": "https://authjs.dev",
|
||||
"repository": "https://github.com/nextauthjs/next-auth",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@auth/neo4j-adapter",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"description": "neo4j adapter for Auth.js",
|
||||
"homepage": "https://authjs.dev",
|
||||
"repository": "https://github.com/nextauthjs/next-auth",
|
||||
@@ -56,4 +56,4 @@
|
||||
"jest": {
|
||||
"preset": "@auth/adapter-test/jest"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@auth/pg-adapter",
|
||||
"version": "0.2.0",
|
||||
"version": "0.2.1",
|
||||
"description": "Postgres adapter for next-auth.",
|
||||
"homepage": "https://authjs.dev",
|
||||
"repository": "https://github.com/nextauthjs/next-auth",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* <div style={{display: "flex", justifyContent: "space-between", alignItems: "center", padding: 16}}>
|
||||
* <p style={{fontWeight: "normal"}}>An official <a href="https://www.postgresql.org/">PostgreSQL</a> adapter for Auth.js / NextAuth.js.</p>
|
||||
* <a href="https://www.postgresql.org/">
|
||||
* <img style={{display: "block"}} src="/img/adapters/pg.svg" width="48" />
|
||||
* <img style={{display: "block"}} src="/img/adapters/pg.png" width="48" />
|
||||
* </a>
|
||||
* </div>
|
||||
*
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@auth/pouchdb-adapter",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"description": "PouchDB adapter for next-auth.",
|
||||
"homepage": "https://authjs.dev",
|
||||
"repository": "https://github.com/nextauthjs/next-auth",
|
||||
@@ -56,4 +56,4 @@
|
||||
"jest": {
|
||||
"preset": "@auth/adapter-test/jest"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@auth/prisma-adapter",
|
||||
"version": "1.0.2",
|
||||
"version": "1.0.3",
|
||||
"description": "Prisma adapter for Auth.js",
|
||||
"homepage": "https://authjs.dev/reference/adapter/prisma",
|
||||
"repository": "https://github.com/nextauthjs/next-auth",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@auth/sequelize-adapter",
|
||||
"version": "1.0.2",
|
||||
"version": "1.0.3",
|
||||
"description": "Sequelize adapter for Auth.js",
|
||||
"homepage": "https://authjs.dev",
|
||||
"repository": "https://github.com/nextauthjs/next-auth",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@auth/supabase-adapter",
|
||||
"version": "0.1.2",
|
||||
"version": "0.1.4",
|
||||
"description": "Supabase adapter for Auth.js",
|
||||
"homepage": "https://authjs.dev",
|
||||
"repository": "https://github.com/nextauthjs/next-auth",
|
||||
@@ -50,4 +50,4 @@
|
||||
"jest": {
|
||||
"preset": "@auth/adapter-test/jest"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -260,7 +260,7 @@ export interface SupabaseAdapterOptions {
|
||||
* For example, given the following public schema:
|
||||
*
|
||||
* ```sql
|
||||
* // Note: This table contains user data. Users should only be able to view and update their own data.
|
||||
* -- Note: This table contains user data. Users should only be able to view and update their own data.
|
||||
* create table users (
|
||||
* -- UUID from next_auth.users
|
||||
* id uuid not null primary key,
|
||||
@@ -276,7 +276,7 @@ export interface SupabaseAdapterOptions {
|
||||
* create policy "Can view own user data." on users for select using (next_auth.uid() = id);
|
||||
* create policy "Can update own user data." on users for update using (next_auth.uid() = id);
|
||||
*
|
||||
* // This trigger automatically creates a user entry when a new user signs up via NextAuth.
|
||||
* -- This trigger automatically creates a user entry when a new user signs up via NextAuth.
|
||||
* create function public.handle_new_user()
|
||||
* returns trigger as $$
|
||||
* begin
|
||||
|
||||
28
packages/adapter-surrealdb/README.md
Normal file
28
packages/adapter-surrealdb/README.md
Normal file
@@ -0,0 +1,28 @@
|
||||
<p align="center">
|
||||
<br/>
|
||||
<a href="https://authjs.dev" target="_blank">
|
||||
<img height="64px" src="https://authjs.dev/img/logo/logo-sm.png" />
|
||||
</a>
|
||||
<a href="https://surrealdb.com/" target="_blank">
|
||||
<img height="64px" src="https://authjs.dev/img/adapters/surrealdb.png"/>
|
||||
</a>
|
||||
<h3 align="center"><b>Surreal DB Adapter</b> - NextAuth.js / Auth.js</a></h3>
|
||||
<p align="center" style="align: center;">
|
||||
<a href="https://npm.im/@auth/surrealdb-adapter">
|
||||
<img src="https://img.shields.io/badge/TypeScript-blue?style=flat-square" alt="TypeScript" />
|
||||
</a>
|
||||
<a href="https://npm.im/@auth/surrealdb-adapter">
|
||||
<img alt="npm" src="https://img.shields.io/npm/v/@auth/surrealdb-adapter?color=green&label=@auth/surrealdb-adapter&style=flat-square">
|
||||
</a>
|
||||
<a href="https://www.npmtrends.com/@auth/surrealdb-adapter">
|
||||
<img src="https://img.shields.io/npm/dm/@auth/surrealdb-adapter?label=%20downloads&style=flat-square" alt="Downloads" />
|
||||
</a>
|
||||
<a href="https://github.com/nextauthjs/next-auth/stargazers">
|
||||
<img src="https://img.shields.io/github/stars/nextauthjs/next-auth?style=flat-square" alt="Github Stars" />
|
||||
</a>
|
||||
</p>
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
Check out the documentation at [authjs.dev](https://authjs.dev/reference/adapter/surrealdb).
|
||||
58
packages/adapter-surrealdb/package.json
Normal file
58
packages/adapter-surrealdb/package.json
Normal file
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"name": "@auth/surrealdb-adapter",
|
||||
"version": "0.1.1",
|
||||
"description": "SurrealDB adapter for next-auth.",
|
||||
"homepage": "https://authjs.dev",
|
||||
"repository": "https://github.com/nextauthjs/next-auth",
|
||||
"bugs": {
|
||||
"url": "https://github.com/nextauthjs/next-auth/issues"
|
||||
},
|
||||
"author": "Martin Schaer <martin@schaerweb.com>",
|
||||
"contributors": [
|
||||
"Thang Huu Vu <hi@thvu.dev>"
|
||||
],
|
||||
"type": "module",
|
||||
"types": "./index.d.ts",
|
||||
"files": [
|
||||
"*.js",
|
||||
"*.d.ts*",
|
||||
"src"
|
||||
],
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./index.d.ts",
|
||||
"import": "./index.js"
|
||||
}
|
||||
},
|
||||
"license": "ISC",
|
||||
"keywords": [
|
||||
"next-auth",
|
||||
"next.js",
|
||||
"oauth",
|
||||
"mongodb",
|
||||
"adapter"
|
||||
],
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "./tests/test.sh",
|
||||
"test:watch": "./tests/test.sh -w",
|
||||
"build": "tsc"
|
||||
},
|
||||
"dependencies": {
|
||||
"@auth/core": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"surrealdb.js": "^0.9.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@auth/adapter-test": "workspace:*",
|
||||
"@auth/tsconfig": "workspace:*",
|
||||
"jest": "^27.4.3"
|
||||
},
|
||||
"jest": {
|
||||
"preset": "@auth/adapter-test/jest"
|
||||
}
|
||||
}
|
||||
366
packages/adapter-surrealdb/src/index.ts
Normal file
366
packages/adapter-surrealdb/src/index.ts
Normal file
@@ -0,0 +1,366 @@
|
||||
import Surreal, { ExperimentalSurrealHTTP } from "surrealdb.js"
|
||||
import type {
|
||||
Adapter,
|
||||
AdapterUser,
|
||||
AdapterAccount,
|
||||
AdapterSession,
|
||||
VerificationToken,
|
||||
} from "@auth/core/adapters"
|
||||
import type { ProviderType } from "@auth/core/providers"
|
||||
|
||||
type Document = Record<string, string | null | undefined> & { id: string }
|
||||
export type UserDoc = Document & { email: string }
|
||||
export type AccountDoc<T = string> = {
|
||||
id: string
|
||||
userId: T
|
||||
refresh_token?: string
|
||||
access_token?: string
|
||||
type: Extract<ProviderType, "oauth" | "oidc" | "email">
|
||||
provider: string
|
||||
providerAccountId: string
|
||||
expires_at?: number
|
||||
}
|
||||
export type SessionDoc<T = string> = Document & { userId: T }
|
||||
|
||||
const extractId = (surrealId: string) => surrealId.split(":")[1] ?? surrealId
|
||||
|
||||
// Convert DB object to AdapterUser
|
||||
export const docToUser = (doc: UserDoc): AdapterUser => ({
|
||||
...doc,
|
||||
id: extractId(doc.id),
|
||||
emailVerified: doc.emailVerified ? new Date(doc.emailVerified) : null,
|
||||
})
|
||||
|
||||
// Convert DB object to AdapterAccount
|
||||
export const docToAccount = (doc: AccountDoc) => {
|
||||
const account: AdapterAccount = {
|
||||
...doc,
|
||||
id: extractId(doc.id),
|
||||
userId: doc.userId ? extractId(doc.userId) : "",
|
||||
}
|
||||
return account
|
||||
}
|
||||
|
||||
// Convert DB object to AdapterSession
|
||||
export const docToSession = (
|
||||
doc: SessionDoc<string | UserDoc>
|
||||
): AdapterSession => ({
|
||||
userId: extractId(
|
||||
typeof doc.userId === "string" ? doc.userId : doc.userId.id
|
||||
),
|
||||
expires: new Date(doc.expires ?? ""),
|
||||
sessionToken: doc.sessionToken ?? "",
|
||||
})
|
||||
|
||||
// Convert AdapterUser to DB object
|
||||
const userToDoc = (
|
||||
user: Omit<AdapterUser, "id"> | Partial<AdapterUser>
|
||||
): Omit<UserDoc, "id"> => {
|
||||
const doc = {
|
||||
...user,
|
||||
emailVerified: user.emailVerified?.toISOString(),
|
||||
}
|
||||
return doc
|
||||
}
|
||||
|
||||
// Convert AdapterAccount to DB object
|
||||
const accountToDoc = (account: AdapterAccount): Omit<AccountDoc, "id"> => {
|
||||
const doc = {
|
||||
...account,
|
||||
userId: `user:${account.userId}`,
|
||||
}
|
||||
return doc
|
||||
}
|
||||
|
||||
// Convert AdapterSession to DB object
|
||||
export const sessionToDoc = (
|
||||
session: AdapterSession
|
||||
): Omit<SessionDoc, "id"> => {
|
||||
const doc = {
|
||||
...session,
|
||||
expires: session.expires.toISOString(),
|
||||
}
|
||||
return doc
|
||||
}
|
||||
|
||||
export function SurrealDBAdapter<T>(
|
||||
client: Promise<Surreal | ExperimentalSurrealHTTP<T>>
|
||||
// options = {}
|
||||
): Adapter {
|
||||
return {
|
||||
async createUser(user: Omit<AdapterUser, "id">) {
|
||||
const surreal = await client
|
||||
const doc = userToDoc(user)
|
||||
const userDoc = await surreal.create<UserDoc, Omit<UserDoc, "id">>("user", doc)
|
||||
if (userDoc.length) {
|
||||
return docToUser(userDoc[0])
|
||||
}
|
||||
throw new Error("User not created")
|
||||
},
|
||||
async getUser(id: string) {
|
||||
const surreal = await client
|
||||
try {
|
||||
const queryResult = await surreal.query<[UserDoc[]]>(
|
||||
"SELECT * FROM $user",
|
||||
{
|
||||
user: `user:${id}`,
|
||||
}
|
||||
)
|
||||
const doc = queryResult[0].result?.[0]
|
||||
if (doc) {
|
||||
return docToUser(doc)
|
||||
}
|
||||
} catch (e) { }
|
||||
return null
|
||||
},
|
||||
async getUserByEmail(email: string) {
|
||||
const surreal = await client
|
||||
try {
|
||||
const users = await surreal.query<[UserDoc[]]>(
|
||||
`SELECT * FROM user WHERE email = $email`,
|
||||
{ email }
|
||||
)
|
||||
const doc = users[0].result?.[0]
|
||||
if (doc) return docToUser(doc)
|
||||
} catch (e) { }
|
||||
return null
|
||||
},
|
||||
async getUserByAccount({
|
||||
providerAccountId,
|
||||
provider,
|
||||
}: Pick<AdapterAccount, "provider" | "providerAccountId">) {
|
||||
const surreal = await client
|
||||
try {
|
||||
const users = await surreal.query<[AccountDoc<UserDoc>[]]>(
|
||||
`SELECT userId
|
||||
FROM account
|
||||
WHERE providerAccountId = $providerAccountId
|
||||
AND provider = $provider
|
||||
FETCH userId`,
|
||||
{ providerAccountId, provider }
|
||||
)
|
||||
const user = users[0].result?.[0]
|
||||
?.userId
|
||||
if (user) return docToUser(user)
|
||||
} catch (e) { }
|
||||
return null
|
||||
},
|
||||
async updateUser(user: Partial<AdapterUser>) {
|
||||
const surreal = await client
|
||||
const doc = { ...user, emailVerified: user.emailVerified?.toISOString(), id: undefined }
|
||||
let updatedUser = await surreal.merge<UserDoc, Omit<UserDoc, "id">>(`user:${user.id}`, doc)
|
||||
if (updatedUser.length) {
|
||||
return docToUser(updatedUser[0])
|
||||
} else {
|
||||
throw new Error("User not updated")
|
||||
}
|
||||
},
|
||||
async deleteUser(userId: string) {
|
||||
const surreal = await client
|
||||
|
||||
// delete account
|
||||
try {
|
||||
const accounts = await surreal.query<[AccountDoc[]]>(
|
||||
`SELECT *
|
||||
FROM account
|
||||
WHERE userId = $userId
|
||||
LIMIT 1`,
|
||||
{ userId: `user:${userId}` }
|
||||
)
|
||||
const account = accounts[0].result?.[0]
|
||||
if (account) {
|
||||
const accountId = extractId(account.id)
|
||||
await surreal.delete(`account:${accountId}`)
|
||||
}
|
||||
} catch (e) { }
|
||||
|
||||
// delete session
|
||||
try {
|
||||
const sessions = await surreal.query<[SessionDoc[]]>(
|
||||
`SELECT *
|
||||
FROM session
|
||||
WHERE userId = $userId
|
||||
LIMIT 1`,
|
||||
{ userId: `user:${userId}` }
|
||||
)
|
||||
const session = sessions[0].result?.[0]
|
||||
if (session) {
|
||||
const sessionId = extractId(session.id)
|
||||
await surreal.delete(`session:${sessionId}`)
|
||||
}
|
||||
} catch (e) { }
|
||||
|
||||
// delete user
|
||||
await surreal.delete(`user:${userId}`)
|
||||
|
||||
// TODO: put all 3 deletes inside a Promise all
|
||||
},
|
||||
async linkAccount(account: AdapterAccount) {
|
||||
const surreal = await client
|
||||
const doc = await surreal.create("account", accountToDoc(account))
|
||||
return docToAccount(doc[0])
|
||||
},
|
||||
async unlinkAccount({
|
||||
providerAccountId,
|
||||
provider,
|
||||
}: Pick<AdapterAccount, "provider" | "providerAccountId">) {
|
||||
const surreal = await client
|
||||
try {
|
||||
const accounts = await surreal.query<[AccountDoc[]]>(
|
||||
`SELECT *
|
||||
FROM account
|
||||
WHERE providerAccountId = $providerAccountId
|
||||
AND provider = $provider
|
||||
LIMIT 1`,
|
||||
{ providerAccountId, provider }
|
||||
)
|
||||
const account = accounts[0].result?.[0]
|
||||
if (account) {
|
||||
const accountId = extractId(account.id)
|
||||
await surreal.delete(`account:${accountId}`)
|
||||
}
|
||||
} catch (e) { }
|
||||
},
|
||||
async createSession({
|
||||
sessionToken,
|
||||
userId,
|
||||
expires,
|
||||
}: {
|
||||
sessionToken: string
|
||||
userId: string
|
||||
expires: Date
|
||||
}) {
|
||||
const surreal = await client
|
||||
const doc = {
|
||||
sessionToken,
|
||||
userId: `user:${userId}`,
|
||||
expires,
|
||||
}
|
||||
const result = await surreal.create("session", doc)
|
||||
return result[0] ?? null
|
||||
},
|
||||
async getSessionAndUser(sessionToken: string) {
|
||||
const surreal = await client
|
||||
try {
|
||||
// Can't use limit 1 because it prevent userId to be fetched.
|
||||
// Works setting limit to 2
|
||||
const sessions = await surreal.query<[SessionDoc<UserDoc>[]]>(
|
||||
`SELECT *
|
||||
FROM session
|
||||
WHERE sessionToken = $sessionToken
|
||||
FETCH userId`,
|
||||
{ sessionToken }
|
||||
)
|
||||
const session = sessions[0].result?.[0]
|
||||
if (session) {
|
||||
const userDoc = session.userId
|
||||
if (!userDoc) return null
|
||||
return {
|
||||
user: docToUser(userDoc),
|
||||
session: docToSession({
|
||||
...session,
|
||||
userId: userDoc.id,
|
||||
}),
|
||||
}
|
||||
}
|
||||
} catch (e) { }
|
||||
return null
|
||||
},
|
||||
async updateSession(
|
||||
session: Partial<AdapterSession> & Pick<AdapterSession, "sessionToken">
|
||||
) {
|
||||
const surreal = await client
|
||||
try {
|
||||
const sessions = await surreal.query<[SessionDoc[]]>(
|
||||
`SELECT *
|
||||
FROM session
|
||||
WHERE sessionToken = $sessionToken
|
||||
LIMIT 1`,
|
||||
{ sessionToken: session.sessionToken }
|
||||
)
|
||||
const sessionDoc = sessions[0].result?.[0]
|
||||
if (sessionDoc && session.expires) {
|
||||
const sessionId = extractId(sessionDoc.id)
|
||||
let updatedSession = await surreal.merge<SessionDoc, Omit<SessionDoc, "id">>(
|
||||
`session:${sessionId}`,
|
||||
sessionToDoc({
|
||||
...sessionDoc,
|
||||
...session,
|
||||
userId: sessionDoc.userId,
|
||||
expires: session.expires,
|
||||
})
|
||||
)
|
||||
if (updatedSession.length) {
|
||||
return docToSession(updatedSession[0])
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
} catch (e) { }
|
||||
return null
|
||||
},
|
||||
async deleteSession(sessionToken: string) {
|
||||
const surreal = await client
|
||||
try {
|
||||
const sessions = await surreal.query<[SessionDoc[]]>(
|
||||
`SELECT *
|
||||
FROM session
|
||||
WHERE sessionToken = $sessionToken
|
||||
LIMIT 1`,
|
||||
{ sessionToken }
|
||||
)
|
||||
const session = sessions[0].result?.[0]
|
||||
if (session) {
|
||||
const sessionId = extractId(session.id)
|
||||
await surreal.delete(`session:${sessionId}`)
|
||||
return
|
||||
}
|
||||
} catch (e) { }
|
||||
},
|
||||
async createVerificationToken({
|
||||
identifier,
|
||||
expires,
|
||||
token,
|
||||
}: VerificationToken) {
|
||||
const surreal = await client
|
||||
const doc = {
|
||||
identifier,
|
||||
expires,
|
||||
token,
|
||||
}
|
||||
const result = await surreal.create("verification_token", doc)
|
||||
return result[0] ?? null
|
||||
},
|
||||
async useVerificationToken({
|
||||
identifier,
|
||||
token,
|
||||
}: {
|
||||
identifier: string
|
||||
token: string
|
||||
}) {
|
||||
const surreal = await client
|
||||
try {
|
||||
const tokens = await surreal.query<[{ identifier: string, expires: string, token: string, id: string }[]]>(
|
||||
`SELECT *
|
||||
FROM verification_token
|
||||
WHERE identifier = $identifier
|
||||
AND token = $verificationToken
|
||||
LIMIT 1`,
|
||||
{ identifier, verificationToken: token }
|
||||
)
|
||||
if (tokens.length && tokens[0].result) {
|
||||
const vt = tokens[0].result[0]
|
||||
if (vt) {
|
||||
await surreal.delete(vt.id)
|
||||
return {
|
||||
identifier: vt.identifier,
|
||||
expires: new Date(vt.expires),
|
||||
token: vt.token,
|
||||
}
|
||||
}
|
||||
} else { return null }
|
||||
} catch (e) { }
|
||||
return null
|
||||
},
|
||||
}
|
||||
}
|
||||
77
packages/adapter-surrealdb/tests/common.ts
Normal file
77
packages/adapter-surrealdb/tests/common.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import Surreal, { ExperimentalSurrealHTTP } from "surrealdb.js"
|
||||
|
||||
import {
|
||||
SurrealDBAdapter,
|
||||
docToUser,
|
||||
docToAccount,
|
||||
docToSession,
|
||||
} from "../src/index"
|
||||
import type { UserDoc, AccountDoc, SessionDoc } from "../src/index"
|
||||
|
||||
export const config = (
|
||||
clientPromise: Promise<Surreal | ExperimentalSurrealHTTP<typeof fetch>>
|
||||
) => ({
|
||||
adapter: SurrealDBAdapter(clientPromise),
|
||||
db: {
|
||||
async disconnect() {
|
||||
const surreal = await clientPromise
|
||||
if (surreal.close) surreal.close()
|
||||
},
|
||||
async user(id: string) {
|
||||
const surreal = await clientPromise
|
||||
try {
|
||||
const users = await surreal.query<[UserDoc[]]>("SELECT * FROM $user", {
|
||||
user: `user:${id}`,
|
||||
})
|
||||
const user = users[0]
|
||||
if (user.result?.[0] !== undefined) return docToUser(user.result[0])
|
||||
} catch (e) {}
|
||||
return null
|
||||
},
|
||||
async account({ provider, providerAccountId }) {
|
||||
const surreal = await clientPromise
|
||||
const accounts = await surreal.query<[AccountDoc[]]>(
|
||||
`SELECT * FROM account WHERE provider = $provider AND providerAccountId = $providerAccountId`,
|
||||
{ provider, providerAccountId }
|
||||
)
|
||||
const account = accounts[0]
|
||||
if (account.result?.[0] !== undefined)
|
||||
return docToAccount(account.result[0])
|
||||
return null
|
||||
},
|
||||
async session(sessionToken: string) {
|
||||
const surreal = await clientPromise
|
||||
const sessions = await surreal.query<[SessionDoc[]]>(
|
||||
`SELECT * FROM session WHERE sessionToken = $sessionToken`,
|
||||
{ sessionToken }
|
||||
)
|
||||
const session = sessions[0].result?.[0]
|
||||
if (session !== undefined) {
|
||||
return docToSession(session)
|
||||
}
|
||||
return null
|
||||
},
|
||||
async verificationToken({ identifier, token }) {
|
||||
const surreal = await clientPromise
|
||||
const tokens = await surreal.query<
|
||||
[{ identifier: string; expires: string; token: string; id: string }[]]
|
||||
>(
|
||||
`SELECT *
|
||||
FROM verification_token
|
||||
WHERE identifier = $identifier
|
||||
AND token = $verificationToken
|
||||
LIMIT 1`,
|
||||
{ identifier, verificationToken: token }
|
||||
)
|
||||
const verificationToken = tokens[0].result?.[0]
|
||||
if (verificationToken) {
|
||||
return {
|
||||
identifier: verificationToken.identifier,
|
||||
expires: new Date(verificationToken.expires),
|
||||
token: verificationToken.token,
|
||||
}
|
||||
}
|
||||
return null
|
||||
},
|
||||
},
|
||||
})
|
||||
23
packages/adapter-surrealdb/tests/index.test.ts
Normal file
23
packages/adapter-surrealdb/tests/index.test.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import Surreal from "surrealdb.js"
|
||||
import { runBasicTests } from "@auth/adapter-test"
|
||||
|
||||
import { config } from "./common"
|
||||
|
||||
const clientPromise = new Promise<Surreal>(async (resolve, reject) => {
|
||||
const db = new Surreal();
|
||||
try {
|
||||
await db.connect('http://0.0.0.0:8000/rpc', {
|
||||
ns: "test",
|
||||
db: "test",
|
||||
auth: {
|
||||
user: "test",
|
||||
pass: "test",
|
||||
}
|
||||
})
|
||||
resolve(db)
|
||||
} catch (e) {
|
||||
reject(e)
|
||||
}
|
||||
})
|
||||
|
||||
runBasicTests(config(clientPromise))
|
||||
27
packages/adapter-surrealdb/tests/rest.test.ts
Normal file
27
packages/adapter-surrealdb/tests/rest.test.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
test("TODO: test rest", () => {
|
||||
expect(true).toBe(true)
|
||||
})
|
||||
// import { ExperimentalSurrealHTTP } from "surrealdb.js"
|
||||
// import { runBasicTests } from "@auth/adapter-test"
|
||||
// import fetch from "node-fetch"
|
||||
|
||||
// import { config } from "./common"
|
||||
|
||||
// const clientPromise = new Promise<ExperimentalSurrealHTTP<typeof fetch>>(async (resolve, reject) => {
|
||||
// try {
|
||||
// const db = new ExperimentalSurrealHTTP("http://0.0.0.0:8000", {
|
||||
// fetch,
|
||||
// auth: {
|
||||
// user: "test",
|
||||
// pass: "test",
|
||||
// },
|
||||
// ns: "test",
|
||||
// db: "test",
|
||||
// })
|
||||
// resolve(db)
|
||||
// } catch (e) {
|
||||
// reject(e)
|
||||
// }
|
||||
// })
|
||||
|
||||
// runBasicTests(config(clientPromise))
|
||||
34
packages/adapter-surrealdb/tests/test.sh
Executable file
34
packages/adapter-surrealdb/tests/test.sh
Executable file
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
CONTAINER_NAME=next-auth-surrealdb-test
|
||||
|
||||
JEST_WATCH=false
|
||||
|
||||
# Is the watch flag passed to the script?
|
||||
while getopts w flag
|
||||
do
|
||||
case "${flag}" in
|
||||
w) JEST_WATCH=true;;
|
||||
*) continue;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Start db
|
||||
docker run -d --rm -p 8000:8000 --name ${CONTAINER_NAME} surrealdb/surrealdb:latest start --log debug --user test --pass test memory
|
||||
|
||||
echo "Waiting 3 sec for db to start..."
|
||||
sleep 3
|
||||
|
||||
if $JEST_WATCH; then
|
||||
# Run jest in watch mode
|
||||
npx jest tests --watch
|
||||
# Only stop the container after jest has been quit
|
||||
docker stop "${CONTAINER_NAME}"
|
||||
else
|
||||
# Always stop container, but exit with 1 when tests are failing
|
||||
if npx jest;then
|
||||
docker stop ${CONTAINER_NAME}
|
||||
else
|
||||
docker stop ${CONTAINER_NAME} && exit 1
|
||||
fi
|
||||
fi
|
||||
25
packages/adapter-surrealdb/tsconfig.json
Normal file
25
packages/adapter-surrealdb/tsconfig.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"extends": "@auth/tsconfig/tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"baseUrl": ".",
|
||||
"isolatedModules": true,
|
||||
"target": "ES2020",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "node",
|
||||
"outDir": ".",
|
||||
"rootDir": "src",
|
||||
"skipDefaultLibCheck": true,
|
||||
"strictNullChecks": true,
|
||||
"stripInternal": true,
|
||||
"declarationMap": true,
|
||||
"declaration": true
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"*.js",
|
||||
"*.d.ts",
|
||||
]
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@auth/typeorm-adapter",
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.4",
|
||||
"description": "TypeORM adapter for Auth.js.",
|
||||
"homepage": "https://authjs.dev/reference/adapter/typeorm",
|
||||
"repository": "https://github.com/nextauthjs/next-auth",
|
||||
@@ -57,13 +57,13 @@
|
||||
"mysql": "^2.18.1",
|
||||
"pg": "^8.7.3",
|
||||
"sqlite3": "^5.0.8",
|
||||
"typeorm": "0.3.15",
|
||||
"typeorm": "0.3.17",
|
||||
"typeorm-naming-strategies": "^4.1.0",
|
||||
"typescript": "5.2.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"mssql": "^6.2.1 || 7",
|
||||
"mysql": "^2.18.1",
|
||||
"mssql": "^6.2.1 || ^7 || ^8 || ^9",
|
||||
"mysql": "^2.18.1 || ^3",
|
||||
"pg": "^8.2.1",
|
||||
"sqlite3": "^5.0.2",
|
||||
"typeorm": "^0.3.7"
|
||||
@@ -85,4 +85,4 @@
|
||||
"jest": {
|
||||
"preset": "@auth/adapter-test/jest"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,15 +42,16 @@ export async function getManager(options: {
|
||||
dataSource: string | DataSourceOptions
|
||||
entities: Entities
|
||||
}): Promise<EntityManager> {
|
||||
const { dataSource, entities } = options
|
||||
|
||||
const config = {
|
||||
...parseDataSourceConfig(dataSource),
|
||||
entities: Object.values(entities),
|
||||
if (!_dataSource) {
|
||||
const { dataSource, entities } = options
|
||||
const config = {
|
||||
...parseDataSourceConfig(dataSource),
|
||||
entities: Object.values(entities),
|
||||
}
|
||||
_dataSource = new DataSource(config)
|
||||
}
|
||||
|
||||
if (!_dataSource) _dataSource = new DataSource(config)
|
||||
|
||||
const manager = _dataSource?.manager
|
||||
|
||||
if (!manager.connection.isInitialized) {
|
||||
@@ -58,7 +59,7 @@ export async function getManager(options: {
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
await updateConnectionEntities(_dataSource, config.entities)
|
||||
await updateConnectionEntities(_dataSource, Object.values(options.entities))
|
||||
}
|
||||
return manager
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user