mirror of
https://github.com/SrIzan10/next-auth.git
synced 2026-05-01 10:55:20 +00:00
Compare commits
7 Commits
@auth/dyna
...
@auth/d1-a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d0cc046e2d | ||
|
|
c818d028aa | ||
|
|
3ba8a0e40a | ||
|
|
770d3565f8 | ||
|
|
ed32236712 | ||
|
|
307f7b5eb9 | ||
|
|
120d7a29ee |
@@ -61,7 +61,7 @@ pnpm install
|
||||
4. Start the development server
|
||||
|
||||
```bash
|
||||
$ pnpm dev:docs
|
||||
pnpm dev:docs
|
||||
```
|
||||
|
||||
And thats all! Now you should have a local copy of this docs site running at [localhost:3000](http://localhost:3000)!
|
||||
|
||||
@@ -89,7 +89,7 @@ NEXTAUTH_SECRET="This is an example"
|
||||
`NEXTAUTH_SECRET` is a random string used by the library to encrypt tokens and email verification hashes, and **it's mandatory to keep things secure**! 🔥 🔐 . You can use:
|
||||
|
||||
```
|
||||
$ openssl rand -base64 32
|
||||
openssl rand -base64 32
|
||||
```
|
||||
|
||||
or https://generate-secret.vercel.app/32 to generate a random value for it.
|
||||
@@ -242,7 +242,7 @@ AUTH_SECRET="This is an example"
|
||||
`AUTH_SECRET` is a random string used by the library to encrypt tokens and email verification hashes, and **it's mandatory to keep things secure**! 🔥 🔐 . You can use:
|
||||
|
||||
```
|
||||
$ openssl rand -base64 32
|
||||
openssl rand -base64 32
|
||||
```
|
||||
|
||||
or https://generate-secret.vercel.app/32 to generate a random value for it.
|
||||
@@ -288,7 +288,7 @@ To protect your API Routes (blocking unauthorized access to resources), you can
|
||||
```ts title="routes/api/movies/+server.ts"
|
||||
import { json, error } from "@sveltejs/kit";
|
||||
import type { RequestEvent } from "./$types";
|
||||
|
||||
|
||||
export async function GET({ locals }: RequestEvent) {
|
||||
const session = await locals.getSession()
|
||||
if (!session?.user) {
|
||||
@@ -429,7 +429,7 @@ export default NextAuth({
|
||||
Great! We're now ready to run our application locally. Start the Next.js app by running on your terminal the following command and navigating to [`http://localhost:3000`](http://localhost:3000):
|
||||
|
||||
```
|
||||
$ npm run next dev
|
||||
npm run next dev
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
@@ -448,7 +448,7 @@ export const handle = SvelteKitAuth({
|
||||
Great! We're now ready to run our application locally. Start the Svelte app by running on your terminal the following command and navigating to [`http://localhost:5173`](http://localhost:5173):
|
||||
|
||||
```
|
||||
$ npm run vite dev
|
||||
npm run vite dev
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -580,7 +580,7 @@ Auth.js used to generate a secret for convenience, when the user did not define
|
||||
You can generate a secret to be placed in the `secret` configuration option via the following command:
|
||||
|
||||
```bash
|
||||
$ openssl rand -base64 32
|
||||
openssl rand -base64 32
|
||||
```
|
||||
|
||||
Therefore, your Auth.js config should look something like this:
|
||||
|
||||
@@ -5,6 +5,10 @@ title: Overview
|
||||
Using an Auth.js / NextAuth.js adapter you can connect to any database service or even several different services at the same time. The following listed official adapters are created and maintained by the community:
|
||||
|
||||
<div class="adapter-card-list">
|
||||
<a href="/reference/adapter/d1" class="adapter-card">
|
||||
<img src="/img/adapters/d1.svg" width="40" />
|
||||
<h4 class="adapter-card__title">D1 Adapter</h4>
|
||||
</a>
|
||||
<a href="/reference/adapter/edgedb" class="adapter-card">
|
||||
<img src="/img/adapters/edgedb.svg" width="30" />
|
||||
<h4 class="adapter-card__title">EdgeDB Adapter</h4>
|
||||
|
||||
@@ -282,6 +282,7 @@ const docusaurusConfig = {
|
||||
...(process.env.TYPEDOC_SKIP_ADAPTERS
|
||||
? []
|
||||
: [
|
||||
typedocAdapter("D1"),
|
||||
typedocAdapter("EdgeDb"),
|
||||
typedocAdapter("Dgraph"),
|
||||
typedocAdapter("Drizzle"),
|
||||
|
||||
@@ -46,6 +46,7 @@ module.exports = {
|
||||
label: "Database Adapters",
|
||||
link: { type: "doc", id: "reference/adapters/index" },
|
||||
items: [
|
||||
{ type: "doc", id: "reference/adapter/edgedb/index" },
|
||||
{ type: "doc", id: "reference/adapter/dgraph/index" },
|
||||
{ type: "doc", id: "reference/adapter/drizzle/index" },
|
||||
{ type: "doc", id: "reference/adapter/dynamodb/index" },
|
||||
|
||||
5
docs/static/img/adapters/d1.svg
vendored
Normal file
5
docs/static/img/adapters/d1.svg
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="49" viewBox="0 0 48 49">
|
||||
<path d="m18.63 37.418-9.645-12.9 9.592-12.533-1.852-2.527L5.917 23.595l-.015 1.808 10.86 14.542 1.868-2.527z" fill="rgb(243, 128, 32)"></path>
|
||||
<path d="M21.997 6.503h-3.712l13.387 18.3-13.072 17.7h3.735L35.4 24.81 21.997 6.503z" fill="rgb(243, 128, 32)"></path>
|
||||
<path d="M29.175 6.503h-3.758l13.598 18.082-13.598 17.918h3.765l12.908-17.01v-1.808L29.175 6.503z" fill="rgb(243, 128, 32)"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 497 B |
28
packages/adapter-d1/README.md
Normal file
28
packages/adapter-d1/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://developers.cloudflare.com/d1/" target="_blank">
|
||||
<img height="64px" src="https://authjs.dev/img/adapters/d1.svg"/>
|
||||
</a>
|
||||
<h3 align="center"><b>Cloudflare D1 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/d1-adapter">
|
||||
<img alt="npm" src="https://img.shields.io/npm/v/@auth/d1-adapter?color=green&label=@auth/d1-adapter&style=flat-square">
|
||||
</a>
|
||||
<a href="https://www.npmtrends.com/@auth/d1-adapter">
|
||||
<img src="https://img.shields.io/npm/dm/@auth/d1-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/d1).
|
||||
59
packages/adapter-d1/package.json
Normal file
59
packages/adapter-d1/package.json
Normal file
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"name": "@auth/d1-adapter",
|
||||
"version": "0.2.0",
|
||||
"description": "A Cloudflare D1 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": "Josh Schlesser <josh@schlesser.dev>",
|
||||
"contributors": [
|
||||
"Thang Huu Vu <hi@thvu.dev>"
|
||||
],
|
||||
"license": "ISC",
|
||||
"keywords": [
|
||||
"next-auth",
|
||||
"@auth",
|
||||
"Auth.js",
|
||||
"next.js",
|
||||
"oauth",
|
||||
"d1"
|
||||
],
|
||||
"type": "module",
|
||||
"types": "dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./index.d.ts",
|
||||
"import": "./index.js"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"*.d.ts*",
|
||||
"*.js",
|
||||
"src"
|
||||
],
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"clean": "rm -rf dist",
|
||||
"test": "jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@auth/core": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@auth/adapter-test": "workspace:*",
|
||||
"@auth/tsconfig": "workspace:*",
|
||||
"@cloudflare/workers-types": "^4.20230321.0",
|
||||
"@miniflare/d1": "^2.12.2",
|
||||
"better-sqlite3": "^7.0.0",
|
||||
"jest": "^29.3.0"
|
||||
},
|
||||
"jest": {
|
||||
"preset": "@auth/adapter-test/jest"
|
||||
}
|
||||
}
|
||||
432
packages/adapter-d1/src/index.ts
Normal file
432
packages/adapter-d1/src/index.ts
Normal file
@@ -0,0 +1,432 @@
|
||||
/**
|
||||
* <div style={{display: "flex", justifyContent: "space-between", alignItems: "center", padding: 16}}>
|
||||
* <p style={{fontWeight: "normal"}}>An unofficial <a href="https://developers.cloudflare.com/d1/">Cloudflare D1</a> adapter for Auth.js / NextAuth.js.</p>
|
||||
* <a href="https://developers.cloudflare.com/d1/">
|
||||
* <img style={{display: "block"}} src="/img/adapters/d1.svg" width="48" />
|
||||
* </a>
|
||||
* </div>
|
||||
*
|
||||
* ## Warning
|
||||
* This adapter is not developed or maintained by Clouflare and they haven't declared the D1 api stable. The author will make an effort to keep this adapter up to date.
|
||||
* The adapter is compatible with the D1 api as of March 22, 2023.
|
||||
*
|
||||
* ## Installation
|
||||
*
|
||||
* ```bash npm2yarn2pnpm
|
||||
* npm install next-auth @auth/d1-adapter
|
||||
* ```
|
||||
*
|
||||
* @module @auth/d1-adapter
|
||||
*/
|
||||
|
||||
import type { D1Database as WorkerDatabase } from "@cloudflare/workers-types"
|
||||
import type { D1Database as MiniflareD1Database } from "@miniflare/d1"
|
||||
import type {
|
||||
Adapter,
|
||||
AdapterSession,
|
||||
AdapterUser,
|
||||
AdapterAccount,
|
||||
VerificationToken as AdapterVerificationToken,
|
||||
} from "@auth/core/adapters"
|
||||
|
||||
export { up } from "./migrations"
|
||||
|
||||
/**
|
||||
* @type @cloudflare/workers-types.D1Database | @miniflare/d1.D1Database
|
||||
*/
|
||||
export type D1Database = WorkerDatabase | MiniflareD1Database
|
||||
|
||||
// all the sqls
|
||||
// USER
|
||||
export const CREATE_USER_SQL = `INSERT INTO users (id, name, email, emailVerified, image) VALUES (?, ?, ?, ?, ?)`
|
||||
export const GET_USER_BY_ID_SQL = `SELECT * FROM users WHERE id = ?`
|
||||
export const GET_USER_BY_EMAIL_SQL = `SELECT * FROM users WHERE email = ?`
|
||||
export const GET_USER_BY_ACCOUNTL_SQL = `
|
||||
SELECT u.*
|
||||
FROM users u JOIN accounts a ON a.userId = u.id
|
||||
WHERE a.providerAccountId = ? AND a.provider = ?`
|
||||
export const UPDATE_USER_BY_ID_SQL = `
|
||||
UPDATE users
|
||||
SET name = ?, email = ?, emailVerified = ?, image = ?
|
||||
WHERE id = ? `
|
||||
export const DELETE_USER_SQL = `DELETE FROM users WHERE id = ?`
|
||||
|
||||
// SESSION
|
||||
export const CREATE_SESSION_SQL =
|
||||
"INSERT INTO sessions (id, sessionToken, userId, expires) VALUES (?,?,?,?)"
|
||||
export const GET_SESSION_BY_TOKEN_SQL = `
|
||||
SELECT id, sessionToken, userId, expires
|
||||
FROM sessions
|
||||
WHERE sessionToken = ?`
|
||||
export const UPDATE_SESSION_BY_SESSION_TOKEN_SQL = `UPDATE sessions SET expires = ? WHERE sessionToken = ?`
|
||||
export const DELETE_SESSION_SQL = `DELETE FROM sessions WHERE sessionToken = ?`
|
||||
export const DELETE_SESSION_BY_USER_ID_SQL = `DELETE FROM sessions WHERE userId = ?`
|
||||
|
||||
// ACCOUNT
|
||||
export const CREATE_ACCOUNT_SQL = `
|
||||
INSERT INTO accounts (
|
||||
id, userId, type, provider,
|
||||
providerAccountId, refresh_token, access_token,
|
||||
expires_at, token_type, scope, id_token, session_state,
|
||||
oauth_token, oauth_token_secret
|
||||
)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)`
|
||||
export const GET_ACCOUNT_BY_ID_SQL = `SELECT * FROM accounts WHERE id = ? `
|
||||
export const GET_ACCOUNT_BY_PROVIDER_AND_PROVIDER_ACCOUNT_ID_SQL = `SELECT * FROM accounts WHERE provider = ? AND providerAccountId = ?`
|
||||
export const DELETE_ACCOUNT_BY_PROVIDER_AND_PROVIDER_ACCOUNT_ID_SQL = `DELETE FROM accounts WHERE provider = ? AND providerAccountId = ?`
|
||||
export const DELETE_ACCOUNT_BY_USER_ID_SQL = `DELETE FROM accounts WHERE userId = ?`
|
||||
|
||||
// VERIFICATION_TOKEN
|
||||
export const GET_VERIFICATION_TOKEN_BY_IDENTIFIER_AND_TOKEN_SQL = `SELECT * FROM verification_tokens WHERE identifier = ? AND token = ?`
|
||||
export const CREATE_VERIFICATION_TOKEN_SQL = `INSERT INTO verification_tokens (identifier, expires, token) VALUES (?,?,?)`
|
||||
export const DELETE_VERIFICATION_TOKEN_SQL = `DELETE FROM verification_tokens WHERE identifier = ? and token = ?`
|
||||
|
||||
// helper functions
|
||||
|
||||
// isDate is borrowed from the supabase adapter, graciously
|
||||
// depending on error messages ("Invalid Date") is always precarious, but probably fine for a built in native like Date
|
||||
function isDate(date: any) {
|
||||
return (
|
||||
new Date(date).toString() !== "Invalid Date" && !isNaN(Date.parse(date))
|
||||
)
|
||||
}
|
||||
|
||||
// format is borrowed from the supabase adapter, graciously
|
||||
function format<T>(obj: Record<string, any>): T {
|
||||
for (const [key, value] of Object.entries(obj)) {
|
||||
if (value === null) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
||||
delete obj[key]
|
||||
}
|
||||
|
||||
if (isDate(value)) {
|
||||
obj[key] = new Date(value)
|
||||
}
|
||||
}
|
||||
|
||||
return obj as T
|
||||
}
|
||||
|
||||
// D1 doesnt like undefined, it wants null when calling bind
|
||||
function cleanBindings(bindings: any[]) {
|
||||
return bindings.map((e) => (e === undefined ? null : e))
|
||||
}
|
||||
|
||||
export async function createRecord<RecordType>(
|
||||
db: D1Database,
|
||||
CREATE_SQL: string,
|
||||
bindings: any[],
|
||||
GET_SQL: string,
|
||||
getBindings: any[]
|
||||
) {
|
||||
try {
|
||||
bindings = cleanBindings(bindings)
|
||||
await db
|
||||
.prepare(CREATE_SQL)
|
||||
.bind(...bindings)
|
||||
.run()
|
||||
return await getRecord<RecordType>(db, GET_SQL, getBindings)
|
||||
} catch (e: any) {
|
||||
console.error(e.message, e.cause?.message)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
export async function getRecord<RecordType>(
|
||||
db: D1Database,
|
||||
SQL: string,
|
||||
bindings: any[]
|
||||
): Promise<RecordType | null> {
|
||||
try {
|
||||
bindings = cleanBindings(bindings)
|
||||
const res: any = await db
|
||||
.prepare(SQL)
|
||||
.bind(...bindings)
|
||||
.first()
|
||||
if (res) {
|
||||
return format<RecordType>(res)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
} catch (e: any) {
|
||||
console.error(e.message, e.cause?.message)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
export async function updateRecord(
|
||||
db: D1Database,
|
||||
SQL: string,
|
||||
bindings: any[]
|
||||
) {
|
||||
try {
|
||||
bindings = cleanBindings(bindings)
|
||||
return await db
|
||||
.prepare(SQL)
|
||||
.bind(...bindings)
|
||||
.run()
|
||||
} catch (e: any) {
|
||||
console.error(e.message, e.cause?.message)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteRecord(
|
||||
db: D1Database,
|
||||
SQL: string,
|
||||
bindings: any[]
|
||||
) {
|
||||
// eslint-disable-next-line no-useless-catch
|
||||
try {
|
||||
bindings = cleanBindings(bindings)
|
||||
await db
|
||||
.prepare(SQL)
|
||||
.bind(...bindings)
|
||||
.run()
|
||||
} catch (e: any) {
|
||||
console.error(e.message, e.cause?.message)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* ## Setup
|
||||
*
|
||||
* This is the D1 Adapter for [`next-auth`](https://authjs.dev). This package can only be used in conjunction with the primary `next-auth` package. It is not a standalone package.
|
||||
*
|
||||
* ### Configure Auth.js
|
||||
*
|
||||
* ```javascript title="pages/api/auth/[...nextauth].js"
|
||||
* import NextAuth from "next-auth"
|
||||
* import { D1Adapter, up } from "@auth/d1-adapter"
|
||||
*
|
||||
*
|
||||
* // For more information on each option (and a full list of options) go to
|
||||
* // https://authjs.dev/reference/configuration/auth-options
|
||||
* export default NextAuth({
|
||||
* // https://authjs.dev/reference/providers/
|
||||
* providers: [],
|
||||
* adapter: D1Adapter(env.db)
|
||||
* ...
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
* ### Migrations
|
||||
*
|
||||
* Somewhere in the initialization of your application you need to run the `up(env.db)` function to create the tables in D1.
|
||||
* It will create 4 tables if they don't already exist:
|
||||
* `accounts`, `sessions`, `users`, `verification_tokens`.
|
||||
*
|
||||
* The table prefix "" is not configurable at this time.
|
||||
*
|
||||
* You can use something like the following to attempt the migration once each time your worker starts up. Running migrations more than once will not erase your existing tables.
|
||||
* ```javascript
|
||||
* import { up } from "@auth/d1-adapter"
|
||||
*
|
||||
* let migrated = false;
|
||||
* async function migrationHandle({event, resolve}) {
|
||||
* if(!migrated) {
|
||||
* try {
|
||||
* await up(event.platform.env.db)
|
||||
* migrated = true
|
||||
* } catch(e) {
|
||||
* console.log(e.cause.message, e.message)
|
||||
* }
|
||||
* }
|
||||
* return resolve(event)
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
*
|
||||
* You can also initialize your tables manually. Look in [init.ts](https://github.com/nextauthjs/next-auth/packages/adapter-d1/src/migrations/init.ts) for the relevant sql.
|
||||
* Paste and run the SQL into your D1 dashboard query tool.
|
||||
*
|
||||
**/
|
||||
export function D1Adapter(db: D1Database): Adapter {
|
||||
// we need to run migrations if we dont have the right tables
|
||||
|
||||
return {
|
||||
async createUser(user) {
|
||||
const id: string = crypto.randomUUID()
|
||||
const createBindings = [
|
||||
id,
|
||||
user.name,
|
||||
user.email,
|
||||
user.emailVerified?.toISOString(),
|
||||
user.image,
|
||||
]
|
||||
const getBindings = [id]
|
||||
|
||||
const newUser = await createRecord<AdapterUser>(
|
||||
db,
|
||||
CREATE_USER_SQL,
|
||||
createBindings,
|
||||
GET_USER_BY_ID_SQL,
|
||||
getBindings
|
||||
)
|
||||
if (newUser) return newUser
|
||||
throw new Error("Error creating user: Cannot get user after creation.")
|
||||
},
|
||||
async getUser(id) {
|
||||
return await getRecord<AdapterUser>(db, GET_USER_BY_ID_SQL, [id])
|
||||
},
|
||||
async getUserByEmail(email) {
|
||||
return await getRecord<AdapterUser>(db, GET_USER_BY_EMAIL_SQL, [email])
|
||||
},
|
||||
async getUserByAccount({ providerAccountId, provider }) {
|
||||
return await getRecord<AdapterUser>(db, GET_USER_BY_ACCOUNTL_SQL, [
|
||||
providerAccountId,
|
||||
provider,
|
||||
])
|
||||
},
|
||||
async updateUser(user) {
|
||||
const params = await getRecord<AdapterUser>(db, GET_USER_BY_ID_SQL, [
|
||||
user.id,
|
||||
])
|
||||
if (params) {
|
||||
// copy any properties not in the update into the existing one and use that for bind params
|
||||
// covers the scenario where the user arg doesnt have all of the current users properties
|
||||
Object.assign(params, user)
|
||||
const res = await updateRecord(db, UPDATE_USER_BY_ID_SQL, [
|
||||
params.name,
|
||||
params.email,
|
||||
params.emailVerified?.toISOString(),
|
||||
params.image,
|
||||
params.id,
|
||||
])
|
||||
if (res.success) {
|
||||
// we could probably just return
|
||||
const user = await getRecord<AdapterUser>(db, GET_USER_BY_ID_SQL, [
|
||||
params.id,
|
||||
])
|
||||
if (user) return user
|
||||
throw new Error(
|
||||
"Error updating user: Cannot get user after updating."
|
||||
)
|
||||
}
|
||||
}
|
||||
throw new Error("Error updating user: Failed to run the update SQL.")
|
||||
},
|
||||
async deleteUser(userId) {
|
||||
// this should probably be in a db.batch but batch has problems right now in miniflare
|
||||
// no multi line sql statements
|
||||
await deleteRecord(db, DELETE_ACCOUNT_BY_USER_ID_SQL, [userId])
|
||||
await deleteRecord(db, DELETE_SESSION_BY_USER_ID_SQL, [userId])
|
||||
await deleteRecord(db, DELETE_USER_SQL, [userId])
|
||||
return null
|
||||
},
|
||||
async linkAccount(a) {
|
||||
// convert user_id to userId and provider_account_id to providerAccountId
|
||||
const id = crypto.randomUUID()
|
||||
const createBindings = [
|
||||
id,
|
||||
a.userId,
|
||||
a.type,
|
||||
a.provider,
|
||||
a.providerAccountId,
|
||||
a.refresh_token,
|
||||
a.access_token,
|
||||
a.expires_at,
|
||||
a.token_type,
|
||||
a.scope,
|
||||
a.id_token,
|
||||
a.session_state,
|
||||
a.oauth_token ?? null,
|
||||
a.oauth_token_secret ?? null,
|
||||
]
|
||||
const getBindings = [id]
|
||||
return await createRecord<AdapterAccount>(
|
||||
db,
|
||||
CREATE_ACCOUNT_SQL,
|
||||
createBindings,
|
||||
GET_ACCOUNT_BY_ID_SQL,
|
||||
getBindings
|
||||
)
|
||||
},
|
||||
async unlinkAccount({ providerAccountId, provider }) {
|
||||
await deleteRecord(
|
||||
db,
|
||||
DELETE_ACCOUNT_BY_PROVIDER_AND_PROVIDER_ACCOUNT_ID_SQL,
|
||||
[provider, providerAccountId]
|
||||
)
|
||||
},
|
||||
async createSession({ sessionToken, userId, expires }) {
|
||||
const id = crypto.randomUUID()
|
||||
const createBindings = [id, sessionToken, userId, expires.toISOString()]
|
||||
const getBindings = [sessionToken]
|
||||
const session = await createRecord<AdapterSession>(
|
||||
db,
|
||||
CREATE_SESSION_SQL,
|
||||
createBindings,
|
||||
GET_SESSION_BY_TOKEN_SQL,
|
||||
getBindings
|
||||
)
|
||||
if (session) return session
|
||||
throw new Error(`Couldn't create session`)
|
||||
},
|
||||
async getSessionAndUser(sessionToken) {
|
||||
const session: any = await getRecord<AdapterSession>(
|
||||
db,
|
||||
GET_SESSION_BY_TOKEN_SQL,
|
||||
[sessionToken]
|
||||
)
|
||||
// no session? no user!
|
||||
if (session === null) return null
|
||||
|
||||
// this shouldnt happen, but just in case
|
||||
const user = await getRecord<AdapterUser>(db, GET_USER_BY_ID_SQL, [
|
||||
session.userId,
|
||||
])
|
||||
if (user === null) return null
|
||||
|
||||
return { session, user }
|
||||
},
|
||||
async updateSession({ sessionToken, expires }) {
|
||||
// kinda strange that we have to deal with an undefined expires,
|
||||
// we dont have any policy to enforce, lets just expire it now.
|
||||
if (expires === undefined) {
|
||||
await deleteRecord(db, DELETE_SESSION_SQL, [sessionToken])
|
||||
return null
|
||||
}
|
||||
const session = await getRecord<AdapterSession>(
|
||||
db,
|
||||
GET_SESSION_BY_TOKEN_SQL,
|
||||
[sessionToken]
|
||||
)
|
||||
if (!session) return null
|
||||
session.expires = expires
|
||||
await updateRecord(db, UPDATE_SESSION_BY_SESSION_TOKEN_SQL, [
|
||||
expires?.toISOString(),
|
||||
sessionToken,
|
||||
])
|
||||
return await db
|
||||
.prepare(UPDATE_SESSION_BY_SESSION_TOKEN_SQL)
|
||||
.bind(expires?.toISOString(), sessionToken)
|
||||
.first()
|
||||
},
|
||||
async deleteSession(sessionToken) {
|
||||
await deleteRecord(db, DELETE_SESSION_SQL, [sessionToken])
|
||||
return null
|
||||
},
|
||||
async createVerificationToken({ identifier, expires, token }) {
|
||||
return await createRecord(
|
||||
db,
|
||||
CREATE_VERIFICATION_TOKEN_SQL,
|
||||
[identifier, expires.toISOString(), token],
|
||||
GET_VERIFICATION_TOKEN_BY_IDENTIFIER_AND_TOKEN_SQL,
|
||||
[identifier, token]
|
||||
)
|
||||
},
|
||||
async useVerificationToken({ identifier, token }) {
|
||||
const verificationToken = await getRecord<AdapterVerificationToken>(
|
||||
db,
|
||||
GET_VERIFICATION_TOKEN_BY_IDENTIFIER_AND_TOKEN_SQL,
|
||||
[identifier, token]
|
||||
)
|
||||
if (!verificationToken) return null
|
||||
await deleteRecord(db, DELETE_VERIFICATION_TOKEN_SQL, [identifier, token])
|
||||
return verificationToken
|
||||
},
|
||||
}
|
||||
}
|
||||
66
packages/adapter-d1/src/migrations.ts
Normal file
66
packages/adapter-d1/src/migrations.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import type { D1Database } from "."
|
||||
|
||||
export const upSQLStatements = [
|
||||
`CREATE TABLE IF NOT EXISTS "accounts" (
|
||||
"id" text NOT NULL,
|
||||
"userId" text NOT NULL DEFAULT NULL,
|
||||
"type" text NOT NULL DEFAULT NULL,
|
||||
"provider" text NOT NULL DEFAULT NULL,
|
||||
"providerAccountId" text NOT NULL DEFAULT NULL,
|
||||
"refresh_token" text DEFAULT NULL,
|
||||
"access_token" text DEFAULT NULL,
|
||||
"expires_at" number DEFAULT NULL,
|
||||
"token_type" text DEFAULT NULL,
|
||||
"scope" text DEFAULT NULL,
|
||||
"id_token" text DEFAULT NULL,
|
||||
"session_state" text DEFAULT NULL,
|
||||
"oauth_token_secret" text DEFAULT NULL,
|
||||
"oauth_token" text DEFAULT NULL,
|
||||
PRIMARY KEY (id)
|
||||
);`,
|
||||
`CREATE TABLE IF NOT EXISTS "sessions" (
|
||||
"id" text NOT NULL,
|
||||
"sessionToken" text NOT NULL,
|
||||
"userId" text NOT NULL DEFAULT NULL,
|
||||
"expires" datetime NOT NULL DEFAULT NULL,
|
||||
PRIMARY KEY (sessionToken)
|
||||
);`,
|
||||
`CREATE TABLE IF NOT EXISTS "users" (
|
||||
"id" text NOT NULL DEFAULT '',
|
||||
"name" text DEFAULT NULL,
|
||||
"email" text DEFAULT NULL,
|
||||
"emailVerified" datetime DEFAULT NULL,
|
||||
"image" text DEFAULT NULL,
|
||||
PRIMARY KEY (id)
|
||||
);`,
|
||||
`CREATE TABLE IF NOT EXISTS "verification_tokens" (
|
||||
"identifier" text NOT NULL,
|
||||
"token" text NOT NULL DEFAULT NULL,
|
||||
"expires" datetime NOT NULL DEFAULT NULL,
|
||||
PRIMARY KEY (token)
|
||||
);`,
|
||||
]
|
||||
|
||||
export const down = [
|
||||
`DROP TABLE IF EXISTS "accounts";`,
|
||||
`DROP TABLE IF EXISTS "sessions";`,
|
||||
`DROP TABLE IF EXISTS "users";`,
|
||||
`DROP TABLE IF EXISTS "verification_token";`,
|
||||
]
|
||||
|
||||
/**
|
||||
*
|
||||
* @param db
|
||||
*/
|
||||
async function up(db: D1Database) {
|
||||
// run the migration
|
||||
upSQLStatements.forEach(async (sql) => {
|
||||
try {
|
||||
const res = await db.prepare(sql).run()
|
||||
} catch (e: any) {
|
||||
console.error(e.cause?.message, e.message)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export { up }
|
||||
56
packages/adapter-d1/tests/index.test.ts
Normal file
56
packages/adapter-d1/tests/index.test.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import {
|
||||
D1Adapter,
|
||||
up,
|
||||
getRecord,
|
||||
GET_USER_BY_ID_SQL,
|
||||
GET_SESSION_BY_TOKEN_SQL,
|
||||
GET_ACCOUNT_BY_PROVIDER_AND_PROVIDER_ACCOUNT_ID_SQL,
|
||||
GET_VERIFICATION_TOKEN_BY_IDENTIFIER_AND_TOKEN_SQL,
|
||||
} from "../src"
|
||||
import {
|
||||
AdapterSession,
|
||||
AdapterUser,
|
||||
AdapterAccount,
|
||||
} from "@auth/core/adapters"
|
||||
import { D1Database, D1DatabaseAPI } from "@miniflare/d1"
|
||||
import { runBasicTests } from "@auth/adapter-test"
|
||||
import Database from "better-sqlite3"
|
||||
|
||||
globalThis.crypto ??= require("node:crypto").webcrypto
|
||||
|
||||
if (process.env.CI) {
|
||||
// TODO: Fix this
|
||||
test('Skipping D1Adapter tests in CI because of "Error: Must use import to load ES Module: next-auth/node_modules/.pnpm/undici@5.20.0/node_modules/undici/lib/llhttp/llhttp.wasm" errors. Should revisit', () => {
|
||||
expect(true).toBe(true)
|
||||
})
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
const sqliteDB = new Database(":memory:")
|
||||
let db = new D1Database(new D1DatabaseAPI(sqliteDB as any))
|
||||
let adapter = D1Adapter(db)
|
||||
|
||||
// put stuff here if we need some async init
|
||||
beforeAll(async () => await up(db))
|
||||
runBasicTests({
|
||||
adapter,
|
||||
db: {
|
||||
user: async (id) =>
|
||||
await getRecord<AdapterUser>(db, GET_USER_BY_ID_SQL, [id]),
|
||||
session: async (sessionToken) =>
|
||||
await getRecord<AdapterSession>(db, GET_SESSION_BY_TOKEN_SQL, [
|
||||
sessionToken,
|
||||
]),
|
||||
account: async ({ provider, providerAccountId }) =>
|
||||
await getRecord<AdapterAccount>(
|
||||
db,
|
||||
GET_ACCOUNT_BY_PROVIDER_AND_PROVIDER_ACCOUNT_ID_SQL,
|
||||
[provider, providerAccountId]
|
||||
),
|
||||
verificationToken: async ({ identifier, token }) =>
|
||||
await getRecord(db, GET_VERIFICATION_TOKEN_BY_IDENTIFIER_AND_TOKEN_SQL, [
|
||||
identifier,
|
||||
token,
|
||||
]),
|
||||
},
|
||||
})
|
||||
20
packages/adapter-d1/tsconfig.json
Normal file
20
packages/adapter-d1/tsconfig.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"extends": "@auth/tsconfig/tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"baseUrl": ".",
|
||||
"isolatedModules": true,
|
||||
"target": "ES2020",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "node",
|
||||
"outDir": ".",
|
||||
"rootDir": "src",
|
||||
"skipDefaultLibCheck": true,
|
||||
"strictNullChecks": true,
|
||||
"stripInternal": true,
|
||||
"declarationMap": true,
|
||||
"declaration": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["*.js", "*.d.ts"]
|
||||
}
|
||||
@@ -8,6 +8,9 @@
|
||||
"url": "https://github.com/nextauthjs/next-auth/issues"
|
||||
},
|
||||
"author": "Bruno Crosier",
|
||||
"contributors": [
|
||||
"Thang Huu Vu <hi@thvu.dev>"
|
||||
],
|
||||
"type": "module",
|
||||
"types": "./index.d.ts",
|
||||
"files": [
|
||||
@@ -37,9 +40,11 @@
|
||||
"build": "tsc",
|
||||
"test": "jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@auth/core": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"edgedb": "^1.0.1",
|
||||
"@auth/core": "^0.3.0"
|
||||
"edgedb": "^1.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@auth/adapter-test": "workspace:^0.0.0",
|
||||
@@ -52,4 +57,4 @@
|
||||
"jest": {
|
||||
"preset": "@auth/adapter-test/jest"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"Balázs Orbán <info@balazsorban.com>",
|
||||
"Nico Domino <yo@ndo.dev>",
|
||||
"Lluis Agusti <hi@llu.lu>",
|
||||
"Thang Huu Vu <thvu@hey.com>"
|
||||
"Thang Huu Vu <hi@thvu.dev>"
|
||||
],
|
||||
"main": "index.js",
|
||||
"module": "index.js",
|
||||
|
||||
1832
pnpm-lock.yaml
generated
1832
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user