mirror of
https://github.com/SrIzan10/next-auth.git
synced 2026-05-01 10:55:20 +00:00
feat(adapters): add Hasura adapter (#5707)
* feat(adapters): add Hasura adapter * chore: formatting * chore: formatting * chore: formatting * chore: formatting * Merge branch 'main' into hasura-adapter * feat(adapter): add Hasura adapter * chore: update Hasura adapter readme * chore(docs): add Hasura * feat(adapter): move Hasura codegen to script * feat(adapter): remove docker from Hasura build * chore: resolve conflict * fix test * fix test --------- Co-authored-by: Thang Vu <hi@thvu.dev>
This commit is contained in:
1
.github/ISSUE_TEMPLATE/3_bug_adapter.yml
vendored
1
.github/ISSUE_TEMPLATE/3_bug_adapter.yml
vendored
@@ -29,6 +29,7 @@ body:
|
||||
- "@auth/dynamodb-adapter"
|
||||
- "@auth/fauna-adapter"
|
||||
- "@auth/firebase-adapter"
|
||||
- "@auth/hasura-adapter"
|
||||
- "@auth/kysely-adapter"
|
||||
- "@auth/mikro-orm-adapter"
|
||||
- "@auth/mongodb-adapter"
|
||||
|
||||
1
.github/pr-labeler.yml
vendored
1
.github/pr-labeler.yml
vendored
@@ -11,6 +11,7 @@ dynamodb: ["packages/adapter-dynamodb/**/*"]
|
||||
examples: ["apps/examples/**/*"]
|
||||
fauna: ["packages/adapter-fauna/**/*"]
|
||||
firebase: ["packages/adapter-firebase/**/*"]
|
||||
hasura: ["packages/adapter-hasura/**/*"]
|
||||
frameworks: ["packages/frameworks-*/**/*"]
|
||||
legacy: ["packages/next-auth/**/*"]
|
||||
mikro-orm: ["packages/adapter-mikro-orm/**/*"]
|
||||
|
||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -21,6 +21,7 @@ on:
|
||||
- "@auth/dynamodb-adapter"
|
||||
- "@auth/fauna-adapter"
|
||||
- "@auth/firebase-adapter"
|
||||
- "@auth/hasura-adapter"
|
||||
- "@auth/mikro-orm-adapter"
|
||||
- "@auth/mongodb-adapter"
|
||||
- "@auth/neo4j-adapter"
|
||||
@@ -45,6 +46,7 @@ on:
|
||||
- "adapter-dynamodb"
|
||||
- "adapter-fauna"
|
||||
- "adapter-firebase"
|
||||
- "adapter-hasura"
|
||||
- "adapter-mikro-orm"
|
||||
- "adapter-mongodb"
|
||||
- "adapter-neo4j"
|
||||
|
||||
@@ -37,6 +37,10 @@ Using an Auth.js / NextAuth.js adapter you can connect to any database service o
|
||||
<img src="/img/adapters/firebase.svg" width="40" />
|
||||
<h4 class="adapter-card__title">Firebase Adapter</h4>
|
||||
</a>
|
||||
<a href="/reference/adapter/hasura" class="adapter-card">
|
||||
<img src="/img/adapters/hasura.svg" width="40" />
|
||||
<h4 class="adapter-card__title">Hasura Adapter</h4>
|
||||
</a>
|
||||
<a href="/reference/adapter/kysely" class="adapter-card">
|
||||
<img src="/img/adapters/kysely.svg" width="40" />
|
||||
<h4 class="adapter-card__title">Kysely Adapter</h4>
|
||||
|
||||
@@ -284,6 +284,7 @@ const docusaurusConfig = {
|
||||
typedocAdapter("DynamoDB"),
|
||||
typedocAdapter("Fauna"),
|
||||
typedocAdapter("Firebase"),
|
||||
typedocAdapter("Hasura"),
|
||||
typedocAdapter("Kysely"),
|
||||
typedocAdapter("Mikro ORM"),
|
||||
typedocAdapter("MongoDB"),
|
||||
|
||||
@@ -55,6 +55,7 @@ module.exports = {
|
||||
{ type: "doc", id: "reference/adapter/dynamodb/index" },
|
||||
{ type: "doc", id: "reference/adapter/fauna/index" },
|
||||
{ type: "doc", id: "reference/adapter/firebase/index" },
|
||||
{ type: "doc", id: "reference/adapter/hasura/index" },
|
||||
{ type: "doc", id: "reference/adapter/kysely/index" },
|
||||
{ type: "doc", id: "reference/adapter/mikro-orm/index" },
|
||||
{ type: "doc", id: "reference/adapter/mongodb/index" },
|
||||
|
||||
1
docs/static/img/adapters/hasura.svg
vendored
Normal file
1
docs/static/img/adapters/hasura.svg
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Hasura</title><path d="M2.1216.0014c-.1221-.01-.2481.0345-.3354.1382C.448 1.7248.0338 6.021.7236 8.1721c.228.714.293 1.4694.1567 2.2072-.1332.7289-.2692 1.6118-.2692 2.2221C.6111 18.8946 5.712 24 12.0001 24c6.2909 0 11.3889-5.1024 11.3889-11.3986 0-.6133-.1334-1.4932-.2696-2.2221-.1362-.7378-.071-1.4931.157-2.2072.6899-2.151.2753-6.4473-1.0628-8.0325-.1746-.2074-.5033-.1777-.6483.0504l-1.6491 2.5895a1.2678 1.2678 0 0 1-1.6934.2757C16.4348 1.885 14.2973 1.2034 12 1.2034c-2.2973 0-4.435.6815-6.223 1.8518-.5507.3615-1.2849.2399-1.6934-.2757L2.4345.19a.4092.4092 0 0 0-.3129-.1886zM12 3.8046c1.347 0 2.626.3053 3.7716.8505 2.848 1.351 4.8582 4.1864 5.0358 7.499.009.157.0117.3143.0117.4743-.0029 4.865-3.958 8.8234-8.819 8.8234-4.8612 0-8.8165-3.9584-8.8165-8.8234 0-.16.006-.3173.012-.4743.1776-3.3155 2.1878-6.1509 5.0358-7.502C9.374 4.107 10.653 3.8047 12 3.8047zM9.5664 8.732a.2539.2539 0 0 0-.2192.1274c-.0444.08-.0444.1775.003.2546l1.8474 3.1112-2.4811 3.7866a.257.257 0 0 0-.0117.2607.252.252 0 0 0 .222.1333h1.8592a.2575.2575 0 0 0 .2133-.1157l1.3409-2.0976 1.202 2.0859a.2511.2511 0 0 0 .2191.1274h1.8325a.2471.2471 0 0 0 .2188-.1274c.0534-.08.0536-.175.0062-.2549l-2.2529-3.9081-1.9332-3.259a.2512.2512 0 0 0-.2192-.1244Z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
32
packages/adapter-hasura/README.md
Normal file
32
packages/adapter-hasura/README.md
Normal file
@@ -0,0 +1,32 @@
|
||||
<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://hasura.io" target="_blank">
|
||||
<img height="64px" src="https://authjs.dev/img/adapters/hasura.svg"/>
|
||||
</a>
|
||||
<h3 align="center"><b>Hasura Adapter</b> - NextAuth.js / Auth.js</a></h3>
|
||||
<p align="center" style="align: center;">
|
||||
<a href="https://npm.im/@auth/hasura-adapter">
|
||||
<img src="https://img.shields.io/badge/TypeScript-blue?style=flat-square" alt="TypeScript" />
|
||||
</a>
|
||||
<a href="https://npm.im/@auth/hasura-adapter">
|
||||
<img alt="npm" src="https://img.shields.io/npm/v/@auth/hasura-adapter?color=green&label=@auth/hasura-adapter&style=flat-square">
|
||||
</a>
|
||||
<a href="https://www.npmtrends.com/@auth/hasura-adapter">
|
||||
<img src="https://img.shields.io/npm/dm/@auth/hasura-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/hasura).
|
||||
|
||||
## Credit
|
||||
|
||||
Based on code from [Amruth Pillai](https://github.com/AmruthPillai)
|
||||
28
packages/adapter-hasura/codegen.ts
Normal file
28
packages/adapter-hasura/codegen.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import type { CodegenConfig } from "@graphql-codegen/cli"
|
||||
|
||||
const config: CodegenConfig = {
|
||||
overwrite: true,
|
||||
schema: "schema.graphql",
|
||||
emitLegacyCommonJSImports: false,
|
||||
documents: "src/**/*.graphql",
|
||||
generates: {
|
||||
"src/lib/": {
|
||||
preset: "client",
|
||||
config: {
|
||||
documentMode: "string",
|
||||
skipTypename: true,
|
||||
enumsAsTypes: true,
|
||||
strictScalars: true,
|
||||
useTypeImports: true,
|
||||
scalars: {
|
||||
timestamptz: "string",
|
||||
uuid: "string",
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
},
|
||||
},
|
||||
hooks: { afterAllFileWrite: ["prettier --write"] },
|
||||
}
|
||||
|
||||
export default config
|
||||
38
packages/adapter-hasura/docker-compose.yml
Normal file
38
packages/adapter-hasura/docker-compose.yml
Normal file
@@ -0,0 +1,38 @@
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16
|
||||
volumes:
|
||||
- db_data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
environment:
|
||||
POSTGRES_PASSWORD: postgrespassword
|
||||
graphql-engine:
|
||||
image: hasura/graphql-engine:v2.33.4.cli-migrations-v3
|
||||
ports:
|
||||
- "8080:8080"
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
## postgres database to store Hasura metadata
|
||||
HASURA_GRAPHQL_METADATA_DATABASE_URL: postgres://postgres:postgrespassword@postgres:5432/postgres
|
||||
## this env var can be used to add the above postgres database to Hasura as a data source. this can be removed/updated based on your needs
|
||||
PG_DATABASE_URL: postgres://postgres:postgrespassword@postgres:5432/postgres
|
||||
## enable the console served by server
|
||||
HASURA_GRAPHQL_ENABLE_CONSOLE: "true" # set to "false" to disable console
|
||||
## enable debugging mode. It is recommended to disable this in production
|
||||
HASURA_GRAPHQL_DEV_MODE: "true"
|
||||
HASURA_GRAPHQL_ENABLED_LOG_TYPES: startup, http-log, webhook-log, websocket-log, query-log
|
||||
## uncomment next line to run console offline (i.e load console assets from server instead of CDN)
|
||||
# HASURA_GRAPHQL_CONSOLE_ASSETS_DIR: /srv/console-assets
|
||||
## uncomment next line to set an admin secret
|
||||
HASURA_GRAPHQL_ADMIN_SECRET: myadminsecretkey
|
||||
volumes:
|
||||
- ./hasura/migrations:/hasura-migrations
|
||||
- ./hasura/metadata:/hasura-metadata
|
||||
volumes:
|
||||
db_data:
|
||||
17
packages/adapter-hasura/hasura.svg
Normal file
17
packages/adapter-hasura/hasura.svg
Normal file
@@ -0,0 +1,17 @@
|
||||
<svg width="285" height="84" viewBox="0 0 285 84" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_5273_22171)">
|
||||
<path d="M81.7705 28.7985C84.2257 21.3329 82.7488 6.43936 77.9912 0.94089C77.3677 0.219628 76.2075 0.322665 75.6894 1.11886L69.8294 10.1019C68.3809 11.9003 65.7722 12.3125 63.8056 11.0573C57.4466 6.99202 49.8506 4.6315 41.6884 4.6315C33.5262 4.6315 25.93 6.99202 19.571 11.0573C17.6143 12.3125 15.0055 11.891 13.5476 10.1019L7.68738 1.11886C7.16947 0.322665 6.00889 0.228995 5.38548 0.94089C0.628213 6.44869 -0.84884 21.3423 1.60652 28.7985C2.42178 31.2807 2.64238 33.9035 2.16282 36.4513C1.68325 38.9804 1.20369 42.0341 1.20369 44.1604C1.20369 65.9951 19.3312 83.6891 41.6788 83.6891C64.036 83.6891 82.1537 65.9858 82.1537 44.1604C82.1537 42.0341 81.6744 38.9804 81.195 36.4513C80.7249 33.9035 80.9548 31.2807 81.7705 28.7985ZM41.6788 74.8658C24.3954 74.8658 10.3442 61.1335 10.3442 44.2541C10.3442 43.7014 10.3633 43.1581 10.3921 42.6148C11.0156 31.1121 18.1706 21.2861 28.2894 16.6026C32.3465 14.7104 36.8928 13.6613 41.6884 13.6613C46.484 13.6613 51.0207 14.7104 55.0875 16.6119C65.2061 21.2955 72.3611 31.1308 72.9846 42.6242C73.0135 43.1675 73.0326 43.7201 73.0326 44.2634C73.0228 61.1335 58.9623 74.8658 41.6788 74.8658Z" fill="#1EB4D4"/>
|
||||
<path d="M55.2596 56.0378L47.251 42.4741L40.3837 31.1681C40.2206 30.8965 39.9233 30.7372 39.6068 30.7372H33.0463C32.7202 30.7372 32.4229 30.9058 32.2599 31.1869C32.0968 31.4585 32.1064 31.7957 32.2695 32.0673L38.8395 42.8488L30.0251 55.9816C29.8429 56.2533 29.8333 56.5996 29.9867 56.8806C30.1402 57.1615 30.4471 57.3398 30.7828 57.3398H37.3912C37.6981 57.3398 37.9859 57.1898 38.1489 56.9367L42.9158 49.668L47.1934 56.9089C47.3565 57.1806 47.6538 57.3491 47.9704 57.3491H54.4828C54.8087 57.3491 55.1062 57.1806 55.2596 56.9089C55.4229 56.6466 55.4229 56.3095 55.2596 56.0378Z" fill="#1EB4D4"/>
|
||||
<path d="M119.484 21.5306H127.838V66.2673H119.484V47.2056H110.046V66.2771H101.692V21.5306H110.046V40.9578H119.484V21.5306Z" fill="#1B2738"/>
|
||||
<path d="M153.61 66.276L151.864 56.9842H141.842L140.24 66.276H131.886L141.103 21.5391H152.353L162.012 66.276H153.61ZM142.935 50.8111H150.704L146.714 29.398L142.935 50.8111Z" fill="#1B2738"/>
|
||||
<path d="M180.619 58.1171V48.5443C180.619 47.7855 180.476 47.2797 180.188 47.0174C179.9 46.7551 179.363 46.624 178.586 46.624H172.707C167.719 46.624 165.226 44.2635 165.226 39.5331V28.555C165.226 23.8715 167.834 21.5391 173.061 21.5391H181.051C186.278 21.5391 188.887 23.8808 188.887 28.555V34.7934H180.466V29.6884C180.466 28.9297 180.322 28.4239 180.035 28.1616C179.747 27.8993 179.21 27.7682 178.433 27.7682H175.671C174.846 27.7682 174.289 27.8993 174.002 28.1616C173.714 28.4239 173.57 28.9297 173.57 29.6884V38.6901C173.57 39.4489 173.714 39.9547 174.002 40.2169C174.289 40.4792 174.846 40.6104 175.671 40.6104H181.406C186.489 40.6104 189.031 42.924 189.031 47.5607V59.2604C189.031 63.9437 186.394 66.276 181.118 66.276H173.273C167.998 66.276 165.36 63.9344 165.36 59.2604V53.0873H173.704V58.1171C173.704 58.8758 173.848 59.382 174.136 59.6444C174.424 59.9062 174.98 60.0377 175.805 60.0377H178.567C179.344 60.0377 179.871 59.9062 180.169 59.6444C180.466 59.382 180.619 58.8758 180.619 58.1171Z" fill="#1B2738"/>
|
||||
<path d="M211.32 21.5306H219.664V59.2517C219.664 63.9349 217.027 66.2673 211.752 66.2673H202.899C197.624 66.2673 194.986 63.9257 194.986 59.2517V21.5306H203.34V58.1182C203.34 58.8769 203.484 59.3826 203.772 59.6449C204.059 59.9073 204.597 60.0382 205.374 60.0382H209.219C210.044 60.0382 210.601 59.9073 210.888 59.6449C211.176 59.3826 211.32 58.8769 211.32 58.1182V21.5306Z" fill="#1B2738"/>
|
||||
<path d="M234.914 48.8355V66.2771H226.569V21.5306H243.412C248.687 21.5306 251.324 23.8723 251.324 28.5465V41.8102C251.324 45.6882 249.56 47.955 246.021 48.62L253.646 66.2771H244.64L237.667 48.8355H234.914ZM234.914 27.769V42.8031H240.937C241.714 42.8031 242.242 42.672 242.539 42.4097C242.827 42.1474 242.97 41.6416 242.97 40.8829V29.6893C242.97 28.9305 242.827 28.4247 242.539 28.1624C242.251 27.9001 241.714 27.769 240.937 27.769H234.914Z" fill="#1B2738"/>
|
||||
<path d="M276.589 66.276L274.843 56.9842H264.82L263.218 66.276H254.874L264.091 21.5391H275.342L285 66.276H276.589ZM265.923 50.8111H273.692L269.702 29.398L265.923 50.8111Z" fill="#1B2738"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_5273_22171">
|
||||
<rect width="285" height="84" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.3 KiB |
6
packages/adapter-hasura/hasura/config.yaml
Normal file
6
packages/adapter-hasura/hasura/config.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
version: 3
|
||||
endpoint: http://localhost:8080
|
||||
metadata_directory: metadata
|
||||
actions:
|
||||
kind: synchronous
|
||||
handler_webhook_baseurl: http://localhost:3000
|
||||
6
packages/adapter-hasura/hasura/metadata/actions.yaml
Normal file
6
packages/adapter-hasura/hasura/metadata/actions.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
actions: []
|
||||
custom_types:
|
||||
enums: []
|
||||
input_objects: []
|
||||
objects: []
|
||||
scalars: []
|
||||
1
packages/adapter-hasura/hasura/metadata/allow_list.yaml
Normal file
1
packages/adapter-hasura/hasura/metadata/allow_list.yaml
Normal file
@@ -0,0 +1 @@
|
||||
[]
|
||||
1
packages/adapter-hasura/hasura/metadata/api_limits.yaml
Normal file
1
packages/adapter-hasura/hasura/metadata/api_limits.yaml
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -0,0 +1 @@
|
||||
[]
|
||||
@@ -0,0 +1,11 @@
|
||||
- name: default
|
||||
kind: postgres
|
||||
configuration:
|
||||
connection_info:
|
||||
database_url:
|
||||
from_env: PG_DATABASE_URL
|
||||
isolation_level: read-committed
|
||||
use_prepared_statements: false
|
||||
customization:
|
||||
naming_convention: hasura-default
|
||||
tables: "!include default/tables/tables.yaml"
|
||||
@@ -0,0 +1,7 @@
|
||||
table:
|
||||
name: accounts
|
||||
schema: public
|
||||
object_relationships:
|
||||
- name: user
|
||||
using:
|
||||
foreign_key_constraint_on: userId
|
||||
@@ -0,0 +1,4 @@
|
||||
table:
|
||||
name: provider_type
|
||||
schema: public
|
||||
is_enum: true
|
||||
@@ -0,0 +1,7 @@
|
||||
table:
|
||||
name: sessions
|
||||
schema: public
|
||||
object_relationships:
|
||||
- name: user
|
||||
using:
|
||||
foreign_key_constraint_on: userId
|
||||
@@ -0,0 +1,18 @@
|
||||
table:
|
||||
name: users
|
||||
schema: public
|
||||
array_relationships:
|
||||
- name: accounts
|
||||
using:
|
||||
foreign_key_constraint_on:
|
||||
column: userId
|
||||
table:
|
||||
name: accounts
|
||||
schema: public
|
||||
- name: sessions
|
||||
using:
|
||||
foreign_key_constraint_on:
|
||||
column: userId
|
||||
table:
|
||||
name: sessions
|
||||
schema: public
|
||||
@@ -0,0 +1,3 @@
|
||||
table:
|
||||
name: verification_tokens
|
||||
schema: public
|
||||
@@ -0,0 +1,5 @@
|
||||
- "!include public_accounts.yaml"
|
||||
- "!include public_provider_type.yaml"
|
||||
- "!include public_sessions.yaml"
|
||||
- "!include public_users.yaml"
|
||||
- "!include public_verification_tokens.yaml"
|
||||
@@ -0,0 +1 @@
|
||||
disabled_for_roles: []
|
||||
@@ -0,0 +1 @@
|
||||
[]
|
||||
1
packages/adapter-hasura/hasura/metadata/network.yaml
Normal file
1
packages/adapter-hasura/hasura/metadata/network.yaml
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -0,0 +1 @@
|
||||
[]
|
||||
@@ -0,0 +1 @@
|
||||
[]
|
||||
@@ -0,0 +1 @@
|
||||
[]
|
||||
1
packages/adapter-hasura/hasura/metadata/version.yaml
Normal file
1
packages/adapter-hasura/hasura/metadata/version.yaml
Normal file
@@ -0,0 +1 @@
|
||||
version: 3
|
||||
@@ -0,0 +1,68 @@
|
||||
CREATE TABLE accounts (
|
||||
id uuid DEFAULT gen_random_uuid() NOT NULL,
|
||||
type text NOT NULL,
|
||||
provider text NOT NULL,
|
||||
"providerAccountId" text NOT NULL,
|
||||
refresh_token text,
|
||||
access_token text,
|
||||
expires_at integer,
|
||||
token_type text,
|
||||
scope text,
|
||||
id_token text,
|
||||
session_state text,
|
||||
"userId" uuid NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE sessions (
|
||||
id uuid DEFAULT gen_random_uuid() NOT NULL,
|
||||
"sessionToken" text NOT NULL,
|
||||
"userId" uuid NOT NULL,
|
||||
expires timestamptz NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE users (
|
||||
id uuid DEFAULT gen_random_uuid() NOT NULL,
|
||||
name text,
|
||||
email text NOT NULL,
|
||||
"emailVerified" timestamptz,
|
||||
image text
|
||||
);
|
||||
|
||||
CREATE TABLE verification_tokens (
|
||||
token text NOT NULL,
|
||||
identifier text NOT NULL,
|
||||
expires timestamptz NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE provider_type (
|
||||
value text NOT NULL
|
||||
);
|
||||
|
||||
ALTER TABLE ONLY accounts
|
||||
ADD CONSTRAINT accounts_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY sessions
|
||||
ADD CONSTRAINT sessions_pkey PRIMARY KEY ("sessionToken");
|
||||
|
||||
ALTER TABLE ONLY users
|
||||
ADD CONSTRAINT users_email_key UNIQUE (email);
|
||||
|
||||
ALTER TABLE ONLY users
|
||||
ADD CONSTRAINT users_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY verification_tokens
|
||||
ADD CONSTRAINT verification_tokens_pkey PRIMARY KEY (token);
|
||||
|
||||
ALTER TABLE ONLY provider_type
|
||||
ADD CONSTRAINT provider_type_pkey PRIMARY KEY (value);
|
||||
|
||||
ALTER TABLE ONLY accounts
|
||||
ADD CONSTRAINT "accounts_userId_fkey" FOREIGN KEY ("userId") REFERENCES public.users(id) ON UPDATE RESTRICT ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY sessions
|
||||
ADD CONSTRAINT "sessions_userId_fkey" FOREIGN KEY ("userId") REFERENCES public.users(id) ON UPDATE RESTRICT ON DELETE CASCADE;
|
||||
|
||||
INSERT INTO provider_type (value) VALUES ('credentials'), ('email'), ('oauth'), ('oidc');
|
||||
|
||||
ALTER TABLE ONLY accounts
|
||||
ADD CONSTRAINT "accounts_type_fkey" FOREIGN KEY ("type") REFERENCES public.provider_type(value) ON UPDATE RESTRICT ON DELETE RESTRICT;
|
||||
68
packages/adapter-hasura/package.json
Normal file
68
packages/adapter-hasura/package.json
Normal file
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"name": "@auth/hasura-adapter",
|
||||
"version": "0.1.0",
|
||||
"description": "Hasura adapter for Auth.js.",
|
||||
"homepage": "https://authjs.dev",
|
||||
"repository": "https://github.com/nextauthjs/next-auth",
|
||||
"bugs": {
|
||||
"url": "https://github.com/nextauthjs/next-auth/issues"
|
||||
},
|
||||
"contributors": [
|
||||
"Hasura Team",
|
||||
"Amruth Pillai <im.amruth@gmail.com>",
|
||||
"Arjun Yelamanchili"
|
||||
],
|
||||
"license": "ISC",
|
||||
"keywords": [
|
||||
"@auth",
|
||||
"authjs",
|
||||
"Auth.js",
|
||||
"next-auth",
|
||||
"next.js",
|
||||
"oauth",
|
||||
"hasura"
|
||||
],
|
||||
"type": "module",
|
||||
"types": "./index.d.ts",
|
||||
"files": [
|
||||
"*.js",
|
||||
"*.d.ts*",
|
||||
"lib",
|
||||
"src"
|
||||
],
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./index.d.ts",
|
||||
"import": "./index.js"
|
||||
}
|
||||
},
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "./tests/test.sh",
|
||||
"build": "graphql-codegen-esm --config codegen.ts && tsc"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"graphql": "^16",
|
||||
"graphql-request": "^6"
|
||||
},
|
||||
"dependencies": {
|
||||
"@auth/core": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@auth/adapter-test": "workspace:*",
|
||||
"@auth/tsconfig": "workspace:*",
|
||||
"@graphql-codegen/cli": "^5.0.0",
|
||||
"@graphql-codegen/client-preset": "^4.1.0",
|
||||
"@graphql-typed-document-node/core": "^3.2.0",
|
||||
"graphql": "^16.8.1",
|
||||
"graphql-request": "^6.1.0",
|
||||
"jest": "^29.7.0",
|
||||
"typescript": "^5.2.2"
|
||||
},
|
||||
"jest": {
|
||||
"preset": "@auth/adapter-test/jest"
|
||||
}
|
||||
}
|
||||
2291
packages/adapter-hasura/schema.graphql
Normal file
2291
packages/adapter-hasura/schema.graphql
Normal file
File diff suppressed because it is too large
Load Diff
493
packages/adapter-hasura/src/index.ts
Normal file
493
packages/adapter-hasura/src/index.ts
Normal file
@@ -0,0 +1,493 @@
|
||||
/**
|
||||
* <div style={{display: "flex", justifyContent: "space-between", alignItems: "center", padding: 16}}>
|
||||
* <p style={{fontWeight: "normal"}}>Official <a href="https://hasura.io/">Hasura</a> adapter for Auth.js / NextAuth.js.</p>
|
||||
* <a href="https://hasura.io/">
|
||||
* <img style={{display: "block"}} src="/img/adapters/hasura.svg" width="38" />
|
||||
* </a>
|
||||
* </div>
|
||||
*
|
||||
* ## Installation
|
||||
*
|
||||
* ```bash npm2yarn2pnpm
|
||||
* npm install next-auth @auth/hasura-adapter graphql graphql-request
|
||||
* ```
|
||||
*
|
||||
* @module @auth/hasura-adapter
|
||||
*/
|
||||
|
||||
import { GraphQLClient } from "graphql-request"
|
||||
import type { Adapter, AdapterAccount } from "@auth/core/adapters"
|
||||
import { useFragment } from "./lib"
|
||||
import {
|
||||
AccountFragmentDoc,
|
||||
CreateAccountDocument,
|
||||
CreateSessionDocument,
|
||||
CreateUserDocument,
|
||||
CreateVerificationTokenDocument,
|
||||
DeleteAccountDocument,
|
||||
DeleteSessionDocument,
|
||||
DeleteUserDocument,
|
||||
DeleteVerificationTokenDocument,
|
||||
GetSessionAndUserDocument,
|
||||
GetUserDocument,
|
||||
GetUsersDocument,
|
||||
SessionFragmentDoc,
|
||||
UpdateSessionDocument,
|
||||
UpdateUserDocument,
|
||||
UserFragmentDoc,
|
||||
VerificationTokenFragmentDoc,
|
||||
} from "./lib/graphql"
|
||||
import type {
|
||||
AccountFragment,
|
||||
CreateAccountMutation,
|
||||
CreateAccountMutationVariables,
|
||||
CreateSessionMutation,
|
||||
CreateSessionMutationVariables,
|
||||
CreateUserMutation,
|
||||
CreateUserMutationVariables,
|
||||
CreateVerificationTokenMutation,
|
||||
CreateVerificationTokenMutationVariables,
|
||||
DeleteAccountMutation,
|
||||
DeleteAccountMutationVariables,
|
||||
DeleteSessionMutation,
|
||||
DeleteSessionMutationVariables,
|
||||
DeleteUserMutation,
|
||||
DeleteUserMutationVariables,
|
||||
DeleteVerificationTokenMutation,
|
||||
DeleteVerificationTokenMutationVariables,
|
||||
GetSessionAndUserQuery,
|
||||
GetSessionAndUserQueryVariables,
|
||||
GetUserQuery,
|
||||
GetUserQueryVariables,
|
||||
GetUsersQuery,
|
||||
GetUsersQueryVariables,
|
||||
UpdateSessionMutation,
|
||||
UpdateSessionMutationVariables,
|
||||
UpdateUserMutation,
|
||||
UpdateUserMutationVariables,
|
||||
} from "./lib/graphql"
|
||||
import { formatDateConversion } from "./utils"
|
||||
import type { NonNullify } from "./utils"
|
||||
|
||||
interface HasuraAdapterArgs {
|
||||
endpoint: string
|
||||
adminSecret: string
|
||||
graphqlRequestOptions?: any
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* ## Setup
|
||||
*
|
||||
* 1. Create the next-auth schema in your database using SQL.
|
||||
*
|
||||
* ```sql
|
||||
* CREATE TABLE accounts (
|
||||
* id uuid DEFAULT gen_random_uuid() NOT NULL,
|
||||
* type text NOT NULL,
|
||||
* provider text NOT NULL,
|
||||
* "providerAccountId" text NOT NULL,
|
||||
* refresh_token text,
|
||||
* access_token text,
|
||||
* expires_at integer,
|
||||
* token_type text,
|
||||
* scope text,
|
||||
* id_token text,
|
||||
* session_state text,
|
||||
* "userId" uuid NOT NULL,
|
||||
* );
|
||||
*
|
||||
* CREATE TABLE sessions (
|
||||
* id uuid DEFAULT gen_random_uuid() NOT NULL,
|
||||
* "sessionToken" text NOT NULL,
|
||||
* "userId" uuid NOT NULL,
|
||||
* expires timestamptz NOT NULL
|
||||
* );
|
||||
*
|
||||
* CREATE TABLE users (
|
||||
* id uuid DEFAULT gen_random_uuid() NOT NULL,
|
||||
* name text,
|
||||
* email text NOT NULL,
|
||||
* "emailVerified" timestamptz,
|
||||
* image text
|
||||
* );
|
||||
*
|
||||
* CREATE TABLE verification_tokens (
|
||||
* token text NOT NULL,
|
||||
* identifier text NOT NULL,
|
||||
* expires timestamptz NOT NULL
|
||||
* );
|
||||
*
|
||||
* CREATE TABLE provider_type (
|
||||
* value text NOT NULL
|
||||
* );
|
||||
*
|
||||
* ALTER TABLE ONLY accounts
|
||||
* ADD CONSTRAINT accounts_pkey PRIMARY KEY (id);
|
||||
*
|
||||
* ALTER TABLE ONLY sessions
|
||||
* ADD CONSTRAINT sessions_pkey PRIMARY KEY ("sessionToken");
|
||||
*
|
||||
* ALTER TABLE ONLY users
|
||||
* ADD CONSTRAINT users_email_key UNIQUE (email);
|
||||
*
|
||||
* ALTER TABLE ONLY users
|
||||
* ADD CONSTRAINT users_pkey PRIMARY KEY (id);
|
||||
*
|
||||
* ALTER TABLE ONLY verification_tokens
|
||||
* ADD CONSTRAINT verification_tokens_pkey PRIMARY KEY (token);
|
||||
*
|
||||
* ALTER TABLE ONLY provider_type
|
||||
* ADD CONSTRAINT provider_type_pkey PRIMARY KEY (value);
|
||||
*
|
||||
* ALTER TABLE ONLY accounts
|
||||
* ADD CONSTRAINT "accounts_userId_fkey" FOREIGN KEY ("userId") REFERENCES public.users(id) ON UPDATE RESTRICT ON DELETE CASCADE;
|
||||
*
|
||||
* ALTER TABLE ONLY sessions
|
||||
* ADD CONSTRAINT "sessions_userId_fkey" FOREIGN KEY ("userId") REFERENCES public.users(id) ON UPDATE RESTRICT ON DELETE CASCADE;
|
||||
*
|
||||
* INSERT INTO provider_type (value) VALUES ('credentials'), ('email'), ('oauth'), ('oidc');
|
||||
*
|
||||
* ALTER TABLE ONLY accounts
|
||||
* ADD CONSTRAINT "accounts_type_fkey" FOREIGN KEY ("type") REFERENCES public.provider_type(value) ON UPDATE RESTRICT ON DELETE RESTRICT;
|
||||
* ```
|
||||
*
|
||||
* :::info
|
||||
* Tips: [Track all the tables and relationships in Hasura](https://hasura.io/docs/latest/schema/postgres/using-existing-database/#step-1-track-tablesviews)
|
||||
* :::
|
||||
*
|
||||
*1. Configure your NextAuth.js to use the Hasura Adapter:
|
||||
*
|
||||
* ```javascript title="pages/api/auth/[...nextauth].js"
|
||||
* import NextAuth from "next-auth"
|
||||
* import { HasuraAdapter } from "@next-auth/hasura-adapter"
|
||||
*
|
||||
* // For more information on each option (and a full list of options) go to
|
||||
* // https://next-auth.js.org/configuration/options
|
||||
* export default nextAuth({
|
||||
* adapter: HasuraAdapter({
|
||||
* endpoint: "<Hasura-GraphQL-endpoint>",
|
||||
* adminSecret: "<admin-secret>",
|
||||
* graphqlRequestOptions: {
|
||||
* // Optional graphql-request options
|
||||
* },
|
||||
* }),
|
||||
* ...
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
*## Passing dynamic headers
|
||||
*
|
||||
*If you use [graphql-request's dynamic headers feature](https://github.com/prisma-labs/graphql-request#passing-dynamic-headers-to-the-client), you are responsible for passing the 'X-Hasura-Admin-Secret' header
|
||||
*
|
||||
*```js
|
||||
*export default nextAuth({
|
||||
* adapter: HasuraAdapter({
|
||||
* endpoint: "<Hasura-GraphQL-endpoint>",
|
||||
* adminSecret: "<admin-secret>",
|
||||
* graphqlRequestOptions: {
|
||||
* headers: () => ({
|
||||
* "X-Hasura-Admin-Secret": "<admin-secret>",
|
||||
* // your headers here
|
||||
* }),
|
||||
* },
|
||||
* }),
|
||||
* ...
|
||||
*})
|
||||
*```
|
||||
|
||||
*/
|
||||
export const HasuraAdapter = ({
|
||||
endpoint,
|
||||
adminSecret,
|
||||
graphqlRequestOptions,
|
||||
}: HasuraAdapterArgs): Adapter => {
|
||||
const client = new GraphQLClient(endpoint, {
|
||||
fetch: fetch ?? undefined,
|
||||
...graphqlRequestOptions,
|
||||
headers:
|
||||
graphqlRequestOptions?.headers instanceof Function
|
||||
? graphqlRequestOptions?.headers
|
||||
: {
|
||||
...graphqlRequestOptions?.headers,
|
||||
"x-hasura-admin-secret": adminSecret,
|
||||
},
|
||||
})
|
||||
|
||||
return {
|
||||
// User
|
||||
createUser: async (newUser) => {
|
||||
const variables: CreateUserMutationVariables = {
|
||||
data: formatDateConversion(newUser, "emailVerified", "toDatabase"),
|
||||
}
|
||||
const { insert_users_one } = await client.request<CreateUserMutation>(
|
||||
CreateUserDocument.toString(),
|
||||
variables
|
||||
)
|
||||
const user = useFragment(UserFragmentDoc, insert_users_one)
|
||||
|
||||
if (!user) {
|
||||
throw new Error("Error creating user")
|
||||
}
|
||||
return formatDateConversion(user, "emailVerified", "toJS")
|
||||
},
|
||||
getUser: async (id) => {
|
||||
const variables: GetUserQueryVariables = { id }
|
||||
const { users_by_pk } = await client.request<GetUserQuery>(
|
||||
GetUserDocument.toString(),
|
||||
variables
|
||||
)
|
||||
const user = useFragment(UserFragmentDoc, users_by_pk)
|
||||
|
||||
return user ? formatDateConversion(user, "emailVerified", "toJS") : null
|
||||
},
|
||||
getUserByEmail: async (email) => {
|
||||
const variables: GetUsersQueryVariables = {
|
||||
where: { email: { _eq: email } },
|
||||
}
|
||||
const { users } = await client.request<GetUsersQuery>(
|
||||
GetUsersDocument.toString(),
|
||||
variables
|
||||
)
|
||||
|
||||
const user = useFragment(UserFragmentDoc, users?.[0])
|
||||
|
||||
if (!user) return null
|
||||
|
||||
return user ? formatDateConversion(user, "emailVerified", "toJS") : null
|
||||
},
|
||||
getUserByAccount: async ({ providerAccountId, provider }) => {
|
||||
const variables: GetUsersQueryVariables = {
|
||||
where: {
|
||||
accounts: {
|
||||
provider: { _eq: provider },
|
||||
providerAccountId: { _eq: providerAccountId },
|
||||
},
|
||||
},
|
||||
}
|
||||
const { users } = await client.request<GetUsersQuery>(
|
||||
GetUsersDocument.toString(),
|
||||
variables
|
||||
)
|
||||
const user = useFragment(UserFragmentDoc, users?.[0])
|
||||
|
||||
if (!user) return null
|
||||
|
||||
return user ? formatDateConversion(user, "emailVerified", "toJS") : null
|
||||
},
|
||||
updateUser: async ({ id, ...data }) => {
|
||||
const variables: UpdateUserMutationVariables = {
|
||||
id,
|
||||
data: formatDateConversion(data, "emailVerified", "toDatabase"),
|
||||
}
|
||||
const { update_users_by_pk } = await client.request<UpdateUserMutation>(
|
||||
UpdateUserDocument.toString(),
|
||||
variables
|
||||
)
|
||||
const user = useFragment(UserFragmentDoc, update_users_by_pk)
|
||||
|
||||
if (!user) {
|
||||
throw new Error("Error updating user")
|
||||
}
|
||||
|
||||
return formatDateConversion(user, "emailVerified", "toJS")
|
||||
},
|
||||
deleteUser: async (id) => {
|
||||
const variables: DeleteUserMutationVariables = {
|
||||
id,
|
||||
}
|
||||
const { delete_users_by_pk } = await client.request<DeleteUserMutation>(
|
||||
DeleteUserDocument.toString(),
|
||||
variables
|
||||
)
|
||||
const user = useFragment(UserFragmentDoc, delete_users_by_pk)
|
||||
|
||||
if (!user) {
|
||||
throw new Error("Error deleting user")
|
||||
}
|
||||
return formatDateConversion(user, "emailVerified", "toJS")
|
||||
},
|
||||
// Session
|
||||
createSession: async (data) => {
|
||||
const variables: CreateSessionMutationVariables = {
|
||||
data: formatDateConversion(data, "expires", "toDatabase"),
|
||||
}
|
||||
const { insert_sessions_one } =
|
||||
await client.request<CreateSessionMutation>(
|
||||
CreateSessionDocument.toString(),
|
||||
variables
|
||||
)
|
||||
const session = useFragment(SessionFragmentDoc, insert_sessions_one)
|
||||
|
||||
if (!session) {
|
||||
throw new Error("Error creating session")
|
||||
}
|
||||
session.expires
|
||||
return formatDateConversion(session, "expires", "toJS")
|
||||
},
|
||||
getSessionAndUser: async (sessionToken) => {
|
||||
const variables: GetSessionAndUserQueryVariables = {
|
||||
sessionToken,
|
||||
}
|
||||
const { sessions } = await client.request<GetSessionAndUserQuery>(
|
||||
GetSessionAndUserDocument.toString(),
|
||||
variables
|
||||
)
|
||||
const session = sessions?.[0]
|
||||
|
||||
if (!session) {
|
||||
return null
|
||||
}
|
||||
|
||||
const { user, ...sessionData } = session
|
||||
|
||||
return {
|
||||
session: formatDateConversion(
|
||||
useFragment(SessionFragmentDoc, sessionData),
|
||||
"expires",
|
||||
"toJS"
|
||||
),
|
||||
user: formatDateConversion(
|
||||
useFragment(UserFragmentDoc, user),
|
||||
"emailVerified",
|
||||
"toJS"
|
||||
),
|
||||
}
|
||||
},
|
||||
updateSession: async ({ sessionToken, ...data }) => {
|
||||
const variables: UpdateSessionMutationVariables = {
|
||||
sessionToken,
|
||||
data: formatDateConversion(data, "expires", "toDatabase"),
|
||||
}
|
||||
const { update_sessions } = await client.request<UpdateSessionMutation>(
|
||||
UpdateSessionDocument.toString(),
|
||||
variables
|
||||
)
|
||||
const session = update_sessions?.returning?.[0]
|
||||
|
||||
if (!session) {
|
||||
return null
|
||||
}
|
||||
|
||||
return formatDateConversion(
|
||||
useFragment(SessionFragmentDoc, session),
|
||||
"expires",
|
||||
"toJS"
|
||||
)
|
||||
},
|
||||
deleteSession: async (sessionToken) => {
|
||||
const variables: DeleteSessionMutationVariables = {
|
||||
sessionToken,
|
||||
}
|
||||
const { delete_sessions } = await client.request<DeleteSessionMutation>(
|
||||
DeleteSessionDocument.toString(),
|
||||
variables
|
||||
)
|
||||
const session = delete_sessions?.returning?.[0]
|
||||
|
||||
if (!session) {
|
||||
return null
|
||||
}
|
||||
|
||||
return formatDateConversion(
|
||||
useFragment(SessionFragmentDoc, session),
|
||||
"expires",
|
||||
"toJS"
|
||||
)
|
||||
},
|
||||
// Account
|
||||
linkAccount: async (data) => {
|
||||
const variables: CreateAccountMutationVariables = { data }
|
||||
const { insert_accounts_one } =
|
||||
await client.request<CreateAccountMutation>(
|
||||
CreateAccountDocument.toString(),
|
||||
variables
|
||||
)
|
||||
|
||||
if (!insert_accounts_one) {
|
||||
return
|
||||
}
|
||||
|
||||
const account = useFragment(
|
||||
AccountFragmentDoc,
|
||||
insert_accounts_one
|
||||
) as NonNullify<
|
||||
Omit<AccountFragment, "type"> & { type: "email" | "oauth" | "oidc" }
|
||||
>
|
||||
if (account) {
|
||||
return account as AdapterAccount
|
||||
}
|
||||
},
|
||||
unlinkAccount: async ({ providerAccountId, provider }) => {
|
||||
const variables: DeleteAccountMutationVariables = {
|
||||
provider,
|
||||
providerAccountId,
|
||||
}
|
||||
const { delete_accounts } = await client.request<DeleteAccountMutation>(
|
||||
DeleteAccountDocument.toString(),
|
||||
variables
|
||||
)
|
||||
const account = delete_accounts?.returning[0]
|
||||
|
||||
if (!account) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const accountFragment = useFragment(
|
||||
AccountFragmentDoc,
|
||||
account
|
||||
) as NonNullify<
|
||||
Omit<AccountFragment, "type"> & { type: "email" | "oauth" | "oidc" }
|
||||
>
|
||||
if (accountFragment) {
|
||||
return accountFragment as AdapterAccount
|
||||
}
|
||||
},
|
||||
// Verification Token
|
||||
createVerificationToken: async (data) => {
|
||||
const variables: CreateVerificationTokenMutationVariables = {
|
||||
data: formatDateConversion(data, "expires", "toDatabase"),
|
||||
}
|
||||
const { insert_verification_tokens_one } =
|
||||
await client.request<CreateVerificationTokenMutation>(
|
||||
CreateVerificationTokenDocument.toString(),
|
||||
variables
|
||||
)
|
||||
|
||||
if (!insert_verification_tokens_one) {
|
||||
return null
|
||||
}
|
||||
|
||||
return formatDateConversion(
|
||||
useFragment(
|
||||
VerificationTokenFragmentDoc,
|
||||
insert_verification_tokens_one
|
||||
),
|
||||
"expires",
|
||||
"toJS"
|
||||
)
|
||||
},
|
||||
useVerificationToken: async ({ identifier, token }) => {
|
||||
const variables: DeleteVerificationTokenMutationVariables = {
|
||||
identifier,
|
||||
token,
|
||||
}
|
||||
const { delete_verification_tokens } =
|
||||
await client.request<DeleteVerificationTokenMutation>(
|
||||
DeleteVerificationTokenDocument.toString(),
|
||||
variables
|
||||
)
|
||||
const verificationToken = delete_verification_tokens?.returning?.[0]
|
||||
|
||||
if (!verificationToken) {
|
||||
return null
|
||||
}
|
||||
|
||||
return formatDateConversion(
|
||||
useFragment(VerificationTokenFragmentDoc, verificationToken),
|
||||
"expires",
|
||||
"toJS"
|
||||
)
|
||||
},
|
||||
}
|
||||
}
|
||||
2
packages/adapter-hasura/src/lib/.gitignore
vendored
Normal file
2
packages/adapter-hasura/src/lib/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
||||
29
packages/adapter-hasura/src/queries/account.graphql
Normal file
29
packages/adapter-hasura/src/queries/account.graphql
Normal file
@@ -0,0 +1,29 @@
|
||||
mutation CreateAccount($data: accounts_insert_input!) {
|
||||
insert_accounts_one(object: $data) {
|
||||
...Account
|
||||
}
|
||||
}
|
||||
|
||||
mutation DeleteAccount($provider: String!, $providerAccountId: String!) {
|
||||
delete_accounts(
|
||||
where: {
|
||||
provider: { _eq: $provider }
|
||||
providerAccountId: { _eq: $providerAccountId }
|
||||
}
|
||||
) {
|
||||
returning {
|
||||
...Account
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
query GetAccount($provider: String!, $providerAccountId: String!) {
|
||||
accounts(
|
||||
where: {
|
||||
provider: { _eq: $provider }
|
||||
providerAccountId: { _eq: $providerAccountId }
|
||||
}
|
||||
) {
|
||||
...Account
|
||||
}
|
||||
}
|
||||
14
packages/adapter-hasura/src/queries/delete.graphql
Normal file
14
packages/adapter-hasura/src/queries/delete.graphql
Normal file
@@ -0,0 +1,14 @@
|
||||
mutation DeleteAll {
|
||||
delete_accounts(where: {}) {
|
||||
affected_rows
|
||||
}
|
||||
delete_sessions(where: {}) {
|
||||
affected_rows
|
||||
}
|
||||
delete_users(where: {}) {
|
||||
affected_rows
|
||||
}
|
||||
delete_verification_tokens(where: {}) {
|
||||
affected_rows
|
||||
}
|
||||
}
|
||||
35
packages/adapter-hasura/src/queries/fragments.graphql
Normal file
35
packages/adapter-hasura/src/queries/fragments.graphql
Normal file
@@ -0,0 +1,35 @@
|
||||
fragment User on users {
|
||||
id
|
||||
name
|
||||
email
|
||||
image
|
||||
emailVerified
|
||||
}
|
||||
|
||||
fragment Session on sessions {
|
||||
id
|
||||
userId
|
||||
expires
|
||||
sessionToken
|
||||
}
|
||||
|
||||
fragment Account on accounts {
|
||||
id
|
||||
type
|
||||
scope
|
||||
userId
|
||||
id_token
|
||||
provider
|
||||
expires_at
|
||||
token_type
|
||||
access_token
|
||||
refresh_token
|
||||
session_state
|
||||
providerAccountId
|
||||
}
|
||||
|
||||
fragment VerificationToken on verification_tokens {
|
||||
token
|
||||
expires
|
||||
identifier
|
||||
}
|
||||
39
packages/adapter-hasura/src/queries/session.graphql
Normal file
39
packages/adapter-hasura/src/queries/session.graphql
Normal file
@@ -0,0 +1,39 @@
|
||||
query GetSessionAndUser($sessionToken: String!) {
|
||||
sessions(where: { sessionToken: { _eq: $sessionToken } }) {
|
||||
...Session
|
||||
user {
|
||||
...User
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
query GetSession($sessionToken: String!) {
|
||||
sessions_by_pk(sessionToken: $sessionToken) {
|
||||
...Session
|
||||
}
|
||||
}
|
||||
|
||||
mutation CreateSession($data: sessions_insert_input!) {
|
||||
insert_sessions_one(object: $data) {
|
||||
...Session
|
||||
}
|
||||
}
|
||||
|
||||
mutation UpdateSession($sessionToken: String, $data: sessions_set_input!) {
|
||||
update_sessions(
|
||||
where: { sessionToken: { _eq: $sessionToken } }
|
||||
_set: $data
|
||||
) {
|
||||
returning {
|
||||
...Session
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mutation DeleteSession($sessionToken: String!) {
|
||||
delete_sessions(where: { sessionToken: { _eq: $sessionToken } }) {
|
||||
returning {
|
||||
...Session
|
||||
}
|
||||
}
|
||||
}
|
||||
29
packages/adapter-hasura/src/queries/user.graphql
Normal file
29
packages/adapter-hasura/src/queries/user.graphql
Normal file
@@ -0,0 +1,29 @@
|
||||
query GetUser($id: uuid!) {
|
||||
users_by_pk(id: $id) {
|
||||
...User
|
||||
}
|
||||
}
|
||||
|
||||
query GetUsers($where: users_bool_exp!) {
|
||||
users(where: $where) {
|
||||
...User
|
||||
}
|
||||
}
|
||||
|
||||
mutation CreateUser($data: users_insert_input!) {
|
||||
insert_users_one(object: $data) {
|
||||
...User
|
||||
}
|
||||
}
|
||||
|
||||
mutation UpdateUser($id: uuid!, $data: users_set_input!) {
|
||||
update_users_by_pk(pk_columns: { id: $id }, _set: $data) {
|
||||
...User
|
||||
}
|
||||
}
|
||||
|
||||
mutation DeleteUser($id: uuid!) {
|
||||
delete_users_by_pk(id: $id) {
|
||||
...User
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
mutation CreateVerificationToken($data: verification_tokens_insert_input!) {
|
||||
insert_verification_tokens_one(object: $data) {
|
||||
...VerificationToken
|
||||
}
|
||||
}
|
||||
|
||||
mutation DeleteVerificationToken($identifier: String!, $token: String!) {
|
||||
delete_verification_tokens(
|
||||
where: { token: { _eq: $token }, identifier: { _eq: $identifier } }
|
||||
) {
|
||||
returning {
|
||||
...VerificationToken
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
query GetVerificationToken($identifier: String!, $token: String!) {
|
||||
verification_tokens(
|
||||
where: { token: { _eq: $token }, identifier: { _eq: $identifier } }
|
||||
) {
|
||||
...VerificationToken
|
||||
}
|
||||
}
|
||||
47
packages/adapter-hasura/src/utils.ts
Normal file
47
packages/adapter-hasura/src/utils.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
export type NonNullify<T> = {
|
||||
[K in keyof T]: T[K] extends null | infer U ? U : T[K]
|
||||
}
|
||||
|
||||
type FormatToJS<T, K extends keyof T> = T[K] extends string
|
||||
? Omit<T, K> & Record<K, Date>
|
||||
: Omit<T, K> & Record<K, Date | null>
|
||||
|
||||
type FormatToDatabase<T, K extends keyof T> = T[K] extends Date
|
||||
? Omit<T, K> & Record<K, string>
|
||||
: Omit<T, K> & Record<K, string | null>
|
||||
|
||||
export function formatDateConversion<T, K extends keyof T>(
|
||||
object: T,
|
||||
key: K,
|
||||
direction: "toJS"
|
||||
): FormatToJS<T, K>
|
||||
|
||||
export function formatDateConversion<T, K extends keyof T>(
|
||||
object: T,
|
||||
key: K,
|
||||
direction: "toDatabase"
|
||||
): FormatToDatabase<T, K>
|
||||
|
||||
export function formatDateConversion<T, K extends keyof T>(
|
||||
object: T,
|
||||
key: K,
|
||||
direction: "toJS" | "toDatabase"
|
||||
) {
|
||||
if (!object) return object
|
||||
|
||||
const value = object[key]
|
||||
|
||||
if (value === undefined) return object
|
||||
|
||||
if (direction === "toJS") {
|
||||
return {
|
||||
...object,
|
||||
[key]: value ? new Date(value as string) : null,
|
||||
} as FormatToJS<T, K>
|
||||
} else {
|
||||
return {
|
||||
...object,
|
||||
[key]: value ? (value as unknown as Date).toISOString() : null,
|
||||
} as FormatToDatabase<T, K>
|
||||
}
|
||||
}
|
||||
110
packages/adapter-hasura/tests/index.test.ts
Normal file
110
packages/adapter-hasura/tests/index.test.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
import { runBasicTests } from "@auth/adapter-test"
|
||||
import { GraphQLClient } from "graphql-request"
|
||||
import { HasuraAdapter } from "../src"
|
||||
import { useFragment } from "../src/lib"
|
||||
import {
|
||||
AccountFragmentDoc,
|
||||
DeleteAllDocument,
|
||||
GetAccountDocument,
|
||||
GetSessionDocument,
|
||||
GetUserDocument,
|
||||
GetVerificationTokenDocument,
|
||||
SessionFragmentDoc,
|
||||
UserFragmentDoc,
|
||||
VerificationTokenFragmentDoc,
|
||||
} from "../src/lib/graphql"
|
||||
import type {
|
||||
GetAccountQuery,
|
||||
GetAccountQueryVariables,
|
||||
GetSessionQuery,
|
||||
GetSessionQueryVariables,
|
||||
GetUserQuery,
|
||||
GetUserQueryVariables,
|
||||
GetVerificationTokenQuery,
|
||||
GetVerificationTokenQueryVariables,
|
||||
} from "../src/lib/graphql"
|
||||
import { formatDateConversion } from "../src/utils"
|
||||
|
||||
const client = new GraphQLClient("http://localhost:8080/v1/graphql", {
|
||||
headers: {
|
||||
"x-hasura-admin-secret": "myadminsecretkey",
|
||||
},
|
||||
})
|
||||
|
||||
runBasicTests({
|
||||
adapter: HasuraAdapter({
|
||||
adminSecret: "myadminsecretkey",
|
||||
endpoint: "http://localhost:8080/v1/graphql",
|
||||
}),
|
||||
db: {
|
||||
connect: async () => {
|
||||
await client.request(DeleteAllDocument.toString())
|
||||
},
|
||||
disconnect: async () => {
|
||||
await client.request(DeleteAllDocument.toString())
|
||||
},
|
||||
user: async (id) => {
|
||||
const variables: GetUserQueryVariables = { id }
|
||||
const { users_by_pk } = await client.request<GetUserQuery>(
|
||||
GetUserDocument.toString(),
|
||||
variables
|
||||
)
|
||||
const user = useFragment(UserFragmentDoc, users_by_pk)
|
||||
|
||||
return user ? formatDateConversion(user, "emailVerified", "toJS") : null
|
||||
},
|
||||
account: async ({ providerAccountId, provider }) => {
|
||||
const variables: GetAccountQueryVariables = {
|
||||
provider,
|
||||
providerAccountId,
|
||||
}
|
||||
const { accounts } = await client.request<GetAccountQuery>(
|
||||
GetAccountDocument.toString(),
|
||||
variables
|
||||
)
|
||||
|
||||
const account = useFragment(AccountFragmentDoc, accounts?.[0])
|
||||
return account ?? null
|
||||
},
|
||||
session: async (sessionToken) => {
|
||||
const variables: GetSessionQueryVariables = {
|
||||
sessionToken,
|
||||
}
|
||||
const { sessions_by_pk } = await client.request<GetSessionQuery>(
|
||||
GetSessionDocument.toString(),
|
||||
variables
|
||||
)
|
||||
if (!sessions_by_pk) {
|
||||
return null
|
||||
}
|
||||
|
||||
return formatDateConversion(
|
||||
useFragment(SessionFragmentDoc, sessions_by_pk),
|
||||
"expires",
|
||||
"toJS"
|
||||
)
|
||||
},
|
||||
verificationToken: async ({ identifier, token }) => {
|
||||
const variables: GetVerificationTokenQueryVariables = {
|
||||
identifier,
|
||||
token,
|
||||
}
|
||||
const { verification_tokens } =
|
||||
await client.request<GetVerificationTokenQuery>(
|
||||
GetVerificationTokenDocument.toString(),
|
||||
variables
|
||||
)
|
||||
const verificationToken = verification_tokens?.[0]
|
||||
|
||||
if (!verificationToken) {
|
||||
return null
|
||||
}
|
||||
|
||||
return formatDateConversion(
|
||||
useFragment(VerificationTokenFragmentDoc, verificationToken),
|
||||
"expires",
|
||||
"toJS"
|
||||
)
|
||||
},
|
||||
},
|
||||
})
|
||||
14
packages/adapter-hasura/tests/test.sh
Executable file
14
packages/adapter-hasura/tests/test.sh
Executable file
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Start Hasura
|
||||
docker-compose up -d
|
||||
|
||||
echo "Waiting 5 sec for Hasura to start..."
|
||||
sleep 5
|
||||
|
||||
# Always stop container, but exit with 1 when tests are failing
|
||||
if npx jest;then
|
||||
docker compose down -v
|
||||
else
|
||||
docker compose down -v && exit 1
|
||||
fi
|
||||
21
packages/adapter-hasura/tsconfig.json
Normal file
21
packages/adapter-hasura/tsconfig.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"extends": "@auth/tsconfig/tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"baseUrl": ".",
|
||||
"isolatedModules": true,
|
||||
"target": "ES2020",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "node",
|
||||
"outDir": ".",
|
||||
"rootDir": "src",
|
||||
"skipDefaultLibCheck": true,
|
||||
"strictNullChecks": true,
|
||||
"stripInternal": true,
|
||||
"declarationMap": true,
|
||||
"declaration": true,
|
||||
"verbatimModuleSyntax": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["*.js", "*.d.ts"]
|
||||
}
|
||||
2318
pnpm-lock.yaml
generated
2318
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -91,6 +91,7 @@
|
||||
"@auth/dynamodb-adapter#build",
|
||||
"@auth/fauna-adapter#build",
|
||||
"@auth/firebase-adapter#build",
|
||||
"@auth/hasura-adapter#build",
|
||||
"@auth/kysely-adapter#build",
|
||||
"@auth/mikro-orm-adapter#build",
|
||||
"@auth/mongodb-adapter#build",
|
||||
@@ -121,6 +122,7 @@
|
||||
"@auth/dynamodb-adapter#build",
|
||||
"@auth/fauna-adapter#build",
|
||||
"@auth/firebase-adapter#build",
|
||||
"@auth/hasura-adapter#build",
|
||||
"@auth/kysely-adapter#build",
|
||||
"@auth/mikro-orm-adapter#build",
|
||||
"@auth/mongodb-adapter#build",
|
||||
|
||||
Reference in New Issue
Block a user