diff --git a/AGENTS.md b/AGENTS.md index c334c89b..5e2b2b20 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -39,7 +39,7 @@ e2b template build --name your-template-name --cmd "/compile_page.sh" ### Tech Stack - **Frontend**: Next.js 15 (App Router), React 19, TypeScript, Tailwind CSS v4, Shadcn/ui - **Backend**: Convex (real-time database), tRPC (type-safe APIs) -- **Auth**: Clerk with JWT authentication +- **Auth**: WorkOS AuthKit with JWT authentication - **AI**: Vercel AI Gateway (Claude via Anthropic), Inngest Agent Kit - **Code Execution**: E2B Code Interpreter (isolated sandboxes) - **Background Jobs**: Inngest @@ -123,9 +123,10 @@ Required for development: - `AI_GATEWAY_API_KEY`: Vercel AI Gateway key - `AI_GATEWAY_BASE_URL`: https://ai-gateway.vercel.sh/v1/ - `E2B_API_KEY`: E2B sandbox API key -- `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY`: Clerk publishable key -- `CLERK_SECRET_KEY`: Clerk secret key -- `CLERK_JWT_ISSUER_DOMAIN`: Clerk JWT issuer domain (from dashboard) +- `WORKOS_API_KEY`: WorkOS API key (from dashboard) +- `WORKOS_CLIENT_ID`: WorkOS client ID (from dashboard) +- `WORKOS_REDIRECT_URI`: Auth callback URL (e.g., http://localhost:3000/callback) +- `WORKOS_WEBHOOK_SECRET`: WorkOS webhook secret (from dashboard) - `INNGEST_EVENT_KEY`: Inngest event key - `INNGEST_SIGNING_KEY`: Inngest signing key diff --git a/CLAUDE.md b/CLAUDE.md index 79946434..e176185d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -10,7 +10,7 @@ ZapDev is an AI-powered development platform that enables users to create web ap **Frontend**: Next.js 15 (Turbopack), React 19, TypeScript 5.9, Tailwind CSS v4, Shadcn/ui, React Query **Backend**: Convex (real-time database), tRPC (type-safe APIs) -**Authentication**: Clerk (user auth & JWT) +**Authentication**: WorkOS AuthKit (user auth & JWT) **AI & Execution**: Vercel AI Gateway, Inngest 3.44 (job orchestration), E2B Code Interpreter (sandboxes) **Monitoring**: Sentry, OpenTelemetry @@ -176,6 +176,8 @@ Subscriptions enable real-time UI updates when data changes. **Query Client**: React Query configured in `src/trpc/query-client.ts` for caching, refetching, and optimistic updates. +**Authentication Flow**: WorkOS AuthKit provides hosted authentication UI with OAuth callback handling. Users are redirected to WorkOS for sign-in/sign-up, then returned to the callback route with session tokens. + ## Configuration ### Environment Variables @@ -192,10 +194,12 @@ CONVEX_DEPLOYMENT # Code Execution E2B_API_KEY -# Authentication (Clerk) -NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY -CLERK_SECRET_KEY -CLERK_JWT_ISSUER_DOMAIN # From Clerk Dashboard → JWT Templates +# Authentication (WorkOS) +WORKOS_API_KEY # From WorkOS Dashboard → API Keys +WORKOS_CLIENT_ID # From WorkOS Dashboard → Configuration +WORKOS_REDIRECT_URI # Auth callback URL (e.g., http://localhost:3000/callback) +WORKOS_WEBHOOK_SECRET # From WorkOS Dashboard → Webhooks +WORKOS_API_URL # WorkOS API endpoint (https://api.workos.com) # File Upload (UploadThing) UPLOADTHING_TOKEN # Get from https://uploadthing.com/dashboard diff --git a/README.md b/README.md index 0c506dd0..c916dfb7 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ AI-powered development platform that lets you create web applications by chattin - 💬 Conversational project development with message history - 🎯 Smart usage tracking and rate limiting - 💳 Subscription management with pro features -- 🔐 Authentication with Clerk +- 🔐 Authentication with WorkOS AuthKit - ⚙️ Background job processing with Inngest - 🗃️ Project management and persistence @@ -23,11 +23,11 @@ AI-powered development platform that lets you create web applications by chattin - Tailwind CSS v4 - Shadcn/ui - tRPC -- Prisma ORM -- PostgreSQL +- Convex (real-time database) +- tRPC (type-safe APIs) - Vercel AI Gateway (supports OpenAI, Anthropic, Grok, and more) - E2B Code Interpreter -- Clerk Authentication +- WorkOS AuthKit - Inngest - Prisma - Radix UI @@ -142,13 +142,12 @@ AI_GATEWAY_BASE_URL="https://ai-gateway.vercel.sh/v1/" # E2B E2B_API_KEY="" -# Clerk -NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY="" -CLERK_SECRET_KEY="" -NEXT_PUBLIC_CLERK_SIGN_IN_URL="/sign-in" -NEXT_PUBLIC_CLERK_SIGN_UP_URL="/sign-up" -NEXT_PUBLIC_CLERK_SIGN_IN_FALLBACK_REDIRECT_URL="/" -NEXT_PUBLIC_CLERK_SIGN_UP_FALLBACK_REDIRECT_URL="/" +# WorkOS Authentication +WORKOS_API_KEY="" # From WorkOS Dashboard → API Keys +WORKOS_CLIENT_ID="" # From WorkOS Dashboard → Configuration +WORKOS_REDIRECT_URI="http://localhost:3000/callback" # Auth callback URL +WORKOS_WEBHOOK_SECRET="" # From WorkOS Dashboard → Webhooks +WORKOS_API_URL="https://api.workos.com" # Inngest (for background job processing) INNGEST_EVENT_KEY="" diff --git a/assets/image-7ce41e19-6014-4c28-a2d6-9ea1b063ad9d.png b/assets/image-7ce41e19-6014-4c28-a2d6-9ea1b063ad9d.png new file mode 100644 index 00000000..e69de29b diff --git a/bun.lock b/bun.lock index 715c66fe..7115c8ab 100644 --- a/bun.lock +++ b/bun.lock @@ -5,7 +5,6 @@ "": { "name": "vibe", "dependencies": { - "@clerk/nextjs": "^6.35.5", "@databuddy/sdk": "^2.2.1", "@e2b/code-interpreter": "^1.5.1", "@hookform/resolvers": "^3.3.4", @@ -52,6 +51,7 @@ "@typescript/native-preview": "^7.0.0-dev.20251104.1", "@uploadthing/react": "^7.3.3", "@vercel/speed-insights": "^1.2.0", + "@workos-inc/authkit-nextjs": "^0.14.0", "canvas-confetti": "^1.9.4", "class-variance-authority": "^0.7.1", "claude": "^0.1.2", @@ -192,16 +192,10 @@ "@bufbuild/protobuf": ["@bufbuild/protobuf@2.9.0", "", {}, "sha512-rnJenoStJ8nvmt9Gzye8nkYd6V22xUAnu4086ER7h1zJ508vStko4pMvDeQ446ilDTFpV5wnoc5YS7XvMwwMqA=="], - "@clerk/backend": ["@clerk/backend@2.24.0", "", { "dependencies": { "@clerk/shared": "^3.36.0", "@clerk/types": "^4.101.3", "cookie": "1.0.2", "standardwebhooks": "^1.0.0", "tslib": "2.8.1" } }, "sha512-6If+zmUiNEWVt5MoTjCl+0xiAq+uRR5kj+6HBXDG75KwRrG0eW3gH43QntQYBQU/SqhGwY2UbogEplM8ndF7Fg=="], - "@clerk/clerk-react": ["@clerk/clerk-react@5.57.0", "", { "dependencies": { "@clerk/shared": "^3.36.0", "tslib": "2.8.1" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-0", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-0" } }, "sha512-GCBFF03HjEWvx58myjauJ7NrwTqhxHdetjWWxVM3YJGPOsAVXg4WuquL/hyn8KDuduCYSkRin4Hg6+QVP1NXAg=="], - "@clerk/nextjs": ["@clerk/nextjs@6.35.5", "", { "dependencies": { "@clerk/backend": "^2.24.0", "@clerk/clerk-react": "^5.57.0", "@clerk/shared": "^3.36.0", "@clerk/types": "^4.101.3", "server-only": "0.0.1", "tslib": "2.8.1" }, "peerDependencies": { "next": "^13.5.7 || ^14.2.25 || ^15.2.3 || ^16", "react": "^18.0.0 || ^19.0.0 || ^19.0.0-0", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-0" } }, "sha512-UmVSrfvMpkAU4ky+bam9bSc/Uvbv0XKR+KMWGWBKiGxsMluQI9Xt2bm7kPoxhHvTUnUQzP3BFgkbEgwIKeL4nQ=="], - "@clerk/shared": ["@clerk/shared@3.36.0", "", { "dependencies": { "csstype": "3.1.3", "dequal": "2.0.3", "glob-to-regexp": "0.4.1", "js-cookie": "3.0.5", "std-env": "^3.9.0", "swr": "2.3.4" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-0", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-0" }, "optionalPeers": ["react", "react-dom"] }, "sha512-Yp4tL/x/iVft40DnxBjT/g/kQilZ+i9mYrqC1Lk6fUnfZV8t7E54GX19JtJSSONzjHsH6sCv3BmJaF1f7Eomkw=="], - "@clerk/types": ["@clerk/types@4.101.3", "", { "dependencies": { "@clerk/shared": "^3.36.0" } }, "sha512-QkYSiR8EDjLhQ3K9aCZ323knzZQggzhi3qxSdFrtI/C8Osyytua3Bu4TOGGRgYSSD4VO3s8WUz3wQf4Qe0ps/g=="], - "@connectrpc/connect": ["@connectrpc/connect@2.0.0-rc.3", "", { "peerDependencies": { "@bufbuild/protobuf": "^2.2.0" } }, "sha512-ARBt64yEyKbanyRETTjcjJuHr2YXorzQo0etyS5+P6oSeW8xEuzajA9g+zDnMcj1hlX2dQE93foIWQGfpru7gQ=="], "@connectrpc/connect-web": ["@connectrpc/connect-web@2.0.0-rc.3", "", { "peerDependencies": { "@bufbuild/protobuf": "^2.2.0", "@connectrpc/connect": "2.0.0-rc.3" } }, "sha512-w88P8Lsn5CCsA7MFRl2e6oLY4J/5toiNtJns/YJrlyQaWOy3RO8pDgkz+iIkG98RPMhj2thuBvsd3Cn4DKKCkw=="], @@ -624,6 +618,12 @@ "@opentelemetry/sql-common": ["@opentelemetry/sql-common@0.41.2", "", { "dependencies": { "@opentelemetry/core": "^2.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0" } }, "sha512-4mhWm3Z8z+i508zQJ7r6Xi7y4mmoJpdvH0fZPFRkWrdp5fq7hhZ2HhYokEOLkfqSMgPR4Z9EyB3DBkbKGOqZiQ=="], + "@peculiar/asn1-schema": ["@peculiar/asn1-schema@2.6.0", "", { "dependencies": { "asn1js": "^3.0.6", "pvtsutils": "^1.3.6", "tslib": "^2.8.1" } }, "sha512-xNLYLBFTBKkCzEZIw842BxytQQATQv+lDTCEMZ8C196iJcJJMBUZxrhSTxLaohMyKK8QlzRNTRkUmanucnDSqg=="], + + "@peculiar/json-schema": ["@peculiar/json-schema@1.1.12", "", { "dependencies": { "tslib": "^2.0.0" } }, "sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w=="], + + "@peculiar/webcrypto": ["@peculiar/webcrypto@1.5.0", "", { "dependencies": { "@peculiar/asn1-schema": "^2.3.8", "@peculiar/json-schema": "^1.1.12", "pvtsutils": "^1.3.5", "tslib": "^2.6.2", "webcrypto-core": "^1.8.0" } }, "sha512-BRs5XUAwiyCDQMsVA9IDvDa7UBR9gAvPHgugOeGng3YN6vJ9JYonyDc0lNczErgtCWtucjR5N7VtaonboD/ezg=="], + "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], "@pkgr/core": ["@pkgr/core@0.2.9", "", {}, "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA=="], @@ -914,6 +914,8 @@ "@tybys/wasm-util": ["@tybys/wasm-util@0.9.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw=="], + "@types/accepts": ["@types/accepts@1.3.7", "", { "dependencies": { "@types/node": "*" } }, "sha512-Pay9fq2lM2wXPWbteBsRAGiWH2hig4ZE2asK+mm7kUzlxRTfL961rj89I6zV/E3PcIkDqyuBEcMxFT7rccugeQ=="], + "@types/aws-lambda": ["@types/aws-lambda@8.10.155", "", {}, "sha512-wd1XgoL0gy/ybo7WozUKQBd+IOgUkdfG6uUGI0fQOTEq06FBFdO7tmPDSxgjkFkl8GlfApvk5TvqZlAl0g+Lbg=="], "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="], @@ -924,12 +926,20 @@ "@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="], + "@types/body-parser": ["@types/body-parser@1.19.6", "", { "dependencies": { "@types/connect": "*", "@types/node": "*" } }, "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g=="], + "@types/bunyan": ["@types/bunyan@1.8.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-758fRH7umIMk5qt5ELmRMff4mLDlN+xyYzC+dkPTdKwbSkJFvz6xwyScrytPU0QIBbRRwbiE8/BIg8bpajerNQ=="], "@types/canvas-confetti": ["@types/canvas-confetti@1.9.0", "", {}, "sha512-aBGj/dULrimR1XDZLtG9JwxX1b4HPRF6CX9Yfwh3NvstZEm1ZL7RBnel4keCPSqs1ANRu1u2Aoz9R+VmtjYuTg=="], "@types/connect": ["@types/connect@3.4.38", "", { "dependencies": { "@types/node": "*" } }, "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug=="], + "@types/content-disposition": ["@types/content-disposition@0.5.9", "", {}, "sha512-8uYXI3Gw35MhiVYhG3s295oihrxRyytcRHjSjqnqZVDDy/xcGBRny7+Xj1Wgfhv5QzRtN2hB2dVRBUX9XW3UcQ=="], + + "@types/cookie": ["@types/cookie@0.5.4", "", {}, "sha512-7z/eR6O859gyWIAjuvBWFzNURmf2oPBmJlfVWkwehU5nzIyjwBsTh7WMmEEV4JFnHuQ3ex4oyTvfKzcyJVDBNA=="], + + "@types/cookies": ["@types/cookies@0.9.2", "", { "dependencies": { "@types/connect": "*", "@types/express": "*", "@types/keygrip": "*", "@types/node": "*" } }, "sha512-1AvkDdZM2dbyFybL4fxpuNCaWyv//0AwsuUk2DWeXyM1/5ZKm6W3z6mQi24RZ4l2ucY+bkSHzbDVpySqPGuV8A=="], + "@types/d3-array": ["@types/d3-array@3.2.1", "", {}, "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg=="], "@types/d3-color": ["@types/d3-color@3.1.3", "", {}, "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="], @@ -958,8 +968,16 @@ "@types/estree-jsx": ["@types/estree-jsx@1.0.5", "", { "dependencies": { "@types/estree": "*" } }, "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg=="], + "@types/express": ["@types/express@4.17.25", "", { "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.33", "@types/qs": "*", "@types/serve-static": "^1" } }, "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw=="], + + "@types/express-serve-static-core": ["@types/express-serve-static-core@4.19.7", "", { "dependencies": { "@types/node": "*", "@types/qs": "*", "@types/range-parser": "*", "@types/send": "*" } }, "sha512-FvPtiIf1LfhzsaIXhv/PHan/2FeQBbtBDtfX2QfvPxdUelMDEckK08SM6nqo1MIZY3RUlfA+HV8+hFUSio78qg=="], + "@types/hast": ["@types/hast@3.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ=="], + "@types/http-assert": ["@types/http-assert@1.5.6", "", {}, "sha512-TTEwmtjgVbYAzZYWyeHPrrtWnfVkm8tQkP8P21uQifPgMRgjrow3XDEYqucuC8SKZJT7pUnhU/JymvjggxO9vw=="], + + "@types/http-errors": ["@types/http-errors@2.0.5", "", {}, "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg=="], + "@types/istanbul-lib-coverage": ["@types/istanbul-lib-coverage@2.0.6", "", {}, "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w=="], "@types/istanbul-lib-report": ["@types/istanbul-lib-report@3.0.3", "", { "dependencies": { "@types/istanbul-lib-coverage": "*" } }, "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA=="], @@ -970,10 +988,18 @@ "@types/json5": ["@types/json5@0.0.29", "", {}, "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ=="], + "@types/keygrip": ["@types/keygrip@1.0.6", "", {}, "sha512-lZuNAY9xeJt7Bx4t4dx0rYCDqGPW8RXhQZK1td7d4H6E9zYbLoOtjBvfwdTKpsyxQI/2jv+armjX/RW+ZNpXOQ=="], + + "@types/koa": ["@types/koa@2.15.0", "", { "dependencies": { "@types/accepts": "*", "@types/content-disposition": "*", "@types/cookies": "*", "@types/http-assert": "*", "@types/http-errors": "*", "@types/keygrip": "*", "@types/koa-compose": "*", "@types/node": "*" } }, "sha512-7QFsywoE5URbuVnG3loe03QXuGajrnotr3gQkXcEBShORai23MePfFYdhz90FEtBBpkyIYQbVD+evKtloCgX3g=="], + + "@types/koa-compose": ["@types/koa-compose@3.2.9", "", { "dependencies": { "@types/koa": "*" } }, "sha512-BroAZ9FTvPiCy0Pi8tjD1OfJ7bgU1gQf0eR6e1Vm+JJATy9eKOG3hQMFtMciMawiSOVnLMdmUOC46s7HBhSTsA=="], + "@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="], "@types/memcached": ["@types/memcached@2.2.10", "", { "dependencies": { "@types/node": "*" } }, "sha512-AM9smvZN55Gzs2wRrqeMHVP7KE8KWgCJO/XL5yCly2xF6EKa4YlbpK+cLSAH4NG/Ah64HrlegmGqW8kYws7Vxg=="], + "@types/mime": ["@types/mime@1.3.5", "", {}, "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w=="], + "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="], "@types/mysql": ["@types/mysql@2.15.27", "", { "dependencies": { "@types/node": "*" } }, "sha512-YfWiV16IY0OeBfBCk8+hXKmdTKrKlwKN1MNKAPBu5JYxLwBEZl7QzeEpGnlZb3VMGJrrGmB84gXiH+ofs/TezA=="], @@ -988,10 +1014,18 @@ "@types/prismjs": ["@types/prismjs@1.26.5", "", {}, "sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ=="], + "@types/qs": ["@types/qs@6.14.0", "", {}, "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ=="], + + "@types/range-parser": ["@types/range-parser@1.2.7", "", {}, "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ=="], + "@types/react": ["@types/react@19.2.2", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA=="], "@types/react-dom": ["@types/react-dom@19.2.2", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw=="], + "@types/send": ["@types/send@0.17.6", "", { "dependencies": { "@types/mime": "^1", "@types/node": "*" } }, "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og=="], + + "@types/serve-static": ["@types/serve-static@1.15.10", "", { "dependencies": { "@types/http-errors": "*", "@types/node": "*", "@types/send": "<1" } }, "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw=="], + "@types/shimmer": ["@types/shimmer@1.2.0", "", {}, "sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg=="], "@types/stack-utils": ["@types/stack-utils@2.0.3", "", {}, "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw=="], @@ -1118,6 +1152,10 @@ "@webassemblyjs/wast-printer": ["@webassemblyjs/wast-printer@1.14.1", "", { "dependencies": { "@webassemblyjs/ast": "1.14.1", "@xtuc/long": "4.2.2" } }, "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw=="], + "@workos-inc/authkit-nextjs": ["@workos-inc/authkit-nextjs@0.14.0", "", { "dependencies": { "@workos-inc/node": "^7.29.0", "iron-session": "^8.0.1", "jose": "^5.2.3", "path-to-regexp": "^6.2.2" }, "peerDependencies": { "next": "^13.5.4 || ^14.0.3 || ^15.0.0", "react": "^18.0 || ^19.0.0", "react-dom": "^18.0 || ^19.0.0" } }, "sha512-6hBBw4HPq00aP4jO0DHuF4gO6U+MLCeJLJ+eJSRqNSw0rhojJLbmNNyG55XH2OgNhKk2qomSlqBqurFnFq7lZA=="], + + "@workos-inc/node": ["@workos-inc/node@7.74.2", "", { "dependencies": { "iron-session": "~6.3.1", "jose": "~5.6.3", "leb": "^1.0.0", "qs": "6.14.0" } }, "sha512-uXLEUqNo7AkM8oBzVPY29uyEpU0r8Jt2SppL2KzwFWB3f0hLLzp51wrIQ4PNJgd9qI5L/+kP62SoZFc8byI+4g=="], + "@xtuc/ieee754": ["@xtuc/ieee754@1.2.0", "", {}, "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA=="], "@xtuc/long": ["@xtuc/long@4.2.2", "", {}, "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ=="], @@ -1172,6 +1210,8 @@ "arraybuffer.prototype.slice": ["arraybuffer.prototype.slice@1.0.4", "", { "dependencies": { "array-buffer-byte-length": "^1.0.1", "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "is-array-buffer": "^3.0.4" } }, "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ=="], + "asn1js": ["asn1js@3.0.6", "", { "dependencies": { "pvtsutils": "^1.3.6", "pvutils": "^1.1.3", "tslib": "^2.8.1" } }, "sha512-UOCGPYbl0tv8+006qks/dTgV9ajs97X2p0FAbyS2iyCRrmLSRolDaHdp+v/CLgnzHc3fVB+CwYiUmei7ndFcgA=="], + "ast-types-flow": ["ast-types-flow@0.0.8", "", {}, "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ=="], "async-function": ["async-function@1.0.0", "", {}, "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA=="], @@ -1200,6 +1240,8 @@ "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], + "baseline-browser-mapping": ["baseline-browser-mapping@2.8.14", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-GM9c0cWWR8Ga7//Ves/9KRgTS8nLausCkP3CGiFLrnwA2CDUluXgaQqvrULoR2Ujrd/mz/lkX87F5BHFsNr5sQ=="], "bignumber.js": ["bignumber.js@9.3.0", "", {}, "sha512-EM7aMFTXbptt/wZdMlBv2t8IViwQL+h6SLHosp8Yf0dqJMTnY6iL32opnAB6kAdL0SZPuvcAzFr31o0c/R3/RA=="], @@ -1218,6 +1260,8 @@ "bser": ["bser@2.1.1", "", { "dependencies": { "node-int64": "^0.4.0" } }, "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ=="], + "buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="], + "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="], @@ -1302,7 +1346,7 @@ "convex": ["convex@1.29.0", "", { "dependencies": { "esbuild": "0.25.4", "prettier": "^3.0.0" }, "peerDependencies": { "@auth0/auth0-react": "^2.0.1", "@clerk/clerk-react": "^4.12.8 || ^5.0.0", "react": "^18.0.0 || ^19.0.0-0 || ^19.0.0" }, "optionalPeers": ["@auth0/auth0-react", "@clerk/clerk-react", "react"], "bin": { "convex": "bin/main.js" } }, "sha512-uoIPXRKIp2eLCkkR9WJ2vc9NtgQtx8Pml59WPUahwbrd5EuW2WLI/cf2E7XrUzOSifdQC3kJZepisk4wJNTJaA=="], - "cookie": ["cookie@1.0.2", "", {}, "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA=="], + "cookie": ["cookie@0.7.1", "", {}, "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w=="], "cookie-signature": ["cookie-signature@1.0.6", "", {}, "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="], @@ -1660,6 +1704,8 @@ "iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], + "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], + "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], "immediate": ["immediate@3.0.6", "", {}, "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="], @@ -1688,6 +1734,10 @@ "ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="], + "iron-session": ["iron-session@8.0.4", "", { "dependencies": { "cookie": "^0.7.2", "iron-webcrypto": "^1.2.1", "uncrypto": "^0.1.3" } }, "sha512-9ivNnaKOd08osD0lJ3i6If23GFS2LsxyMU8Gf/uBUEgm8/8CC1hrrCHFDpMo3IFbpBgwoo/eairRsaD3c5itxA=="], + + "iron-webcrypto": ["iron-webcrypto@1.2.1", "", {}, "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg=="], + "is-alphabetical": ["is-alphabetical@2.0.1", "", {}, "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ=="], "is-alphanumerical": ["is-alphanumerical@2.0.1", "", { "dependencies": { "is-alphabetical": "^2.0.0", "is-decimal": "^2.0.0" } }, "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw=="], @@ -1870,6 +1920,8 @@ "language-tags": ["language-tags@1.0.9", "", { "dependencies": { "language-subtag-registry": "^0.3.20" } }, "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA=="], + "leb": ["leb@1.0.0", "", {}, "sha512-Y3c3QZfvKWHX60BVOQPhLCvVGmDYWyJEiINE3drOog6KCyN2AOwvuQQzlS3uJg1J85kzpILXIUwRXULWavir+w=="], + "leven": ["leven@3.1.0", "", {}, "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A=="], "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], @@ -2120,7 +2172,7 @@ "path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], - "path-to-regexp": ["path-to-regexp@0.1.12", "", {}, "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ=="], + "path-to-regexp": ["path-to-regexp@6.3.0", "", {}, "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ=="], "pg-int8": ["pg-int8@1.0.1", "", {}, "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw=="], @@ -2178,6 +2230,10 @@ "pure-rand": ["pure-rand@6.1.0", "", {}, "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA=="], + "pvtsutils": ["pvtsutils@1.3.6", "", { "dependencies": { "tslib": "^2.8.1" } }, "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg=="], + + "pvutils": ["pvutils@1.1.5", "", {}, "sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA=="], + "qs": ["qs@6.13.0", "", { "dependencies": { "side-channel": "^1.0.6" } }, "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg=="], "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], @@ -2464,6 +2520,8 @@ "unbox-primitive": ["unbox-primitive@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "has-bigints": "^1.0.2", "has-symbols": "^1.1.0", "which-boxed-primitive": "^1.1.1" } }, "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw=="], + "uncrypto": ["uncrypto@0.1.3", "", {}, "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q=="], + "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], "unicode-emoji-modifier-base": ["unicode-emoji-modifier-base@1.0.0", "", {}, "sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g=="], @@ -2528,6 +2586,8 @@ "web-vitals": ["web-vitals@5.1.0", "", {}, "sha512-ArI3kx5jI0atlTtmV0fWU3fjpLmq/nD3Zr1iFFlJLaqa5wLBkUSzINwBPySCX/8jRyjlmy1Volw1kz1g9XE4Jg=="], + "webcrypto-core": ["webcrypto-core@1.8.1", "", { "dependencies": { "@peculiar/asn1-schema": "^2.3.13", "@peculiar/json-schema": "^1.1.12", "asn1js": "^3.0.5", "pvtsutils": "^1.3.5", "tslib": "^2.7.0" } }, "sha512-P+x1MvlNCXlKbLSOY4cYrdreqPG5hbzkmawbcXLKN/mf6DZW0SdNNkZ+sjwsqVkI4A4Ko2sPZmkZtCKY58w83A=="], + "webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], "webpack": ["webpack@5.102.1", "", { "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", "@types/json-schema": "^7.0.15", "@webassemblyjs/ast": "^1.14.1", "@webassemblyjs/wasm-edit": "^1.14.1", "@webassemblyjs/wasm-parser": "^1.14.1", "acorn": "^8.15.0", "acorn-import-phases": "^1.0.3", "browserslist": "^4.26.3", "chrome-trace-event": "^1.0.2", "enhanced-resolve": "^5.17.3", "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.2.11", "json-parse-even-better-errors": "^2.3.1", "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", "schema-utils": "^4.3.3", "tapable": "^2.3.0", "terser-webpack-plugin": "^5.3.11", "watchpack": "^2.4.4", "webpack-sources": "^3.3.3" }, "bin": { "webpack": "bin/webpack.js" } }, "sha512-7h/weGm9d/ywQ6qzJ+Xy+r9n/3qgp/thalBbpOi5i223dPXKi04IBtqPN9nTd+jBc7QKfvDbaBnFipYp4sJAUQ=="], @@ -2952,6 +3012,14 @@ "@typescript-eslint/typescript-estree/semver": ["semver@7.7.2", "", { "bin": "bin/semver.js" }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="], + "@workos-inc/authkit-nextjs/jose": ["jose@5.10.0", "", {}, "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg=="], + + "@workos-inc/node/iron-session": ["iron-session@6.3.1", "", { "dependencies": { "@peculiar/webcrypto": "^1.4.0", "@types/cookie": "^0.5.1", "@types/express": "^4.17.13", "@types/koa": "^2.13.5", "@types/node": "^17.0.41", "cookie": "^0.5.0", "iron-webcrypto": "^0.2.5" }, "peerDependencies": { "express": ">=4", "koa": ">=2", "next": ">=10" }, "optionalPeers": ["express", "koa", "next"] }, "sha512-3UJ7y2vk/WomAtEySmPgM6qtYF1cZ3tXuWX5GsVX4PJXAcs5y/sV9HuSfpjKS6HkTL/OhZcTDWJNLZ7w+Erx3A=="], + + "@workos-inc/node/jose": ["jose@5.6.3", "", {}, "sha512-1Jh//hEEwMhNYPDDLwXHa2ePWgWiFNNUadVmguAAw2IJ6sj9mNxV5tGXJNqlMkJAybF6Lgw1mISDxTePP/187g=="], + + "@workos-inc/node/qs": ["qs@6.14.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w=="], + "accepts/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], "ajv-formats/ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="], @@ -2994,10 +3062,10 @@ "execa/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], - "express/cookie": ["cookie@0.7.1", "", {}, "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w=="], - "express/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], + "express/path-to-regexp": ["path-to-regexp@0.1.12", "", {}, "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ=="], + "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], "finalhandler/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], @@ -3020,6 +3088,8 @@ "inngest/zod": ["zod@4.1.12", "", {}, "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ=="], + "iron-session/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], + "is-bun-module/semver": ["semver@7.7.2", "", { "bin": "bin/semver.js" }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="], "istanbul-lib-instrument/semver": ["semver@7.7.2", "", { "bin": "bin/semver.js" }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="], @@ -3198,8 +3268,6 @@ "@modelcontextprotocol/sdk/express/content-disposition": ["content-disposition@1.0.0", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg=="], - "@modelcontextprotocol/sdk/express/cookie": ["cookie@0.7.1", "", {}, "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w=="], - "@modelcontextprotocol/sdk/express/cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="], "@modelcontextprotocol/sdk/express/finalhandler": ["finalhandler@2.1.0", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q=="], @@ -3436,6 +3504,12 @@ "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + "@workos-inc/node/iron-session/@types/node": ["@types/node@17.0.45", "", {}, "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw=="], + + "@workos-inc/node/iron-session/cookie": ["cookie@0.5.0", "", {}, "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw=="], + + "@workos-inc/node/iron-session/iron-webcrypto": ["iron-webcrypto@0.2.8", "", { "dependencies": { "buffer": "^6" } }, "sha512-YPdCvjFMOBjXaYuDj5tiHst5CEk6Xw84Jo8Y2+jzhMceclAnb3+vNPP/CTtb5fO2ZEuXEaO4N+w62Vfko757KA=="], + "accepts/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], "ajv-formats/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], diff --git a/convex/auth.config.ts b/convex/auth.config.ts index 5658d849..ebcab4ba 100644 --- a/convex/auth.config.ts +++ b/convex/auth.config.ts @@ -1,8 +1,25 @@ +const workosApiUrl = process.env.WORKOS_API_URL || "https://api.workos.com"; +const workosClientId = process.env.WORKOS_CLIENT_ID; + +if (!workosClientId) { + throw new Error("WORKOS_CLIENT_ID is not set in the environment"); +} + export default { providers: [ { - domain: process.env.CLERK_FRONTEND_API_URL, - applicationID: "convex", + type: "customJwt", + issuer: `${workosApiUrl}/sso`, + jwks: `${workosApiUrl}/sso/jwks`, + algorithm: "RS256", + applicationID: workosClientId, + }, + { + type: "customJwt", + issuer: `${workosApiUrl}/user_management`, + jwks: `${workosApiUrl}/user_management/jwks`, + algorithm: "RS256", + applicationID: workosClientId, }, ], }; diff --git a/explanations/WORKOS_MIGRATION_GUIDE.md b/explanations/WORKOS_MIGRATION_GUIDE.md new file mode 100644 index 00000000..33710196 --- /dev/null +++ b/explanations/WORKOS_MIGRATION_GUIDE.md @@ -0,0 +1,271 @@ +# WorkOS Migration Guide + +This guide helps you migrate from Clerk to WorkOS AuthKit authentication. + +## Overview + +We've migrated from Clerk to WorkOS AuthKit for authentication. WorkOS provides enterprise-ready authentication with better pricing (free up to 1M users) and seamless integration with Convex. + +## What Changed + +### 1. Authentication Provider +- **Before**: Clerk (`@clerk/nextjs`) +- **After**: WorkOS AuthKit (`@workos-inc/authkit-nextjs`) + +### 2. Environment Variables + +**Remove these Clerk variables:** +```bash +NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY +CLERK_SECRET_KEY +CLERK_JWT_ISSUER_DOMAIN +CLERK_WEBHOOK_SECRET +NEXT_PUBLIC_CLERK_SIGN_IN_URL +NEXT_PUBLIC_CLERK_SIGN_UP_URL +NEXT_PUBLIC_CLERK_SIGN_IN_FALLBACK_REDIRECT_URL +NEXT_PUBLIC_CLERK_SIGN_UP_FALLBACK_REDIRECT_URL +``` + +**Add these WorkOS variables:** +```bash +WORKOS_API_KEY=sk_test_xxx # From WorkOS Dashboard → API Keys +WORKOS_CLIENT_ID=client_xxx # From WorkOS Dashboard → Configuration +WORKOS_REDIRECT_URI=http://localhost:3000/callback # Auth callback URL +WORKOS_WEBHOOK_SECRET=wh_secret_xxx # From WorkOS Dashboard → Webhooks +WORKOS_API_URL=https://api.workos.com # WorkOS API endpoint +``` + +### 3. Files Changed + +**Modified:** +- `src/middleware.ts` - Uses WorkOS middleware +- `src/components/convex-provider.tsx` - Integrates WorkOS with Convex +- `src/lib/auth-server.ts` - Uses WorkOS server functions +- `src/components/user-control.tsx` - Uses WorkOS user hooks +- `src/app/sign-in/[[...sign-in]]/page.tsx` - Redirects to WorkOS +- `src/app/sign-up/[[...sign-up]]/page.tsx` - Redirects to WorkOS +- `src/modules/home/ui/components/navbar.tsx` - Updated auth buttons +- `convex/auth.config.ts` - Points to WorkOS JWKS endpoint + +**Created:** +- `src/app/callback/route.ts` - OAuth callback handler +- `src/app/sign-out/route.ts` - Sign out handler +- `src/app/api/webhooks/workos/route.ts` - WorkOS webhook handler + +**Deleted:** +- `src/lib/clerk-config.ts` - Clerk configuration (no longer needed) +- `src/app/api/webhooks/clerk/route.ts` - Clerk webhook handler + +## Setup Instructions + +### Step 1: Create WorkOS Account + +1. Go to [WorkOS Dashboard](https://dashboard.workos.com) +2. Sign up for a free account +3. Create a new environment (Development) + +### Step 2: Configure AuthKit + +1. In WorkOS Dashboard, go to **AuthKit** → **Configuration** +2. Set up your redirect URI: + - Development: `http://localhost:3000/callback` + - Production: `https://your-domain.com/callback` +3. Copy your **Client ID** + +### Step 3: Get API Keys + +1. Go to **API Keys** in WorkOS Dashboard +2. Copy your **API Key** (starts with `sk_`) +3. Store securely in your `.env.local` file + +### Step 4: Set Up Webhooks + +1. Go to **Webhooks** in WorkOS Dashboard +2. Create a new webhook endpoint: + - Development: Use ngrok or similar tunneling service + - Production: `https://your-domain.com/api/webhooks/workos` +3. Subscribe to these events: + - `user.created` + - `user.updated` + - `user.deleted` +4. Copy the **Webhook Secret** + +### Step 5: Update Environment Variables + +Create or update your `.env.local` file: + +```bash +# WorkOS Authentication +WORKOS_API_KEY=sk_test_YOUR_API_KEY +WORKOS_CLIENT_ID=client_YOUR_CLIENT_ID +WORKOS_REDIRECT_URI=http://localhost:3000/callback +WORKOS_WEBHOOK_SECRET=wh_secret_YOUR_WEBHOOK_SECRET +WORKOS_API_URL=https://api.workos.com +``` + +### Step 6: Update Convex Deployment + +If you're using Convex, update the environment variables: + +```bash +npx convex env set WORKOS_API_URL https://api.workos.com +``` + +Or set it in your Convex Dashboard → Settings → Environment Variables. + +### Step 7: Install Dependencies + +```bash +bun install +``` + +This will install `@workos-inc/authkit-nextjs` and remove `@clerk/nextjs`. + +### Step 8: Test Authentication Flow + +1. Start your development server: + ```bash + bun run dev + ``` + +2. Start Convex: + ```bash + bun run convex:dev + ``` + +3. Navigate to `http://localhost:3000` + +4. Click "Sign in" - you should be redirected to WorkOS AuthKit + +5. Create a test account and verify: + - You're redirected back to your app + - User data appears in user control dropdown + - You can access protected routes + +## User Data Mapping + +WorkOS user object differs from Clerk: + +| Clerk | WorkOS | +|-------|--------| +| `user.fullName` | `[user.firstName, user.lastName].join(' ')` | +| `user.primaryEmailAddress.emailAddress` | `user.email` | +| `user.imageUrl` | `user.profilePictureUrl` | +| `user.id` | `user.id` | + +## Migration Considerations + +### User IDs +- Existing users have Clerk-formatted user IDs in your database +- WorkOS generates different user ID formats +- You may need to: + 1. Add a migration script to map old Clerk IDs to new WorkOS IDs + 2. Maintain a mapping table during transition period + 3. Ask existing users to re-authenticate + +### Social Logins +- Reconfigure OAuth apps (Google, GitHub, etc.) in WorkOS Dashboard +- Update redirect URIs in each OAuth provider to point to WorkOS + +### Custom Claims +- Clerk: Used `publicMetadata` for custom claims (e.g., `plan: "pro"`) +- WorkOS: Uses organization metadata for custom claims +- Current implementation: Plan is stored in Convex `subscriptions` table + +### Session Management +- WorkOS sessions work differently from Clerk +- Session tokens are managed automatically by WorkOS SDK +- No need to manually refresh tokens + +## Troubleshooting + +### Issue: "Missing WORKOS_API_KEY" +**Solution**: Ensure all WorkOS environment variables are set in `.env.local` + +### Issue: Redirect loop after sign-in +**Solution**: Verify `WORKOS_REDIRECT_URI` matches exactly what's configured in WorkOS Dashboard + +### Issue: "Invalid session" +**Solution**: +1. Clear browser cookies +2. Verify `WORKOS_API_URL` is set correctly +3. Check Convex `auth.config.ts` points to WorkOS + +### Issue: Webhook not receiving events +**Solution**: +1. For local development, use ngrok: `ngrok http 3000` +2. Update webhook URL in WorkOS Dashboard +3. Verify `WORKOS_WEBHOOK_SECRET` matches + +### Issue: User data not showing +**Solution**: +1. Check browser console for errors +2. Verify `useUser()` hook returns data +3. Ensure session is valid + +## Production Deployment + +### Vercel Deployment + +1. Add all WorkOS environment variables to Vercel: + ```bash + vercel env add WORKOS_API_KEY + vercel env add WORKOS_CLIENT_ID + vercel env add WORKOS_REDIRECT_URI + vercel env add WORKOS_WEBHOOK_SECRET + vercel env add WORKOS_API_URL + ``` + +2. Update `WORKOS_REDIRECT_URI` to production URL: + ``` + https://your-domain.com/callback + ``` + +3. Update WorkOS webhook endpoint to production: + ``` + https://your-domain.com/api/webhooks/workos + ``` + +4. Deploy: + ```bash + vercel --prod + ``` + +### Convex Production + +Update Convex production environment: + +```bash +npx convex deploy +npx convex env set WORKOS_API_URL https://api.workos.com --prod +``` + +## Benefits of WorkOS + +1. **Cost**: Free up to 1M users (vs Clerk's pricing) +2. **Enterprise Ready**: Built-in support for SSO, SCIM, RBAC +3. **Convex Integration**: Official integration with Convex +4. **Developer Experience**: Simple API, excellent docs +5. **Flexibility**: Easy to customize authentication flows + +## Support + +- [WorkOS Documentation](https://workos.com/docs) +- [WorkOS Next.js Guide](https://workos.com/docs/authkit/nextjs) +- [Convex + WorkOS Integration](https://docs.convex.dev/auth/authkit) + +## Migration Checklist + +- [ ] Create WorkOS account +- [ ] Configure AuthKit in WorkOS Dashboard +- [ ] Get API keys and webhook secret +- [ ] Update `.env.local` with WorkOS variables +- [ ] Run `bun install` +- [ ] Test sign-in/sign-up flow locally +- [ ] Verify user data displays correctly +- [ ] Test protected routes +- [ ] Set up webhooks for production +- [ ] Update Vercel environment variables +- [ ] Deploy to production +- [ ] Test production authentication flow +- [ ] Monitor for errors in Sentry/logs diff --git a/package.json b/package.json index dcb91eef..9ab0ddad 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "convex:deploy": "bunx convex deploy" }, "dependencies": { - "@clerk/nextjs": "^6.35.5", + "@workos-inc/authkit-nextjs": "^0.14.0", "@databuddy/sdk": "^2.2.1", "@e2b/code-interpreter": "^1.5.1", "@hookform/resolvers": "^3.3.4", diff --git a/src/app/api/webhooks/clerk/route.ts b/src/app/api/webhooks/clerk/route.ts deleted file mode 100644 index fc71fae9..00000000 --- a/src/app/api/webhooks/clerk/route.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { Webhook } from "svix"; -import { headers } from "next/headers"; -import { WebhookEvent } from "@clerk/nextjs/server"; -import { ConvexHttpClient } from "convex/browser"; -import { api } from "@/convex/_generated/api"; - -export async function POST(req: Request) { - // You can find this in the Clerk Dashboard -> Webhooks -> choose the webhook - const WEBHOOK_SECRET = process.env.CLERK_WEBHOOK_SECRET; - - if (!WEBHOOK_SECRET) { - throw new Error( - "Please add CLERK_WEBHOOK_SECRET from Clerk Dashboard to .env or .env.local" - ); - } - - // Get the headers - const headerPayload = await headers(); - const svix_id = headerPayload.get("svix-id"); - const svix_timestamp = headerPayload.get("svix-timestamp"); - const svix_signature = headerPayload.get("svix-signature"); - - // If there are no headers, error out - if (!svix_id || !svix_timestamp || !svix_signature) { - return new Response("Error occured -- no svix headers", { - status: 400, - }); - } - - // Get the body - const payload = await req.json(); - const body = JSON.stringify(payload); - - // Create a new Svix instance with your secret. - const wh = new Webhook(WEBHOOK_SECRET); - - let evt: WebhookEvent; - - // Verify the payload with the headers - try { - evt = wh.verify(body, { - "svix-id": svix_id, - "svix-timestamp": svix_timestamp, - "svix-signature": svix_signature, - }) as WebhookEvent; - } catch (err) { - console.error("Error verifying webhook:", err); - return new Response("Error occured", { - status: 400, - }); - } - - // Get the event type - const eventType = evt.type; - - // Initialize Convex client - const convex = new ConvexHttpClient(process.env.NEXT_PUBLIC_CONVEX_URL!); - - if (eventType === "user.created") { - const { id, email_addresses, first_name, last_name, image_url } = evt.data; - - const email = email_addresses[0]?.email_address; - const name = `${first_name || ""} ${last_name || ""}`.trim(); - - console.log(`User created: ${id} (${email})`); - - // Initialize usage for the new user - // This ensures they have free tier credits immediately - try { - await convex.mutation(api.usage.checkAndConsumeCreditForUser, { - userId: id, - }); - // We actually just want to initialize, not consume. - // But checkAndConsumeCreditForUser consumes 1 credit. - // Let's use a specific initialization mutation if possible, or just accept the 1 credit cost (unideal). - // Looking at usage.ts, there isn't a pure "init" mutation exposed. - // However, `getUsageForUser` calls `getUsageInternal` which does NOT create the record if missing (it just returns default values). - // `checkAndConsumeCreditInternal` creates it if missing. - - // Let's rely on lazy initialization for now as per the existing design in `usage.ts`. - // The `usage.ts` logic says: "If no usage record or expired, create/reset with max points" inside `checkAndConsumeCredit`. - // So we don't strictly NEED to do anything here for usage. - // But we might want to store the user in a `users` table if we had one. - // Since we don't have a `users` table (as per schema review), we just log it. - - console.log(`User ${id} created. Usage will be initialized lazily on first generation.`); - - } catch (error) { - console.error("Error initializing user data:", error); - } - } - - return new Response("", { status: 200 }); -} diff --git a/src/app/api/webhooks/workos/route.ts b/src/app/api/webhooks/workos/route.ts new file mode 100644 index 00000000..4e862e27 --- /dev/null +++ b/src/app/api/webhooks/workos/route.ts @@ -0,0 +1,93 @@ +import { WorkOS } from "@workos-inc/node"; +import { headers } from "next/headers"; + +let workos: WorkOS | null = null; + +const getWorkOSClient = () => { + if (!workos) { + const WORKOS_API_KEY = process.env.WORKOS_API_KEY; + + if (!WORKOS_API_KEY) { + throw new Error( + "Please add WORKOS_API_KEY from WorkOS Dashboard to .env or .env.local" + ); + } + + workos = new WorkOS(WORKOS_API_KEY); + } + + return workos; +}; + +export async function POST(req: Request) { + // Get WorkOS webhook secret + const WEBHOOK_SECRET = process.env.WORKOS_WEBHOOK_SECRET; + + if (!WEBHOOK_SECRET) { + throw new Error( + "Please add WORKOS_WEBHOOK_SECRET from WorkOS Dashboard to .env or .env.local" + ); + } + + // Get the headers + const headerPayload = headers(); + const signature = headerPayload.get("workos-signature"); + + if (!signature) { + return new Response("Error occurred -- no WorkOS signature header", { + status: 400, + }); + } + + const rawBody = await req.text(); + + let event; + + try { + const payload = JSON.parse(rawBody); + + event = await getWorkOSClient().webhooks.constructEvent({ + payload, + sigHeader: signature, + secret: WEBHOOK_SECRET, + }); + } catch (error) { + console.error("Error verifying WorkOS webhook:", error); + + return new Response("Invalid signature", { status: 400 }); + } + + const { event: eventType, data } = event; + + if (eventType === "user.created") { + const { id, email, firstName, lastName, profilePictureUrl } = data; + + console.log(`User created: ${id} (${email})`, { + firstName, + lastName, + profilePictureUrl, + }); + + // Initialize usage for the new user + try { + // The usage system will initialize lazily on first generation + console.log(`User ${id} created. Usage will be initialized lazily on first generation.`); + } catch (error) { + console.error("Error initializing user data:", error); + } + } + + if (eventType === "user.updated") { + const { id, email } = data; + console.log(`User updated: ${id} (${email})`); + // Handle user updates if needed + } + + if (eventType === "user.deleted") { + const { id } = data; + console.log(`User deleted: ${id}`); + // Handle user deletion if needed (cleanup projects, messages, etc.) + } + + return new Response("", { status: 200 }); +} diff --git a/src/app/callback/route.ts b/src/app/callback/route.ts new file mode 100644 index 00000000..95449baa --- /dev/null +++ b/src/app/callback/route.ts @@ -0,0 +1,3 @@ +import { handleAuth } from "@workos-inc/authkit-nextjs"; + +export const GET = handleAuth(); diff --git a/src/app/sign-in/[[...sign-in]]/page.tsx b/src/app/sign-in/[[...sign-in]]/page.tsx index 1739046f..9774a3f3 100644 --- a/src/app/sign-in/[[...sign-in]]/page.tsx +++ b/src/app/sign-in/[[...sign-in]]/page.tsx @@ -1,9 +1,7 @@ -import { SignIn } from "@clerk/nextjs"; +import { getSignInUrl } from "@workos-inc/authkit-nextjs"; +import { redirect } from "next/navigation"; -export default function Page() { - return ( -
- -
- ); +export default async function SignInPage() { + const signInUrl = await getSignInUrl(); + redirect(signInUrl); } diff --git a/src/app/sign-out/route.ts b/src/app/sign-out/route.ts new file mode 100644 index 00000000..5e3430d4 --- /dev/null +++ b/src/app/sign-out/route.ts @@ -0,0 +1,7 @@ +import { NextResponse } from "next/server"; +import { signOut } from "@workos-inc/authkit-nextjs"; + +export async function GET(request: Request) { + await signOut(); + return NextResponse.redirect(new URL("/", request.url)); +} diff --git a/src/app/sign-up/[[...sign-up]]/page.tsx b/src/app/sign-up/[[...sign-up]]/page.tsx index f7d49d23..04129b79 100644 --- a/src/app/sign-up/[[...sign-up]]/page.tsx +++ b/src/app/sign-up/[[...sign-up]]/page.tsx @@ -1,9 +1,7 @@ -import { SignUp } from "@clerk/nextjs"; +import { getSignUpUrl } from "@workos-inc/authkit-nextjs"; +import { redirect } from "next/navigation"; -export default function Page() { - return ( -
- -
- ); +export default async function SignUpPage() { + const signUpUrl = await getSignUpUrl(); + redirect(signUpUrl); } diff --git a/src/components/convex-provider.tsx b/src/components/convex-provider.tsx index cfeb1a21..879ca5ac 100644 --- a/src/components/convex-provider.tsx +++ b/src/components/convex-provider.tsx @@ -1,32 +1,41 @@ "use client"; -import { ReactNode } from "react"; +import { ReactNode, useMemo } from "react"; import { ConvexReactClient } from "convex/react"; -import { ClerkProvider, useAuth } from "@clerk/nextjs"; -import { ConvexProviderWithClerk } from "convex/react-clerk"; +import { ConvexProviderWithAuth } from "convex/react"; +import { useAccessToken } from "@workos-inc/authkit-nextjs/components"; if (!process.env.NEXT_PUBLIC_CONVEX_URL) { throw new Error("Missing NEXT_PUBLIC_CONVEX_URL in your .env file"); } -if (!process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY) { - throw new Error("Missing NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY in your .env file"); -} - const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL); export function ConvexClientProvider({ children }: { children: ReactNode }) { return ( - - - {children} - - + + {children} + + ); +} + +function useWorkOSAuth() { + const { accessToken, loading, refresh } = useAccessToken(); + + return useMemo( + () => ({ + isLoading: loading, + isAuthenticated: !!accessToken, + fetchAccessToken: async ({ + forceRefreshToken, + }: { + forceRefreshToken: boolean; + }) => { + if (!accessToken && !forceRefreshToken) return null; + if (forceRefreshToken) await refresh(); + return accessToken ?? null; + }, + }), + [accessToken, loading] ); } diff --git a/src/components/user-control.tsx b/src/components/user-control.tsx index 3d7f045a..509970eb 100644 --- a/src/components/user-control.tsx +++ b/src/components/user-control.tsx @@ -1,6 +1,6 @@ "use client"; -import { useUser, useClerk } from "@clerk/nextjs"; +import { useUser } from "@workos-inc/authkit-nextjs"; import { useRouter } from "next/navigation"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { @@ -20,16 +20,15 @@ interface Props { export const UserControl = ({ showName }: Props) => { const router = useRouter(); const { user } = useUser(); - const { signOut } = useClerk(); if (!user) return null; const handleSignOut = async () => { - await signOut(); - router.push("/"); + // WorkOS sign out - redirect to sign out endpoint + window.location.href = "/sign-out"; }; - const displayName = user.fullName || user.primaryEmailAddress?.emailAddress || "User"; + const displayName = [user.firstName, user.lastName].filter(Boolean).join(" ") || user.email || "User"; const initials = displayName ?.split(" ") .map((n) => n[0]) @@ -37,7 +36,7 @@ export const UserControl = ({ showName }: Props) => { .toUpperCase() .slice(0, 2) || "U"; - const avatarSrc = user.imageUrl || undefined; + const avatarSrc = user.profilePictureUrl || undefined; return ( @@ -57,7 +56,7 @@ export const UserControl = ({ showName }: Props) => {

{displayName}

- {user.primaryEmailAddress?.emailAddress} + {user.email}

diff --git a/src/lib/auth-server.ts b/src/lib/auth-server.ts index 412e10e5..e8cda75a 100644 --- a/src/lib/auth-server.ts +++ b/src/lib/auth-server.ts @@ -1,19 +1,18 @@ import { ConvexHttpClient } from "convex/browser"; -import { auth, currentUser } from "@clerk/nextjs/server"; +import { getUser as getWorkOSUser, getAccessToken, getSignInUrl, getSignUpUrl, signOut } from "@workos-inc/authkit-nextjs"; /** - * Get the authenticated user from Clerk + * Get the authenticated user from WorkOS */ export async function getUser() { - return await currentUser(); + return await getWorkOSUser(); } /** * Get the authentication token for Convex */ export async function getToken() { - const { getToken } = await auth(); - return await getToken({ template: "convex" }); + return await getAccessToken(); } /** @@ -28,7 +27,7 @@ export async function getAuthHeaders() { } /** - * Create a Convex HTTP client with Clerk authentication + * Create a Convex HTTP client with WorkOS authentication * Use this in API routes that need to call Convex */ export async function getConvexClientWithAuth() { @@ -47,3 +46,8 @@ export async function getConvexClientWithAuth() { return httpClient; } + +/** + * Get WorkOS sign in URL + */ +export { getSignInUrl, getSignUpUrl, signOut }; diff --git a/src/lib/clerk-config.ts b/src/lib/clerk-config.ts deleted file mode 100644 index a7663d52..00000000 --- a/src/lib/clerk-config.ts +++ /dev/null @@ -1,117 +0,0 @@ -/** - * Centralizes Clerk-related environment handling with sensible fallbacks. - * - Accepts either publishableKey or frontendApi (host only, no protocol). - * - Normalizes optional proxy/sign-in URLs and trims stray slashes/protocols. - * - Validates proxy URLs to prevent CORS/404 errors from invalid domains. - */ -export function getClerkInstanceConfig() { - const publishableKey = - process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY || - process.env.CLERK_PUBLISHABLE_KEY || - process.env.VITE_CLERK_PUBLISHABLE_KEY || - undefined; - - const rawFrontendApi = - process.env.NEXT_PUBLIC_CLERK_FRONTEND_API || - process.env.CLERK_FRONTEND_API_URL || - process.env.NEXT_CLERK_FRONTEND_API_URL || - process.env.CLERK_JWT_ISSUER_DOMAIN; - - const frontendApi = validateClerkDomain(rawFrontendApi); - - if (!publishableKey && !frontendApi) { - throw new Error( - "Missing Clerk configuration. Set NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY or NEXT_PUBLIC_CLERK_FRONTEND_API", - ); - } - - return { - publishableKey, - frontendApi, - proxyUrl: validateProxyUrl(process.env.NEXT_PUBLIC_CLERK_PROXY_URL), - signInUrl: - normalizePath(process.env.NEXT_PUBLIC_CLERK_SIGN_IN_URL) || "/sign-in", - signUpUrl: - normalizePath(process.env.NEXT_PUBLIC_CLERK_SIGN_UP_URL) || "/sign-up", - signInFallbackRedirectUrl: - normalizePath( - process.env.NEXT_PUBLIC_CLERK_SIGN_IN_FALLBACK_REDIRECT_URL, - ) || "/", - signUpFallbackRedirectUrl: - normalizePath( - process.env.NEXT_PUBLIC_CLERK_SIGN_UP_FALLBACK_REDIRECT_URL, - ) || "/", - appUrl: normalizeUrl(process.env.NEXT_PUBLIC_APP_URL), - }; -} - -function normalizeHost(value?: string | null) { - if (!value) return undefined; - return value.replace(/^https?:\/\//, "").replace(/\/$/, ""); -} - -function normalizeUrl(value?: string | null) { - if (!value) return undefined; - return value.replace(/\/$/, ""); -} - -function normalizePath(value?: string | null) { - if (!value) return undefined; - return value.startsWith("/") ? value : `/${value}`; -} - -const VALID_CLERK_DOMAINS = [ - ".clerk.accounts.dev", - ".clerk.dev", - ".clerkstage.dev", - ".lclclerk.com", - "clerk.zapdev.link", -]; - -function isValidClerkDomain(hostname: string): boolean { - const lower = hostname.toLowerCase(); - return VALID_CLERK_DOMAINS.some(domain => - domain.startsWith(".") ? lower.endsWith(domain) : lower === domain - ); -} - -function validateClerkDomain(value?: string | null): string | undefined { - if (!value) return undefined; - - const normalized = normalizeHost(value); - if (!normalized) return undefined; - - if (isValidClerkDomain(normalized)) { - return normalized; - } - - console.warn( - `[Clerk Config] Invalid Clerk domain "${normalized}" - must be a valid Clerk domain (e.g., *.clerk.accounts.dev). Ignoring to prevent CORS/404 errors.` - ); - return undefined; -} - -function validateProxyUrl(value?: string | null): string | undefined { - if (!value) return undefined; - - const normalized = normalizeUrl(value); - if (!normalized) return undefined; - - try { - const url = new URL(normalized); - - if (!isValidClerkDomain(url.hostname)) { - console.warn( - `[Clerk Config] Invalid proxy URL "${normalized}" - must be a Clerk domain. Ignoring to prevent CORS/404 errors.` - ); - return undefined; - } - - return normalized; - } catch { - console.warn( - `[Clerk Config] Invalid proxy URL format "${value}". Ignoring.` - ); - return undefined; - } -} diff --git a/src/middleware.ts b/src/middleware.ts index 385a44fc..223fc4c9 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -1,20 +1,20 @@ -import { clerkMiddleware, createRouteMatcher } from "@clerk/nextjs/server"; +import { authkitMiddleware } from "@workos-inc/authkit-nextjs"; -const isPublicRoute = createRouteMatcher([ - "/", - "/sign-in(.*)", - "/sign-up(.*)", - "/api/webhooks(.*)", - "/api/inngest(.*)", - "/api/uploadthing(.*)", - "/api/vitals", - "/api/rss", -]); - -export default clerkMiddleware(async (auth, request) => { - if (!isPublicRoute(request)) { - await auth.protect(); - } +export default authkitMiddleware({ + middlewareAuth: { + enabled: true, + unauthenticatedPaths: [ + "/", + "/sign-in", + "/sign-up", + "/pricing", + "/api/webhooks(.*)", + "/api/inngest(.*)", + "/api/uploadthing(.*)", + "/api/vitals", + "/api/rss", + ], + }, }); export const config = { diff --git a/src/modules/home/ui/components/navbar.tsx b/src/modules/home/ui/components/navbar.tsx index edbff23c..40bf9ddf 100644 --- a/src/modules/home/ui/components/navbar.tsx +++ b/src/modules/home/ui/components/navbar.tsx @@ -6,7 +6,7 @@ import { cn } from "@/lib/utils"; import { useScroll } from "@/hooks/use-scroll"; import { Button } from "@/components/ui/button"; import { UserControl } from "@/components/user-control"; -import { useUser, SignInButton, SignUpButton } from "@clerk/nextjs"; +import { useAuth } from "@workos-inc/authkit-nextjs/components"; import { NavigationMenu, NavigationMenuItem, @@ -25,7 +25,13 @@ import { CalendarCheckIcon, MailIcon } from "lucide-react"; export const Navbar = () => { const isScrolled = useScroll(); - const { user } = useUser(); + const { user, loading, organizationId, refreshAuth } = useAuth(); + + // Keep organization context and refresh handler available when needed. + void organizationId; + void refreshAuth; + + if (loading) return null; return ( <> @@ -86,16 +92,12 @@ export const Navbar = () => {
{!user ? (
- - - - - - + +
) : (