From 844aa6a50cfee68c4347321adebcb9be2cae5963 Mon Sep 17 00:00:00 2001 From: danilo neves cruz Date: Mon, 20 Oct 2025 17:35:26 -0300 Subject: [PATCH 1/4] =?UTF-8?q?=E2=9E=95=20server:=20install=20better=20au?= =?UTF-8?q?th?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .changeset/pretty-chicken-hang.md | 5 + pnpm-lock.yaml | 380 +++++++++++++++++++++++++++++- pnpm-workspace.yaml | 1 + server/package.json | 2 + 4 files changed, 386 insertions(+), 2 deletions(-) create mode 100644 .changeset/pretty-chicken-hang.md diff --git a/.changeset/pretty-chicken-hang.md b/.changeset/pretty-chicken-hang.md new file mode 100644 index 0000000000..e538747de4 --- /dev/null +++ b/.changeset/pretty-chicken-hang.md @@ -0,0 +1,5 @@ +--- +"@exactly/server": patch +--- + +➕ install better auth diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e94d554d0e..c4397976b3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -772,6 +772,9 @@ importers: async-mutex: specifier: ^0.5.0 version: 0.5.0 + better-auth: + specifier: ^1.6.3 + version: 1.6.3(@opentelemetry/api@1.9.1)(better-sqlite3@12.9.0)(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(better-sqlite3@12.9.0)(kysely@0.28.16)(pg@8.20.0))(pg@8.20.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vitest@4.1.2) bullmq: specifier: ^5.71.1 version: 5.71.1 @@ -780,7 +783,7 @@ importers: version: 4.4.3 drizzle-orm: specifier: ^0.45.2 - version: 0.45.2(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(pg@8.20.0) + version: 0.45.2(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(better-sqlite3@12.9.0)(kysely@0.28.16)(pg@8.20.0) graphql: specifier: ^16.13.2 version: 16.13.2 @@ -851,6 +854,9 @@ importers: '@wagmi/core': specifier: ^3.4.1 version: 3.4.1(@tanstack/query-core@5.95.2)(@types/react@19.2.14)(ox@0.14.7(typescript@5.9.3)(zod@4.3.6))(react@19.2.0)(typescript@5.9.3)(use-sync-external-store@1.6.0(react@19.2.0))(viem@2.47.6(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@4.3.6)) + better-sqlite3: + specifier: ^12.9.0 + version: 12.9.0 drizzle-kit: specifier: ^0.31.10 version: 0.31.10 @@ -1692,6 +1698,83 @@ packages: resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} engines: {node: '>=18'} + '@better-auth/core@1.6.3': + resolution: {integrity: sha512-HefGR2SNfAi2RhT6XvSYViH4a0xoCGGL10bSDiv6sQGrmY6ulEQEV1X4nebTHeG0P6jdBmXAoEW3k37nhpk99w==} + peerDependencies: + '@better-auth/utils': 0.4.0 + '@better-fetch/fetch': 1.1.21 + '@cloudflare/workers-types': '>=4' + '@opentelemetry/api': ^1.9.0 + better-call: 1.3.5 + jose: ^6.1.0 + kysely: ^0.28.5 + nanostores: ^1.0.1 + peerDependenciesMeta: + '@cloudflare/workers-types': + optional: true + + '@better-auth/drizzle-adapter@1.6.3': + resolution: {integrity: sha512-P5erUYKoctOnOf+hd3umkOhOqJA+WuDByzmgnxZMBQLhgmusn5cgW10449B9aZu8HxIcU/tUQo/8ucwXHNzZ0A==} + peerDependencies: + '@better-auth/core': ^1.6.3 + '@better-auth/utils': 0.4.0 + drizzle-orm: '>=0.41.0' + peerDependenciesMeta: + drizzle-orm: + optional: true + + '@better-auth/kysely-adapter@1.6.3': + resolution: {integrity: sha512-4iZLGaajEdPMgtiTARINbNZGl6CPHSzlS0fl4ONWryP/52iakYhXYNBJIB70Ls1Xl+kEqYkBFmndfj/x4j18RQ==} + peerDependencies: + '@better-auth/core': ^1.6.3 + '@better-auth/utils': 0.4.0 + kysely: ^0.27.0 || ^0.28.0 + peerDependenciesMeta: + kysely: + optional: true + + '@better-auth/memory-adapter@1.6.3': + resolution: {integrity: sha512-0HCogGjUqVBl5j+7pkoovyIIAcCKsy8wiebDbTnedD99bCXQ+BhBAf8KQG1wMx6Nnc8fFwDuhSBhvTmCrdlmMQ==} + peerDependencies: + '@better-auth/core': ^1.6.3 + '@better-auth/utils': 0.4.0 + + '@better-auth/mongo-adapter@1.6.3': + resolution: {integrity: sha512-xer3hjuYaqcx/qMdZMXTUQz4ROLeS14Knas6OSY2gK8jgAidZO7twcb+wLgTbtJYmoXZqKFzSxoWuf6LxVvZCw==} + peerDependencies: + '@better-auth/core': ^1.6.3 + '@better-auth/utils': 0.4.0 + mongodb: ^6.0.0 || ^7.0.0 + peerDependenciesMeta: + mongodb: + optional: true + + '@better-auth/prisma-adapter@1.6.3': + resolution: {integrity: sha512-vrlGEdrpzNH+S0AjnQt6T9jeIxqYDNRwq/1lOQ50wS5OAzSjtZQ+Q/UCrBTF8ZBrYzQq28zIAuk6k2+xhqxZpQ==} + peerDependencies: + '@better-auth/core': ^1.6.3 + '@better-auth/utils': 0.4.0 + '@prisma/client': ^5.0.0 || ^6.0.0 || ^7.0.0 + prisma: ^5.0.0 || ^6.0.0 || ^7.0.0 + peerDependenciesMeta: + '@prisma/client': + optional: true + prisma: + optional: true + + '@better-auth/telemetry@1.6.3': + resolution: {integrity: sha512-Kw2LFnxBt36KF0Cfw46qcOaNtuqgr6kjJPDHKHCx3b7tbiSAEeEhZCc7wvWYbZPXkgI58IGi+bMrgnWjFCG1Zw==} + peerDependencies: + '@better-auth/core': ^1.6.3 + '@better-auth/utils': 0.4.0 + '@better-fetch/fetch': 1.1.21 + + '@better-auth/utils@0.4.0': + resolution: {integrity: sha512-RpMtLUIQAEWMgdPLNVbIF5ON2mm+CH0U3rCdUCU1VyeAUui4m38DyK7/aXMLZov2YDjG684pS1D0MBllrmgjQA==} + + '@better-fetch/fetch@1.1.21': + resolution: {integrity: sha512-/ImESw0sskqlVR94jB+5+Pxjf+xBwDZF/N5+y2/q4EqD7IARUTSpPfIo8uf39SYpCxyOCtbyYpUrZ3F/k0zT4A==} + '@braintree/sanitize-url@7.1.2': resolution: {integrity: sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==} @@ -3652,6 +3735,10 @@ packages: resolution: {integrity: sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==} engines: {node: ^14.21.3 || >=16} + '@noble/ciphers@2.2.0': + resolution: {integrity: sha512-Z6pjIZ/8IJcCGzb2S/0Px5J81yij85xASuk1teLNeg75bfT07MV3a/O2Mtn1I2se43k3lkVEcFaR10N4cgQcZA==} + engines: {node: '>= 20.19.0'} + '@noble/curves@1.9.1': resolution: {integrity: sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==} engines: {node: ^14.21.3 || >=16} @@ -6854,6 +6941,76 @@ packages: peerDependencies: ajv: 4.11.8 - 8 + better-auth@1.6.3: + resolution: {integrity: sha512-jMsoSYQyO8nNRuLEoCP+OUShLyeIGU8ioPYqra0IteLjnS3WNjHj21YE/COSJ/V/f0H5SInZiF+uXcEEHREDMQ==} + peerDependencies: + '@lynx-js/react': '*' + '@prisma/client': ^5.0.0 || ^6.0.0 || ^7.0.0 + '@sveltejs/kit': ^2.0.0 + '@tanstack/react-start': ^1.0.0 + '@tanstack/solid-start': ^1.0.0 + better-sqlite3: ^12.0.0 + drizzle-kit: '>=0.31.4' + drizzle-orm: '>=0.41.0' + mongodb: ^6.0.0 || ^7.0.0 + mysql2: ^3.0.0 + next: ^14.0.0 || ^15.0.0 || ^16.0.0 + pg: ^8.0.0 + prisma: ^5.0.0 || ^6.0.0 || ^7.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + solid-js: ^1.0.0 + svelte: ^4.0.0 || ^5.0.0 + vitest: ^2.0.0 || ^3.0.0 || ^4.0.0 + vue: ^3.0.0 + peerDependenciesMeta: + '@lynx-js/react': + optional: true + '@prisma/client': + optional: true + '@sveltejs/kit': + optional: true + '@tanstack/react-start': + optional: true + '@tanstack/solid-start': + optional: true + better-sqlite3: + optional: true + drizzle-kit: + optional: true + drizzle-orm: + optional: true + mongodb: + optional: true + mysql2: + optional: true + next: + optional: true + pg: + optional: true + prisma: + optional: true + react: + optional: true + react-dom: + optional: true + solid-js: + optional: true + svelte: + optional: true + vitest: + optional: true + vue: + optional: true + + better-call@1.3.5: + resolution: {integrity: sha512-kOFJkBP7utAQLEYrobZm3vkTH8mXq5GNgvjc5/XEST1ilVHaxXUXfeDeFlqoETMtyqS4+3/h4ONX2i++ebZrvA==} + peerDependencies: + zod: ^4.0.0 + peerDependenciesMeta: + zod: + optional: true + better-opn@3.0.2: resolution: {integrity: sha512-aVNobHnJqLiUelTaHat9DZ1qM2w0C0Eym4LPI/3JxOnSokGVdsl1T1kN7TFvsEAD8G47A6VKQ0TVHqbBnYMJlQ==} engines: {node: '>=12.0.0'} @@ -6862,6 +7019,10 @@ packages: resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} engines: {node: '>=4'} + better-sqlite3@12.9.0: + resolution: {integrity: sha512-wqUv4Gm3toFpHDQmaKD4QhZm3g1DjUBI0yzS4UBl6lElUmXFYdTQmmEDpAFa5o8FiFiymURypEnfVHzILKaxqQ==} + engines: {node: 20.x || 22.x || 23.x || 24.x || 25.x} + bidi-js@1.0.3: resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} @@ -6869,6 +7030,9 @@ packages: resolution: {integrity: sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==} engines: {node: '>=0.6'} + bindings@1.5.0: + resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} + birecord@0.1.1: resolution: {integrity: sha512-VUpsf/qykW0heRlC8LooCq28Kxn3mAqKohhDG/49rrsQ1dT1CXyj/pgXS+5BSRzFTR/3DyIBOqQOrGyZOh71Aw==} @@ -7092,6 +7256,9 @@ packages: resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} engines: {node: '>= 20.19.0'} + chownr@1.1.4: + resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + chownr@3.0.0: resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} engines: {node: '>=18'} @@ -8498,6 +8665,10 @@ packages: resolution: {integrity: sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==} engines: {node: ^18.19.0 || >=20.5.0} + expand-template@2.0.3: + resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} + engines: {node: '>=6'} + expect-type@1.3.0: resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} engines: {node: '>=12.0.0'} @@ -8867,6 +9038,9 @@ packages: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} + file-uri-to-path@1.0.0: + resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} + filelist@1.0.6: resolution: {integrity: sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==} @@ -9122,6 +9296,9 @@ packages: resolution: {integrity: sha512-VilgtJj/ALgGY77fiLam5iD336eSWi96Q15JSAG1zi8NRBysm3LXKdGnHb4m5cuyxvOLQQKWpBZAT6ni4FI2iQ==} engines: {node: '>=6'} + github-from-package@0.0.0: + resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} + github-slugger@2.0.0: resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==} @@ -10056,6 +10233,10 @@ packages: resolution: {integrity: sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==} engines: {node: '>= 8'} + kysely@0.28.16: + resolution: {integrity: sha512-3i5pmOiZvMDj00qhrIVbH0AnioVTx22DMP7Vn5At4yJO46iy+FM8Y/g61ltenLVSo3fiO8h8Q3QOFgf/gQ72ww==} + engines: {node: '>=20.0.0'} + lan-network@0.2.0: resolution: {integrity: sha512-EZgbsXMrGS+oK+Ta12mCjzBFse+SIewGdwrSTr5g+MSymnjpox2x05ceI20PQejJOFvOgzcXrfDk/SdY7dSCtw==} hasBin: true @@ -10840,6 +11021,9 @@ packages: typescript: optional: true + mkdirp-classic@0.5.3: + resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + mkdirp@1.0.4: resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} engines: {node: '>=10'} @@ -10900,6 +11084,13 @@ packages: nanospinner@1.2.2: resolution: {integrity: sha512-Zt/AmG6qRU3e+WnzGGLuMCEAO/dAu45stNbHY223tUxldaDAeE+FxSPsd9Q+j+paejmm0ZbrNVs5Sraqy3dRxA==} + nanostores@1.2.0: + resolution: {integrity: sha512-F0wCzbsH80G7XXo0Jd9/AVQC7ouWY6idUCTnMwW5t/Rv9W8qmO6endavDwg7TNp5GbugwSukFMVZqzPSrSMndg==} + engines: {node: ^20.0.0 || >=22.0.0} + + napi-build-utils@2.0.0: + resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==} + napi-postinstall@0.3.4: resolution: {integrity: sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} @@ -11497,6 +11688,12 @@ packages: resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} engines: {node: '>=0.10.0'} + prebuild-install@7.1.3: + resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==} + engines: {node: '>=10'} + deprecated: No longer maintained. Please contact the author of the relevant native addon; alternatives are available. + hasBin: true + prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -12163,6 +12360,9 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + rou3@0.7.12: + resolution: {integrity: sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg==} + roughjs@4.6.6: resolution: {integrity: sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==} @@ -12269,6 +12469,9 @@ packages: set-blocking@2.0.0: resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} + set-cookie-parser@3.1.0: + resolution: {integrity: sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw==} + set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} @@ -12360,6 +12563,12 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} + simple-concat@1.0.1: + resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} + + simple-get@4.0.1: + resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} + simple-plist@1.3.1: resolution: {integrity: sha512-iMSw5i0XseMnrhtIzRb7XpQEXepa9xhWxGUojHBL43SIpQuDQkh3Wpy67ZbDzZVr6EKxvwVChnVpdl8hEVLDiw==} @@ -12689,6 +12898,9 @@ packages: resolution: {integrity: sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==} engines: {node: '>=6'} + tar-fs@2.1.4: + resolution: {integrity: sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==} + tar-stream@2.2.0: resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} engines: {node: '>=6'} @@ -12848,6 +13060,9 @@ packages: resolution: {integrity: sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw==} engines: {node: '>= 6.0.0'} + tunnel-agent@0.6.0: + resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -14764,6 +14979,60 @@ snapshots: '@bcoe/v8-coverage@1.0.2': {} + '@better-auth/core@1.6.3(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.3.6))(jose@6.2.2)(kysely@0.28.16)(nanostores@1.2.0)': + dependencies: + '@better-auth/utils': 0.4.0 + '@better-fetch/fetch': 1.1.21 + '@opentelemetry/api': 1.9.1 + '@opentelemetry/semantic-conventions': 1.40.0 + '@standard-schema/spec': 1.1.0 + better-call: 1.3.5(zod@4.3.6) + jose: 6.2.2 + kysely: 0.28.16 + nanostores: 1.2.0 + zod: 4.3.6 + + '@better-auth/drizzle-adapter@1.6.3(@better-auth/core@1.6.3(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.3.6))(jose@6.2.2)(kysely@0.28.16)(nanostores@1.2.0))(@better-auth/utils@0.4.0)(drizzle-orm@0.45.2(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(better-sqlite3@12.9.0)(kysely@0.28.16)(pg@8.20.0))': + dependencies: + '@better-auth/core': 1.6.3(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.3.6))(jose@6.2.2)(kysely@0.28.16)(nanostores@1.2.0) + '@better-auth/utils': 0.4.0 + optionalDependencies: + drizzle-orm: 0.45.2(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(better-sqlite3@12.9.0)(kysely@0.28.16)(pg@8.20.0) + + '@better-auth/kysely-adapter@1.6.3(@better-auth/core@1.6.3(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.3.6))(jose@6.2.2)(kysely@0.28.16)(nanostores@1.2.0))(@better-auth/utils@0.4.0)(kysely@0.28.16)': + dependencies: + '@better-auth/core': 1.6.3(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.3.6))(jose@6.2.2)(kysely@0.28.16)(nanostores@1.2.0) + '@better-auth/utils': 0.4.0 + optionalDependencies: + kysely: 0.28.16 + + '@better-auth/memory-adapter@1.6.3(@better-auth/core@1.6.3(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.3.6))(jose@6.2.2)(kysely@0.28.16)(nanostores@1.2.0))(@better-auth/utils@0.4.0)': + dependencies: + '@better-auth/core': 1.6.3(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.3.6))(jose@6.2.2)(kysely@0.28.16)(nanostores@1.2.0) + '@better-auth/utils': 0.4.0 + + '@better-auth/mongo-adapter@1.6.3(@better-auth/core@1.6.3(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.3.6))(jose@6.2.2)(kysely@0.28.16)(nanostores@1.2.0))(@better-auth/utils@0.4.0)': + dependencies: + '@better-auth/core': 1.6.3(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.3.6))(jose@6.2.2)(kysely@0.28.16)(nanostores@1.2.0) + '@better-auth/utils': 0.4.0 + + '@better-auth/prisma-adapter@1.6.3(@better-auth/core@1.6.3(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.3.6))(jose@6.2.2)(kysely@0.28.16)(nanostores@1.2.0))(@better-auth/utils@0.4.0)': + dependencies: + '@better-auth/core': 1.6.3(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.3.6))(jose@6.2.2)(kysely@0.28.16)(nanostores@1.2.0) + '@better-auth/utils': 0.4.0 + + '@better-auth/telemetry@1.6.3(@better-auth/core@1.6.3(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.3.6))(jose@6.2.2)(kysely@0.28.16)(nanostores@1.2.0))(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)': + dependencies: + '@better-auth/core': 1.6.3(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.3.6))(jose@6.2.2)(kysely@0.28.16)(nanostores@1.2.0) + '@better-auth/utils': 0.4.0 + '@better-fetch/fetch': 1.1.21 + + '@better-auth/utils@0.4.0': + dependencies: + '@noble/hashes': 2.0.1 + + '@better-fetch/fetch@1.1.21': {} + '@braintree/sanitize-url@7.1.2': {} '@bufbuild/buf-darwin-arm64@1.66.1': @@ -17011,6 +17280,8 @@ snapshots: '@noble/ciphers@1.3.0': {} + '@noble/ciphers@2.2.0': {} + '@noble/curves@1.9.1': dependencies: '@noble/hashes': 1.8.0 @@ -21407,6 +21678,46 @@ snapshots: jsonpointer: 5.0.1 leven: 3.1.0 + better-auth@1.6.3(@opentelemetry/api@1.9.1)(better-sqlite3@12.9.0)(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(better-sqlite3@12.9.0)(kysely@0.28.16)(pg@8.20.0))(pg@8.20.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vitest@4.1.2): + dependencies: + '@better-auth/core': 1.6.3(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.3.6))(jose@6.2.2)(kysely@0.28.16)(nanostores@1.2.0) + '@better-auth/drizzle-adapter': 1.6.3(@better-auth/core@1.6.3(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.3.6))(jose@6.2.2)(kysely@0.28.16)(nanostores@1.2.0))(@better-auth/utils@0.4.0)(drizzle-orm@0.45.2(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(better-sqlite3@12.9.0)(kysely@0.28.16)(pg@8.20.0)) + '@better-auth/kysely-adapter': 1.6.3(@better-auth/core@1.6.3(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.3.6))(jose@6.2.2)(kysely@0.28.16)(nanostores@1.2.0))(@better-auth/utils@0.4.0)(kysely@0.28.16) + '@better-auth/memory-adapter': 1.6.3(@better-auth/core@1.6.3(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.3.6))(jose@6.2.2)(kysely@0.28.16)(nanostores@1.2.0))(@better-auth/utils@0.4.0) + '@better-auth/mongo-adapter': 1.6.3(@better-auth/core@1.6.3(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.3.6))(jose@6.2.2)(kysely@0.28.16)(nanostores@1.2.0))(@better-auth/utils@0.4.0) + '@better-auth/prisma-adapter': 1.6.3(@better-auth/core@1.6.3(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.3.6))(jose@6.2.2)(kysely@0.28.16)(nanostores@1.2.0))(@better-auth/utils@0.4.0) + '@better-auth/telemetry': 1.6.3(@better-auth/core@1.6.3(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.3.6))(jose@6.2.2)(kysely@0.28.16)(nanostores@1.2.0))(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21) + '@better-auth/utils': 0.4.0 + '@better-fetch/fetch': 1.1.21 + '@noble/ciphers': 2.2.0 + '@noble/hashes': 2.0.1 + better-call: 1.3.5(zod@4.3.6) + defu: 6.1.6 + jose: 6.2.2 + kysely: 0.28.16 + nanostores: 1.2.0 + zod: 4.3.6 + optionalDependencies: + better-sqlite3: 12.9.0 + drizzle-kit: 0.31.10 + drizzle-orm: 0.45.2(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(better-sqlite3@12.9.0)(kysely@0.28.16)(pg@8.20.0) + pg: 8.20.0 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + vitest: 4.1.2(@opentelemetry/api@1.9.1)(@types/node@25.5.0)(@vitest/ui@4.1.2)(vite@8.0.5(@types/node@25.5.0)(esbuild@0.27.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + transitivePeerDependencies: + - '@cloudflare/workers-types' + - '@opentelemetry/api' + + better-call@1.3.5(zod@4.3.6): + dependencies: + '@better-auth/utils': 0.4.0 + '@better-fetch/fetch': 1.1.21 + rou3: 0.7.12 + set-cookie-parser: 3.1.0 + optionalDependencies: + zod: 4.3.6 + better-opn@3.0.2: dependencies: open: 8.4.2 @@ -21415,12 +21726,21 @@ snapshots: dependencies: is-windows: 1.0.2 + better-sqlite3@12.9.0: + dependencies: + bindings: 1.5.0 + prebuild-install: 7.1.3 + bidi-js@1.0.3: dependencies: require-from-string: 2.0.2 big-integer@1.6.52: {} + bindings@1.5.0: + dependencies: + file-uri-to-path: 1.0.0 + birecord@0.1.1: {} bl@4.1.0: @@ -21676,6 +21996,8 @@ snapshots: dependencies: readdirp: 5.0.0 + chownr@1.1.4: {} + chownr@3.0.0: {} chrome-launcher@0.15.2: @@ -22448,10 +22770,12 @@ snapshots: esbuild: 0.25.12 tsx: 4.21.0 - drizzle-orm@0.45.2(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(pg@8.20.0): + drizzle-orm@0.45.2(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(better-sqlite3@12.9.0)(kysely@0.28.16)(pg@8.20.0): optionalDependencies: '@opentelemetry/api': 1.9.1 '@types/pg': 8.20.0 + better-sqlite3: 12.9.0 + kysely: 0.28.16 pg: 8.20.0 dset@3.1.4: {} @@ -23425,6 +23749,8 @@ snapshots: strip-final-newline: 4.0.0 yoctocolors: 2.1.2 + expand-template@2.0.3: {} + expect-type@1.3.0: {} expo-application@55.0.10(expo@55.0.9): @@ -23901,6 +24227,8 @@ snapshots: dependencies: flat-cache: 4.0.1 + file-uri-to-path@1.0.0: {} + filelist@1.0.6: dependencies: minimatch: 5.1.9 @@ -24176,6 +24504,8 @@ snapshots: getenv@2.0.0: {} + github-from-package@0.0.0: {} + github-slugger@2.0.0: {} gitmojis@3.15.0: {} @@ -25244,6 +25574,8 @@ snapshots: klona@2.0.6: {} + kysely@0.28.16: {} + lan-network@0.2.0: {} langium@4.2.1: @@ -26678,6 +27010,8 @@ snapshots: optionalDependencies: typescript: 5.9.3 + mkdirp-classic@0.5.3: {} + mkdirp@1.0.4: {} mlly@1.8.2: @@ -26737,6 +27071,10 @@ snapshots: dependencies: picocolors: 1.1.1 + nanostores@1.2.0: {} + + napi-build-utils@2.0.0: {} + napi-postinstall@0.3.4: {} natural-compare@1.4.0: {} @@ -27407,6 +27745,21 @@ snapshots: dependencies: xtend: 4.0.2 + prebuild-install@7.1.3: + dependencies: + detect-libc: 2.1.2 + expand-template: 2.0.3 + github-from-package: 0.0.0 + minimist: 1.2.8 + mkdirp-classic: 0.5.3 + napi-build-utils: 2.0.0 + node-abi: 3.89.0 + pump: 3.0.4 + rc: 1.2.8 + simple-get: 4.0.1 + tar-fs: 2.1.4 + tunnel-agent: 0.6.0 + prelude-ls@1.2.1: {} prettier-linter-helpers@1.0.1: @@ -28261,6 +28614,8 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.60.0 fsevents: 2.3.3 + rou3@0.7.12: {} + roughjs@4.6.6: dependencies: hachure-fill: 0.5.2 @@ -28405,6 +28760,8 @@ snapshots: set-blocking@2.0.0: {} + set-cookie-parser@3.1.0: {} + set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 @@ -28575,6 +28932,14 @@ snapshots: signal-exit@4.1.0: {} + simple-concat@1.0.1: {} + + simple-get@4.0.1: + dependencies: + decompress-response: 6.0.0 + once: 1.4.0 + simple-concat: 1.0.1 + simple-plist@1.3.1: dependencies: bplist-creator: 0.1.0 @@ -28991,6 +29356,13 @@ snapshots: tapable@2.3.2: {} + tar-fs@2.1.4: + dependencies: + chownr: 1.1.4 + mkdirp-classic: 0.5.3 + pump: 3.0.4 + tar-stream: 2.2.0 + tar-stream@2.2.0: dependencies: bl: 4.1.0 @@ -29137,6 +29509,10 @@ snapshots: dependencies: tslib: 1.14.1 + tunnel-agent@0.6.0: + dependencies: + safe-buffer: 5.2.1 + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 0a4e49db05..b01590d82c 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -23,6 +23,7 @@ catalog: ignoreWorkspaceRootCheck: true includeWorkspaceRoot: true minimumReleaseAge: 1440 +minimumReleaseAgeExclude: ["@better-auth/*", better-auth] packages: - "." - common diff --git a/server/package.json b/server/package.json index 6e4274819d..a6391b0325 100644 --- a/server/package.json +++ b/server/package.json @@ -45,6 +45,7 @@ "@types/debug": "^4.1.13", "@valibot/to-json-schema": "^1.6.0", "async-mutex": "^0.5.0", + "better-auth": "^1.6.3", "bullmq": "^5.71.1", "debug": "^4.4.3", "drizzle-orm": "^0.45.2", @@ -73,6 +74,7 @@ "@vitest/coverage-v8": "^4.1.2", "@vitest/ui": "^4.1.2", "@wagmi/core": "catalog:", + "better-sqlite3": "^12.9.0", "drizzle-kit": "^0.31.10", "embedded-postgres": "^18.3.0-beta.16", "eslint": "catalog:", From 06af0c367c89d403ff38c5ae5b91f687ac4ee87c Mon Sep 17 00:00:00 2001 From: danilo neves cruz Date: Fri, 12 Dec 2025 15:36:59 -0300 Subject: [PATCH 2/4] =?UTF-8?q?=F0=9F=97=83=EF=B8=8F=20server:=20setup=20b?= =?UTF-8?q?etter=20auth=20database=20tables?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/database/schema.ts | 193 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 193 insertions(+) diff --git a/server/database/schema.ts b/server/database/schema.ts index 27f8c9fa79..5fc799d621 100644 --- a/server/database/schema.ts +++ b/server/database/schema.ts @@ -1,6 +1,7 @@ import { relations } from "drizzle-orm"; import { bigint, + boolean, char, customType, index, @@ -12,6 +13,7 @@ import { primaryKey, serial, text, + timestamp, uniqueIndex, } from "drizzle-orm/pg-core"; @@ -108,3 +110,194 @@ export const exaPlugins = substreams.table( }, ({ address, account }) => [primaryKey({ columns: [address, account] })], ); + +export const users = pgTable("users", { + id: text("id").primaryKey(), + name: text("name").notNull(), + email: text("email").notNull().unique(), + emailVerified: boolean("email_verified").default(false).notNull(), + image: text("image"), + createdAt: timestamp("created_at").defaultNow().notNull(), + updatedAt: timestamp("updated_at") + .defaultNow() + .$onUpdate(() => /* @__PURE__ */ new Date()) + .notNull(), +}); + +export const sessions = pgTable( + "sessions", + { + id: text("id").primaryKey(), + expiresAt: timestamp("expires_at").notNull(), + token: text("token").notNull().unique(), + createdAt: timestamp("created_at").defaultNow().notNull(), + updatedAt: timestamp("updated_at") + .$onUpdate(() => /* @__PURE__ */ new Date()) + .notNull(), + ipAddress: text("ip_address"), + userAgent: text("user_agent"), + userId: text("user_id") + .notNull() + .references(() => users.id, { onDelete: "cascade" }), + activeOrganizationId: text("active_organization_id"), + }, + (table) => [index("sessions_user_idx").on(table.userId)], +); + +export const authenticators = pgTable( + "authenticators", + { + id: text("id").primaryKey(), + accountId: text("account_id").notNull(), + providerId: text("provider_id").notNull(), + userId: text("user_id") + .notNull() + .references(() => users.id, { onDelete: "cascade" }), + accessToken: text("access_token"), + refreshToken: text("refresh_token"), + idToken: text("id_token"), + accessTokenExpiresAt: timestamp("access_token_expires_at"), + refreshTokenExpiresAt: timestamp("refresh_token_expires_at"), + scope: text("scope"), + password: text("password"), + createdAt: timestamp("created_at").defaultNow().notNull(), + updatedAt: timestamp("updated_at") + .$onUpdate(() => /* @__PURE__ */ new Date()) + .notNull(), + }, + (table) => [index("authenticators_user_idx").on(table.userId)], +); + +export const verifications = pgTable( + "verifications", + { + id: text("id").primaryKey(), + identifier: text("identifier").notNull(), + value: text("value").notNull(), + expiresAt: timestamp("expires_at").notNull(), + createdAt: timestamp("created_at").defaultNow().notNull(), + updatedAt: timestamp("updated_at") + .defaultNow() + .$onUpdate(() => /* @__PURE__ */ new Date()) + .notNull(), + }, + (table) => [index("verifications_identifier_idx").on(table.identifier)], +); + +export const walletAddresses = pgTable( + "wallet_addresses", + { + id: text("id").primaryKey(), + userId: text("user_id") + .notNull() + .references(() => users.id, { onDelete: "cascade" }), + address: text("address").notNull(), + chainId: integer("chain_id").notNull(), + isPrimary: boolean("is_primary").default(false), + createdAt: timestamp("created_at").notNull(), + }, + (table) => [index("wallet_addresses_user_idx").on(table.userId)], +); + +export const organizations = pgTable("organizations", { + id: text("id").primaryKey(), + name: text("name").notNull(), + slug: text("slug").notNull().unique(), + logo: text("logo"), + createdAt: timestamp("created_at").notNull(), + metadata: text("metadata"), +}); + +export const members = pgTable( + "members", + { + id: text("id").primaryKey(), + organizationId: text("organization_id") + .notNull() + .references(() => organizations.id, { onDelete: "cascade" }), + userId: text("user_id") + .notNull() + .references(() => users.id, { onDelete: "cascade" }), + role: text("role").default("member").notNull(), + createdAt: timestamp("created_at").notNull(), + }, + (table) => [index("members_organization_idx").on(table.organizationId), index("members_user_idx").on(table.userId)], +); + +export const invitations = pgTable( + "invitations", + { + id: text("id").primaryKey(), + organizationId: text("organization_id") + .notNull() + .references(() => organizations.id, { onDelete: "cascade" }), + email: text("email").notNull(), + role: text("role"), + status: text("status").default("pending").notNull(), + expiresAt: timestamp("expires_at").notNull(), + createdAt: timestamp("created_at").defaultNow().notNull(), + inviterId: text("inviter_id") + .notNull() + .references(() => users.id, { onDelete: "cascade" }), + }, + (table) => [ + index("invitations_organization_idx").on(table.organizationId), + index("invitations_email_idx").on(table.email), + ], +); + +export const usersRelations = relations(users, ({ many }) => ({ + sessions: many(sessions), + authenticators: many(authenticators), + walletAddresses: many(walletAddresses), + members: many(members), + invitations: many(invitations), +})); + +export const sessionsRelations = relations(sessions, ({ one }) => ({ + user: one(users, { + fields: [sessions.userId], + references: [users.id], + }), +})); + +export const authenticatorsRelations = relations(authenticators, ({ one }) => ({ + user: one(users, { + fields: [authenticators.userId], + references: [users.id], + }), +})); + +export const walletAddressesRelations = relations(walletAddresses, ({ one }) => ({ + user: one(users, { + fields: [walletAddresses.userId], + references: [users.id], + }), +})); + +export const organizationsRelations = relations(organizations, ({ many }) => ({ + members: many(members), + invitations: many(invitations), +})); + +export const membersRelations = relations(members, ({ one }) => ({ + organization: one(organizations, { + fields: [members.organizationId], + references: [organizations.id], + }), + user: one(users, { + fields: [members.userId], + references: [users.id], + }), +})); + +export const invitationsRelations = relations(invitations, ({ one }) => ({ + organization: one(organizations, { + fields: [invitations.organizationId], + references: [organizations.id], + }), + user: one(users, { + fields: [invitations.inviterId], + references: [users.id], + }), +})); From ab159984c1241bd58e86a2040d39777c8003d897 Mon Sep 17 00:00:00 2001 From: nfmelendez Date: Wed, 10 Sep 2025 16:58:25 -0300 Subject: [PATCH 3/4] =?UTF-8?q?=F0=9F=9B=82=20server:=20setup=20better=20a?= =?UTF-8?q?uth?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .changeset/rare-pears-sort.md | 5 + docs/astro.config.ts | 2 +- .../docs/organization-authentication.md | 143 ++++++++++++++++++ server/api/index.ts | 4 +- server/database/index.ts | 19 ++- server/index.ts | 2 - server/script/openapi.ts | 5 +- server/utils/auth.ts | 67 ++++++++ 8 files changed, 240 insertions(+), 7 deletions(-) create mode 100644 .changeset/rare-pears-sort.md create mode 100644 docs/src/content/docs/organization-authentication.md create mode 100644 server/utils/auth.ts diff --git a/.changeset/rare-pears-sort.md b/.changeset/rare-pears-sort.md new file mode 100644 index 0000000000..384ab4c81e --- /dev/null +++ b/.changeset/rare-pears-sort.md @@ -0,0 +1,5 @@ +--- +"@exactly/server": patch +--- + +🛂 setup better auth diff --git a/docs/astro.config.ts b/docs/astro.config.ts index 5c83c0ebe2..f2fc8ce97f 100644 --- a/docs/astro.config.ts +++ b/docs/astro.config.ts @@ -15,7 +15,7 @@ export default defineConfig({ { base: "api", schema: "node_modules/@exactly/server/generated/openapi.json", sidebar: { collapsed: false } }, ]), ], - sidebar: openAPISidebarGroups, + sidebar: [{ label: "Docs", items: ["index", "organization-authentication"] }, ...openAPISidebarGroups], }), mermaid(), ], diff --git a/docs/src/content/docs/organization-authentication.md b/docs/src/content/docs/organization-authentication.md new file mode 100644 index 0000000000..12cb074fbc --- /dev/null +++ b/docs/src/content/docs/organization-authentication.md @@ -0,0 +1,143 @@ +--- +title: Organizations, authentication and authorization +sidebar: + label: Organizations and authentication + order: 10 +--- + +Creating organizations is permission-less. Any user can create an organization and will be the owner. +Then the owner can add members with admin role and those admins will be able to add more members with different roles. + +Better auth client and viem are the recommended libraries to use for authentication and signing using SIWE. + +## SIWE Authentication + +Example code to authenticate using SIWE, it will create the user if doesn't exist. +Note: Check viem account to use a private key instead of a mnemonic. + +```typescript +import { createAuthClient } from "better-auth/client"; +import { siweClient, organizationClient } from "better-auth/client/plugins"; +import { mnemonicToAccount } from "viem/accounts"; +import { optimismSepolia } from "viem/chains"; +import { createSiweMessage } from "viem/siwe"; + +const chainId = optimismSepolia.id; + +const authClient = createAuthClient({ + baseURL: "http://localhost:3000", + plugins: [siweClient(), organizationClient()], +}); + +const owner = mnemonicToAccount("test test test test test test test test test test test test"); + +authClient.siwe + .nonce({ + walletAddress: owner.address, + chainId, + }) + .then(async ({ data: nonceResult }) => { + //can be any statement + const statement = "i accept exa terms and conditions"; + const nonce = nonceResult?.nonce ?? ""; + const message = createSiweMessage({ + statement, + resources: ["https://exactly.github.io/exa"], + nonce, + uri: "https://localhost", + address: owner.address, + chainId, + scheme: "https", + version: "1", + domain: "localhost", + }); + const signature = await owner.signMessage({ message }); + + await authClient.siwe.verify( + { + message, + signature, + walletAddress: owner.address, + chainId, + }, + { + onSuccess: async (context) => { + // authentication successful, session cookie is now set + }, + onError: (context) => { + console.log("authorization error", context); + }, + }, + ); + }).catch((error: unknown) => { + console.error("nonce error", error); + }); +``` + +## Creating an organization + +Owner account will be the owner of the created organization. + +```typescript +const chainId = optimismSepolia.id; + +const authClient = createAuthClient({ + baseURL: "http://localhost:3000", + plugins: [siweClient(), organizationClient()], +}); + +const owner = mnemonicToAccount("test test test test test test test test test test test siwe"); + +authClient.siwe + .nonce({ + walletAddress: owner.address, + chainId, + }) + .then(async ({ data: nonceResult }) => { + const statement = `i accept exa terms and conditions`; + const nonce = nonceResult?.nonce ?? ""; + const message = createSiweMessage({ + statement, + resources: ["https://exactly.github.io/exa"], + nonce, + uri: `https://localhost`, + address: owner.address, + chainId, + scheme: "https", + version: "1", + domain: "localhost", + }); + const signature = await owner.signMessage({ message }); + + await authClient.siwe.verify( + { + message, + signature, + walletAddress: owner.address, + chainId, + }, + { + onSuccess: async (context) => { + const headers = new Headers(); + headers.set("cookie", context.response.headers.get("set-cookie") ?? ""); + const createOrganizationResult = await authClient.organization.create({ + fetchOptions: { headers }, + name: "Uphold", + slug: "uphold", + keepCurrentActiveOrganization: false, + }); + if (createOrganizationResult.data) { + console.log(`organization created id: ${createOrganizationResult.data.id}`); + } else { + console.error("Failed to create organization error:", createOrganizationResult.error); + } + }, + onError: (context) => { + console.log("authorization error", context); + }, + }, + ); + }).catch((error: unknown) => { + console.error("nonce error", error); + }); + ``` diff --git a/server/api/index.ts b/server/api/index.ts index ec7c913289..6f8d355079 100644 --- a/server/api/index.ts +++ b/server/api/index.ts @@ -11,6 +11,7 @@ import passkey from "./passkey"; import pax from "./pax"; import ramp from "./ramp"; import appOrigin from "../utils/appOrigin"; +import auth from "../utils/auth"; const api = new Hono() .use(cors({ origin: [appOrigin, "http://localhost:8081"], credentials: true, exposeHeaders: ["X-Session-Id"] })) @@ -26,7 +27,8 @@ const api = new Hono() .route("/kyc", kyc) .route("/passkey", passkey) // eslint-disable-line @typescript-eslint/no-deprecated -- // TODO remove .route("/pax", pax) - .route("/ramp", ramp); + .route("/ramp", ramp) + .on(["POST", "GET"], "/auth/*", (c) => auth.handler(c.req.raw)); export default api; export type ExaAPI = typeof api; diff --git a/server/database/index.ts b/server/database/index.ts index d288cb2f38..32f57021e7 100644 --- a/server/database/index.ts +++ b/server/database/index.ts @@ -1,3 +1,4 @@ +import { drizzleAdapter } from "better-auth/adapters/drizzle"; import { drizzle } from "drizzle-orm/node-postgres"; import { env } from "node:process"; @@ -5,6 +6,22 @@ import * as schema from "./schema"; if (!env.POSTGRES_URL) throw new Error("missing postgres url"); -export default drizzle(env.POSTGRES_URL, { schema }); +const database = drizzle(env.POSTGRES_URL, { schema }); + +export default database; export * from "./schema"; + +export const authAdapter = drizzleAdapter(database, { + provider: "pg", + schema: { + user: schema.users, + session: schema.sessions, + account: schema.authenticators, + verification: schema.verifications, + walletAddress: schema.walletAddresses, + organization: schema.organizations, + member: schema.members, + invitation: schema.invitations, + }, +}); diff --git a/server/index.ts b/server/index.ts index 667fbae63b..7c2654e182 100644 --- a/server/index.ts +++ b/server/index.ts @@ -27,9 +27,7 @@ import type { UnofficialStatusCode } from "hono/utils/http-status"; const app = new Hono(); app.use(trimTrailingSlash()); - app.route("/api", api); - app.route("/hooks/activity", activityHook); app.route("/hooks/block", block); app.route("/hooks/bridge", bridge); diff --git a/server/script/openapi.ts b/server/script/openapi.ts index 124c512409..07ddc0b34e 100644 --- a/server/script/openapi.ts +++ b/server/script/openapi.ts @@ -1,12 +1,12 @@ import { generateSpecs } from "hono-openapi"; import { writeFile } from "node:fs/promises"; -import { padHex } from "viem"; +import { padHex, zeroHash } from "viem"; import { version } from "../package.json"; process.env.ALCHEMY_ACTIVITY_ID = "activity"; process.env.ALCHEMY_WEBHOOKS_KEY = "webhooks"; -process.env.AUTH_SECRET = "auth"; +process.env.AUTH_SECRET = zeroHash; process.env.BRIDGE_API_KEY = "bridge"; process.env.BRIDGE_API_URL = "https://bridge.test"; process.env.EXPO_PUBLIC_ALCHEMY_API_KEY = " "; @@ -47,6 +47,7 @@ import("../api") in: "cookie", name: "credential_id", }, + siweAuth: { type: "apiKey", in: "cookie", name: "__Secure-better-auth.session_token" }, }, }, }, diff --git a/server/utils/auth.ts b/server/utils/auth.ts new file mode 100644 index 0000000000..2d0d8d9400 --- /dev/null +++ b/server/utils/auth.ts @@ -0,0 +1,67 @@ +import { captureException } from "@sentry/core"; +import { betterAuth } from "better-auth"; +import { organization, siwe } from "better-auth/plugins"; +import { createAccessControl } from "better-auth/plugins/access"; +import { adminAc, defaultStatements, memberAc, ownerAc } from "better-auth/plugins/organization/access"; +import { safeParse } from "valibot"; +import { verifyMessage } from "viem"; +import { generateSiweNonce } from "viem/siwe"; + +import domain from "@exactly/common/domain"; +import chain from "@exactly/common/generated/chain"; +import { Address, Hex } from "@exactly/common/validation"; + +import appOrigin from "./appOrigin"; +import authSecret from "./authSecret"; +import { authAdapter } from "../database/index"; +const ac = createAccessControl({ + ...defaultStatements, +}); + +export default betterAuth({ + database: authAdapter, + baseURL: appOrigin, + trustedOrigins: [appOrigin], + secret: authSecret, + user: { changeEmail: { enabled: true } }, + plugins: [ + siwe({ + domain, + emailDomainName: domain === "localhost" ? "localhost.com" : domain, + anonymous: true, + getNonce: () => Promise.resolve(generateSiweNonce()), + verifyMessage: async ({ message, signature, address, chainId }) => { + if (chainId !== chain.id) return false; + + const parsedAddress = safeParse(Address, address); + const parsedSignature = safeParse(Hex, signature); + if (!parsedAddress.success || !parsedSignature.success) return false; + try { + return await verifyMessage({ + address: parsedAddress.output, + message, + signature: parsedSignature.output, + }); + } catch (error) { + captureException(error, { level: "error" }); + return false; + } + }, + }), + organization({ + ac, + roles: { + admin: ac.newRole({ + ...adminAc.statements, + }), + owner: ac.newRole({ + ...ownerAc.statements, + }), + member: ac.newRole({ + ...memberAc.statements, + }), + }, + allowUserToCreateOrganization: () => true, + }), + ], +}); From 80fc2a38d596d74b2cc0f293b84208c18b3c5e96 Mon Sep 17 00:00:00 2001 From: nfmelendez Date: Wed, 15 Apr 2026 09:23:04 -0300 Subject: [PATCH 4/4] =?UTF-8?q?=F0=9F=9B=82=20server:=20add=20org=20auth?= =?UTF-8?q?=20middleware?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .changeset/brisk-otter-glides.md | 5 +++++ server/middleware/org.ts | 18 ++++++++++++++++++ server/test/middleware/org.test.ts | 24 ++++++++++++++++++++++++ 3 files changed, 47 insertions(+) create mode 100644 .changeset/brisk-otter-glides.md create mode 100644 server/middleware/org.ts create mode 100644 server/test/middleware/org.test.ts diff --git a/.changeset/brisk-otter-glides.md b/.changeset/brisk-otter-glides.md new file mode 100644 index 0000000000..937260182e --- /dev/null +++ b/.changeset/brisk-otter-glides.md @@ -0,0 +1,5 @@ +--- +"@exactly/server": patch +--- + +🛂 add org auth middleware diff --git a/server/middleware/org.ts b/server/middleware/org.ts new file mode 100644 index 0000000000..31088a3eaf --- /dev/null +++ b/server/middleware/org.ts @@ -0,0 +1,18 @@ +import { createMiddleware } from "hono/factory"; + +import auth from "../utils/auth"; + +import type { BlankInput, Env, Input } from "hono/types"; + +export default function org() { + return createMiddleware< + E & { Variables: { session: NonNullable>> } }, + P, + I + >(async (c, next) => { + const session = await auth.api.getSession({ headers: c.req.raw.headers }); + if (!session) return c.json({ code: "unauthorized" }, 401); + c.set("session", session); + await next(); + }); +} diff --git a/server/test/middleware/org.test.ts b/server/test/middleware/org.test.ts new file mode 100644 index 0000000000..0dd4c4b73b --- /dev/null +++ b/server/test/middleware/org.test.ts @@ -0,0 +1,24 @@ +import { Hono } from "hono"; +import { describe, expect, it, vi } from "vitest"; + +import org from "../../middleware/org"; +import betterAuth from "../../utils/auth"; + +describe("organization middleware", () => { + it("returns unauthorized when no session is present", async () => { + vi.spyOn(betterAuth.api, "getSession").mockResolvedValueOnce(null); + const app = new Hono().get("/", org(), (c) => c.text("ok")); + const response = await app.request("/"); + expect(response.status).toBe(401); + await expect(response.json()).resolves.toStrictEqual({ code: "unauthorized" }); + }); + + it("passes through and exposes the session when authenticated", async () => { + const fakeSession = { session: { id: "ses01", activeOrganizationId: "org01" }, user: { id: "user01" } }; + vi.spyOn(betterAuth.api, "getSession").mockResolvedValueOnce(fakeSession as never); + const app = new Hono().get("/", org(), (c) => c.json(c.var.session)); + const response = await app.request("/"); + expect(response.status).toBe(200); + await expect(response.json()).resolves.toStrictEqual(fakeSession); + }); +});