From afb5343191f7d2a045ac53fc5a713d0d41921b97 Mon Sep 17 00:00:00 2001 From: TheRealToxicDev Date: Wed, 3 Jun 2026 14:39:44 -0600 Subject: [PATCH 1/3] fix stuff and things --- apps/web/next.config.js | 2 - apps/web/package.json | 5 +- .../migration.sql | 59 ++ apps/web/prisma/schema.prisma | 74 ++- .../broadcasts/[broadcastId]/compose/page.tsx | 336 ++++++----- .../settings/account/account-settings.tsx | 330 ++++++++++- .../app/api/auth/backup-email-login/route.ts | 127 +++++ apps/web/src/app/auth/2fa-verify/content.tsx | 99 +++- apps/web/src/app/login/login-page.tsx | 93 ++- apps/web/src/server/api/routers/user.ts | 535 +++++++++++++++++- apps/web/src/server/auth.ts | 80 +++ apps/web/src/server/mailer.ts | 11 +- pnpm-lock.yaml | 145 ++--- 13 files changed, 1576 insertions(+), 320 deletions(-) create mode 100644 apps/web/prisma/migrations/20260603195140_add_backup_emails_and_two_sided_verification/migration.sql create mode 100644 apps/web/src/app/api/auth/backup-email-login/route.ts diff --git a/apps/web/next.config.js b/apps/web/next.config.js index e01fdde..aa16265 100644 --- a/apps/web/next.config.js +++ b/apps/web/next.config.js @@ -9,8 +9,6 @@ const config = { output: process.env.DOCKER_OUTPUT ? "standalone" : undefined, serverExternalPackages: ["bullmq"], transpilePackages: ["@bytesend/ui", "@bytesend/email-editor"], - typescript: { ignoreBuildErrors: true }, - eslint: { ignoreDuringBuilds: true }, images: { formats: ["image/avif", "image/webp"], remotePatterns: [ diff --git a/apps/web/package.json b/apps/web/package.json index 9ac2e1f..728a7e3 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -55,7 +55,8 @@ "@trpc/client": "^11.1.1", "@trpc/next": "^11.1.1", "@trpc/react-query": "^11.1.1", - "@trpc/server": "^11.1.1", + "@trpc/server": "^11.8.0", + "bcryptjs": "^3.0.3", "bullmq": "^5.51.1", "bytesend-js": "workspace:*", "chrono-node": "^2.8.0", @@ -69,7 +70,7 @@ "lucide-react": "^0.503.0", "mime-types": "^3.0.1", "nanoid": "^5.1.5", - "next": "15.5.9", + "next": "^16.2.7", "next-auth": "^4.24.14", "nodemailer": "^7.0.3", "otplib": "^13.4.1", diff --git a/apps/web/prisma/migrations/20260603195140_add_backup_emails_and_two_sided_verification/migration.sql b/apps/web/prisma/migrations/20260603195140_add_backup_emails_and_two_sided_verification/migration.sql new file mode 100644 index 0000000..502359b --- /dev/null +++ b/apps/web/prisma/migrations/20260603195140_add_backup_emails_and_two_sided_verification/migration.sql @@ -0,0 +1,59 @@ +/* + Warnings: + + - You are about to drop the column `code` on the `PendingEmailChange` table. All the data in the column will be lost. + - Added the required column `codeNew` to the `PendingEmailChange` table without a default value. This is not possible if the table is not empty. + +*/ +-- AlterTable +ALTER TABLE "PendingEmailChange" DROP COLUMN "code", +ADD COLUMN "codeNew" TEXT NOT NULL, +ADD COLUMN "codeOld" TEXT, +ADD COLUMN "verifiedNew" BOOLEAN NOT NULL DEFAULT false, +ADD COLUMN "verifiedOld" BOOLEAN NOT NULL DEFAULT false; + +-- CreateTable +CREATE TABLE "BackupEmail" ( + "id" TEXT NOT NULL, + "userId" INTEGER NOT NULL, + "email" TEXT NOT NULL, + "passwordHash" TEXT NOT NULL, + "emailVerified" TIMESTAMP(3), + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "BackupEmail_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "PendingBackupEmailVerification" ( + "id" TEXT NOT NULL, + "userId" INTEGER NOT NULL, + "email" TEXT NOT NULL, + "code" TEXT NOT NULL, + "expiresAt" TIMESTAMP(3) NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "PendingBackupEmailVerification_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "BackupEmail_email_key" ON "BackupEmail"("email"); + +-- CreateIndex +CREATE INDEX "BackupEmail_userId_emailVerified_idx" ON "BackupEmail"("userId", "emailVerified"); + +-- CreateIndex +CREATE INDEX "BackupEmail_userId_idx" ON "BackupEmail"("userId"); + +-- CreateIndex +CREATE INDEX "PendingBackupEmailVerification_expiresAt_idx" ON "PendingBackupEmailVerification"("expiresAt"); + +-- CreateIndex +CREATE UNIQUE INDEX "PendingBackupEmailVerification_userId_email_key" ON "PendingBackupEmailVerification"("userId", "email"); + +-- AddForeignKey +ALTER TABLE "BackupEmail" ADD CONSTRAINT "BackupEmail_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "PendingBackupEmailVerification" ADD CONSTRAINT "PendingBackupEmailVerification_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/apps/web/prisma/schema.prisma b/apps/web/prisma/schema.prisma index a0f55cf..3f85b16 100644 --- a/apps/web/prisma/schema.prisma +++ b/apps/web/prisma/schema.prisma @@ -80,37 +80,71 @@ model VerificationToken { } model User { - id Int @id @default(autoincrement()) - name String? - email String? @unique - emailVerified DateTime? - image String? - isBetaUser Boolean @default(false) - isAdmin Boolean @default(false) - isBanned Boolean @default(false) - twoFactorEnabled Boolean @default(false) - twoFactorSecret String? - twoFactorTempSecret String? - createdAt DateTime @default(now()) - accounts Account[] - sessions Session[] - teamUsers TeamUser[] - webhookEndpoints Webhook[] - pendingEmailChanges PendingEmailChange[] - twoFactorRecoveryCodes TwoFactorRecoveryCode[] + id Int @id @default(autoincrement()) + name String? + email String? @unique + emailVerified DateTime? + image String? + isBetaUser Boolean @default(false) + isAdmin Boolean @default(false) + isBanned Boolean @default(false) + twoFactorEnabled Boolean @default(false) + twoFactorSecret String? + twoFactorTempSecret String? + createdAt DateTime @default(now()) + accounts Account[] + sessions Session[] + teamUsers TeamUser[] + webhookEndpoints Webhook[] + pendingEmailChanges PendingEmailChange[] + twoFactorRecoveryCodes TwoFactorRecoveryCode[] + backupEmails BackupEmail[] @relation("userBackupEmails") + pendingBackupEmailVerifications PendingBackupEmailVerification[] } model PendingEmailChange { + id String @id @default(cuid()) + userId Int + newEmail String + codeOld String? + codeNew String + verifiedOld Boolean @default(false) + verifiedNew Boolean @default(false) + expiresAt DateTime + createdAt DateTime @default(now()) + + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@unique([userId, newEmail]) + @@index([expiresAt]) +} + +model BackupEmail { + id String @id @default(cuid()) + userId Int + email String @unique + passwordHash String + emailVerified DateTime? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + user User @relation("userBackupEmails", fields: [userId], references: [id], onDelete: Cascade) + + @@index([userId, emailVerified]) + @@index([userId]) +} + +model PendingBackupEmailVerification { id String @id @default(cuid()) userId Int - newEmail String + email String code String expiresAt DateTime createdAt DateTime @default(now()) user User @relation(fields: [userId], references: [id], onDelete: Cascade) - @@unique([userId, newEmail]) + @@unique([userId, email]) @@index([expiresAt]) } diff --git a/apps/web/src/app/(dashboard)/broadcasts/[broadcastId]/compose/page.tsx b/apps/web/src/app/(dashboard)/broadcasts/[broadcastId]/compose/page.tsx index c2a2b5a..aa90d75 100644 --- a/apps/web/src/app/(dashboard)/broadcasts/[broadcastId]/compose/page.tsx +++ b/apps/web/src/app/(dashboard)/broadcasts/[broadcastId]/compose/page.tsx @@ -19,6 +19,12 @@ import { DialogHeader, DialogTitle, } from "@bytesend/ui/src/dialog"; +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from "@bytesend/ui/src/accordion"; import { toast } from "@bytesend/ui/src/toaster"; import { useDebouncedCallback } from "use-debounce"; import { formatDistanceToNow } from "date-fns"; @@ -275,178 +281,206 @@ function BroadcastComposer({ - {/* Body: sidebar + editor */} -
- {/* Left sidebar */} -