Compare commits

...

27 Commits

Author SHA1 Message Date
GitHub Actions
bf2835d38f chore(release): bump package version(s) [skip ci] 2023-08-11 08:26:58 +00:00
Simon Sardorf
89d230666b feat(adapters): standardize default table names to be singular (#8282)
standardize all table names in drizzle adapter to be singular
2023-08-10 12:18:21 +01:00
Jonathan Edenström
f86e56f78a fix: sort cookie chunks correctly (#8278)
* fix: sort cookie chunks correctly

* chore: remove v4 next-auth change
2023-08-10 12:18:00 +01:00
Balázs Orbán
fe20b943ae docs: Update README.md 2023-08-10 00:57:02 +02:00
Balázs Orbán
4678c4d4fc docs: Update README.md 2023-08-10 00:56:24 +02:00
Balázs Orbán
3eb3f8f107 docs: typo 2023-08-09 23:15:35 +02:00
Balázs Orbán
7fd03f38e3 docs: remove heading from README.md 2023-08-09 23:14:53 +02:00
Balázs Orbán
ae44b72765 Merge branch 'main' of github.com:nextauthjs/next-auth 2023-08-09 23:08:13 +02:00
Balázs Orbán
a996ab57e8 🤖 lazy commit 2023-08-09 23:07:28 +02:00
Thang Vu
ebdeaf740d chore: move Turbo env vars to top level 2023-08-09 19:07:01 +07:00
GitHub Actions
c5c8a81462 chore(release): bump package version(s) [skip ci] 2023-08-09 09:39:28 +00:00
Balázs Orbán
61d30f3dcd fix(docs): correct broken link 2023-08-09 11:33:17 +02:00
Balázs Orbán
a9180a752b fix(docs): correct broken links 2023-08-09 11:33:08 +02:00
Balázs Orbán
6c4180146e chore(docs): add @auth/solid-start to turbo cache 2023-08-09 11:28:37 +02:00
Balázs Orbán
ec6c4ea2be docs: fix redirects 2023-08-09 11:20:10 +02:00
Balázs Orbán
3dfc86334e docs: fix redirects 2023-08-09 11:13:36 +02:00
Balázs Orbán
01d6019638 docs: fix redirects 2023-08-09 11:07:20 +02:00
GitHub Actions
4730429a9f chore(release): bump package version(s) [skip ci] 2023-08-09 09:05:20 +00:00
Adam James
a49236ef62 fix(ts): corrected sqlite condition (#8269) 2023-08-09 10:59:08 +02:00
Balázs Orbán
96ade948ef chore(docs): fix redirect 2023-08-09 01:08:58 +02:00
GitHub Actions
550507b2d1 chore(release): bump package version(s) [skip ci] 2023-08-08 23:07:59 +00:00
Mark
1eddcf643c feat(adapters): add Kysely adapter (#5464)
* feat: kysely-adapter with PostgreSQL and MySQL support

* feat: kysely-adapter with SQLite support

* docs: add docs for kysely-adapter

* chore: cleanup

* chore: update adapter lists

* chore: update column types

* chore: remove pgcrypto install

* chore: add indexes

* chore: Object.assign and cleanup

* feat: add AuthedKysely wrapper

* docs: add Naming Conventions section

* chore: add coerceReturnData to reduce repitition

* chore: add coerceInputData to reduce repitition

* chore: move AuthedKysely export to end

* chore: cleanup

* docs: remove unused import

* feat: add support for using AuthedKysely with generated types from kysely-codegen

* docs: formatting

* chore: CodeGen --> Codegen

* docs: wording update, ts

Co-authored-by: Julius Marminge <julius0216@outlook.com>

* chore: use latest kysely version, update model

* docs: move content to source code

* chore: update deps

* chore: update logo location, add link in overview

* chore: bump kysely version

Co-authored-by: Igal Klebanov <igalklebanov@gmail.com>

* chore: update docs

Co-authored-by: Igal Klebanov <igalklebanov@gmail.com>

* chore: update docs with links to new Kysely docs

Co-authored-by: Jie Peng <dean.leehom@gmail.com>

* feat: emailVerified shouldn't have a default

Co-authored-by: Lars Graubner <lgraubner@users.noreply.github.com>

* simplify, update code

* add README.md

* clean up docs

* fix adapter name

* add to turbo

* fix test

* revert some changes

* test fixes

---------

Co-authored-by: Julius Marminge <julius0216@outlook.com>
Co-authored-by: Igal Klebanov <igalklebanov@gmail.com>
Co-authored-by: Jie Peng <dean.leehom@gmail.com>
Co-authored-by: Lars Graubner <lgraubner@users.noreply.github.com>
Co-authored-by: Balázs Orbán <info@balazsorban.com>
2023-08-09 00:01:59 +01:00
Julius Marminge
17d71a04d6 feat(adapters): support multi-project schema (#8266)
* feat: multi-project schema support

Ref: https://orm.drizzle.team/docs/goodies#multi-project-schema

* Update index.ts

* Update index.ts

* doc

* tests

---------

Co-authored-by: Balázs Orbán <info@balazsorban.com>
2023-08-08 23:59:50 +01:00
Balázs Orbán
3c65e264af chore: add drizzle to issue labeler 2023-08-09 00:44:54 +02:00
Balázs Orbán
28d8d4894d chore: add drizzle to turbo 2023-08-09 00:43:49 +02:00
Balázs Orbán
c6b98a8f08 chore: gitignore generated .npmrc files 2023-08-09 00:03:10 +02:00
Balázs Orbán
d042f933c6 fix(docs): update logo URL 2023-08-09 00:01:44 +02:00
65 changed files with 1809 additions and 249 deletions

View File

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

View File

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

View File

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

View File

@@ -57,6 +57,8 @@ on:
- "adapter-xata"
- "next-auth"
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
FORCE_COLOR: true
jobs:
@@ -79,22 +81,12 @@ jobs:
run: pnpm install
- name: Build
run: pnpm build
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
- 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: ${{ vars.TURBO_TEAM }}
- name: Upload Turbo artifacts
uses: actions/upload-artifact@v3
with:
name: turbo-report
path: .turbo/runs/
# - name: Run E2E tests
# if: github.repository == 'nextauthjs/next-auth'
# run: pnpm e2e

2
.gitignore vendored
View File

@@ -6,6 +6,8 @@
.env.development.local
.env.test.local
.env.production.local
packages/*/.npmrc
npm-debug.log*
yarn-debug.log*

View File

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

153
README.md Normal file
View 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

View File

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

View File

@@ -269,6 +269,7 @@ const docusaurusConfig = {
typedocAdapter("DynamoDB"),
typedocAdapter("Fauna"),
typedocAdapter("Firebase"),
typedocAdapter("Kysely"),
typedocAdapter("Mikro ORM"),
typedocAdapter("MongoDB"),
typedocAdapter("Neo4j"),

View File

@@ -59,6 +59,7 @@ module.exports = {
{ 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" },

14
docs/static/img/adapters/kysely.svg vendored Normal file
View 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

View File

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

View File

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

View File

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

View File

@@ -4,7 +4,7 @@
<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"/>
<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;">

View File

@@ -1,6 +1,6 @@
{
"name": "@auth/drizzle-adapter",
"version": "0.1.0",
"version": "0.3.0",
"description": "Drizzle adapter for Auth.js.",
"homepage": "https://authjs.dev",
"repository": "https://github.com/nextauthjs/next-auth",

View File

@@ -16,6 +16,9 @@
* @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"
@@ -24,12 +27,13 @@ import {
isPgDatabase,
isSQLiteDatabase,
SqlFlavorOptions,
TableFn,
} 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.
* 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"
@@ -47,6 +51,10 @@ import type { Adapter } from "@auth/core/adapters"
* ],
* })
* ```
*
* :::info
* If you're using multi-project schemas, you can pass your table function as a second argument
* :::
*
* ## Setup
*
@@ -69,7 +77,7 @@ import type { Adapter } from "@auth/core/adapters"
* } from "drizzle-orm/pg-core"
* import type { AdapterAccount } from '@auth/core/adapters'
*
* export const users = pgTable("users", {
* export const users = pgTable("user", {
* id: text("id").notNull().primaryKey(),
* name: text("name"),
* email: text("email").notNull(),
@@ -78,7 +86,7 @@ import type { Adapter } from "@auth/core/adapters"
* })
*
* export const accounts = pgTable(
* "accounts",
* "account",
* {
* userId: text("userId")
* .notNull()
@@ -99,7 +107,7 @@ import type { Adapter } from "@auth/core/adapters"
* })
* )
*
* export const sessions = pgTable("sessions", {
* export const sessions = pgTable("session", {
* sessionToken: text("sessionToken").notNull().primaryKey(),
* userId: text("userId")
* .notNull()
@@ -132,7 +140,7 @@ import type { Adapter } from "@auth/core/adapters"
* } from "drizzle-orm/mysql-core"
* import type { AdapterAccount } from "@auth/core/adapters"
*
* export const users = mysqlTable("users", {
* export const users = mysqlTable("user", {
* id: varchar("id", { length: 255 }).notNull().primaryKey(),
* name: varchar("name", { length: 255 }),
* email: varchar("email", { length: 255 }).notNull(),
@@ -141,7 +149,7 @@ import type { Adapter } from "@auth/core/adapters"
* })
*
* export const accounts = mysqlTable(
* "accounts",
* "account",
* {
* userId: varchar("userId", { length: 255 })
* .notNull()
@@ -162,7 +170,7 @@ import type { Adapter } from "@auth/core/adapters"
* })
* )
*
* export const sessions = mysqlTable("sessions", {
* export const sessions = mysqlTable("session", {
* sessionToken: varchar("sessionToken", { length: 255 }).notNull().primaryKey(),
* userId: varchar("userId", { length: 255 })
* .notNull()
@@ -189,7 +197,7 @@ import type { Adapter } from "@auth/core/adapters"
* import { integer, sqliteTable, text, primaryKey } from "drizzle-orm/sqlite-core"
* import type { AdapterAccount } from "@auth/core/adapters"
*
* export const users = sqliteTable("users", {
* export const users = sqliteTable("user", {
* id: text("id").notNull().primaryKey(),
* name: text("name"),
* email: text("email").notNull(),
@@ -198,7 +206,7 @@ import type { Adapter } from "@auth/core/adapters"
* })
*
* export const accounts = sqliteTable(
* "accounts",
* "account",
* {
* userId: text("userId")
* .notNull()
@@ -219,7 +227,7 @@ import type { Adapter } from "@auth/core/adapters"
* })
* )
*
* export const sessions = sqliteTable("sessions", {
* export const sessions = sqliteTable("session", {
* sessionToken: text("sessionToken").notNull().primaryKey(),
* userId: text("userId")
* .notNull()
@@ -249,19 +257,20 @@ import type { Adapter } from "@auth/core/adapters"
*
**/
export function DrizzleAdapter<SqlFlavor extends SqlFlavorOptions>(
db: SqlFlavor
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)
return mySqlDrizzleAdapter(db, table as MySqlTableFn)
}
if (isPgDatabase(db)) {
return pgDrizzleAdapter(db)
return pgDrizzleAdapter(db, table as PgTableFn)
}
if (isSQLiteDatabase(db)) {
return SQLiteDrizzleAdapter(db)
return SQLiteDrizzleAdapter(db, table as SQLiteTableFn)
}
throw new Error("Unsupported database type in Auth.js Drizzle adapter.")

View File

@@ -2,75 +2,87 @@ import { and, eq } from "drizzle-orm"
import {
int,
timestamp,
mysqlTable,
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 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 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 }),
})
export const accounts = mysqlTable(
"accounts",
{
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" }),
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
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>>
client: MySql2Database<Record<string, never>>,
tableFn = defaultMySqlTableFn
): Adapter {
const { users, accounts, sessions, verificationTokens } =
createTables(tableFn)
return {
async createUser(data) {
const id = crypto.randomUUID()
@@ -175,7 +187,7 @@ export function mySqlDrizzleAdapter(
return null
}
return dbAccount.users
return dbAccount.user
},
async deleteSession(sessionToken) {
const session =

View File

@@ -1,71 +1,79 @@
import { and, eq } from "drizzle-orm"
import {
timestamp,
pgTable,
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 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 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"),
})
export const accounts = pgTable(
"accounts",
{
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" }),
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
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>>
client: PostgresJsDatabase<Record<string, never>>,
tableFn = defaultPgTableFn
): Adapter {
const { users, accounts, sessions, verificationTokens } =
createTables(tableFn)
return {
async createUser(data) {
return await client
@@ -166,7 +174,7 @@ export function pgDrizzleAdapter(
return null
}
return dbAccount.users
return dbAccount.user
},
async deleteSession(sessionToken) {
const session = await client

View File

@@ -1,70 +1,78 @@
import { eq, and } from "drizzle-orm"
import {
integer,
sqliteTable,
sqliteTable as defaultSqliteTableFn,
text,
primaryKey,
BaseSQLiteDatabase,
SQLiteTableFn,
} 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 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"),
})
export const accounts = sqliteTable(
"accounts",
{
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" }),
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
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>
client: BaseSQLiteDatabase<any, any>,
tableFn = defaultSqliteTableFn
): Adapter {
const { users, accounts, sessions, verificationTokens } =
createTables(tableFn)
return {
createUser(data) {
return client
@@ -151,7 +159,7 @@ export function SQLiteDrizzleAdapter(
)
.get()
return results?.users ?? null
return results?.user ?? null
},
deleteSession(sessionToken) {
return (

View File

@@ -2,9 +2,9 @@ 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 { 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"
@@ -32,6 +32,14 @@ export type ClientFlavors<Flavor> = Flavor extends AnyMySqlDatabase
? 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> {

View 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

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

View File

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

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

View File

@@ -1,20 +1,7 @@
import type { AdapterAccount } from "@auth/core/adapters"
import {
mysqlTable,
varchar,
timestamp,
int,
primaryKey,
} from "drizzle-orm/mysql-core"
import { mysqlTable } 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"
import { createTables } from "../../src/lib/mysql"
const poolConnection = createPool({
host: "localhost",
@@ -23,7 +10,8 @@ const poolConnection = createPool({
database: "next-auth",
})
export { users, accounts, sessions, verificationTokens }
export const db = drizzle(poolConnection, {
schema: schema,
})
export const { users, accounts, sessions, verificationTokens } =
createTables(mysqlTable)
export const schema = { users, accounts, sessions, verificationTokens }
export const db = drizzle(poolConnection, { schema })

View 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

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

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

View File

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

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

View File

@@ -1,11 +1,15 @@
import { drizzle } from "drizzle-orm/postgres-js"
import postgres from "postgres"
import { users, accounts, sessions, verificationTokens } from "../../src/lib/pg"
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: { users, accounts, sessions, verificationTokens },
schema,
})
export { users, accounts, sessions, verificationTokens }

View 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

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

View File

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

View 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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,28 @@
<p align="center">
<br/>
<a href="https://authjs.dev" target="_blank">
<img height="64px" src="https://authjs.dev/img/logo/logo-sm.png" />
</a>
<a href="https://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).

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

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

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

View File

@@ -0,0 +1,3 @@
CREATE USER 'kysely'@'%' IDENTIFIED WITH mysql_native_password BY 'kysely';
GRANT ALL ON *.* TO 'kysely'@'%';
CREATE DATABASE kysely_test;

View 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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -162,8 +162,8 @@ export class SessionStore {
get value() {
// Sort the chunks by their keys before joining
const sortedKeys = Object.keys(this.#chunks).sort((a, b) => {
const aSuffix = parseInt(a.split(".")[1] || "0");
const bSuffix = parseInt(b.split(".")[1] || "0");
const aSuffix = parseInt(a.split(".").pop() || "0");
const bSuffix = parseInt(b.split(".").pop() || "0");
return aSuffix - bSuffix;
});

View File

@@ -1,7 +1,7 @@
/**
* <div style={{backgroundColor: "#000", display: "flex", justifyContent: "space-between", color: "#fff", padding: 16}}>
* <span>Built-in <b>VK</b> integration.</span>
* <a href="www.vk.com/">
* <a href="https://vk.com/">
* <img style={{display: "block"}} src="https://authjs.dev/img/providers/vk.svg" height="48" />
* </a>
* </div>

View File

@@ -1,7 +1,7 @@
/**
* <div style={{backgroundColor: "#000", display: "flex", justifyContent: "space-between", color: "#fff", padding: 16}}>
* <span>Built-in <b> Wikimedia</b> integration.</span>
* <a href="www.mediawiki.org/">
* <a href="https://mediawiki.org/">
* <img style={{display: "block"}} src="https://authjs.dev/img/providers/wikimedia.svg" height="48" />
* </a>
* </div>

View File

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

View File

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

124
pnpm-lock.yaml generated
View File

@@ -318,6 +318,31 @@ importers:
firebase-tools: 11.16.1
jest: 29.3.1
packages/adapter-kysely:
specifiers:
'@auth/core': workspace:*
'@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
dependencies:
'@auth/core': link:../core
devDependencies:
'@next-auth/adapter-test': link:../adapter-test
'@next-auth/tsconfig': link:../tsconfig
'@types/better-sqlite3': 7.6.4
'@types/pg': 8.10.2
better-sqlite3: 8.5.0
jest: 27.5.1
kysely: 0.24.2
mysql2: 3.6.0
pg: 8.11.2
packages/adapter-mikro-orm:
specifiers:
'@auth/core': workspace:*
@@ -10139,6 +10164,14 @@ packages:
resolution: {integrity: sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==}
dev: true
/@types/pg/8.10.2:
resolution: {integrity: sha512-MKFs9P6nJ+LAeHLU3V0cODEOgyThJ3OAnmOlsZsxux6sfQs3HRXR5bBn7xG5DjckEFhTAxsXi7k7cd0pCMxpJw==}
dependencies:
'@types/node': 20.4.8
pg-protocol: 1.5.0
pg-types: 4.0.1
dev: true
/@types/phoenix/1.5.4:
resolution: {integrity: sha512-L5eZmzw89eXBKkiqVBcJfU1QGx9y+wurRIEgt0cuLH0hwNtVUxtx+6cu0R2STwWj468sjXyBYPYDtGclUd1kjQ==}
@@ -21590,6 +21623,11 @@ packages:
resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==}
dev: true
/kysely/0.24.2:
resolution: {integrity: sha512-+7eaTJNUYm2yRq1x+lEOZc+78TO35dTZ9b0dh49+Z9CTt2byMSbMiOKpwPlOyCAaHD4kILkAYWYZNywFlmBwRA==}
engines: {node: '>=14.0.0'}
dev: true
/latest-version/5.1.0:
resolution: {integrity: sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==}
engines: {node: '>=8'}
@@ -23801,15 +23839,30 @@ packages:
resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==}
dev: true
/pg-cloudflare/1.1.1:
resolution: {integrity: sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==}
requiresBuild: true
dev: true
optional: true
/pg-connection-string/2.5.0:
resolution: {integrity: sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==}
dev: true
/pg-connection-string/2.6.2:
resolution: {integrity: sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA==}
dev: true
/pg-int8/1.0.1:
resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==}
engines: {node: '>=4.0.0'}
dev: true
/pg-numeric/1.0.2:
resolution: {integrity: sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==}
engines: {node: '>=4'}
dev: true
/pg-pool/3.5.1_pg@8.7.3:
resolution: {integrity: sha512-6iCR0wVrro6OOHFsyavV+i6KYL4lVNyYAB9RD18w66xSzN+d8b66HiwuP30Gp1SH5O9T82fckkzsRjlrhD0ioQ==}
peerDependencies:
@@ -23818,10 +23871,22 @@ packages:
pg: 8.7.3
dev: true
/pg-pool/3.6.1_pg@8.11.2:
resolution: {integrity: sha512-jizsIzhkIitxCGfPRzJn1ZdcosIt3pz9Sh3V01fm1vZnbnCMgmGl5wvGGdNN2EL9Rmb0EcFoCkixH4Pu+sP9Og==}
peerDependencies:
pg: '>=8.0'
dependencies:
pg: 8.11.2
dev: true
/pg-protocol/1.5.0:
resolution: {integrity: sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ==}
dev: true
/pg-protocol/1.6.0:
resolution: {integrity: sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q==}
dev: true
/pg-types/2.2.0:
resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==}
engines: {node: '>=4'}
@@ -23833,6 +23898,39 @@ packages:
postgres-interval: 1.2.0
dev: true
/pg-types/4.0.1:
resolution: {integrity: sha512-hRCSDuLII9/LE3smys1hRHcu5QGcLs9ggT7I/TCs0IE+2Eesxi9+9RWAAwZ0yaGjxoWICF/YHLOEjydGujoJ+g==}
engines: {node: '>=10'}
dependencies:
pg-int8: 1.0.1
pg-numeric: 1.0.2
postgres-array: 3.0.2
postgres-bytea: 3.0.0
postgres-date: 2.0.1
postgres-interval: 3.0.0
postgres-range: 1.1.3
dev: true
/pg/8.11.2:
resolution: {integrity: sha512-l4rmVeV8qTIrrPrIR3kZQqBgSN93331s9i6wiUiLOSk0Q7PmUxZD/m1rQI622l3NfqBby9Ar5PABfS/SulfieQ==}
engines: {node: '>= 8.0.0'}
peerDependencies:
pg-native: '>=3.0.1'
peerDependenciesMeta:
pg-native:
optional: true
dependencies:
buffer-writer: 2.0.0
packet-reader: 1.0.0
pg-connection-string: 2.6.2
pg-pool: 3.6.1_pg@8.11.2
pg-protocol: 1.6.0
pg-types: 2.2.0
pgpass: 1.0.5
optionalDependencies:
pg-cloudflare: 1.1.1
dev: true
/pg/8.7.3:
resolution: {integrity: sha512-HPmH4GH4H3AOprDJOazoIcpI49XFsHCe8xlrjHkWiapdbHK+HLtbm/GQzXYAZwmPju/kzKhjaSfMACG+8cgJcw==}
engines: {node: '>= 8.0.0'}
@@ -25032,16 +25130,33 @@ packages:
engines: {node: '>=4'}
dev: true
/postgres-array/3.0.2:
resolution: {integrity: sha512-6faShkdFugNQCLwucjPcY5ARoW1SlbnrZjmGl0IrrqewpvxvhSLHimCVzqeuULCbG0fQv7Dtk1yDbG3xv7Veog==}
engines: {node: '>=12'}
dev: true
/postgres-bytea/1.0.0:
resolution: {integrity: sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==}
engines: {node: '>=0.10.0'}
dev: true
/postgres-bytea/3.0.0:
resolution: {integrity: sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw==}
engines: {node: '>= 6'}
dependencies:
obuf: 1.1.2
dev: true
/postgres-date/1.0.7:
resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==}
engines: {node: '>=0.10.0'}
dev: true
/postgres-date/2.0.1:
resolution: {integrity: sha512-YtMKdsDt5Ojv1wQRvUhnyDJNSr2dGIC96mQVKz7xufp07nfuFONzdaowrMHjlAzY6GDLd4f+LUHHAAM1h4MdUw==}
engines: {node: '>=12'}
dev: true
/postgres-interval/1.2.0:
resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==}
engines: {node: '>=0.10.0'}
@@ -25049,6 +25164,15 @@ packages:
xtend: 4.0.2
dev: true
/postgres-interval/3.0.0:
resolution: {integrity: sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw==}
engines: {node: '>=12'}
dev: true
/postgres-range/1.1.3:
resolution: {integrity: sha512-VdlZoocy5lCP0c/t66xAfclglEapXPCIVhqqJRncYpvbCgImF0w67aPKfbqUMr72tO2k5q0TdTZwCLjPTI6C9g==}
dev: true
/postgres/3.3.5:
resolution: {integrity: sha512-+JD93VELV9gHkqpV5gdL5/70HdGtEw4/XE1S4BC8f1mcPmdib3K5XsKVbnR1XcAyC41zOnifJ+9YRKxdIsXiUw==}
dev: true

View File

@@ -2,12 +2,21 @@
"$schema": "https://turborepo.org/schema.json",
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**/*", "*.js", "*.d.ts", "*.d.ts.map"],
"dependsOn": [
"^build"
],
"outputs": [
"dist/**/*",
"*.js",
"*.d.ts",
"*.d.ts.map"
],
"outputMode": "new-only"
},
"next-auth#build": {
"dependsOn": ["^build"],
"dependsOn": [
"^build"
],
"outputs": [
"client/**",
"core/**",
@@ -22,7 +31,9 @@
"outputMode": "new-only"
},
"@auth/core#build": {
"dependsOn": ["^build"],
"dependsOn": [
"^build"
],
"outputs": [
"lib/**",
"providers/**",
@@ -35,8 +46,14 @@
"outputMode": "new-only"
},
"@auth/sveltekit#build": {
"dependsOn": ["^build"],
"outputs": [".svelte-kit/**", "client.*", "index.*"],
"dependsOn": [
"^build"
],
"outputs": [
".svelte-kit/**",
"client.*",
"index.*"
],
"outputMode": "new-only"
},
"clean": {
@@ -49,20 +66,28 @@
"outputMode": "new-only"
},
"e2e": {
"outputs": ["playwright-report/**"]
"outputs": [
"playwright-report/**"
]
},
"@auth/upstash-redis-adapter#test": {
"env": ["UPSTASH_REDIS_KEY", "UPSTASH_REDIS_URL"]
"env": [
"UPSTASH_REDIS_KEY",
"UPSTASH_REDIS_URL"
]
},
"docs#dev": {
"dependsOn": [
"@auth/core#build",
"@auth/prisma-adapter#build",
"@auth/solid-start#build",
"@auth/sveltekit#build",
"@auth/dgraph-adapter#build",
"@auth/drizzle-adapter#build",
"@auth/dynamodb-adapter#build",
"@auth/fauna-adapter#build",
"@auth/firebase-adapter#build",
"@auth/kysely-adapter#build",
"@auth/mikro-orm-adapter#build",
"@auth/mongodb-adapter#build",
"@auth/neo4j-adapter#build",
@@ -81,11 +106,14 @@
"dependsOn": [
"@auth/core#build",
"@auth/prisma-adapter#build",
"@auth/solid-start#build",
"@auth/sveltekit#build",
"@auth/dgraph-adapter#build",
"@auth/drizzle-adapter#build",
"@auth/dynamodb-adapter#build",
"@auth/fauna-adapter#build",
"@auth/firebase-adapter#build",
"@auth/kysely-adapter#build",
"@auth/mikro-orm-adapter#build",
"@auth/mongodb-adapter#build",
"@auth/neo4j-adapter#build",
@@ -107,4 +135,4 @@
]
}
}
}
}