Compare commits

...

21 Commits

Author SHA1 Message Date
GitHub Actions
8f8067a23a chore(release): bump package version(s) [skip ci] 2023-09-28 10:52:10 +00:00
Balázs Orbán
8629e16255 fix: allow csrfDisabled on session action 2023-09-28 12:48:04 +02:00
Balázs Orbán
bfa0d910d7 chore: disable build of next-auth on main
it's released from the `v4` branch right now
2023-09-28 12:21:50 +02:00
Balázs Orbán
cff0d61e07 chore: format turbo.json 2023-09-28 01:45:58 +02:00
Balázs Orbán
41c24542b5 chore: drop duplicate provider-logos 2023-09-28 01:21:50 +02:00
Balázs Orbán
77a439b2a2 chore: docs fix config 2023-09-28 01:10:49 +02:00
Balázs Orbán
95eb8aaf69 docs: pull out docs changes from #7443
to minimize the diff there
2023-09-28 01:08:06 +02:00
Balázs Orbán
559842fe02 chore: remove v4 dev app 2023-09-28 00:51:04 +02:00
Balázs Orbán
ce7a49910e chore(examples): add cognito issuer 2023-09-27 13:41:35 +02:00
Balázs Orbán
e895f42302 docs: fix edit links for adapters 2023-09-26 12:39:29 +02:00
panstabolitis
db2ace585d fix(docs): correct comment syntax in SQL code snippets (#8720)
Changed the SQL comment syntax from // to -- in packages/adapter-supabase/src/index.ts
2023-09-26 02:07:10 +02:00
Natsuki Ikeguchi
c9fc84ee82 build(deps): Remove better-sqlite3@7 (#8719) 2023-09-26 02:05:56 +02:00
Balázs Orbán
77933b23f0 chore: only validate bugs reports for repro links 2023-09-25 11:32:58 +02:00
Trần Minh Quang
cbbe27102e feat(providers): update LinkedIn to use OIDC (#8396)
Co-authored-by: Balázs Orbán <info@balazsorban.com>
2023-09-25 11:32:58 +02:00
GitHub Actions
e274c51807 chore(release): bump package version(s) [skip ci] 2023-09-24 13:46:27 +00:00
Thang Vu
2b3836d945 chore: add missing adapters in misc files 2023-09-24 20:30:31 +07:00
Thang Vu
b729f8af0b feat(adapters): azure tables adapter (#8708)
* feat(adapter): Add Azure Table Storage DB adapter

* add newlines

* remove sessionByUserId together with the session

* include import in the readme file

* add types to response objects

* introduce contracts for the db entities

* Rename the lib in docs

Co-authored-by: Nico Domino <yo@ndo.dev>

* run prettier

* feat: azure tables adapter

---------

Co-authored-by: Nikita Dmitriev <nikitadmitry@gmail.com>
Co-authored-by: Nikita Dmitriev <106996965+nikitaclicks@users.noreply.github.com>
Co-authored-by: Nico Domino <yo@ndo.dev>
2023-09-24 17:50:05 +07:00
Guy Korland
9f54222c0e fix(adapters): Avoid parseDataSourceConfig on each call (#8581)
Improve performance by avoiding call to parseDataSourceConfig before checking if _dataSource was already initialized.

Co-authored-by: Thang Vu <hi@thvu.dev>
2023-09-24 10:18:30 +07:00
Joachim Bjørge
a5ac491cb8 fix(providers): optional chaining in azure-ad-b2c profile (#8616)
Fix crash in azure-ad-b2c.ts

Not all b2c-setups return a list of emails. This fixes the resulting crash by using defensive access when setting the profile email address field.

Co-authored-by: Thang Vu <hi@thvu.dev>
2023-09-24 10:01:43 +07:00
Thang Vu
a96dcdbca3 chore: format surrealdb deps 2023-09-24 09:45:44 +07:00
Martin Schaer
bec01a82ea feat(adapters): add SurrealDB adapter (#6251)
* feat(adapter-surrealdb): implemented with unit tests

* chore: update README

* Use stateless DB connection

* Update surrealdb-rest-ts

* chore: bump turbo and pnpm

* chore(docs): fix dynamodb typo (#7130)

fix: typo

* chore: bump pnpm

* chore: update lock file, bump dev dependencies

* chore: run `pnpm install --fix-lockfile`

* chore: re-run pnpm install

* chore: add missing dev dep

* revert lock

* update lock

* use surrealdb.js

* add rest test

* remove commented-out code

* update readme

* modularize repeated code

* fix(docs): fix default `maxAge` formula (#7406)

* feat(adapters): add Account mapping before database write (#7369)

* feat: map Account before saving to database

* document `acconut()`, explain default behaviour

* generate `expires_at` based on `expires_in`

Fixes #6538

* rename

* strip undefined on `defaultProfile`

* don't forward defaults to account callback

* improve internal namings, types, docs

* chore: improve errors, add more docs (#7415)

* JWT Token -> JWT

* document some errors

* improve errors, docs

* fix: loosen profile types

* chore: type fixes

* fix: allow handling OAuth callback error response

related #7407

* fix(docs): remove extra heading

Fixes #7426

* chore: use `@ts-ignore`

* chore: support release any package as experimental

* chore: separate manual release job

* chore: skip test for manual release

* chore: tweak

* chore: tweaks

* chore: tweak manual release version

* Use query instead of select to be able to use query params

* Fix lint errors

* Update surrealdb.js and remove surrealdb-rest-ts in favor of ExperimentalSurrealHTTP

* update pnpm-lock

* fix merge

* fix merge

* fix merge

* migrate surrealdb.js api

* fix queries

* update package.json

* fix types

* prepare for rest

* update readme

* chore: format PR

* Update README.md

* Update package.json

---------

Co-authored-by: Balázs Orbán <info@balazsorban.com>
Co-authored-by: jakzo <jack@jf.id.au>
Co-authored-by: Victor <saptefrativictor@gmail.com>
Co-authored-by: Thang Vu <hi@thvu.dev>
2023-09-23 21:14:40 +07:00
173 changed files with 1784 additions and 3068 deletions

View File

@@ -1,6 +1,6 @@
name: Bug report
description: Report an issue so we can improve
labels: [triage]
labels: [triage, bug]
body:
- type: markdown
attributes:

View File

@@ -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:

View File

@@ -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"

View File

@@ -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/**/*"]

View File

@@ -39,6 +39,7 @@ on:
options:
- "core"
- "frameworks-nextjs"
- "adapter-edgedb"
- "adapter-dgraph"
- "adapter-drizzle"
- "adapter-dynamodb"

View File

@@ -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"

View File

@@ -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

View File

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

View File

@@ -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)

View File

@@ -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 }

View File

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

View File

@@ -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>
}

View File

@@ -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>
</>
)
}

View File

@@ -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>
)
}

View File

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

View File

@@ -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>
)
}

View File

@@ -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;
}

View File

@@ -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 />
</>
)
}

View File

@@ -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,
// },
// })

View File

@@ -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.

View File

@@ -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 },
}

View File

@@ -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"
}
}

View File

@@ -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>
)
}

View File

@@ -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>
)
}

View File

@@ -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)

View File

@@ -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))
}

View File

@@ -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.",
})
}
}

View File

@@ -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)
}

View File

@@ -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))
}

View File

@@ -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>&lt;/Header&gt;</strong> component.
</p>
<p>
The <strong>useSession()</strong> React Hook easy to use and allows pages to render very quickly.
</p>
<p>
The advantage of this approach is that session state is shared between pages by using the <strong>Provider</strong> in <strong>_app.js</strong> so
that navigation between pages using <strong>useSession()</strong> is very fast.
</p>
<p>
The disadvantage of <strong>useSession()</strong> is that it requires client side JavaScript.
</p>
</Layout>
)
}

View File

@@ -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>
)
}

View File

@@ -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>
)
}

View File

@@ -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>
)
}

View File

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

View File

@@ -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>
)
}

View File

@@ -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,
},
}
}

View File

@@ -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>
)
}

View File

@@ -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),
},
}
}

View File

@@ -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);
}

View File

@@ -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>
)
}

View File

@@ -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,
},
}
}

View File

@@ -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");

View File

@@ -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"

View File

@@ -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])
}

View File

@@ -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"
]
}

View File

@@ -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
}
}

View File

@@ -41,6 +41,6 @@
"pg": "^8.7.3",
"prisma": "^3",
"sqlite3": "^5.0.8",
"typeorm": "0.3.7"
"typeorm": "0.3.17"
}
}

View File

@@ -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

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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.

View File

@@ -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). |

View File

@@ -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,10 +239,13 @@ 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"
},
@@ -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"),
]),

View File

@@ -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": [

View File

@@ -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" },

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

View File

@@ -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",

View File

@@ -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" }],

View File

@@ -28,7 +28,7 @@
"@actions/core": "^1.10.0",
"@balazsorban/monorepo-release": "0.1.8",
"@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",

View 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>
---

View File

@@ -0,0 +1,59 @@
{
"name": "@auth/azure-tables-adapter",
"version": "0.1.0",
"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"
}
}

View 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
}
},
}
}

View 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
}
},
},
})

View 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

View 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"]
}

View File

@@ -49,10 +49,10 @@
"@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": {
"preset": "@auth/adapter-test/jest"
}
}
}

View File

@@ -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"

View File

@@ -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",

View File

@@ -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>
*

View File

@@ -1,6 +1,6 @@
{
"name": "@auth/supabase-adapter",
"version": "0.1.2",
"version": "0.1.3",
"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"
}
}
}

View File

@@ -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

View 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).

View File

@@ -0,0 +1,58 @@
{
"name": "@auth/surrealdb-adapter",
"version": "0.1.0",
"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"
}
}

View 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
},
}
}

View 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
},
},
})

View 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))

View 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))

View 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

View 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",
]
}

View File

@@ -1,6 +1,6 @@
{
"name": "@auth/typeorm-adapter",
"version": "1.0.1",
"version": "1.0.2",
"description": "TypeORM adapter for Auth.js.",
"homepage": "https://authjs.dev/reference/adapter/typeorm",
"repository": "https://github.com/nextauthjs/next-auth",
@@ -57,7 +57,7 @@
"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"
},

View File

@@ -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
}

View File

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

View File

@@ -136,10 +136,7 @@ export async function AuthInternal<
switch (action) {
case "signin":
if ((csrfDisabled || options.csrfTokenVerified) && options.provider) {
const signin = await routes.signin(
request,
options
)
const signin = await routes.signin(request, options)
if (signin.cookies) cookies.push(...signin.cookies)
return { ...signin, cookies }
}
@@ -177,7 +174,7 @@ export async function AuthInternal<
}
break
case "session": {
if (options.csrfTokenVerified) {
if (options.csrfTokenVerified || csrfDisabled) {
const session = await routes.session({
options,
sessionStore,

View File

@@ -118,7 +118,7 @@ export default function AzureADB2C(
return {
id: profile.sub,
name: profile.name,
email: profile.emails[0],
email: profile?.emails?.[0],
image: null,
}
},

View File

@@ -8,17 +8,22 @@
*
* @module providers/linkedin
*/
import type { OAuthConfig, OAuthUserConfig } from "./index.js"
import type { OIDCConfig, OIDCUserConfig } from "./index.js"
export interface LinkedInProfile {
sub: string
name: string
email: string
picture: string
/** @see https://learn.microsoft.com/en-us/linkedin/consumer/integrations/self-serve/sign-in-with-linkedin-v2#response-body-schema */
export interface LinkedInProfile extends Record<string, any> {
sub: string,
name: string,
given_name: string,
family_name: string,
picture: string,
locale: string,
email: string,
email_verified: boolean
}
/**
* Add Linkedin login to your page.
* Add LinkedIn login to your page.
*
* ### Setup
*
@@ -30,27 +35,27 @@ export interface LinkedInProfile {
* #### Configuration
*```js
* import Auth from "@auth/core"
* import Linkedin from "@auth/core/providers/linkedin"
* import LinkedIn from "@auth/core/providers/linkedin"
*
* const request = new Request(origin)
* const response = await Auth(request, {
* providers: [Linkedin({ clientId: LINKEDIN_CLIENT_ID, clientSecret: LINKEDIN_CLIENT_SECRET })],
* providers: [LinkedIn({ clientId: LINKEDIN_CLIENT_ID, clientSecret: LINKEDIN_CLIENT_SECRET })],
* })
* ```
*
* ### Resources
*
* - [Linkedin OAuth documentation](https://docs.microsoft.com/en-us/linkedin/shared/authentication/authorization-code-flow)
* - [Linkedin app console](https://www.linkedin.com/developers/apps/)
* - [LinkedIn OAuth documentation](https://docs.microsoft.com/en-us/linkedin/shared/authentication/authorization-code-flow)
* - [LinkedIn app console](https://www.linkedin.com/developers/apps/)
*
* ### Notes
*
* By default, Auth.js assumes that the Linkedin provider is
* based on the [OAuth 2](https://www.rfc-editor.org/rfc/rfc6749.html) specification.
* By default, Auth.js assumes that the LinkedIn provider is
* based on the [OIDC](https://openid.net/specs/openid-connect-core-1_0.html) specification.
*
* :::tip
*
* The Linkedin provider comes with a [default configuration](https://github.com/nextauthjs/next-auth/blob/main/packages/core/src/providers/linkedin.ts).
* The LinkedIn provider comes with a [default configuration](https://github.com/nextauthjs/next-auth/blob/main/packages/core/src/providers/linkedin.ts).
* To override the defaults for your use case, check out [customizing a built-in OAuth provider](https://authjs.dev/guides/providers/custom-provider#override-default-options).
*
* :::
@@ -66,14 +71,22 @@ export interface LinkedInProfile {
* :::
*/
export default function LinkedIn<P extends LinkedInProfile>(
options: OAuthUserConfig<P>
): OAuthConfig<P> {
options: OIDCUserConfig<P>
): OIDCConfig<P> {
return {
id: "linkedin",
name: "LinkedIn",
type: "oidc",
client: {
token_endpoint_auth_method: "client_secret_post",
client: { token_endpoint_auth_method: "client_secret_post" },
issuer: "https://www.linkedin.com",
jwks_endpoint: "https://www.linkedin.com/oauth/openid/jwks",
async profile(profile) {
return {
id: profile.sub,
name: profile.name,
email: profile.email,
image: profile.picture
}
},
style: {
logo: "/linkedin.svg",

View File

@@ -37,7 +37,7 @@
"./providers/*": "./providers/*.js"
},
"scripts": {
"build": "pnpm clean && pnpm build:js && pnpm build:css",
"build": "echo 'Check out the v4 branch'; exit 0",
"build:js": "pnpm clean && pnpm generate-providers && pnpm tsc --project tsconfig.json && babel --config-file ./config/babel.config.js src --out-dir . --extensions \".tsx,.ts,.js,.jsx\"",
"clean": "rm -rf coverage client css utils providers core jwt react next index.d.ts index.js adapters.d.ts middleware.d.ts middleware.js",
"build:css": "postcss --config config/postcss.config.js src/**/*.css --base src --dir . && node config/wrap-css.js",

View File

@@ -1,4 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="4 32 376.4 449.4" width="32" height="32">
<title>Apple icon</title>
<path fill="#fff" d="M318.7 268.7c-.2-36.7 16.4-64.4 50-84.8-18.8-26.9-47.2-41.7-84.7-44.6-35.5-2.8-74.3 20.7-88.5 20.7-15 0-49.4-19.7-76.4-19.7C63.3 141.2 4 184.8 4 273.5q0 39.3 14.4 81.2c12.8 36.7 59 126.7 107.2 125.2 25.2-.6 43-17.9 75.8-17.9 31.8 0 48.3 17.9 76.4 17.9 48.6-.7 90.4-82.5 102.6-119.3-65.2-30.7-61.7-90-61.7-91.9zm-56.6-164.2c27.3-32.4 24.8-61.9 24-72.5-24.1 1.4-52 16.4-67.9 34.9-17.5 19.8-27.8 44.3-25.6 71.9 26.1 2 49.9-11.4 69.5-34.3z"/>
</svg>

Before

Width:  |  Height:  |  Size: 588 B

View File

@@ -1,4 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="4 32 376.4 449.4" width="32" height="32">
<title>Apple icon</title>
<path d="M318.7 268.7c-.2-36.7 16.4-64.4 50-84.8-18.8-26.9-47.2-41.7-84.7-44.6-35.5-2.8-74.3 20.7-88.5 20.7-15 0-49.4-19.7-76.4-19.7C63.3 141.2 4 184.8 4 273.5q0 39.3 14.4 81.2c12.8 36.7 59 126.7 107.2 125.2 25.2-.6 43-17.9 75.8-17.9 31.8 0 48.3 17.9 76.4 17.9 48.6-.7 90.4-82.5 102.6-119.3-65.2-30.7-61.7-90-61.7-91.9zm-56.6-164.2c27.3-32.4 24.8-61.9 24-72.5-24.1 1.4-52 16.4-67.9 34.9-17.5 19.8-27.8 44.3-25.6 71.9 26.1 2 49.9-11.4 69.5-34.3z"/>
</svg>

Before

Width:  |  Height:  |  Size: 576 B

View File

@@ -1,8 +0,0 @@
<svg viewBox="0.29136862699701993 -41.138268758326056 145.22149045698177 186.73799623391153" xmlns="http://www.w3.org/2000/svg" width="32" height="32">
<linearGradient id="a" gradientTransform="matrix(1 0 0 -1 0 228)" gradientUnits="userSpaceOnUse" x1="62.57" x2="25.03" y1="150.13" y2="85.11">
<stop offset="0" stop-color="#0052cc"/>
<stop offset=".92" stop-color="#2684ff"/>
</linearGradient>
<path d="M43 67a4.14 4.14 0 0 0-5.79-.78A4.29 4.29 0 0 0 36 67.73L.45 138.85a4.25 4.25 0 0 0 1.9 5.7 4.18 4.18 0 0 0 1.9.45h49.53a4.08 4.08 0 0 0 3.8-2.35C68.27 120.57 61.79 87 43 67z" fill="url(#a)"/>
<path d="M69.13 2.28a93.82 93.82 0 0 0-5.48 92.61l23.88 47.76a4.25 4.25 0 0 0 3.8 2.35h49.52a4.24 4.24 0 0 0 4.25-4.25 4.31 4.31 0 0 0-.44-1.9L76.36 2.26a4 4 0 0 0-7.23 0z" fill="#2684ff"/>
</svg>

Before

Width:  |  Height:  |  Size: 810 B

View File

@@ -1,4 +0,0 @@
<svg viewBox="0.29136862699701993 -41.138268758326056 145.22149045698177 186.73799623391153" xmlns="http://www.w3.org/2000/svg" width="32" height="32">
<path d="M43 67a4.14 4.14 0 0 0-5.79-.78A4.29 4.29 0 0 0 36 67.73L.45 138.85a4.25 4.25 0 0 0 1.9 5.7 4.18 4.18 0 0 0 1.9.45h49.53a4.08 4.08 0 0 0 3.8-2.35C68.27 120.57 61.79 87 43 67z" fill="#fff"/>
<path d="M69.13 2.28a93.82 93.82 0 0 0-5.48 92.61l23.88 47.76a4.25 4.25 0 0 0 3.8 2.35h49.52a4.24 4.24 0 0 0 4.25-4.25 4.31 4.31 0 0 0-.44-1.9L76.36 2.26a4 4 0 0 0-7.23 0z" fill="#fff"/>
</svg>

Before

Width:  |  Height:  |  Size: 549 B

View File

@@ -1,12 +0,0 @@
<svg width="32" height="32" viewBox="0 0 41 45" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0)">
<path d="M35.3018 0H20.5L25.0737 14.076H39.8755L27.9009 22.4701L32.4746 36.6253C40.1827 31.081 42.7027 22.6883 39.8755 14.076L35.3018 0Z" fill="white"/>
<path d="M1.12504 14.076H15.9268L20.5005 0H5.69875L1.12504 14.076C-1.70213 22.6898 0.8178 31.081 8.52592 36.6253L13.0996 22.4701L1.12504 14.076Z" fill="white"/>
<path d="M8.52539 36.6251L20.5 44.9998L32.4746 36.6251L20.5 28.1084L8.52539 36.6251Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0">
<rect width="41" height="45" fill="none"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 677 B

View File

@@ -1,3 +0,0 @@
<svg width="32" height="32" viewBox="0 0 256 287" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMinYMin meet">
<path d="M203.24 231.531l-28.73-88.434 75.208-54.64h-92.966L128.019.025l-.009-.024h92.98l28.74 88.446.002-.002.024-.013c16.69 51.31-.5 109.67-46.516 143.098zm-150.45 0l-.023.017 75.228 54.655 75.245-54.67-75.221-54.656-75.228 54.654zM6.295 88.434c-17.57 54.088 2.825 111.4 46.481 143.108l.007-.028 28.735-88.429-75.192-54.63h92.944L128.004.024 128.01 0H35.025L6.294 88.434z" fill="#EB5424"/>
</svg>

Before

Width:  |  Height:  |  Size: 523 B

View File

@@ -1,3 +0,0 @@
<svg viewBox="0 0 59.242 47.271" width="32" height="32" xmlns="http://www.w3.org/2000/svg">
<path d="m32.368 0-17.468 15.145-14.9 26.75h13.437zm2.323 3.543-7.454 21.008 14.291 17.956-27.728 4.764h45.442z" fill="#fff"/>
</svg>

Before

Width:  |  Height:  |  Size: 228 B

View File

@@ -1,3 +0,0 @@
<svg viewBox="0 0 59.242 47.271" width="32" height="32" xmlns="http://www.w3.org/2000/svg">
<path d="m32.368 0-17.468 15.145-14.9 26.75h13.437zm2.323 3.543-7.454 21.008 14.291 17.956-27.728 4.764h45.442z" fill="#0072c6"/>
</svg>

Before

Width:  |  Height:  |  Size: 231 B

View File

@@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 580.44" width="32" height="32">
<path d="M473.49,197.63c-75.94-35.11-185.08-57.42-287.78-49.11,5.15-34,17.85-57.69,38.7-62.68,28.69-6.88,60,12,89.84,46.35,19.55,2.53,42.73,7,58.86,10.71C318.7,40.56,245.72-16.8,190.21,4.36,148,20.47,126.39,78.59,129,156.69c-55,11.7-97.87,32.49-125.31,62.39-1.39,1.61-4.53,5.67-3.41,7.61.85,1.47,3.65-.18,4.85-1,31.83-22.26,72.58-34.31,125.66-41.89,7.56,83.32,42.81,189,101.36,273.78-32,12.56-58.89,13.39-73.64-2.17-20.29-21.41-19.61-57.95-4.77-101-7.58-18.2-15.31-40.51-20.15-56.34C72.12,396.41,58.93,488.29,105,525.78c35.07,28.52,96.18,18.15,162.54-23.12,37.64,41.79,77.07,68.51,116.69,77.33,2.09.39,7.17,1.09,8.29-.85.85-1.47-2-3.08-3.28-3.71-35.19-16.44-66-45.71-99.1-87.88C358.53,439.34,432.42,356,476.57,262.88c26.9,21.47,41,44.3,34.94,64.85-8.4,28.29-40.38,46-85.06,54.63C414.48,398,399,415.86,387.74,428c115.84,4,202-30.47,211.43-89.12,7.17-44.64-32.37-92.38-101.3-129.21,17.38-53.49,20.8-101,8.63-139.72-.71-2-2.64-6.76-4.88-6.76-1.7,0-1.68,3.26-1.58,4.7C503.41,106.55,493.47,147.88,473.49,197.63ZM260.21,444.33c-49-78.61-77.24-171.21-77.06-264.84h0C275.71,176.39,370,198.2,451,245.17h0c-43.59,81.71-109.64,152.49-190.82,199.15Z" fill="#fff"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 580.44" width="32" height="32">
<path d="M473.49,197.63c-75.94-35.11-185.08-57.42-287.78-49.11,5.15-34,17.85-57.69,38.7-62.68,28.69-6.88,60,12,89.84,46.35,19.55,2.53,42.73,7,58.86,10.71C318.7,40.56,245.72-16.8,190.21,4.36,148,20.47,126.39,78.59,129,156.69c-55,11.7-97.87,32.49-125.31,62.39-1.39,1.61-4.53,5.67-3.41,7.61.85,1.47,3.65-.18,4.85-1,31.83-22.26,72.58-34.31,125.66-41.89,7.56,83.32,42.81,189,101.36,273.78-32,12.56-58.89,13.39-73.64-2.17-20.29-21.41-19.61-57.95-4.77-101-7.58-18.2-15.31-40.51-20.15-56.34C72.12,396.41,58.93,488.29,105,525.78c35.07,28.52,96.18,18.15,162.54-23.12,37.64,41.79,77.07,68.51,116.69,77.33,2.09.39,7.17,1.09,8.29-.85.85-1.47-2-3.08-3.28-3.71-35.19-16.44-66-45.71-99.1-87.88C358.53,439.34,432.42,356,476.57,262.88c26.9,21.47,41,44.3,34.94,64.85-8.4,28.29-40.38,46-85.06,54.63C414.48,398,399,415.86,387.74,428c115.84,4,202-30.47,211.43-89.12,7.17-44.64-32.37-92.38-101.3-129.21,17.38-53.49,20.8-101,8.63-139.72-.71-2-2.64-6.76-4.88-6.76-1.7,0-1.68,3.26-1.58,4.7C503.41,106.55,493.47,147.88,473.49,197.63ZM260.21,444.33c-49-78.61-77.24-171.21-77.06-264.84h0C275.71,176.39,370,198.2,451,245.17h0c-43.59,81.71-109.64,152.49-190.82,199.15Z" fill="#148eff"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -1,6 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 444.893 245.414">
<g fill="#fff">
<path d="M239.038 72.43c-33.081 0-61.806 18.6-76.322 45.904-14.516-27.305-43.24-45.902-76.32-45.902-19.443 0-37.385 6.424-51.821 17.266V16.925h-.008C34.365 7.547 26.713 0 17.286 0 7.858 0 .208 7.547.008 16.925H0v143.333h.036c.768 47.051 39.125 84.967 86.359 84.967 33.08 0 61.805-18.603 76.32-45.908 14.517 27.307 43.241 45.906 76.321 45.906 47.715 0 86.396-38.684 86.396-86.396.001-47.718-38.682-86.397-86.394-86.397zM86.395 210.648c-28.621 0-51.821-23.201-51.821-51.82 0-28.623 23.201-51.823 51.821-51.823 28.621 0 51.822 23.2 51.822 51.823 0 28.619-23.201 51.82-51.822 51.82zm152.643 0c-28.622 0-51.821-23.201-51.821-51.822 0-28.623 23.2-51.821 51.821-51.821 28.619 0 51.822 23.198 51.822 51.821-.001 28.621-23.203 51.822-51.822 51.822z"/>
<path d="M441.651 218.033l-44.246-59.143 44.246-59.144-.008-.007c5.473-7.62 3.887-18.249-3.652-23.913-7.537-5.658-18.187-4.221-23.98 3.157l-.004-.002-38.188 51.047-38.188-51.047-.006.009c-5.793-7.385-16.441-8.822-23.981-3.16-7.539 5.664-9.125 16.293-3.649 23.911l-.008.005 44.245 59.144-44.245 59.143.008.005c-5.477 7.62-3.89 18.247 3.649 23.909 7.54 5.664 18.188 4.225 23.981-3.155l.006.007 38.188-51.049 38.188 51.049.004-.002c5.794 7.377 16.443 8.814 23.98 3.154 7.539-5.662 9.125-16.291 3.652-23.91l.008-.008z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

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