mirror of
https://github.com/SrIzan10/next-auth.git
synced 2026-05-01 10:55:20 +00:00
Compare commits
34 Commits
@auth/svel
...
@auth/core
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a85de2c5f | ||
|
|
d47b56743e | ||
|
|
363440e515 | ||
|
|
60c5037ee1 | ||
|
|
97394baed1 | ||
|
|
f94abb8f70 | ||
|
|
bbfc11e74c | ||
|
|
2a70514df1 | ||
|
|
96d666465f | ||
|
|
ecbf0be22e | ||
|
|
87ec13bd00 | ||
|
|
c0f9af4c56 | ||
|
|
c7b36f45a3 | ||
|
|
68ff69f9eb | ||
|
|
23c0a393da | ||
|
|
f130f62a91 | ||
|
|
c111b436d2 | ||
|
|
ea895b8864 | ||
|
|
cfedc3b1a3 | ||
|
|
287a5fc05a | ||
|
|
f3ad659e91 | ||
|
|
48b9a0203e | ||
|
|
39fbccb783 | ||
|
|
f207e94146 | ||
|
|
b845729cdb | ||
|
|
e459d2d7e2 | ||
|
|
db1fd9007c | ||
|
|
0439fc5fc6 | ||
|
|
d0dd2ababc | ||
|
|
ba58d48dba | ||
|
|
a8d76ed440 | ||
|
|
3d7b8720db | ||
|
|
1e886b97bc | ||
|
|
ecb14ccecd |
16
.github/workflows/release.yml
vendored
16
.github/workflows/release.yml
vendored
@@ -56,6 +56,8 @@ on:
|
|||||||
- "adapter-upstash-redis"
|
- "adapter-upstash-redis"
|
||||||
- "adapter-xata"
|
- "adapter-xata"
|
||||||
- "next-auth"
|
- "next-auth"
|
||||||
|
env:
|
||||||
|
FORCE_COLOR: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
@@ -75,6 +77,11 @@ jobs:
|
|||||||
cache: "pnpm"
|
cache: "pnpm"
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install
|
run: pnpm install
|
||||||
|
- name: Build
|
||||||
|
run: pnpm build
|
||||||
|
env:
|
||||||
|
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||||
|
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: pnpm test
|
run: pnpm test
|
||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
@@ -82,7 +89,12 @@ jobs:
|
|||||||
UPSTASH_REDIS_URL: ${{ secrets.UPSTASH_REDIS_URL }}
|
UPSTASH_REDIS_URL: ${{ secrets.UPSTASH_REDIS_URL }}
|
||||||
UPSTASH_REDIS_KEY: ${{ secrets.UPSTASH_REDIS_KEY }}
|
UPSTASH_REDIS_KEY: ${{ secrets.UPSTASH_REDIS_KEY }}
|
||||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||||
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
|
||||||
|
- name: Upload Turbo artifacts
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: turbo-report
|
||||||
|
path: .turbo/runs/
|
||||||
# - name: Run E2E tests
|
# - name: Run E2E tests
|
||||||
# if: github.repository == 'nextauthjs/next-auth'
|
# if: github.repository == 'nextauthjs/next-auth'
|
||||||
# run: pnpm e2e
|
# run: pnpm e2e
|
||||||
@@ -91,7 +103,7 @@ jobs:
|
|||||||
# AUTH0_USERNAME: ${{ secrets.AUTH0_USERNAME }}
|
# AUTH0_USERNAME: ${{ secrets.AUTH0_USERNAME }}
|
||||||
# AUTH0_PASSWORD: ${{ secrets.AUTH0_PASSWORD }}
|
# AUTH0_PASSWORD: ${{ secrets.AUTH0_PASSWORD }}
|
||||||
# TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
# TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||||
# TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
# TURBO_TEAM: ${{ vars.TURBO_TEAM }}
|
||||||
# - name: Upload E2E artifacts
|
# - name: Upload E2E artifacts
|
||||||
# if: github.repository == 'nextauthjs/next-auth'
|
# if: github.repository == 'nextauthjs/next-auth'
|
||||||
# uses: actions/upload-artifact@v3
|
# uses: actions/upload-artifact@v3
|
||||||
|
|||||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -65,6 +65,7 @@ packages/adapter-prisma/prisma/dev.db
|
|||||||
packages/adapter-prisma/prisma/migrations
|
packages/adapter-prisma/prisma/migrations
|
||||||
db.sqlite
|
db.sqlite
|
||||||
packages/adapter-supabase/supabase/.branches
|
packages/adapter-supabase/supabase/.branches
|
||||||
|
packages/adapter-drizzle/.drizzle
|
||||||
|
|
||||||
# Tests
|
# Tests
|
||||||
coverage
|
coverage
|
||||||
@@ -97,5 +98,7 @@ packages/frameworks-sveltekit/vite.config.js.timestamp-*
|
|||||||
packages/frameworks-sveltekit/vite.config.ts.timestamp-*
|
packages/frameworks-sveltekit/vite.config.ts.timestamp-*
|
||||||
|
|
||||||
# Adapters
|
# Adapters
|
||||||
|
|
||||||
docs/docs/reference/adapter
|
docs/docs/reference/adapter
|
||||||
|
|
||||||
|
## Drizzle migration folder
|
||||||
|
.drizzle
|
||||||
@@ -3,4 +3,4 @@
|
|||||||
This folder contains a Next.js app using NextAuth.js for local development. See the following section on how to start:
|
This folder contains a Next.js app using NextAuth.js for local development. See the following section on how to start:
|
||||||
|
|
||||||
[Setting up local environment
|
[Setting up local environment
|
||||||
](https://github.com/nextauthjs/next-auth/blob/main/CONTRIBUTING.md#setting-up-local-environment)
|
](https://github.com/nextauthjs/.github/blob/main/CONTRIBUTING.md#setting-up-local-environment)
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
"@prisma/client": "^3",
|
"@prisma/client": "^3",
|
||||||
"@supabase/supabase-js": "^2.0.5",
|
"@supabase/supabase-js": "^2.0.5",
|
||||||
"faunadb": "^4",
|
"faunadb": "^4",
|
||||||
"next": "13.3.0",
|
"next": "13.4.0",
|
||||||
"next-auth": "workspace:*",
|
"next-auth": "workspace:*",
|
||||||
"nodemailer": "^6",
|
"nodemailer": "^6",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ import { Auth, type AuthConfig } from "@auth/core"
|
|||||||
|
|
||||||
// Providers
|
// Providers
|
||||||
import Apple from "@auth/core/providers/apple"
|
import Apple from "@auth/core/providers/apple"
|
||||||
import Asgardeo from "@auth/core/providers/asgardeo"
|
// import Asgardeo from "@auth/core/providers/asgardeo"
|
||||||
import Auth0 from "@auth/core/providers/auth0"
|
import Auth0 from "@auth/core/providers/auth0"
|
||||||
import AzureAD from "@auth/core/providers/azure-ad"
|
import AzureAD from "@auth/core/providers/azure-ad"
|
||||||
import AzureB2C from "@auth/core/providers/azure-ad-b2c"
|
import AzureB2C from "@auth/core/providers/azure-ad-b2c"
|
||||||
import BeyondIdentity from "@auth/core/providers/beyondidentity"
|
// import BeyondIdentity from "@auth/core/providers/beyondidentity"
|
||||||
import BoxyHQSAML from "@auth/core/providers/boxyhq-saml"
|
import BoxyHQSAML from "@auth/core/providers/boxyhq-saml"
|
||||||
// import Cognito from "@auth/core/providers/cognito"
|
// import Cognito from "@auth/core/providers/cognito"
|
||||||
import Credentials from "@auth/core/providers/credentials"
|
import Credentials from "@auth/core/providers/credentials"
|
||||||
@@ -86,8 +86,8 @@ export const authConfig: AuthConfig = {
|
|||||||
return { name: "Fill Murray", email: "bill@fillmurray.com", image: "https://www.fillmurray.com/64/64", id: "1", foo: "" }
|
return { name: "Fill Murray", email: "bill@fillmurray.com", image: "https://www.fillmurray.com/64/64", id: "1", foo: "" }
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
Apple({ clientId: process.env.APPLE_ID, clientSecret: process.env.APPLE_SECRET }),
|
Apple({ clientId: process.env.APPLE_ID, clientSecret: process.env.APPLE_SECRET as string }),
|
||||||
Asgardeo({ clientId: process.env.ASGARDEO_CLIENT_ID, clientSecret: process.env.ASGARDEO_CLIENT_SECRET, issuer: process.env.ASGARDEO_ISSUER }),
|
// Asgardeo({ clientId: process.env.ASGARDEO_CLIENT_ID, clientSecret: process.env.ASGARDEO_CLIENT_SECRET, issuer: process.env.ASGARDEO_ISSUER }),
|
||||||
Auth0({ clientId: process.env.AUTH0_ID, clientSecret: process.env.AUTH0_SECRET, issuer: process.env.AUTH0_ISSUER }),
|
Auth0({ clientId: process.env.AUTH0_ID, clientSecret: process.env.AUTH0_SECRET, issuer: process.env.AUTH0_ISSUER }),
|
||||||
AzureAD({
|
AzureAD({
|
||||||
clientId: process.env.AZURE_AD_CLIENT_ID,
|
clientId: process.env.AZURE_AD_CLIENT_ID,
|
||||||
@@ -95,11 +95,11 @@ export const authConfig: AuthConfig = {
|
|||||||
tenantId: process.env.AZURE_AD_TENANT_ID,
|
tenantId: process.env.AZURE_AD_TENANT_ID,
|
||||||
}),
|
}),
|
||||||
AzureB2C({ clientId: process.env.AZURE_B2C_ID, clientSecret: process.env.AZURE_B2C_SECRET, issuer: process.env.AZURE_B2C_ISSUER }),
|
AzureB2C({ clientId: process.env.AZURE_B2C_ID, clientSecret: process.env.AZURE_B2C_SECRET, issuer: process.env.AZURE_B2C_ISSUER }),
|
||||||
BeyondIdentity({
|
// BeyondIdentity({
|
||||||
clientId: process.env.BEYOND_IDENTITY_CLIENT_ID,
|
// clientId: process.env.BEYOND_IDENTITY_CLIENT_ID,
|
||||||
clientSecret: process.env.BEYOND_IDENTITY_CLIENT_SECRET,
|
// clientSecret: process.env.BEYOND_IDENTITY_CLIENT_SECRET,
|
||||||
issuer: process.env.BEYOND_IDENTITY_ISSUER,
|
// issuer: process.env.BEYOND_IDENTITY_ISSUER,
|
||||||
}),
|
// }),
|
||||||
BoxyHQSAML({ issuer: "https://jackson-demo.boxyhq.com", clientId: "tenant=boxyhq.com&product=saml-demo.boxyhq.com", clientSecret: "dummy" }),
|
BoxyHQSAML({ issuer: "https://jackson-demo.boxyhq.com", clientId: "tenant=boxyhq.com&product=saml-demo.boxyhq.com", clientSecret: "dummy" }),
|
||||||
// Cognito({ clientId: process.env.COGNITO_ID, clientSecret: process.env.COGNITO_SECRET, issuer: process.env.COGNITO_ISSUER }),
|
// Cognito({ clientId: process.env.COGNITO_ID, clientSecret: process.env.COGNITO_SECRET, issuer: process.env.COGNITO_ISSUER }),
|
||||||
Descope({ clientId: process.env.DESCOPE_ID, clientSecret: process.env.DESCOPE_SECRET }),
|
Descope({ clientId: process.env.DESCOPE_ID, clientSecret: process.env.DESCOPE_SECRET }),
|
||||||
@@ -108,7 +108,7 @@ export const authConfig: AuthConfig = {
|
|||||||
Facebook({ clientId: process.env.FACEBOOK_ID, clientSecret: process.env.FACEBOOK_SECRET }),
|
Facebook({ clientId: process.env.FACEBOOK_ID, clientSecret: process.env.FACEBOOK_SECRET }),
|
||||||
Foursquare({ clientId: process.env.FOURSQUARE_ID, clientSecret: process.env.FOURSQUARE_SECRET }),
|
Foursquare({ clientId: process.env.FOURSQUARE_ID, clientSecret: process.env.FOURSQUARE_SECRET }),
|
||||||
Freshbooks({ clientId: process.env.FRESHBOOKS_ID, clientSecret: process.env.FRESHBOOKS_SECRET }),
|
Freshbooks({ clientId: process.env.FRESHBOOKS_ID, clientSecret: process.env.FRESHBOOKS_SECRET }),
|
||||||
GitHub({ clientId: process.env.GITHUB_ID, clientSecret: process.env.GITHUB_SECRET, redirectProxy: process.env.AUTH_REDIRECT_PROXY_URL }),
|
GitHub({ clientId: process.env.GITHUB_ID, clientSecret: process.env.GITHUB_SECRET, redirectProxyUrl: process.env.AUTH_REDIRECT_PROXY_URL }),
|
||||||
Gitlab({ clientId: process.env.GITLAB_ID, clientSecret: process.env.GITLAB_SECRET }),
|
Gitlab({ clientId: process.env.GITLAB_ID, clientSecret: process.env.GITLAB_SECRET }),
|
||||||
Google({ clientId: process.env.GOOGLE_ID, clientSecret: process.env.GOOGLE_SECRET }),
|
Google({ clientId: process.env.GOOGLE_ID, clientSecret: process.env.GOOGLE_SECRET }),
|
||||||
// IDS4({ clientId: process.env.IDS4_ID, clientSecret: process.env.IDS4_SECRET, issuer: process.env.IDS4_ISSUER }),
|
// IDS4({ clientId: process.env.IDS4_ID, clientSecret: process.env.IDS4_SECRET, issuer: process.env.IDS4_ISSUER }),
|
||||||
@@ -117,7 +117,7 @@ export const authConfig: AuthConfig = {
|
|||||||
Line({ clientId: process.env.LINE_ID, clientSecret: process.env.LINE_SECRET }),
|
Line({ clientId: process.env.LINE_ID, clientSecret: process.env.LINE_SECRET }),
|
||||||
LinkedIn({ clientId: process.env.LINKEDIN_ID, clientSecret: process.env.LINKEDIN_SECRET }),
|
LinkedIn({ clientId: process.env.LINKEDIN_ID, clientSecret: process.env.LINKEDIN_SECRET }),
|
||||||
Mailchimp({ clientId: process.env.MAILCHIMP_ID, clientSecret: process.env.MAILCHIMP_SECRET }),
|
Mailchimp({ clientId: process.env.MAILCHIMP_ID, clientSecret: process.env.MAILCHIMP_SECRET }),
|
||||||
Notion({ clientId: process.env.NOTION_ID, clientSecret: process.env.NOTION_SECRET, redirectUri: process.env.NOTION_REDIRECT_URI }),
|
Notion({ clientId: process.env.NOTION_ID, clientSecret: process.env.NOTION_SECRET, redirectUri: process.env.NOTION_REDIRECT_URI as string }),
|
||||||
// Okta({ clientId: process.env.OKTA_ID, clientSecret: process.env.OKTA_SECRET, issuer: process.env.OKTA_ISSUER }),
|
// Okta({ clientId: process.env.OKTA_ID, clientSecret: process.env.OKTA_SECRET, issuer: process.env.OKTA_ISSUER }),
|
||||||
Osu({ clientId: process.env.OSU_CLIENT_ID, clientSecret: process.env.OSU_CLIENT_SECRET }),
|
Osu({ clientId: process.env.OSU_CLIENT_ID, clientSecret: process.env.OSU_CLIENT_SECRET }),
|
||||||
Patreon({ clientId: process.env.PATREON_ID, clientSecret: process.env.PATREON_SECRET }),
|
Patreon({ clientId: process.env.PATREON_ID, clientSecret: process.env.PATREON_SECRET }),
|
||||||
@@ -162,4 +162,4 @@ function AuthHandler(...args: any[]) {
|
|||||||
|
|
||||||
export default AuthHandler(authConfig)
|
export default AuthHandler(authConfig)
|
||||||
|
|
||||||
export const config = { runtime: "experimental-edge" }
|
export const config = { runtime: "edge" }
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
"svelte": "3.55.0",
|
"svelte": "3.55.0",
|
||||||
"svelte-check": "2.10.2",
|
"svelte-check": "2.10.2",
|
||||||
"typescript": "4.9.4",
|
"typescript": "4.9.4",
|
||||||
"vite": "4.0.1"
|
"vite": "4.0.5"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@auth/core": "workspace:*",
|
"@auth/core": "workspace:*",
|
||||||
|
|||||||
@@ -37,22 +37,31 @@ This documentation site is based on the [Docusaurus](https://docusaurus.io) fram
|
|||||||
|
|
||||||
To start a local environment of this project, please do the following.
|
To start a local environment of this project, please do the following.
|
||||||
|
|
||||||
1. Clone the repository.
|
1. Clone the repo:
|
||||||
|
|
||||||
```bash
|
```sh
|
||||||
$ git clone https://github.com/nextauthjs/docs.git
|
git clone git@github.com:nextauthjs/next-auth.git
|
||||||
|
cd next-auth
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Install dependencies
|
2. Set up the correct pnpm version, using [Corepack](https://nodejs.org/api/corepack.html). Run the following in the project'a root:
|
||||||
|
|
||||||
```bash
|
```sh
|
||||||
$ npm install
|
corepack enable pnpm
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Start the development server
|
(Now, if you run `pnpm --version`, it should print the same verion as the `packageManager` property in the [`package.json` file](https://github.com/nextauthjs/next-auth/blob/main/package.json))
|
||||||
|
|
||||||
|
3. Install packages. Developing requires Node.js v18:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pnpm install
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Start the development server
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ npm start
|
$ pnpm dev:docs
|
||||||
```
|
```
|
||||||
|
|
||||||
And thats all! Now you should have a local copy of this docs site running at [localhost:3000](http://localhost:3000)!
|
And thats all! Now you should have a local copy of this docs site running at [localhost:3000](http://localhost:3000)!
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ npm install -D nodemailer
|
|||||||
|
|
||||||
## 2. Setting up a SMTP service
|
## 2. Setting up a SMTP service
|
||||||
|
|
||||||
Next we need a [SMTP service](https://sendgrid.com/blog/what-is-an-smtp-server/) which will be in charge of sending emails from our application. There's a number of services available for this, however [here are the ones](http://nodemailer.com/smtp/well-known/) known to work with `nodemailer`.
|
Next we need a [SMTP service](https://sendgrid.com/blog/what-is-an-smtp-server/) which will be in charge of sending emails from our application. There's a number of services available for this, however [here are the ones](https://community.nodemailer.com/2-0-0-beta/setup-smtp/well-known-services) known to work with `nodemailer`.
|
||||||
|
|
||||||
:::info
|
:::info
|
||||||
For this tutorial, we're going to be using [Sendgrid](https://sendgrid.com/), but any of the services linked above should work the same
|
For this tutorial, we're going to be using [Sendgrid](https://sendgrid.com/), but any of the services linked above should work the same
|
||||||
|
|||||||
@@ -100,11 +100,12 @@ NextAuth.js provides [`useSession()`](/reference/react/#usesession) - a [React H
|
|||||||
|
|
||||||
```ts title="pages/_app.tsx"
|
```ts title="pages/_app.tsx"
|
||||||
import { SessionProvider } from "next-auth/react"
|
import { SessionProvider } from "next-auth/react"
|
||||||
|
import type { AppProps } from 'next/app'
|
||||||
|
|
||||||
export default function App({
|
export default function App({
|
||||||
Component,
|
Component,
|
||||||
pageProps: { session, ...pageProps },
|
pageProps: { session, ...pageProps },
|
||||||
}) {
|
}: AppProps) {
|
||||||
return (
|
return (
|
||||||
<SessionProvider session={session}>
|
<SessionProvider session={session}>
|
||||||
<Component {...pageProps} />
|
<Component {...pageProps} />
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
title: Using a database adapter
|
title: Using a database adapter
|
||||||
---
|
---
|
||||||
|
|
||||||
An **Adapter** in Auth.js connects your application to whatever database or backend system you want to use to store data for users, their accounts, sessions, etc. Adapters are optional, unless you need to persist user information in your own database, or you want to implement certain flows. The [Email Provider](/getting-started/email-tutorial) requires an adapter to be able to save [Verification Tokens](/reference/adapters/models#verification-token).
|
An **Adapter** in Auth.js connects your application to whatever database or backend system you want to use to store data for users, their accounts, sessions, etc. Adapters are optional, unless you need to persist user information in your own database, or you want to implement certain flows. The [Email Provider](/getting-started/email-tutorial) requires an adapter to be able to save [Verification Tokens](/reference/adapters#verification-token).
|
||||||
|
|
||||||
:::tip
|
:::tip
|
||||||
When using a database, you can still use JWT for session handling for fast access. See the [`session.strategy`](/reference/configuration/auth-config#session) option. Read about the trade-offs of JWT in the [FAQ](/concepts/faq#json-web-tokens).
|
When using a database, you can still use JWT for session handling for fast access. See the [`session.strategy`](/reference/configuration/auth-config#session) option. Read about the trade-offs of JWT in the [FAQ](/concepts/faq#json-web-tokens).
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ You can override any of the options to suit your own use case.
|
|||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
1. Auth.js does not include `nodemailer` as a dependency, so you'll need to install it yourself if you want to use the Email Provider. Run `npm install nodemailer` or `yarn add nodemailer`.
|
1. Auth.js does not include `nodemailer` as a dependency, so you'll need to install it yourself if you want to use the Email Provider. Run `npm install nodemailer` or `yarn add nodemailer`.
|
||||||
2. You will need an SMTP account; ideally for one of the [services known to work with `nodemailer`](https://community.nodemailer.com/2-0-0-beta/setup-smtp/well-known-services/).
|
2. You will need an SMTP account; such as [the official Nodemailer recommended service](https://nodemailer.com/about/#example) of [Forward Email](https://forwardemail.net).
|
||||||
3. There are two ways to configure the SMTP server connection.
|
3. There are two ways to configure the SMTP server connection.
|
||||||
|
|
||||||
You can either use a connection string or a `nodemailer` configuration object.
|
You can either use a connection string or a `nodemailer` configuration object.
|
||||||
@@ -40,8 +40,8 @@ You can either use a connection string or a `nodemailer` configuration object.
|
|||||||
Create an `.env` file to the root of your project and add the connection string and email address.
|
Create an `.env` file to the root of your project and add the connection string and email address.
|
||||||
|
|
||||||
```js title=".env" {1}
|
```js title=".env" {1}
|
||||||
EMAIL_SERVER=smtp://username:password@smtp.example.com:587
|
EMAIL_SERVER=smtp://username:password@smtp.forwardemail.net:587
|
||||||
EMAIL_FROM=noreply@example.com
|
EMAIL_FROM=support@example.com
|
||||||
```
|
```
|
||||||
|
|
||||||
Now you can add the email provider like this:
|
Now you can add the email provider like this:
|
||||||
@@ -64,7 +64,7 @@ In your `.env` file in the root of your project simply add the configuration obj
|
|||||||
```js title=".env"
|
```js title=".env"
|
||||||
EMAIL_SERVER_USER=username
|
EMAIL_SERVER_USER=username
|
||||||
EMAIL_SERVER_PASSWORD=password
|
EMAIL_SERVER_PASSWORD=password
|
||||||
EMAIL_SERVER_HOST=smtp.example.com
|
EMAIL_SERVER_HOST=smtp.forwardemail.net
|
||||||
EMAIL_SERVER_PORT=587
|
EMAIL_SERVER_PORT=587
|
||||||
EMAIL_FROM=noreply@example.com
|
EMAIL_FROM=noreply@example.com
|
||||||
```
|
```
|
||||||
@@ -112,6 +112,7 @@ providers: [
|
|||||||
identifier: email,
|
identifier: email,
|
||||||
url,
|
url,
|
||||||
provider: { server, from },
|
provider: { server, from },
|
||||||
|
request // for example can be used to get the user agent (`request.headers.get("user-agent")`) to parse and pass on to the user in the email so they can be more confident they originated the request
|
||||||
}) {
|
}) {
|
||||||
/* your function */
|
/* your function */
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -8,6 +8,10 @@ Using an Auth.js / NextAuth.js adapter you can connect to any database service o
|
|||||||
<a href="/reference/adapter/dgraph" class="adapter-card">
|
<a href="/reference/adapter/dgraph" class="adapter-card">
|
||||||
<img src="/img/adapters/dgraph.png" width="30" />
|
<img src="/img/adapters/dgraph.png" width="30" />
|
||||||
<h4 class="adapter-card__title">Dgraph Adapter</h4>
|
<h4 class="adapter-card__title">Dgraph Adapter</h4>
|
||||||
|
</a>
|
||||||
|
<a href="/reference/adapter/drizzle" class="adapter-card">
|
||||||
|
<img src="/img/adapters/drizzle-orm.png" width="30" />
|
||||||
|
<h4 class="adapter-card__title">Drizzle Adapter</h4>
|
||||||
</a>
|
</a>
|
||||||
<a href="/reference/adapter/dynamodb" class="adapter-card">
|
<a href="/reference/adapter/dynamodb" class="adapter-card">
|
||||||
<img src="/img/adapters/dynamodb.png" width="30" />
|
<img src="/img/adapters/dynamodb.png" width="30" />
|
||||||
@@ -67,10 +71,8 @@ Using an Auth.js / NextAuth.js adapter you can connect to any database service o
|
|||||||
If you don't find an adapter for the database or service you use, you can always create one yourself. Have a look at our guide on [how to create a database adapter](/guides/adapters/creating-a-database-adapter).
|
If you don't find an adapter for the database or service you use, you can always create one yourself. Have a look at our guide on [how to create a database adapter](/guides/adapters/creating-a-database-adapter).
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
|
||||||
## Models
|
## Models
|
||||||
|
|
||||||
|
|
||||||
Auth.js can be used with any database. Models tell you what structures Auth.js expects from your database. Models will vary slightly depending on which adapter you use, but in general, will look something like this:
|
Auth.js can be used with any database. Models tell you what structures Auth.js expects from your database. Models will vary slightly depending on which adapter you use, but in general, will look something like this:
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
|
|||||||
@@ -265,6 +265,7 @@ const docusaurusConfig = {
|
|||||||
? []
|
? []
|
||||||
: [
|
: [
|
||||||
typedocAdapter("Dgraph"),
|
typedocAdapter("Dgraph"),
|
||||||
|
typedocAdapter("Drizzle"),
|
||||||
typedocAdapter("DynamoDB"),
|
typedocAdapter("DynamoDB"),
|
||||||
typedocAdapter("Fauna"),
|
typedocAdapter("Fauna"),
|
||||||
typedocAdapter("Firebase"),
|
typedocAdapter("Firebase"),
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ module.exports = {
|
|||||||
link: { type: "doc", id: "reference/adapters/index" },
|
link: { type: "doc", id: "reference/adapters/index" },
|
||||||
items: [
|
items: [
|
||||||
{ type: "doc", id: "reference/adapter/dgraph/index" },
|
{ type: "doc", id: "reference/adapter/dgraph/index" },
|
||||||
|
{ type: "doc", id: "reference/adapter/drizzle/index" },
|
||||||
{ type: "doc", id: "reference/adapter/dynamodb/index" },
|
{ type: "doc", id: "reference/adapter/dynamodb/index" },
|
||||||
{ type: "doc", id: "reference/adapter/fauna/index" },
|
{ type: "doc", id: "reference/adapter/fauna/index" },
|
||||||
{ type: "doc", id: "reference/adapter/firebase/index" },
|
{ type: "doc", id: "reference/adapter/firebase/index" },
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ html[data-theme="dark"] .navbar__item.navbar__link[href*="npm"]:before {
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
color: #000;
|
color: #000;
|
||||||
top: -10px;
|
top: -10px;
|
||||||
right: -45px;
|
right: 4px;
|
||||||
font-size: 9px;
|
font-size: 9px;
|
||||||
background-color: #ccc;
|
background-color: #ccc;
|
||||||
padding: 2px 5px;
|
padding: 2px 5px;
|
||||||
|
|||||||
@@ -101,13 +101,13 @@ export default function Home() {
|
|||||||
.fetch("https://api.github.com/repos/nextauthjs/next-auth")
|
.fetch("https://api.github.com/repos/nextauthjs/next-auth")
|
||||||
.then((res) => res.json())
|
.then((res) => res.json())
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
const navLinks = document.getElementsByClassName(
|
const githubLink = document.querySelector(
|
||||||
"navbar__item navbar__link"
|
".navbar__item.navbar__link[href*='github']"
|
||||||
)
|
)
|
||||||
const githubStat = document.createElement("span")
|
const githubStat = document.createElement("span")
|
||||||
githubStat.innerHTML = kFormatter(data.stargazers_count)
|
githubStat.innerHTML = kFormatter(data.stargazers_count)
|
||||||
githubStat.className = "github-counter"
|
githubStat.className = "github-counter"
|
||||||
navLinks[4].appendChild(githubStat)
|
githubLink.appendChild(githubStat)
|
||||||
})
|
})
|
||||||
}, [])
|
}, [])
|
||||||
return (
|
return (
|
||||||
|
|||||||
BIN
docs/static/img/adapters/drizzle-orm.png
vendored
Normal file
BIN
docs/static/img/adapters/drizzle-orm.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 94 KiB |
@@ -7,7 +7,7 @@
|
|||||||
"build:app": "turbo run build --filter=next-auth-app",
|
"build:app": "turbo run build --filter=next-auth-app",
|
||||||
"build:docs": "turbo run build --filter=docs",
|
"build:docs": "turbo run build --filter=docs",
|
||||||
"build": "turbo run build --filter=next-auth --filter=@next-auth/* --filter=@auth/* --no-deps",
|
"build": "turbo run build --filter=next-auth --filter=@next-auth/* --filter=@auth/* --no-deps",
|
||||||
"test": "turbo run test --concurrency=1 --filter=[HEAD^1] --filter=./packages/* --filter=!@*upstash* --filter=!*dynamodb-*",
|
"test": "turbo run test --concurrency=1 --filter=[HEAD^1] --filter=./packages/* --filter=!@*upstash* --filter=!*dynamodb-* --filter=!*app*",
|
||||||
"clean": "turbo run clean --no-cache",
|
"clean": "turbo run clean --no-cache",
|
||||||
"dev:db": "turbo run dev --parallel --continue --filter=next-auth-app...",
|
"dev:db": "turbo run dev --parallel --continue --filter=next-auth-app...",
|
||||||
"dev": "turbo run dev --parallel --continue --filter=next-auth-app... --filter=!./packages/adapter-*",
|
"dev": "turbo run dev --parallel --continue --filter=next-auth-app... --filter=!./packages/adapter-*",
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
"eslint-plugin-svelte3": "^4.0.0",
|
"eslint-plugin-svelte3": "^4.0.0",
|
||||||
"prettier": "2.8.1",
|
"prettier": "2.8.1",
|
||||||
"prettier-plugin-svelte": "^2.8.1",
|
"prettier-plugin-svelte": "^2.8.1",
|
||||||
"turbo": "1.10.1",
|
"turbo": "^1.10.3",
|
||||||
"typescript": "4.9.4"
|
"typescript": "4.9.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|||||||
1
packages/adapter-drizzle/.npmrc
Normal file
1
packages/adapter-drizzle/.npmrc
Normal file
@@ -0,0 +1 @@
|
|||||||
|
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
|
||||||
28
packages/adapter-drizzle/README.md
Normal file
28
packages/adapter-drizzle/README.md
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<p align="center">
|
||||||
|
<br/>
|
||||||
|
<a href="https://authjs.dev" target="_blank">
|
||||||
|
<img height="64px" src="https://authjs.dev/img/logo/logo-sm.png" />
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/drizzle-team/drizzle-orm" target="_blank">
|
||||||
|
<img height="64px" src="https://pbs.twimg.com/profile_images/1598308842391179266/CtXrfLnk_400x400.jpg"/>
|
||||||
|
</a>
|
||||||
|
<h3 align="center"><b>Drizzle ORM Adapter</b> - NextAuth.js / Auth.js</a></h3>
|
||||||
|
<p align="center" style="align: center;">
|
||||||
|
<a href="https://npm.im/@auth/drizzle-adapter">
|
||||||
|
<img src="https://img.shields.io/badge/TypeScript-blue?style=flat-square" alt="TypeScript" />
|
||||||
|
</a>
|
||||||
|
<a href="https://npm.im/@auth/drizzle-adapter">
|
||||||
|
<img alt="npm" src="https://img.shields.io/npm/v/@auth/drizzle-adapter?color=green&label=@auth/drizzle-adapter&style=flat-square">
|
||||||
|
</a>
|
||||||
|
<a href="https://www.npmtrends.com/@auth/drizzle-adapter">
|
||||||
|
<img src="https://img.shields.io/npm/dm/@auth/drizzle-adapter?label=%20downloads&style=flat-square" alt="Downloads" />
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/nextauthjs/next-auth/stargazers">
|
||||||
|
<img src="https://img.shields.io/github/stars/nextauthjs/next-auth?style=flat-square" alt="Github Stars" />
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Check out the documentation at [authjs.dev](https://authjs.dev/reference/adapter/drizzle).
|
||||||
65
packages/adapter-drizzle/package.json
Normal file
65
packages/adapter-drizzle/package.json
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
{
|
||||||
|
"name": "@auth/drizzle-adapter",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "Drizzle adapter for Auth.js.",
|
||||||
|
"homepage": "https://authjs.dev",
|
||||||
|
"repository": "https://github.com/nextauthjs/next-auth",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/nextauthjs/next-auth/issues"
|
||||||
|
},
|
||||||
|
"author": "Anthony Shew",
|
||||||
|
"type": "module",
|
||||||
|
"types": "./index.d.ts",
|
||||||
|
"files": [
|
||||||
|
"*.js",
|
||||||
|
"*.d.ts*",
|
||||||
|
"lib",
|
||||||
|
"src"
|
||||||
|
],
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./index.d.ts",
|
||||||
|
"import": "./index.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"license": "ISC",
|
||||||
|
"keywords": [
|
||||||
|
"next-auth",
|
||||||
|
"@auth",
|
||||||
|
"Auth.js",
|
||||||
|
"next.js",
|
||||||
|
"oauth",
|
||||||
|
"drizzle"
|
||||||
|
],
|
||||||
|
"private": false,
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"clean": "find . -type d -name \".drizzle\" | xargs rm -rf",
|
||||||
|
"test": "pnpm test:mysql && pnpm test:sqlite && pnpm test:pg",
|
||||||
|
"test:mysql": "pnpm clean && ./tests/mysql/test.sh",
|
||||||
|
"test:sqlite": "pnpm clean && ./tests/sqlite/test.sh",
|
||||||
|
"test:pg": "pnpm clean && ./tests/pg/test.sh",
|
||||||
|
"build": "tsc",
|
||||||
|
"dev": "drizzle-kit generate:mysql --schema=src/schema.ts --out=.drizzle && tsc -w"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@auth/core": "workspace:*"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@next-auth/adapter-test": "workspace:*",
|
||||||
|
"@next-auth/tsconfig": "workspace:*",
|
||||||
|
"@types/better-sqlite3": "^7.6.4",
|
||||||
|
"@types/uuid": "^8.3.3",
|
||||||
|
"better-sqlite3": "^8.4.0",
|
||||||
|
"drizzle-kit": "^0.19.5",
|
||||||
|
"drizzle-orm": "^0.27.0",
|
||||||
|
"jest": "^27.4.3",
|
||||||
|
"mysql2": "^3.2.0",
|
||||||
|
"postgres": "^3.3.4"
|
||||||
|
},
|
||||||
|
"jest": {
|
||||||
|
"preset": "@next-auth/adapter-test/jest"
|
||||||
|
}
|
||||||
|
}
|
||||||
268
packages/adapter-drizzle/src/index.ts
Normal file
268
packages/adapter-drizzle/src/index.ts
Normal file
@@ -0,0 +1,268 @@
|
|||||||
|
/**
|
||||||
|
* <div style={{display: "flex", justifyContent: "space-between", alignItems: "center", padding: 16}}>
|
||||||
|
* <p style={{fontWeight: "normal"}}>Official <a href="https://orm.drizzle.team">Drizzle ORM</a> adapter for Auth.js / NextAuth.js.</p>
|
||||||
|
* <a href="https://orm.drizzle.team">
|
||||||
|
* <img style={{display: "block"}} src="/img/adapters/drizzle-orm.png" width="38" />
|
||||||
|
* </a>
|
||||||
|
* </div>
|
||||||
|
*
|
||||||
|
* ## Installation
|
||||||
|
*
|
||||||
|
* ```bash npm2yarn2pnpm
|
||||||
|
* npm install drizzle-orm @auth/drizzle-adapter
|
||||||
|
* npm install drizzle-kit --save-dev
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @module @auth/drizzle-adapter
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { mySqlDrizzleAdapter } from "./lib/mysql.js"
|
||||||
|
import { pgDrizzleAdapter } from "./lib/pg.js"
|
||||||
|
import { SQLiteDrizzleAdapter } from "./lib/sqlite.js"
|
||||||
|
import {
|
||||||
|
isMySqlDatabase,
|
||||||
|
isPgDatabase,
|
||||||
|
isSQLiteDatabase,
|
||||||
|
SqlFlavorOptions,
|
||||||
|
} from "./lib/utils.js"
|
||||||
|
|
||||||
|
import type { Adapter } from "@auth/core/adapters"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the adapter to your `app/api/[...nextauth]/route.js` next-auth configuration object.
|
||||||
|
*
|
||||||
|
* ```ts title="pages/api/auth/[...nextauth].ts"
|
||||||
|
* import NextAuth from "next-auth"
|
||||||
|
* import GoogleProvider from "next-auth/providers/google"
|
||||||
|
* import { DrizzleAdapter } from "@auth/drizzle-adapter"
|
||||||
|
* import { db } from "./schema"
|
||||||
|
*
|
||||||
|
* export default NextAuth({
|
||||||
|
* adapter: DrizzleAdapter(db),
|
||||||
|
* providers: [
|
||||||
|
* GoogleProvider({
|
||||||
|
* clientId: process.env.GOOGLE_CLIENT_ID,
|
||||||
|
* clientSecret: process.env.GOOGLE_CLIENT_SECRET,
|
||||||
|
* }),
|
||||||
|
* ],
|
||||||
|
* })
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* ## Setup
|
||||||
|
*
|
||||||
|
* First, create a schema that includes [the minimum requirements for a `next-auth` adapter](/reference/adapters#models). You can select your favorite SQL flavor below and copy it.
|
||||||
|
* Additionally, you may extend the schema from the minimum requirements to suit your needs.
|
||||||
|
*
|
||||||
|
* - [Postgres](#postgres)
|
||||||
|
* - [MySQL](#mysql)
|
||||||
|
* - [SQLite](#sqlite)
|
||||||
|
*
|
||||||
|
* ### Postgres
|
||||||
|
|
||||||
|
* ```ts title="schema.ts"
|
||||||
|
* import {
|
||||||
|
* timestamp,
|
||||||
|
* pgTable,
|
||||||
|
* text,
|
||||||
|
* primaryKey,
|
||||||
|
* integer
|
||||||
|
* } from "drizzle-orm/pg-core"
|
||||||
|
* import type { AdapterAccount } from '@auth/core/adapters'
|
||||||
|
*
|
||||||
|
* export const users = pgTable("users", {
|
||||||
|
* id: text("id").notNull().primaryKey(),
|
||||||
|
* name: text("name"),
|
||||||
|
* email: text("email").notNull(),
|
||||||
|
* emailVerified: timestamp("emailVerified", { mode: "date" }),
|
||||||
|
* image: text("image"),
|
||||||
|
* })
|
||||||
|
*
|
||||||
|
* export const accounts = pgTable(
|
||||||
|
* "accounts",
|
||||||
|
* {
|
||||||
|
* userId: text("userId")
|
||||||
|
* .notNull()
|
||||||
|
* .references(() => users.id, { onDelete: "cascade" }),
|
||||||
|
* type: text("type").$type<AdapterAccount["type"]>().notNull(),
|
||||||
|
* provider: text("provider").notNull(),
|
||||||
|
* providerAccountId: text("providerAccountId").notNull(),
|
||||||
|
* refresh_token: text("refresh_token"),
|
||||||
|
* access_token: text("access_token"),
|
||||||
|
* expires_at: integer("expires_at"),
|
||||||
|
* token_type: text("token_type"),
|
||||||
|
* scope: text("scope"),
|
||||||
|
* id_token: text("id_token"),
|
||||||
|
* session_state: text("session_state"),
|
||||||
|
* },
|
||||||
|
* (account) => ({
|
||||||
|
* compoundKey: primaryKey(account.provider, account.providerAccountId),
|
||||||
|
* })
|
||||||
|
* )
|
||||||
|
*
|
||||||
|
* export const sessions = pgTable("sessions", {
|
||||||
|
* sessionToken: text("sessionToken").notNull().primaryKey(),
|
||||||
|
* userId: text("userId")
|
||||||
|
* .notNull()
|
||||||
|
* .references(() => users.id, { onDelete: "cascade" }),
|
||||||
|
* expires: timestamp("expires", { mode: "date" }).notNull(),
|
||||||
|
* })
|
||||||
|
*
|
||||||
|
* export const verificationTokens = pgTable(
|
||||||
|
* "verificationToken",
|
||||||
|
* {
|
||||||
|
* identifier: text("identifier").notNull(),
|
||||||
|
* token: text("token").notNull(),
|
||||||
|
* expires: timestamp("expires", { mode: "date" }).notNull(),
|
||||||
|
* },
|
||||||
|
* (vt) => ({
|
||||||
|
* compoundKey: primaryKey(vt.identifier, vt.token),
|
||||||
|
* })
|
||||||
|
* )
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* ### MySQL
|
||||||
|
*
|
||||||
|
* ```ts title="schema.ts"
|
||||||
|
* import {
|
||||||
|
* int,
|
||||||
|
* timestamp,
|
||||||
|
* mysqlTable,
|
||||||
|
* primaryKey,
|
||||||
|
* varchar,
|
||||||
|
* } from "drizzle-orm/mysql-core"
|
||||||
|
* import type { AdapterAccount } from "@auth/core/adapters"
|
||||||
|
*
|
||||||
|
* export const users = mysqlTable("users", {
|
||||||
|
* id: varchar("id", { length: 255 }).notNull().primaryKey(),
|
||||||
|
* name: varchar("name", { length: 255 }),
|
||||||
|
* email: varchar("email", { length: 255 }).notNull(),
|
||||||
|
* emailVerified: timestamp("emailVerified", { mode: "date", fsp: 3 }).defaultNow(),
|
||||||
|
* image: varchar("image", { length: 255 }),
|
||||||
|
* })
|
||||||
|
*
|
||||||
|
* export const accounts = mysqlTable(
|
||||||
|
* "accounts",
|
||||||
|
* {
|
||||||
|
* userId: varchar("userId", { length: 255 })
|
||||||
|
* .notNull()
|
||||||
|
* .references(() => users.id, { onDelete: "cascade" }),
|
||||||
|
* type: varchar("type", { length: 255 }).$type<AdapterAccount["type"]>().notNull(),
|
||||||
|
* provider: varchar("provider", { length: 255 }).notNull(),
|
||||||
|
* providerAccountId: varchar("providerAccountId", { length: 255 }).notNull(),
|
||||||
|
* refresh_token: varchar("refresh_token", { length: 255 }),
|
||||||
|
* access_token: varchar("access_token", { length: 255 }),
|
||||||
|
* expires_at: int("expires_at"),
|
||||||
|
* token_type: varchar("token_type", { length: 255 }),
|
||||||
|
* scope: varchar("scope", { length: 255 }),
|
||||||
|
* id_token: varchar("id_token", { length: 255 }),
|
||||||
|
* session_state: varchar("session_state", { length: 255 }),
|
||||||
|
* },
|
||||||
|
* (account) => ({
|
||||||
|
* compoundKey: primaryKey(account.provider, account.providerAccountId),
|
||||||
|
* })
|
||||||
|
* )
|
||||||
|
*
|
||||||
|
* export const sessions = mysqlTable("sessions", {
|
||||||
|
* sessionToken: varchar("sessionToken", { length: 255 }).notNull().primaryKey(),
|
||||||
|
* userId: varchar("userId", { length: 255 })
|
||||||
|
* .notNull()
|
||||||
|
* .references(() => users.id, { onDelete: "cascade" }),
|
||||||
|
* expires: timestamp("expires", { mode: "date" }).notNull(),
|
||||||
|
* })
|
||||||
|
*
|
||||||
|
* export const verificationTokens = mysqlTable(
|
||||||
|
* "verificationToken",
|
||||||
|
* {
|
||||||
|
* identifier: varchar("identifier", { length: 255 }).notNull(),
|
||||||
|
* token: varchar("token", { length: 255 }).notNull(),
|
||||||
|
* expires: timestamp("expires", { mode: "date" }).notNull(),
|
||||||
|
* },
|
||||||
|
* (vt) => ({
|
||||||
|
* compoundKey: primaryKey(vt.identifier, vt.token),
|
||||||
|
* })
|
||||||
|
* )
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* ### SQLite
|
||||||
|
*
|
||||||
|
* ```ts title="schema.ts"
|
||||||
|
* import { integer, sqliteTable, text, primaryKey } from "drizzle-orm/sqlite-core"
|
||||||
|
* import type { AdapterAccount } from "@auth/core/adapters"
|
||||||
|
*
|
||||||
|
* export const users = sqliteTable("users", {
|
||||||
|
* id: text("id").notNull().primaryKey(),
|
||||||
|
* name: text("name"),
|
||||||
|
* email: text("email").notNull(),
|
||||||
|
* emailVerified: integer("emailVerified", { mode: "timestamp_ms" }),
|
||||||
|
* image: text("image"),
|
||||||
|
* })
|
||||||
|
*
|
||||||
|
* export const accounts = sqliteTable(
|
||||||
|
* "accounts",
|
||||||
|
* {
|
||||||
|
* userId: text("userId")
|
||||||
|
* .notNull()
|
||||||
|
* .references(() => users.id, { onDelete: "cascade" }),
|
||||||
|
* type: text("type").$type<AdapterAccount["type"]>().notNull(),
|
||||||
|
* provider: text("provider").notNull(),
|
||||||
|
* providerAccountId: text("providerAccountId").notNull(),
|
||||||
|
* refresh_token: text("refresh_token"),
|
||||||
|
* access_token: text("access_token"),
|
||||||
|
* expires_at: integer("expires_at"),
|
||||||
|
* token_type: text("token_type"),
|
||||||
|
* scope: text("scope"),
|
||||||
|
* id_token: text("id_token"),
|
||||||
|
* session_state: text("session_state"),
|
||||||
|
* },
|
||||||
|
* (account) => ({
|
||||||
|
* compoundKey: primaryKey(account.provider, account.providerAccountId),
|
||||||
|
* })
|
||||||
|
* )
|
||||||
|
*
|
||||||
|
* export const sessions = sqliteTable("sessions", {
|
||||||
|
* sessionToken: text("sessionToken").notNull().primaryKey(),
|
||||||
|
* userId: text("userId")
|
||||||
|
* .notNull()
|
||||||
|
* .references(() => users.id, { onDelete: "cascade" }),
|
||||||
|
* expires: integer("expires", { mode: "timestamp_ms" }).notNull(),
|
||||||
|
* })
|
||||||
|
*
|
||||||
|
* export const verificationTokens = sqliteTable(
|
||||||
|
* "verificationToken",
|
||||||
|
* {
|
||||||
|
* identifier: text("identifier").notNull(),
|
||||||
|
* token: text("token").notNull(),
|
||||||
|
* expires: integer("expires", { mode: "timestamp_ms" }).notNull(),
|
||||||
|
* },
|
||||||
|
* (vt) => ({
|
||||||
|
* compoundKey: primaryKey(vt.identifier, vt.token),
|
||||||
|
* })
|
||||||
|
* )
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* ## Migrating your database
|
||||||
|
* With your schema now described in your code, you'll need to migrate your database to your schema.
|
||||||
|
*
|
||||||
|
* For full documentation on how to run migrations with Drizzle, [visit the Drizzle documentation](https://orm.drizzle.team/kit-docs/overview#running-migrations).
|
||||||
|
*
|
||||||
|
* ---
|
||||||
|
*
|
||||||
|
**/
|
||||||
|
export function DrizzleAdapter<SqlFlavor extends SqlFlavorOptions>(
|
||||||
|
db: SqlFlavor
|
||||||
|
): Adapter {
|
||||||
|
if (isMySqlDatabase(db)) {
|
||||||
|
// We need to cast to unknown since the type overlaps (PScale is MySQL based)
|
||||||
|
return mySqlDrizzleAdapter(db)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPgDatabase(db)) {
|
||||||
|
return pgDrizzleAdapter(db)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSQLiteDatabase(db)) {
|
||||||
|
return SQLiteDrizzleAdapter(db)
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error("Unsupported database type in Auth.js Drizzle adapter.")
|
||||||
|
}
|
||||||
255
packages/adapter-drizzle/src/lib/mysql.ts
Normal file
255
packages/adapter-drizzle/src/lib/mysql.ts
Normal file
@@ -0,0 +1,255 @@
|
|||||||
|
import { and, eq } from "drizzle-orm"
|
||||||
|
import {
|
||||||
|
int,
|
||||||
|
timestamp,
|
||||||
|
mysqlTable,
|
||||||
|
primaryKey,
|
||||||
|
varchar,
|
||||||
|
} from "drizzle-orm/mysql-core"
|
||||||
|
|
||||||
|
import type { Adapter, AdapterAccount } from "@auth/core/adapters"
|
||||||
|
import type { MySql2Database } from "drizzle-orm/mysql2"
|
||||||
|
|
||||||
|
export const users = mysqlTable("users", {
|
||||||
|
id: varchar("id", { length: 255 }).notNull().primaryKey(),
|
||||||
|
name: varchar("name", { length: 255 }),
|
||||||
|
email: varchar("email", { length: 255 }).notNull(),
|
||||||
|
emailVerified: timestamp("emailVerified", {
|
||||||
|
mode: "date",
|
||||||
|
fsp: 3,
|
||||||
|
}).defaultNow(),
|
||||||
|
image: varchar("image", { length: 255 }),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const accounts = mysqlTable(
|
||||||
|
"accounts",
|
||||||
|
{
|
||||||
|
userId: varchar("userId", { length: 255 })
|
||||||
|
.notNull()
|
||||||
|
.references(() => users.id, { onDelete: "cascade" }),
|
||||||
|
type: varchar("type", { length: 255 })
|
||||||
|
.$type<AdapterAccount["type"]>()
|
||||||
|
.notNull(),
|
||||||
|
provider: varchar("provider", { length: 255 }).notNull(),
|
||||||
|
providerAccountId: varchar("providerAccountId", { length: 255 }).notNull(),
|
||||||
|
refresh_token: varchar("refresh_token", { length: 255 }),
|
||||||
|
access_token: varchar("access_token", { length: 255 }),
|
||||||
|
expires_at: int("expires_at"),
|
||||||
|
token_type: varchar("token_type", { length: 255 }),
|
||||||
|
scope: varchar("scope", { length: 255 }),
|
||||||
|
id_token: varchar("id_token", { length: 255 }),
|
||||||
|
session_state: varchar("session_state", { length: 255 }),
|
||||||
|
},
|
||||||
|
(account) => ({
|
||||||
|
compoundKey: primaryKey(account.provider, account.providerAccountId),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
export const sessions = mysqlTable("sessions", {
|
||||||
|
sessionToken: varchar("sessionToken", { length: 255 }).notNull().primaryKey(),
|
||||||
|
userId: varchar("userId", { length: 255 })
|
||||||
|
.notNull()
|
||||||
|
.references(() => users.id, { onDelete: "cascade" }),
|
||||||
|
expires: timestamp("expires", { mode: "date" }).notNull(),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const verificationTokens = mysqlTable(
|
||||||
|
"verificationToken",
|
||||||
|
{
|
||||||
|
identifier: varchar("identifier", { length: 255 }).notNull(),
|
||||||
|
token: varchar("token", { length: 255 }).notNull(),
|
||||||
|
expires: timestamp("expires", { mode: "date" }).notNull(),
|
||||||
|
},
|
||||||
|
(vt) => ({
|
||||||
|
compoundKey: primaryKey(vt.identifier, vt.token),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
export const schema = { users, accounts, sessions, verificationTokens }
|
||||||
|
export type DefaultSchema = typeof schema
|
||||||
|
|
||||||
|
export function mySqlDrizzleAdapter(
|
||||||
|
client: MySql2Database<Record<string, never>>
|
||||||
|
): Adapter {
|
||||||
|
return {
|
||||||
|
async createUser(data) {
|
||||||
|
const id = crypto.randomUUID()
|
||||||
|
|
||||||
|
await client.insert(users).values({ ...data, id })
|
||||||
|
|
||||||
|
return await client
|
||||||
|
.select()
|
||||||
|
.from(users)
|
||||||
|
.where(eq(users.id, id))
|
||||||
|
.then((res) => res[0])
|
||||||
|
},
|
||||||
|
async getUser(data) {
|
||||||
|
const thing =
|
||||||
|
(await client
|
||||||
|
.select()
|
||||||
|
.from(users)
|
||||||
|
.where(eq(users.id, data))
|
||||||
|
.then((res) => res[0])) ?? null
|
||||||
|
|
||||||
|
return thing
|
||||||
|
},
|
||||||
|
async getUserByEmail(data) {
|
||||||
|
const user =
|
||||||
|
(await client
|
||||||
|
.select()
|
||||||
|
.from(users)
|
||||||
|
.where(eq(users.email, data))
|
||||||
|
.then((res) => res[0])) ?? null
|
||||||
|
|
||||||
|
return user
|
||||||
|
},
|
||||||
|
async createSession(data) {
|
||||||
|
await client.insert(sessions).values(data)
|
||||||
|
|
||||||
|
return await client
|
||||||
|
.select()
|
||||||
|
.from(sessions)
|
||||||
|
.where(eq(sessions.sessionToken, data.sessionToken))
|
||||||
|
.then((res) => res[0])
|
||||||
|
},
|
||||||
|
async getSessionAndUser(data) {
|
||||||
|
const sessionAndUser =
|
||||||
|
(await client
|
||||||
|
.select({
|
||||||
|
session: sessions,
|
||||||
|
user: users,
|
||||||
|
})
|
||||||
|
.from(sessions)
|
||||||
|
.where(eq(sessions.sessionToken, data))
|
||||||
|
.innerJoin(users, eq(users.id, sessions.userId))
|
||||||
|
.then((res) => res[0])) ?? null
|
||||||
|
|
||||||
|
return sessionAndUser
|
||||||
|
},
|
||||||
|
async updateUser(data) {
|
||||||
|
if (!data.id) {
|
||||||
|
throw new Error("No user id.")
|
||||||
|
}
|
||||||
|
|
||||||
|
await client.update(users).set(data).where(eq(users.id, data.id))
|
||||||
|
|
||||||
|
return await client
|
||||||
|
.select()
|
||||||
|
.from(users)
|
||||||
|
.where(eq(users.id, data.id))
|
||||||
|
.then((res) => res[0])
|
||||||
|
},
|
||||||
|
async updateSession(data) {
|
||||||
|
await client
|
||||||
|
.update(sessions)
|
||||||
|
.set(data)
|
||||||
|
.where(eq(sessions.sessionToken, data.sessionToken))
|
||||||
|
|
||||||
|
return await client
|
||||||
|
.select()
|
||||||
|
.from(sessions)
|
||||||
|
.where(eq(sessions.sessionToken, data.sessionToken))
|
||||||
|
.then((res) => res[0])
|
||||||
|
},
|
||||||
|
async linkAccount(rawAccount) {
|
||||||
|
await client
|
||||||
|
.insert(accounts)
|
||||||
|
.values(rawAccount)
|
||||||
|
.then((res) => res[0])
|
||||||
|
},
|
||||||
|
async getUserByAccount(account) {
|
||||||
|
const dbAccount =
|
||||||
|
(await client
|
||||||
|
.select()
|
||||||
|
.from(accounts)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(accounts.providerAccountId, account.providerAccountId),
|
||||||
|
eq(accounts.provider, account.provider)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.leftJoin(users, eq(accounts.userId, users.id))
|
||||||
|
.then((res) => res[0])) ?? null
|
||||||
|
|
||||||
|
if (!dbAccount) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return dbAccount.users
|
||||||
|
},
|
||||||
|
async deleteSession(sessionToken) {
|
||||||
|
const session =
|
||||||
|
(await client
|
||||||
|
.select()
|
||||||
|
.from(sessions)
|
||||||
|
.where(eq(sessions.sessionToken, sessionToken))
|
||||||
|
.then((res) => res[0])) ?? null
|
||||||
|
|
||||||
|
await client
|
||||||
|
.delete(sessions)
|
||||||
|
.where(eq(sessions.sessionToken, sessionToken))
|
||||||
|
|
||||||
|
return session
|
||||||
|
},
|
||||||
|
async createVerificationToken(token) {
|
||||||
|
await client.insert(verificationTokens).values(token)
|
||||||
|
|
||||||
|
return await client
|
||||||
|
.select()
|
||||||
|
.from(verificationTokens)
|
||||||
|
.where(eq(verificationTokens.identifier, token.identifier))
|
||||||
|
.then((res) => res[0])
|
||||||
|
},
|
||||||
|
async useVerificationToken(token) {
|
||||||
|
try {
|
||||||
|
const deletedToken =
|
||||||
|
(await client
|
||||||
|
.select()
|
||||||
|
.from(verificationTokens)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(verificationTokens.identifier, token.identifier),
|
||||||
|
eq(verificationTokens.token, token.token)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.then((res) => res[0])) ?? null
|
||||||
|
|
||||||
|
await client
|
||||||
|
.delete(verificationTokens)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(verificationTokens.identifier, token.identifier),
|
||||||
|
eq(verificationTokens.token, token.token)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return deletedToken
|
||||||
|
} catch (err) {
|
||||||
|
throw new Error("No verification token found.")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async deleteUser(id) {
|
||||||
|
const user = await client
|
||||||
|
.select()
|
||||||
|
.from(users)
|
||||||
|
.where(eq(users.id, id))
|
||||||
|
.then((res) => res[0] ?? null)
|
||||||
|
|
||||||
|
await client.delete(users).where(eq(users.id, id))
|
||||||
|
|
||||||
|
return user
|
||||||
|
},
|
||||||
|
async unlinkAccount(account) {
|
||||||
|
await client
|
||||||
|
.delete(accounts)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(accounts.providerAccountId, account.providerAccountId),
|
||||||
|
eq(accounts.provider, account.provider)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return undefined
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
225
packages/adapter-drizzle/src/lib/pg.ts
Normal file
225
packages/adapter-drizzle/src/lib/pg.ts
Normal file
@@ -0,0 +1,225 @@
|
|||||||
|
import { and, eq } from "drizzle-orm"
|
||||||
|
import {
|
||||||
|
timestamp,
|
||||||
|
pgTable,
|
||||||
|
text,
|
||||||
|
primaryKey,
|
||||||
|
integer,
|
||||||
|
} from "drizzle-orm/pg-core"
|
||||||
|
|
||||||
|
import type { PostgresJsDatabase } from "drizzle-orm/postgres-js"
|
||||||
|
import type { Adapter, AdapterAccount } from "@auth/core/adapters"
|
||||||
|
|
||||||
|
export const users = pgTable("users", {
|
||||||
|
id: text("id").notNull().primaryKey(),
|
||||||
|
name: text("name"),
|
||||||
|
email: text("email").notNull(),
|
||||||
|
emailVerified: timestamp("emailVerified", { mode: "date" }),
|
||||||
|
image: text("image"),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const accounts = pgTable(
|
||||||
|
"accounts",
|
||||||
|
{
|
||||||
|
userId: text("userId")
|
||||||
|
.notNull()
|
||||||
|
.references(() => users.id, { onDelete: "cascade" }),
|
||||||
|
type: text("type").$type<AdapterAccount["type"]>().notNull(),
|
||||||
|
provider: text("provider").notNull(),
|
||||||
|
providerAccountId: text("providerAccountId").notNull(),
|
||||||
|
refresh_token: text("refresh_token"),
|
||||||
|
access_token: text("access_token"),
|
||||||
|
expires_at: integer("expires_at"),
|
||||||
|
token_type: text("token_type"),
|
||||||
|
scope: text("scope"),
|
||||||
|
id_token: text("id_token"),
|
||||||
|
session_state: text("session_state"),
|
||||||
|
},
|
||||||
|
(account) => ({
|
||||||
|
compoundKey: primaryKey(account.provider, account.providerAccountId),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
export const sessions = pgTable("sessions", {
|
||||||
|
sessionToken: text("sessionToken").notNull().primaryKey(),
|
||||||
|
userId: text("userId")
|
||||||
|
.notNull()
|
||||||
|
.references(() => users.id, { onDelete: "cascade" }),
|
||||||
|
expires: timestamp("expires", { mode: "date" }).notNull(),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const verificationTokens = pgTable(
|
||||||
|
"verificationToken",
|
||||||
|
{
|
||||||
|
identifier: text("identifier").notNull(),
|
||||||
|
token: text("token").notNull(),
|
||||||
|
expires: timestamp("expires", { mode: "date" }).notNull(),
|
||||||
|
},
|
||||||
|
(vt) => ({
|
||||||
|
compoundKey: primaryKey(vt.identifier, vt.token),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
export const schema = { users, accounts, sessions, verificationTokens }
|
||||||
|
export type DefaultSchema = typeof schema
|
||||||
|
|
||||||
|
export function pgDrizzleAdapter(
|
||||||
|
client: PostgresJsDatabase<Record<string, never>>
|
||||||
|
): Adapter {
|
||||||
|
return {
|
||||||
|
async createUser(data) {
|
||||||
|
return await client
|
||||||
|
.insert(users)
|
||||||
|
.values({ ...data, id: crypto.randomUUID() })
|
||||||
|
.returning()
|
||||||
|
.then((res) => res[0] ?? null)
|
||||||
|
},
|
||||||
|
async getUser(data) {
|
||||||
|
return await client
|
||||||
|
.select()
|
||||||
|
.from(users)
|
||||||
|
.where(eq(users.id, data))
|
||||||
|
.then((res) => res[0] ?? null)
|
||||||
|
},
|
||||||
|
async getUserByEmail(data) {
|
||||||
|
return await client
|
||||||
|
.select()
|
||||||
|
.from(users)
|
||||||
|
.where(eq(users.email, data))
|
||||||
|
.then((res) => res[0] ?? null)
|
||||||
|
},
|
||||||
|
async createSession(data) {
|
||||||
|
return await client
|
||||||
|
.insert(sessions)
|
||||||
|
.values(data)
|
||||||
|
.returning()
|
||||||
|
.then((res) => res[0])
|
||||||
|
},
|
||||||
|
async getSessionAndUser(data) {
|
||||||
|
return await client
|
||||||
|
.select({
|
||||||
|
session: sessions,
|
||||||
|
user: users,
|
||||||
|
})
|
||||||
|
.from(sessions)
|
||||||
|
.where(eq(sessions.sessionToken, data))
|
||||||
|
.innerJoin(users, eq(users.id, sessions.userId))
|
||||||
|
.then((res) => res[0] ?? null)
|
||||||
|
},
|
||||||
|
async updateUser(data) {
|
||||||
|
if (!data.id) {
|
||||||
|
throw new Error("No user id.")
|
||||||
|
}
|
||||||
|
|
||||||
|
return await client
|
||||||
|
.update(users)
|
||||||
|
.set(data)
|
||||||
|
.where(eq(users.id, data.id))
|
||||||
|
.returning()
|
||||||
|
.then((res) => res[0])
|
||||||
|
},
|
||||||
|
async updateSession(data) {
|
||||||
|
return await client
|
||||||
|
.update(sessions)
|
||||||
|
.set(data)
|
||||||
|
.where(eq(sessions.sessionToken, data.sessionToken))
|
||||||
|
.returning()
|
||||||
|
.then((res) => res[0])
|
||||||
|
},
|
||||||
|
async linkAccount(rawAccount) {
|
||||||
|
const updatedAccount = await client
|
||||||
|
.insert(accounts)
|
||||||
|
.values(rawAccount)
|
||||||
|
.returning()
|
||||||
|
.then((res) => res[0])
|
||||||
|
|
||||||
|
// Drizzle will return `null` for fields that are not defined.
|
||||||
|
// However, the return type is expecting `undefined`.
|
||||||
|
const account = {
|
||||||
|
...updatedAccount,
|
||||||
|
access_token: updatedAccount.access_token ?? undefined,
|
||||||
|
token_type: updatedAccount.token_type ?? undefined,
|
||||||
|
id_token: updatedAccount.id_token ?? undefined,
|
||||||
|
refresh_token: updatedAccount.refresh_token ?? undefined,
|
||||||
|
scope: updatedAccount.scope ?? undefined,
|
||||||
|
expires_at: updatedAccount.expires_at ?? undefined,
|
||||||
|
session_state: updatedAccount.session_state ?? undefined,
|
||||||
|
}
|
||||||
|
|
||||||
|
return account
|
||||||
|
},
|
||||||
|
async getUserByAccount(account) {
|
||||||
|
const dbAccount =
|
||||||
|
(await client
|
||||||
|
.select()
|
||||||
|
.from(accounts)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(accounts.providerAccountId, account.providerAccountId),
|
||||||
|
eq(accounts.provider, account.provider)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.leftJoin(users, eq(accounts.userId, users.id))
|
||||||
|
.then((res) => res[0])) ?? null
|
||||||
|
|
||||||
|
if (!dbAccount) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return dbAccount.users
|
||||||
|
},
|
||||||
|
async deleteSession(sessionToken) {
|
||||||
|
const session = await client
|
||||||
|
.delete(sessions)
|
||||||
|
.where(eq(sessions.sessionToken, sessionToken))
|
||||||
|
.returning()
|
||||||
|
.then((res) => res[0] ?? null)
|
||||||
|
|
||||||
|
return session
|
||||||
|
},
|
||||||
|
async createVerificationToken(token) {
|
||||||
|
return await client
|
||||||
|
.insert(verificationTokens)
|
||||||
|
.values(token)
|
||||||
|
.returning()
|
||||||
|
.then((res) => res[0])
|
||||||
|
},
|
||||||
|
async useVerificationToken(token) {
|
||||||
|
try {
|
||||||
|
return await client
|
||||||
|
.delete(verificationTokens)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(verificationTokens.identifier, token.identifier),
|
||||||
|
eq(verificationTokens.token, token.token)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.returning()
|
||||||
|
.then((res) => res[0] ?? null)
|
||||||
|
} catch (err) {
|
||||||
|
throw new Error("No verification token found.")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async deleteUser(id) {
|
||||||
|
await client
|
||||||
|
.delete(users)
|
||||||
|
.where(eq(users.id, id))
|
||||||
|
.returning()
|
||||||
|
.then((res) => res[0] ?? null)
|
||||||
|
},
|
||||||
|
async unlinkAccount(account) {
|
||||||
|
const { type, provider, providerAccountId, userId } = await client
|
||||||
|
.delete(accounts)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(accounts.providerAccountId, account.providerAccountId),
|
||||||
|
eq(accounts.provider, account.provider)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.returning()
|
||||||
|
.then((res) => res[0] ?? null)
|
||||||
|
|
||||||
|
return { provider, type, providerAccountId, userId }
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
203
packages/adapter-drizzle/src/lib/sqlite.ts
Normal file
203
packages/adapter-drizzle/src/lib/sqlite.ts
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
import { eq, and } from "drizzle-orm"
|
||||||
|
import {
|
||||||
|
integer,
|
||||||
|
sqliteTable,
|
||||||
|
text,
|
||||||
|
primaryKey,
|
||||||
|
BaseSQLiteDatabase,
|
||||||
|
} from "drizzle-orm/sqlite-core"
|
||||||
|
|
||||||
|
import type { Adapter, AdapterAccount } from "@auth/core/adapters"
|
||||||
|
|
||||||
|
export const users = sqliteTable("users", {
|
||||||
|
id: text("id").notNull().primaryKey(),
|
||||||
|
name: text("name"),
|
||||||
|
email: text("email").notNull(),
|
||||||
|
emailVerified: integer("emailVerified", { mode: "timestamp_ms" }),
|
||||||
|
image: text("image"),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const accounts = sqliteTable(
|
||||||
|
"accounts",
|
||||||
|
{
|
||||||
|
userId: text("userId")
|
||||||
|
.notNull()
|
||||||
|
.references(() => users.id, { onDelete: "cascade" }),
|
||||||
|
type: text("type").$type<AdapterAccount["type"]>().notNull(),
|
||||||
|
provider: text("provider").notNull(),
|
||||||
|
providerAccountId: text("providerAccountId").notNull(),
|
||||||
|
refresh_token: text("refresh_token"),
|
||||||
|
access_token: text("access_token"),
|
||||||
|
expires_at: integer("expires_at"),
|
||||||
|
token_type: text("token_type"),
|
||||||
|
scope: text("scope"),
|
||||||
|
id_token: text("id_token"),
|
||||||
|
session_state: text("session_state"),
|
||||||
|
},
|
||||||
|
(account) => ({
|
||||||
|
compoundKey: primaryKey(account.provider, account.providerAccountId),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
export const sessions = sqliteTable("sessions", {
|
||||||
|
sessionToken: text("sessionToken").notNull().primaryKey(),
|
||||||
|
userId: text("userId")
|
||||||
|
.notNull()
|
||||||
|
.references(() => users.id, { onDelete: "cascade" }),
|
||||||
|
expires: integer("expires", { mode: "timestamp_ms" }).notNull(),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const verificationTokens = sqliteTable(
|
||||||
|
"verificationToken",
|
||||||
|
{
|
||||||
|
identifier: text("identifier").notNull(),
|
||||||
|
token: text("token").notNull(),
|
||||||
|
expires: integer("expires", { mode: "timestamp_ms" }).notNull(),
|
||||||
|
},
|
||||||
|
(vt) => ({
|
||||||
|
compoundKey: primaryKey(vt.identifier, vt.token),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
export const schema = { users, accounts, sessions, verificationTokens }
|
||||||
|
export type DefaultSchema = typeof schema
|
||||||
|
|
||||||
|
export function SQLiteDrizzleAdapter(
|
||||||
|
client: BaseSQLiteDatabase<any, any>
|
||||||
|
): Adapter {
|
||||||
|
return {
|
||||||
|
createUser(data) {
|
||||||
|
return client
|
||||||
|
.insert(users)
|
||||||
|
.values({ ...data, id: crypto.randomUUID() })
|
||||||
|
.returning()
|
||||||
|
.get()
|
||||||
|
},
|
||||||
|
getUser(data) {
|
||||||
|
return client.select().from(users).where(eq(users.id, data)).get() ?? null
|
||||||
|
},
|
||||||
|
getUserByEmail(data) {
|
||||||
|
return (
|
||||||
|
client.select().from(users).where(eq(users.email, data)).get() ?? null
|
||||||
|
)
|
||||||
|
},
|
||||||
|
createSession(data) {
|
||||||
|
return client.insert(sessions).values(data).returning().get()
|
||||||
|
},
|
||||||
|
getSessionAndUser(data) {
|
||||||
|
return (
|
||||||
|
client
|
||||||
|
.select({
|
||||||
|
session: sessions,
|
||||||
|
user: users,
|
||||||
|
})
|
||||||
|
.from(sessions)
|
||||||
|
.where(eq(sessions.sessionToken, data))
|
||||||
|
.innerJoin(users, eq(users.id, sessions.userId))
|
||||||
|
.get() ?? null
|
||||||
|
)
|
||||||
|
},
|
||||||
|
updateUser(data) {
|
||||||
|
if (!data.id) {
|
||||||
|
throw new Error("No user id.")
|
||||||
|
}
|
||||||
|
|
||||||
|
return client
|
||||||
|
.update(users)
|
||||||
|
.set(data)
|
||||||
|
.where(eq(users.id, data.id))
|
||||||
|
.returning()
|
||||||
|
.get()
|
||||||
|
},
|
||||||
|
updateSession(data) {
|
||||||
|
return client
|
||||||
|
.update(sessions)
|
||||||
|
.set(data)
|
||||||
|
.where(eq(sessions.sessionToken, data.sessionToken))
|
||||||
|
.returning()
|
||||||
|
.get()
|
||||||
|
},
|
||||||
|
linkAccount(rawAccount) {
|
||||||
|
const updatedAccount = client
|
||||||
|
.insert(accounts)
|
||||||
|
.values(rawAccount)
|
||||||
|
.returning()
|
||||||
|
.get()
|
||||||
|
|
||||||
|
const account: AdapterAccount = {
|
||||||
|
...updatedAccount,
|
||||||
|
type: updatedAccount.type,
|
||||||
|
access_token: updatedAccount.access_token ?? undefined,
|
||||||
|
token_type: updatedAccount.token_type ?? undefined,
|
||||||
|
id_token: updatedAccount.id_token ?? undefined,
|
||||||
|
refresh_token: updatedAccount.refresh_token ?? undefined,
|
||||||
|
scope: updatedAccount.scope ?? undefined,
|
||||||
|
expires_at: updatedAccount.expires_at ?? undefined,
|
||||||
|
session_state: updatedAccount.session_state ?? undefined,
|
||||||
|
}
|
||||||
|
|
||||||
|
return account
|
||||||
|
},
|
||||||
|
getUserByAccount(account) {
|
||||||
|
const results = client
|
||||||
|
.select()
|
||||||
|
.from(accounts)
|
||||||
|
.leftJoin(users, eq(users.id, accounts.userId))
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(accounts.provider, account.provider),
|
||||||
|
eq(accounts.providerAccountId, account.providerAccountId)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.get()
|
||||||
|
|
||||||
|
return results?.users ?? null
|
||||||
|
},
|
||||||
|
deleteSession(sessionToken) {
|
||||||
|
return (
|
||||||
|
client
|
||||||
|
.delete(sessions)
|
||||||
|
.where(eq(sessions.sessionToken, sessionToken))
|
||||||
|
.returning()
|
||||||
|
.get() ?? null
|
||||||
|
)
|
||||||
|
},
|
||||||
|
createVerificationToken(token) {
|
||||||
|
return client.insert(verificationTokens).values(token).returning().get()
|
||||||
|
},
|
||||||
|
useVerificationToken(token) {
|
||||||
|
try {
|
||||||
|
return (
|
||||||
|
client
|
||||||
|
.delete(verificationTokens)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(verificationTokens.identifier, token.identifier),
|
||||||
|
eq(verificationTokens.token, token.token)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.returning()
|
||||||
|
.get() ?? null
|
||||||
|
)
|
||||||
|
} catch (err) {
|
||||||
|
throw new Error("No verification token found.")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deleteUser(id) {
|
||||||
|
return client.delete(users).where(eq(users.id, id)).returning().get()
|
||||||
|
},
|
||||||
|
unlinkAccount(account) {
|
||||||
|
client
|
||||||
|
.delete(accounts)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(accounts.providerAccountId, account.providerAccountId),
|
||||||
|
eq(accounts.provider, account.provider)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.run()
|
||||||
|
|
||||||
|
return undefined
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
47
packages/adapter-drizzle/src/lib/utils.ts
Normal file
47
packages/adapter-drizzle/src/lib/utils.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import { MySqlDatabase } from "drizzle-orm/mysql-core"
|
||||||
|
import { PgDatabase } from "drizzle-orm/pg-core"
|
||||||
|
import { BaseSQLiteDatabase } from "drizzle-orm/sqlite-core"
|
||||||
|
|
||||||
|
import type { AnyMySqlTable } from "drizzle-orm/mysql-core"
|
||||||
|
import type { AnyPgTable } from "drizzle-orm/pg-core"
|
||||||
|
import type { AnySQLiteTable } from "drizzle-orm/sqlite-core"
|
||||||
|
import type { DefaultSchema as PgSchema } from "./pg.js"
|
||||||
|
import type { DefaultSchema as MySqlSchema } from "./mysql.js"
|
||||||
|
import type { DefaultSchema as SQLiteSchema } from "./sqlite.js"
|
||||||
|
|
||||||
|
export type AnyMySqlDatabase = MySqlDatabase<any, any>
|
||||||
|
export type AnyPgDatabase = PgDatabase<any, any, any>
|
||||||
|
export type AnySQLiteDatabase = BaseSQLiteDatabase<any, any, any, any>
|
||||||
|
|
||||||
|
export interface MinimumSchema {
|
||||||
|
mysql: MySqlSchema & Record<string, AnyMySqlTable>
|
||||||
|
pg: PgSchema & Record<string, AnyPgTable>
|
||||||
|
sqlite: SQLiteSchema & Record<string, AnySQLiteTable>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SqlFlavorOptions =
|
||||||
|
| AnyMySqlDatabase
|
||||||
|
| AnyPgDatabase
|
||||||
|
| AnySQLiteDatabase
|
||||||
|
|
||||||
|
export type ClientFlavors<Flavor> = Flavor extends AnyMySqlDatabase
|
||||||
|
? MinimumSchema["mysql"]
|
||||||
|
: Flavor extends AnyPgDatabase
|
||||||
|
? MinimumSchema["pg"]
|
||||||
|
: Flavor extends AnySQLiteDatabase
|
||||||
|
? MinimumSchema["sqlite"]
|
||||||
|
: never
|
||||||
|
|
||||||
|
export function isMySqlDatabase(
|
||||||
|
db: any
|
||||||
|
): db is MySqlDatabase<any, any, any, any> {
|
||||||
|
return db instanceof MySqlDatabase
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isPgDatabase(db: any): db is PgDatabase<any, any, any> {
|
||||||
|
return db instanceof PgDatabase
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isSQLiteDatabase(db: any): db is AnySQLiteDatabase {
|
||||||
|
return db instanceof BaseSQLiteDatabase
|
||||||
|
}
|
||||||
43
packages/adapter-drizzle/tests/fixtures.ts
Normal file
43
packages/adapter-drizzle/tests/fixtures.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
// This work is needed as workaround to Drizzle truncating millisecond precision.
|
||||||
|
// https://github.com/drizzle-team/drizzle-orm/pull/668
|
||||||
|
|
||||||
|
import { randomUUID } from "../../adapter-test"
|
||||||
|
|
||||||
|
const emailVerified = new Date()
|
||||||
|
emailVerified.setMilliseconds(0)
|
||||||
|
|
||||||
|
const ONE_WEEK_FROM_NOW = new Date(Date.now() + 1000 * 60 * 60 * 24 * 7)
|
||||||
|
ONE_WEEK_FROM_NOW.setMilliseconds(0)
|
||||||
|
const FIFTEEN_MINUTES_FROM_NOW = new Date(Date.now() + 15 * 60 * 1000)
|
||||||
|
FIFTEEN_MINUTES_FROM_NOW.setMilliseconds(0)
|
||||||
|
|
||||||
|
const ONE_MONTH = 1000 * 60 * 60 * 24 * 30
|
||||||
|
const ONE_MONTH_FROM_NOW = new Date(Date.now() + ONE_MONTH)
|
||||||
|
ONE_MONTH_FROM_NOW.setMilliseconds(0)
|
||||||
|
|
||||||
|
export const fixtures = {
|
||||||
|
user: {
|
||||||
|
email: "fill@murray.com",
|
||||||
|
image: "https://www.fillmurray.com/460/300",
|
||||||
|
name: "Fill Murray",
|
||||||
|
emailVerified,
|
||||||
|
},
|
||||||
|
session: {
|
||||||
|
sessionToken: randomUUID(),
|
||||||
|
expires: ONE_WEEK_FROM_NOW,
|
||||||
|
},
|
||||||
|
sessionUpdateExpires: ONE_MONTH_FROM_NOW,
|
||||||
|
verificationTokenExpires: FIFTEEN_MINUTES_FROM_NOW,
|
||||||
|
account: {
|
||||||
|
provider: "github",
|
||||||
|
providerAccountId: randomUUID(),
|
||||||
|
type: "oauth",
|
||||||
|
access_token: randomUUID(),
|
||||||
|
expires_at: ONE_MONTH / 1000,
|
||||||
|
id_token: randomUUID(),
|
||||||
|
refresh_token: randomUUID(),
|
||||||
|
token_type: "bearer",
|
||||||
|
scope: "user",
|
||||||
|
session_state: randomUUID(),
|
||||||
|
},
|
||||||
|
}
|
||||||
13
packages/adapter-drizzle/tests/mysql/drizzle.config.ts
Normal file
13
packages/adapter-drizzle/tests/mysql/drizzle.config.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import type { Config } from "drizzle-kit"
|
||||||
|
|
||||||
|
export default {
|
||||||
|
schema: "./tests/mysql/schema.ts",
|
||||||
|
out: "./tests/mysql/.drizzle",
|
||||||
|
driver: "mysql2",
|
||||||
|
dbCredentials: {
|
||||||
|
host: "localhost",
|
||||||
|
user: "root",
|
||||||
|
password: "password",
|
||||||
|
database: "next-auth",
|
||||||
|
},
|
||||||
|
} satisfies Config
|
||||||
71
packages/adapter-drizzle/tests/mysql/index.test.ts
Normal file
71
packages/adapter-drizzle/tests/mysql/index.test.ts
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import { runBasicTests } from "../../../adapter-test"
|
||||||
|
import { DrizzleAdapter } from "../../src"
|
||||||
|
import { db, sessions, verificationTokens, accounts, users } from "./schema"
|
||||||
|
import { eq, and } from "drizzle-orm"
|
||||||
|
import { fixtures } from "../fixtures"
|
||||||
|
|
||||||
|
globalThis.crypto ??= require("node:crypto").webcrypto
|
||||||
|
|
||||||
|
runBasicTests({
|
||||||
|
adapter: DrizzleAdapter(db),
|
||||||
|
fixtures,
|
||||||
|
db: {
|
||||||
|
connect: async () => {
|
||||||
|
await Promise.all([
|
||||||
|
db.delete(sessions),
|
||||||
|
db.delete(accounts),
|
||||||
|
db.delete(verificationTokens),
|
||||||
|
db.delete(users),
|
||||||
|
])
|
||||||
|
},
|
||||||
|
disconnect: async () => {
|
||||||
|
await Promise.all([
|
||||||
|
db.delete(sessions),
|
||||||
|
db.delete(accounts),
|
||||||
|
db.delete(verificationTokens),
|
||||||
|
db.delete(users),
|
||||||
|
])
|
||||||
|
},
|
||||||
|
user: async (id) => {
|
||||||
|
const user = await db
|
||||||
|
.select()
|
||||||
|
.from(users)
|
||||||
|
.where(eq(users.id, id))
|
||||||
|
.then((res) => res[0] ?? null)
|
||||||
|
return user
|
||||||
|
},
|
||||||
|
session: async (sessionToken) => {
|
||||||
|
const session = await db
|
||||||
|
.select()
|
||||||
|
.from(sessions)
|
||||||
|
.where(eq(sessions.sessionToken, sessionToken))
|
||||||
|
.then((res) => res[0] ?? null)
|
||||||
|
|
||||||
|
return session
|
||||||
|
},
|
||||||
|
account: (provider_providerAccountId) => {
|
||||||
|
const account = db
|
||||||
|
.select()
|
||||||
|
.from(accounts)
|
||||||
|
.where(
|
||||||
|
eq(
|
||||||
|
accounts.providerAccountId,
|
||||||
|
provider_providerAccountId.providerAccountId
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.then((res) => res[0] ?? null)
|
||||||
|
return account
|
||||||
|
},
|
||||||
|
verificationToken: (identifier_token) =>
|
||||||
|
db
|
||||||
|
.select()
|
||||||
|
.from(verificationTokens)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(verificationTokens.token, identifier_token.token),
|
||||||
|
eq(verificationTokens.identifier, identifier_token.identifier)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.then((res) => res[0]) ?? null,
|
||||||
|
},
|
||||||
|
})
|
||||||
29
packages/adapter-drizzle/tests/mysql/schema.ts
Normal file
29
packages/adapter-drizzle/tests/mysql/schema.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import type { AdapterAccount } from "@auth/core/adapters"
|
||||||
|
import {
|
||||||
|
mysqlTable,
|
||||||
|
varchar,
|
||||||
|
timestamp,
|
||||||
|
int,
|
||||||
|
primaryKey,
|
||||||
|
} from "drizzle-orm/mysql-core"
|
||||||
|
import { drizzle } from "drizzle-orm/mysql2"
|
||||||
|
import { createPool } from "mysql2"
|
||||||
|
import {
|
||||||
|
users,
|
||||||
|
accounts,
|
||||||
|
sessions,
|
||||||
|
verificationTokens,
|
||||||
|
schema,
|
||||||
|
} from "../../src/lib/mysql"
|
||||||
|
|
||||||
|
const poolConnection = createPool({
|
||||||
|
host: "localhost",
|
||||||
|
user: "root",
|
||||||
|
password: "password",
|
||||||
|
database: "next-auth",
|
||||||
|
})
|
||||||
|
|
||||||
|
export { users, accounts, sessions, verificationTokens }
|
||||||
|
export const db = drizzle(poolConnection, {
|
||||||
|
schema: schema,
|
||||||
|
})
|
||||||
22
packages/adapter-drizzle/tests/mysql/test.sh
Executable file
22
packages/adapter-drizzle/tests/mysql/test.sh
Executable file
@@ -0,0 +1,22 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
echo "Initializing container for MySQL tests."
|
||||||
|
|
||||||
|
MYSQL_DATABASE=next-auth
|
||||||
|
MYSQL_ROOT_PASSWORD=password
|
||||||
|
MYSQL_CONTAINER_NAME=next-auth-mysql-test
|
||||||
|
|
||||||
|
docker run -d --rm \
|
||||||
|
-e MYSQL_DATABASE=${MYSQL_DATABASE} \
|
||||||
|
-e MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD} \
|
||||||
|
--name "${MYSQL_CONTAINER_NAME}" \
|
||||||
|
-p 3306:3306 \
|
||||||
|
mysql:8 \
|
||||||
|
--default-authentication-plugin=mysql_native_password
|
||||||
|
|
||||||
|
echo "Waiting 15 sec for db to start..." && sleep 15
|
||||||
|
|
||||||
|
drizzle-kit generate:mysql --config=./tests/mysql/drizzle.config.ts
|
||||||
|
drizzle-kit push:mysql --config=./tests/mysql/drizzle.config.ts
|
||||||
|
jest ./tests/mysql/index.test.ts --forceExit
|
||||||
|
docker stop ${MYSQL_CONTAINER_NAME}
|
||||||
13
packages/adapter-drizzle/tests/pg/drizzle.config.ts
Normal file
13
packages/adapter-drizzle/tests/pg/drizzle.config.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import type { Config } from "drizzle-kit"
|
||||||
|
|
||||||
|
export default {
|
||||||
|
schema: "./tests/pg/schema.ts",
|
||||||
|
out: "./tests/pg/.drizzle",
|
||||||
|
dbCredentials: {
|
||||||
|
database: "nextauth",
|
||||||
|
host: "nextauth",
|
||||||
|
user: "nextauth",
|
||||||
|
password: "nextauth",
|
||||||
|
port: 5432,
|
||||||
|
},
|
||||||
|
} satisfies Config
|
||||||
65
packages/adapter-drizzle/tests/pg/index.test.ts
Normal file
65
packages/adapter-drizzle/tests/pg/index.test.ts
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import { runBasicTests } from "../../../adapter-test"
|
||||||
|
import { DrizzleAdapter } from "../../src"
|
||||||
|
import { db, accounts, sessions, users, verificationTokens } from "./schema"
|
||||||
|
import { eq, and } from "drizzle-orm"
|
||||||
|
import { fixtures } from "../fixtures"
|
||||||
|
|
||||||
|
globalThis.crypto ??= require("node:crypto").webcrypto
|
||||||
|
|
||||||
|
runBasicTests({
|
||||||
|
adapter: DrizzleAdapter(db),
|
||||||
|
fixtures,
|
||||||
|
db: {
|
||||||
|
connect: async () => {
|
||||||
|
await Promise.all([
|
||||||
|
db.delete(sessions),
|
||||||
|
db.delete(accounts),
|
||||||
|
db.delete(verificationTokens),
|
||||||
|
db.delete(users),
|
||||||
|
])
|
||||||
|
},
|
||||||
|
disconnect: async () => {
|
||||||
|
await Promise.all([
|
||||||
|
db.delete(sessions),
|
||||||
|
db.delete(accounts),
|
||||||
|
db.delete(verificationTokens),
|
||||||
|
db.delete(users),
|
||||||
|
])
|
||||||
|
},
|
||||||
|
user: async (id) =>
|
||||||
|
db
|
||||||
|
.select()
|
||||||
|
.from(users)
|
||||||
|
.where(eq(users.id, id))
|
||||||
|
.then((res) => res[0] ?? null),
|
||||||
|
session: (sessionToken) =>
|
||||||
|
db
|
||||||
|
.select()
|
||||||
|
.from(sessions)
|
||||||
|
.where(eq(sessions.sessionToken, sessionToken))
|
||||||
|
.then((res) => res[0] ?? null),
|
||||||
|
account: (provider_providerAccountId) => {
|
||||||
|
return db
|
||||||
|
.select()
|
||||||
|
.from(accounts)
|
||||||
|
.where(
|
||||||
|
eq(
|
||||||
|
accounts.providerAccountId,
|
||||||
|
provider_providerAccountId.providerAccountId
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.then((res) => res[0] ?? null)
|
||||||
|
},
|
||||||
|
verificationToken: (identifier_token) =>
|
||||||
|
db
|
||||||
|
.select()
|
||||||
|
.from(verificationTokens)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(verificationTokens.token, identifier_token.token),
|
||||||
|
eq(verificationTokens.identifier, identifier_token.identifier)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.then((res) => res[0] ?? null),
|
||||||
|
},
|
||||||
|
})
|
||||||
10
packages/adapter-drizzle/tests/pg/migrator.ts
Normal file
10
packages/adapter-drizzle/tests/pg/migrator.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { migrate } from "drizzle-orm/postgres-js/migrator"
|
||||||
|
import { db } from "./schema"
|
||||||
|
|
||||||
|
const migrator = async () => {
|
||||||
|
await migrate(db, { migrationsFolder: "./tests/pg/.drizzle" })
|
||||||
|
}
|
||||||
|
|
||||||
|
migrator()
|
||||||
|
.then(() => process.exit(0))
|
||||||
|
.catch(() => process.exit(1))
|
||||||
11
packages/adapter-drizzle/tests/pg/schema.ts
Normal file
11
packages/adapter-drizzle/tests/pg/schema.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { drizzle } from "drizzle-orm/postgres-js"
|
||||||
|
import postgres from "postgres"
|
||||||
|
import { users, accounts, sessions, verificationTokens } from "../../src/lib/pg"
|
||||||
|
|
||||||
|
const connectionString = "postgres://nextauth:nextauth@localhost:5432/nextauth"
|
||||||
|
const sql = postgres(connectionString, { max: 1 })
|
||||||
|
|
||||||
|
export const db = drizzle(sql, {
|
||||||
|
schema: { users, accounts, sessions, verificationTokens },
|
||||||
|
})
|
||||||
|
export { users, accounts, sessions, verificationTokens }
|
||||||
25
packages/adapter-drizzle/tests/pg/test.sh
Executable file
25
packages/adapter-drizzle/tests/pg/test.sh
Executable file
@@ -0,0 +1,25 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
echo "Initializing container for PostgreSQL tests."
|
||||||
|
|
||||||
|
PGUSER=nextauth
|
||||||
|
PGPASSWORD=nextauth
|
||||||
|
PGDATABASE=nextauth
|
||||||
|
PGPORT=5432
|
||||||
|
PG_CONTAINER_NAME=next-auth-postgres-test
|
||||||
|
|
||||||
|
docker run -d --rm \
|
||||||
|
-e POSTGRES_USER=${PGUSER} \
|
||||||
|
-e POSTGRES_PASSWORD=${PGUSER} \
|
||||||
|
-e POSTGRES_DB=${PGDATABASE} \
|
||||||
|
-e POSTGRES_HOST_AUTH_METHOD=trust \
|
||||||
|
--name "${PG_CONTAINER_NAME}" \
|
||||||
|
-p ${PGPORT}:5432 \
|
||||||
|
postgres:15.3
|
||||||
|
|
||||||
|
echo "Waiting 15 sec for db to start..." && sleep 15
|
||||||
|
|
||||||
|
drizzle-kit generate:pg --config=./tests/pg/drizzle.config.ts
|
||||||
|
npx tsx ./tests/pg/migrator.ts
|
||||||
|
jest ./tests/pg/index.test.ts --forceExit
|
||||||
|
docker stop ${PG_CONTAINER_NAME}
|
||||||
10
packages/adapter-drizzle/tests/sqlite/drizzle.config.ts
Normal file
10
packages/adapter-drizzle/tests/sqlite/drizzle.config.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import type { Config } from "drizzle-kit"
|
||||||
|
|
||||||
|
export default {
|
||||||
|
schema: "./tests/sqlite/schema.ts",
|
||||||
|
out: "./tests/sqlite/.drizzle",
|
||||||
|
driver: "better-sqlite",
|
||||||
|
dbCredentials: {
|
||||||
|
url: "./db.sqlite",
|
||||||
|
},
|
||||||
|
} satisfies Config
|
||||||
60
packages/adapter-drizzle/tests/sqlite/index.test.ts
Normal file
60
packages/adapter-drizzle/tests/sqlite/index.test.ts
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import { runBasicTests } from "../../../adapter-test"
|
||||||
|
import { DrizzleAdapter } from "../../src"
|
||||||
|
import { db, accounts, sessions, users, verificationTokens } from "./schema"
|
||||||
|
import { eq, and } from "drizzle-orm"
|
||||||
|
|
||||||
|
globalThis.crypto ??= require("node:crypto").webcrypto
|
||||||
|
|
||||||
|
runBasicTests({
|
||||||
|
adapter: DrizzleAdapter(db),
|
||||||
|
db: {
|
||||||
|
connect: async () => {
|
||||||
|
await Promise.all([
|
||||||
|
db.delete(sessions),
|
||||||
|
db.delete(accounts),
|
||||||
|
db.delete(verificationTokens),
|
||||||
|
db.delete(users),
|
||||||
|
])
|
||||||
|
},
|
||||||
|
disconnect: async () => {
|
||||||
|
await Promise.all([
|
||||||
|
db.delete(sessions),
|
||||||
|
db.delete(accounts),
|
||||||
|
db.delete(verificationTokens),
|
||||||
|
db.delete(users),
|
||||||
|
])
|
||||||
|
},
|
||||||
|
user: (id) => db.select().from(users).where(eq(users.id, id)).get() ?? null,
|
||||||
|
session: (sessionToken) =>
|
||||||
|
db
|
||||||
|
.select()
|
||||||
|
.from(sessions)
|
||||||
|
.where(eq(sessions.sessionToken, sessionToken))
|
||||||
|
.get() ?? null,
|
||||||
|
account: (provider_providerAccountId) => {
|
||||||
|
return (
|
||||||
|
db
|
||||||
|
.select()
|
||||||
|
.from(accounts)
|
||||||
|
.where(
|
||||||
|
eq(
|
||||||
|
accounts.providerAccountId,
|
||||||
|
provider_providerAccountId.providerAccountId
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.get() ?? null
|
||||||
|
)
|
||||||
|
},
|
||||||
|
verificationToken: (identifier_token) =>
|
||||||
|
db
|
||||||
|
.select()
|
||||||
|
.from(verificationTokens)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(verificationTokens.token, identifier_token.token),
|
||||||
|
eq(verificationTokens.identifier, identifier_token.identifier)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.get() ?? null,
|
||||||
|
},
|
||||||
|
})
|
||||||
20
packages/adapter-drizzle/tests/sqlite/schema.ts
Normal file
20
packages/adapter-drizzle/tests/sqlite/schema.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { drizzle } from "drizzle-orm/better-sqlite3"
|
||||||
|
import Database from "better-sqlite3"
|
||||||
|
import {
|
||||||
|
users,
|
||||||
|
accounts,
|
||||||
|
sessions,
|
||||||
|
verificationTokens,
|
||||||
|
} from "../../src/lib/sqlite"
|
||||||
|
|
||||||
|
const sqlite = new Database("db.sqlite")
|
||||||
|
|
||||||
|
export { users, accounts, sessions, verificationTokens }
|
||||||
|
export const db = drizzle(sqlite, {
|
||||||
|
schema: {
|
||||||
|
users,
|
||||||
|
accounts,
|
||||||
|
sessions,
|
||||||
|
verificationTokens,
|
||||||
|
},
|
||||||
|
})
|
||||||
12
packages/adapter-drizzle/tests/sqlite/test.sh
Executable file
12
packages/adapter-drizzle/tests/sqlite/test.sh
Executable file
@@ -0,0 +1,12 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
|
||||||
|
echo "Running SQLite tests."
|
||||||
|
|
||||||
|
rm -f db.sqlite
|
||||||
|
|
||||||
|
drizzle-kit generate:sqlite --config=./tests/sqlite/drizzle.config.ts
|
||||||
|
drizzle-kit push:sqlite --config=./tests/sqlite/drizzle.config.ts
|
||||||
|
jest ./tests/sqlite/index.test.ts --forceExit
|
||||||
25
packages/adapter-drizzle/tsconfig.json
Normal file
25
packages/adapter-drizzle/tsconfig.json
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"extends": "@next-auth/tsconfig/tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"allowJs": true,
|
||||||
|
"baseUrl": ".",
|
||||||
|
"isolatedModules": true,
|
||||||
|
"target": "ES2020",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"outDir": ".",
|
||||||
|
"rootDir": "src",
|
||||||
|
"skipDefaultLibCheck": true,
|
||||||
|
"strictNullChecks": true,
|
||||||
|
"stripInternal": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
"declaration": true
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"*.js",
|
||||||
|
"*.d.ts",
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@auth/dynamodb-adapter",
|
"name": "@auth/dynamodb-adapter",
|
||||||
"repository": "https://github.com/nextauthjs/next-auth",
|
"repository": "https://github.com/nextauthjs/next-auth",
|
||||||
"version": "1.0.0",
|
"version": "1.0.1",
|
||||||
"description": "AWS DynamoDB adapter for next-auth.",
|
"description": "AWS DynamoDB adapter for next-auth.",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"next-auth",
|
"next-auth",
|
||||||
|
|||||||
@@ -265,9 +265,8 @@ export function DynamoDBAdapter(
|
|||||||
const data = await client.update({
|
const data = await client.update({
|
||||||
TableName,
|
TableName,
|
||||||
Key: {
|
Key: {
|
||||||
// next-auth type is incorrect it should be Partial<AdapterUser> & {id: string} instead of just Partial<AdapterUser>
|
[pk]: `USER#${user.id}`,
|
||||||
[pk]: `USER#${user.id as string}`,
|
[sk]: `USER#${user.id}`,
|
||||||
[sk]: `USER#${user.id as string}`,
|
|
||||||
},
|
},
|
||||||
UpdateExpression,
|
UpdateExpression,
|
||||||
ExpressionAttributeNames,
|
ExpressionAttributeNames,
|
||||||
|
|||||||
@@ -15,6 +15,13 @@ const requiredMethods = [
|
|||||||
]
|
]
|
||||||
export interface TestOptions {
|
export interface TestOptions {
|
||||||
adapter: Adapter
|
adapter: Adapter
|
||||||
|
fixtures?: {
|
||||||
|
user?: any
|
||||||
|
session?: any
|
||||||
|
account?: any
|
||||||
|
sessionUpdateExpires?: Date
|
||||||
|
verificationTokenExpires?: Date
|
||||||
|
},
|
||||||
db: {
|
db: {
|
||||||
/** Generates UUID v4 by default. Use it to override how the test suite should generate IDs, like user id. */
|
/** Generates UUID v4 by default. Use it to override how the test suite should generate IDs, like user id. */
|
||||||
id?: () => string
|
id?: () => string
|
||||||
@@ -67,11 +74,11 @@ export async function runBasicTests(options: TestOptions) {
|
|||||||
await options.db.disconnect?.()
|
await options.db.disconnect?.()
|
||||||
})
|
})
|
||||||
|
|
||||||
let user: any = {
|
let user: any = options.fixtures?.user ?? {
|
||||||
email: "fill@murray.com",
|
email: "fill@murray.com",
|
||||||
image: "https://www.fillmurray.com/460/300",
|
image: "https://www.fillmurray.com/460/300",
|
||||||
name: "Fill Murray",
|
name: "Fill Murray",
|
||||||
emailVerified: new Date(),
|
emailVerified: new Date()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.env.CUSTOM_MODEL === "1") {
|
if (process.env.CUSTOM_MODEL === "1") {
|
||||||
@@ -79,12 +86,12 @@ export async function runBasicTests(options: TestOptions) {
|
|||||||
user.phone = "00000000000"
|
user.phone = "00000000000"
|
||||||
}
|
}
|
||||||
|
|
||||||
const session: any = {
|
const session: any = options.fixtures?.session ?? {
|
||||||
sessionToken: randomUUID(),
|
sessionToken: randomUUID(),
|
||||||
expires: ONE_WEEK_FROM_NOW,
|
expires: ONE_WEEK_FROM_NOW,
|
||||||
}
|
}
|
||||||
|
|
||||||
const account: any = {
|
const account: any = options.fixtures?.account ?? {
|
||||||
provider: "github",
|
provider: "github",
|
||||||
providerAccountId: randomUUID(),
|
providerAccountId: randomUUID(),
|
||||||
type: "oauth",
|
type: "oauth",
|
||||||
@@ -175,15 +182,17 @@ export async function runBasicTests(options: TestOptions) {
|
|||||||
test("updateSession", async () => {
|
test("updateSession", async () => {
|
||||||
let dbSession = await db.session(session.sessionToken)
|
let dbSession = await db.session(session.sessionToken)
|
||||||
|
|
||||||
expect(dbSession.expires.valueOf()).not.toBe(ONE_MONTH_FROM_NOW.valueOf())
|
const expires = options.fixtures?.sessionUpdateExpires ?? ONE_MONTH_FROM_NOW
|
||||||
|
|
||||||
|
expect(dbSession.expires.valueOf()).not.toBe(expires.valueOf())
|
||||||
|
|
||||||
await adapter.updateSession({
|
await adapter.updateSession({
|
||||||
sessionToken: session.sessionToken,
|
sessionToken: session.sessionToken,
|
||||||
expires: ONE_MONTH_FROM_NOW,
|
expires,
|
||||||
})
|
})
|
||||||
|
|
||||||
dbSession = await db.session(session.sessionToken)
|
dbSession = await db.session(session.sessionToken)
|
||||||
expect(dbSession.expires.valueOf()).toBe(ONE_MONTH_FROM_NOW.valueOf())
|
expect(dbSession.expires.valueOf()).toBe(expires.valueOf())
|
||||||
})
|
})
|
||||||
|
|
||||||
test("linkAccount", async () => {
|
test("linkAccount", async () => {
|
||||||
@@ -232,7 +241,7 @@ export async function runBasicTests(options: TestOptions) {
|
|||||||
const verificationToken = {
|
const verificationToken = {
|
||||||
token: hashedToken,
|
token: hashedToken,
|
||||||
identifier,
|
identifier,
|
||||||
expires: FIFTEEN_MINUTES_FROM_NOW,
|
expires: options.fixtures?.verificationTokenExpires ?? FIFTEEN_MINUTES_FROM_NOW,
|
||||||
}
|
}
|
||||||
await adapter.createVerificationToken?.(verificationToken)
|
await adapter.createVerificationToken?.(verificationToken)
|
||||||
|
|
||||||
@@ -251,7 +260,7 @@ export async function runBasicTests(options: TestOptions) {
|
|||||||
const verificationToken = {
|
const verificationToken = {
|
||||||
token: hashedToken,
|
token: hashedToken,
|
||||||
identifier,
|
identifier,
|
||||||
expires: FIFTEEN_MINUTES_FROM_NOW,
|
expires: options.fixtures?.verificationTokenExpires ?? FIFTEEN_MINUTES_FROM_NOW,
|
||||||
}
|
}
|
||||||
await adapter.createVerificationToken?.(verificationToken)
|
await adapter.createVerificationToken?.(verificationToken)
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ module.exports = {
|
|||||||
".(js|jsx)$": ["@swc/jest", swcConfig],
|
".(js|jsx)$": ["@swc/jest", swcConfig],
|
||||||
},
|
},
|
||||||
transformIgnorePatterns: ["[/\\\\]node_modules[/\\\\].+\\.(js|jsx)$"],
|
transformIgnorePatterns: ["[/\\\\]node_modules[/\\\\].+\\.(js|jsx)$"],
|
||||||
moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"],
|
moduleFileExtensions: ["mjs", "cjs", "ts", "tsx", "js", "jsx", "json", "node"],
|
||||||
rootDir: ".",
|
rootDir: ".",
|
||||||
// coverageDirectory: "<rootDir>/coverage/",
|
// coverageDirectory: "<rootDir>/coverage/",
|
||||||
// collectCoverageFrom: ["<rootDir>/packages/*/src/**/*.{ts,tsx}"],
|
// collectCoverageFrom: ["<rootDir>/packages/*/src/**/*.{ts,tsx}"],
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@auth/core",
|
"name": "@auth/core",
|
||||||
"version": "0.9.0",
|
"version": "0.10.1",
|
||||||
"description": "Authentication for the Web.",
|
"description": "Authentication for the Web.",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"authentication",
|
"authentication",
|
||||||
|
|||||||
@@ -223,7 +223,7 @@ export interface Adapter {
|
|||||||
getUserByAccount?(
|
getUserByAccount?(
|
||||||
providerAccountId: Pick<AdapterAccount, "provider" | "providerAccountId">
|
providerAccountId: Pick<AdapterAccount, "provider" | "providerAccountId">
|
||||||
): Awaitable<AdapterUser | null>
|
): Awaitable<AdapterUser | null>
|
||||||
updateUser?(user: Partial<AdapterUser>): Awaitable<AdapterUser>
|
updateUser?(user: Partial<AdapterUser> & Pick<AdapterUser, 'id'>): Awaitable<AdapterUser>
|
||||||
/** @todo This method is currently not invoked yet. */
|
/** @todo This method is currently not invoked yet. */
|
||||||
deleteUser?(
|
deleteUser?(
|
||||||
userId: string
|
userId: string
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
import { createHash, randomString } from "../web.js"
|
import { createHash, randomString, toRequest } from "../web.js"
|
||||||
|
|
||||||
import type { InternalOptions } from "../../types.js"
|
import type { InternalOptions, RequestInternal } from "../../types.js"
|
||||||
/**
|
/**
|
||||||
* Starts an e-mail login flow, by generating a token,
|
* Starts an e-mail login flow, by generating a token,
|
||||||
* and sending it to the user's e-mail (with the help of a DB adapter)
|
* and sending it to the user's e-mail (with the help of a DB adapter)
|
||||||
*/
|
*/
|
||||||
export default async function email(
|
export default async function email(
|
||||||
identifier: string,
|
identifier: string,
|
||||||
options: InternalOptions<"email">
|
options: InternalOptions<"email">,
|
||||||
|
request: RequestInternal
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const { url, adapter, provider, callbackUrl, theme } = options
|
const { url, adapter, provider, callbackUrl, theme } = options
|
||||||
const token =
|
const token =
|
||||||
@@ -31,6 +32,7 @@ export default async function email(
|
|||||||
url: _url,
|
url: _url,
|
||||||
provider,
|
provider,
|
||||||
theme,
|
theme,
|
||||||
|
request: toRequest(request),
|
||||||
}),
|
}),
|
||||||
// @ts-expect-error -- Verified in `assertConfig`.
|
// @ts-expect-error -- Verified in `assertConfig`.
|
||||||
adapter.createVerificationToken?.({
|
adapter.createVerificationToken?.({
|
||||||
|
|||||||
@@ -137,8 +137,7 @@ export async function AuthInternal<
|
|||||||
case "signin":
|
case "signin":
|
||||||
if ((csrfDisabled || options.csrfTokenVerified) && options.provider) {
|
if ((csrfDisabled || options.csrfTokenVerified) && options.provider) {
|
||||||
const signin = await routes.signin(
|
const signin = await routes.signin(
|
||||||
request.query,
|
request,
|
||||||
request.body,
|
|
||||||
options
|
options
|
||||||
)
|
)
|
||||||
if (signin.cookies) cookies.push(...signin.cookies)
|
if (signin.cookies) cookies.push(...signin.cookies)
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ export async function handleOAuth(
|
|||||||
throw new Error("TODO: Handle www-authenticate challenges as needed")
|
throw new Error("TODO: Handle www-authenticate challenges as needed")
|
||||||
}
|
}
|
||||||
|
|
||||||
let profile: Profile
|
let profile: Profile = {}
|
||||||
let tokens: TokenSet & Pick<Account, "expires_at">
|
let tokens: TokenSet & Pick<Account, "expires_at">
|
||||||
|
|
||||||
if (provider.type === "oidc") {
|
if (provider.type === "oidc") {
|
||||||
@@ -153,7 +153,8 @@ export async function handleOAuth(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (userinfo?.request) {
|
if (userinfo?.request) {
|
||||||
profile = await userinfo.request({ tokens, provider })
|
const _profile = await userinfo.request({ tokens, provider })
|
||||||
|
if (_profile instanceof Object) profile = _profile
|
||||||
} else if (userinfo?.url) {
|
} else if (userinfo?.url) {
|
||||||
const userinfoResponse = await o.userInfoRequest(
|
const userinfoResponse = await o.userInfoRequest(
|
||||||
as,
|
as,
|
||||||
|
|||||||
@@ -16,10 +16,10 @@ import type {
|
|||||||
* For Email, sends an email with a sign in link.
|
* For Email, sends an email with a sign in link.
|
||||||
*/
|
*/
|
||||||
export async function signin(
|
export async function signin(
|
||||||
query: RequestInternal["query"],
|
request: RequestInternal,
|
||||||
body: RequestInternal["body"],
|
|
||||||
options: InternalOptions<"oauth" | "oidc" | "email">
|
options: InternalOptions<"oauth" | "oidc" | "email">
|
||||||
): Promise<ResponseInternal> {
|
): Promise<ResponseInternal> {
|
||||||
|
const { query, body } = request
|
||||||
const { url, logger, provider } = options
|
const { url, logger, provider } = options
|
||||||
try {
|
try {
|
||||||
if (provider.type === "oauth" || provider.type === "oidc") {
|
if (provider.type === "oauth" || provider.type === "oidc") {
|
||||||
@@ -48,7 +48,7 @@ export async function signin(
|
|||||||
|
|
||||||
if (unauthorizedOrError) return unauthorizedOrError
|
if (unauthorizedOrError) return unauthorizedOrError
|
||||||
|
|
||||||
const redirect = await emailSignin(email, options)
|
const redirect = await emailSignin(email, options, request)
|
||||||
return { redirect }
|
return { redirect }
|
||||||
}
|
}
|
||||||
return { redirect: `${url}/signin` }
|
return { redirect: `${url}/signin` }
|
||||||
|
|||||||
@@ -72,6 +72,17 @@ export async function toInternalRequest(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function toRequest(request: RequestInternal): Request {
|
||||||
|
return new Request(request.url, {
|
||||||
|
headers: request.headers,
|
||||||
|
method: request.method,
|
||||||
|
body:
|
||||||
|
request.method === "POST"
|
||||||
|
? JSON.stringify(request.body ?? {})
|
||||||
|
: undefined,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export function toResponse(res: ResponseInternal): Response {
|
export function toResponse(res: ResponseInternal): Response {
|
||||||
const headers = new Headers(res.headers)
|
const headers = new Headers(res.headers)
|
||||||
|
|
||||||
|
|||||||
@@ -131,6 +131,11 @@ export default function AzureAD<P extends AzureADProfile>(
|
|||||||
name: "Azure Active Directory",
|
name: "Azure Active Directory",
|
||||||
type: "oidc",
|
type: "oidc",
|
||||||
wellKnown: `${rest.issuer}}/.well-known/openid-configuration?appid=${options.clientId}`,
|
wellKnown: `${rest.issuer}}/.well-known/openid-configuration?appid=${options.clientId}`,
|
||||||
|
authorization: {
|
||||||
|
params: {
|
||||||
|
scope: 'openid profile email User.Read',
|
||||||
|
},
|
||||||
|
},
|
||||||
async profile(profile, tokens) {
|
async profile(profile, tokens) {
|
||||||
// https://docs.microsoft.com/en-us/graph/api/profilephoto-get?view=graph-rest-1.0#examples
|
// https://docs.microsoft.com/en-us/graph/api/profilephoto-get?view=graph-rest-1.0#examples
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export interface BoxyHQSAMLProfile extends Record<string, any> {
|
|||||||
/**
|
/**
|
||||||
* Add BoxyHQ SAML login to your page.
|
* Add BoxyHQ SAML login to your page.
|
||||||
*
|
*
|
||||||
* BoxyHQ SAML is an open source service that handles the SAML login flow as an OAuth 2.0 flow, abstracting away all the complexities of the SAML protocol.
|
* BoxyHQ SAML is an open source service that handles the SAML SSO login flow as an OAuth 2.0 flow, abstracting away all the complexities of the SAML protocol. Enable Enterprise single-sign-on in your app with ease.
|
||||||
*
|
*
|
||||||
* You can deploy BoxyHQ SAML as a separate service or embed it into your app using our NPM library. [Check out the documentation for more details](https://boxyhq.com/docs/jackson/deploy)
|
* You can deploy BoxyHQ SAML as a separate service or embed it into your app using our NPM library. [Check out the documentation for more details](https://boxyhq.com/docs/jackson/deploy)
|
||||||
*
|
*
|
||||||
@@ -32,13 +32,38 @@ export interface BoxyHQSAMLProfile extends Record<string, any> {
|
|||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* #### Configuration
|
* #### Configuration
|
||||||
|
*
|
||||||
|
* For OAuth 2.0 Flow:
|
||||||
*```js
|
*```js
|
||||||
* import Auth from "@auth/core"
|
* import Auth from "@auth/core"
|
||||||
* import BoxyHQ from "@auth/core/providers/boxyhq-saml"
|
* import BoxyHQ from "@auth/core/providers/boxyhq-saml"
|
||||||
*
|
*
|
||||||
* const request = new Request(origin)
|
* const request = new Request(origin)
|
||||||
* const response = await Auth(request, {
|
* const response = await Auth(request, {
|
||||||
* providers: [BoxyHQ({ clientId: BOXYHQ_SAML_CLIENT_ID, clientSecret: BOXYHQ_SAML_CLIENT_SECRET. issuer: BOXYHQ_SAML_ISSUER })],
|
* providers: [BoxyHQ({
|
||||||
|
* authorization: { params: { scope: "" } }, // This is needed for OAuth 2.0 flow, otherwise default to openid
|
||||||
|
* clientId: BOXYHQ_SAML_CLIENT_ID,
|
||||||
|
* clientSecret: BOXYHQ_SAML_CLIENT_SECRET,
|
||||||
|
* issuer: BOXYHQ_SAML_ISSUER
|
||||||
|
* })],
|
||||||
|
* })
|
||||||
|
* ```
|
||||||
|
* For OIDC Flow:
|
||||||
|
*
|
||||||
|
*```js
|
||||||
|
* import Auth from "@auth/core"
|
||||||
|
* import BoxyHQ from "@auth/core/providers/boxyhq-saml"
|
||||||
|
*
|
||||||
|
* const request = new Request(origin)
|
||||||
|
* const response = await Auth(request, {
|
||||||
|
* providers: [BoxyHQ({
|
||||||
|
* id: "boxyhq-saml-oidc",
|
||||||
|
* wellKnown: `http://localhost:5225/.well-known/openid-configuration`,
|
||||||
|
* authorization: { params: { scope: "openid email" } },
|
||||||
|
* clientId: BOXYHQ_SAML_CLIENT_ID,
|
||||||
|
* clientSecret: BOXYHQ_SAML_CLIENT_SECRET,
|
||||||
|
* issuer: BOXYHQ_SAML_ISSUER
|
||||||
|
* })],
|
||||||
* })
|
* })
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -13,17 +13,25 @@
|
|||||||
|
|
||||||
import type { OIDCConfig, OIDCUserConfig } from "./index.js"
|
import type { OIDCConfig, OIDCUserConfig } from "./index.js"
|
||||||
|
|
||||||
/** The returned user profile from Descope when using the profile callback. */
|
/** The returned user profile from Descope when using the profile callback.
|
||||||
|
* [See Load User](https://docs.descope.com/api/openapi/usermanagement/operation/LoadUser/)
|
||||||
|
*/
|
||||||
export interface DescopeProfile {
|
export interface DescopeProfile {
|
||||||
/** The user Descope ID */
|
/** The user's unique Descope ID */
|
||||||
sub: string
|
sub: string
|
||||||
|
/** The user's name */
|
||||||
name: string
|
name: string
|
||||||
|
/** The user's email */
|
||||||
email: string
|
email: string
|
||||||
|
/** A boolean indicating if the user's email is verified */
|
||||||
email_verified: boolean
|
email_verified: boolean
|
||||||
|
/** The user's phone number */
|
||||||
phone_number: string
|
phone_number: string
|
||||||
|
/** A boolean indicating if the user's phone number is verified */
|
||||||
phone_number_verified: boolean
|
phone_number_verified: boolean
|
||||||
|
/** The user's picture */
|
||||||
picture: string
|
picture: string
|
||||||
/** Custom user's attributes */
|
/** The user's custom attributes */
|
||||||
[claim: string]: unknown
|
[claim: string]: unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,7 +105,7 @@ export default function Descope(
|
|||||||
id: "descope",
|
id: "descope",
|
||||||
name: "Descope",
|
name: "Descope",
|
||||||
type: "oidc",
|
type: "oidc",
|
||||||
clientId: `https://api.descope.com/${config.clientId}`,
|
issuer: `https://api.descope.com/${config.clientId}`,
|
||||||
style: {
|
style: {
|
||||||
logo: "/descope.svg",
|
logo: "/descope.svg",
|
||||||
logoDark: "/descope.svg",
|
logoDark: "/descope.svg",
|
||||||
|
|||||||
@@ -1,9 +1,17 @@
|
|||||||
import { createTransport } from "nodemailer"
|
|
||||||
|
|
||||||
import type { CommonProviderOptions } from "./index.js"
|
import type { CommonProviderOptions } from "./index.js"
|
||||||
import type { Options as SMTPTransportOptions } from "nodemailer/lib/smtp-transport"
|
|
||||||
import type { Awaitable, Theme } from "../types.js"
|
import type { Awaitable, Theme } from "../types.js"
|
||||||
|
|
||||||
|
import { Transport, TransportOptions, createTransport } from "nodemailer"
|
||||||
|
import * as JSONTransport from "nodemailer/lib/json-transport/index.js"
|
||||||
|
import * as SendmailTransport from "nodemailer/lib/sendmail-transport/index.js"
|
||||||
|
import * as SESTransport from "nodemailer/lib/ses-transport/index.js"
|
||||||
|
import * as SMTPTransport from "nodemailer/lib/smtp-transport/index.js"
|
||||||
|
import * as SMTPPool from "nodemailer/lib/smtp-pool/index.js"
|
||||||
|
import * as StreamTransport from "nodemailer/lib/stream-transport/index.js"
|
||||||
|
|
||||||
|
// TODO: Make use of https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html for the string
|
||||||
|
type AllTransportOptions = string | SMTPTransport | SMTPTransport.Options | SMTPPool | SMTPPool.Options | SendmailTransport | SendmailTransport.Options | StreamTransport | StreamTransport.Options | JSONTransport | JSONTransport.Options | SESTransport | SESTransport.Options | Transport<any> | TransportOptions
|
||||||
|
|
||||||
export interface SendVerificationRequestParams {
|
export interface SendVerificationRequestParams {
|
||||||
identifier: string
|
identifier: string
|
||||||
url: string
|
url: string
|
||||||
@@ -11,6 +19,7 @@ export interface SendVerificationRequestParams {
|
|||||||
provider: EmailConfig
|
provider: EmailConfig
|
||||||
token: string
|
token: string
|
||||||
theme: Theme
|
theme: Theme
|
||||||
|
request: Request
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -26,10 +35,9 @@ export interface SendVerificationRequestParams {
|
|||||||
*
|
*
|
||||||
* [Custom email service with Auth.js](https://authjs.dev/guides/providers/email#custom-email-service)
|
* [Custom email service with Auth.js](https://authjs.dev/guides/providers/email#custom-email-service)
|
||||||
*/
|
*/
|
||||||
export interface EmailConfig extends CommonProviderOptions {
|
export interface EmailUserConfig {
|
||||||
type: "email"
|
server?: AllTransportOptions
|
||||||
// TODO: Make use of https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html
|
type?: "email"
|
||||||
server?: string | SMTPTransportOptions
|
|
||||||
/** @default `"Auth.js <no-reply@authjs.dev>"` */
|
/** @default `"Auth.js <no-reply@authjs.dev>"` */
|
||||||
from?: string
|
from?: string
|
||||||
/**
|
/**
|
||||||
@@ -40,7 +48,7 @@ export interface EmailConfig extends CommonProviderOptions {
|
|||||||
*/
|
*/
|
||||||
maxAge?: number
|
maxAge?: number
|
||||||
/** [Documentation](https://authjs.dev/guides/providers/email#customizing-emails) */
|
/** [Documentation](https://authjs.dev/guides/providers/email#customizing-emails) */
|
||||||
sendVerificationRequest: (
|
sendVerificationRequest?: (
|
||||||
params: SendVerificationRequestParams
|
params: SendVerificationRequestParams
|
||||||
) => Awaitable<void>
|
) => Awaitable<void>
|
||||||
/**
|
/**
|
||||||
@@ -77,6 +85,31 @@ export interface EmailConfig extends CommonProviderOptions {
|
|||||||
normalizeIdentifier?: (identifier: string) => string
|
normalizeIdentifier?: (identifier: string) => string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface EmailConfig extends CommonProviderOptions {
|
||||||
|
// defaults
|
||||||
|
id: "email"
|
||||||
|
type: "email"
|
||||||
|
name: "Email"
|
||||||
|
server: AllTransportOptions
|
||||||
|
from: string
|
||||||
|
maxAge: number
|
||||||
|
sendVerificationRequest: (
|
||||||
|
params: SendVerificationRequestParams
|
||||||
|
) => Awaitable<void>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is copied into EmailConfig in parseProviders() don't use elsewhere
|
||||||
|
*/
|
||||||
|
options: EmailUserConfig
|
||||||
|
|
||||||
|
// user options
|
||||||
|
// TODO figure out a better way than copying from EmailUserConfig
|
||||||
|
secret?: string
|
||||||
|
generateVerificationToken?: () => Awaitable<string>
|
||||||
|
normalizeIdentifier?: (identifier: string) => string
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// TODO: Rename to Token provider
|
// TODO: Rename to Token provider
|
||||||
// when started working on https://github.com/nextauthjs/next-auth/discussions/1465
|
// when started working on https://github.com/nextauthjs/next-auth/discussions/1465
|
||||||
export type EmailProviderType = "email"
|
export type EmailProviderType = "email"
|
||||||
@@ -313,7 +346,7 @@ export type EmailProviderType = "email"
|
|||||||
* Always make sure this returns a single e-mail address, even if multiple ones were passed in.
|
* Always make sure this returns a single e-mail address, even if multiple ones were passed in.
|
||||||
* :::
|
* :::
|
||||||
*/
|
*/
|
||||||
export default function Email(config: EmailConfig): EmailConfig {
|
export default function Email(config: EmailUserConfig): EmailConfig {
|
||||||
return {
|
return {
|
||||||
id: "email",
|
id: "email",
|
||||||
type: "email",
|
type: "email",
|
||||||
@@ -337,7 +370,6 @@ export default function Email(config: EmailConfig): EmailConfig {
|
|||||||
throw new Error(`Email (${failed.join(", ")}) could not be sent`)
|
throw new Error(`Email (${failed.join(", ")}) could not be sent`)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// @ts-expect-error
|
|
||||||
options: config,
|
options: config,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ type EndpointRequest<C, R, P> = (
|
|||||||
callbackUrl: string
|
callbackUrl: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
) => Awaitable<R>
|
) => Awaitable<R> | void
|
||||||
|
|
||||||
/** Gives granular control of the request to the given endpoint */
|
/** Gives granular control of the request to the given endpoint */
|
||||||
interface AdvancedEndpointHandler<P extends UrlParams, C, R> {
|
interface AdvancedEndpointHandler<P extends UrlParams, C, R> {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@auth/sveltekit",
|
"name": "@auth/sveltekit",
|
||||||
"version": "0.3.5",
|
"version": "0.3.6",
|
||||||
"description": "Authentication for SvelteKit.",
|
"description": "Authentication for SvelteKit.",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"authentication",
|
"authentication",
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
"svelte-check": "^2.9.2",
|
"svelte-check": "^2.9.2",
|
||||||
"tslib": "^2.4.1",
|
"tslib": "^2.4.1",
|
||||||
"typescript": "^4.9.3",
|
"typescript": "^4.9.3",
|
||||||
"vite": "^4.0.0",
|
"vite": "^4.0.5",
|
||||||
"vitest": "^0.25.3"
|
"vitest": "^0.25.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -211,12 +211,13 @@ import type { AuthAction, AuthConfig, Session } from "@auth/core/types"
|
|||||||
|
|
||||||
export async function getSession(
|
export async function getSession(
|
||||||
req: Request,
|
req: Request,
|
||||||
config: AuthConfig
|
config: SvelteKitAuthConfig
|
||||||
): ReturnType<App.Locals["getSession"]> {
|
): ReturnType<App.Locals["getSession"]> {
|
||||||
config.secret ??= env.AUTH_SECRET
|
config.secret ??= env.AUTH_SECRET
|
||||||
config.trustHost ??= true
|
config.trustHost ??= true
|
||||||
|
|
||||||
const url = new URL("/api/auth/session", req.url)
|
const prefix = config.prefix ?? "/auth"
|
||||||
|
const url = new URL(prefix + "/session", req.url)
|
||||||
const request = new Request(url, { headers: req.headers })
|
const request = new Request(url, { headers: req.headers })
|
||||||
const response = await Auth(request, config)
|
const response = await Auth(request, config)
|
||||||
|
|
||||||
|
|||||||
8215
pnpm-lock.yaml
generated
8215
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
|||||||
packages:
|
packages:
|
||||||
- "packages/*"
|
- "packages/*"
|
||||||
- "apps/dev/*"
|
- "apps/dev/*"
|
||||||
- "apps/playgrounds/*"
|
|
||||||
- "docs"
|
- "docs"
|
||||||
|
|
||||||
14
turbo.json
14
turbo.json
@@ -3,7 +3,8 @@
|
|||||||
"pipeline": {
|
"pipeline": {
|
||||||
"build": {
|
"build": {
|
||||||
"dependsOn": ["^build"],
|
"dependsOn": ["^build"],
|
||||||
"outputs": ["dist/**/*", "*.js", "*.d.ts", "*.d.ts.map"]
|
"outputs": ["dist/**/*", "*.js", "*.d.ts", "*.d.ts.map"],
|
||||||
|
"outputMode": "new-only"
|
||||||
},
|
},
|
||||||
"next-auth#build": {
|
"next-auth#build": {
|
||||||
"dependsOn": ["^build"],
|
"dependsOn": ["^build"],
|
||||||
@@ -17,7 +18,8 @@
|
|||||||
"react/**",
|
"react/**",
|
||||||
"*.js",
|
"*.js",
|
||||||
"*.d.ts"
|
"*.d.ts"
|
||||||
]
|
],
|
||||||
|
"outputMode": "new-only"
|
||||||
},
|
},
|
||||||
"@auth/core#build": {
|
"@auth/core#build": {
|
||||||
"dependsOn": ["^build"],
|
"dependsOn": ["^build"],
|
||||||
@@ -29,11 +31,13 @@
|
|||||||
"*.d.ts.map",
|
"*.d.ts.map",
|
||||||
"src/lib/pages/styles.ts",
|
"src/lib/pages/styles.ts",
|
||||||
"src/providers/oauth-types.ts"
|
"src/providers/oauth-types.ts"
|
||||||
]
|
],
|
||||||
|
"outputMode": "new-only"
|
||||||
},
|
},
|
||||||
"@auth/sveltekit#build": {
|
"@auth/sveltekit#build": {
|
||||||
"dependsOn": ["^build"],
|
"dependsOn": ["^build"],
|
||||||
"outputs": [".svelte-kit/**", "client.*", "index.*"]
|
"outputs": [".svelte-kit/**", "client.*", "index.*"],
|
||||||
|
"outputMode": "new-only"
|
||||||
},
|
},
|
||||||
"clean": {
|
"clean": {
|
||||||
"cache": false
|
"cache": false
|
||||||
@@ -42,7 +46,7 @@
|
|||||||
"cache": false
|
"cache": false
|
||||||
},
|
},
|
||||||
"test": {
|
"test": {
|
||||||
"outputs": []
|
"outputMode": "new-only"
|
||||||
},
|
},
|
||||||
"e2e": {
|
"e2e": {
|
||||||
"outputs": ["playwright-report/**"]
|
"outputs": ["playwright-report/**"]
|
||||||
|
|||||||
Reference in New Issue
Block a user