feat: Migrate authentication from Clerk to WorkOS AuthKit#149
feat: Migrate authentication from Clerk to WorkOS AuthKit#149Jackson57279 merged 2 commits intomasterfrom
Conversation
- Replace @clerk/nextjs with @workos-inc/authkit-nextjs - Update middleware to use WorkOS authkitMiddleware - Create OAuth callback and sign-out routes - Update Convex provider to integrate with WorkOS sessions - Update auth server helpers to use WorkOS functions - Update user control component with WorkOS hooks - Update sign-in/sign-up pages to redirect to WorkOS - Update navbar with WorkOS authentication buttons - Configure Convex auth.config.ts for WorkOS JWKS - Create WorkOS webhook handler for user events - Delete Clerk-specific files (clerk-config.ts, webhooks/clerk) - Update documentation (CLAUDE.md, AGENTS.md, README.md) - Add .env.example with WorkOS environment variables - Create comprehensive WorkOS migration guide This migration provides better pricing (free up to 1M users) and enterprise-ready authentication with seamless Convex integration.
CodeCapy Review ₍ᐢ•(ܫ)•ᐢ₎Codebase SummaryZapDev is an AI-powered development platform that lets users create web applications using AI agents and real-time sandboxes. The system uses Next.js for the frontend, Convex as a real-time database backend, and integrates various authentication and API services to provide features like sign-in/sign-up, live code previews, file management, and more. PR ChangesThis pull request migrates authentication from Clerk to WorkOS AuthKit. Changes include replacing Clerk components with WorkOS equivalents in sign-in, sign-up, OAuth callback, sign-out routes, and updating Convex provider integration. Documentation and configuration files (AGENTS.md, CLAUDE.md, README.md, .env.example) have been updated accordingly. Clerk-specific files and code (including middleware, user-control, and configuration files) have been removed or refactored to work with WorkOS. Setup Instructions
Generated Test Cases1: WorkOS Sign In Redirection ❗️❗️❗️Description: Verifies that when a user navigates to the sign in page, they are redirected to the WorkOS hosted authentication UI. Prerequisites:
Steps:
Expected Result: The browser should be redirected to the WorkOS authentication interface. The URL should change to that provided by WorkOS AuthKit sign in endpoint. 2: WorkOS Sign Up Redirection ❗️❗️❗️Description: Ensures that navigation to the sign-up page redirects the user to the WorkOS sign up interface. Prerequisites:
Steps:
Expected Result: The sign-up page should not render local UI; instead the browser is redirected to the WorkOS sign up endpoint. 3: OAuth Callback Handling ❗️❗️❗️Description: Tests that the OAuth callback route correctly processes the WorkOS authentication response and establishes a user session. Prerequisites:
Steps:
Expected Result: The OAuth callback route should process the incoming authentication data, establish a valid session, and the app should render authenticated UI elements. 4: User Control Correct Data & Sign Out ❗️❗️❗️Description: Verifies that the user control component displays the proper WorkOS user details and that sign-out functionality works. Prerequisites:
Steps:
Expected Result: The user control displays correct user information from WorkOS. When sign-out is clicked, the session is ended and the UI updates to a signed-out state. 5: Protected Route Middleware Redirection ❗️❗️Description: Checks that middleware prevents unauthenticated access to protected routes and redirects the user to sign in. Prerequisites:
Steps:
Expected Result: Access to the protected route is blocked and user is redirected to sign-in. The redirection should follow the WorkOS authentication flow. 6: WorkOS Webhook Handler Response ❗️❗️Description: Ensures that the webhook endpoint for WorkOS events processes incoming requests correctly. Prerequisites:
Steps:
Expected Result: The webhook endpoint should return an HTTP 200 response indicating successful processing. The server logs should indicate handling of the event (e.g., 'User created: ...' printed to console). Raw Changes AnalyzedFile: AGENTS.md
Changes:
@@ -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
File: CLAUDE.md
Changes:
@@ -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
File: README.md
Changes:
@@ -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=""
File: bun.lock
Changes:
@@ -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=="],
File: convex/auth.config.ts
Changes:
@@ -1,7 +1,7 @@
export default {
providers: [
{
- domain: process.env.CLERK_FRONTEND_API_URL,
+ domain: process.env.WORKOS_API_URL || "https://api.workos.com",
applicationID: "convex",
},
],
File: explanations/WORKOS_MIGRATION_GUIDE.md
Changes:
@@ -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
File: package.json
Changes:
@@ -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",
File: src/app/api/webhooks/clerk/route.ts
Changes:
@@ -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 });
-}
File: src/app/api/webhooks/workos/route.ts
Changes:
@@ -0,0 +1,68 @@
+import { headers } from "next/headers";
+import { ConvexHttpClient } from "convex/browser";
+import { api } from "@/convex/_generated/api";
+
+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 = await headers();
+ const signature = headerPayload.get("workos-signature");
+
+ if (!signature) {
+ return new Response("Error occurred -- no WorkOS signature header", {
+ status: 400,
+ });
+ }
+
+ // Get the body
+ const payload = await req.json();
+
+ // TODO: Verify webhook signature using WorkOS SDK
+ // For now, we'll process the event without verification
+ // In production, you should verify the signature:
+ // const isValid = await verifyWorkOSWebhook(payload, signature, WEBHOOK_SECRET);
+ // if (!isValid) {
+ // return new Response("Invalid signature", { status: 400 });
+ // }
+
+ const eventType = payload.event;
+
+ // Initialize Convex client
+ const convex = new ConvexHttpClient(process.env.NEXT_PUBLIC_CONVEX_URL!);
+
+ if (eventType === "user.created") {
+ const { id, email, first_name, last_name, profile_picture_url } = payload.data;
+
+ console.log(`User created: ${id} (${email})`);
+
+ // 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 } = payload.data;
+ console.log(`User updated: ${id} (${email})`);
+ // Handle user updates if needed
+ }
+
+ if (eventType === "user.deleted") {
+ const { id } = payload.data;
+ console.log(`User deleted: ${id}`);
+ // Handle user deletion if needed (cleanup projects, messages, etc.)
+ }
+
+ return new Response("", { status: 200 });
+}
File: src/app/callback/route.ts
Changes:
@@ -0,0 +1,3 @@
+import { handleAuth } from "@workos-inc/authkit-nextjs";
+
+export const GET = handleAuth();
File: src/app/sign-in/[[...sign-in]]/page.tsx
Changes:
@@ -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 (
- <div className="flex h-screen w-full items-center justify-center">
- <SignIn />
- </div>
- );
+export default async function SignInPage() {
+ const signInUrl = await getSignInUrl();
+ redirect(signInUrl);
}
File: src/app/sign-out/route.ts
Changes:
@@ -0,0 +1,5 @@
+import { signOut } from "@workos-inc/authkit-nextjs";
+
+export async function GET() {
+ return await signOut();
+}
File: src/app/sign-up/[[...sign-up]]/page.tsx
Changes:
@@ -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 (
- <div className="flex h-screen w-full items-center justify-center">
- <SignUp />
- </div>
- );
+export default async function SignUpPage() {
+ const signUpUrl = await getSignUpUrl();
+ redirect(signUpUrl);
}
File: src/components/convex-provider.tsx
Changes:
@@ -1,32 +1,36 @@
"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 { useSession } from "@workos-inc/authkit-nextjs";
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 (
- <ClerkProvider
- publishableKey={process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY}
- signInUrl="/sign-in"
- signUpUrl="/sign-up"
- signInFallbackRedirectUrl="/"
- signUpFallbackRedirectUrl="/"
- >
- <ConvexProviderWithClerk client={convex} useAuth={useAuth}>
- {children}
- </ConvexProviderWithClerk>
- </ClerkProvider>
+ <ConvexProviderWithAuth client={convex} useAuth={useWorkOSAuth}>
+ {children}
+ </ConvexProviderWithAuth>
+ );
+}
+
+function useWorkOSAuth() {
+ const { session } = useSession();
+
+ return useMemo(
+ () => ({
+ isLoading: false,
+ isAuthenticated: session !== null,
+ fetchAccessToken: async ({ forceRefreshToken }: { forceRefreshToken: boolean }) => {
+ if (!session) return null;
+ return session.accessToken;
+ },
+ }),
+ [session]
);
}
File: src/components/user-control.tsx
Changes:
@@ -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,24 +20,23 @@ 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])
.join("")
.toUpperCase()
.slice(0, 2) || "U";
- const avatarSrc = user.imageUrl || undefined;
+ const avatarSrc = user.profilePictureUrl || undefined;
return (
<DropdownMenu>
@@ -57,7 +56,7 @@ export const UserControl = ({ showName }: Props) => {
<div className="flex flex-col space-y-1">
<p className="text-sm font-medium leading-none">{displayName}</p>
<p className="text-xs leading-none text-muted-foreground">
- {user.primaryEmailAddress?.emailAddress}
+ {user.email}
</p>
</div>
</DropdownMenuLabel>
File: src/lib/auth-server.ts
Changes:
@@ -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 };
File: src/lib/clerk-config.ts
Changes:
@@ -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;
- }
-}
File: src/middleware.ts
Changes:
@@ -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 = {
File: src/modules/home/ui/components/navbar.tsx
Changes:
@@ -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 { useUser } from "@workos-inc/authkit-nextjs";
import {
NavigationMenu,
NavigationMenuItem,
@@ -86,16 +86,12 @@ export const Navbar = () => {
</DropdownMenu>
{!user ? (
<div className="flex gap-2">
- <SignUpButton mode="modal">
- <Button variant="outline" size="sm">
- Sign up
- </Button>
- </SignUpButton>
- <SignInButton mode="modal">
- <Button size="sm">
- Sign in
- </Button>
- </SignInButton>
+ <Button variant="outline" size="sm" asChild>
+ <Link href="/sign-up">Sign up</Link>
+ </Button>
+ <Button size="sm" asChild>
+ <Link href="/sign-in">Sign in</Link>
+ </Button>
</div>
) : (
<UserControl showName />
|
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. WalkthroughReplace Clerk authentication with WorkOS AuthKit across the app: dependencies, environment variables, middleware, auth pages/routes, webhook handling, Convex auth configuration, UI components, and documentation. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant User
participant Browser
participant NextApp
participant WorkOS
participant Convex
Note over Browser,NextApp: Sign-in redirect flow
User->>Browser: Click "Sign in"
Browser->>NextApp: GET /sign-in
NextApp->>WorkOS: request sign-in URL (getSignInUrl)
WorkOS-->>NextApp: sign-in URL (redirect target)
NextApp-->>Browser: 302 redirect to WorkOS hosted UI
Note over WorkOS,NextApp: OAuth callback
WorkOS->>Browser: OAuth callback -> /callback?code=...
Browser->>NextApp: GET /callback
NextApp->>WorkOS: handleAuth() exchanges code for token, obtains user
WorkOS-->>NextApp: access token + user info
NextApp->>Convex: (optional) initialize or attach auth token for server-side calls
Convex-->>NextApp: ack
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Possibly related PRs
Suggested labels
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
🚀 Scrapybara Ubuntu instance started! |
|
🔧 Setting up test environment... Agent Steps |
|
❌ Something went wrong: |
PR Review: Migration from Clerk to WorkOS AuthKitOverviewThis PR successfully migrates authentication from Clerk to WorkOS AuthKit. The migration is well-structured with comprehensive documentation and follows best practices. However, there are several critical security issues and improvements needed before merging. 🚨 Critical Security Issues1. Missing Webhook Signature Verification (HIGH PRIORITY)File: The webhook handler currently has signature verification commented out with a TODO: // TODO: Verify webhook signature using WorkOS SDK
// For now, we'll process the event without verificationRisk: Without signature verification, anyone can send fake webhook events to your endpoint, potentially:
Recommendation: Implement signature verification immediately using WorkOS SDK or standard webhook signature validation: import { Webhook } from 'standardwebhooks';
const webhook = new Webhook(WEBHOOK_SECRET);
try {
const payload = webhook.verify(await req.text(), {
'workos-signature': signature,
});
// Process verified payload
} catch (err) {
return new Response("Invalid signature", { status: 400 });
}2. Convex Client Without Authentication in WebhookFile: The webhook creates an unauthenticated Convex client, which may fail if Convex enforces authentication for all operations. Recommendation: If Convex operations require auth, consider using service account credentials or ensure the webhook can perform necessary operations without auth. 🔒 Security Concerns3. Missing Environment Variable ValidationThe webhook throws an error if Recommendation: Create an environment validation file (e.g., import { z } from 'zod';
const envSchema = z.object({
WORKOS_API_KEY: z.string().min(1),
WORKOS_CLIENT_ID: z.string().min(1),
WORKOS_REDIRECT_URI: z.string().url(),
WORKOS_WEBHOOK_SECRET: z.string().min(1),
// ... other vars
});
export const env = envSchema.parse(process.env);4. Client-Side Sign-Out ImplementationFile: Using Recommendation: Use Next.js router for better UX: import { useRouter } from 'next/navigation';
const router = useRouter();
const handleSignOut = () => {
router.push('/sign-out');
};🐛 Potential Bugs5. Unused Variables in Webhook HandlerFile: Variables Recommendation: Either remove unused variables or add a comment explaining they're reserved for future use. 6. Missing Error Handling in Auth HookFile: The Recommendation: Add error handling: fetchAccessToken: async ({ forceRefreshToken }: { forceRefreshToken: boolean }) => {
try {
if (!session) return null;
return session.accessToken;
} catch (error) {
console.error('Failed to fetch access token:', error);
return null;
}
},💡 Code Quality Improvements7. Hard-Coded isLoading StateFile:
Recommendation: Track actual loading state from WorkOS: const { session, isLoading: sessionLoading } = useSession();
return useMemo(
() => ({
isLoading: sessionLoading,
// ...
}),
[session, sessionLoading]
);8. Missing Type Safety in Webhook PayloadFile: The webhook payload is typed as Recommendation: import { z } from 'zod';
const userEventSchema = z.object({
event: z.enum(['user.created', 'user.updated', 'user.deleted']),
data: z.object({
id: z.string(),
email: z.string().email(),
first_name: z.string().optional(),
last_name: z.string().optional(),
profile_picture_url: z.string().url().optional(),
}),
});
const payload = userEventSchema.parse(await req.json());9. Inconsistent Error HandlingThe webhook has try-catch for user creation but not for other events. Error handling should be consistent. 📚 Documentation & Testing10. Excellent Migration Guide ✅The Minor suggestion: Add a troubleshooting section for common issues:
11. Missing TestsNo tests were added for the new authentication flow. Recommendation: Add tests for:
🎯 Best Practices12. Convex Auth ConfigFile: The domain uses Minor improvement: Consider validating this is a valid URL. 13. Middleware Configuration ✅The middleware correctly excludes API routes, webhooks, and public paths. Well done! ⚡ Performance Considerations14. useMemo Optimization ✅Good use of 15. Webhook Response TimeThe webhook handler performs synchronous operations that could be slow. Consider using background jobs for heavy operations. Recommendation: For user deletion cleanup, use Inngest to handle in background: if (eventType === "user.deleted") {
await inngest.send({
name: "user/cleanup",
data: { userId: id }
});
}📦 Dependencies16. Dependency Changes ✅
Note: The 🔍 Testing ChecklistBefore merging, ensure you've tested:
📋 SummaryStrengths:
Must Fix Before Merge:
Nice to Have:
Overall Assessment: This is a solid migration, but the missing webhook signature verification is a critical security vulnerability that must be addressed before merging to production. 🎖️ RecommendationDo not merge until webhook signature verification is implemented. Once that's addressed, this will be ready for staging deployment and testing. Great work on the migration overall! The documentation and code structure are excellent. Just need to close those security gaps. 🚀 |
Greptile OverviewGreptile SummaryThis PR migrates authentication from Clerk to WorkOS AuthKit, replacing the authentication provider, middleware, and user management across the application. Key Changes:
Critical Issues Found:
Benefits: Confidence Score: 2/5
Important Files ChangedFile Analysis
Sequence DiagramsequenceDiagram
participant User
participant Browser
participant Middleware as Next.js Middleware
participant WorkOS as WorkOS AuthKit
participant Callback as /callback Route
participant Convex as Convex Backend
Note over User,Convex: Sign-In Flow
User->>Browser: Click "Sign in"
Browser->>Middleware: Navigate to /sign-in
Middleware->>WorkOS: Redirect to WorkOS hosted UI
WorkOS->>User: Display authentication form
User->>WorkOS: Enter credentials
WorkOS->>Callback: Redirect to /callback with code
Callback->>WorkOS: Exchange code for session
WorkOS->>Callback: Return access token & user data
Callback->>Browser: Set session cookie & redirect
Browser->>Convex: Request with session token
Convex->>WorkOS: Verify JWT via JWKS endpoint
WorkOS->>Convex: Return user identity
Convex->>Browser: Authenticated response
Note over User,Convex: Protected Route Access
Browser->>Middleware: Request protected route
Middleware->>WorkOS: Validate session
alt Session Valid
Middleware->>Browser: Allow access
Browser->>Convex: Query with access token
Convex->>WorkOS: Verify JWT
Convex->>Browser: Return user data
else Session Invalid
Middleware->>WorkOS: Redirect to sign-in
end
Note over User,Convex: Sign-Out Flow
User->>Browser: Click "Sign out"
Browser->>Callback: Navigate to /sign-out
Callback->>WorkOS: Clear session
WorkOS->>Browser: Redirect to home page
|
There was a problem hiding this comment.
Additional Comments (4)
-
src/modules/home/ui/components/projects-list.tsx, line 5 (link)syntax: still imports
@clerk/nextjsinstead of@workos-inc/authkit-nextjs -
src/modules/projects/ui/components/message-form.tsx, line 14 (link)syntax: still imports
@clerk/nextjsinstead of@workos-inc/authkit-nextjs -
src/modules/projects/ui/views/project-view.tsx, line 9 (link)syntax: still imports
@clerk/nextjsinstead of@workos-inc/authkit-nextjs -
src/app/dashboard/subscription/page.tsx, line 5 (link)syntax: still imports
@clerk/nextjsinstead of@workos-inc/authkit-nextjs
18 files reviewed, 6 comments
src/app/api/webhooks/workos/route.ts
Outdated
|
|
||
| // TODO: Verify webhook signature using WorkOS SDK | ||
| // For now, we'll process the event without verification | ||
| // In production, you should verify the signature: | ||
| // const isValid = await verifyWorkOSWebhook(payload, signature, WEBHOOK_SECRET); | ||
| // if (!isValid) { | ||
| // return new Response("Invalid signature", { status: 400 }); | ||
| // } |
There was a problem hiding this comment.
logic: webhook signature verification is commented out, leaving the endpoint vulnerable to spoofed requests. implement verification using WorkOS SDK before deploying to production
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/api/webhooks/workos/route.ts
Line: 27:34
Comment:
**logic:** webhook signature verification is commented out, leaving the endpoint vulnerable to spoofed requests. implement verification using WorkOS SDK before deploying to production
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/components/user-control.tsx (1)
3-37: Fix incorrect WorkOS hook import and add type annotation to map callbackThe
useUserhook doesn't exist in@workos-inc/authkit-nextjs. The correct export isuseSession. Additionally, you need to type themapcallback parameter to satisfy strict mode.Proposed fix:
-import { useUser } from "@workos-inc/authkit-nextjs"; +import { useSession } from "@workos-inc/authkit-nextjs"; import { useRouter } from "next/navigation"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { @@ export const UserControl = ({ showName }: Props) => { const router = useRouter(); - const { user } = useUser(); + const { session } = useSession(); + const user = session?.user; if (!user) return null; @@ const displayName = [user.firstName, user.lastName].filter(Boolean).join(" ") || user.email || "User"; const initials = displayName ?.split(" ") - .map((n) => n[0]) + .map((n: string) => n[0])Also update
src/modules/home/ui/components/navbar.tsxline 9 with the same import correction.src/lib/auth-server.ts (1)
2-48: Perfect! I now have complete verification. Let me finalize my findings:Key Findings:
- ✅
getUser()IS a valid server export in v0.14.0 and works correctly- ❌
getAccessToken()does NOT exist as a server-side export in v0.14.0 (it's only available as a client hook viauseAccessToken)- ✅
getToken()is actually called in the codebase (ingetAuthHeaders()andgetConvexClientWithAuth())- ✅
withAuth()is a valid alternative that provides both user and accessTokenVerdict: The review comment is partially incorrect in its diagnosis. It flags both
getUserandgetAccessTokenas non-existent, but onlygetAccessTokenis actually broken. However, the suggested fix usingwithAuth()is valid and will resolve the actual error.
Use
withAuth()to obtain the access token;getUser()import is already correctThe import of
getAccessTokenis failing because it doesn't exist as a server-side export in@workos-inc/authkit-nextjs@0.14.0—it's only available as a client hook (useAccessToken). However,getUser()is a valid server export and works as-is.The fix is to update only the
getToken()function to usewithAuth():-import { getUser as getWorkOSUser, getAccessToken, getSignInUrl, getSignUpUrl, signOut } from "@workos-inc/authkit-nextjs"; +import { getUser as getWorkOSUser, withAuth, getSignInUrl, getSignUpUrl, signOut } from "@workos-inc/authkit-nextjs"; @@ /** * Get the authentication token for Convex */ export async function getToken() { - return await getAccessToken(); + const { accessToken } = await withAuth(); + return accessToken ?? null; }The rest of the file (auth headers and Convex client wiring) can remain unchanged.
Make sure all call sites use these helpers only in server contexts where
withAuth()can access the current session (server components, route handlers, etc.).
🧹 Nitpick comments (5)
package.json (1)
15-15: Verify@workos-inc/authkit-nextjsversion against Next 16 support and recent security advisoryAdding AuthKit Next.js makes sense for the migration, but
^0.14.0is an older line while:
- Current WorkOS docs/examples (including Convex + AuthKit) assume the latest AuthKit Next.js APIs and env vars.
- There is a recent CVE (CVE-2025-64762) affecting
@workos-inc/authkit-nextjsreleases up to 2.11.0, with a fix in 2.11.1.- Community reports have called out peer-dependency conflicts between some AuthKit Next.js versions and Next 16.
Please confirm that
^0.14.0is:
- Explicitly supported with Next 16, and
- Either not affected by CVE-2025-64762 or upgraded to a patched line once Next 16 support is available (e.g., 2.11.1+).
Also ensure your env vars follow current AuthKit Next.js guidance (
WORKOS_CLIENT_ID,WORKOS_API_KEY,WORKOS_COOKIE_PASSWORD,NEXT_PUBLIC_WORKOS_REDIRECT_URI) so the SDK picks them up correctly.README.md (1)
14-31: Tech stack bullets look good, buttRPCis listed twiceThe new “Authentication with WorkOS AuthKit” and “WorkOS AuthKit” tech‑stack bullets correctly reflect the migration.
Small nit:
tRPCnow appears twice in the Tech Stack list (generic- tRPCand- tRPC (type-safe APIs)), which is redundant. You can drop one of them to keep the list concise.AGENTS.md (1)
40-44: Auth description is accurate; mirror full WorkOS env requirements hereThe updated architecture line:
- **Auth**: WorkOS AuthKit with JWT authenticationmatches the new auth stack and looks good.
In the “Environment Variables” section, you currently list the WorkOS vars as:
- WORKOS_API_KEY - WORKOS_CLIENT_ID - WORKOS_REDIRECT_URI - WORKOS_WEBHOOK_SECRETTo keep this in sync with both the README and the AuthKit Next.js + Convex docs, I’d suggest:
- Adding
WORKOS_COOKIE_PASSWORD(≥32 chars, required for encrypted cookies).- Clarifying that the redirect env used by AuthKit Next.js is
NEXT_PUBLIC_WORKOS_REDIRECT_URI(or documenting both if you intentionally useWORKOS_REDIRECT_URIelsewhere in server code).Also, if you care about markdownlint’s MD034 warning, you can wrap bare URLs like
https://ai-gateway.vercel.sh/v1/in<...>or[link](...)to quiet the linter.Also applies to: 120-132
explanations/WORKOS_MIGRATION_GUIDE.md (1)
50-58: Polish wording and fenced code blocks in the migration guideTwo small doc nits:
- Around Line 52, consider hyphenating “Sign out handler” → “Sign‑out handler” for consistency.
- Around Lines 220 and 225, the fenced code blocks lack a language spec. To satisfy MD040 and improve rendering, tag them, e.g.:
- ``` + ```text https://your-domain.com/callback(and similarly for the webhook URL block). Also applies to: 218-227 </blockquote></details> <details> <summary>src/middleware.ts (1)</summary><blockquote> `1-18`: **Middleware config matches AuthKit’s middlewareAuth pattern; consider Next 16 `proxy.ts` guidance** The `authkitMiddleware` usage with `middlewareAuth.enabled` and the `unauthenticatedPaths` list is in line with AuthKit’s documented “middleware auth” configuration. ([github.com](https://github.com/workos/authkit-nextjs)) Since you’re on Next 16, AuthKit’s docs recommend using a `proxy.ts` entrypoint for middleware on 16+ (same code, different filename/function). It’s not strictly required for this PR, but worth double‑checking against your deployment setup. </blockquote></details> </blockquote></details> <details> <summary>📜 Review details</summary> **Configuration used**: CodeRabbit UI **Review profile**: CHILL **Plan**: Pro **Disabled knowledge base sources:** - Linear integration is disabled by default for public repositories > You can enable these sources in your CodeRabbit configuration. <details> <summary>📥 Commits</summary> Reviewing files that changed from the base of the PR and between 6c0d6787e74981d7e55f7142d01b884968e79465 and c13163bee900902d4296c2997fbe4f6b4b61716d. </details> <details> <summary>⛔ Files ignored due to path filters (2)</summary> * `assets/image-7ce41e19-6014-4c28-a2d6-9ea1b063ad9d.png` is excluded by `!**/*.png` * `bun.lock` is excluded by `!**/*.lock` </details> <details> <summary>📒 Files selected for processing (18)</summary> * `AGENTS.md` (2 hunks) * `CLAUDE.md` (3 hunks) * `README.md` (3 hunks) * `convex/auth.config.ts` (1 hunks) * `explanations/WORKOS_MIGRATION_GUIDE.md` (1 hunks) * `package.json` (1 hunks) * `src/app/api/webhooks/clerk/route.ts` (0 hunks) * `src/app/api/webhooks/workos/route.ts` (1 hunks) * `src/app/callback/route.ts` (1 hunks) * `src/app/sign-in/[[...sign-in]]/page.tsx` (1 hunks) * `src/app/sign-out/route.ts` (1 hunks) * `src/app/sign-up/[[...sign-up]]/page.tsx` (1 hunks) * `src/components/convex-provider.tsx` (1 hunks) * `src/components/user-control.tsx` (3 hunks) * `src/lib/auth-server.ts` (3 hunks) * `src/lib/clerk-config.ts` (0 hunks) * `src/middleware.ts` (1 hunks) * `src/modules/home/ui/components/navbar.tsx` (2 hunks) </details> <details> <summary>💤 Files with no reviewable changes (2)</summary> * src/app/api/webhooks/clerk/route.ts * src/lib/clerk-config.ts </details> <details> <summary>🧰 Additional context used</summary> <details> <summary>🧬 Code graph analysis (6)</summary> <details> <summary>src/app/sign-out/route.ts (2)</summary><blockquote> <details> <summary>src/app/callback/route.ts (1)</summary> * `GET` (3-3) </details> <details> <summary>src/lib/auth-server.ts (1)</summary> * `signOut` (53-53) </details> </blockquote></details> <details> <summary>src/app/api/webhooks/workos/route.ts (1)</summary><blockquote> <details> <summary>tests/mocks/convex-browser.ts (1)</summary> * `ConvexHttpClient` (1-11) </details> </blockquote></details> <details> <summary>src/modules/home/ui/components/navbar.tsx (1)</summary><blockquote> <details> <summary>src/components/ui/button.tsx (1)</summary> * `Button` (60-60) </details> </blockquote></details> <details> <summary>src/app/sign-up/[[...sign-up]]/page.tsx (1)</summary><blockquote> <details> <summary>src/lib/auth-server.ts (1)</summary> * `getSignUpUrl` (53-53) </details> </blockquote></details> <details> <summary>src/app/sign-in/[[...sign-in]]/page.tsx (1)</summary><blockquote> <details> <summary>src/lib/auth-server.ts (1)</summary> * `getSignInUrl` (53-53) </details> </blockquote></details> <details> <summary>src/app/callback/route.ts (1)</summary><blockquote> <details> <summary>src/app/sign-out/route.ts (1)</summary> * `GET` (3-5) </details> </blockquote></details> </details><details> <summary>🪛 GitHub Actions: CI</summary> <details> <summary>src/components/user-control.tsx</summary> [error] 3-3: Module "@workos-inc/authkit-nextjs" has no exported member 'useUser'. --- [error] 34-34: Parameter 'n' implicitly has an 'any' type. </details> <details> <summary>src/modules/home/ui/components/navbar.tsx</summary> [error] 9-9: "@workos-inc/authkit-nextjs" has no exported member 'useUser'. </details> <details> <summary>src/components/convex-provider.tsx</summary> [error] 6-6: "@workos-inc/authkit-nextjs" has no exported member named 'useSession'. Did you mean 'getSession'? </details> <details> <summary>src/lib/auth-server.ts</summary> [error] 2-2: Module "@workos-inc/authkit-nextjs" has no exported member 'getUser'. --- [error] 2-2: Module "@workos-inc/authkit-nextjs" has no exported member 'getAccessToken'. </details> </details> <details> <summary>🪛 LanguageTool</summary> <details> <summary>explanations/WORKOS_MIGRATION_GUIDE.md</summary> [grammar] ~52-~52: Use a hyphen to join words. Context: ...ler - `src/app/sign-out/route.ts` - Sign out handler - `src/app/api/webhooks/work... (QB_NEW_EN_HYPHEN) </details> </details> <details> <summary>🪛 markdownlint-cli2 (0.18.1)</summary> <details> <summary>AGENTS.md</summary> 128-128: Bare URL used (MD034, no-bare-urls) </details> <details> <summary>explanations/WORKOS_MIGRATION_GUIDE.md</summary> 220-220: Fenced code blocks should have a language specified (MD040, fenced-code-language) --- 225-225: Fenced code blocks should have a language specified (MD040, fenced-code-language) </details> </details> </details> <details> <summary>⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)</summary> * GitHub Check: claude-review * GitHub Check: Codacy Security Scan * GitHub Check: Analyze (javascript-typescript) </details> <details> <summary>🔇 Additional comments (3)</summary><blockquote> <details> <summary>src/app/callback/route.ts (1)</summary><blockquote> `1-3`: **Callback handler matches AuthKit docs; just confirm redirect URI wiring** Using: ```ts import { handleAuth } from "@workos-inc/authkit-nextjs"; export const GET = handleAuth();is exactly how the AuthKit Next.js docs and Convex + AuthKit examples define the callback route.
Please just double‑check that:
- This file is located at
/app/callback/route.ts(path/callback), and- Your WorkOS Dashboard “Redirect URI” and
NEXT_PUBLIC_WORKOS_REDIRECT_URI(or equivalent) are set tohttp://localhost:3000/callbackin dev and the matching URL in prod.src/app/sign-in/[[...sign-in]]/page.tsx (1)
1-6: Sign-in redirect flow looks correctThe page cleanly delegates to WorkOS by fetching
getSignInUrl()server-side and callingredirect(signInUrl), which matches the documented AuthKit pattern for sign-in redirects. (github.com)src/app/sign-up/[[...sign-up]]/page.tsx (1)
1-6: Sign-up redirect flow is consistent and aligned with AuthKit usageThis page mirrors the sign-in behavior by resolving
getSignUpUrl()server-side and callingredirect(signUpUrl), which is exactly how AuthKit expects you to kick off sign-up. (github.com)
CodeCapy Review ₍ᐢ•(ܫ)•ᐢ₎Codebase SummaryZapDev is an AI-powered development platform built with Next.js, React, TypeScript, and Tailwind CSS. It includes features like real-time sandboxed development, AI code generation through Inngest and Vercel AI Gateway, and a real-time database with Convex. The recent changes migrate the authentication system from Clerk to WorkOS AuthKit, updating sign-in/sign-up flows, OAuth callback and sign-out routes, navigation components, middleware, and several configuration files. PR ChangesThe pull request replaces Clerk with WorkOS AuthKit for authentication. This includes changes in sign-in, sign-up, and sign-out pages/routes, middleware modifications, updates to user control and navbar components, and adjustments in server-side authentication helpers. The documentation and environment variable examples have been updated accordingly. Setup Instructions
Generated Test Cases1: WorkOS Sign In Redirect Flow ❗️❗️❗️Description: This test verifies that when a user navigates to the sign in page (via /sign-in), they are automatically redirected to the WorkOS-hosted sign in page, reflecting the migration from Clerk to WorkOS AuthKit. Prerequisites:
Steps:
Expected Result: The browser is redirected to the WorkOS sign in page, and the URL reflects a WorkOS sign in URL (e.g., containing parameters or domain from WorkOS). 2: WorkOS Sign Up Redirect Flow ❗️❗️❗️Description: This test confirms that navigating to the sign up page redirects users to the WorkOS sign up page, ensuring that the new authentication flow for sign-up is correctly integrated. Prerequisites:
Steps:
Expected Result: The browser is redirected to the WorkOS sign up page and the redirection occurs without any in-app sign up form rendering. 3: WorkOS Sign Out Flow ❗️❗️Description: This test checks that when a signed-in user chooses to sign out, the system calls the WorkOS sign out functionality and the user is redirected to the home page. Prerequisites:
Steps:
Expected Result: The user is successfully signed out and redirected to the home page (http://localhost:3000), with no user-specific data displayed in the user control. 4: Navbar Authentication Buttons Display for Unauthenticated Users ❗️❗️Description: This test verifies that the navigation bar displays the correct authentication buttons (Sign In and Sign Up) when no user is logged in. Prerequisites:
Steps:
Expected Result: The navbar should display both the 'Sign in' and 'Sign up' buttons. Clicking these buttons should navigate to /sign-in and /sign-up respectively, initiating the redirection flow to WorkOS. 5: User Control Display After Successful Authentication ❗️❗️❗️Description: This test validates that once a user has successfully signed in via WorkOS, the user control in the navbar correctly displays the user’s name (combining first and last names) and email, and shows an avatar if available. Prerequisites:
Steps:
Expected Result: The user control should show the user's full name (constructed from first and last name) and email address accurately. If an avatar is provided, it should be visible; otherwise, a fallback avatar is displayed. Raw Changes AnalyzedFile: AGENTS.md
Changes:
@@ -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
File: CLAUDE.md
Changes:
@@ -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
File: README.md
Changes:
@@ -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=""
File: bun.lock
Changes:
@@ -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=="],
File: convex/auth.config.ts
Changes:
@@ -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,
},
],
};
File: explanations/WORKOS_MIGRATION_GUIDE.md
Changes:
@@ -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
File: package.json
Changes:
@@ -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",
File: src/app/api/webhooks/clerk/route.ts
Changes:
@@ -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 });
-}
File: src/app/api/webhooks/workos/route.ts
Changes:
@@ -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 });
+}
File: src/app/callback/route.ts
Changes:
@@ -0,0 +1,3 @@
+import { handleAuth } from "@workos-inc/authkit-nextjs";
+
+export const GET = handleAuth();
File: src/app/sign-in/[[...sign-in]]/page.tsx
Changes:
@@ -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 (
- <div className="flex h-screen w-full items-center justify-center">
- <SignIn />
- </div>
- );
+export default async function SignInPage() {
+ const signInUrl = await getSignInUrl();
+ redirect(signInUrl);
}
File: src/app/sign-out/route.ts
Changes:
@@ -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));
+}
File: src/app/sign-up/[[...sign-up]]/page.tsx
Changes:
@@ -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 (
- <div className="flex h-screen w-full items-center justify-center">
- <SignUp />
- </div>
- );
+export default async function SignUpPage() {
+ const signUpUrl = await getSignUpUrl();
+ redirect(signUpUrl);
}
File: src/components/convex-provider.tsx
Changes:
@@ -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 (
- <ClerkProvider
- publishableKey={process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY}
- signInUrl="/sign-in"
- signUpUrl="/sign-up"
- signInFallbackRedirectUrl="/"
- signUpFallbackRedirectUrl="/"
- >
- <ConvexProviderWithClerk client={convex} useAuth={useAuth}>
- {children}
- </ConvexProviderWithClerk>
- </ClerkProvider>
+ <ConvexProviderWithAuth client={convex} useAuth={useWorkOSAuth}>
+ {children}
+ </ConvexProviderWithAuth>
+ );
+}
+
+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]
);
}
File: src/components/user-control.tsx
Changes:
@@ -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,24 +20,23 @@ 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])
.join("")
.toUpperCase()
.slice(0, 2) || "U";
- const avatarSrc = user.imageUrl || undefined;
+ const avatarSrc = user.profilePictureUrl || undefined;
return (
<DropdownMenu>
@@ -57,7 +56,7 @@ export const UserControl = ({ showName }: Props) => {
<div className="flex flex-col space-y-1">
<p className="text-sm font-medium leading-none">{displayName}</p>
<p className="text-xs leading-none text-muted-foreground">
- {user.primaryEmailAddress?.emailAddress}
+ {user.email}
</p>
</div>
</DropdownMenuLabel>
File: src/lib/auth-server.ts
Changes:
@@ -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 };
File: src/lib/clerk-config.ts
Changes:
@@ -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;
- }
-}
File: src/middleware.ts
Changes:
@@ -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 = {
File: src/modules/home/ui/components/navbar.tsx
Changes:
@@ -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 = () => {
</DropdownMenu>
{!user ? (
<div className="flex gap-2">
- <SignUpButton mode="modal">
- <Button variant="outline" size="sm">
- Sign up
- </Button>
- </SignUpButton>
- <SignInButton mode="modal">
- <Button size="sm">
- Sign in
- </Button>
- </SignInButton>
+ <Button variant="outline" size="sm" asChild>
+ <Link href="/sign-up">Sign up</Link>
+ </Button>
+ <Button size="sm" asChild>
+ <Link href="/sign-in">Sign in</Link>
+ </Button>
</div>
) : (
<UserControl showName />
|
|
🚀 Scrapybara Ubuntu instance started! |
|
🔧 Setting up test environment... Agent Steps |
|
❌ Something went wrong: |
Code Review: WorkOS AuthKit MigrationThis is a well-executed authentication migration from Clerk to WorkOS AuthKit. The implementation is clean and follows best practices. Here's my detailed review: ✅ Strengths1. Clean Migration Approach
2. Security Implementation
3. Code Quality
4. Documentation
|
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (3)
src/modules/home/ui/components/navbar.tsx (1)
9-9: Same module import issue - verify correct import path foruseAuth.This file has the same CI failure as
convex-provider.tsx. The@workos-inc/authkit-nextjs/componentssubpath may not exist. Check the package exports to find the correct import path.src/components/convex-provider.tsx (1)
35-39: Addrefreshto dependency array to prevent stale closure.
refreshis invoked infetchAccessTokenbut omitted from theuseMemodependencies. Ifrefreshchanges identity between renders, the memoized callback will reference a stale function.}), - [accessToken, loading] + [accessToken, loading, refresh] );convex/auth.config.ts (1)
10-23: Based on official WorkOS and Convex documentation, I can now verify the review comment's accuracy.JWKS URL format requires the clientId in the path—current implementation is incorrect.
The review comment's core concern is valid. Official documentation confirms:
- JWKS endpoint must be:
https://api.workos.com/sso/jwks/{clientId}(not just/sso/jwks)- First issuer should be:
https://api.workos.com/- Second issuer should be:
https://api.workos.com/user_management/{clientId}- Both providers use the same JWKS endpoint
The current code omits the
{clientId}from the JWKS path, which will fail token verification.The suggested diff is correct. Apply it:
{ type: "customJwt", - issuer: `${workosApiUrl}/sso`, - jwks: `${workosApiUrl}/sso/jwks`, + issuer: `${workosApiUrl}/`, + jwks: `${workosApiUrl}/sso/jwks/${workosClientId}`, algorithm: "RS256", applicationID: workosClientId, }, { type: "customJwt", - issuer: `${workosApiUrl}/user_management`, - jwks: `${workosApiUrl}/user_management/jwks`, + issuer: `${workosApiUrl}/user_management/${workosClientId}`, + jwks: `${workosApiUrl}/sso/jwks/${workosClientId}`, algorithm: "RS256", applicationID: workosClientId, },
🧹 Nitpick comments (2)
src/app/api/webhooks/workos/route.ts (1)
71-77: Remove empty try-catch or add actual initialization logic.This try-catch wraps only a log statement and catches errors that can never occur. Either implement the lazy initialization or remove the dead code.
// 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); - } + // The usage system will initialize lazily on first generation + console.log(`User ${id} created. Usage will be initialized lazily on first generation.`);src/modules/home/ui/components/navbar.tsx (1)
30-32: Consider using an underscore prefix instead of void statements for unused destructured variables.The
voidpattern works but is unconventional. A more common TypeScript/ESLint pattern is prefixing with underscore.- const { user, loading, organizationId, refreshAuth } = useAuth(); - - // Keep organization context and refresh handler available when needed. - void organizationId; - void refreshAuth; + const { user, loading, organizationId: _organizationId, refreshAuth: _refreshAuth } = useAuth();Alternatively, if these aren't needed, simply omit them from destructuring.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (5)
convex/auth.config.ts(1 hunks)src/app/api/webhooks/workos/route.ts(1 hunks)src/app/sign-out/route.ts(1 hunks)src/components/convex-provider.tsx(1 hunks)src/modules/home/ui/components/navbar.tsx(3 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/app/sign-out/route.ts (2)
src/app/callback/route.ts (1)
GET(3-3)src/lib/auth-server.ts (1)
signOut(53-53)
src/modules/home/ui/components/navbar.tsx (1)
src/components/ui/button.tsx (1)
Button(60-60)
🪛 GitHub Actions: CI
src/app/api/webhooks/workos/route.ts
[error] 34-35: Property 'get' does not exist on type 'Promise'.
src/components/convex-provider.tsx
[error] 6-32: Cannot find module '@workos-inc/authkit-nextjs/components' or its corresponding type declarations.
src/modules/home/ui/components/navbar.tsx
[error] 9-25: Cannot find module '@workos-inc/authkit-nextjs/components' or its corresponding type declarations.
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Codacy Security Scan
- GitHub Check: claude-review
🔇 Additional comments (4)
src/app/sign-out/route.ts (1)
4-7: LGTM - Correctly handles sign-out with explicit redirect.The previous issue has been addressed. The route now properly awaits
signOut()and returns an explicitNextResponse.redirect(), which is the correct pattern for a Next.js route handler sincesignOut()returnsPromise<void>.src/app/api/webhooks/workos/route.ts (1)
46-58: Webhook signature verification is now properly implemented.The previous security concern has been addressed. The handler now:
- Reads the raw body
- Parses payload and verifies via
webhooks.constructEvent()- Returns 400 on verification failure
src/modules/home/ui/components/navbar.tsx (1)
95-100: Sign-in/sign-up buttons correctly migrated to Link-based navigation.The Clerk
SignInButton/SignUpButtoncomponents have been properly replaced with standardLinkcomponents pointing to the WorkOS-based routes.src/components/convex-provider.tsx (1)
6-6: The import path is correct and follows official WorkOS AuthKit documentation—this review comment is incorrect.The module import
@workos-inc/authkit-nextjs/componentsis the documented and valid export path foruseAccessTokenin authkit-nextjs v0.14.0. Official WorkOS AuthKit documentation confirms this is the proper subpath. The hook is used correctly throughout the codebase (convex-provider.tsx line 23, navbar.tsx line 9), and no module resolution issue exists.Likely an incorrect or invalid review comment.
This migration provides better pricing (free up to 1M users) and
enterprise-ready authentication with seamless Convex integration.
Summary by CodeRabbit
New Features
Documentation
Chores
✏️ Tip: You can customize this high-level summary in your review settings.