mirror of
https://github.com/SrIzan10/next-auth.git
synced 2026-05-01 10:55:20 +00:00
Compare commits
75 Commits
@auth/core
...
@auth/core
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0681531627 | ||
|
|
ea81c467e9 | ||
|
|
bf2835d38f | ||
|
|
89d230666b | ||
|
|
f86e56f78a | ||
|
|
fe20b943ae | ||
|
|
4678c4d4fc | ||
|
|
3eb3f8f107 | ||
|
|
7fd03f38e3 | ||
|
|
ae44b72765 | ||
|
|
a996ab57e8 | ||
|
|
ebdeaf740d | ||
|
|
c5c8a81462 | ||
|
|
61d30f3dcd | ||
|
|
a9180a752b | ||
|
|
6c4180146e | ||
|
|
ec6c4ea2be | ||
|
|
3dfc86334e | ||
|
|
01d6019638 | ||
|
|
4730429a9f | ||
|
|
a49236ef62 | ||
|
|
96ade948ef | ||
|
|
550507b2d1 | ||
|
|
1eddcf643c | ||
|
|
17d71a04d6 | ||
|
|
3c65e264af | ||
|
|
28d8d4894d | ||
|
|
c6b98a8f08 | ||
|
|
d042f933c6 | ||
|
|
3a85de2c5f | ||
|
|
d47b56743e | ||
|
|
363440e515 | ||
|
|
60c5037ee1 | ||
|
|
97394baed1 | ||
|
|
f94abb8f70 | ||
|
|
bbfc11e74c | ||
|
|
2a70514df1 | ||
|
|
96d666465f | ||
|
|
ecbf0be22e | ||
|
|
87ec13bd00 | ||
|
|
c0f9af4c56 | ||
|
|
c7b36f45a3 | ||
|
|
68ff69f9eb | ||
|
|
23c0a393da | ||
|
|
f130f62a91 | ||
|
|
c111b436d2 | ||
|
|
ea895b8864 | ||
|
|
cfedc3b1a3 | ||
|
|
287a5fc05a | ||
|
|
f3ad659e91 | ||
|
|
48b9a0203e | ||
|
|
39fbccb783 | ||
|
|
f207e94146 | ||
|
|
b845729cdb | ||
|
|
e459d2d7e2 | ||
|
|
db1fd9007c | ||
|
|
0439fc5fc6 | ||
|
|
d0dd2ababc | ||
|
|
ba58d48dba | ||
|
|
a8d76ed440 | ||
|
|
3d7b8720db | ||
|
|
1e886b97bc | ||
|
|
ecb14ccecd | ||
|
|
8cee24d4ab | ||
|
|
0189a197be | ||
|
|
c44bf75c65 | ||
|
|
cf13b6c7e3 | ||
|
|
dc1a79e547 | ||
|
|
78964c115b | ||
|
|
7fa51e2a61 | ||
|
|
a79774f6e8 | ||
|
|
f779f05906 | ||
|
|
3245c02eac | ||
|
|
a8dfc8ebb1 | ||
|
|
1b80a18dd4 |
1
.github/ISSUE_TEMPLATE/2_bug_provider.yml
vendored
1
.github/ISSUE_TEMPLATE/2_bug_provider.yml
vendored
@@ -37,6 +37,7 @@ body:
|
||||
- "Bungie"
|
||||
- "Cognito"
|
||||
- "Coinbase"
|
||||
- "Descope"
|
||||
- "Discord"
|
||||
- "Dropbox"
|
||||
- "EVE Online"
|
||||
|
||||
3
.github/ISSUE_TEMPLATE/3_bug_adapter.yml
vendored
3
.github/ISSUE_TEMPLATE/3_bug_adapter.yml
vendored
@@ -22,9 +22,12 @@ body:
|
||||
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"
|
||||
|
||||
6
.github/issue-labeler.yml
vendored
6
.github/issue-labeler.yml
vendored
@@ -3,6 +3,9 @@
|
||||
dgraph:
|
||||
- "@auth/dgraph-adapter"
|
||||
|
||||
drizzle:
|
||||
- "@auth/drizzle-adapter"
|
||||
|
||||
dynamodb:
|
||||
- "@auth/dynamodb-adapter"
|
||||
|
||||
@@ -12,6 +15,9 @@ fauna:
|
||||
firebase:
|
||||
- "@auth/firebase-adapter"
|
||||
|
||||
kysely:
|
||||
- "@auth/kysely-adapter"
|
||||
|
||||
mikro-orm:
|
||||
- "@auth/mikro-orm-adapter"
|
||||
|
||||
|
||||
1
.github/pr-labeler.yml
vendored
1
.github/pr-labeler.yml
vendored
@@ -15,6 +15,7 @@ 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/**/*"]
|
||||
|
||||
84
.github/workflows/release.yml
vendored
84
.github/workflows/release.yml
vendored
@@ -11,51 +11,55 @@ 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/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"
|
||||
# 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"
|
||||
- "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
|
||||
|
||||
jobs:
|
||||
test:
|
||||
@@ -75,14 +79,14 @@ jobs:
|
||||
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
|
||||
@@ -91,7 +95,7 @@ jobs:
|
||||
# AUTH0_USERNAME: ${{ secrets.AUTH0_USERNAME }}
|
||||
# AUTH0_PASSWORD: ${{ secrets.AUTH0_PASSWORD }}
|
||||
# TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
# TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||
# TURBO_TEAM: ${{ vars.TURBO_TEAM }}
|
||||
# - name: Upload E2E artifacts
|
||||
# if: github.repository == 'nextauthjs/next-auth'
|
||||
# uses: actions/upload-artifact@v3
|
||||
|
||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -6,6 +6,8 @@
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
packages/*/.npmrc
|
||||
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
@@ -65,6 +67,7 @@ packages/adapter-prisma/prisma/dev.db
|
||||
packages/adapter-prisma/prisma/migrations
|
||||
db.sqlite
|
||||
packages/adapter-supabase/supabase/.branches
|
||||
packages/adapter-drizzle/.drizzle
|
||||
|
||||
# Tests
|
||||
coverage
|
||||
@@ -97,5 +100,7 @@ packages/frameworks-sveltekit/vite.config.js.timestamp-*
|
||||
packages/frameworks-sveltekit/vite.config.ts.timestamp-*
|
||||
|
||||
# Adapters
|
||||
docs/docs/reference/adapter
|
||||
|
||||
docs/docs/reference/adapter
|
||||
## Drizzle migration folder
|
||||
.drizzle
|
||||
153
README.md
Normal file
153
README.md
Normal file
@@ -0,0 +1,153 @@
|
||||
<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
|
||||
@@ -13,6 +13,9 @@ AUTH0_ID=
|
||||
AUTH0_SECRET=
|
||||
AUTH0_ISSUER=
|
||||
|
||||
DESCOPE_ID=
|
||||
DESCOPE_SECRET=
|
||||
|
||||
KEYCLOAK_ID=
|
||||
KEYCLOAK_SECRET=
|
||||
KEYCLOAK_ISSUER=
|
||||
|
||||
@@ -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/next-auth/blob/main/CONTRIBUTING.md#setting-up-local-environment)
|
||||
](https://github.com/nextauthjs/.github/blob/main/CONTRIBUTING.md#setting-up-local-environment)
|
||||
@@ -22,6 +22,9 @@ BEYOND_IDENTITY_CLIENT_ID=
|
||||
BEYOND_IDENTITY_CLIENT_SECRET=
|
||||
BEYOND_IDENTITY_ISSUER=
|
||||
|
||||
DESCOPE_ID=
|
||||
DESCOPE_SECRET=
|
||||
|
||||
GITHUB_ID=
|
||||
GITHUB_SECRET=
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
"@prisma/client": "^3",
|
||||
"@supabase/supabase-js": "^2.0.5",
|
||||
"faunadb": "^4",
|
||||
"next": "13.3.0",
|
||||
"next": "13.4.0",
|
||||
"next-auth": "workspace:*",
|
||||
"nodemailer": "^6",
|
||||
"react": "^18",
|
||||
|
||||
@@ -2,14 +2,15 @@ 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 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 Email from "@auth/core/providers/email"
|
||||
@@ -85,8 +86,8 @@ export const authConfig: AuthConfig = {
|
||||
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 }),
|
||||
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 as string }),
|
||||
// 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,
|
||||
@@ -94,19 +95,20 @@ 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, redirectProxy: process.env.AUTH_REDIRECT_PROXY_URL }),
|
||||
GitHub({ clientId: process.env.GITHUB_ID, clientSecret: process.env.GITHUB_SECRET, redirectProxyUrl: 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 }),
|
||||
@@ -115,7 +117,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 }),
|
||||
Notion({ clientId: process.env.NOTION_ID, clientSecret: process.env.NOTION_SECRET, redirectUri: process.env.NOTION_REDIRECT_URI as string }),
|
||||
// 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 }),
|
||||
@@ -160,4 +162,4 @@ function AuthHandler(...args: any[]) {
|
||||
|
||||
export default AuthHandler(authConfig)
|
||||
|
||||
export const config = { runtime: "experimental-edge" }
|
||||
export const config = { runtime: "edge" }
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
"svelte": "3.55.0",
|
||||
"svelte-check": "2.10.2",
|
||||
"typescript": "4.9.4",
|
||||
"vite": "4.0.1"
|
||||
"vite": "4.0.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@auth/core": "workspace:*",
|
||||
|
||||
@@ -6,6 +6,9 @@ AUTH0_ID=
|
||||
AUTH0_SECRET=
|
||||
AUTH0_ISSUER=
|
||||
|
||||
DESCOPE_ID=
|
||||
DESCOPE_SECRET=
|
||||
|
||||
FACEBOOK_ID=
|
||||
FACEBOOK_SECRET=
|
||||
|
||||
|
||||
2
apps/examples/nextjs/process.d.ts
vendored
2
apps/examples/nextjs/process.d.ts
vendored
@@ -12,5 +12,7 @@ declare namespace NodeJS {
|
||||
GOOGLE_SECRET: string
|
||||
AUTH0_ID: string
|
||||
AUTH0_SECRET: string
|
||||
DESCOPE_ID: string
|
||||
DESCOPE_SECRET: string
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,22 +37,31 @@ 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 repository.
|
||||
1. Clone the repo:
|
||||
|
||||
```bash
|
||||
$ git clone https://github.com/nextauthjs/docs.git
|
||||
```sh
|
||||
git clone git@github.com:nextauthjs/next-auth.git
|
||||
cd next-auth
|
||||
```
|
||||
|
||||
2. Install dependencies
|
||||
2. Set up the correct pnpm version, using [Corepack](https://nodejs.org/api/corepack.html). Run the following in the project'a root:
|
||||
|
||||
```bash
|
||||
$ npm install
|
||||
```sh
|
||||
corepack enable pnpm
|
||||
```
|
||||
|
||||
3. Start the development server
|
||||
(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
|
||||
|
||||
```bash
|
||||
$ npm start
|
||||
$ pnpm dev:docs
|
||||
```
|
||||
|
||||
And thats all! Now you should have a local copy of this docs site running at [localhost:3000](http://localhost:3000)!
|
||||
|
||||
@@ -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](http://nodemailer.com/smtp/well-known/) 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](https://community.nodemailer.com/2-0-0-beta/setup-smtp/well-known-services) 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
|
||||
|
||||
@@ -100,11 +100,12 @@ 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} />
|
||||
|
||||
@@ -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/models#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#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).
|
||||
|
||||
@@ -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`),
|
||||
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.),
|
||||
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,6 +42,9 @@ 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>
|
||||
|
||||
@@ -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; ideally for one of the [services known to work with `nodemailer`](https://community.nodemailer.com/2-0-0-beta/setup-smtp/well-known-services/).
|
||||
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).
|
||||
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.example.com:587
|
||||
EMAIL_FROM=noreply@example.com
|
||||
EMAIL_SERVER=smtp://username:password@smtp.forwardemail.net:587
|
||||
EMAIL_FROM=support@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.example.com
|
||||
EMAIL_SERVER_HOST=smtp.forwardemail.net
|
||||
EMAIL_SERVER_PORT=587
|
||||
EMAIL_FROM=noreply@example.com
|
||||
```
|
||||
@@ -112,6 +112,7 @@ 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 */
|
||||
},
|
||||
|
||||
@@ -8,6 +8,10 @@ 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" />
|
||||
@@ -21,6 +25,10 @@ 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>
|
||||
@@ -67,10 +75,8 @@ 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
|
||||
@@ -131,7 +137,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.
|
||||
|
||||
@@ -265,9 +265,11 @@ const docusaurusConfig = {
|
||||
? []
|
||||
: [
|
||||
typedocAdapter("Dgraph"),
|
||||
typedocAdapter("Drizzle"),
|
||||
typedocAdapter("DynamoDB"),
|
||||
typedocAdapter("Fauna"),
|
||||
typedocAdapter("Firebase"),
|
||||
typedocAdapter("Kysely"),
|
||||
typedocAdapter("Mikro ORM"),
|
||||
typedocAdapter("MongoDB"),
|
||||
typedocAdapter("Neo4j"),
|
||||
|
||||
@@ -55,9 +55,11 @@ 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" },
|
||||
|
||||
@@ -7,6 +7,7 @@ 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",
|
||||
|
||||
@@ -91,7 +91,7 @@ html[data-theme="dark"] .navbar__item.navbar__link[href*="npm"]:before {
|
||||
position: absolute;
|
||||
color: #000;
|
||||
top: -10px;
|
||||
right: -45px;
|
||||
right: 4px;
|
||||
font-size: 9px;
|
||||
background-color: #ccc;
|
||||
padding: 2px 5px;
|
||||
|
||||
@@ -101,13 +101,13 @@ export default function Home() {
|
||||
.fetch("https://api.github.com/repos/nextauthjs/next-auth")
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
const navLinks = document.getElementsByClassName(
|
||||
"navbar__item navbar__link"
|
||||
const githubLink = document.querySelector(
|
||||
".navbar__item.navbar__link[href*='github']"
|
||||
)
|
||||
const githubStat = document.createElement("span")
|
||||
githubStat.innerHTML = kFormatter(data.stargazers_count)
|
||||
githubStat.className = "github-counter"
|
||||
navLinks[4].appendChild(githubStat)
|
||||
githubLink.appendChild(githubStat)
|
||||
})
|
||||
}, [])
|
||||
return (
|
||||
|
||||
BIN
docs/static/img/adapters/drizzle-orm.png
vendored
Normal file
BIN
docs/static/img/adapters/drizzle-orm.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 94 KiB |
14
docs/static/img/adapters/kysely.svg
vendored
Normal file
14
docs/static/img/adapters/kysely.svg
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
<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>
|
||||
|
After Width: | Height: | Size: 637 B |
50
docs/static/img/providers/descope.svg
vendored
Normal file
50
docs/static/img/providers/descope.svg
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
<?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>
|
||||
|
After Width: | Height: | Size: 3.1 KiB |
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "https://openapi.vercel.sh/vercel.json",
|
||||
"cleanUrls": true,
|
||||
"headers": [
|
||||
{
|
||||
@@ -76,10 +77,15 @@
|
||||
"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/adapters/:path*"
|
||||
"destination": "https://authjs.dev/reference/adapter/:path*"
|
||||
},
|
||||
{
|
||||
"source": "/:path",
|
||||
|
||||
@@ -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-*",
|
||||
"test": "turbo run test --concurrency=1 --filter=[HEAD^1] --filter=./packages/* --filter=!@*upstash* --filter=!*dynamodb-* --filter=!*app*",
|
||||
"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.1",
|
||||
"turbo": "^1.10.3",
|
||||
"typescript": "4.9.4"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
|
||||
28
packages/adapter-drizzle/README.md
Normal file
28
packages/adapter-drizzle/README.md
Normal file
@@ -0,0 +1,28 @@
|
||||
<p align="center">
|
||||
<br/>
|
||||
<a href="https://authjs.dev" target="_blank">
|
||||
<img height="64px" src="https://authjs.dev/img/logo/logo-sm.png" />
|
||||
</a>
|
||||
<a href="https://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).
|
||||
65
packages/adapter-drizzle/package.json
Normal file
65
packages/adapter-drizzle/package.json
Normal file
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
277
packages/adapter-drizzle/src/index.ts
Normal file
277
packages/adapter-drizzle/src/index.ts
Normal file
@@ -0,0 +1,277 @@
|
||||
/**
|
||||
* <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.")
|
||||
}
|
||||
267
packages/adapter-drizzle/src/lib/mysql.ts
Normal file
267
packages/adapter-drizzle/src/lib/mysql.ts
Normal file
@@ -0,0 +1,267 @@
|
||||
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
|
||||
},
|
||||
}
|
||||
}
|
||||
233
packages/adapter-drizzle/src/lib/pg.ts
Normal file
233
packages/adapter-drizzle/src/lib/pg.ts
Normal file
@@ -0,0 +1,233 @@
|
||||
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 }
|
||||
},
|
||||
}
|
||||
}
|
||||
211
packages/adapter-drizzle/src/lib/sqlite.ts
Normal file
211
packages/adapter-drizzle/src/lib/sqlite.ts
Normal file
@@ -0,0 +1,211 @@
|
||||
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
|
||||
},
|
||||
}
|
||||
}
|
||||
55
packages/adapter-drizzle/src/lib/utils.ts
Normal file
55
packages/adapter-drizzle/src/lib/utils.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
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
|
||||
}
|
||||
43
packages/adapter-drizzle/tests/fixtures.ts
Normal file
43
packages/adapter-drizzle/tests/fixtures.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
// 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(),
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
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
|
||||
@@ -0,0 +1,71 @@
|
||||
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,
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,19 @@
|
||||
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 })
|
||||
22
packages/adapter-drizzle/tests/mysql-multi-project-schema/test.sh
Executable file
22
packages/adapter-drizzle/tests/mysql-multi-project-schema/test.sh
Executable file
@@ -0,0 +1,22 @@
|
||||
#!/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}
|
||||
13
packages/adapter-drizzle/tests/mysql/drizzle.config.ts
Normal file
13
packages/adapter-drizzle/tests/mysql/drizzle.config.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
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
|
||||
71
packages/adapter-drizzle/tests/mysql/index.test.ts
Normal file
71
packages/adapter-drizzle/tests/mysql/index.test.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
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,
|
||||
},
|
||||
})
|
||||
17
packages/adapter-drizzle/tests/mysql/schema.ts
Normal file
17
packages/adapter-drizzle/tests/mysql/schema.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
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 })
|
||||
22
packages/adapter-drizzle/tests/mysql/test.sh
Executable file
22
packages/adapter-drizzle/tests/mysql/test.sh
Executable file
@@ -0,0 +1,22 @@
|
||||
#!/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}
|
||||
@@ -0,0 +1,13 @@
|
||||
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
|
||||
@@ -0,0 +1,65 @@
|
||||
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),
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,10 @@
|
||||
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))
|
||||
@@ -0,0 +1,17 @@
|
||||
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,
|
||||
})
|
||||
25
packages/adapter-drizzle/tests/pg-multi-project-schema/test.sh
Executable file
25
packages/adapter-drizzle/tests/pg-multi-project-schema/test.sh
Executable file
@@ -0,0 +1,25 @@
|
||||
#!/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}
|
||||
13
packages/adapter-drizzle/tests/pg/drizzle.config.ts
Normal file
13
packages/adapter-drizzle/tests/pg/drizzle.config.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
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
|
||||
65
packages/adapter-drizzle/tests/pg/index.test.ts
Normal file
65
packages/adapter-drizzle/tests/pg/index.test.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
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),
|
||||
},
|
||||
})
|
||||
10
packages/adapter-drizzle/tests/pg/migrator.ts
Normal file
10
packages/adapter-drizzle/tests/pg/migrator.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
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))
|
||||
15
packages/adapter-drizzle/tests/pg/schema.ts
Normal file
15
packages/adapter-drizzle/tests/pg/schema.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
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,
|
||||
})
|
||||
25
packages/adapter-drizzle/tests/pg/test.sh
Executable file
25
packages/adapter-drizzle/tests/pg/test.sh
Executable file
@@ -0,0 +1,25 @@
|
||||
#!/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}
|
||||
@@ -0,0 +1,10 @@
|
||||
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
|
||||
@@ -0,0 +1,60 @@
|
||||
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,
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,14 @@
|
||||
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 })
|
||||
12
packages/adapter-drizzle/tests/sqlite-multi-project-schema/test.sh
Executable file
12
packages/adapter-drizzle/tests/sqlite-multi-project-schema/test.sh
Executable file
@@ -0,0 +1,12 @@
|
||||
#!/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
|
||||
10
packages/adapter-drizzle/tests/sqlite/drizzle.config.ts
Normal file
10
packages/adapter-drizzle/tests/sqlite/drizzle.config.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
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
|
||||
60
packages/adapter-drizzle/tests/sqlite/index.test.ts
Normal file
60
packages/adapter-drizzle/tests/sqlite/index.test.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
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,
|
||||
},
|
||||
})
|
||||
12
packages/adapter-drizzle/tests/sqlite/schema.ts
Normal file
12
packages/adapter-drizzle/tests/sqlite/schema.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
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 })
|
||||
12
packages/adapter-drizzle/tests/sqlite/test.sh
Executable file
12
packages/adapter-drizzle/tests/sqlite/test.sh
Executable file
@@ -0,0 +1,12 @@
|
||||
#!/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
|
||||
25
packages/adapter-drizzle/tsconfig.json
Normal file
25
packages/adapter-drizzle/tsconfig.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"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",
|
||||
]
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@auth/dynamodb-adapter",
|
||||
"repository": "https://github.com/nextauthjs/next-auth",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"description": "AWS DynamoDB adapter for next-auth.",
|
||||
"keywords": [
|
||||
"next-auth",
|
||||
|
||||
@@ -265,9 +265,8 @@ export function DynamoDBAdapter(
|
||||
const data = await client.update({
|
||||
TableName,
|
||||
Key: {
|
||||
// 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}`,
|
||||
[pk]: `USER#${user.id}`,
|
||||
[sk]: `USER#${user.id}`,
|
||||
},
|
||||
UpdateExpression,
|
||||
ExpressionAttributeNames,
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
|
||||
@@ -1 +0,0 @@
|
||||
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
|
||||
28
packages/adapter-kysely/README.md
Normal file
28
packages/adapter-kysely/README.md
Normal file
@@ -0,0 +1,28 @@
|
||||
<p align="center">
|
||||
<br/>
|
||||
<a href="https://authjs.dev" target="_blank">
|
||||
<img height="64px" src="https://authjs.dev/img/logo/logo-sm.png" />
|
||||
</a>
|
||||
<a href="https://kysely.io" target="_blank">
|
||||
<img height="64px" src="https://authjs.dev/img/adapters/kysely.svg"/>
|
||||
</a>
|
||||
<h3 align="center"><b>Kysely Adapter</b> - NextAuth.js / Auth.js</a></h3>
|
||||
<p align="center" style="align: center;">
|
||||
<a href="https://npm.im/@auth/kysely-adapter">
|
||||
<img src="https://img.shields.io/badge/TypeScript-blue?style=flat-square" alt="TypeScript" />
|
||||
</a>
|
||||
<a href="https://npm.im/@auth/kysely-adapter">
|
||||
<img alt="npm" src="https://img.shields.io/npm/v/@auth/kysely-adapter?color=green&label=@auth/kysely-adapter&style=flat-square">
|
||||
</a>
|
||||
<a href="https://www.npmtrends.com/@auth/kysely-adapter">
|
||||
<img src="https://img.shields.io/npm/dm/@auth/kysely-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/kysely).
|
||||
56
packages/adapter-kysely/package.json
Normal file
56
packages/adapter-kysely/package.json
Normal file
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"name": "@auth/kysely-adapter",
|
||||
"version": "0.1.1",
|
||||
"description": "Kysely adapter for Auth.js",
|
||||
"homepage": "https://authjs.dev/reference/adapter/kysely",
|
||||
"repository": "https://github.com/nextauthjs/next-auth",
|
||||
"bugs": {
|
||||
"url": "https://github.com/nextauthjs/next-auth/issues"
|
||||
},
|
||||
"author": "mwojtul <mark.wojtul@gmail.com> (https://github.com/mwojtul)",
|
||||
"license": "ISC",
|
||||
"keywords": [
|
||||
"authjs",
|
||||
"next-auth",
|
||||
"next.js",
|
||||
"oauth",
|
||||
"kysely"
|
||||
],
|
||||
"type": "module",
|
||||
"types": "./index.d.ts",
|
||||
"files": [
|
||||
"*.js",
|
||||
"*.d.ts*",
|
||||
"src"
|
||||
],
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./index.d.ts",
|
||||
"import": "./index.js"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"test": "./tests/test.sh"
|
||||
},
|
||||
"dependencies": {
|
||||
"@auth/core": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"kysely": "^0.26.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@next-auth/adapter-test": "workspace:*",
|
||||
"@next-auth/tsconfig": "workspace:*",
|
||||
"@types/better-sqlite3": "^7.6.3",
|
||||
"@types/pg": "^8.6.5",
|
||||
"better-sqlite3": "^8.2.0",
|
||||
"jest": "^27.4.3",
|
||||
"kysely": "^0.24.2",
|
||||
"mysql2": "^3.2.0",
|
||||
"pg": "^8.10.0"
|
||||
},
|
||||
"jest": {
|
||||
"preset": "@next-auth/adapter-test/jest"
|
||||
}
|
||||
}
|
||||
472
packages/adapter-kysely/src/index.ts
Normal file
472
packages/adapter-kysely/src/index.ts
Normal file
@@ -0,0 +1,472 @@
|
||||
/**
|
||||
* <div style={{display: "flex", justifyContent: "space-between", alignItems: "center", padding: 16}}>
|
||||
* <p style={{fontWeight: "normal"}}>Official <a href="https://kysely.dev/">Kysely</a> adapter for Auth.js / NextAuth.js.</p>
|
||||
* <a href="https://kysely.dev/">
|
||||
* <img style={{display: "block"}} src="/img/adapters/kysely.svg" width="38" />
|
||||
* </a>
|
||||
* </div>
|
||||
*
|
||||
* ## Installation
|
||||
*
|
||||
* ```bash npm2yarn2pnpm
|
||||
* npm install kysely @auth/kysely-adapter
|
||||
* ```
|
||||
*
|
||||
* @module @auth/kysely-adapter
|
||||
*/
|
||||
|
||||
import { Kysely, SqliteAdapter } from "kysely"
|
||||
|
||||
import type { Adapter } from "@auth/core/adapters"
|
||||
import type { GeneratedAlways } from "kysely"
|
||||
|
||||
export interface Database {
|
||||
User: {
|
||||
id: GeneratedAlways<string>
|
||||
name: string | null
|
||||
email: string
|
||||
emailVerified: Date | string | null
|
||||
image: string | null
|
||||
}
|
||||
Account: {
|
||||
id: GeneratedAlways<string>
|
||||
userId: string
|
||||
type: string
|
||||
provider: string
|
||||
providerAccountId: string
|
||||
refresh_token: string | null
|
||||
access_token: string | null
|
||||
expires_at: number | null
|
||||
token_type: string | null
|
||||
scope: string | null
|
||||
id_token: string | null
|
||||
session_state: string | null
|
||||
}
|
||||
Session: {
|
||||
id: GeneratedAlways<string>
|
||||
userId: string
|
||||
sessionToken: string
|
||||
expires: Date | string
|
||||
}
|
||||
VerificationToken: {
|
||||
identifier: string
|
||||
token: string
|
||||
expires: Date | string
|
||||
}
|
||||
}
|
||||
|
||||
export const format = {
|
||||
/**
|
||||
* Helper function to return the passed in object and its specified prop
|
||||
* as an ISO string if SQLite is being used.
|
||||
*/
|
||||
from<T extends Partial<Record<K, Date | null>>, K extends keyof T>(
|
||||
data: T,
|
||||
key: K,
|
||||
isSqlite: boolean
|
||||
) {
|
||||
const value = data[key]
|
||||
return {
|
||||
...data,
|
||||
[key]: value && isSqlite ? value.toISOString() : value,
|
||||
}
|
||||
},
|
||||
to,
|
||||
}
|
||||
|
||||
type ReturnData<T = never> = Record<string, Date | string | T>
|
||||
|
||||
/**
|
||||
* Helper function to return the passed in object and its specified prop as a date.
|
||||
* Necessary because SQLite has no date type so we store dates as ISO strings.
|
||||
*/
|
||||
function to<T extends Partial<ReturnData>, K extends keyof T>(
|
||||
data: T,
|
||||
key: K
|
||||
): Omit<T, K> & Record<K, Date>
|
||||
function to<T extends Partial<ReturnData<null>>, K extends keyof T>(
|
||||
data: T,
|
||||
key: K
|
||||
): Omit<T, K> & Record<K, Date | null>
|
||||
function to<T extends Partial<ReturnData<null>>, K extends keyof T>(
|
||||
data: T,
|
||||
key: K
|
||||
) {
|
||||
const value = data[key]
|
||||
return Object.assign(data, {
|
||||
[key]: value && typeof value === "string" ? new Date(value) : value,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* ## Setup
|
||||
*
|
||||
* This adapter supports the same first party dialects that Kysely (as of v0.24.2) supports: PostgreSQL, MySQL, and SQLite. The examples below use PostgreSQL with the [pg](https://www.npmjs.com/package/pg) client.
|
||||
*
|
||||
* ```bash npm2yarn2pnpm
|
||||
* npm install pg
|
||||
* npm install --save-dev @types/pg
|
||||
* ```
|
||||
*
|
||||
* ```typescript title="pages/api/auth/[...nextauth].ts"
|
||||
* import NextAuth from "next-auth"
|
||||
* import GoogleProvider from "next-auth/providers/google"
|
||||
* import { KyselyAdapter } from "@auth/kysely-adapter"
|
||||
* import { db } from "../../../db"
|
||||
*
|
||||
* export default NextAuth({
|
||||
* adapter: KyselyAdapter(db),
|
||||
* providers: [
|
||||
* GoogleProvider({
|
||||
* clientId: process.env.GOOGLE_CLIENT_ID,
|
||||
* clientSecret: process.env.GOOGLE_CLIENT_SECRET,
|
||||
* }),
|
||||
* ],
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
* Kysely's constructor requires a database interface that contains an entry with an interface for each of your tables. You can define these types manually, or use `kysely-codegen` / `prisma-kysely` to automatically generate them. Check out the default [models](/reference/adapters#models) required by Auth.js.
|
||||
*
|
||||
* ```ts title="db.ts"
|
||||
* import { PostgresDialect } from "kysely"
|
||||
* import { Pool } from "pg"
|
||||
*
|
||||
* // This adapter exports a wrapper of the original `Kysely` class called `KyselyAuth`,
|
||||
* // that can be used to provide additional type-safety.
|
||||
* // While using it isn't required, it is recommended as it will verify
|
||||
* // that the database interface has all the fields that Auth.js expects.
|
||||
* import { KyselyAuth } from "@auth/kysely-adapter"
|
||||
*
|
||||
* import type { GeneratedAlways } from "kysely"
|
||||
*
|
||||
* interface Database {
|
||||
* User: {
|
||||
* id: GeneratedAlways<string>
|
||||
* name: string | null
|
||||
* email: string
|
||||
* emailVerified: Date | null
|
||||
* image: string | null
|
||||
* }
|
||||
* Account: {
|
||||
* id: GeneratedAlways<string>
|
||||
* userId: string
|
||||
* type: string
|
||||
* provider: string
|
||||
* providerAccountId: string
|
||||
* refresh_token: string | null
|
||||
* access_token: string | null
|
||||
* expires_at: number | null
|
||||
* token_type: string | null
|
||||
* scope: string | null
|
||||
* id_token: string | null
|
||||
* session_state: string | null
|
||||
* }
|
||||
* Session: {
|
||||
* id: GeneratedAlways<string>
|
||||
* userId: string
|
||||
* sessionToken: string
|
||||
* expires: Date
|
||||
* }
|
||||
* VerificationToken: {
|
||||
* identifier: string
|
||||
* token: string
|
||||
* expires: Date
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* export const db = new KyselyAuth<Database>({
|
||||
* dialect: new PostgresDialect({
|
||||
* pool: new Pool({
|
||||
* host: process.env.DATABASE_HOST,
|
||||
* database: process.env.DATABASE_NAME,
|
||||
* user: process.env.DATABASE_USER,
|
||||
* password: process.env.DATABASE_PASSWORD,
|
||||
* }),
|
||||
* }),
|
||||
* })
|
||||
```
|
||||
*
|
||||
*
|
||||
* :::note
|
||||
* An alternative to manually defining types is generating them from the database schema using [kysely-codegen](https://github.com/RobinBlomberg/kysely-codegen), or from Prisma schemas using [prisma-kysely](https://github.com/valtyr/prisma-kysely). When using generated types with `KyselyAuth`, import `Codegen` and pass it as the second generic arg:
|
||||
* ```ts
|
||||
* import type { Codegen } from "@auth/kysely-adapter"
|
||||
* new KyselyAuth<Database, Codegen>(...)
|
||||
* ```
|
||||
* :::
|
||||
* ### Schema
|
||||
* ```ts title="db/migrations/001_create_db.ts"
|
||||
* import { Kysely, sql } from "kysely"
|
||||
*
|
||||
* export async function up(db: Kysely<any>): Promise<void> {
|
||||
* await db.schema
|
||||
* .createTable("User")
|
||||
* .addColumn("id", "uuid", (col) =>
|
||||
* col.primaryKey().defaultTo(sql`gen_random_uuid()`)
|
||||
* )
|
||||
* .addColumn("name", "text")
|
||||
* .addColumn("email", "text", (col) => col.unique().notNull())
|
||||
* .addColumn("emailVerified", "timestamptz")
|
||||
* .addColumn("image", "text")
|
||||
* .execute()
|
||||
*
|
||||
* await db.schema
|
||||
* .createTable("Account")
|
||||
* .addColumn("id", "uuid", (col) =>
|
||||
* col.primaryKey().defaultTo(sql`gen_random_uuid()`)
|
||||
* )
|
||||
* .addColumn("userId", "uuid", (col) =>
|
||||
* col.references("User.id").onDelete("cascade").notNull()
|
||||
* )
|
||||
* .addColumn("type", "text", (col) => col.notNull())
|
||||
* .addColumn("provider", "text", (col) => col.notNull())
|
||||
* .addColumn("providerAccountId", "text", (col) => col.notNull())
|
||||
* .addColumn("refresh_token", "text")
|
||||
* .addColumn("access_token", "text")
|
||||
* .addColumn("expires_at", "bigint")
|
||||
* .addColumn("token_type", "text")
|
||||
* .addColumn("scope", "text")
|
||||
* .addColumn("id_token", "text")
|
||||
* .addColumn("session_state", "text")
|
||||
* .execute()
|
||||
*
|
||||
* await db.schema
|
||||
* .createTable("Session")
|
||||
* .addColumn("id", "uuid", (col) =>
|
||||
* col.primaryKey().defaultTo(sql`gen_random_uuid()`)
|
||||
* )
|
||||
* .addColumn("userId", "uuid", (col) =>
|
||||
* col.references("User.id").onDelete("cascade").notNull()
|
||||
* )
|
||||
* .addColumn("sessionToken", "text", (col) => col.notNull().unique())
|
||||
* .addColumn("expires", "timestamptz", (col) => col.notNull())
|
||||
* .execute()
|
||||
*
|
||||
* await db.schema
|
||||
* .createTable("VerificationToken")
|
||||
* .addColumn("identifier", "text", (col) => col.notNull())
|
||||
* .addColumn("token", "text", (col) => col.notNull().unique())
|
||||
* .addColumn("expires", "timestamptz", (col) => col.notNull())
|
||||
* .execute()
|
||||
*
|
||||
* await db.schema
|
||||
* .createIndex("Account_userId_index")
|
||||
* .on("Account")
|
||||
* .column("userId")
|
||||
* .execute()
|
||||
*
|
||||
* await db.schema
|
||||
* .createIndex("Session_userId_index")
|
||||
* .on("Session")
|
||||
* .column("userId")
|
||||
* .execute()
|
||||
* }
|
||||
*
|
||||
* export async function down(db: Kysely<any>): Promise<void> {
|
||||
* await db.schema.dropTable("Account").ifExists().execute()
|
||||
* await db.schema.dropTable("Session").ifExists().execute()
|
||||
* await db.schema.dropTable("User").ifExists().execute()
|
||||
* await db.schema.dropTable("VerificationToken").ifExists().execute()
|
||||
* }
|
||||
* ```
|
||||
* > This schema is adapted for use in Kysely and is based upon our main [schema](/reference/adapters#models).
|
||||
*
|
||||
* For more information about creating and running migrations with Kysely, refer to the [Kysely migrations documentation](https://kysely.dev/docs/migrations).
|
||||
*
|
||||
* ### Naming conventions
|
||||
* If mixed snake_case and camelCase column names is an issue for you and/or your underlying database system, we recommend using Kysely's `CamelCasePlugin` ([see the documentation here](https://kysely-org.github.io/kysely/classes/CamelCasePlugin.html)) feature to change the field names. This won't affect NextAuth.js, but will allow you to have consistent casing when using Kysely.
|
||||
*/
|
||||
export function KyselyAdapter(db: Kysely<Database>): Adapter {
|
||||
const { adapter } = db.getExecutor()
|
||||
const supportsReturning = adapter.supportsReturning
|
||||
const isSqlite = adapter instanceof SqliteAdapter
|
||||
|
||||
return {
|
||||
async createUser(data) {
|
||||
const userData = format.from(data, "emailVerified", isSqlite)
|
||||
const query = db.insertInto("User").values(userData)
|
||||
const result = supportsReturning
|
||||
? await query.returningAll().executeTakeFirstOrThrow()
|
||||
: await query.executeTakeFirstOrThrow().then(async () => {
|
||||
return await db
|
||||
.selectFrom("User")
|
||||
.selectAll()
|
||||
.where("email", "=", `${userData.email}`)
|
||||
.executeTakeFirstOrThrow()
|
||||
})
|
||||
return to(result, "emailVerified")
|
||||
},
|
||||
async getUser(id) {
|
||||
const result =
|
||||
(await db
|
||||
.selectFrom("User")
|
||||
.selectAll()
|
||||
.where("id", "=", id)
|
||||
.executeTakeFirst()) ?? null
|
||||
if (!result) return null
|
||||
return to(result, "emailVerified")
|
||||
},
|
||||
async getUserByEmail(email) {
|
||||
const result =
|
||||
(await db
|
||||
.selectFrom("User")
|
||||
.selectAll()
|
||||
.where("email", "=", email)
|
||||
.executeTakeFirst()) ?? null
|
||||
if (!result) return null
|
||||
return to(result, "emailVerified")
|
||||
},
|
||||
async getUserByAccount({ providerAccountId, provider }) {
|
||||
const result =
|
||||
(await db
|
||||
.selectFrom("User")
|
||||
.innerJoin("Account", "User.id", "Account.userId")
|
||||
.selectAll("User")
|
||||
.where("Account.providerAccountId", "=", providerAccountId)
|
||||
.where("Account.provider", "=", provider)
|
||||
.executeTakeFirst()) ?? null
|
||||
if (!result) return null
|
||||
return to(result, "emailVerified")
|
||||
},
|
||||
async updateUser({ id, ...user }) {
|
||||
if (!id) throw new Error("User not found")
|
||||
const userData = format.from(user, "emailVerified", isSqlite)
|
||||
const query = db.updateTable("User").set(userData).where("id", "=", id)
|
||||
const result = supportsReturning
|
||||
? await query.returningAll().executeTakeFirstOrThrow()
|
||||
: await query.executeTakeFirstOrThrow().then(async () => {
|
||||
return await db
|
||||
.selectFrom("User")
|
||||
.selectAll()
|
||||
.where("id", "=", id)
|
||||
.executeTakeFirstOrThrow()
|
||||
})
|
||||
return to(result, "emailVerified")
|
||||
},
|
||||
async deleteUser(userId) {
|
||||
await db.deleteFrom("User").where("User.id", "=", userId).execute()
|
||||
},
|
||||
async linkAccount(account) {
|
||||
await db.insertInto("Account").values(account).executeTakeFirstOrThrow()
|
||||
},
|
||||
async unlinkAccount({ providerAccountId, provider }) {
|
||||
await db
|
||||
.deleteFrom("Account")
|
||||
.where("Account.providerAccountId", "=", providerAccountId)
|
||||
.where("Account.provider", "=", provider)
|
||||
.executeTakeFirstOrThrow()
|
||||
},
|
||||
async createSession(data) {
|
||||
const sessionData = format.from(data, "expires", isSqlite)
|
||||
const query = db.insertInto("Session").values(sessionData)
|
||||
const result = supportsReturning
|
||||
? await query.returningAll().executeTakeFirstOrThrow()
|
||||
: await (async () => {
|
||||
await query.executeTakeFirstOrThrow()
|
||||
return await db
|
||||
.selectFrom("Session")
|
||||
.selectAll()
|
||||
.where("sessionToken", "=", sessionData.sessionToken)
|
||||
.executeTakeFirstOrThrow()
|
||||
})()
|
||||
return to(result, "expires")
|
||||
},
|
||||
async getSessionAndUser(sessionTokenArg) {
|
||||
const result = await db
|
||||
.selectFrom("Session")
|
||||
.innerJoin("User", "User.id", "Session.userId")
|
||||
.selectAll("User")
|
||||
.select([
|
||||
"Session.id as sessionId",
|
||||
"Session.userId",
|
||||
"Session.sessionToken",
|
||||
"Session.expires",
|
||||
])
|
||||
.where("Session.sessionToken", "=", sessionTokenArg)
|
||||
.executeTakeFirst()
|
||||
if (!result) return null
|
||||
const { sessionId: id, userId, sessionToken, expires, ...user } = result
|
||||
return {
|
||||
user: to({ ...user }, "emailVerified"),
|
||||
session: to({ id, userId, sessionToken, expires }, "expires"),
|
||||
}
|
||||
},
|
||||
async updateSession(session) {
|
||||
const sessionData = format.from(session, "expires", isSqlite)
|
||||
const query = db
|
||||
.updateTable("Session")
|
||||
.set(sessionData)
|
||||
.where("Session.sessionToken", "=", session.sessionToken)
|
||||
const result = supportsReturning
|
||||
? await query.returningAll().executeTakeFirstOrThrow()
|
||||
: await query.executeTakeFirstOrThrow().then(async () => {
|
||||
return await db
|
||||
.selectFrom("Session")
|
||||
.selectAll()
|
||||
.where("Session.sessionToken", "=", sessionData.sessionToken)
|
||||
.executeTakeFirstOrThrow()
|
||||
})
|
||||
return to(result, "expires")
|
||||
},
|
||||
async deleteSession(sessionToken) {
|
||||
await db
|
||||
.deleteFrom("Session")
|
||||
.where("Session.sessionToken", "=", sessionToken)
|
||||
.executeTakeFirstOrThrow()
|
||||
},
|
||||
async createVerificationToken(verificationToken) {
|
||||
const verificationTokenData = format.from(
|
||||
verificationToken,
|
||||
"expires",
|
||||
isSqlite
|
||||
)
|
||||
const query = db
|
||||
.insertInto("VerificationToken")
|
||||
.values(verificationTokenData)
|
||||
const result = supportsReturning
|
||||
? await query.returningAll().executeTakeFirstOrThrow()
|
||||
: await query.executeTakeFirstOrThrow().then(async () => {
|
||||
return await db
|
||||
.selectFrom("VerificationToken")
|
||||
.selectAll()
|
||||
.where("token", "=", verificationTokenData.token)
|
||||
.executeTakeFirstOrThrow()
|
||||
})
|
||||
return to(result, "expires")
|
||||
},
|
||||
async useVerificationToken({ identifier, token }) {
|
||||
const query = db
|
||||
.deleteFrom("VerificationToken")
|
||||
.where("VerificationToken.token", "=", token)
|
||||
.where("VerificationToken.identifier", "=", identifier)
|
||||
const result = supportsReturning
|
||||
? (await query.returningAll().executeTakeFirst()) ?? null
|
||||
: await db
|
||||
.selectFrom("VerificationToken")
|
||||
.selectAll()
|
||||
.where("token", "=", token)
|
||||
.executeTakeFirst()
|
||||
.then(async (res) => {
|
||||
await query.executeTakeFirst()
|
||||
return res
|
||||
})
|
||||
if (!result) return null
|
||||
return to(result, "expires")
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper over the original `Kysely` class in order to validate the passed in
|
||||
* database interface. A regular Kysely instance may also be used, but wrapping
|
||||
* it ensures the database interface implements the fields that Auth.js
|
||||
* requires. When used with `kysely-codegen`, the `Codegen` type can be passed as
|
||||
* the second generic argument. The generated types will be used, and
|
||||
* `KyselyAuth` will only verify that the correct fields exist.
|
||||
**/
|
||||
export class KyselyAuth<DB extends T, T = Database> extends Kysely<DB> {}
|
||||
|
||||
export type Codegen = {
|
||||
[K in keyof Database]: { [J in keyof Database[K]]: unknown }
|
||||
}
|
||||
250
packages/adapter-kysely/tests/index.test.ts
Normal file
250
packages/adapter-kysely/tests/index.test.ts
Normal file
@@ -0,0 +1,250 @@
|
||||
import { runBasicTests } from "@next-auth/adapter-test"
|
||||
import { Pool } from "pg"
|
||||
import {
|
||||
Kysely,
|
||||
MysqlDialect,
|
||||
PostgresDialect,
|
||||
SchemaModule,
|
||||
sql,
|
||||
SqliteAdapter,
|
||||
SqliteDialect,
|
||||
} from "kysely"
|
||||
import { KyselyAdapter, KyselyAuth } from "../src"
|
||||
import { createPool } from "mysql2"
|
||||
import SqliteDatabase from "better-sqlite3"
|
||||
import type { Database } from "../src"
|
||||
import { DataTypeExpression } from "kysely/dist/cjs/parser/data-type-parser"
|
||||
|
||||
type BuiltInDialect = "postgres" | "mysql" | "sqlite"
|
||||
|
||||
const POOL_SIZE = 20
|
||||
const DIALECT_CONFIGS = {
|
||||
postgres: {
|
||||
host: "localhost",
|
||||
database: "kysely_test",
|
||||
user: "kysely",
|
||||
port: 5434,
|
||||
max: POOL_SIZE,
|
||||
},
|
||||
mysql: {
|
||||
database: "kysely_test",
|
||||
host: "localhost",
|
||||
user: "kysely",
|
||||
password: "kysely",
|
||||
port: 3308,
|
||||
supportBigNumbers: true,
|
||||
bigNumberStrings: true,
|
||||
connectionLimit: POOL_SIZE,
|
||||
},
|
||||
sqlite: {
|
||||
databasePath: ":memory:",
|
||||
},
|
||||
} as const
|
||||
|
||||
async function dropDatabase(db: Kysely<Database>): Promise<void> {
|
||||
await Promise.all([
|
||||
db.schema.dropTable("Account").ifExists().execute(),
|
||||
db.schema.dropTable("Session").ifExists().execute(),
|
||||
db.schema.dropTable("User").ifExists().execute(),
|
||||
db.schema.dropTable("VerificationToken").ifExists().execute(),
|
||||
])
|
||||
}
|
||||
|
||||
export function createTableWithId(
|
||||
schema: SchemaModule,
|
||||
dialect: BuiltInDialect,
|
||||
tableName: string
|
||||
) {
|
||||
const builder = schema.createTable(tableName)
|
||||
|
||||
if (dialect === "postgres") {
|
||||
return builder.addColumn("id", "uuid", (col) =>
|
||||
col.primaryKey().defaultTo(sql`gen_random_uuid()`)
|
||||
)
|
||||
} else if (dialect === "mysql") {
|
||||
return builder.addColumn("id", "varchar(36)", (col) =>
|
||||
col.primaryKey().defaultTo(sql`(UUID())`)
|
||||
)
|
||||
} else {
|
||||
return builder.addColumn("id", "integer", (col) =>
|
||||
col.autoIncrement().primaryKey()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
async function createDatabase(
|
||||
db: Kysely<Database>,
|
||||
dialect: BuiltInDialect
|
||||
): Promise<void> {
|
||||
const defaultTimestamp = {
|
||||
postgres: sql`NOW()`,
|
||||
mysql: sql`NOW(3)`,
|
||||
sqlite: sql`CURRENT_TIMESTAMP`,
|
||||
}[dialect]
|
||||
const uuidColumnType: DataTypeExpression =
|
||||
dialect === "mysql" ? "varchar(36)" : "uuid"
|
||||
const dateColumnType: DataTypeExpression =
|
||||
dialect === "mysql" ? sql`DATETIME(3)` : "timestamptz"
|
||||
const textColumnType: DataTypeExpression =
|
||||
dialect === "mysql" ? "varchar(255)" : "text"
|
||||
|
||||
await dropDatabase(db)
|
||||
|
||||
await createTableWithId(db.schema, dialect, "User")
|
||||
.addColumn("name", textColumnType)
|
||||
.addColumn("email", textColumnType, (col) => col.unique().notNull())
|
||||
.addColumn("emailVerified", dateColumnType, (col) =>
|
||||
col.defaultTo(defaultTimestamp)
|
||||
)
|
||||
.addColumn("image", textColumnType)
|
||||
.execute()
|
||||
|
||||
let createAccountTable = createTableWithId(db.schema, dialect, "Account")
|
||||
.addColumn("userId", uuidColumnType, (col) =>
|
||||
col.references("User.id").onDelete("cascade").notNull()
|
||||
)
|
||||
.addColumn("type", textColumnType, (col) => col.notNull())
|
||||
.addColumn("provider", textColumnType, (col) => col.notNull())
|
||||
.addColumn("providerAccountId", textColumnType, (col) => col.notNull())
|
||||
.addColumn("refresh_token", textColumnType)
|
||||
.addColumn("access_token", textColumnType)
|
||||
.addColumn("expires_at", "bigint")
|
||||
.addColumn("token_type", textColumnType)
|
||||
.addColumn("scope", textColumnType)
|
||||
.addColumn("id_token", textColumnType)
|
||||
.addColumn("session_state", textColumnType)
|
||||
if (dialect === "mysql")
|
||||
createAccountTable = createAccountTable.addForeignKeyConstraint(
|
||||
"Account_userId_fk",
|
||||
["userId"],
|
||||
"User",
|
||||
["id"],
|
||||
(cb) => cb.onDelete("cascade")
|
||||
)
|
||||
await createAccountTable.execute()
|
||||
|
||||
let createSessionTable = createTableWithId(db.schema, dialect, "Session")
|
||||
.addColumn("userId", uuidColumnType, (col) =>
|
||||
col.references("User.id").onDelete("cascade").notNull()
|
||||
)
|
||||
.addColumn("sessionToken", textColumnType, (col) => col.notNull().unique())
|
||||
.addColumn("expires", dateColumnType, (col) => col.notNull())
|
||||
|
||||
if (dialect === "mysql")
|
||||
createSessionTable = createSessionTable.addForeignKeyConstraint(
|
||||
"Session_userId_fk",
|
||||
["userId"],
|
||||
"User",
|
||||
["id"],
|
||||
(cb) => cb.onDelete("cascade")
|
||||
)
|
||||
await createSessionTable.execute()
|
||||
|
||||
await db.schema
|
||||
.createTable("VerificationToken")
|
||||
.addColumn("identifier", textColumnType, (col) => col.notNull())
|
||||
.addColumn("token", textColumnType, (col) => col.notNull().unique())
|
||||
.addColumn("expires", dateColumnType, (col) => col.notNull())
|
||||
.execute()
|
||||
|
||||
await db.schema
|
||||
.createIndex("Account_userId_index")
|
||||
.on("Account")
|
||||
.column("userId")
|
||||
.execute()
|
||||
}
|
||||
|
||||
const runDialectBasicTests = (
|
||||
db: Kysely<Database>,
|
||||
dialect: BuiltInDialect
|
||||
) => {
|
||||
const datesStoredAsISOStrings =
|
||||
db.getExecutor().adapter instanceof SqliteAdapter
|
||||
|
||||
runBasicTests({
|
||||
adapter: KyselyAdapter(db),
|
||||
db: {
|
||||
async connect() {
|
||||
await dropDatabase(db)
|
||||
await createDatabase(db, dialect)
|
||||
},
|
||||
async disconnect() {
|
||||
await db.destroy()
|
||||
},
|
||||
async user(userId) {
|
||||
const user =
|
||||
(await db
|
||||
.selectFrom("User")
|
||||
.selectAll()
|
||||
.where("id", "=", userId)
|
||||
.executeTakeFirst()) ?? null
|
||||
if (datesStoredAsISOStrings && user?.emailVerified)
|
||||
user.emailVerified = new Date(user.emailVerified)
|
||||
return user
|
||||
},
|
||||
async account({ provider, providerAccountId }) {
|
||||
const result = await db
|
||||
.selectFrom("Account")
|
||||
.selectAll()
|
||||
.where("provider", "=", provider)
|
||||
.where("providerAccountId", "=", providerAccountId)
|
||||
.executeTakeFirst()
|
||||
if (!result) return null
|
||||
const { ...account } = result
|
||||
if (typeof account.expires_at === "string")
|
||||
account.expires_at = Number(account.expires_at)
|
||||
return account
|
||||
},
|
||||
async session(sessionToken) {
|
||||
const session =
|
||||
(await db
|
||||
.selectFrom("Session")
|
||||
.selectAll()
|
||||
.where("sessionToken", "=", sessionToken)
|
||||
.executeTakeFirst()) ?? null
|
||||
if (datesStoredAsISOStrings && session?.expires)
|
||||
session.expires = new Date(session.expires)
|
||||
return session
|
||||
},
|
||||
async verificationToken({ identifier, token }) {
|
||||
const verificationToken = await db
|
||||
.selectFrom("VerificationToken")
|
||||
.selectAll()
|
||||
.where("identifier", "=", identifier)
|
||||
.where("token", "=", token)
|
||||
.executeTakeFirstOrThrow()
|
||||
if (datesStoredAsISOStrings)
|
||||
verificationToken.expires = new Date(verificationToken.expires)
|
||||
return verificationToken
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
describe("Testing PostgresDialect", () => {
|
||||
const db = new KyselyAuth<Database>({
|
||||
dialect: new PostgresDialect({
|
||||
pool: new Pool(DIALECT_CONFIGS.postgres),
|
||||
}),
|
||||
})
|
||||
runDialectBasicTests(db, "postgres")
|
||||
})
|
||||
|
||||
describe("Testing MysqlDialect", () => {
|
||||
const db = new KyselyAuth<Database>({
|
||||
dialect: new MysqlDialect({
|
||||
pool: createPool(DIALECT_CONFIGS.mysql),
|
||||
}),
|
||||
})
|
||||
runDialectBasicTests(db, "mysql")
|
||||
})
|
||||
|
||||
describe("Testing SqliteDialect", () => {
|
||||
const db = new KyselyAuth<Database>({
|
||||
dialect: new SqliteDialect({
|
||||
database: async () =>
|
||||
new SqliteDatabase(DIALECT_CONFIGS.sqlite.databasePath),
|
||||
}),
|
||||
})
|
||||
runDialectBasicTests(db, "sqlite")
|
||||
})
|
||||
3
packages/adapter-kysely/tests/scripts/mysql-init.sql
Normal file
3
packages/adapter-kysely/tests/scripts/mysql-init.sql
Normal file
@@ -0,0 +1,3 @@
|
||||
CREATE USER 'kysely'@'%' IDENTIFIED WITH mysql_native_password BY 'kysely';
|
||||
GRANT ALL ON *.* TO 'kysely'@'%';
|
||||
CREATE DATABASE kysely_test;
|
||||
30
packages/adapter-kysely/tests/test.sh
Executable file
30
packages/adapter-kysely/tests/test.sh
Executable file
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
docker run -d \
|
||||
--name mysql \
|
||||
--rm \
|
||||
-e MYSQL_ROOT_PASSWORD=root \
|
||||
-e MYSQL_DATABASE=kysely_test \
|
||||
-p 3308:3306 \
|
||||
-v "$(pwd)"/tests/scripts/mysql-init.sql:/data/application/init.sql \
|
||||
mysql/mysql-server \
|
||||
--init-file /data/application/init.sql
|
||||
|
||||
docker run -d \
|
||||
--name postgres \
|
||||
--rm \
|
||||
-e POSTGRES_DB=kysely_test \
|
||||
-e POSTGRES_USER=kysely \
|
||||
-e POSTGRES_HOST_AUTH_METHOD=trust \
|
||||
-p 5434:5432 \
|
||||
postgres
|
||||
|
||||
echo "waiting 15 seconds for databases to start..."
|
||||
sleep 15
|
||||
|
||||
# Always stop container, but exit with 1 when tests are failing
|
||||
if npx jest tests; then
|
||||
docker stop mysql && docker stop postgres
|
||||
else
|
||||
docker stop mysql && docker stop postgres && exit 1
|
||||
fi
|
||||
25
packages/adapter-kysely/tsconfig.json
Normal file
25
packages/adapter-kysely/tsconfig.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"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",
|
||||
]
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@auth/mikro-orm-adapter",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"description": "MikroORM adapter for Auth.js",
|
||||
"homepage": "https://authjs.dev",
|
||||
"repository": "https://github.com/nextauthjs/next-auth",
|
||||
|
||||
@@ -24,7 +24,7 @@ import type { Adapter } from "@auth/core/adapters"
|
||||
|
||||
import { MikroORM, wrap } from "@mikro-orm/core"
|
||||
|
||||
import * as defaultEntities from "./lib/entities"
|
||||
import * as defaultEntities from "./lib/entities.js"
|
||||
|
||||
export { defaultEntities }
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
|
||||
@@ -1 +0,0 @@
|
||||
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
|
||||
@@ -1 +0,0 @@
|
||||
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
|
||||
@@ -1 +0,0 @@
|
||||
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@auth/prisma-adapter",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"description": "Prisma adapter for Auth.js",
|
||||
"homepage": "https://authjs.dev/reference/adapter/prisma",
|
||||
"repository": "https://github.com/nextauthjs/next-auth",
|
||||
|
||||
@@ -21,7 +21,7 @@ import type { Adapter, AdapterAccount } from "@auth/core/adapters"
|
||||
/**
|
||||
* ## Setup
|
||||
*
|
||||
* Add this adapter to your `pages/api/[...nextauth].js` next-auth configuration object:
|
||||
* Add this adapter to your `pages/api/auth/[...nextauth].js` next-auth configuration object:
|
||||
*
|
||||
* ```js title="pages/api/auth/[...nextauth].js"
|
||||
* import NextAuth from "next-auth"
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
|
||||
@@ -1 +0,0 @@
|
||||
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
|
||||
@@ -15,6 +15,13 @@ const requiredMethods = [
|
||||
]
|
||||
export interface TestOptions {
|
||||
adapter: Adapter
|
||||
fixtures?: {
|
||||
user?: any
|
||||
session?: any
|
||||
account?: any
|
||||
sessionUpdateExpires?: Date
|
||||
verificationTokenExpires?: Date
|
||||
},
|
||||
db: {
|
||||
/** Generates UUID v4 by default. Use it to override how the test suite should generate IDs, like user id. */
|
||||
id?: () => string
|
||||
@@ -67,11 +74,11 @@ export async function runBasicTests(options: TestOptions) {
|
||||
await options.db.disconnect?.()
|
||||
})
|
||||
|
||||
let user: any = {
|
||||
let user: any = options.fixtures?.user ?? {
|
||||
email: "fill@murray.com",
|
||||
image: "https://www.fillmurray.com/460/300",
|
||||
name: "Fill Murray",
|
||||
emailVerified: new Date(),
|
||||
emailVerified: new Date()
|
||||
}
|
||||
|
||||
if (process.env.CUSTOM_MODEL === "1") {
|
||||
@@ -79,12 +86,12 @@ export async function runBasicTests(options: TestOptions) {
|
||||
user.phone = "00000000000"
|
||||
}
|
||||
|
||||
const session: any = {
|
||||
const session: any = options.fixtures?.session ?? {
|
||||
sessionToken: randomUUID(),
|
||||
expires: ONE_WEEK_FROM_NOW,
|
||||
}
|
||||
|
||||
const account: any = {
|
||||
const account: any = options.fixtures?.account ?? {
|
||||
provider: "github",
|
||||
providerAccountId: randomUUID(),
|
||||
type: "oauth",
|
||||
@@ -175,15 +182,17 @@ export async function runBasicTests(options: TestOptions) {
|
||||
test("updateSession", async () => {
|
||||
let dbSession = await db.session(session.sessionToken)
|
||||
|
||||
expect(dbSession.expires.valueOf()).not.toBe(ONE_MONTH_FROM_NOW.valueOf())
|
||||
const expires = options.fixtures?.sessionUpdateExpires ?? ONE_MONTH_FROM_NOW
|
||||
|
||||
expect(dbSession.expires.valueOf()).not.toBe(expires.valueOf())
|
||||
|
||||
await adapter.updateSession({
|
||||
sessionToken: session.sessionToken,
|
||||
expires: ONE_MONTH_FROM_NOW,
|
||||
expires,
|
||||
})
|
||||
|
||||
dbSession = await db.session(session.sessionToken)
|
||||
expect(dbSession.expires.valueOf()).toBe(ONE_MONTH_FROM_NOW.valueOf())
|
||||
expect(dbSession.expires.valueOf()).toBe(expires.valueOf())
|
||||
})
|
||||
|
||||
test("linkAccount", async () => {
|
||||
@@ -232,7 +241,7 @@ export async function runBasicTests(options: TestOptions) {
|
||||
const verificationToken = {
|
||||
token: hashedToken,
|
||||
identifier,
|
||||
expires: FIFTEEN_MINUTES_FROM_NOW,
|
||||
expires: options.fixtures?.verificationTokenExpires ?? FIFTEEN_MINUTES_FROM_NOW,
|
||||
}
|
||||
await adapter.createVerificationToken?.(verificationToken)
|
||||
|
||||
@@ -251,7 +260,7 @@ export async function runBasicTests(options: TestOptions) {
|
||||
const verificationToken = {
|
||||
token: hashedToken,
|
||||
identifier,
|
||||
expires: FIFTEEN_MINUTES_FROM_NOW,
|
||||
expires: options.fixtures?.verificationTokenExpires ?? FIFTEEN_MINUTES_FROM_NOW,
|
||||
}
|
||||
await adapter.createVerificationToken?.(verificationToken)
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ module.exports = {
|
||||
".(js|jsx)$": ["@swc/jest", swcConfig],
|
||||
},
|
||||
transformIgnorePatterns: ["[/\\\\]node_modules[/\\\\].+\\.(js|jsx)$"],
|
||||
moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"],
|
||||
moduleFileExtensions: ["mjs", "cjs", "ts", "tsx", "js", "jsx", "json", "node"],
|
||||
rootDir: ".",
|
||||
// coverageDirectory: "<rootDir>/coverage/",
|
||||
// collectCoverageFrom: ["<rootDir>/packages/*/src/**/*.{ts,tsx}"],
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@auth/typeorm-adapter",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"description": "TypeORM adapter for Auth.js.",
|
||||
"homepage": "https://authjs.dev/reference/adapter/typeorm",
|
||||
"repository": "https://github.com/nextauthjs/next-auth",
|
||||
|
||||
@@ -21,8 +21,8 @@ import type {
|
||||
AdapterSession,
|
||||
} from "@auth/core/adapters"
|
||||
import { DataSourceOptions, DataSource, EntityManager } from "typeorm"
|
||||
import * as defaultEntities from "./entities"
|
||||
import { parseDataSourceConfig, updateConnectionEntities } from "./utils"
|
||||
import * as defaultEntities from "./entities.js"
|
||||
import { parseDataSourceConfig, updateConnectionEntities } from "./utils.js"
|
||||
|
||||
export const entities = defaultEntities
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { DataSource, DataSourceOptions } from "typeorm"
|
||||
import * as defaultEntities from "./entities"
|
||||
import * as defaultEntities from "./entities.js"
|
||||
|
||||
/** Ensure configOrString is normalized to an object. */
|
||||
export function parseDataSourceConfig(
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
|
||||
@@ -1 +0,0 @@
|
||||
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user