diff --git a/Dockerfile b/Dockerfile index ddbb9ee..ba08535 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,11 +23,11 @@ WORKDIR /app # Declare build arguments for Next.js public variables ARG NEXT_PUBLIC_VAPID_PUBLIC_KEY -ARG NEXTAUTH_URL +ARG BETTER_AUTH_URL # Set environment variables from build args ENV NEXT_PUBLIC_VAPID_PUBLIC_KEY=$NEXT_PUBLIC_VAPID_PUBLIC_KEY -ENV NEXTAUTH_URL=$NEXTAUTH_URL +ENV BETTER_AUTH_URL=$BETTER_AUTH_URL # Copy package files COPY package.json pnpm-lock.yaml ./ diff --git a/README.md b/README.md index c7784f7..51faf50 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ The purpose of this repository is to proved a template for a base starting point for C4G course projects. This template is not mandatory, but it captures the needs of a typical C4G project that uses a website and a database. A team that uses the template for building their project can also use it to meet course reporting requirements. Features include: -- Authentication using google with 2 provided test accounts +- Authentication with email/password and Google (2 test accounts provided) - CI/CD which will deploy the application and DB to the [c4g.dev](https://c4g.dev) server using your applications sub-domain. - A user management page for `ADMIN` users. Base roles are `ADMIN` and `STAFF`. - Project specific deliverable pages to quickly update as we progress through the course. Found by clicking the `Team` link in the footer of the application. @@ -100,7 +100,7 @@ This project uses [`next/font`](https://nextjs.org/docs/app/building-your-applic - [Shadcn](https://ui.shadcn.com/) - UI component library - [RadixUI](https://www.radix-ui.com/) - UI component library - [Lucide-React](https://lucide.dev/guide/packages/lucide-react) - UI icons -- [Next-Auth](https://authjs.dev/) - authentication with google +- [Better Auth](https://better-auth.com/) - authentication with email/password and google - [Ag-Grid](https://www.ag-grid.com/) - grid / table component - [Resend](https://resend.com) - emails @@ -166,6 +166,6 @@ The migration container (`template-migrations`) runs once per deployment and aut All required environment variables must be set in your `.env` file before deployment. See `example.env` for the complete list. Key variables: - `DATABASE_*`: PostgreSQL connection settings -- `AUTH_*`: NextAuth configuration +- `AUTH_*` / `BETTER_AUTH_URL`: Better Auth configuration - `NEXT_PUBLIC_VAPID_PUBLIC_KEY` / `VAPID_PRIVATE_KEY`: Push notification keys - `RESEND_API_KEY`: Email service configuration diff --git a/docker-compose.yml b/docker-compose.yml index 9c9a280..ebed194 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -36,7 +36,7 @@ services: dockerfile: Dockerfile args: - NEXT_PUBLIC_VAPID_PUBLIC_KEY=${NEXT_PUBLIC_VAPID_PUBLIC_KEY} - - NEXTAUTH_URL=${NEXTAUTH_URL:-http://localhost:3000} + - BETTER_AUTH_URL=${BETTER_AUTH_URL:-http://localhost:3000} ports: - "${APP_PORT-3001:}3000" environment: @@ -52,8 +52,7 @@ services: - RESEND_API_KEY=${RESEND_API_KEY} - NEXT_PUBLIC_VAPID_PUBLIC_KEY=${NEXT_PUBLIC_VAPID_PUBLIC_KEY} - VAPID_PRIVATE_KEY=${VAPID_PRIVATE_KEY} - - NEXTAUTH_URL=${NEXTAUTH_URL:-http://localhost:3000} - - AUTH_TRUST_HOST=${AUTH_TRUST_HOST:-true} + - BETTER_AUTH_URL=${BETTER_AUTH_URL:-http://localhost:3000} command: node server.js depends_on: template-db: diff --git a/example.env b/example.env index bcb177e..531f0b6 100644 --- a/example.env +++ b/example.env @@ -7,9 +7,9 @@ DATABASE_HOST=localhost DATABASE_PORT=5431 # database url used for prisma (combination of the 5 lines above) DATABASE_URL=postgresql://root:euI3dxzgDt8NaLuaR4QPWIoJjVXeAKqEovTEV8ecKxw=@localhost:5431/template?schema=public -# Nextauth url for authentication -NEXTAUTH_URL=http://localhost:3000 -# Auth secret for nextauth +# Base url for better-auth +BETTER_AUTH_URL=http://localhost:3000 +# Auth secret for better-auth # replace this with output from: openssl rand -base64 32 AUTH_SECRET=PxVYHb3UPLDMO/F8IWh1EmDDCWqm9Y+l2meScUjUQog= # Replace the next two from google cloud console diff --git a/package.json b/package.json index be280c3..d127ccf 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,6 @@ "docker:build": "docker compose build" }, "dependencies": { - "@auth/prisma-adapter": "^2.11.1", "@prisma/adapter-pg": "^7.3.0", "@prisma/client": "^7.3.0", "@radix-ui/react-avatar": "^1.1.1", @@ -39,12 +38,12 @@ "@types/web-push": "^3.6.4", "ag-grid-community": "^35.0.1", "ag-grid-react": "^35.0.1", + "better-auth": "^1.6.23", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "dotenv": "^17.2.3", "lucide-react": "^0.464.0", "next": "16.1.6", - "next-auth": "^5.0.0-beta.30", "next-themes": "^0.4.4", "papaparse": "^5.5.3", "react": "19.2.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 339789f..176a748 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,9 +8,6 @@ importers: .: dependencies: - '@auth/prisma-adapter': - specifier: ^2.11.1 - version: 2.11.1(@prisma/client@7.3.0(prisma@7.3.0(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3))(typescript@5.9.3)) '@prisma/adapter-pg': specifier: ^7.3.0 version: 7.3.0 @@ -50,6 +47,9 @@ importers: ag-grid-react: specifier: ^35.0.1 version: 35.0.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + better-auth: + specifier: ^1.6.23 + version: 1.6.23(@prisma/client@7.3.0(prisma@7.3.0(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3))(typescript@5.9.3))(mysql2@3.15.3)(next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(pg@8.18.0)(prisma@7.3.0(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3))(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(vitest@4.0.18(@types/node@20.19.33)(jiti@2.6.1)(jsdom@28.0.0(@noble/hashes@2.2.0))(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2)) class-variance-authority: specifier: ^0.7.1 version: 0.7.1 @@ -65,9 +65,6 @@ importers: next: specifier: 16.1.6 version: 16.1.6(@babel/core@7.29.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - next-auth: - specifier: ^5.0.0-beta.30 - version: 5.0.0-beta.30(next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react@19.2.1) next-themes: specifier: ^0.4.4 version: 0.4.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1) @@ -134,7 +131,7 @@ importers: version: 5.1.4(vite@7.3.1(@types/node@20.19.33)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2)) '@vitest/eslint-plugin': specifier: ^1.6.9 - version: 1.6.9(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)(vitest@4.0.18(@types/node@20.19.33)(jiti@2.6.1)(jsdom@28.0.0(@noble/hashes@1.8.0))(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2)) + version: 1.6.9(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)(vitest@4.0.18(@types/node@20.19.33)(jiti@2.6.1)(jsdom@28.0.0(@noble/hashes@2.2.0))(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2)) eslint: specifier: ^9.39.2 version: 9.39.2(jiti@2.6.1) @@ -149,7 +146,7 @@ importers: version: 9.1.7 jsdom: specifier: ^28.0.0 - version: 28.0.0(@noble/hashes@1.8.0) + version: 28.0.0(@noble/hashes@2.2.0) lint-staged: specifier: ^15.2.11 version: 15.5.2 @@ -179,7 +176,7 @@ importers: version: 6.1.1(typescript@5.9.3)(vite@7.3.1(@types/node@20.19.33)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2)) vitest: specifier: ^4.0.18 - version: 4.0.18(@types/node@20.19.33)(jiti@2.6.1)(jsdom@28.0.0(@noble/hashes@1.8.0))(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2) + version: 4.0.18(@types/node@20.19.33)(jiti@2.6.1)(jsdom@28.0.0(@noble/hashes@2.2.0))(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2) packages: @@ -202,39 +199,6 @@ packages: '@asamuzakjp/nwsapi@2.3.9': resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==} - '@auth/core@0.41.0': - resolution: {integrity: sha512-Wd7mHPQ/8zy6Qj7f4T46vg3aoor8fskJm6g2Zyj064oQ3+p0xNZXAV60ww0hY+MbTesfu29kK14Zk5d5JTazXQ==} - peerDependencies: - '@simplewebauthn/browser': ^9.0.1 - '@simplewebauthn/server': ^9.0.2 - nodemailer: ^6.8.0 - peerDependenciesMeta: - '@simplewebauthn/browser': - optional: true - '@simplewebauthn/server': - optional: true - nodemailer: - optional: true - - '@auth/core@0.41.1': - resolution: {integrity: sha512-t9cJ2zNYAdWMacGRMT6+r4xr1uybIdmYa49calBPeTqwgAFPV/88ac9TEvCR85pvATiSPt8VaNf+Gt24JIT/uw==} - peerDependencies: - '@simplewebauthn/browser': ^9.0.1 - '@simplewebauthn/server': ^9.0.2 - nodemailer: ^7.0.7 - peerDependenciesMeta: - '@simplewebauthn/browser': - optional: true - '@simplewebauthn/server': - optional: true - nodemailer: - optional: true - - '@auth/prisma-adapter@2.11.1': - resolution: {integrity: sha512-Ke7DXP0Fy0Mlmjz/ZJLXwQash2UkA4621xCM0rMtEczr1kppLc/njCbUkHkIQ/PnmILjqSPEKeTjDPsYruvkug==} - peerDependencies: - '@prisma/client': '>=2.26.0 || >=3 || >=4 || >=5 || >=6' - '@babel/code-frame@7.29.0': resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} engines: {node: '>=6.9.0'} @@ -322,6 +286,85 @@ packages: resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} + '@better-auth/core@1.6.23': + resolution: {integrity: sha512-beEhOs0uVeOxYOZKUfIEBd/nQV2Bd4/6wyLxZ0OFkn6CMTK2Vi+hXuZLnyPBeB6RdHpebEoJWiHqwHxBIxgPDQ==} + peerDependencies: + '@better-auth/utils': 0.4.2 + '@better-fetch/fetch': 1.3.1 + '@cloudflare/workers-types': '>=4' + '@opentelemetry/api': ^1.9.0 + better-call: 1.3.7 + jose: ^6.1.0 + kysely: ^0.28.5 || ^0.29.0 + nanostores: ^1.0.1 + peerDependenciesMeta: + '@cloudflare/workers-types': + optional: true + '@opentelemetry/api': + optional: true + + '@better-auth/drizzle-adapter@1.6.23': + resolution: {integrity: sha512-2+/PTVfIP9E7iz6af8TB3lhnowHUj9ljC66kECmHaFEdUqPgzHoWux9epotKwO7XDg2ui4ttWQ8CMeNFLvQeKQ==} + peerDependencies: + '@better-auth/core': ^1.6.23 + '@better-auth/utils': 0.4.2 + drizzle-orm: ^0.45.2 + peerDependenciesMeta: + drizzle-orm: + optional: true + + '@better-auth/kysely-adapter@1.6.23': + resolution: {integrity: sha512-zbNJsMbG09exfkGyvFqBLLqWoMPAUWjxCuUnEK5AsjbYoZeIjj/QGZgdf4CapVWryKxjA9Q6Jlr6fbiPpC3VAg==} + peerDependencies: + '@better-auth/core': ^1.6.23 + '@better-auth/utils': 0.4.2 + kysely: ^0.28.17 || ^0.29.0 + peerDependenciesMeta: + kysely: + optional: true + + '@better-auth/memory-adapter@1.6.23': + resolution: {integrity: sha512-krIiR0pIVkaKlAzm690n5bcMW4NGbqeMg0HQSD9fz/KcQF/eWLqcq9gG/BhHTj2i/y96qH+W5JWPmaSOS5iTgQ==} + peerDependencies: + '@better-auth/core': ^1.6.23 + '@better-auth/utils': 0.4.2 + + '@better-auth/mongo-adapter@1.6.23': + resolution: {integrity: sha512-7+QdevitGlKBbP6JbiSk5SBnzPsKV/mDrQBGBn8hwByQLeJwqpqbuBPw7ZI8vzUlFfAAnyFiqwP3Eb8mxnp7pA==} + peerDependencies: + '@better-auth/core': ^1.6.23 + '@better-auth/utils': 0.4.2 + mongodb: ^6.0.0 || ^7.0.0 + peerDependenciesMeta: + mongodb: + optional: true + + '@better-auth/prisma-adapter@1.6.23': + resolution: {integrity: sha512-2qSdzidq4tkb1eS5TTqb4Nzg0mdZWm3Qky9SYeXeb8PpVQbC2sxqJhEM5mK7y12uU6I8hc64wO9f7AFVNL+6UQ==} + peerDependencies: + '@better-auth/core': ^1.6.23 + '@better-auth/utils': 0.4.2 + '@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.23': + resolution: {integrity: sha512-/R2Kb+z2BpDOOWwVHqOk+c0VNpuwfCv4Hp5Yr9003WIZPax/zyNraGLB9CFE8qF2gZW8Dsz419k4I8CPrGzpDA==} + peerDependencies: + '@better-auth/core': ^1.6.23 + '@better-auth/utils': 0.4.2 + '@better-fetch/fetch': 1.3.1 + + '@better-auth/utils@0.4.2': + resolution: {integrity: sha512-AUxrvu+HaaODsUyzDxFgwd/8RZ1yZaYo42LXKSrU2oGgR38pS1ij8nqQKNgtTWoYGpNevNXtCfgTy6loHveW9A==} + + '@better-fetch/fetch@1.3.1': + resolution: {integrity: sha512-ABkD1WhyfPZprKRQI3bhATjeiFuNWC9PXhfGWqL+sg/gKrM977oFrYkdb4msM3hgUGonr7KlOsOFT5TU2rht9g==} + '@chevrotain/cst-dts-gen@10.5.0': resolution: {integrity: sha512-lhmC/FyqQ2o7pGK4Om+hzuDrm9rhFYIJ/AXoQBeongmn870Xeb0L6oGEiuR8nohFNL5sMaQEJWCxr1oIVIVXrw==} @@ -862,10 +905,18 @@ packages: cpu: [x64] os: [win32] + '@noble/ciphers@2.2.0': + resolution: {integrity: sha512-Z6pjIZ/8IJcCGzb2S/0Px5J81yij85xASuk1teLNeg75bfT07MV3a/O2Mtn1I2se43k3lkVEcFaR10N4cgQcZA==} + engines: {node: '>= 20.19.0'} + '@noble/hashes@1.8.0': resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} engines: {node: ^14.21.3 || >=16} + '@noble/hashes@2.2.0': + resolution: {integrity: sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==} + engines: {node: '>= 20.19.0'} + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -882,8 +933,9 @@ packages: resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} engines: {node: '>=12.4.0'} - '@panva/hkdf@1.2.1': - resolution: {integrity: sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==} + '@opentelemetry/semantic-conventions@1.41.1': + resolution: {integrity: sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA==} + engines: {node: '>=14'} '@paralleldrive/cuid2@2.3.1': resolution: {integrity: sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==} @@ -2202,6 +2254,76 @@ packages: resolution: {integrity: sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==} hasBin: true + better-auth@1.6.23: + resolution: {integrity: sha512-4vOaRd9UiKGKm9R+ej0jjU1es3MiJIiNc9Qq3VCnYqOZ4/nb5272QqTxWYoDxyUXl5x6A2x2we5KZKQO9teTQQ==} + 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.45.2 + 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.7: + resolution: {integrity: sha512-Al51/hjp2SSp6CRTa3F2ptcx4yQVS1xWKoY6jcVXqNYOap6mHFP2jUBn5EwIL4iIed1/Sq4hlQ+Umm6EflZG+w==} + peerDependencies: + zod: ^4.0.0 + peerDependenciesMeta: + zod: + optional: true + bidi-js@1.0.3: resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} @@ -3086,8 +3208,8 @@ packages: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true - jose@6.1.3: - resolution: {integrity: sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==} + jose@6.2.3: + resolution: {integrity: sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==} js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -3141,6 +3263,10 @@ packages: keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + kysely@0.29.2: + resolution: {integrity: sha512-s6WVJyEZrbm6jhBpiKHsGHyePMrVQKJ85wZCFCr9W4QHv6WTjWIrdvTmO9hDEA3bNK0xkrE2DqrHsXMLWuZpQg==} + engines: {node: '>=22.0.0'} + language-subtag-registry@0.3.23: resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==} @@ -3369,6 +3495,10 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + nanostores@1.4.0: + resolution: {integrity: sha512-i0tloweeudshAEuddpDxcg9Ik6pkPfVsHIgKyf143JrgG7/MOh0+q7BypdLXZPoOP7fOYt1eTcwGkyiVmhJFkA==} + engines: {node: ^20.0.0 || >=22.0.0} + napi-postinstall@0.3.4: resolution: {integrity: sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} @@ -3377,22 +3507,6 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - next-auth@5.0.0-beta.30: - resolution: {integrity: sha512-+c51gquM3F6nMVmoAusRJ7RIoY0K4Ts9HCCwyy/BRoe4mp3msZpOzYMyb5LAYc1wSo74PMQkGDcaghIO7W6Xjg==} - peerDependencies: - '@simplewebauthn/browser': ^9.0.1 - '@simplewebauthn/server': ^9.0.2 - next: ^14.0.0-0 || ^15.0.0 || ^16.0.0 - nodemailer: ^7.0.7 - react: ^18.2.0 || ^19.0.0 - peerDependenciesMeta: - '@simplewebauthn/browser': - optional: true - '@simplewebauthn/server': - optional: true - nodemailer: - optional: true - next-themes@0.4.6: resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==} peerDependencies: @@ -3439,9 +3553,6 @@ packages: engines: {node: '>=18'} hasBin: true - oauth4webapi@3.8.4: - resolution: {integrity: sha512-EKlVEgav8zH31IXxvhCqjEgQws6S9QmnmJyLXmeV5REf59g7VmqRVa5l/rhGWtUqGm2rLVTNwukn9hla5kJ2WQ==} - object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -3630,14 +3741,6 @@ packages: resolution: {integrity: sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw==} engines: {node: '>=12'} - preact-render-to-string@6.5.11: - resolution: {integrity: sha512-ubnauqoGczeGISiOh6RjX0/cdaF8v/oDXIjO85XALCQjwQP+SB4RDXXtvZ6yTYSjG+PC1QRP2AhPgCEsM2EvUw==} - peerDependencies: - preact: '>=10' - - preact@10.24.3: - resolution: {integrity: sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA==} - prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -3866,6 +3969,9 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + rou3@0.7.12: + resolution: {integrity: sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg==} + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -3909,6 +4015,9 @@ packages: seq-queue@0.0.5: resolution: {integrity: sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==} + set-cookie-parser@3.1.1: + resolution: {integrity: sha512-vM9SUhjsUYs6UeJUmygc5Ofm5eQGe85riob5ju6XCgFGJI5PLV4nrDAQpQjd+LkFBpAkADn5BQQpZ9EUNkyLuA==} + set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} @@ -4443,31 +4552,6 @@ snapshots: '@asamuzakjp/nwsapi@2.3.9': {} - '@auth/core@0.41.0': - dependencies: - '@panva/hkdf': 1.2.1 - jose: 6.1.3 - oauth4webapi: 3.8.4 - preact: 10.24.3 - preact-render-to-string: 6.5.11(preact@10.24.3) - - '@auth/core@0.41.1': - dependencies: - '@panva/hkdf': 1.2.1 - jose: 6.1.3 - oauth4webapi: 3.8.4 - preact: 10.24.3 - preact-render-to-string: 6.5.11(preact@10.24.3) - - '@auth/prisma-adapter@2.11.1(@prisma/client@7.3.0(prisma@7.3.0(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3))(typescript@5.9.3))': - dependencies: - '@auth/core': 0.41.1 - '@prisma/client': 7.3.0(prisma@7.3.0(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3))(typescript@5.9.3) - transitivePeerDependencies: - - '@simplewebauthn/browser' - - '@simplewebauthn/server' - - nodemailer - '@babel/code-frame@7.29.0': dependencies: '@babel/helper-validator-identifier': 7.28.5 @@ -4582,6 +4666,60 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 + '@better-auth/core@1.6.23(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.3.6))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.4.0)': + dependencies: + '@better-auth/utils': 0.4.2 + '@better-fetch/fetch': 1.3.1 + '@opentelemetry/semantic-conventions': 1.41.1 + '@standard-schema/spec': 1.1.0 + better-call: 1.3.7(zod@4.3.6) + jose: 6.2.3 + kysely: 0.29.2 + nanostores: 1.4.0 + zod: 4.3.6 + + '@better-auth/drizzle-adapter@1.6.23(@better-auth/core@1.6.23(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.3.6))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.4.0))(@better-auth/utils@0.4.2)': + dependencies: + '@better-auth/core': 1.6.23(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.3.6))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.4.0) + '@better-auth/utils': 0.4.2 + + '@better-auth/kysely-adapter@1.6.23(@better-auth/core@1.6.23(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.3.6))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.4.0))(@better-auth/utils@0.4.2)(kysely@0.29.2)': + dependencies: + '@better-auth/core': 1.6.23(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.3.6))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.4.0) + '@better-auth/utils': 0.4.2 + optionalDependencies: + kysely: 0.29.2 + + '@better-auth/memory-adapter@1.6.23(@better-auth/core@1.6.23(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.3.6))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.4.0))(@better-auth/utils@0.4.2)': + dependencies: + '@better-auth/core': 1.6.23(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.3.6))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.4.0) + '@better-auth/utils': 0.4.2 + + '@better-auth/mongo-adapter@1.6.23(@better-auth/core@1.6.23(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.3.6))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.4.0))(@better-auth/utils@0.4.2)': + dependencies: + '@better-auth/core': 1.6.23(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.3.6))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.4.0) + '@better-auth/utils': 0.4.2 + + '@better-auth/prisma-adapter@1.6.23(@better-auth/core@1.6.23(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.3.6))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.4.0))(@better-auth/utils@0.4.2)(@prisma/client@7.3.0(prisma@7.3.0(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3))(typescript@5.9.3))(prisma@7.3.0(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3))': + dependencies: + '@better-auth/core': 1.6.23(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.3.6))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.4.0) + '@better-auth/utils': 0.4.2 + optionalDependencies: + '@prisma/client': 7.3.0(prisma@7.3.0(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3))(typescript@5.9.3) + prisma: 7.3.0(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3) + + '@better-auth/telemetry@1.6.23(@better-auth/core@1.6.23(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.3.6))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.4.0))(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)': + dependencies: + '@better-auth/core': 1.6.23(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.3.6))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.4.0) + '@better-auth/utils': 0.4.2 + '@better-fetch/fetch': 1.3.1 + + '@better-auth/utils@0.4.2': + dependencies: + '@noble/hashes': 2.2.0 + + '@better-fetch/fetch@1.3.1': {} + '@chevrotain/cst-dts-gen@10.5.0': dependencies: '@chevrotain/gast': 10.5.0 @@ -4769,9 +4907,9 @@ snapshots: '@eslint/core': 0.17.0 levn: 0.4.1 - '@exodus/bytes@1.14.1(@noble/hashes@1.8.0)': + '@exodus/bytes@1.14.1(@noble/hashes@2.2.0)': optionalDependencies: - '@noble/hashes': 1.8.0 + '@noble/hashes': 2.2.0 '@floating-ui/core@1.7.4': dependencies: @@ -4963,8 +5101,12 @@ snapshots: '@next/swc-win32-x64-msvc@16.1.6': optional: true + '@noble/ciphers@2.2.0': {} + '@noble/hashes@1.8.0': {} + '@noble/hashes@2.2.0': {} + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -4979,7 +5121,7 @@ snapshots: '@nolyfill/is-core-module@1.0.39': {} - '@panva/hkdf@1.2.1': {} + '@opentelemetry/semantic-conventions@1.41.1': {} '@paralleldrive/cuid2@2.3.1': dependencies: @@ -6023,14 +6165,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@vitest/eslint-plugin@1.6.9(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)(vitest@4.0.18(@types/node@20.19.33)(jiti@2.6.1)(jsdom@28.0.0(@noble/hashes@1.8.0))(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2))': + '@vitest/eslint-plugin@1.6.9(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)(vitest@4.0.18(@types/node@20.19.33)(jiti@2.6.1)(jsdom@28.0.0(@noble/hashes@2.2.0))(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2))': dependencies: '@typescript-eslint/scope-manager': 8.55.0 '@typescript-eslint/utils': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) eslint: 9.39.2(jiti@2.6.1) optionalDependencies: typescript: 5.9.3 - vitest: 4.0.18(@types/node@20.19.33)(jiti@2.6.1)(jsdom@28.0.0(@noble/hashes@1.8.0))(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2) + vitest: 4.0.18(@types/node@20.19.33)(jiti@2.6.1)(jsdom@28.0.0(@noble/hashes@2.2.0))(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2) transitivePeerDependencies: - supports-color @@ -6229,6 +6371,47 @@ snapshots: baseline-browser-mapping@2.9.19: {} + better-auth@1.6.23(@prisma/client@7.3.0(prisma@7.3.0(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3))(typescript@5.9.3))(mysql2@3.15.3)(next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(pg@8.18.0)(prisma@7.3.0(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3))(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(vitest@4.0.18(@types/node@20.19.33)(jiti@2.6.1)(jsdom@28.0.0(@noble/hashes@2.2.0))(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2)): + dependencies: + '@better-auth/core': 1.6.23(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.3.6))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.4.0) + '@better-auth/drizzle-adapter': 1.6.23(@better-auth/core@1.6.23(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.3.6))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.4.0))(@better-auth/utils@0.4.2) + '@better-auth/kysely-adapter': 1.6.23(@better-auth/core@1.6.23(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.3.6))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.4.0))(@better-auth/utils@0.4.2)(kysely@0.29.2) + '@better-auth/memory-adapter': 1.6.23(@better-auth/core@1.6.23(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.3.6))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.4.0))(@better-auth/utils@0.4.2) + '@better-auth/mongo-adapter': 1.6.23(@better-auth/core@1.6.23(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.3.6))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.4.0))(@better-auth/utils@0.4.2) + '@better-auth/prisma-adapter': 1.6.23(@better-auth/core@1.6.23(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.3.6))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.4.0))(@better-auth/utils@0.4.2)(@prisma/client@7.3.0(prisma@7.3.0(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3))(typescript@5.9.3))(prisma@7.3.0(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3)) + '@better-auth/telemetry': 1.6.23(@better-auth/core@1.6.23(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.3.6))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.4.0))(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1) + '@better-auth/utils': 0.4.2 + '@better-fetch/fetch': 1.3.1 + '@noble/ciphers': 2.2.0 + '@noble/hashes': 2.2.0 + better-call: 1.3.7(zod@4.3.6) + defu: 6.1.4 + jose: 6.2.3 + kysely: 0.29.2 + nanostores: 1.4.0 + zod: 4.3.6 + optionalDependencies: + '@prisma/client': 7.3.0(prisma@7.3.0(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3))(typescript@5.9.3) + mysql2: 3.15.3 + next: 16.1.6(@babel/core@7.29.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + pg: 8.18.0 + prisma: 7.3.0(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + vitest: 4.0.18(@types/node@20.19.33)(jiti@2.6.1)(jsdom@28.0.0(@noble/hashes@2.2.0))(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2) + transitivePeerDependencies: + - '@cloudflare/workers-types' + - '@opentelemetry/api' + + better-call@1.3.7(zod@4.3.6): + dependencies: + '@better-auth/utils': 0.4.2 + '@better-fetch/fetch': 1.3.1 + rou3: 0.7.12 + set-cookie-parser: 3.1.1 + optionalDependencies: + zod: 4.3.6 + bidi-js@1.0.3: dependencies: require-from-string: 2.0.2 @@ -6381,10 +6564,10 @@ snapshots: damerau-levenshtein@1.0.8: {} - data-urls@7.0.0(@noble/hashes@1.8.0): + data-urls@7.0.0(@noble/hashes@2.2.0): dependencies: whatwg-mimetype: 5.0.0 - whatwg-url: 16.0.0(@noble/hashes@1.8.0) + whatwg-url: 16.0.0(@noble/hashes@2.2.0) transitivePeerDependencies: - '@noble/hashes' @@ -7062,9 +7245,9 @@ snapshots: hono@4.11.4: {} - html-encoding-sniffer@6.0.0(@noble/hashes@1.8.0): + html-encoding-sniffer@6.0.0(@noble/hashes@2.2.0): dependencies: - '@exodus/bytes': 1.14.1(@noble/hashes@1.8.0) + '@exodus/bytes': 1.14.1(@noble/hashes@2.2.0) transitivePeerDependencies: - '@noble/hashes' @@ -7277,7 +7460,7 @@ snapshots: jiti@2.6.1: {} - jose@6.1.3: {} + jose@6.2.3: {} js-tokens@4.0.0: {} @@ -7285,15 +7468,15 @@ snapshots: dependencies: argparse: 2.0.1 - jsdom@28.0.0(@noble/hashes@1.8.0): + jsdom@28.0.0(@noble/hashes@2.2.0): dependencies: '@acemir/cssom': 0.9.31 '@asamuzakjp/dom-selector': 6.8.1 - '@exodus/bytes': 1.14.1(@noble/hashes@1.8.0) + '@exodus/bytes': 1.14.1(@noble/hashes@2.2.0) cssstyle: 5.3.7 - data-urls: 7.0.0(@noble/hashes@1.8.0) + data-urls: 7.0.0(@noble/hashes@2.2.0) decimal.js: 10.6.0 - html-encoding-sniffer: 6.0.0(@noble/hashes@1.8.0) + html-encoding-sniffer: 6.0.0(@noble/hashes@2.2.0) http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.6 is-potential-custom-element-name: 1.0.1 @@ -7305,7 +7488,7 @@ snapshots: w3c-xmlserializer: 5.0.0 webidl-conversions: 8.0.1 whatwg-mimetype: 5.0.0 - whatwg-url: 16.0.0(@noble/hashes@1.8.0) + whatwg-url: 16.0.0(@noble/hashes@2.2.0) xml-name-validator: 5.0.0 transitivePeerDependencies: - '@noble/hashes' @@ -7347,6 +7530,8 @@ snapshots: dependencies: json-buffer: 3.0.1 + kysely@0.29.2: {} + language-subtag-registry@0.3.23: {} language-tags@1.0.9: @@ -7558,16 +7743,12 @@ snapshots: nanoid@3.3.11: {} + nanostores@1.4.0: {} + napi-postinstall@0.3.4: {} natural-compare@1.4.0: {} - next-auth@5.0.0-beta.30(next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react@19.2.1): - dependencies: - '@auth/core': 0.41.0 - next: 16.1.6(@babel/core@7.29.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - react: 19.2.1 - next-themes@0.4.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1): dependencies: react: 19.2.1 @@ -7613,8 +7794,6 @@ snapshots: pathe: 2.0.3 tinyexec: 1.0.2 - oauth4webapi@3.8.4: {} - object-assign@4.1.1: {} object-inspect@1.13.4: {} @@ -7798,12 +7977,6 @@ snapshots: postgres@3.4.7: {} - preact-render-to-string@6.5.11(preact@10.24.3): - dependencies: - preact: 10.24.3 - - preact@10.24.3: {} - prelude-ls@1.2.1: {} prettier-plugin-tailwindcss@0.7.2(prettier@3.8.1): @@ -7999,6 +8172,8 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.57.1 fsevents: 2.3.3 + rou3@0.7.12: {} + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 @@ -8042,6 +8217,8 @@ snapshots: seq-queue@0.0.5: {} + set-cookie-parser@3.1.1: {} + set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 @@ -8473,7 +8650,7 @@ snapshots: tsx: 4.21.0 yaml: 2.8.2 - vitest@4.0.18(@types/node@20.19.33)(jiti@2.6.1)(jsdom@28.0.0(@noble/hashes@1.8.0))(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2): + vitest@4.0.18(@types/node@20.19.33)(jiti@2.6.1)(jsdom@28.0.0(@noble/hashes@2.2.0))(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2): dependencies: '@vitest/expect': 4.0.18 '@vitest/mocker': 4.0.18(vite@7.3.1(@types/node@20.19.33)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2)) @@ -8497,7 +8674,7 @@ snapshots: why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 20.19.33 - jsdom: 28.0.0(@noble/hashes@1.8.0) + jsdom: 28.0.0(@noble/hashes@2.2.0) transitivePeerDependencies: - jiti - less @@ -8529,9 +8706,9 @@ snapshots: whatwg-mimetype@5.0.0: {} - whatwg-url@16.0.0(@noble/hashes@1.8.0): + whatwg-url@16.0.0(@noble/hashes@2.2.0): dependencies: - '@exodus/bytes': 1.14.1(@noble/hashes@1.8.0) + '@exodus/bytes': 1.14.1(@noble/hashes@2.2.0) tr46: 6.0.0 webidl-conversions: 8.0.1 transitivePeerDependencies: diff --git a/prisma/migrations/20260705000000_better_auth/migration.sql b/prisma/migrations/20260705000000_better_auth/migration.sql new file mode 100644 index 0000000..1042729 --- /dev/null +++ b/prisma/migrations/20260705000000_better_auth/migration.sql @@ -0,0 +1,101 @@ +-- Migrate from NextAuth (Auth.js) to Better Auth. +-- Existing users and their Google account links are preserved. +-- Active sessions are dropped; users will need to sign in again. + +-- User: name becomes required, emailVerified becomes a boolean +UPDATE "User" SET "name" = split_part("email", '@', 1) WHERE "name" IS NULL; +ALTER TABLE "User" ALTER COLUMN "name" SET NOT NULL; +ALTER TABLE "User" + ALTER COLUMN "emailVerified" TYPE BOOLEAN USING ("emailVerified" IS NOT NULL), + ALTER COLUMN "emailVerified" SET DEFAULT false, + ALTER COLUMN "emailVerified" SET NOT NULL; + +-- Account: rebuild in Better Auth's shape, carrying over existing rows +ALTER TABLE "Account" RENAME TO "Account_nextauth"; +ALTER TABLE "Account_nextauth" RENAME CONSTRAINT "Account_pkey" TO "Account_nextauth_pkey"; +ALTER TABLE "Account_nextauth" RENAME CONSTRAINT "Account_userId_fkey" TO "Account_nextauth_userId_fkey"; + +CREATE TABLE "Account" ( + "id" TEXT NOT NULL, + "accountId" TEXT NOT NULL, + "providerId" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "accessToken" TEXT, + "refreshToken" TEXT, + "idToken" TEXT, + "accessTokenExpiresAt" TIMESTAMP(3), + "refreshTokenExpiresAt" TIMESTAMP(3), + "scope" TEXT, + "password" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Account_pkey" PRIMARY KEY ("id") +); + +INSERT INTO "Account" ( + "id", + "accountId", + "providerId", + "userId", + "accessToken", + "refreshToken", + "idToken", + "accessTokenExpiresAt", + "scope", + "createdAt", + "updatedAt" +) +SELECT + gen_random_uuid()::text, + "providerAccountId", + "provider", + "userId", + "access_token", + "refresh_token", + "id_token", + to_timestamp("expires_at"), + "scope", + "createdAt", + "updatedAt" +FROM "Account_nextauth"; + +DROP TABLE "Account_nextauth"; + +CREATE UNIQUE INDEX "Account_providerId_accountId_key" ON "Account"("providerId", "accountId"); + +ALTER TABLE "Account" ADD CONSTRAINT "Account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- Session: rebuild in Better Auth's shape (existing sessions are discarded) +DROP TABLE "Session"; + +CREATE TABLE "Session" ( + "id" TEXT NOT NULL, + "token" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "expiresAt" TIMESTAMP(3) NOT NULL, + "ipAddress" TEXT, + "userAgent" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Session_pkey" PRIMARY KEY ("id") +); + +CREATE UNIQUE INDEX "Session_token_key" ON "Session"("token"); + +ALTER TABLE "Session" ADD CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- VerificationToken is replaced by Better Auth's Verification table +DROP TABLE "VerificationToken"; + +CREATE TABLE "Verification" ( + "id" TEXT NOT NULL, + "identifier" TEXT NOT NULL, + "value" TEXT NOT NULL, + "expiresAt" TIMESTAMP(3) NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Verification_pkey" PRIMARY KEY ("id") +); diff --git a/prisma/schema/account.prisma b/prisma/schema/account.prisma index 498a0fa..03c8525 100644 --- a/prisma/schema/account.prisma +++ b/prisma/schema/account.prisma @@ -1,20 +1,20 @@ model Account { - userId String - type String - provider String - providerAccountId String - refresh_token String? - access_token String? - expires_at Int? - token_type String? - scope String? - id_token String? - session_state String? + id String @id @default(cuid()) + accountId String + providerId String + userId String + accessToken String? + refreshToken String? + idToken String? + accessTokenExpiresAt DateTime? + refreshTokenExpiresAt DateTime? + scope String? + password String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id], onDelete: Cascade) - @@id([provider, providerAccountId]) + @@unique([providerId, accountId]) } diff --git a/prisma/schema/session.prisma b/prisma/schema/session.prisma index 2c865e2..85b504a 100644 --- a/prisma/schema/session.prisma +++ b/prisma/schema/session.prisma @@ -1,8 +1,11 @@ model Session { - sessionToken String @unique - userId String - expires DateTime - user User @relation(fields: [userId], references: [id], onDelete: Cascade) + id String @id @default(cuid()) + token String @unique + userId String + expiresAt DateTime + ipAddress String? + userAgent String? + user User @relation(fields: [userId], references: [id], onDelete: Cascade) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt diff --git a/prisma/schema/user.prisma b/prisma/schema/user.prisma index bafa3ed..358a656 100644 --- a/prisma/schema/user.prisma +++ b/prisma/schema/user.prisma @@ -1,8 +1,8 @@ model User { id String @id @default(cuid()) - name String? + name String email String @unique - emailVerified DateTime? + emailVerified Boolean @default(false) image String? role UserRole? pushSubscription String? diff --git a/prisma/schema/verification-token.prisma b/prisma/schema/verification-token.prisma deleted file mode 100644 index 032f3dc..0000000 --- a/prisma/schema/verification-token.prisma +++ /dev/null @@ -1,7 +0,0 @@ -model VerificationToken { - identifier String - token String - expires DateTime - - @@id([identifier, token]) -} diff --git a/prisma/schema/verification.prisma b/prisma/schema/verification.prisma new file mode 100644 index 0000000..1832106 --- /dev/null +++ b/prisma/schema/verification.prisma @@ -0,0 +1,9 @@ +model Verification { + id String @id @default(cuid()) + identifier String + value String + expiresAt DateTime + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} diff --git a/prisma/seed/users.mjs b/prisma/seed/users.mjs index 58447ef..e90748e 100644 --- a/prisma/seed/users.mjs +++ b/prisma/seed/users.mjs @@ -1,4 +1,3 @@ -import { PrismaAdapter } from '@auth/prisma-adapter'; import { createId } from '@paralleldrive/cuid2'; const USERS = [ @@ -18,8 +17,7 @@ const USERS = [ const seedUser = async ( prisma, - adapter, - { email, providerAccountId, access_token, role, userName } + { email, providerAccountId, role, userName } ) => { const hasUser = await prisma.user.findUnique({ where: { email }, @@ -28,36 +26,29 @@ const seedUser = async ( console.warn(`${email} already exists and will not be seeded!`); return; } - if (!hasUser && adapter.createUser) { - const user = await adapter.createUser({ + await prisma.user.create({ + data: { id: createId(), + name: userName, email, - emailVerified: new Date(), - }); - await prisma.account.create({ - data: { - userId: user.id, - type: 'oauth', - provider: 'google', - providerAccountId, - access_token, - token_type: 'bearer', - scope: - 'https://www.googleapis.com/auth/userinfo.email openid https://www.googleapis.com/auth/userinfo.profile', - expires_at: Math.floor(Date.now() / 1000) + 365 * 24 * 60 * 60, + emailVerified: true, + role, + accounts: { + create: { + id: createId(), + providerId: 'google', + accountId: providerAccountId, + scope: + 'https://www.googleapis.com/auth/userinfo.email openid https://www.googleapis.com/auth/userinfo.profile', + }, }, - }); - await prisma.user.update({ - where: { id: user.id }, - data: { name: userName, role }, - }); - console.log(`${email} has been seeded`); - } + }, + }); + console.log(`${email} has been seeded`); }; export const seedUsers = async (prisma) => { - const adapter = PrismaAdapter(prisma); for (const user of USERS) { - await seedUser(prisma, adapter, user); + await seedUser(prisma, user); } }; diff --git a/src/app/api/auth/[...all]/route.ts b/src/app/api/auth/[...all]/route.ts new file mode 100644 index 0000000..9900a3b --- /dev/null +++ b/src/app/api/auth/[...all]/route.ts @@ -0,0 +1,4 @@ +import { auth } from '@/lib/auth'; +import { toNextJsHandler } from 'better-auth/next-js'; + +export const { GET, POST } = toNextJsHandler(auth); diff --git a/src/app/api/auth/[...nextauth]/route.ts b/src/app/api/auth/[...nextauth]/route.ts deleted file mode 100644 index 640fc46..0000000 --- a/src/app/api/auth/[...nextauth]/route.ts +++ /dev/null @@ -1,2 +0,0 @@ -import { handlers } from '@/lib/auth'; -export const { GET, POST } = handlers; diff --git a/src/app/api/export-csv/route.ts b/src/app/api/export-csv/route.ts index 9d298ca..8ac0e8b 100644 --- a/src/app/api/export-csv/route.ts +++ b/src/app/api/export-csv/route.ts @@ -1,4 +1,4 @@ -import { auth } from '@/lib/auth'; +import { getSession } from '@/lib/auth'; import { convertFilterModelToPrisma } from '@/lib/pagination'; import { prisma } from '@/lib/prisma'; import { NextRequest, NextResponse } from 'next/server'; @@ -30,7 +30,7 @@ function isPrismaModel(model: unknown): model is PrismaModel { * Returns a CSV file as an attachment. */ export async function POST(req: NextRequest) { - const session = await auth(); + const session = await getSession(); if (session?.user?.role !== 'ADMIN') { return NextResponse.json({ error: 'Not authenticated' }, { status: 401 }); diff --git a/src/app/api/impersonate/route.ts b/src/app/api/impersonate/route.ts index a3be823..a1b8e63 100644 --- a/src/app/api/impersonate/route.ts +++ b/src/app/api/impersonate/route.ts @@ -1,11 +1,11 @@ -import { auth } from '@/lib/auth'; +import { getSession } from '@/lib/auth'; import { prisma } from '@/lib/prisma'; import { cookies } from 'next/headers'; import { NextRequest, NextResponse } from 'next/server'; export async function POST(request: NextRequest) { try { - const session = await auth(); + const session = await getSession(); if (!session?.user?.id) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); @@ -74,7 +74,7 @@ export async function POST(request: NextRequest) { export async function DELETE() { try { - const session = await auth(); + const session = await getSession(); if (!session?.user?.id) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); diff --git a/src/app/api/impersonate/status/route.ts b/src/app/api/impersonate/status/route.ts index 726bab8..a0bdffa 100644 --- a/src/app/api/impersonate/status/route.ts +++ b/src/app/api/impersonate/status/route.ts @@ -1,11 +1,11 @@ -import { auth } from '@/lib/auth'; +import { getSession } from '@/lib/auth'; import { prisma } from '@/lib/prisma'; import { cookies } from 'next/headers'; import { NextResponse } from 'next/server'; export async function GET() { try { - const session = await auth(); + const session = await getSession(); if (!session?.user?.id) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); diff --git a/src/app/api/notifications/send/route.ts b/src/app/api/notifications/send/route.ts index 88a86e1..d968781 100644 --- a/src/app/api/notifications/send/route.ts +++ b/src/app/api/notifications/send/route.ts @@ -1,4 +1,4 @@ -import { auth } from '@/lib/auth'; +import { getSession } from '@/lib/auth'; import { prisma } from '@/lib/prisma'; import { ensureWebPushConfigured, webpush } from '@/lib/web-push'; import { NextRequest, NextResponse } from 'next/server'; @@ -6,7 +6,7 @@ import { NextRequest, NextResponse } from 'next/server'; export async function POST(request: NextRequest) { try { ensureWebPushConfigured(); - const session = await auth(); + const session = await getSession(); if (!session?.user?.email) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); diff --git a/src/app/api/notifications/subscribe/route.ts b/src/app/api/notifications/subscribe/route.ts index e4346c3..ebbb127 100644 --- a/src/app/api/notifications/subscribe/route.ts +++ b/src/app/api/notifications/subscribe/route.ts @@ -1,4 +1,4 @@ -import { auth } from '@/lib/auth'; +import { getSession } from '@/lib/auth'; import { prisma } from '@/lib/prisma'; import { ensureWebPushConfigured, publicKey } from '@/lib/web-push'; import { NextRequest, NextResponse } from 'next/server'; @@ -6,7 +6,7 @@ import { NextRequest, NextResponse } from 'next/server'; export async function POST(request: NextRequest) { try { ensureWebPushConfigured(); - const session = await auth(); + const session = await getSession(); if (!session?.user?.email) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); @@ -41,7 +41,7 @@ export async function POST(request: NextRequest) { export async function DELETE() { try { - const session = await auth(); + const session = await getSession(); if (!session?.user?.email) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); diff --git a/src/app/api/send/route.ts b/src/app/api/send/route.ts index 183aa0e..62424da 100644 --- a/src/app/api/send/route.ts +++ b/src/app/api/send/route.ts @@ -1,4 +1,4 @@ -import { auth } from '@/lib/auth'; +import { getSession } from '@/lib/auth'; import { NextResponse } from 'next/server'; import { Resend } from 'resend'; import React from 'react'; @@ -21,7 +21,7 @@ function getResendClient() { } export async function POST(req: Request) { - const session = await auth(); + const session = await getSession(); const { email, name, diff --git a/src/app/api/users/route.ts b/src/app/api/users/route.ts index 4e505dc..b476d1b 100644 --- a/src/app/api/users/route.ts +++ b/src/app/api/users/route.ts @@ -1,4 +1,4 @@ -import { auth } from '@/lib/auth'; +import { getSession } from '@/lib/auth'; import { convertFilterModelToPrisma, convertSortModelToPrisma, @@ -49,7 +49,7 @@ import { NextRequest, NextResponse } from 'next/server'; * } */ export async function GET(request: NextRequest) { - const session = await auth(); + const session = await getSession(); if (session?.user?.role !== 'ADMIN') { return NextResponse.json({ error: 'Not authenticated' }, { status: 401 }); @@ -129,7 +129,7 @@ export async function GET(request: NextRequest) { * } */ export async function POST(req: Request) { - const session = await auth(); + const session = await getSession(); if (session?.user?.role !== 'ADMIN') { return NextResponse.json({ error: 'Not authenticated' }, { status: 401 }); @@ -179,7 +179,7 @@ export async function POST(req: Request) { * } */ export async function PUT(req: Request) { - const session = await auth(); + const session = await getSession(); if (!session || !session.user) { return NextResponse.json({ error: 'Not authenticated' }, { status: 401 }); @@ -233,7 +233,7 @@ export async function PUT(req: Request) { * // 404: { "error": "User not found" } */ export async function DELETE(req: Request) { - const session = await auth(); + const session = await getSession(); if (session?.user?.role !== 'ADMIN') { return NextResponse.json({ error: 'Not authenticated' }, { status: 401 }); diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx index a9d4c83..a713237 100644 --- a/src/app/dashboard/page.tsx +++ b/src/app/dashboard/page.tsx @@ -1,4 +1,4 @@ -import { auth } from '@/lib/auth'; +import { getSession } from '@/lib/auth'; import { Metadata } from 'next'; import { redirect } from 'next/navigation'; @@ -8,7 +8,7 @@ export const metadata: Metadata = { }; export default async function DashboardPage() { - const session = await auth(); + const session = await getSession(); if (!session?.user) { redirect('/'); diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 54bfc19..7a4dd8b 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -4,9 +4,7 @@ import { Header } from '@/components/layout/header'; import { ServiceWorkerRegistration } from '@/components/layout/service-worker-registration'; import { ThemeProvider } from '@/components/layout/theme-provider'; import { Toaster } from '@/components/ui/toaster'; -import { auth } from '@/lib/auth'; import type { Metadata, Viewport } from 'next'; -import { SessionProvider } from 'next-auth/react'; import localFont from 'next/font/local'; import Script from 'next/script'; import './globals.css'; @@ -52,29 +50,24 @@ export const viewport: Viewport = { themeColor: '#000000', }; -export default async function RootLayout({ +export default function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { - const session = await auth(); return (