mirror of
https://github.com/SrIzan10/next-auth.git
synced 2026-05-01 10:55:20 +00:00
Compare commits
11 Commits
test/turbo
...
@auth/core
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a85de2c5f | ||
|
|
d47b56743e | ||
|
|
363440e515 | ||
|
|
60c5037ee1 | ||
|
|
97394baed1 | ||
|
|
f94abb8f70 | ||
|
|
bbfc11e74c | ||
|
|
2a70514df1 | ||
|
|
96d666465f | ||
|
|
ecbf0be22e | ||
|
|
87ec13bd00 |
84
.github/workflows/release.yml
vendored
84
.github/workflows/release.yml
vendored
@@ -8,58 +8,54 @@ on:
|
||||
- next
|
||||
- 3.x
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- ".vscode/**"
|
||||
- "**/*.md"
|
||||
- ".github/ISSUE_TEMPLATE/**"
|
||||
# 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:
|
||||
FORCE_COLOR: true
|
||||
|
||||
@@ -85,7 +81,7 @@ jobs:
|
||||
run: pnpm build
|
||||
env:
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
|
||||
- name: Run tests
|
||||
run: pnpm test
|
||||
timeout-minutes: 15
|
||||
@@ -93,7 +89,7 @@ jobs:
|
||||
UPSTASH_REDIS_URL: ${{ secrets.UPSTASH_REDIS_URL }}
|
||||
UPSTASH_REDIS_KEY: ${{ secrets.UPSTASH_REDIS_KEY }}
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
|
||||
- name: Upload Turbo artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
@@ -107,7 +103,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
|
||||
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -65,6 +65,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 +98,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
|
||||
@@ -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)
|
||||
@@ -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:*",
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
@@ -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" />
|
||||
@@ -67,10 +71,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 +133,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,6 +265,7 @@ const docusaurusConfig = {
|
||||
? []
|
||||
: [
|
||||
typedocAdapter("Dgraph"),
|
||||
typedocAdapter("Drizzle"),
|
||||
typedocAdapter("DynamoDB"),
|
||||
typedocAdapter("Fauna"),
|
||||
typedocAdapter("Firebase"),
|
||||
|
||||
@@ -55,6 +55,7 @@ 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" },
|
||||
|
||||
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 |
@@ -6,8 +6,8 @@
|
||||
"scripts": {
|
||||
"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 --summarize",
|
||||
"test": "turbo run test --concurrency=1 --summarize --filter=[HEAD^1] --filter=./packages/* --filter=!@*upstash* --filter=!*dynamodb-* --filter=!*app*",
|
||||
"build": "turbo run build --filter=next-auth --filter=@next-auth/* --filter=@auth/* --no-deps",
|
||||
"test": "turbo run test --concurrency=1 --filter=[HEAD^1] --filter=./packages/* --filter=!@*upstash* --filter=!*dynamodb-* --filter=!*app*",
|
||||
"clean": "turbo run clean --no-cache",
|
||||
"dev:db": "turbo run dev --parallel --continue --filter=next-auth-app...",
|
||||
"dev": "turbo run dev --parallel --continue --filter=next-auth-app... --filter=!./packages/adapter-*",
|
||||
@@ -43,7 +43,7 @@
|
||||
"eslint-plugin-svelte3": "^4.0.0",
|
||||
"prettier": "2.8.1",
|
||||
"prettier-plugin-svelte": "^2.8.1",
|
||||
"turbo": "1.10.3",
|
||||
"turbo": "^1.10.3",
|
||||
"typescript": "4.9.4"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
1
packages/adapter-drizzle/.npmrc
Normal file
1
packages/adapter-drizzle/.npmrc
Normal file
@@ -0,0 +1 @@
|
||||
//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://pbs.twimg.com/profile_images/1598308842391179266/CtXrfLnk_400x400.jpg"/>
|
||||
</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.1.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"
|
||||
}
|
||||
}
|
||||
268
packages/adapter-drizzle/src/index.ts
Normal file
268
packages/adapter-drizzle/src/index.ts
Normal file
@@ -0,0 +1,268 @@
|
||||
/**
|
||||
* <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 { mySqlDrizzleAdapter } from "./lib/mysql.js"
|
||||
import { pgDrizzleAdapter } from "./lib/pg.js"
|
||||
import { SQLiteDrizzleAdapter } from "./lib/sqlite.js"
|
||||
import {
|
||||
isMySqlDatabase,
|
||||
isPgDatabase,
|
||||
isSQLiteDatabase,
|
||||
SqlFlavorOptions,
|
||||
} from "./lib/utils.js"
|
||||
|
||||
import type { Adapter } from "@auth/core/adapters"
|
||||
|
||||
/**
|
||||
* Add the adapter to your `app/api/[...nextauth]/route.js` 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,
|
||||
* }),
|
||||
* ],
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
* ## 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("users", {
|
||||
* id: text("id").notNull().primaryKey(),
|
||||
* name: text("name"),
|
||||
* email: text("email").notNull(),
|
||||
* emailVerified: timestamp("emailVerified", { mode: "date" }),
|
||||
* image: text("image"),
|
||||
* })
|
||||
*
|
||||
* export const accounts = pgTable(
|
||||
* "accounts",
|
||||
* {
|
||||
* 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("sessions", {
|
||||
* 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("users", {
|
||||
* 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(
|
||||
* "accounts",
|
||||
* {
|
||||
* 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("sessions", {
|
||||
* 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("users", {
|
||||
* 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(
|
||||
* "accounts",
|
||||
* {
|
||||
* 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("sessions", {
|
||||
* 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
|
||||
): Adapter {
|
||||
if (isMySqlDatabase(db)) {
|
||||
// We need to cast to unknown since the type overlaps (PScale is MySQL based)
|
||||
return mySqlDrizzleAdapter(db)
|
||||
}
|
||||
|
||||
if (isPgDatabase(db)) {
|
||||
return pgDrizzleAdapter(db)
|
||||
}
|
||||
|
||||
if (isSQLiteDatabase(db)) {
|
||||
return SQLiteDrizzleAdapter(db)
|
||||
}
|
||||
|
||||
throw new Error("Unsupported database type in Auth.js Drizzle adapter.")
|
||||
}
|
||||
255
packages/adapter-drizzle/src/lib/mysql.ts
Normal file
255
packages/adapter-drizzle/src/lib/mysql.ts
Normal file
@@ -0,0 +1,255 @@
|
||||
import { and, eq } from "drizzle-orm"
|
||||
import {
|
||||
int,
|
||||
timestamp,
|
||||
mysqlTable,
|
||||
primaryKey,
|
||||
varchar,
|
||||
} from "drizzle-orm/mysql-core"
|
||||
|
||||
import type { Adapter, AdapterAccount } from "@auth/core/adapters"
|
||||
import type { MySql2Database } from "drizzle-orm/mysql2"
|
||||
|
||||
export const users = mysqlTable("users", {
|
||||
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(
|
||||
"accounts",
|
||||
{
|
||||
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("sessions", {
|
||||
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),
|
||||
})
|
||||
)
|
||||
|
||||
export const schema = { users, accounts, sessions, verificationTokens }
|
||||
export type DefaultSchema = typeof schema
|
||||
|
||||
export function mySqlDrizzleAdapter(
|
||||
client: MySql2Database<Record<string, never>>
|
||||
): Adapter {
|
||||
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.users
|
||||
},
|
||||
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
|
||||
},
|
||||
}
|
||||
}
|
||||
225
packages/adapter-drizzle/src/lib/pg.ts
Normal file
225
packages/adapter-drizzle/src/lib/pg.ts
Normal file
@@ -0,0 +1,225 @@
|
||||
import { and, eq } from "drizzle-orm"
|
||||
import {
|
||||
timestamp,
|
||||
pgTable,
|
||||
text,
|
||||
primaryKey,
|
||||
integer,
|
||||
} from "drizzle-orm/pg-core"
|
||||
|
||||
import type { PostgresJsDatabase } from "drizzle-orm/postgres-js"
|
||||
import type { Adapter, AdapterAccount } from "@auth/core/adapters"
|
||||
|
||||
export const users = pgTable("users", {
|
||||
id: text("id").notNull().primaryKey(),
|
||||
name: text("name"),
|
||||
email: text("email").notNull(),
|
||||
emailVerified: timestamp("emailVerified", { mode: "date" }),
|
||||
image: text("image"),
|
||||
})
|
||||
|
||||
export const accounts = pgTable(
|
||||
"accounts",
|
||||
{
|
||||
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("sessions", {
|
||||
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),
|
||||
})
|
||||
)
|
||||
|
||||
export const schema = { users, accounts, sessions, verificationTokens }
|
||||
export type DefaultSchema = typeof schema
|
||||
|
||||
export function pgDrizzleAdapter(
|
||||
client: PostgresJsDatabase<Record<string, never>>
|
||||
): Adapter {
|
||||
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.users
|
||||
},
|
||||
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 }
|
||||
},
|
||||
}
|
||||
}
|
||||
203
packages/adapter-drizzle/src/lib/sqlite.ts
Normal file
203
packages/adapter-drizzle/src/lib/sqlite.ts
Normal file
@@ -0,0 +1,203 @@
|
||||
import { eq, and } from "drizzle-orm"
|
||||
import {
|
||||
integer,
|
||||
sqliteTable,
|
||||
text,
|
||||
primaryKey,
|
||||
BaseSQLiteDatabase,
|
||||
} from "drizzle-orm/sqlite-core"
|
||||
|
||||
import type { Adapter, AdapterAccount } from "@auth/core/adapters"
|
||||
|
||||
export const users = sqliteTable("users", {
|
||||
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(
|
||||
"accounts",
|
||||
{
|
||||
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("sessions", {
|
||||
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),
|
||||
})
|
||||
)
|
||||
|
||||
export const schema = { users, accounts, sessions, verificationTokens }
|
||||
export type DefaultSchema = typeof schema
|
||||
|
||||
export function SQLiteDrizzleAdapter(
|
||||
client: BaseSQLiteDatabase<any, any>
|
||||
): Adapter {
|
||||
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?.users ?? 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
|
||||
},
|
||||
}
|
||||
}
|
||||
47
packages/adapter-drizzle/src/lib/utils.ts
Normal file
47
packages/adapter-drizzle/src/lib/utils.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { MySqlDatabase } from "drizzle-orm/mysql-core"
|
||||
import { PgDatabase } from "drizzle-orm/pg-core"
|
||||
import { BaseSQLiteDatabase } from "drizzle-orm/sqlite-core"
|
||||
|
||||
import type { AnyMySqlTable } from "drizzle-orm/mysql-core"
|
||||
import type { AnyPgTable } from "drizzle-orm/pg-core"
|
||||
import type { AnySQLiteTable } 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 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(),
|
||||
},
|
||||
}
|
||||
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,
|
||||
},
|
||||
})
|
||||
29
packages/adapter-drizzle/tests/mysql/schema.ts
Normal file
29
packages/adapter-drizzle/tests/mysql/schema.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import type { AdapterAccount } from "@auth/core/adapters"
|
||||
import {
|
||||
mysqlTable,
|
||||
varchar,
|
||||
timestamp,
|
||||
int,
|
||||
primaryKey,
|
||||
} from "drizzle-orm/mysql-core"
|
||||
import { drizzle } from "drizzle-orm/mysql2"
|
||||
import { createPool } from "mysql2"
|
||||
import {
|
||||
users,
|
||||
accounts,
|
||||
sessions,
|
||||
verificationTokens,
|
||||
schema,
|
||||
} from "../../src/lib/mysql"
|
||||
|
||||
const poolConnection = createPool({
|
||||
host: "localhost",
|
||||
user: "root",
|
||||
password: "password",
|
||||
database: "next-auth",
|
||||
})
|
||||
|
||||
export { users, accounts, sessions, verificationTokens }
|
||||
export const db = drizzle(poolConnection, {
|
||||
schema: 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}
|
||||
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))
|
||||
11
packages/adapter-drizzle/tests/pg/schema.ts
Normal file
11
packages/adapter-drizzle/tests/pg/schema.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { drizzle } from "drizzle-orm/postgres-js"
|
||||
import postgres from "postgres"
|
||||
import { users, accounts, sessions, verificationTokens } from "../../src/lib/pg"
|
||||
|
||||
const connectionString = "postgres://nextauth:nextauth@localhost:5432/nextauth"
|
||||
const sql = postgres(connectionString, { max: 1 })
|
||||
|
||||
export const db = drizzle(sql, {
|
||||
schema: { users, accounts, sessions, verificationTokens },
|
||||
})
|
||||
export { users, accounts, sessions, verificationTokens }
|
||||
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}
|
||||
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,
|
||||
},
|
||||
})
|
||||
20
packages/adapter-drizzle/tests/sqlite/schema.ts
Normal file
20
packages/adapter-drizzle/tests/sqlite/schema.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { drizzle } from "drizzle-orm/better-sqlite3"
|
||||
import Database from "better-sqlite3"
|
||||
import {
|
||||
users,
|
||||
accounts,
|
||||
sessions,
|
||||
verificationTokens,
|
||||
} from "../../src/lib/sqlite"
|
||||
|
||||
const sqlite = new Database("db.sqlite")
|
||||
|
||||
export { users, accounts, sessions, verificationTokens }
|
||||
export const db = drizzle(sqlite, {
|
||||
schema: {
|
||||
users,
|
||||
accounts,
|
||||
sessions,
|
||||
verificationTokens,
|
||||
},
|
||||
})
|
||||
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",
|
||||
]
|
||||
}
|
||||
@@ -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,6 +1,6 @@
|
||||
{
|
||||
"name": "@auth/core",
|
||||
"version": "0.10.0",
|
||||
"version": "0.10.1",
|
||||
"description": "Authentication for the Web.",
|
||||
"keywords": [
|
||||
"authentication",
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
"svelte-check": "^2.9.2",
|
||||
"tslib": "^2.4.1",
|
||||
"typescript": "^4.9.3",
|
||||
"vite": "^4.0.0",
|
||||
"vite": "^4.0.5",
|
||||
"vitest": "^0.25.3"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
1225
pnpm-lock.yaml
generated
1225
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user