Compare commits

..

20 Commits

Author SHA1 Message Date
ndom91
307ca24cb8 Merge branch 'main' into ndom91/webauthn-test 2023-05-09 19:12:56 +02:00
Balázs Orbán
b96f01319c chore: tweak manual release version 2023-05-05 02:38:47 +02:00
Balázs Orbán
8f416b68ec chore: tweaks 2023-05-04 23:58:54 +02:00
Balázs Orbán
eaf5080721 chore: tweak 2023-05-04 23:50:49 +02:00
Balázs Orbán
e0b5f18c5b chore: skip test for manual release 2023-05-04 23:42:05 +02:00
Balázs Orbán
99247ce446 chore: separate manual release job 2023-05-04 23:40:37 +02:00
Balázs Orbán
d6bc65f0d8 chore: support release any package as experimental 2023-05-04 23:25:29 +02:00
Balázs Orbán
6f5a50313f chore: use @ts-ignore 2023-05-04 22:21:36 +02:00
Balázs Orbán
e3bdb38df2 fix(docs): remove extra heading
Fixes #7426
2023-05-03 12:40:30 +02:00
Balázs Orbán
92a0fc42fa fix: allow handling OAuth callback error response
related #7407
2023-05-01 13:49:17 +02:00
Balázs Orbán
62e2ad115c chore: type fixes 2023-05-01 13:46:23 +02:00
Balázs Orbán
542c35d729 fix: loosen profile types 2023-05-01 13:22:16 +02:00
Balázs Orbán
5400645221 chore: improve errors, add more docs (#7415)
* JWT Token -> JWT

* document some errors

* improve errors, docs
2023-05-01 10:32:20 +01:00
Balázs Orbán
d739e8e04e 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
2023-04-30 12:25:26 +01:00
Victor
c2eb9b3ad4 fix(docs): fix default maxAge formula (#7406) 2023-04-30 10:34:05 +02:00
Balázs Orbán
5e6b461908 Merge branch 'main' into ndom91/webauthn-test 2023-02-10 02:26:41 +01:00
ndom91
2f7f9ccfe3 feat: add simplewebauthn 2022-12-18 17:53:00 +01:00
Nico Domino
65ee6756bc Merge branch 'main' into ndom91/webauthn-test 2022-12-18 15:32:20 +01:00
Nico Domino
963e407d5f Merge branch 'main' into ndom91/webauthn-test 2022-12-18 00:36:10 +01:00
ndom91
4e294edec1 feat: test webauthn platform auth 2022-12-15 03:31:32 +01:00
231 changed files with 7906 additions and 8849 deletions

View File

@@ -37,7 +37,6 @@ body:
- "Bungie"
- "Cognito"
- "Coinbase"
- "Descope"
- "Discord"
- "Dropbox"
- "EVE Online"

View File

@@ -21,23 +21,20 @@ body:
multiple: true
options:
- "Custom adapter"
- "@auth/dgraph-adapter"
- "@auth/drizzle-adapter"
- "@auth/dynamodb-adapter"
- "@auth/drizzle-adapter"
- "@auth/fauna-adapter"
- "@auth/firebase-adapter"
- "@auth/kysely-adapter"
- "@auth/mikro-orm-adapter"
- "@auth/mongodb-adapter"
- "@auth/neo4j-adapter"
- "@auth/pouchdb-adapter"
- "@auth/prisma-adapter"
- "@auth/sequelize-adapter"
- "@auth/supabase-adapter"
- "@auth/typeorm-adapter"
- "@auth/upstash-redis-adapter"
- "@auth/xata-adapter"
- "@next-auth/dgraph-adapter"
- "@next-auth/dynamodb-adapter"
- "@next-auth/fauna-adapter"
- "@next-auth/firebase-adapter"
- "@next-auth/mikro-orm-adapter"
- "@next-auth/mongodb-adapter"
- "@next-auth/neo4j-adapter"
- "@next-auth/pouchdb-adapter"
- "@next-auth/prisma-adapter"
- "@next-auth/sequelize-adapter"
- "@next-auth/supabase-adapter"
- "@next-auth/typeorm-legacy-adapter"
- "@next-auth/upstash-redis-adapter"
- "@next-auth/xata-adapter"
validations:
required: true
- type: textarea

View File

@@ -1,49 +1,43 @@
# https://github.com/github/issue-labeler#basic-examples
dgraph:
- "@auth/dgraph-adapter"
drizzle:
- "@auth/drizzle-adapter"
- "@next-auth/dgraph-adapter"
dynamodb:
- "@auth/dynamodb-adapter"
- "@next-auth/dynamodb-adapter"
fauna:
- "@auth/fauna-adapter"
- "@next-auth/fauna-adapter"
firebase:
- "@auth/firebase-adapter"
kysely:
- "@auth/kysely-adapter"
- "@next-auth/firebase-adapter"
mikro-orm:
- "@auth/mikro-orm-adapter"
- "@next-auth/mikro-orm-adapter"
mongodb:
- "@auth/mongodb-adapter"
- "@next-auth/mongodb-adapter"
neo4j:
- "@auth/neo4j-adapter"
- "@next-auth/neo4j-adapter"
pouchdb:
- "@auth/pouchdb-adapter"
- "@next-auth/pouchdb-adapter"
prisma:
- "@auth/prisma-adapter"
- "@next-auth/prisma-adapter"
sequelize:
- "@auth/sequelize-adapter"
- "@next-auth/sequelize-adapter"
supabase:
- "@auth/supabase-adapter"
- "@next-auth/supabase-adapter"
typeorm:
- "@auth/typeorm-adapter"
typeorm-legacy:
- "@next-auth/typeorm-legacy-adapter"
upstash-redis:
- "@auth/upstash-redis-adapter"
- "@next-auth/upstash-redis-adapter"
xata:
- "@auth/xata-adapter"
- "@next-auth/xata-adapter"

View File

@@ -15,13 +15,12 @@ neo4j: ["packages/adapter-neo4j/**/*"]
playgrounds: ["apps/playgrounds/**/*"]
pouchdb: ["packages/adapter-pouchdb/**/*"]
prisma: ["packages/adapter-prisma/**/*"]
kysely: ["packages/adapter-kysely/**/*"]
providers: ["packages/core/src/providers/**/*"]
sequelize: ["packages/adapter-sequelize/**/*"]
solidjs: ["packages/frameworks-solid-start/**/*"]
supabase: ["packages/adapter-supabase/**/*"]
svelte: ["packages/frameworks-sveltekit/**/*"]
test: ["**test**/*"]
typeorm: ["packages/adapter-typeorm/**/*"]
typeorm-legacy: ["packages/adapter-typeorm-legacy/**/*"]
upstash-redis: ["packages/adapter-upstash-redis/**/*"]
xata: ["packages/adapter-xata/**/*"]

View File

@@ -11,55 +11,21 @@ on:
# TODO: Support latest releases
workflow_dispatch:
inputs:
name:
name:
type: choice
description: Package name (npm)
options:
- "@auth/core"
- "@auth/nextjs"
- "@auth/dgraph-adapter"
- "@auth/drizzle-adapter"
- "@auth/dynamodb-adapter"
- "@auth/fauna-adapter"
- "@auth/firebase-adapter"
- "@auth/mikro-orm-adapter"
- "@auth/mongodb-adapter"
- "@auth/neo4j-adapter"
- "@auth/pouchdb-adapter"
- "@auth/prisma-adapter"
- "@auth/sequelize-adapter"
- "@auth/supabase-adapter"
- "@auth/typeorm-adapter"
- "@auth/upstash-redis-adapter"
- "@auth/xata-adapter"
- "next-auth"
- "@auth/nextjs"
- "@auth/core"
- "next-auth"
# TODO: Infer from package name
path:
type: choice
description: Directory name (packages/*)
options:
- "core"
- "frameworks-nextjs"
- "adapter-dgraph"
- "adapter-drizzle"
- "adapter-dynamodb"
- "adapter-fauna"
- "adapter-firebase"
- "adapter-mikro-orm"
- "adapter-mongodb"
- "adapter-neo4j"
- "adapter-pouchdb"
- "adapter-prisma"
- "adapter-sequelize"
- "adapter-supabase"
- "adapter-typeorm"
- "adapter-upstash-redis"
- "adapter-xata"
- "next-auth"
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
FORCE_COLOR: true
- "frameworks-nextjs"
- "core"
- "next-auth"
jobs:
test:
@@ -76,17 +42,16 @@ jobs:
uses: actions/setup-node@v3
with:
node-version: 18
cache: "pnpm"
- name: Install dependencies
run: pnpm install
- name: Build
run: pnpm build
- name: Run tests
run: pnpm test
timeout-minutes: 15
env:
UPSTASH_REDIS_URL: ${{ secrets.UPSTASH_REDIS_URL }}
UPSTASH_REDIS_KEY: ${{ secrets.UPSTASH_REDIS_KEY }}
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
# - name: Run E2E tests
# if: github.repository == 'nextauthjs/next-auth'
# run: pnpm e2e
@@ -95,7 +60,7 @@ jobs:
# AUTH0_USERNAME: ${{ secrets.AUTH0_USERNAME }}
# AUTH0_PASSWORD: ${{ secrets.AUTH0_PASSWORD }}
# TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
# TURBO_TEAM: ${{ vars.TURBO_TEAM }}
# TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
# - name: Upload E2E artifacts
# if: github.repository == 'nextauthjs/next-auth'
# uses: actions/upload-artifact@v3
@@ -126,7 +91,6 @@ jobs:
uses: actions/setup-node@v3
with:
node-version: 18
cache: "pnpm"
- name: Install dependencies
run: pnpm install
- name: Publish to npm and GitHub
@@ -151,7 +115,6 @@ jobs:
uses: actions/setup-node@v3
with:
node-version: 18
cache: "pnpm"
- name: Install dependencies
run: pnpm install
- name: Determine version
@@ -190,7 +153,6 @@ jobs:
uses: actions/setup-node@v3
with:
node-version: 18
cache: "pnpm"
- name: Install dependencies
run: pnpm install
- name: Determine version
@@ -200,7 +162,6 @@ jobs:
PACKAGE_PATH: ${{ github.event.inputs.path }}
- name: Publish to npm
run: |
pnpm build
cd packages/$PACKAGE_PATH
echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> .npmrc
pnpm publish --no-git-checks --access public --tag experimental

8
.gitignore vendored
View File

@@ -6,8 +6,6 @@
.env.development.local
.env.test.local
.env.production.local
packages/*/.npmrc
npm-debug.log*
yarn-debug.log*
@@ -40,7 +38,6 @@ packages/next-auth/next
packages/*/*.js
packages/*/*.d.ts
packages/*/*.d.ts.map
packages/*/lib
# Development app
apps/dev/src/css
@@ -67,7 +64,6 @@ packages/adapter-prisma/prisma/dev.db
packages/adapter-prisma/prisma/migrations
db.sqlite
packages/adapter-supabase/supabase/.branches
packages/adapter-drizzle/.drizzle
# Tests
coverage
@@ -100,7 +96,5 @@ packages/frameworks-sveltekit/vite.config.js.timestamp-*
packages/frameworks-sveltekit/vite.config.ts.timestamp-*
# Adapters
docs/docs/reference/adapter
## Drizzle migration folder
.drizzle
docs/docs/reference/adapter

153
README.md
View File

@@ -1,153 +0,0 @@
<p align="center">
<br/>
<a href="https://authjs.dev" target="_blank"><img width="96px" src="https://authjs.dev/img/logo/logo-sm.png" /></a>
<h3 align="center">Auth.js</h3>
<p align="center">Authentication for the Web.</p>
<p align="center">Open Source. Full Stack. Own Your Data.</p>
<p align="center" style="align: center;">
<a href="https://npm.im/@auth/prisma-adapter">
<img src="https://img.shields.io/badge/TypeScript-blue?style=flat-square" alt="TypeScript" />
</a>
<a href="https://www.npmtrends.com/next-auth">
<img src="https://img.shields.io/npm/dm/next-auth?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>
<a href="https://www.npmjs.com/package/next-auth">
<img src="https://img.shields.io/github/v/release/nextauthjs/next-auth?label=latest&style=flat-square" alt="Github Stable Release" />
</a>
</p>
</p>
Auth.js is a set of open-source packages that are built on Web Standard APIs for authentication in modern applications with any framework on any platform in any JS runtime.
See [authjs.dev](https://authjs.dev) for our framework-specific libraries, or check out [next-auth.js.org](https://next-auth.js.org) for `next-auth` (Next.js).
## Features
### Flexible and easy to use
- Designed to work with any OAuth service, it supports 2.0+, OIDC
- Built-in support for [many popular sign-in services](https://github.com/nextauthjs/next-auth/tree/main/packages/core/src/providers)
- Email/Passwordless authentication
- Bring Your Database - or none! - stateless authentication with any backend (Active Directory, LDAP, etc.)
- Runtime-agnostic, runs anywhere! (Vercel Edge Functions, Node.js, Serverless, etc.)
### Own your data
Auth.js can be used with or without a database.
- An open-source solution that allows you to keep control of your data
- Built-in support for [MySQL, MariaDB, Postgres, Microsoft SQL Server, MongoDB, SQLite, etc.](https://adapters.authjs.dev)
- Works great with databases from popular hosting providers
### Secure by default
- Promotes the use of passwordless sign-in mechanisms
- Designed to be secure by default and encourage best practices for safeguarding user data
- Uses Cross-Site Request Forgery (CSRF) Tokens on POST routes (sign in, sign out)
- Default cookie policy aims for the most restrictive policy appropriate for each cookie
- When JSON Web Tokens are used, they are encrypted by default (JWE) with A256GCM
- Features tab/window syncing and session polling to support short-lived sessions
- Attempts to implement the latest guidance published by [Open Web Application Security Project](https://owasp.org)
Advanced configuration allows you to define your routines to handle controlling what accounts are allowed to sign in, for encoding and decoding JSON Web Tokens and to set custom cookie security policies and session properties, so you can control who can sign in and how often sessions have to be re-validated.
### TypeScript
Auth.js libraries are written with type safety in mind. [Check out the docs](https://authjs.dev/getting-started/typescript) for more information.
## Security
If you think you have found a vulnerability (or are not sure) in Auth.js or any of the related packages (i.e. Adapters), we ask you to read our [Security Policy](https://authjs.dev/security) to reach out responsibly. Please do not open Pull Requests/Issues/Discussions before consulting with us.
## Acknowledgments
[Auth.js is made possible thanks to all of its contributors.](https://authjs.dev/contributors)
<a href="https://github.com/nextauthjs/next-auth/graphs/contributors">
<img width="500px" src="https://contrib.rocks/image?repo=nextauthjs/next-auth" />
</a>
<div>
<a href="https://vercel.com?utm_source=nextauthjs&utm_campaign=oss"></a>
</div>
### Support
We have an [OpenCollective](https://opencollective.com/nextauth) for individuals and companies looking to contribute financially to the project!
<!--sponsors start-->
<table>
<tbody>
<tr>
<td align="center" valign="top">
<a href="https://vercel.com" target="_blank">
<img width="128px" src="https://avatars.githubusercontent.com/u/14985020?v=4" alt="Vercel Logo" />
</a><br />
<div>Vercel</div><br />
<sub>🥉 Bronze Financial Sponsor <br /> ☁️ Infrastructure Support</sub>
</td>
<td align="center" valign="top">
<a href="https://prisma.io" target="_blank">
<img width="128px" src="https://avatars.githubusercontent.com/u/17219288?v=4" alt="Prisma Logo" />
</a><br />
<div>Prisma</div><br />
<sub>🥉 Bronze Financial Sponsor</sub>
</td>
<td align="center" valign="top">
<a href="https://clerk.com" target="_blank">
<img width="128px" src="https://avatars.githubusercontent.com/u/49538330?s=200&v=4" alt="Clerk Logo" />
</a><br />
<div>Clerk</div><br />
<sub>🥉 Bronze Financial Sponsor</sub>
</td>
<td align="center" valign="top">
<a href="https://lowdefy.com" target="_blank">
<img width="128px" src="https://avatars.githubusercontent.com/u/47087496?s=200&v=4" alt="Lowdefy Logo" />
</a><br />
<div>Lowdefy</div><br />
<sub>🥉 Bronze Financial Sponsor</sub>
</td>
<td align="center" valign="top">
<a href="https://workos.com" target="_blank">
<img width="128px" src="https://avatars.githubusercontent.com/u/47638084?s=200&v=4" alt="WorkOS Logo" />
</a><br />
<div>WorkOS</div><br />
<sub>🥉 Bronze Financial Sponsor</sub>
</td>
<td align="center" valign="top">
<a href="https://www.descope.com" target="_blank">
<img width="128px" src="https://avatars.githubusercontent.com/u/97479186?v=4" alt="Descope Logo" />
</a><br />
<div>Descope</div><br />
<sub>🥉 Bronze Financial Sponsor</sub>
</td>
<td align="center" valign="top">
<a href="https://checklyhq.com" target="_blank">
<img width="128px" src="https://avatars.githubusercontent.com/u/25982255?v=4" alt="Checkly Logo" />
</a><br />
<div>Checkly</div><br />
<sub>☁️ Infrastructure Support</sub>
</td>
<td align="center" valign="top">
<a href="https://superblog.ai/" target="_blank">
<img width="128px" src="https://d33wubrfki0l68.cloudfront.net/cdc4a3833bd878933fcc131655878dbf226ac1c5/10cd6/images/logo_bolt_small.png" alt="superblog Logo" />
</a><br />
<div>superblog</div><br />
<sub>☁️ Infrastructure Support</sub>
</td>
</tr><tr></tr>
</tbody>
</table>
<br />
<!--sponsors end-->
## Contributing
We're open to all community contributions! If you'd like to contribute in any way, please first read
our [Contributing Guide](https://github.com/nextauthjs/.github/blob/main/CONTRIBUTING.md).
## License
ISC

1
README.md Symbolic link
View File

@@ -0,0 +1 @@
packages/next-auth/README.md

View File

@@ -13,9 +13,6 @@ AUTH0_ID=
AUTH0_SECRET=
AUTH0_ISSUER=
DESCOPE_ID=
DESCOPE_SECRET=
KEYCLOAK_ID=
KEYCLOAK_SECRET=
KEYCLOAK_ISSUER=

View File

@@ -3,4 +3,4 @@
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)
](https://github.com/nextauthjs/next-auth/blob/main/CONTRIBUTING.md#setting-up-local-environment)

View File

@@ -14,10 +14,10 @@
},
"license": "ISC",
"dependencies": {
"@auth/fauna-adapter": "workspace:*",
"@auth/prisma-adapter": "workspace:*",
"@auth/supabase-adapter": "workspace:*",
"@auth/typeorm-adapter": "workspace:*",
"@next-auth/fauna-adapter": "workspace:*",
"@next-auth/prisma-adapter": "workspace:*",
"@next-auth/supabase-adapter": "workspace:*",
"@next-auth/typeorm-legacy-adapter": "workspace:*",
"@prisma/client": "^3",
"@supabase/supabase-js": "^2.0.5",
"faunadb": "^4",

View File

@@ -37,22 +37,22 @@ import WorkOS from "next-auth/providers/workos"
// // Prisma
// import { PrismaClient } from "@prisma/client"
// import { PrismaAdapter } from "@auth/prisma-adapter"
// import { PrismaAdapter } from "@next-auth/prisma-adapter"
// const client = globalThis.prisma || new PrismaClient()
// if (process.env.NODE_ENV !== "production") globalThis.prisma = client
// const adapter = PrismaAdapter(client)
// // Fauna
// import { Client as FaunaClient } from "faunadb"
// import { FaunaAdapter } from "@auth/fauna-adapter"
// import { FaunaAdapter } from "@next-auth/fauna-adapter"
// const opts = { secret: process.env.FAUNA_SECRET, domain: process.env.FAUNA_DOMAIN }
// const client = globalThis.fauna || new FaunaClient(opts)
// if (process.env.NODE_ENV !== "production") globalThis.fauna = client
// const adapter = FaunaAdapter(client)
// // TypeORM
// import { TypeORMAdapter } from "@auth/typeorm-adapter"
// const adapter = TypeORMAdapter({
// import { TypeORMLegacyAdapter } from "@next-auth/typeorm-legacy-adapter"
// const adapter = TypeORMLegacyAdapter({
// type: "sqlite",
// name: "next-auth-test-memory",
// database: "./typeorm/dev.db",
@@ -60,7 +60,7 @@ import WorkOS from "next-auth/providers/workos"
// })
// // Supabase
// import { SupabaseAdapter } from "@auth/supabase-adapter"
// import { SupabaseAdapter } from "@next-auth/supabase-adapter"
// const adapter = SupabaseAdapter({
// url: process.env.NEXT_PUBLIC_SUPABASE_URL,
// secret: process.env.SUPABASE_SERVICE_ROLE_KEY,
@@ -78,130 +78,45 @@ export const authOptions: NextAuthOptions = {
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: "",
}
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,
}),
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",
}),
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,
}),
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,
}),
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,
}),
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,
}),
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,
}),
Wikimedia({ clientId: process.env.WIKIMEDIA_ID, clientSecret: process.env.WIKIMEDIA_SECRET }),
WorkOS({ clientId: process.env.WORKOS_ID, clientSecret: process.env.WORKOS_SECRET }),
],
}

View File

@@ -22,9 +22,6 @@ BEYOND_IDENTITY_CLIENT_ID=
BEYOND_IDENTITY_CLIENT_SECRET=
BEYOND_IDENTITY_ISSUER=
DESCOPE_ID=
DESCOPE_SECRET=
GITHUB_ID=
GITHUB_SECRET=

View File

@@ -15,14 +15,14 @@
"license": "ISC",
"dependencies": {
"@auth/core": "workspace:*",
"@auth/fauna-adapter": "workspace:*",
"@auth/prisma-adapter": "workspace:*",
"@auth/supabase-adapter": "workspace:*",
"@auth/typeorm-adapter": "workspace:*",
"@next-auth/fauna-adapter": "workspace:*",
"@next-auth/prisma-adapter": "workspace:*",
"@next-auth/supabase-adapter": "workspace:*",
"@next-auth/typeorm-legacy-adapter": "workspace:*",
"@prisma/client": "^3",
"@supabase/supabase-js": "^2.0.5",
"faunadb": "^4",
"next": "13.4.0",
"next": "13.3.0",
"next-auth": "workspace:*",
"nodemailer": "^6",
"react": "^18",

View File

@@ -2,31 +2,28 @@ import { Auth, type AuthConfig } from "@auth/core"
// Providers
import Apple from "@auth/core/providers/apple"
// import Asgardeo from "@auth/core/providers/asgardeo"
import Asgardeo from "@auth/core/providers/asgardeo"
import Auth0 from "@auth/core/providers/auth0"
import AzureAD from "@auth/core/providers/azure-ad"
import AzureB2C from "@auth/core/providers/azure-ad-b2c"
// import BeyondIdentity from "@auth/core/providers/beyondidentity"
import BoxyHQSAML from "@auth/core/providers/boxyhq-saml"
// import Cognito from "@auth/core/providers/cognito"
import Credentials from "@auth/core/providers/credentials"
import Descope from "@auth/core/providers/descope"
import Discord from "@auth/core/providers/discord"
import DuendeIDS6 from "@auth/core/providers/duende-identity-server6"
// import Discord from "@auth/core/providers/discord"
// import DuendeIDS6 from "@auth/core/providers/duende-identity-server6"
// import Email from "@auth/core/providers/email"
import Facebook from "@auth/core/providers/facebook"
import Foursquare from "@auth/core/providers/foursquare"
import Freshbooks from "@auth/core/providers/freshbooks"
import GitHub from "@auth/core/providers/github"
import Gitlab from "@auth/core/providers/gitlab"
import Google from "@auth/core/providers/google"
// import Facebook from "@auth/core/providers/facebook"
// import Foursquare from "@auth/core/providers/foursquare"
// import Freshbooks from "@auth/core/providers/freshbooks"
// import GitHub from "@auth/core/providers/github"
// import Gitlab from "@auth/core/providers/gitlab"
// import Google from "@auth/core/providers/google"
// import IDS4 from "@auth/core/providers/identity-server4"
import Instagram from "@auth/core/providers/instagram"
// import Instagram from "@auth/core/providers/instagram"
// import Keycloak from "@auth/core/providers/keycloak"
import Line from "@auth/core/providers/line"
import LinkedIn from "@auth/core/providers/linkedin"
import Mailchimp from "@auth/core/providers/mailchimp"
import Notion from "@auth/core/providers/notion"
// import Line from "@auth/core/providers/line"
// import LinkedIn from "@auth/core/providers/linkedin"
// import Mailchimp from "@auth/core/providers/mailchimp"
// import Okta from "@auth/core/providers/okta"
import Osu from "@auth/core/providers/osu"
import Patreon from "@auth/core/providers/patreon"
@@ -42,22 +39,22 @@ import WorkOS from "@auth/core/providers/workos"
// // Prisma
// import { PrismaClient } from "@prisma/client"
// import { PrismaAdapter } from "@auth/prisma-adapter"
// import { PrismaAdapter } from "@next-auth/prisma-adapter"
// const client = globalThis.prisma || new PrismaClient()
// if (process.env.NODE_ENV !== "production") globalThis.prisma = client
// const adapter = PrismaAdapter(client)
// // Fauna
// import { Client as FaunaClient } from "faunadb"
// import { FaunaAdapter } from "@auth/fauna-adapter"
// import { FaunaAdapter } from "@next-auth/fauna-adapter"
// const opts = { secret: process.env.FAUNA_SECRET, domain: process.env.FAUNA_DOMAIN }
// const client = globalThis.fauna || new FaunaClient(opts)
// if (process.env.NODE_ENV !== "production") globalThis.fauna = client
// const adapter = FaunaAdapter(client)
// // TypeORM
// import { TypeORMAdapter } from "@auth/typeorm-adapter"
// const adapter = TypeORMAdapter({
// import { TypeORMLegacyAdapter } from "@next-auth/typeorm-legacy-adapter"
// const adapter = TypeORMLegacyAdapter({
// type: "sqlite",
// name: "next-auth-test-memory",
// database: "./typeorm/dev.db",
@@ -65,7 +62,7 @@ import WorkOS from "@auth/core/providers/workos"
// })
// // Supabase
// import { SupabaseAdapter } from "@auth/supabase-adapter"
// import { SupabaseAdapter } from "@next-auth/supabase-adapter"
// const adapter = SupabaseAdapter({
// url: process.env.NEXT_PUBLIC_SUPABASE_URL,
// secret: process.env.SUPABASE_SERVICE_ROLE_KEY,
@@ -78,16 +75,25 @@ export const authConfig: AuthConfig = {
logo: "https://next-auth.js.org/img/logo/logo-sm.png",
brandColor: "#1786fb",
},
pages: {
signIn: "/auth/signin",
},
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: "" }
// 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 as string }),
// Asgardeo({ clientId: process.env.ASGARDEO_CLIENT_ID, clientSecret: process.env.ASGARDEO_CLIENT_SECRET, issuer: process.env.ASGARDEO_ISSUER }),
Apple({ clientId: process.env.APPLE_ID, clientSecret: process.env.APPLE_SECRET }),
Asgardeo({ clientId: process.env.ASGARDEO_CLIENT_ID, clientSecret: process.env.ASGARDEO_CLIENT_SECRET, issuer: process.env.ASGARDEO_ISSUER }),
Auth0({ clientId: process.env.AUTH0_ID, clientSecret: process.env.AUTH0_SECRET, issuer: process.env.AUTH0_ISSUER }),
AzureAD({
clientId: process.env.AZURE_AD_CLIENT_ID,
@@ -95,20 +101,15 @@ export const authConfig: AuthConfig = {
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 }),
// BeyondIdentity({
// clientId: process.env.BEYOND_IDENTITY_CLIENT_ID,
// clientSecret: process.env.BEYOND_IDENTITY_CLIENT_SECRET,
// issuer: process.env.BEYOND_IDENTITY_ISSUER,
// }),
BeyondIdentity({ clientId: process.env.BEYOND_IDENTITY_CLIENT_ID, clientSecret: process.env.BEYOND_IDENTITY_CLIENT_SECRET, issuer: process.env.BEYOND_IDENTITY_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 }),
Descope({ clientId: process.env.DESCOPE_ID, clientSecret: process.env.DESCOPE_SECRET }),
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, redirectProxyUrl: process.env.AUTH_REDIRECT_PROXY_URL }),
GitHub({ clientId: process.env.GITHUB_ID, clientSecret: process.env.GITHUB_SECRET, redirectProxy: process.env.AUTH_REDIRECT_PROXY_URL }),
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 }),
@@ -117,7 +118,7 @@ export const authConfig: AuthConfig = {
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 }),
Notion({ clientId: process.env.NOTION_ID, clientSecret: process.env.NOTION_SECRET, redirectUri: process.env.NOTION_REDIRECT_URI as string }),
Notion({ clientId: process.env.NOTION_ID, clientSecret: process.env.NOTION_SECRET, redirectUri: process.env.NOTION_REDIRECT_URI }),
// 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 }),
@@ -162,4 +163,4 @@ function AuthHandler(...args: any[]) {
export default AuthHandler(authConfig)
export const config = { runtime: "edge" }
export const config = { runtime: "experimental-edge" }

View File

@@ -0,0 +1,196 @@
import { useEffect, useState } from "react"
import "./style.css"
export default function() {
const [email, setEmail] = useState("")
const [name, setName] = useState("")
const [credentialId, setCredentialId] = useState()
const [challenge, setChallenge] = useState(new Uint8Array(32))
const [loginResponse, setLoginResponse] = useState()
const isWebAuthnSupported = () => {
return !!window.PublicKeyCredential
}
const isPlatformAuthenticatorSupported = () => {
if (!isWebAuthnSupported()) {
throw new Error("WebAuthn API is not available")
}
if (!PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable) {
return false
} else {
return PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()
}
}
useEffect(() => {
isPlatformAuthenticatorSupported()
}, [])
const registerDevice = () => {
const publicKey = {
challenge,
rp: {
name: ".domino",
icon: "https://next-auth.js.org/img/logo/logo-sm.png",
},
user: {
id: new Uint8Array(16),
name: email,
displayName: name,
},
pubKeyCredParams: [
{ type: "public-key", alg: -7 },
{ type: "public-key", alg: -257 },
],
authenticatorSelection: {
userVerification: "required",
},
status: "ok",
}
navigator.credentials
.create({ publicKey })
.then((newCredentialInfo) => {
console.log("** REGISTRATION SUCCESS", newCredentialInfo)
setCredentialId(newCredentialInfo.id)
})
.catch((error) => {
console.log("FAIL", error)
})
}
const login = () => {
const publicKey = {
challenge: challenge,
allowCredentials: [
{ type: "public-key", id: Buffer.from(credentialId, "base64") },
],
userVerification: "required",
status: "ok",
}
navigator.credentials
.get({ publicKey })
.then((getAssertionResponse) => {
console.log("** ASSERTION RECEIVED", getAssertionResponse)
console.log(getAssertionResponse.response.authenticatorData.toString('base64'))
console.log(getAssertionResponse.response.clientDataJSON.toString('base64'))
console.log(getAssertionResponse.response.signature.toString('base64'))
})
.catch((error) => {
console.log("FAIL", error)
})
}
return (
<div
style={{
position: "absolute",
left: 0,
top: 0,
width: "100vw",
height: "100vh",
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: "2rem",
}}
>
<div
style={{
marginTop: "10vh",
backgroundColor: "#0d1117",
padding: "3rem",
borderRadius: "0.5rem",
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: "1rem",
}}
>
<img src="https://next-auth.js.org/img/logo/logo-sm.png" width="64" />
<div>WebAuthn Auth.js Demo</div>
</div>
<div
style={{
backgroundColor: "#0d1117",
padding: "3rem",
borderRadius: "0.5rem",
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: "1rem",
maxWidth: "400px",
}}
>
<h2 style={{ margin: "0" }}>Step 1</h2>
<label htmlFor="email">
Email <span style={{ color: "red" }}>*</span>
</label>
<input
type="text"
name="email"
placeholder="user@company.com"
onChange={(e) => setEmail(e.target.value)}
value={email}
/>
<label htmlFor="displayName">
Name <span style={{ color: "red" }}>*</span>
</label>
<input
type="text"
name="displayName"
placeholder="Joe"
onChange={(e) => setName(e.target.value)}
value={name}
/>
<button
type="button"
onClick={() => registerDevice()}
disabled={!email || !name}
>
Register
</button>
{credentialId && (
<pre
style={{
wordBreak: "break-all",
wordWrap: "break-word",
whiteSpace: "break-spaces",
}}
>
Credential Id: {credentialId}
</pre>
)}
</div>
<div
style={{
backgroundColor: "#0d1117",
padding: "3rem",
borderRadius: "0.5rem",
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: "1rem",
}}
>
<h2>Step 2</h2>
<button type="button" onClick={() => login()} disabled={!credentialId}>
Login
</button>
{loginResponse && (
<pre
style={{
wordBreak: "break-all",
wordWrap: "break-word",
whiteSpace: "break-spaces",
}}
>
Credential Id: {credentialId}
</pre>
)}
</div>
</div>
)
}

View File

@@ -0,0 +1,50 @@
body {
background-color: #161b22;
color: #fff;
}
button {
margin: 0 0 0.75rem 0;
padding: 0.75rem 2rem;
color: white;
background-color: orange;
font-size: 1.1rem;
border-color: rgba(0, 0, 0, 0.1);
border-radius: 0.5rem;
transition: all 0.1s ease-in-out;
font-weight: 900;
position: relative;
display: flex;
align-items: center;
justify-content: center;
}
button:hover {
cursor: pointer;
}
button:active {
cursor: pointer;
}
button:disabled {
filter: grayscale(0.9);
}
button:disabled:hover {
cursor: auto !important;
}
label {
text-align: left;
}
input {
box-sizing: border-box;
display: block;
width: 100%;
padding: 0.5rem 1rem;
border: 1px solid #555;
background: white;
font-size: 1rem;
border-radius: 0.5rem;
color: black;
}
input:focus {
box-shadow: none;
}

View File

@@ -16,7 +16,7 @@
"svelte": "3.55.0",
"svelte-check": "2.10.2",
"typescript": "4.9.4",
"vite": "4.0.5"
"vite": "4.0.1"
},
"dependencies": {
"@auth/core": "workspace:*",

View File

@@ -6,9 +6,6 @@ AUTH0_ID=
AUTH0_SECRET=
AUTH0_ISSUER=
DESCOPE_ID=
DESCOPE_SECRET=
FACEBOOK_ID=
FACEBOOK_SECRET=

View File

@@ -7,7 +7,7 @@ export default function Page() {
<p>Only admin users can see this page.</p>
<p>
To learn more about the NextAuth middleware see&nbsp;
<a href="https://next-auth.js.org/configuration/nextjs#middleware">
<a href="https://docs-git-misc-docs-nextauthjs.vercel.app/configuration/nextjs#middleware">
the docs
</a>
.

View File

@@ -12,7 +12,5 @@ declare namespace NodeJS {
GOOGLE_SECRET: string
AUTH0_ID: string
AUTH0_SECRET: string
DESCOPE_ID: string
DESCOPE_SECRET: string
}
}

View File

@@ -1,4 +1,4 @@
import { Session } from "@auth/core/types"
import { Session } from "@auth/core"
export default function useSession() {
return useState<Session | null>("session", () => null)

View File

@@ -43,7 +43,7 @@ export async function signIn<
// TODO: Handle custom base path
// TODO: Remove this since Sveltekit offers the CSRF protection via origin check
const { csrfToken } = await $fetch<{ csrfToken: string }>("/api/auth/csrf")
const { csrfToken } = await $fetch("/api/auth/csrf")
console.log(_signInUrl)

View File

@@ -1,14 +1,13 @@
import { AuthConfig, Session } from "@auth/core/types"
import { Auth } from "@auth/core"
import { AuthHandler, AuthOptions, Session } from "@auth/core"
import { fromNodeMiddleware, H3Event } from "h3"
import getURL from "requrl"
import { createMiddleware } from "@hattip/adapter-node"
export function NuxtAuthHandler(options: AuthConfig) {
export function NuxtAuthHandler(options: AuthOptions) {
async function handler(ctx: { request: Request }) {
options.trustHost ??= true
return Auth(ctx.request, options)
return AuthHandler(ctx.request, options)
}
const middleware = createMiddleware(handler)
@@ -18,7 +17,7 @@ export function NuxtAuthHandler(options: AuthConfig) {
export async function getSession(
event: H3Event,
options: AuthConfig
options: AuthOptions
): Promise<Session | null> {
options.trustHost ??= true
@@ -31,7 +30,7 @@ export async function getSession(
nodeHeaders.append(key, headers[key] as any)
})
const response = await Auth(
const response = await AuthHandler(
new Request(url, { headers: nodeHeaders }),
options
)

View File

@@ -1,21 +1,21 @@
{
"name": "next-auth-nuxt",
"name": "playground-nuxt",
"private": true,
"scripts": {
"build": "nuxt build",
"dev": "nuxt prepare && nuxt dev",
"build": "nuxt prepare && nuxt build",
"dev": "nuxt prepare && export NODE_OPTIONS='--no-experimental-fetch' && nuxt dev",
"generate": "nuxt generate",
"preview": "nuxt preview"
},
"devDependencies": {
"@nuxt/eslint-config": "^0.1.1",
"eslint": "^8.29.0",
"h3": "1.6.6",
"nuxt": "3.5.1"
"h3": "1.0.2",
"nuxt": "3.0.0"
},
"dependencies": {
"@auth/core": "workspace:*",
"@hattip/adapter-node": "^0.0.34",
"@hattip/adapter-node": "^0.0.22",
"requrl": "^3.0.2"
}
}

View File

@@ -1,4 +1,4 @@
import { Session } from "@auth/core/types"
import { Session } from "@auth/core"
export default defineNuxtPlugin(async () => {
const session = useSession()

View File

@@ -1,10 +1,10 @@
import { NuxtAuthHandler } from "@/lib/auth/server"
import GithubProvider from "@auth/core/providers/github"
import type { AuthConfig } from "@auth/core"
import type { AuthOptions } from "@auth/core"
const runtimeConfig = useRuntimeConfig()
export const authOptions = {
export const authOptions: AuthOptions = {
secret: runtimeConfig.secret,
providers: [
GithubProvider({
@@ -12,6 +12,6 @@ export const authOptions = {
clientSecret: runtimeConfig.github.clientSecret,
}),
],
} as AuthConfig
}
export default NuxtAuthHandler(authOptions)

View File

@@ -37,31 +37,22 @@ This documentation site is based on the [Docusaurus](https://docusaurus.io) fram
To start a local environment of this project, please do the following.
1. Clone the repo:
```sh
git clone git@github.com:nextauthjs/next-auth.git
cd next-auth
```
2. Set up the correct pnpm version, using [Corepack](https://nodejs.org/api/corepack.html). Run the following in the project'a root:
```sh
corepack enable pnpm
```
(Now, if you run `pnpm --version`, it should print the same verion as the `packageManager` property in the [`package.json` file](https://github.com/nextauthjs/next-auth/blob/main/package.json))
3. Install packages. Developing requires Node.js v18:
```sh
pnpm install
```
4. Start the development server
1. Clone the repository.
```bash
$ pnpm dev:docs
$ git clone https://github.com/nextauthjs/docs.git
```
2. Install dependencies
```bash
$ npm install
```
3. Start the development server
```bash
$ npm start
```
And thats all! Now you should have a local copy of this docs site running at [localhost:3000](http://localhost:3000)!

View File

@@ -34,7 +34,7 @@ npm install -D nodemailer
## 2. Setting up a SMTP service
Next we need a [SMTP service](https://sendgrid.com/blog/what-is-an-smtp-server/) which will be in charge of sending emails from our application. There's a number of services available for this, however [here are the ones](https://community.nodemailer.com/2-0-0-beta/setup-smtp/well-known-services) known to work with `nodemailer`.
Next we need a [SMTP service](https://sendgrid.com/blog/what-is-an-smtp-server/) which will be in charge of sending emails from our application. There's a number of services available for this, however [here are the ones](http://nodemailer.com/smtp/well-known/) known to work with `nodemailer`.
:::info
For this tutorial, we're going to be using [Sendgrid](https://sendgrid.com/), but any of the services linked above should work the same
@@ -91,12 +91,12 @@ Finally, we'll need to set up a database adapter to store verification tokens th
An **Adapter** in Auth.js connects your application to whatever database or backend system you want to use to store data for users, their accounts, sessions, etc...
For this tutorial, we're going to use the **MongoDB** adapter, but any of the other adapters will work just fine.
For this tutorial, we're going to use the **MongoDB** adapter, other any of the other adapters will work just fine.
First, let's start by installing the adapter package:
```bash npm2yarn2pnpm
npm install -D @auth/mongodb-adapter mongodb
npm install -D @next-auth/mongodb-adapter mongodb
```
and create a simple MongoDB client:
@@ -142,7 +142,7 @@ And now let's reference this new adapter from our Auth.js configuration file:
```diff title="pages/api/auth/[...nextauth].ts"
import NextAuth from "next-auth"
import EmailProvider from "next-auth/providers/email"
+ import { MongoDBAdapter } from "@auth/mongodb-adapter"
+ import { MongoDBAdapter } from "@next-auth/mongodb-adapter"
+ import clientPromise from "../../../lib/mongodb/client"
export default NextAuth({

View File

@@ -100,12 +100,11 @@ NextAuth.js provides [`useSession()`](/reference/react/#usesession) - a [React H
```ts title="pages/_app.tsx"
import { SessionProvider } from "next-auth/react"
import type { AppProps } from 'next/app'
export default function App({
Component,
pageProps: { session, ...pageProps },
}: AppProps) {
}) {
return (
<SessionProvider session={session}>
<Component {...pageProps} />
@@ -244,13 +243,10 @@ http://localhost:5173/auth/callback/github
TODO Core
</TabItem>
</Tabs>
:::info
The last part of the URL, `[provider]`, is the ID of the provider you're using. In this case, we're using GitHub, so it's `github`. If you're using Google, it'll be `google`, etc... We keep track of the provider IDs internally.
The same id is used in the `signIn()` method we saw earlier.
:::
To register, tap on "Register application" button.
The next screen shows all the configurations for your newly created OAuth app. For now, we need two things from it - the **Client ID** and **Client Secret**:

View File

@@ -2,7 +2,7 @@
title: Using a database adapter
---
An **Adapter** in Auth.js connects your application to whatever database or backend system you want to use to store data for users, their accounts, sessions, etc. Adapters are optional, unless you need to persist user information in your own database, or you want to implement certain flows. The [Email Provider](/getting-started/email-tutorial) requires an adapter to be able to save [Verification Tokens](/reference/adapters#verification-token).
An **Adapter** in Auth.js connects your application to whatever database or backend system you want to use to store data for users, their accounts, sessions, etc. Adapters are optional, unless you need to persist user information in your own database, or you want to implement certain flows. The [Email Provider](/getting-started/email-tutorial) requires an adapter to be able to save [Verification Tokens](/reference/adapters/models#verification-token).
:::tip
When using a database, you can still use JWT for session handling for fast access. See the [`session.strategy`](/reference/configuration/auth-config#session) option. Read about the trade-offs of JWT in the [FAQ](/concepts/faq#json-web-tokens).

View File

@@ -34,7 +34,7 @@ Most OAuth providers cannot be configured with multiple callback URLs or using a
However, Auth.js **supports Preview deployments**, even **with OAuth providers**:
1. Determine a stable deployment URL. Eg.: A deployment whose URL does not change between builds, for example. `auth.yourdomain.com` (using a subdomain is not a requirement, this can simply be the main site's URL too.),
1. Determine a stable deployment URL. Eg.: A deployment whose URL does not change between builds, for example. `auth.yourdomain.com`),
2. Set `AUTH_REDIRECT_PROXY_URL` to that URL, adding the path up until your `[...nextauth]` route. Eg.: (`https://auth.yourdomain.com/api/auth`)
3. For your OAuth provider, set the callback URL using the stable deployment URL. Eg.: For GitHub `https://auth.yourdomain.com/api/auth/callback/github`)
@@ -42,9 +42,6 @@ However, Auth.js **supports Preview deployments**, even **with OAuth providers**
To support preview deployments, the `AUTH_SECRET` value needs to be the same for the stable deployment and deployments that will need OAuth support.
:::
:::note
If you are storing users in a [database](reference/adapters), we recommend using a different OAuth app for development/production so that you don't mix your test and production user base.
:::
<details>
<summary>

View File

@@ -117,7 +117,7 @@ Using the database strategy is very similar, but instead of preserving the `acce
import { Auth } from "@auth/core"
import { type TokenSet } from "@auth/core/types"
import Google from "@auth/core/providers/google"
import { PrismaAdapter } from "@auth/prisma-adapter"
import { PrismaAdapter } from "@next-auth/prisma-adapter"
import { PrismaClient } from "@prisma/client"
const prisma = new PrismaClient()

View File

@@ -30,7 +30,7 @@ You can override any of the options to suit your own use case.
## Configuration
1. Auth.js does not include `nodemailer` as a dependency, so you'll need to install it yourself if you want to use the Email Provider. Run `npm install nodemailer` or `yarn add nodemailer`.
2. You will need an SMTP account; such as [the official Nodemailer recommended service](https://nodemailer.com/about/#example) of [Forward Email](https://forwardemail.net).
2. You will need an SMTP account; ideally for one of the [services known to work with `nodemailer`](https://community.nodemailer.com/2-0-0-beta/setup-smtp/well-known-services/).
3. There are two ways to configure the SMTP server connection.
You can either use a connection string or a `nodemailer` configuration object.
@@ -40,8 +40,8 @@ You can either use a connection string or a `nodemailer` configuration object.
Create an `.env` file to the root of your project and add the connection string and email address.
```js title=".env" {1}
EMAIL_SERVER=smtp://username:password@smtp.forwardemail.net:587
EMAIL_FROM=support@example.com
EMAIL_SERVER=smtp://username:password@smtp.example.com:587
EMAIL_FROM=noreply@example.com
```
Now you can add the email provider like this:
@@ -64,7 +64,7 @@ In your `.env` file in the root of your project simply add the configuration obj
```js title=".env"
EMAIL_SERVER_USER=username
EMAIL_SERVER_PASSWORD=password
EMAIL_SERVER_HOST=smtp.forwardemail.net
EMAIL_SERVER_HOST=smtp.example.com
EMAIL_SERVER_PORT=587
EMAIL_FROM=noreply@example.com
```
@@ -112,7 +112,6 @@ providers: [
identifier: email,
url,
provider: { server, from },
request // for example can be used to get the user agent (`request.headers.get("user-agent")`) to parse and pass on to the user in the email so they can be more confident they originated the request
}) {
/* your function */
},

View File

@@ -22,18 +22,11 @@ Next you will have to create some configuration files for Cypress.
First, the primary cypress config:
```ts title="cypress.config.ts"
import { defineConfig } from 'cypress'
export default defineConfig({
e2e: {
baseUrl: 'http://localhost:3000',
chromeWebSecurity: false,
setupNodeEvents(on, config) {
// implement node event listeners here
},
},
})
```js title="cypress.json"
{
"baseUrl": "http://localhost:3000",
"chromeWebSecurity": false
}
```
This initial Cypress config will tell Cypress where to find your site on initial launch as well as allow it to open up URLs at domains that aren't your page, for example to be able to login to a social provider.
@@ -53,24 +46,14 @@ You must change the login credentials you want to use, but you can also redefine
Third, if you're using the `cypress-social-login` plugin, you must add this to your `/cypress/plugins/index.js` file like so:
```js title="cypress.config.ts" {3-4,10-14}
import { defineConfig } from 'cypress'
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { GoogleSocialLogin } = require('cypress-social-logins').plugins
export default defineConfig({
e2e: {
baseUrl: 'http://localhost:3000',
chromeWebSecurity: false,
setupNodeEvents(on, config) {
on('task', {
GoogleSocialLogin,
})
},
},
})
```js title="cypress/plugins/index.js"
const { GoogleSocialLogin } = require("cypress-social-logins").plugins
module.exports = (on, config) => {
on("task", {
GoogleSocialLogin: GoogleSocialLogin,
})
}
```
Finally, you can also add the following npm scripts to your `package.json`:
@@ -127,6 +110,10 @@ describe("Login page", () => {
secure: cookie.secure,
})
Cypress.Cookies.defaults({
preserve: cookieName,
})
// remove the two lines below if you need to stay logged in
// for your remaining tests
cy.visit("/api/auth/signout")

View File

@@ -8,10 +8,6 @@ Using an Auth.js / NextAuth.js adapter you can connect to any database service o
<a href="/reference/adapter/dgraph" class="adapter-card">
<img src="/img/adapters/dgraph.png" width="30" />
<h4 class="adapter-card__title">Dgraph Adapter</h4>
</a>
<a href="/reference/adapter/drizzle" class="adapter-card">
<img src="/img/adapters/drizzle-orm.png" width="30" />
<h4 class="adapter-card__title">Drizzle Adapter</h4>
</a>
<a href="/reference/adapter/dynamodb" class="adapter-card">
<img src="/img/adapters/dynamodb.png" width="30" />
@@ -25,10 +21,6 @@ Using an Auth.js / NextAuth.js adapter you can connect to any database service o
<img src="/img/adapters/firebase.svg" width="40" />
<h4 class="adapter-card__title">Firebase Adapter</h4>
</a>
<a href="/reference/adapter/kysely" class="adapter-card">
<img src="/img/adapters/kysely.svg" width="40" />
<h4 class="adapter-card__title">Kysely Adapter</h4>
</a>
<a href="/reference/adapter/mikro-orm" class="adapter-card">
<img src="/img/adapters/mikro-orm.png" width="30" />
<h4 class="adapter-card__title">Mikro ORM Adapter</h4>
@@ -75,8 +67,10 @@ Using an Auth.js / NextAuth.js adapter you can connect to any database service o
If you don't find an adapter for the database or service you use, you can always create one yourself. Have a look at our guide on [how to create a database adapter](/guides/adapters/creating-a-database-adapter).
:::
## Models
Auth.js can be used with any database. Models tell you what structures Auth.js expects from your database. Models will vary slightly depending on which adapter you use, but in general, will look something like this:
```mermaid
@@ -102,13 +96,8 @@ erDiagram
string type
string provider
string providerAccountId
string refresh_token
string access_token
int expires_at
string token_type
string scope
string id_token
string session_state
}
VerificationToken {
string identifier
@@ -137,7 +126,7 @@ If a user first signs in with an OAuth provider, then their email address is aut
This provides a way to contact users and for users to maintain access to their account and sign in using email in the event they are unable to sign in with the OAuth provider in the future (if the [Email Provider](/reference/core/providers_email) is configured).
:::
User creation in the database is automatic and happens when the user is logging in for the first time with a provider.
User creation in the database is automatic and happens when the user is logging in for the first time with a provider.
If the first sign-in is via the [OAuth Provider](/reference/core/providers_oauth), the default data saved is `id`, `name`, `email` and `image`. You can add more profile data by returning extra fields in your [OAuth provider](/guides/providers/custom-provider)'s [`profile()`](/reference/core/providers#profile) callback.
If the first sign-in is via the [Email Provider](/reference/core/providers_email), then the saved user will have `id`, `email`, `emailVerified`, where `emailVerified` is the timestamp of when the user was created.
@@ -148,7 +137,7 @@ The Account model is for information about OAuth accounts associated with a User
A single User can have multiple Accounts, but each Account can only have one User.
Account creation in the database is automatic and happens when the user is logging in for the first time with a provider, or the [`Adapter.linkAccount`](/reference/core/adapters#linkaccount) method is invoked. The default data saved is `access_token`, `expires_at`, `refresh_token`, `id_token`, `token_type`, `scope` and `session_state`. You can save other fields or remove the ones you don't need by returning them in the [OAuth provider](/guides/providers/custom-provider)'s [`account()`](/reference/core/providers#account) callback.
Account creation in the database is automatic and happens when the user is logging in for the first time with a provider, or the [`Adapter.linkAccount`](/reference/core/adapters#linkaccount) method is invoked. The default data saved is `access_token`, `refresh_token`, `id_token` and `expires_at`. You can save other fields by returning them in the [OAuth provider](/guides/providers/custom-provider)'s [`account()`](/reference/core/providers#account) callback.
Linking Accounts to Users happen automatically, only when they have the same e-mail address, and the user is currently signed in. Check the [FAQ](/concepts/faq#security) for more information on why this is a requirement.

View File

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

View File

@@ -55,11 +55,9 @@ module.exports = {
link: { type: "doc", id: "reference/adapters/index" },
items: [
{ type: "doc", id: "reference/adapter/dgraph/index" },
{ type: "doc", id: "reference/adapter/drizzle/index" },
{ type: "doc", id: "reference/adapter/dynamodb/index" },
{ type: "doc", id: "reference/adapter/fauna/index" },
{ type: "doc", id: "reference/adapter/firebase/index" },
{ type: "doc", id: "reference/adapter/kysely/index" },
{ type: "doc", id: "reference/adapter/mikro-orm/index" },
{ type: "doc", id: "reference/adapter/mongodb/index" },
{ type: "doc", id: "reference/adapter/neo4j/index" },

View File

@@ -7,7 +7,6 @@ const icons = [
"/img/providers/apple.svg",
"/img/providers/auth0.svg",
"/img/providers/cognito.svg",
"/img/providers/descope.svg",
"/img/providers/battlenet.svg",
"/img/providers/box.svg",
"/img/providers/facebook.svg",

View File

@@ -29,7 +29,6 @@ html[data-theme="dark"] .adapter-card {
color: #f5f5f5;
}
html[data-theme="dark"] .adapter-card:hover,
.adapter-card:hover {
text-decoration: none;
color: black;

View File

@@ -91,7 +91,7 @@ html[data-theme="dark"] .navbar__item.navbar__link[href*="npm"]:before {
position: absolute;
color: #000;
top: -10px;
right: 4px;
right: -45px;
font-size: 9px;
background-color: #ccc;
padding: 2px 5px;

View File

@@ -101,13 +101,13 @@ export default function Home() {
.fetch("https://api.github.com/repos/nextauthjs/next-auth")
.then((res) => res.json())
.then((data) => {
const githubLink = document.querySelector(
".navbar__item.navbar__link[href*='github']"
const navLinks = document.getElementsByClassName(
"navbar__item navbar__link"
)
const githubStat = document.createElement("span")
githubStat.innerHTML = kFormatter(data.stargazers_count)
githubStat.className = "github-counter"
githubLink.appendChild(githubStat)
navLinks[4].appendChild(githubStat)
})
}, [])
return (

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

View File

@@ -1,14 +0,0 @@
<svg width="132" height="132" viewBox="0 0 132 132" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_8_3)">
<rect x="2" y="2" width="128" height="128" rx="16" fill="white" />
<path
d="M41.2983 109V23.9091H46.4918V73.31H47.0735L91.9457 23.9091H98.8427L61.9062 64.1694L98.5103 109H92.0288L58.5824 67.9087L46.4918 81.2873V109H41.2983Z"
fill="black" />
</g>
<rect x="2" y="2" width="128" height="128" rx="16" stroke="#121212" stroke-width="4" />
<defs>
<clipPath id="clip0_8_3">
<rect x="2" y="2" width="128" height="128" rx="16" fill="white" />
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 637 B

View File

@@ -1,50 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="194.7px" height="215.2px" viewBox="0 0 194.7 215.2" style="enable-background:new 0 0 194.7 215.2;" xml:space="preserve"
>
<style type="text/css">
.st0{fill:url(#SVGID_1_);}
.st1{fill:url(#SVGID_00000004519561486438896460000001266960168497785022_);}
.st2{fill:url(#SVGID_00000049204468076180615810000015113731544435055266_);}
.st3{fill:url(#SVGID_00000116951257355544416270000003794629356563808950_);}
</style>
<g>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="68.3919" y1="222.1531" x2="185.0265" y2="41.0264">
<stop offset="1.481436e-07" style="stop-color:#0083B5"/>
<stop offset="0.4173" style="stop-color:#00FFFF"/>
<stop offset="0.9952" style="stop-color:#6FF12D"/>
</linearGradient>
<path class="st0" d="M129.8,174.7c7.6-1.6,14-4.8,19.2-9.7c7.7-7.3,8.8-17.1,8.8-29.4V80.7c0-12.3-1.1-22.1-8.8-29.4
c-5.2-4.9-11.6-8.1-19.2-9.7V15.4c12.5,1.8,22.9,6.5,31,14.2c10.6,10,19.9,23.5,19.9,40.5v75c0,17-9.3,30.5-19.9,40.5
c-8.1,7.7-18.5,12.4-31,14.2V174.7z"/>
<linearGradient id="SVGID_00000040544740507634666800000017273841385603649669_" gradientUnits="userSpaceOnUse" x1="5.037" y1="181.3564" x2="121.6716" y2="0.2297">
<stop offset="1.481436e-07" style="stop-color:#0083B5"/>
<stop offset="0.4173" style="stop-color:#00FFFF"/>
<stop offset="0.9952" style="stop-color:#6FF12D"/>
</linearGradient>
<path style="fill:url(#SVGID_00000040544740507634666800000017273841385603649669_);" d="M33.9,29.6c8.1-7.7,18.5-12.4,31-14.2
v26.3c-7.6,1.6-14,4.8-19.2,9.7c-7.7,7.3-8.8,17-8.8,29.2v55.1c0,12.3,1.1,22.1,8.8,29.4c5.2,4.9,11.6,8.1,19.2,9.7v25.1
c-12.5-1.8-22.9-6.5-31-14.2c-10.6-10-19.9-23.5-19.9-40.5V69.8C13.9,53,23.2,39.6,33.9,29.6z"/>
<g>
<linearGradient id="SVGID_00000060713993868866928010000000698955780952733088_" gradientUnits="userSpaceOnUse" x1="22.0278" y1="192.2974" x2="138.6624" y2="11.1707">
<stop offset="1.481436e-07" style="stop-color:#0083B5"/>
<stop offset="0.4173" style="stop-color:#00FFFF"/>
<stop offset="0.9952" style="stop-color:#6FF12D"/>
</linearGradient>
<path style="fill:url(#SVGID_00000060713993868866928010000000698955780952733088_);" d="M120.2,87.8l8.5-13.7l-17.8-9.4
l-7.5,14.2c-1.1,2.1-3.1,3.3-5.5,3.3c-2.3,0-4.4-1.2-5.5-3.3L85,64.7L67.3,74l12.3,19.7L120.2,87.8z"/>
<linearGradient id="SVGID_00000115475840050352750520000000840372054167564949_" gradientUnits="userSpaceOnUse" x1="37.9651" y1="202.5601" x2="154.5998" y2="21.4334">
<stop offset="1.481436e-07" style="stop-color:#0083B5"/>
<stop offset="0.4173" style="stop-color:#00FFFF"/>
<stop offset="0.9952" style="stop-color:#6FF12D"/>
</linearGradient>
<path style="fill:url(#SVGID_00000115475840050352750520000000840372054167564949_);" d="M142.4,97.7l-87.8,0.8v17.7l27.5-0.1
l-14.8,23.8l17.7,9.3l7.5-14.2c1.1-2.1,3.1-3.3,5.5-3.3c2.3,0,4.4,1.2,5.5,3.3l7.5,14.2l17.8-9.4l-12-19.3L93.7,116l48.7-0.2V97.7
z"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -1,11 +0,0 @@
<svg width="250" height="250" viewBox="0 0 250 250" fill="none" xmlns="http://www.w3.org/2000/svg">
<path opacity="0.8" d="M37.8419 71.6921L65.1425 87.4541C86.2537 99.6426 93.4921 126.639 81.3036 147.75C80.2315 149.607 79.045 151.353 77.7583 152.989L12.6182 115.381C8.67588 113.105 5.58417 109.922 3.44179 106.254C-0.964934 98.6912 -1.30613 89.0446 3.38448 80.9202C10.348 68.8591 25.7752 64.7254 37.8419 71.6921Z" fill="#3D53F6"/>
<path opacity="0.5" d="M37.8419 180.052L65.1425 164.29C86.2537 152.101 93.4921 125.105 81.3036 103.994C80.2315 102.137 79.045 100.391 77.7583 98.7544L12.6182 136.363C8.67588 138.639 5.58417 141.822 3.44179 145.49C-0.964934 153.053 -1.30613 162.699 3.38448 170.824C10.348 182.885 25.7752 187.019 37.8419 180.052Z" fill="#3D53F6"/>
<path opacity="0.5" d="M212.753 71.6921L185.452 87.4541C164.341 99.6426 157.103 126.639 169.291 147.75C170.363 149.607 171.55 151.353 172.836 152.989L237.976 115.381C241.919 113.105 245.01 109.922 247.153 106.254C251.56 98.6912 251.901 89.0446 247.21 80.9202C240.247 68.8591 224.819 64.7254 212.753 71.6921Z" fill="#3D53F6"/>
<path opacity="0.8" d="M212.753 180.052L185.452 164.29C164.341 152.101 157.103 125.105 169.291 103.994C170.363 102.137 171.55 100.391 172.836 98.7544L237.976 136.363C241.919 138.639 245.01 141.822 247.153 145.49C251.56 153.053 251.901 162.699 247.21 170.824C240.247 182.885 224.819 187.019 212.753 180.052Z" fill="#3D53F6"/>
<path d="M125.297 150.525C139.228 150.525 150.52 139.232 150.52 125.302C150.52 111.372 139.228 100.079 125.297 100.079C111.367 100.079 100.075 111.372 100.075 125.302C100.075 139.232 111.367 150.525 125.297 150.525Z" fill="#FF2F1D"/>
<path opacity="0.8" d="M71.1172 212.753L86.8793 185.452C99.0678 164.341 126.064 157.102 147.175 169.291C149.032 170.363 150.778 171.55 152.415 172.836L114.806 237.976C112.53 241.919 109.347 245.01 105.679 247.153C98.1164 251.559 88.4698 251.901 80.3454 247.21C68.2842 240.247 64.1505 224.819 71.1172 212.753Z" fill="#FF2F1D"/>
<path opacity="0.5" d="M179.477 212.752L163.715 185.452C151.526 164.341 124.53 157.102 103.419 169.291C101.562 170.363 99.8157 171.549 98.1794 172.836L135.788 237.976C138.064 241.919 141.247 245.01 144.915 247.153C152.478 251.559 162.124 251.901 170.249 247.21C182.31 240.246 186.444 224.819 179.477 212.752Z" fill="#FF2F1D"/>
<path opacity="0.5" d="M71.1172 37.8419L86.8793 65.1425C99.0678 86.2537 126.064 93.4921 147.175 81.3036C149.032 80.2315 150.778 79.045 152.415 77.7583L114.806 12.6182C112.53 8.67589 109.347 5.58416 105.679 3.44179C98.1164 -0.964936 88.4698 -1.30613 80.3454 3.38449C68.2842 10.348 64.1505 25.7752 71.1172 37.8419Z" fill="#3D53F6"/>
<path opacity="0.8" d="M179.477 37.8419L163.715 65.1425C151.526 86.2537 124.53 93.4921 103.419 81.3036C101.562 80.2315 99.8157 79.045 98.1794 77.7583L135.788 12.6182C138.064 8.67589 141.247 5.58416 144.915 3.44179C152.478 -0.964936 162.124 -1.30613 170.249 3.38449C182.31 10.348 186.444 25.7752 179.477 37.8419Z" fill="#3D53F6"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.9 KiB

View File

@@ -1,5 +1,4 @@
{
"$schema": "https://openapi.vercel.sh/vercel.json",
"cleanUrls": true,
"headers": [
{
@@ -77,15 +76,10 @@
"has": [{ "type": "host", "value": "warnings.authjs.dev" }],
"destination": "https://authjs.dev/reference/warnings/:path*"
},
{
"source": "/",
"has": [{ "type": "host", "value": "adapters.authjs.dev" }],
"destination": "https://authjs.dev/reference/adapters"
},
{
"source": "/:path(.*)",
"has": [{ "type": "host", "value": "adapters.authjs.dev" }],
"destination": "https://authjs.dev/reference/adapter/:path*"
"destination": "https://authjs.dev/reference/adapters/:path*"
},
{
"source": "/:path",

View File

@@ -7,7 +7,7 @@
"build:app": "turbo run build --filter=next-auth-app",
"build:docs": "turbo run build --filter=docs",
"build": "turbo run build --filter=next-auth --filter=@next-auth/* --filter=@auth/* --no-deps",
"test": "turbo run test --concurrency=1 --filter=[HEAD^1] --filter=./packages/* --filter=!@*upstash* --filter=!*dynamodb-* --filter=!*app*",
"test": "turbo run test --concurrency=1 --filter=[HEAD^1] --filter=./packages/* --filter=!@*upstash* --filter=!*dynamodb-*",
"clean": "turbo run clean --no-cache",
"dev:db": "turbo run dev --parallel --continue --filter=next-auth-app...",
"dev": "turbo run dev --parallel --continue --filter=next-auth-app... --filter=!./packages/adapter-*",
@@ -43,7 +43,7 @@
"eslint-plugin-svelte3": "^4.0.0",
"prettier": "2.8.1",
"prettier-plugin-svelte": "^2.8.1",
"turbo": "^1.10.3",
"turbo": "1.8.8",
"typescript": "4.9.4"
},
"engines": {

View File

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

View File

@@ -8,14 +8,14 @@
</a>
<h3 align="center"><b>Dgraph Adapter</b> - NextAuth.js / Auth.js</a></h3>
<p align="center" style="align: center;">
<a href="https://npm.im/@auth/dgraph-adapter">
<a href="https://npm.im/@next-auth/dgraph-adapter">
<img src="https://img.shields.io/badge/TypeScript-blue?style=flat-square" alt="TypeScript" />
</a>
<a href="https://npm.im/@auth/dgraph-adapter">
<img alt="npm" src="https://img.shields.io/npm/v/@auth/dgraph-adapter?color=green&label=@auth/dgraph-adapter&style=flat-square">
<a href="https://npm.im/@next-auth/dgraph-adapter">
<img alt="npm" src="https://img.shields.io/npm/v/@next-auth/dgraph-adapter?color=green&label=@next-auth/dgraph-adapter&style=flat-square">
</a>
<a href="https://www.npmtrends.com/@auth/dgraph-adapter">
<img src="https://img.shields.io/npm/dm/@auth/dgraph-adapter?label=%20downloads&style=flat-square" alt="Downloads" />
<a href="https://www.npmtrends.com/@next-auth/dgraph-adapter">
<img src="https://img.shields.io/npm/dm/@next-auth/dgraph-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" />

View File

@@ -1,30 +1,19 @@
{
"name": "@auth/dgraph-adapter",
"version": "1.0.0",
"description": "Dgraph adapter for Auth.js",
"name": "@next-auth/dgraph-adapter",
"version": "1.0.6",
"description": "Dgraph 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": "Arnaud Derbey <arnaud@derbey.dev>",
"contributors": [
"Balázs Orbán <info@balazsorban.com>"
],
"type": "module",
"types": "./index.d.ts",
"contributors": [],
"main": "dist/index.js",
"files": [
"*.js",
"*.d.ts*",
"lib",
"src"
"dist",
"index.d.ts"
],
"exports": {
".": {
"types": "./index.d.ts",
"import": "./index.js"
}
},
"license": "ISC",
"keywords": [
"next-auth",
@@ -40,6 +29,10 @@
"build": "tsc",
"test": "./tests/test.sh"
},
"peerDependencies": {
"jsonwebtoken": "^8.5.1",
"next-auth": "^4"
},
"devDependencies": {
"@next-auth/adapter-test": "workspace:*",
"@next-auth/tsconfig": "workspace:*",
@@ -47,12 +40,12 @@
"@types/jsonwebtoken": "^8.5.5",
"@types/node-fetch": "^2.5.11",
"jest": "^27.4.3",
"ts-jest": "^27.0.3",
"undici": "5.22.1"
"next-auth": "workspace:*",
"ts-jest": "^27.0.3"
},
"dependencies": {
"@auth/core": "workspace:*",
"jsonwebtoken": "^8.5.1"
"jsonwebtoken": "^8.5.1",
"node-fetch": "^2.6.1"
},
"jest": {
"preset": "@next-auth/adapter-test/jest"

View File

@@ -1,4 +1,7 @@
import * as jwt from "jsonwebtoken"
import fetch from "node-fetch"
import type { HeadersInit } from "node-fetch"
export interface DgraphClientParams {
endpoint: string

View File

@@ -9,18 +9,18 @@
* ## Installation
*
* ```bash npm2yarn2pnpm
* npm install next-auth @auth/dgraph-adapter
* npm install next-auth @next-auth/dgraph-adapter
* ```
*
* @module @auth/dgraph-adapter
* @module @next-auth/dgraph-adapter
*/
import { client as dgraphClient } from "./lib/client"
import { format } from "./lib/utils"
import type { Adapter } from "@auth/core/adapters"
import type { DgraphClientParams } from "./lib/client"
import * as defaultFragments from "./lib/graphql/fragments"
import { client as dgraphClient } from "./client"
import { format } from "./utils"
import type { Adapter } from "next-auth/adapters"
import type { DgraphClientParams } from "./client"
import * as defaultFragments from "./graphql/fragments"
export type { DgraphClientParams, DgraphClientError } from "./lib/client"
export type { DgraphClientParams, DgraphClientError } from "./client"
/** This is the interface of the Dgraph adapter options. */
export interface DgraphAdapterOptions {
@@ -28,7 +28,7 @@ export interface DgraphAdapterOptions {
* The GraphQL {@link https://dgraph.io/docs/query-language/fragments/ Fragments} you can supply to the adapter
* to define how the shapes of the `user`, `account`, `session`, `verificationToken` entities look.
*
* By default the adapter will uses the [default defined fragments](https://github.com/nextauthjs/next-auth/blob/main/packages/adapter-dgraph/src/lib/graphql/fragments.ts)
* By default the adapter will uses the [default defined fragments](https://github.com/nextauthjs/next-auth/blob/main/packages/adapter-dgraph/src/graphql/fragments.ts)
* , this config option allows to extend them.
*/
fragments?: {
@@ -48,7 +48,7 @@ export { format }
*
* ```ts title="pages/api/auth/[...nextauth].js"
* import NextAuth from "next-auth"
* import { DgraphAdapter } from "@auth/dgraph-adapter"
* import { DgraphAdapter } from "@next-auth/dgraph-adapter"
*
* export default NextAuth({
* providers: [],

View File

@@ -1,14 +1,12 @@
import { DgraphAdapter, format } from "../src"
import { client as dgraphClient } from "../src/lib/client"
import * as fragments from "../src/lib/graphql/fragments"
import { client as dgraphClient } from "../src/client"
import * as fragments from "../src/graphql/fragments"
import { runBasicTests } from "@next-auth/adapter-test"
import fs from "fs"
import path from "path"
import type { DgraphClientParams } from "../src"
globalThis.fetch ??= require("undici").fetch
const params: DgraphClientParams = {
endpoint: "http://localhost:8080/graphql",
authToken: "test",

View File

@@ -21,7 +21,7 @@ dgraph/standalone
echo "Waiting 15 sec for db to start..." && sleep 15
head -n -1 src/lib/graphql/schema.gql > tests/test.schema.gql
head -n -1 src/graphql/schema.gql > tests/test.schema.gql
PUBLIC_KEY=$(sed 's/$/\\n/' tests/public.key | tr -d '\n')
echo "# Dgraph.Authorization {\"VerificationKey\":\"$PUBLIC_KEY\",\"Namespace\":\"https://dgraph.io/jwt/claims\",\"Header\":\"Authorization\",\"Algo\":\"RS256\"}" >> tests/test.schema.gql

View File

@@ -1,25 +1,8 @@
{
"extends": "@next-auth/tsconfig/tsconfig.base.json",
"extends": "@next-auth/tsconfig/tsconfig.adapters.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
"outDir": "dist"
},
"include": [
"src/**/*"
],
"exclude": [
"*.js",
"*.d.ts",
]
}
"exclude": ["tests", "dist", "jest.config.js"]
}

View File

@@ -1,28 +0,0 @@
<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://github.com/drizzle-team/drizzle-orm" target="_blank">
<img height="64px" src="https://authjs.dev/img/adapters/drizzle-orm.png"/>
</a>
<h3 align="center"><b>Drizzle ORM Adapter</b> - NextAuth.js / Auth.js</a></h3>
<p align="center" style="align: center;">
<a href="https://npm.im/@auth/drizzle-adapter">
<img src="https://img.shields.io/badge/TypeScript-blue?style=flat-square" alt="TypeScript" />
</a>
<a href="https://npm.im/@auth/drizzle-adapter">
<img alt="npm" src="https://img.shields.io/npm/v/@auth/drizzle-adapter?color=green&label=@auth/drizzle-adapter&style=flat-square">
</a>
<a href="https://www.npmtrends.com/@auth/drizzle-adapter">
<img src="https://img.shields.io/npm/dm/@auth/drizzle-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/drizzle).

View File

@@ -1,65 +0,0 @@
{
"name": "@auth/drizzle-adapter",
"version": "0.3.0",
"description": "Drizzle adapter for Auth.js.",
"homepage": "https://authjs.dev",
"repository": "https://github.com/nextauthjs/next-auth",
"bugs": {
"url": "https://github.com/nextauthjs/next-auth/issues"
},
"author": "Anthony Shew",
"type": "module",
"types": "./index.d.ts",
"files": [
"*.js",
"*.d.ts*",
"lib",
"src"
],
"exports": {
".": {
"types": "./index.d.ts",
"import": "./index.js"
}
},
"license": "ISC",
"keywords": [
"next-auth",
"@auth",
"Auth.js",
"next.js",
"oauth",
"drizzle"
],
"private": false,
"publishConfig": {
"access": "public"
},
"scripts": {
"clean": "find . -type d -name \".drizzle\" | xargs rm -rf",
"test": "pnpm test:mysql && pnpm test:sqlite && pnpm test:pg",
"test:mysql": "pnpm clean && ./tests/mysql/test.sh",
"test:sqlite": "pnpm clean && ./tests/sqlite/test.sh",
"test:pg": "pnpm clean && ./tests/pg/test.sh",
"build": "tsc",
"dev": "drizzle-kit generate:mysql --schema=src/schema.ts --out=.drizzle && tsc -w"
},
"dependencies": {
"@auth/core": "workspace:*"
},
"devDependencies": {
"@next-auth/adapter-test": "workspace:*",
"@next-auth/tsconfig": "workspace:*",
"@types/better-sqlite3": "^7.6.4",
"@types/uuid": "^8.3.3",
"better-sqlite3": "^8.4.0",
"drizzle-kit": "^0.19.5",
"drizzle-orm": "^0.27.0",
"jest": "^27.4.3",
"mysql2": "^3.2.0",
"postgres": "^3.3.4"
},
"jest": {
"preset": "@next-auth/adapter-test/jest"
}
}

View File

@@ -1,277 +0,0 @@
/**
* <div style={{display: "flex", justifyContent: "space-between", alignItems: "center", padding: 16}}>
* <p style={{fontWeight: "normal"}}>Official <a href="https://orm.drizzle.team">Drizzle ORM</a> adapter for Auth.js / NextAuth.js.</p>
* <a href="https://orm.drizzle.team">
* <img style={{display: "block"}} src="/img/adapters/drizzle-orm.png" width="38" />
* </a>
* </div>
*
* ## Installation
*
* ```bash npm2yarn2pnpm
* npm install drizzle-orm @auth/drizzle-adapter
* npm install drizzle-kit --save-dev
* ```
*
* @module @auth/drizzle-adapter
*/
import { MySqlTableFn } from "drizzle-orm/mysql-core/index.js"
import { PgTableFn } from "drizzle-orm/pg-core/index.js"
import { SQLiteTableFn } from "drizzle-orm/sqlite-core/index.js"
import { mySqlDrizzleAdapter } from "./lib/mysql.js"
import { pgDrizzleAdapter } from "./lib/pg.js"
import { SQLiteDrizzleAdapter } from "./lib/sqlite.js"
import {
isMySqlDatabase,
isPgDatabase,
isSQLiteDatabase,
SqlFlavorOptions,
TableFn,
} from "./lib/utils.js"
import type { Adapter } from "@auth/core/adapters"
/**
* Add the adapter to your `pages/api/[...nextauth].ts` next-auth configuration object.
*
* ```ts title="pages/api/auth/[...nextauth].ts"
* import NextAuth from "next-auth"
* import GoogleProvider from "next-auth/providers/google"
* import { DrizzleAdapter } from "@auth/drizzle-adapter"
* import { db } from "./schema"
*
* export default NextAuth({
* adapter: DrizzleAdapter(db),
* providers: [
* GoogleProvider({
* clientId: process.env.GOOGLE_CLIENT_ID,
* clientSecret: process.env.GOOGLE_CLIENT_SECRET,
* }),
* ],
* })
* ```
*
* :::info
* If you're using multi-project schemas, you can pass your table function as a second argument
* :::
*
* ## Setup
*
* First, create a schema that includes [the minimum requirements for a `next-auth` adapter](/reference/adapters#models). You can select your favorite SQL flavor below and copy it.
* Additionally, you may extend the schema from the minimum requirements to suit your needs.
*
* - [Postgres](#postgres)
* - [MySQL](#mysql)
* - [SQLite](#sqlite)
*
* ### Postgres
* ```ts title="schema.ts"
* import {
* timestamp,
* pgTable,
* text,
* primaryKey,
* integer
* } from "drizzle-orm/pg-core"
* import type { AdapterAccount } from '@auth/core/adapters'
*
* export const users = pgTable("user", {
* id: text("id").notNull().primaryKey(),
* name: text("name"),
* email: text("email").notNull(),
* emailVerified: timestamp("emailVerified", { mode: "date" }),
* image: text("image"),
* })
*
* export const accounts = pgTable(
* "account",
* {
* userId: text("userId")
* .notNull()
* .references(() => users.id, { onDelete: "cascade" }),
* type: text("type").$type<AdapterAccount["type"]>().notNull(),
* provider: text("provider").notNull(),
* providerAccountId: text("providerAccountId").notNull(),
* refresh_token: text("refresh_token"),
* access_token: text("access_token"),
* expires_at: integer("expires_at"),
* token_type: text("token_type"),
* scope: text("scope"),
* id_token: text("id_token"),
* session_state: text("session_state"),
* },
* (account) => ({
* compoundKey: primaryKey(account.provider, account.providerAccountId),
* })
* )
*
* export const sessions = pgTable("session", {
* sessionToken: text("sessionToken").notNull().primaryKey(),
* userId: text("userId")
* .notNull()
* .references(() => users.id, { onDelete: "cascade" }),
* expires: timestamp("expires", { mode: "date" }).notNull(),
* })
*
* export const verificationTokens = pgTable(
* "verificationToken",
* {
* identifier: text("identifier").notNull(),
* token: text("token").notNull(),
* expires: timestamp("expires", { mode: "date" }).notNull(),
* },
* (vt) => ({
* compoundKey: primaryKey(vt.identifier, vt.token),
* })
* )
* ```
*
* ### MySQL
*
* ```ts title="schema.ts"
* import {
* int,
* timestamp,
* mysqlTable,
* primaryKey,
* varchar,
* } from "drizzle-orm/mysql-core"
* import type { AdapterAccount } from "@auth/core/adapters"
*
* export const users = mysqlTable("user", {
* id: varchar("id", { length: 255 }).notNull().primaryKey(),
* name: varchar("name", { length: 255 }),
* email: varchar("email", { length: 255 }).notNull(),
* emailVerified: timestamp("emailVerified", { mode: "date", fsp: 3 }).defaultNow(),
* image: varchar("image", { length: 255 }),
* })
*
* export const accounts = mysqlTable(
* "account",
* {
* userId: varchar("userId", { length: 255 })
* .notNull()
* .references(() => users.id, { onDelete: "cascade" }),
* type: varchar("type", { length: 255 }).$type<AdapterAccount["type"]>().notNull(),
* provider: varchar("provider", { length: 255 }).notNull(),
* providerAccountId: varchar("providerAccountId", { length: 255 }).notNull(),
* refresh_token: varchar("refresh_token", { length: 255 }),
* access_token: varchar("access_token", { length: 255 }),
* expires_at: int("expires_at"),
* token_type: varchar("token_type", { length: 255 }),
* scope: varchar("scope", { length: 255 }),
* id_token: varchar("id_token", { length: 255 }),
* session_state: varchar("session_state", { length: 255 }),
* },
* (account) => ({
* compoundKey: primaryKey(account.provider, account.providerAccountId),
* })
* )
*
* export const sessions = mysqlTable("session", {
* sessionToken: varchar("sessionToken", { length: 255 }).notNull().primaryKey(),
* userId: varchar("userId", { length: 255 })
* .notNull()
* .references(() => users.id, { onDelete: "cascade" }),
* expires: timestamp("expires", { mode: "date" }).notNull(),
* })
*
* export const verificationTokens = mysqlTable(
* "verificationToken",
* {
* identifier: varchar("identifier", { length: 255 }).notNull(),
* token: varchar("token", { length: 255 }).notNull(),
* expires: timestamp("expires", { mode: "date" }).notNull(),
* },
* (vt) => ({
* compoundKey: primaryKey(vt.identifier, vt.token),
* })
* )
* ```
*
* ### SQLite
*
* ```ts title="schema.ts"
* import { integer, sqliteTable, text, primaryKey } from "drizzle-orm/sqlite-core"
* import type { AdapterAccount } from "@auth/core/adapters"
*
* export const users = sqliteTable("user", {
* id: text("id").notNull().primaryKey(),
* name: text("name"),
* email: text("email").notNull(),
* emailVerified: integer("emailVerified", { mode: "timestamp_ms" }),
* image: text("image"),
* })
*
* export const accounts = sqliteTable(
* "account",
* {
* userId: text("userId")
* .notNull()
* .references(() => users.id, { onDelete: "cascade" }),
* type: text("type").$type<AdapterAccount["type"]>().notNull(),
* provider: text("provider").notNull(),
* providerAccountId: text("providerAccountId").notNull(),
* refresh_token: text("refresh_token"),
* access_token: text("access_token"),
* expires_at: integer("expires_at"),
* token_type: text("token_type"),
* scope: text("scope"),
* id_token: text("id_token"),
* session_state: text("session_state"),
* },
* (account) => ({
* compoundKey: primaryKey(account.provider, account.providerAccountId),
* })
* )
*
* export const sessions = sqliteTable("session", {
* sessionToken: text("sessionToken").notNull().primaryKey(),
* userId: text("userId")
* .notNull()
* .references(() => users.id, { onDelete: "cascade" }),
* expires: integer("expires", { mode: "timestamp_ms" }).notNull(),
* })
*
* export const verificationTokens = sqliteTable(
* "verificationToken",
* {
* identifier: text("identifier").notNull(),
* token: text("token").notNull(),
* expires: integer("expires", { mode: "timestamp_ms" }).notNull(),
* },
* (vt) => ({
* compoundKey: primaryKey(vt.identifier, vt.token),
* })
* )
* ```
*
* ## Migrating your database
* With your schema now described in your code, you'll need to migrate your database to your schema.
*
* For full documentation on how to run migrations with Drizzle, [visit the Drizzle documentation](https://orm.drizzle.team/kit-docs/overview#running-migrations).
*
* ---
*
**/
export function DrizzleAdapter<SqlFlavor extends SqlFlavorOptions>(
db: SqlFlavor,
table?: TableFn<SqlFlavor>
): Adapter {
if (isMySqlDatabase(db)) {
// We need to cast to unknown since the type overlaps (PScale is MySQL based)
return mySqlDrizzleAdapter(db, table as MySqlTableFn)
}
if (isPgDatabase(db)) {
return pgDrizzleAdapter(db, table as PgTableFn)
}
if (isSQLiteDatabase(db)) {
return SQLiteDrizzleAdapter(db, table as SQLiteTableFn)
}
throw new Error("Unsupported database type in Auth.js Drizzle adapter.")
}

View File

@@ -1,267 +0,0 @@
import { and, eq } from "drizzle-orm"
import {
int,
timestamp,
mysqlTable as defaultMySqlTableFn,
primaryKey,
varchar,
MySqlTableFn,
} from "drizzle-orm/mysql-core"
import type { Adapter, AdapterAccount } from "@auth/core/adapters"
import type { MySql2Database } from "drizzle-orm/mysql2"
export function createTables(mySqlTable: MySqlTableFn) {
const users = mySqlTable("user", {
id: varchar("id", { length: 255 }).notNull().primaryKey(),
name: varchar("name", { length: 255 }),
email: varchar("email", { length: 255 }).notNull(),
emailVerified: timestamp("emailVerified", {
mode: "date",
fsp: 3,
}).defaultNow(),
image: varchar("image", { length: 255 }),
})
const accounts = mySqlTable(
"account",
{
userId: varchar("userId", { length: 255 })
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
type: varchar("type", { length: 255 })
.$type<AdapterAccount["type"]>()
.notNull(),
provider: varchar("provider", { length: 255 }).notNull(),
providerAccountId: varchar("providerAccountId", {
length: 255,
}).notNull(),
refresh_token: varchar("refresh_token", { length: 255 }),
access_token: varchar("access_token", { length: 255 }),
expires_at: int("expires_at"),
token_type: varchar("token_type", { length: 255 }),
scope: varchar("scope", { length: 255 }),
id_token: varchar("id_token", { length: 255 }),
session_state: varchar("session_state", { length: 255 }),
},
(account) => ({
compoundKey: primaryKey(account.provider, account.providerAccountId),
})
)
const sessions = mySqlTable("session", {
sessionToken: varchar("sessionToken", { length: 255 })
.notNull()
.primaryKey(),
userId: varchar("userId", { length: 255 })
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
expires: timestamp("expires", { mode: "date" }).notNull(),
})
const verificationTokens = mySqlTable(
"verificationToken",
{
identifier: varchar("identifier", { length: 255 }).notNull(),
token: varchar("token", { length: 255 }).notNull(),
expires: timestamp("expires", { mode: "date" }).notNull(),
},
(vt) => ({
compoundKey: primaryKey(vt.identifier, vt.token),
})
)
return { users, accounts, sessions, verificationTokens }
}
export type DefaultSchema = ReturnType<typeof createTables>
export function mySqlDrizzleAdapter(
client: MySql2Database<Record<string, never>>,
tableFn = defaultMySqlTableFn
): Adapter {
const { users, accounts, sessions, verificationTokens } =
createTables(tableFn)
return {
async createUser(data) {
const id = crypto.randomUUID()
await client.insert(users).values({ ...data, id })
return await client
.select()
.from(users)
.where(eq(users.id, id))
.then((res) => res[0])
},
async getUser(data) {
const thing =
(await client
.select()
.from(users)
.where(eq(users.id, data))
.then((res) => res[0])) ?? null
return thing
},
async getUserByEmail(data) {
const user =
(await client
.select()
.from(users)
.where(eq(users.email, data))
.then((res) => res[0])) ?? null
return user
},
async createSession(data) {
await client.insert(sessions).values(data)
return await client
.select()
.from(sessions)
.where(eq(sessions.sessionToken, data.sessionToken))
.then((res) => res[0])
},
async getSessionAndUser(data) {
const sessionAndUser =
(await client
.select({
session: sessions,
user: users,
})
.from(sessions)
.where(eq(sessions.sessionToken, data))
.innerJoin(users, eq(users.id, sessions.userId))
.then((res) => res[0])) ?? null
return sessionAndUser
},
async updateUser(data) {
if (!data.id) {
throw new Error("No user id.")
}
await client.update(users).set(data).where(eq(users.id, data.id))
return await client
.select()
.from(users)
.where(eq(users.id, data.id))
.then((res) => res[0])
},
async updateSession(data) {
await client
.update(sessions)
.set(data)
.where(eq(sessions.sessionToken, data.sessionToken))
return await client
.select()
.from(sessions)
.where(eq(sessions.sessionToken, data.sessionToken))
.then((res) => res[0])
},
async linkAccount(rawAccount) {
await client
.insert(accounts)
.values(rawAccount)
.then((res) => res[0])
},
async getUserByAccount(account) {
const dbAccount =
(await client
.select()
.from(accounts)
.where(
and(
eq(accounts.providerAccountId, account.providerAccountId),
eq(accounts.provider, account.provider)
)
)
.leftJoin(users, eq(accounts.userId, users.id))
.then((res) => res[0])) ?? null
if (!dbAccount) {
return null
}
return dbAccount.user
},
async deleteSession(sessionToken) {
const session =
(await client
.select()
.from(sessions)
.where(eq(sessions.sessionToken, sessionToken))
.then((res) => res[0])) ?? null
await client
.delete(sessions)
.where(eq(sessions.sessionToken, sessionToken))
return session
},
async createVerificationToken(token) {
await client.insert(verificationTokens).values(token)
return await client
.select()
.from(verificationTokens)
.where(eq(verificationTokens.identifier, token.identifier))
.then((res) => res[0])
},
async useVerificationToken(token) {
try {
const deletedToken =
(await client
.select()
.from(verificationTokens)
.where(
and(
eq(verificationTokens.identifier, token.identifier),
eq(verificationTokens.token, token.token)
)
)
.then((res) => res[0])) ?? null
await client
.delete(verificationTokens)
.where(
and(
eq(verificationTokens.identifier, token.identifier),
eq(verificationTokens.token, token.token)
)
)
return deletedToken
} catch (err) {
throw new Error("No verification token found.")
}
},
async deleteUser(id) {
const user = await client
.select()
.from(users)
.where(eq(users.id, id))
.then((res) => res[0] ?? null)
await client.delete(users).where(eq(users.id, id))
return user
},
async unlinkAccount(account) {
await client
.delete(accounts)
.where(
and(
eq(accounts.providerAccountId, account.providerAccountId),
eq(accounts.provider, account.provider)
)
)
return undefined
},
}
}

View File

@@ -1,233 +0,0 @@
import { and, eq } from "drizzle-orm"
import {
timestamp,
pgTable as defaultPgTableFn,
text,
primaryKey,
integer,
PgTableFn,
} from "drizzle-orm/pg-core"
import type { PostgresJsDatabase } from "drizzle-orm/postgres-js"
import type { Adapter, AdapterAccount } from "@auth/core/adapters"
export function createTables(pgTable: PgTableFn) {
const users = pgTable("user", {
id: text("id").notNull().primaryKey(),
name: text("name"),
email: text("email").notNull(),
emailVerified: timestamp("emailVerified", { mode: "date" }),
image: text("image"),
})
const accounts = pgTable(
"account",
{
userId: text("userId")
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
type: text("type").$type<AdapterAccount["type"]>().notNull(),
provider: text("provider").notNull(),
providerAccountId: text("providerAccountId").notNull(),
refresh_token: text("refresh_token"),
access_token: text("access_token"),
expires_at: integer("expires_at"),
token_type: text("token_type"),
scope: text("scope"),
id_token: text("id_token"),
session_state: text("session_state"),
},
(account) => ({
compoundKey: primaryKey(account.provider, account.providerAccountId),
})
)
const sessions = pgTable("session", {
sessionToken: text("sessionToken").notNull().primaryKey(),
userId: text("userId")
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
expires: timestamp("expires", { mode: "date" }).notNull(),
})
const verificationTokens = pgTable(
"verificationToken",
{
identifier: text("identifier").notNull(),
token: text("token").notNull(),
expires: timestamp("expires", { mode: "date" }).notNull(),
},
(vt) => ({
compoundKey: primaryKey(vt.identifier, vt.token),
})
)
return { users, accounts, sessions, verificationTokens }
}
export type DefaultSchema = ReturnType<typeof createTables>
export function pgDrizzleAdapter(
client: PostgresJsDatabase<Record<string, never>>,
tableFn = defaultPgTableFn
): Adapter {
const { users, accounts, sessions, verificationTokens } =
createTables(tableFn)
return {
async createUser(data) {
return await client
.insert(users)
.values({ ...data, id: crypto.randomUUID() })
.returning()
.then((res) => res[0] ?? null)
},
async getUser(data) {
return await client
.select()
.from(users)
.where(eq(users.id, data))
.then((res) => res[0] ?? null)
},
async getUserByEmail(data) {
return await client
.select()
.from(users)
.where(eq(users.email, data))
.then((res) => res[0] ?? null)
},
async createSession(data) {
return await client
.insert(sessions)
.values(data)
.returning()
.then((res) => res[0])
},
async getSessionAndUser(data) {
return await client
.select({
session: sessions,
user: users,
})
.from(sessions)
.where(eq(sessions.sessionToken, data))
.innerJoin(users, eq(users.id, sessions.userId))
.then((res) => res[0] ?? null)
},
async updateUser(data) {
if (!data.id) {
throw new Error("No user id.")
}
return await client
.update(users)
.set(data)
.where(eq(users.id, data.id))
.returning()
.then((res) => res[0])
},
async updateSession(data) {
return await client
.update(sessions)
.set(data)
.where(eq(sessions.sessionToken, data.sessionToken))
.returning()
.then((res) => res[0])
},
async linkAccount(rawAccount) {
const updatedAccount = await client
.insert(accounts)
.values(rawAccount)
.returning()
.then((res) => res[0])
// Drizzle will return `null` for fields that are not defined.
// However, the return type is expecting `undefined`.
const account = {
...updatedAccount,
access_token: updatedAccount.access_token ?? undefined,
token_type: updatedAccount.token_type ?? undefined,
id_token: updatedAccount.id_token ?? undefined,
refresh_token: updatedAccount.refresh_token ?? undefined,
scope: updatedAccount.scope ?? undefined,
expires_at: updatedAccount.expires_at ?? undefined,
session_state: updatedAccount.session_state ?? undefined,
}
return account
},
async getUserByAccount(account) {
const dbAccount =
(await client
.select()
.from(accounts)
.where(
and(
eq(accounts.providerAccountId, account.providerAccountId),
eq(accounts.provider, account.provider)
)
)
.leftJoin(users, eq(accounts.userId, users.id))
.then((res) => res[0])) ?? null
if (!dbAccount) {
return null
}
return dbAccount.user
},
async deleteSession(sessionToken) {
const session = await client
.delete(sessions)
.where(eq(sessions.sessionToken, sessionToken))
.returning()
.then((res) => res[0] ?? null)
return session
},
async createVerificationToken(token) {
return await client
.insert(verificationTokens)
.values(token)
.returning()
.then((res) => res[0])
},
async useVerificationToken(token) {
try {
return await client
.delete(verificationTokens)
.where(
and(
eq(verificationTokens.identifier, token.identifier),
eq(verificationTokens.token, token.token)
)
)
.returning()
.then((res) => res[0] ?? null)
} catch (err) {
throw new Error("No verification token found.")
}
},
async deleteUser(id) {
await client
.delete(users)
.where(eq(users.id, id))
.returning()
.then((res) => res[0] ?? null)
},
async unlinkAccount(account) {
const { type, provider, providerAccountId, userId } = await client
.delete(accounts)
.where(
and(
eq(accounts.providerAccountId, account.providerAccountId),
eq(accounts.provider, account.provider)
)
)
.returning()
.then((res) => res[0] ?? null)
return { provider, type, providerAccountId, userId }
},
}
}

View File

@@ -1,211 +0,0 @@
import { eq, and } from "drizzle-orm"
import {
integer,
sqliteTable as defaultSqliteTableFn,
text,
primaryKey,
BaseSQLiteDatabase,
SQLiteTableFn,
} from "drizzle-orm/sqlite-core"
import type { Adapter, AdapterAccount } from "@auth/core/adapters"
export function createTables(sqliteTable: SQLiteTableFn) {
const users = sqliteTable("user", {
id: text("id").notNull().primaryKey(),
name: text("name"),
email: text("email").notNull(),
emailVerified: integer("emailVerified", { mode: "timestamp_ms" }),
image: text("image"),
})
const accounts = sqliteTable(
"account",
{
userId: text("userId")
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
type: text("type").$type<AdapterAccount["type"]>().notNull(),
provider: text("provider").notNull(),
providerAccountId: text("providerAccountId").notNull(),
refresh_token: text("refresh_token"),
access_token: text("access_token"),
expires_at: integer("expires_at"),
token_type: text("token_type"),
scope: text("scope"),
id_token: text("id_token"),
session_state: text("session_state"),
},
(account) => ({
compoundKey: primaryKey(account.provider, account.providerAccountId),
})
)
const sessions = sqliteTable("session", {
sessionToken: text("sessionToken").notNull().primaryKey(),
userId: text("userId")
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
expires: integer("expires", { mode: "timestamp_ms" }).notNull(),
})
const verificationTokens = sqliteTable(
"verificationToken",
{
identifier: text("identifier").notNull(),
token: text("token").notNull(),
expires: integer("expires", { mode: "timestamp_ms" }).notNull(),
},
(vt) => ({
compoundKey: primaryKey(vt.identifier, vt.token),
})
)
return { users, accounts, sessions, verificationTokens }
}
export type DefaultSchema = ReturnType<typeof createTables>
export function SQLiteDrizzleAdapter(
client: BaseSQLiteDatabase<any, any>,
tableFn = defaultSqliteTableFn
): Adapter {
const { users, accounts, sessions, verificationTokens } =
createTables(tableFn)
return {
createUser(data) {
return client
.insert(users)
.values({ ...data, id: crypto.randomUUID() })
.returning()
.get()
},
getUser(data) {
return client.select().from(users).where(eq(users.id, data)).get() ?? null
},
getUserByEmail(data) {
return (
client.select().from(users).where(eq(users.email, data)).get() ?? null
)
},
createSession(data) {
return client.insert(sessions).values(data).returning().get()
},
getSessionAndUser(data) {
return (
client
.select({
session: sessions,
user: users,
})
.from(sessions)
.where(eq(sessions.sessionToken, data))
.innerJoin(users, eq(users.id, sessions.userId))
.get() ?? null
)
},
updateUser(data) {
if (!data.id) {
throw new Error("No user id.")
}
return client
.update(users)
.set(data)
.where(eq(users.id, data.id))
.returning()
.get()
},
updateSession(data) {
return client
.update(sessions)
.set(data)
.where(eq(sessions.sessionToken, data.sessionToken))
.returning()
.get()
},
linkAccount(rawAccount) {
const updatedAccount = client
.insert(accounts)
.values(rawAccount)
.returning()
.get()
const account: AdapterAccount = {
...updatedAccount,
type: updatedAccount.type,
access_token: updatedAccount.access_token ?? undefined,
token_type: updatedAccount.token_type ?? undefined,
id_token: updatedAccount.id_token ?? undefined,
refresh_token: updatedAccount.refresh_token ?? undefined,
scope: updatedAccount.scope ?? undefined,
expires_at: updatedAccount.expires_at ?? undefined,
session_state: updatedAccount.session_state ?? undefined,
}
return account
},
getUserByAccount(account) {
const results = client
.select()
.from(accounts)
.leftJoin(users, eq(users.id, accounts.userId))
.where(
and(
eq(accounts.provider, account.provider),
eq(accounts.providerAccountId, account.providerAccountId)
)
)
.get()
return results?.user ?? null
},
deleteSession(sessionToken) {
return (
client
.delete(sessions)
.where(eq(sessions.sessionToken, sessionToken))
.returning()
.get() ?? null
)
},
createVerificationToken(token) {
return client.insert(verificationTokens).values(token).returning().get()
},
useVerificationToken(token) {
try {
return (
client
.delete(verificationTokens)
.where(
and(
eq(verificationTokens.identifier, token.identifier),
eq(verificationTokens.token, token.token)
)
)
.returning()
.get() ?? null
)
} catch (err) {
throw new Error("No verification token found.")
}
},
deleteUser(id) {
return client.delete(users).where(eq(users.id, id)).returning().get()
},
unlinkAccount(account) {
client
.delete(accounts)
.where(
and(
eq(accounts.providerAccountId, account.providerAccountId),
eq(accounts.provider, account.provider)
)
)
.run()
return undefined
},
}
}

View File

@@ -1,55 +0,0 @@
import { MySqlDatabase } from "drizzle-orm/mysql-core"
import { PgDatabase } from "drizzle-orm/pg-core"
import { BaseSQLiteDatabase } from "drizzle-orm/sqlite-core"
import type { AnyMySqlTable, MySqlTableFn } from "drizzle-orm/mysql-core"
import type { AnyPgTable, PgTableFn } from "drizzle-orm/pg-core"
import type { AnySQLiteTable, SQLiteTableFn } from "drizzle-orm/sqlite-core"
import type { DefaultSchema as PgSchema } from "./pg.js"
import type { DefaultSchema as MySqlSchema } from "./mysql.js"
import type { DefaultSchema as SQLiteSchema } from "./sqlite.js"
export type AnyMySqlDatabase = MySqlDatabase<any, any>
export type AnyPgDatabase = PgDatabase<any, any, any>
export type AnySQLiteDatabase = BaseSQLiteDatabase<any, any, any, any>
export interface MinimumSchema {
mysql: MySqlSchema & Record<string, AnyMySqlTable>
pg: PgSchema & Record<string, AnyPgTable>
sqlite: SQLiteSchema & Record<string, AnySQLiteTable>
}
export type SqlFlavorOptions =
| AnyMySqlDatabase
| AnyPgDatabase
| AnySQLiteDatabase
export type ClientFlavors<Flavor> = Flavor extends AnyMySqlDatabase
? MinimumSchema["mysql"]
: Flavor extends AnyPgDatabase
? MinimumSchema["pg"]
: Flavor extends AnySQLiteDatabase
? MinimumSchema["sqlite"]
: never
export type TableFn<Flavor> = Flavor extends AnyMySqlDatabase
? MySqlTableFn
: Flavor extends AnyPgDatabase
? PgTableFn
: Flavor extends AnySQLiteDatabase
? SQLiteTableFn
: AnySQLiteTable
export function isMySqlDatabase(
db: any
): db is MySqlDatabase<any, any, any, any> {
return db instanceof MySqlDatabase
}
export function isPgDatabase(db: any): db is PgDatabase<any, any, any> {
return db instanceof PgDatabase
}
export function isSQLiteDatabase(db: any): db is AnySQLiteDatabase {
return db instanceof BaseSQLiteDatabase
}

View File

@@ -1,43 +0,0 @@
// This work is needed as workaround to Drizzle truncating millisecond precision.
// https://github.com/drizzle-team/drizzle-orm/pull/668
import { randomUUID } from "../../adapter-test"
const emailVerified = new Date()
emailVerified.setMilliseconds(0)
const ONE_WEEK_FROM_NOW = new Date(Date.now() + 1000 * 60 * 60 * 24 * 7)
ONE_WEEK_FROM_NOW.setMilliseconds(0)
const FIFTEEN_MINUTES_FROM_NOW = new Date(Date.now() + 15 * 60 * 1000)
FIFTEEN_MINUTES_FROM_NOW.setMilliseconds(0)
const ONE_MONTH = 1000 * 60 * 60 * 24 * 30
const ONE_MONTH_FROM_NOW = new Date(Date.now() + ONE_MONTH)
ONE_MONTH_FROM_NOW.setMilliseconds(0)
export const fixtures = {
user: {
email: "fill@murray.com",
image: "https://www.fillmurray.com/460/300",
name: "Fill Murray",
emailVerified,
},
session: {
sessionToken: randomUUID(),
expires: ONE_WEEK_FROM_NOW,
},
sessionUpdateExpires: ONE_MONTH_FROM_NOW,
verificationTokenExpires: FIFTEEN_MINUTES_FROM_NOW,
account: {
provider: "github",
providerAccountId: randomUUID(),
type: "oauth",
access_token: randomUUID(),
expires_at: ONE_MONTH / 1000,
id_token: randomUUID(),
refresh_token: randomUUID(),
token_type: "bearer",
scope: "user",
session_state: randomUUID(),
},
}

View File

@@ -1,13 +0,0 @@
import type { Config } from "drizzle-kit"
export default {
schema: "./tests/mysql/schema.ts",
out: "./tests/mysql/.drizzle",
driver: "mysql2",
dbCredentials: {
host: "localhost",
user: "root",
password: "password",
database: "next-auth",
},
} satisfies Config

View File

@@ -1,71 +0,0 @@
import { runBasicTests } from "../../../adapter-test"
import { DrizzleAdapter } from "../../src"
import { db, sessions, verificationTokens, accounts, users } from "./schema"
import { eq, and } from "drizzle-orm"
import { fixtures } from "../fixtures"
globalThis.crypto ??= require("node:crypto").webcrypto
runBasicTests({
adapter: DrizzleAdapter(db),
fixtures,
db: {
connect: async () => {
await Promise.all([
db.delete(sessions),
db.delete(accounts),
db.delete(verificationTokens),
db.delete(users),
])
},
disconnect: async () => {
await Promise.all([
db.delete(sessions),
db.delete(accounts),
db.delete(verificationTokens),
db.delete(users),
])
},
user: async (id) => {
const user = await db
.select()
.from(users)
.where(eq(users.id, id))
.then((res) => res[0] ?? null)
return user
},
session: async (sessionToken) => {
const session = await db
.select()
.from(sessions)
.where(eq(sessions.sessionToken, sessionToken))
.then((res) => res[0] ?? null)
return session
},
account: (provider_providerAccountId) => {
const account = db
.select()
.from(accounts)
.where(
eq(
accounts.providerAccountId,
provider_providerAccountId.providerAccountId
)
)
.then((res) => res[0] ?? null)
return account
},
verificationToken: (identifier_token) =>
db
.select()
.from(verificationTokens)
.where(
and(
eq(verificationTokens.token, identifier_token.token),
eq(verificationTokens.identifier, identifier_token.identifier)
)
)
.then((res) => res[0]) ?? null,
},
})

View File

@@ -1,19 +0,0 @@
import { mysqlTableCreator } from "drizzle-orm/mysql-core"
import { drizzle } from "drizzle-orm/mysql2"
import { createPool } from "mysql2"
import { createTables } from "../../src/lib/mysql"
const poolConnection = createPool({
host: "localhost",
user: "root",
password: "password",
database: "next-auth",
})
const mysqlTable = mysqlTableCreator((name) => `foobar_${name}`)
export const { users, accounts, sessions, verificationTokens } =
createTables(mysqlTable)
export const schema = { users, accounts, sessions, verificationTokens }
export const db = drizzle(poolConnection, { schema })

View File

@@ -1,22 +0,0 @@
#!/usr/bin/env bash
echo "Initializing container for MySQL tests."
MYSQL_DATABASE=next-auth
MYSQL_ROOT_PASSWORD=password
MYSQL_CONTAINER_NAME=next-auth-mysql-test
docker run -d --rm \
-e MYSQL_DATABASE=${MYSQL_DATABASE} \
-e MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD} \
--name "${MYSQL_CONTAINER_NAME}" \
-p 3306:3306 \
mysql:8 \
--default-authentication-plugin=mysql_native_password
echo "Waiting 15 sec for db to start..." && sleep 15
drizzle-kit generate:mysql --config=./tests/mysql/drizzle.config.ts
drizzle-kit push:mysql --config=./tests/mysql/drizzle.config.ts
jest ./tests/mysql/index.test.ts --forceExit
docker stop ${MYSQL_CONTAINER_NAME}

View File

@@ -1,13 +0,0 @@
import type { Config } from "drizzle-kit"
export default {
schema: "./tests/mysql/schema.ts",
out: "./tests/mysql/.drizzle",
driver: "mysql2",
dbCredentials: {
host: "localhost",
user: "root",
password: "password",
database: "next-auth",
},
} satisfies Config

View File

@@ -1,71 +0,0 @@
import { runBasicTests } from "../../../adapter-test"
import { DrizzleAdapter } from "../../src"
import { db, sessions, verificationTokens, accounts, users } from "./schema"
import { eq, and } from "drizzle-orm"
import { fixtures } from "../fixtures"
globalThis.crypto ??= require("node:crypto").webcrypto
runBasicTests({
adapter: DrizzleAdapter(db),
fixtures,
db: {
connect: async () => {
await Promise.all([
db.delete(sessions),
db.delete(accounts),
db.delete(verificationTokens),
db.delete(users),
])
},
disconnect: async () => {
await Promise.all([
db.delete(sessions),
db.delete(accounts),
db.delete(verificationTokens),
db.delete(users),
])
},
user: async (id) => {
const user = await db
.select()
.from(users)
.where(eq(users.id, id))
.then((res) => res[0] ?? null)
return user
},
session: async (sessionToken) => {
const session = await db
.select()
.from(sessions)
.where(eq(sessions.sessionToken, sessionToken))
.then((res) => res[0] ?? null)
return session
},
account: (provider_providerAccountId) => {
const account = db
.select()
.from(accounts)
.where(
eq(
accounts.providerAccountId,
provider_providerAccountId.providerAccountId
)
)
.then((res) => res[0] ?? null)
return account
},
verificationToken: (identifier_token) =>
db
.select()
.from(verificationTokens)
.where(
and(
eq(verificationTokens.token, identifier_token.token),
eq(verificationTokens.identifier, identifier_token.identifier)
)
)
.then((res) => res[0]) ?? null,
},
})

View File

@@ -1,17 +0,0 @@
import { mysqlTable } from "drizzle-orm/mysql-core"
import { drizzle } from "drizzle-orm/mysql2"
import { createPool } from "mysql2"
import { createTables } from "../../src/lib/mysql"
const poolConnection = createPool({
host: "localhost",
user: "root",
password: "password",
database: "next-auth",
})
export const { users, accounts, sessions, verificationTokens } =
createTables(mysqlTable)
export const schema = { users, accounts, sessions, verificationTokens }
export const db = drizzle(poolConnection, { schema })

View File

@@ -1,22 +0,0 @@
#!/usr/bin/env bash
echo "Initializing container for MySQL tests."
MYSQL_DATABASE=next-auth
MYSQL_ROOT_PASSWORD=password
MYSQL_CONTAINER_NAME=next-auth-mysql-test
docker run -d --rm \
-e MYSQL_DATABASE=${MYSQL_DATABASE} \
-e MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD} \
--name "${MYSQL_CONTAINER_NAME}" \
-p 3306:3306 \
mysql:8 \
--default-authentication-plugin=mysql_native_password
echo "Waiting 15 sec for db to start..." && sleep 15
drizzle-kit generate:mysql --config=./tests/mysql/drizzle.config.ts
drizzle-kit push:mysql --config=./tests/mysql/drizzle.config.ts
jest ./tests/mysql/index.test.ts --forceExit
docker stop ${MYSQL_CONTAINER_NAME}

View File

@@ -1,13 +0,0 @@
import type { Config } from "drizzle-kit"
export default {
schema: "./tests/pg/schema.ts",
out: "./tests/pg/.drizzle",
dbCredentials: {
database: "nextauth",
host: "nextauth",
user: "nextauth",
password: "nextauth",
port: 5432,
},
} satisfies Config

View File

@@ -1,65 +0,0 @@
import { runBasicTests } from "../../../adapter-test"
import { DrizzleAdapter } from "../../src"
import { db, accounts, sessions, users, verificationTokens } from "./schema"
import { eq, and } from "drizzle-orm"
import { fixtures } from "../fixtures"
globalThis.crypto ??= require("node:crypto").webcrypto
runBasicTests({
adapter: DrizzleAdapter(db),
fixtures,
db: {
connect: async () => {
await Promise.all([
db.delete(sessions),
db.delete(accounts),
db.delete(verificationTokens),
db.delete(users),
])
},
disconnect: async () => {
await Promise.all([
db.delete(sessions),
db.delete(accounts),
db.delete(verificationTokens),
db.delete(users),
])
},
user: async (id) =>
db
.select()
.from(users)
.where(eq(users.id, id))
.then((res) => res[0] ?? null),
session: (sessionToken) =>
db
.select()
.from(sessions)
.where(eq(sessions.sessionToken, sessionToken))
.then((res) => res[0] ?? null),
account: (provider_providerAccountId) => {
return db
.select()
.from(accounts)
.where(
eq(
accounts.providerAccountId,
provider_providerAccountId.providerAccountId
)
)
.then((res) => res[0] ?? null)
},
verificationToken: (identifier_token) =>
db
.select()
.from(verificationTokens)
.where(
and(
eq(verificationTokens.token, identifier_token.token),
eq(verificationTokens.identifier, identifier_token.identifier)
)
)
.then((res) => res[0] ?? null),
},
})

View File

@@ -1,10 +0,0 @@
import { migrate } from "drizzle-orm/postgres-js/migrator"
import { db } from "./schema"
const migrator = async () => {
await migrate(db, { migrationsFolder: "./tests/pg/.drizzle" })
}
migrator()
.then(() => process.exit(0))
.catch(() => process.exit(1))

View File

@@ -1,17 +0,0 @@
import { drizzle } from "drizzle-orm/postgres-js"
import postgres from "postgres"
import { createTables } from "../../src/lib/pg"
import { pgTableCreator } from "drizzle-orm/pg-core"
const connectionString = "postgres://nextauth:nextauth@localhost:5432/nextauth"
const sql = postgres(connectionString, { max: 1 })
const pgTable = pgTableCreator((name) => `foobar_${name}`)
export const { users, accounts, sessions, verificationTokens } =
createTables(pgTable)
export const schema = { users, accounts, sessions, verificationTokens }
export const db = drizzle(sql, {
schema,
})

View File

@@ -1,25 +0,0 @@
#!/usr/bin/env bash
echo "Initializing container for PostgreSQL tests."
PGUSER=nextauth
PGPASSWORD=nextauth
PGDATABASE=nextauth
PGPORT=5432
PG_CONTAINER_NAME=next-auth-postgres-test
docker run -d --rm \
-e POSTGRES_USER=${PGUSER} \
-e POSTGRES_PASSWORD=${PGUSER} \
-e POSTGRES_DB=${PGDATABASE} \
-e POSTGRES_HOST_AUTH_METHOD=trust \
--name "${PG_CONTAINER_NAME}" \
-p ${PGPORT}:5432 \
postgres:15.3
echo "Waiting 15 sec for db to start..." && sleep 15
drizzle-kit generate:pg --config=./tests/pg/drizzle.config.ts
npx tsx ./tests/pg/migrator.ts
jest ./tests/pg/index.test.ts --forceExit
docker stop ${PG_CONTAINER_NAME}

View File

@@ -1,13 +0,0 @@
import type { Config } from "drizzle-kit"
export default {
schema: "./tests/pg/schema.ts",
out: "./tests/pg/.drizzle",
dbCredentials: {
database: "nextauth",
host: "nextauth",
user: "nextauth",
password: "nextauth",
port: 5432,
},
} satisfies Config

View File

@@ -1,65 +0,0 @@
import { runBasicTests } from "../../../adapter-test"
import { DrizzleAdapter } from "../../src"
import { db, accounts, sessions, users, verificationTokens } from "./schema"
import { eq, and } from "drizzle-orm"
import { fixtures } from "../fixtures"
globalThis.crypto ??= require("node:crypto").webcrypto
runBasicTests({
adapter: DrizzleAdapter(db),
fixtures,
db: {
connect: async () => {
await Promise.all([
db.delete(sessions),
db.delete(accounts),
db.delete(verificationTokens),
db.delete(users),
])
},
disconnect: async () => {
await Promise.all([
db.delete(sessions),
db.delete(accounts),
db.delete(verificationTokens),
db.delete(users),
])
},
user: async (id) =>
db
.select()
.from(users)
.where(eq(users.id, id))
.then((res) => res[0] ?? null),
session: (sessionToken) =>
db
.select()
.from(sessions)
.where(eq(sessions.sessionToken, sessionToken))
.then((res) => res[0] ?? null),
account: (provider_providerAccountId) => {
return db
.select()
.from(accounts)
.where(
eq(
accounts.providerAccountId,
provider_providerAccountId.providerAccountId
)
)
.then((res) => res[0] ?? null)
},
verificationToken: (identifier_token) =>
db
.select()
.from(verificationTokens)
.where(
and(
eq(verificationTokens.token, identifier_token.token),
eq(verificationTokens.identifier, identifier_token.identifier)
)
)
.then((res) => res[0] ?? null),
},
})

View File

@@ -1,10 +0,0 @@
import { migrate } from "drizzle-orm/postgres-js/migrator"
import { db } from "./schema"
const migrator = async () => {
await migrate(db, { migrationsFolder: "./tests/pg/.drizzle" })
}
migrator()
.then(() => process.exit(0))
.catch(() => process.exit(1))

View File

@@ -1,15 +0,0 @@
import { drizzle } from "drizzle-orm/postgres-js"
import postgres from "postgres"
import { createTables } from "../../src/lib/pg"
import { pgTable } from "drizzle-orm/pg-core"
const connectionString = "postgres://nextauth:nextauth@localhost:5432/nextauth"
const sql = postgres(connectionString, { max: 1 })
export const { users, accounts, sessions, verificationTokens } =
createTables(pgTable)
export const schema = { users, accounts, sessions, verificationTokens }
export const db = drizzle(sql, {
schema,
})

View File

@@ -1,25 +0,0 @@
#!/usr/bin/env bash
echo "Initializing container for PostgreSQL tests."
PGUSER=nextauth
PGPASSWORD=nextauth
PGDATABASE=nextauth
PGPORT=5432
PG_CONTAINER_NAME=next-auth-postgres-test
docker run -d --rm \
-e POSTGRES_USER=${PGUSER} \
-e POSTGRES_PASSWORD=${PGUSER} \
-e POSTGRES_DB=${PGDATABASE} \
-e POSTGRES_HOST_AUTH_METHOD=trust \
--name "${PG_CONTAINER_NAME}" \
-p ${PGPORT}:5432 \
postgres:15.3
echo "Waiting 15 sec for db to start..." && sleep 15
drizzle-kit generate:pg --config=./tests/pg/drizzle.config.ts
npx tsx ./tests/pg/migrator.ts
jest ./tests/pg/index.test.ts --forceExit
docker stop ${PG_CONTAINER_NAME}

View File

@@ -1,10 +0,0 @@
import type { Config } from "drizzle-kit"
export default {
schema: "./tests/sqlite/schema.ts",
out: "./tests/sqlite/.drizzle",
driver: "better-sqlite",
dbCredentials: {
url: "./db.sqlite",
},
} satisfies Config

View File

@@ -1,60 +0,0 @@
import { runBasicTests } from "../../../adapter-test"
import { DrizzleAdapter } from "../../src"
import { db, accounts, sessions, users, verificationTokens } from "./schema"
import { eq, and } from "drizzle-orm"
globalThis.crypto ??= require("node:crypto").webcrypto
runBasicTests({
adapter: DrizzleAdapter(db),
db: {
connect: async () => {
await Promise.all([
db.delete(sessions),
db.delete(accounts),
db.delete(verificationTokens),
db.delete(users),
])
},
disconnect: async () => {
await Promise.all([
db.delete(sessions),
db.delete(accounts),
db.delete(verificationTokens),
db.delete(users),
])
},
user: (id) => db.select().from(users).where(eq(users.id, id)).get() ?? null,
session: (sessionToken) =>
db
.select()
.from(sessions)
.where(eq(sessions.sessionToken, sessionToken))
.get() ?? null,
account: (provider_providerAccountId) => {
return (
db
.select()
.from(accounts)
.where(
eq(
accounts.providerAccountId,
provider_providerAccountId.providerAccountId
)
)
.get() ?? null
)
},
verificationToken: (identifier_token) =>
db
.select()
.from(verificationTokens)
.where(
and(
eq(verificationTokens.token, identifier_token.token),
eq(verificationTokens.identifier, identifier_token.identifier)
)
)
.get() ?? null,
},
})

View File

@@ -1,14 +0,0 @@
import { drizzle } from "drizzle-orm/better-sqlite3"
import Database from "better-sqlite3"
import { createTables } from "../../src/lib/sqlite"
import { sqliteTableCreator } from "drizzle-orm/sqlite-core"
const sqlite = new Database("db.sqlite")
const sqliteTable = sqliteTableCreator((name) => `foobar_${name}`)
export const { users, accounts, sessions, verificationTokens } =
createTables(sqliteTable)
export const schema = { users, accounts, sessions, verificationTokens }
export const db = drizzle(sqlite, { schema })

View File

@@ -1,12 +0,0 @@
#!/usr/bin/env bash
set -eu
echo "Running SQLite tests."
rm -f db.sqlite
drizzle-kit generate:sqlite --config=./tests/sqlite/drizzle.config.ts
drizzle-kit push:sqlite --config=./tests/sqlite/drizzle.config.ts
jest ./tests/sqlite/index.test.ts --forceExit

View File

@@ -1,10 +0,0 @@
import type { Config } from "drizzle-kit"
export default {
schema: "./tests/sqlite/schema.ts",
out: "./tests/sqlite/.drizzle",
driver: "better-sqlite",
dbCredentials: {
url: "./db.sqlite",
},
} satisfies Config

View File

@@ -1,60 +0,0 @@
import { runBasicTests } from "../../../adapter-test"
import { DrizzleAdapter } from "../../src"
import { db, accounts, sessions, users, verificationTokens } from "./schema"
import { eq, and } from "drizzle-orm"
globalThis.crypto ??= require("node:crypto").webcrypto
runBasicTests({
adapter: DrizzleAdapter(db),
db: {
connect: async () => {
await Promise.all([
db.delete(sessions),
db.delete(accounts),
db.delete(verificationTokens),
db.delete(users),
])
},
disconnect: async () => {
await Promise.all([
db.delete(sessions),
db.delete(accounts),
db.delete(verificationTokens),
db.delete(users),
])
},
user: (id) => db.select().from(users).where(eq(users.id, id)).get() ?? null,
session: (sessionToken) =>
db
.select()
.from(sessions)
.where(eq(sessions.sessionToken, sessionToken))
.get() ?? null,
account: (provider_providerAccountId) => {
return (
db
.select()
.from(accounts)
.where(
eq(
accounts.providerAccountId,
provider_providerAccountId.providerAccountId
)
)
.get() ?? null
)
},
verificationToken: (identifier_token) =>
db
.select()
.from(verificationTokens)
.where(
and(
eq(verificationTokens.token, identifier_token.token),
eq(verificationTokens.identifier, identifier_token.identifier)
)
)
.get() ?? null,
},
})

View File

@@ -1,12 +0,0 @@
import { drizzle } from "drizzle-orm/better-sqlite3"
import Database from "better-sqlite3"
import { createTables } from "../../src/lib/sqlite"
import { sqliteTable } from "drizzle-orm/sqlite-core"
const sqlite = new Database("db.sqlite")
export const { users, accounts, sessions, verificationTokens } =
createTables(sqliteTable)
export const schema = { users, accounts, sessions, verificationTokens }
export const db = drizzle(sqlite, { schema })

View File

@@ -1,12 +0,0 @@
#!/usr/bin/env bash
set -eu
echo "Running SQLite tests."
rm -f db.sqlite
drizzle-kit generate:sqlite --config=./tests/sqlite/drizzle.config.ts
drizzle-kit push:sqlite --config=./tests/sqlite/drizzle.config.ts
jest ./tests/sqlite/index.test.ts --forceExit

View File

@@ -1,25 +0,0 @@
{
"extends": "@next-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

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

View File

@@ -8,14 +8,14 @@
</a>
<h3 align="center"><b>DynamoDB Adapter</b> - NextAuth.js / Auth.js</a></h3>
<p align="center" style="align: center;">
<a href="https://npm.im/@auth/dynamodb-adapter">
<a href="https://npm.im/@next-auth/dynamodb-adapter">
<img src="https://img.shields.io/badge/TypeScript-blue?style=flat-square" alt="TypeScript" />
</a>
<a href="https://npm.im/@auth/dynamodb-adapter">
<img alt="npm" src="https://img.shields.io/npm/v/@auth/dynamodb-adapter?color=green&label=@auth/dynamodb-adapter&style=flat-square">
<a href="https://npm.im/@next-auth/dynamodb-adapter">
<img alt="npm" src="https://img.shields.io/npm/v/@next-auth/dynamodb-adapter?color=green&label=@next-auth/dynamodb-adapter&style=flat-square">
</a>
<a href="https://www.npmtrends.com/@auth/dynamodb-adapter">
<img src="https://img.shields.io/npm/dm/@auth/dynamodb-adapter?label=%20downloads&style=flat-square" alt="Downloads" />
<a href="https://www.npmtrends.com/@next-auth/dynamodb-adapter">
<img src="https://img.shields.io/npm/dm/@next-auth/dynamodb-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" />

View File

@@ -1,7 +1,7 @@
{
"name": "@auth/dynamodb-adapter",
"name": "@next-auth/dynamodb-adapter",
"repository": "https://github.com/nextauthjs/next-auth",
"version": "1.0.1",
"version": "3.0.2",
"description": "AWS DynamoDB adapter for next-auth.",
"keywords": [
"next-auth",
@@ -9,10 +9,18 @@
"oauth",
"dynamodb"
],
"type": "module",
"types": "./index.d.ts",
"homepage": "https://authjs.dev",
"bugs": {
"url": "https://github.com/nextauthjs/next-auth/issues"
},
"exports": {
".": {
"types": "./index.d.ts",
"import": "./index.js"
}
},
"private": false,
"publishConfig": {
"access": "public"
@@ -24,27 +32,19 @@
"clean": "rm -rf index.*",
"build": "pnpm clean && tsc"
},
"author": "Pol Marnette",
"contributors": [
"Balázs Orbán <info@balazsorban.com>"
],
"license": "ISC",
"type": "module",
"types": "./index.d.ts",
"files": [
"*.js",
"*.d.ts*",
"README.md",
"index.js",
"index.d.ts",
"index.d.ts.map",
"src"
],
"exports": {
".": {
"types": "./index.d.ts",
"import": "./index.js"
}
},
"author": "Pol Marnette",
"license": "ISC",
"peerDependencies": {
"@aws-sdk/client-dynamodb": "^3.36.1",
"@aws-sdk/lib-dynamodb": "^3.36.1"
"@aws-sdk/lib-dynamodb": "^3.36.1",
"next-auth": "^4"
},
"devDependencies": {
"@aws-sdk/client-dynamodb": "^3.36.1",
@@ -52,9 +52,11 @@
"@next-auth/adapter-test": "workspace:*",
"@next-auth/tsconfig": "workspace:*",
"@shelf/jest-dynamodb": "^2.1.0",
"jest": "^27.4.3"
"@types/uuid": "^9.0.0",
"jest": "^27.4.3",
"next-auth": "workspace:*"
},
"dependencies": {
"@auth/core": "workspace:*"
"uuid": "^9.0.0"
}
}

View File

@@ -9,11 +9,12 @@
* ## Installation
*
* ```bash npm2yarn2pnpm
* npm install next-auth @auth/dynamodb-adapter
* npm install next-auth @next-auth/dynamodb-adapter
* ```
*
* @module @auth/dynamodb-adapter
* @module @next-auth/dynamodb-adapter
*/
import { v4 as uuid } from "uuid"
import type {
BatchWriteCommandInput,
@@ -25,7 +26,7 @@ import type {
AdapterAccount,
AdapterUser,
VerificationToken,
} from "@auth/core/adapters"
} from "next-auth/adapters"
export interface DynamoDBAdapterOptions {
tableName?: string
@@ -52,7 +53,7 @@ export interface DynamoDBAdapterOptions {
* import { DynamoDBDocument } from "@aws-sdk/lib-dynamodb"
* import NextAuth from "next-auth";
* import Providers from "next-auth/providers";
* import { DynamoDBAdapter } from "@auth/dynamodb-adapter"
* import { DynamoDBAdapter } from "@next-auth/dynamodb-adapter"
*
* const config: DynamoDBClientConfig = {
* credentials: {
@@ -186,7 +187,7 @@ export function DynamoDBAdapter(
async createUser(data) {
const user: AdapterUser = {
...(data as any),
id: crypto.randomUUID(),
id: uuid(),
}
await client.put({
@@ -265,8 +266,9 @@ export function DynamoDBAdapter(
const data = await client.update({
TableName,
Key: {
[pk]: `USER#${user.id}`,
[sk]: `USER#${user.id}`,
// next-auth type is incorrect it should be Partial<AdapterUser> & {id: string} instead of just Partial<AdapterUser>
[pk]: `USER#${user.id as string}`,
[sk]: `USER#${user.id as string}`,
},
UpdateExpression,
ExpressionAttributeNames,
@@ -310,7 +312,7 @@ export function DynamoDBAdapter(
async linkAccount(data) {
const item = {
...data,
id: crypto.randomUUID(),
id: uuid(),
[pk]: `USER#${data.userId}`,
[sk]: `ACCOUNT#${data.provider}#${data.providerAccountId}`,
[GSI1PK]: `ACCOUNT#${data.provider}`,
@@ -374,7 +376,7 @@ export function DynamoDBAdapter(
},
async createSession(data) {
const session = {
id: crypto.randomUUID(),
id: uuid(),
...data,
}
await client.put({

View File

@@ -1,25 +1,16 @@
{
"extends": "@next-auth/tsconfig/tsconfig.base.json",
"extends": "@next-auth/tsconfig/tsconfig.adapters.json",
"compilerOptions": {
"allowJs": true,
"baseUrl": ".",
"isolatedModules": true,
"rootDir": "src",
"outDir": ".",
"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",
]
}
"exclude": ["tests", "dist", "jest.config.js", "jest-dynamodb-config.js"]
}

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