Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughAdds a Fireflies integration feature (frontend wizard + five backend endpoints), encrypts and decrypts stored integration credentials across several services, expands privacy policy content for Google data, and updates a WhatsApp docs link. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant Client as Frontend (ManyChat UI)
participant Server as API Server (mastra)
participant DB as Database
participant Fireflies as Fireflies API
User->>Client: Start Fireflies setup
Client->>Server: firefliesCheckExisting(user/team)
Server->>DB: Query integrations for user or team
DB-->>Server: Return existing integration info
Server-->>Client: Show status
Client->>Server: firefliesTestApiKey(apiKey)
Server->>Fireflies: Validate API key
Fireflies-->>Server: Validation result
Server-->>Client: Show validation
Client->>Server: firefliesCreateIntegration(apiKey, scope)
Server->>Fireflies: Test & register integration
Fireflies-->>Server: Confirm
Server->>DB: Encrypt credentials & persist integration/credential records
DB-->>Server: Confirm storage
Server-->>Client: Return integration details
Client->>Server: firefliesGenerateWebhookToken()
Server->>DB: Store generated webhook token (encrypted/expiring)
DB-->>Server: Confirm
Server-->>Client: Return token & webhook info
Client->>Server: firefliesGetWebhookUrl()
Server-->>Client: Return webhook URL and environment config
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ❌ 3❌ Failed checks (2 warnings, 1 inconclusive)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
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 |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Fix all issues with AI agents
In `@src/server/api/routers/mastra.ts`:
- Around line 776-827: quickCreateAction currently hardcodes source: "whatsapp",
which misattributes actions created from other UIs; update quickCreateAction to
accept an optional source in its input (e.g., add source: z.string().optional()
or a z.enum of allowed sources) and use that value when creating the action
(falling back to a sensible default like null or "unknown" if not provided).
Modify the input schema for quickCreateAction, propagate input.source into the
ctx.db.action.create data.source field instead of the hardcoded "whatsapp", and
ensure any returned payload types/serializers account for the optional source
value.
- Around line 1446-1454: The Fireflies integration is saving the API key as
plaintext by passing isEncrypted: false to ctx.db.integrationCredential.create;
replace this with the existing encryption flow: call encryptString(...) from
src/server/utils/encryption.ts on input.apiKey, store the encrypted value as the
key, and set isEncrypted: true (or simply remove the explicit isEncrypted flag
to rely on the DB default), updating the create call in mastra.ts
(ctx.db.integrationCredential.create) accordingly; apply the same fix to other
endpoints that set isEncrypted: false (e.g., integration.ts,
projectWorkflow.ts).
- Around line 1433-1444: getFirefliesIntegration and
getUserFirefliesIntegrations only filter by userId so they miss team-scoped
integrations; update both methods to mirror the OR logic used in
firefliesCheckExisting and firefliesGenerateWebhookToken so queries return
integrations where userId = current user OR teamId is in teams the user belongs
to. Specifically, change the Prisma/DB filters in getFirefliesIntegration and
getUserFirefliesIntegrations to include an OR clause that checks for { userId:
userId } OR { team: { members: { some: { userId } } } } (or teamId equals
provided teamId where appropriate), ensuring team-scoped integrations are
returned for users who are team members.
🧹 Nitpick comments (1)
src/server/api/routers/mastra.ts (1)
1521-1533: Consider expanding production detection heuristic.Line 1531's production detection (
baseUrl.includes('vercel.app') || !baseUrl.includes('localhost')) may miss custom production domains. However, this is informational only and doesn't affect functionality.More robust production check
- isProduction: baseUrl.includes('vercel.app') || !baseUrl.includes('localhost'), + isProduction: process.env.NODE_ENV === 'production' || + (!baseUrl.includes('localhost') && !baseUrl.includes('127.0.0.1')),
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
content/docs/features/whatsapp-gateway.mdsrc/app/(sidemenu)/privacy/page.tsxsrc/app/_components/ManyChat.tsxsrc/server/api/routers/mastra.ts
🧰 Additional context used
📓 Path-based instructions (11)
**/*.{css,scss,sass,pcss,ts,tsx,jsx,js}
📄 CodeRabbit inference engine (.cursorrules)
**/*.{css,scss,sass,pcss,ts,tsx,jsx,js}: NEVER use hardcoded hex colors (#123456,#fff, etc.)
NEVER use RGB/RGBA colors (rgb(), rgba())
ALWAYS use Tailwind classes or CSS variables for colors
Files:
src/app/_components/ManyChat.tsxsrc/app/(sidemenu)/privacy/page.tsxsrc/server/api/routers/mastra.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursorrules)
**/*.{ts,tsx,js,jsx}: Add JSDoc/documentation comments for public functions
Include example usage in documentation/comments where appropriate
Explain non-obvious implementation details in comments
Use the function keyword for pure functions
Avoid unnecessary curly braces in simple conditionals; prefer concise syntax
**/*.{ts,tsx,js,jsx}: Use nullish coalescing operator (??) instead of logical OR (||) operator
All promises must be awaited or explicitly handled with .catch() or .then()
Only await actual promises, not non-promise values
Do not have unused variables or imports
Use descriptive variable names with auxiliary verbs like isLoading, hasError, canSubmit
Files:
src/app/_components/ManyChat.tsxsrc/app/(sidemenu)/privacy/page.tsxsrc/server/api/routers/mastra.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursorrules)
**/*.{ts,tsx}: Verify import/export syntax compatibility (ESM)
Check for proper TypeScript types throughout the codebase
Implement proper type definitions for variables, functions, and return types
Avoid type assertions unless necessary
Use proper generic constraints on TypeScript generics
Use proper TypeScript types for all variables, functions, and return types
Prefer interfaces over type aliases for object shapes
Avoid enums; use maps/objects instead
Use 'nuqs' for URL search parameter state management
**/*.{ts,tsx}: Use Tailwind CSS classes and CSS variables for styling instead of inline styles with hardcoded colors
Do not use any types - use proper TypeScript types instead
Use proper type imports with import type {} syntax for type-only imports
Prefer interfaces over types for defining object shapes in TypeScript
Use Mantine v7 components with Tailwind CSS for styling
Prefer Next.js Link over useRouter.push() for navigation
Use component={Link} with Mantine components for navigation instead of separate Link wrappers
Mantine components should use theme configuration from /src/styles/mantineTheme.ts
Files:
src/app/_components/ManyChat.tsxsrc/app/(sidemenu)/privacy/page.tsxsrc/server/api/routers/mastra.ts
src/app/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursorrules)
src/app/**/*.{ts,tsx}: Use App Router patterns correctly in Next.js
Follow Server Component best practices
Handle client/server code separation properly (use client only when needed)
Minimize use of 'use client', useEffect, and setState; favor React Server ComponentsFollow Next.js App Router patterns with Server Components preferred over Client Components
Files:
src/app/_components/ManyChat.tsxsrc/app/(sidemenu)/privacy/page.tsx
**/*.{tsx,jsx}
📄 CodeRabbit inference engine (.cursorrules)
**/*.{tsx,jsx}: Prefer Next.js over useRouter.push() for navigation
Use component={Link} with Mantine components for navigation
Use declarative JSX
Files:
src/app/_components/ManyChat.tsxsrc/app/(sidemenu)/privacy/page.tsx
**/*.tsx
📄 CodeRabbit inference engine (.cursorrules)
Use functional React components with TypeScript interfaces for props
Files:
src/app/_components/ManyChat.tsxsrc/app/(sidemenu)/privacy/page.tsx
**/*.{tsx,jsx,css,scss,sass}
📄 CodeRabbit inference engine (.cursorrules)
Use Tailwind for components and styling; implement responsive, mobile-first design
Files:
src/app/_components/ManyChat.tsxsrc/app/(sidemenu)/privacy/page.tsx
**/*.{ts,tsx,css,js,jsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx,css,js,jsx}: NEVER USE HARDCODED HEX COLORS like#262626,#C1C2C5,#373A40,#1a1b1e. Use Tailwind classes (e.g., bg-background-primary) or CSS variables (var(--background-primary)) instead
NEVER USE RGB/RGBA COLORS like rgb(26, 27, 30) or rgba(0, 0, 0, 0.5). Use Tailwind classes or CSS variables instead
Use semantic color names for different UI elements (e.g., bg-background-primary, text-text-primary, border-border-primary)
Use CSS variables from /src/styles/globals.css configured for theme switching
Files:
src/app/_components/ManyChat.tsxsrc/app/(sidemenu)/privacy/page.tsxsrc/server/api/routers/mastra.ts
src/server/api/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
src/server/api/**/*.{ts,tsx}: All API logic must use tRPC for end-to-end type safety instead of other API patterns
Use authentication middleware to protect authenticated endpoints in tRPC routers
Files:
src/server/api/routers/mastra.ts
src/server/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use Prisma with proper error handling for database operations
Files:
src/server/api/routers/mastra.ts
src/server/api/routers/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Accept optional workspaceId in tRPC router inputs and filter database queries accordingly
Files:
src/server/api/routers/mastra.ts
🧠 Learnings (1)
📚 Learning: 2026-01-13T14:19:07.763Z
Learnt from: CR
Repo: positonic/exponential PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-13T14:19:07.763Z
Learning: Applies to src/server/api/routers/**/*.{ts,tsx} : Accept optional workspaceId in tRPC router inputs and filter database queries accordingly
Applied to files:
src/server/api/routers/mastra.ts
🧬 Code graph analysis (1)
src/server/api/routers/mastra.ts (2)
src/server/api/trpc.ts (1)
protectedProcedure(208-220)src/server/api/routers/integration.ts (1)
testFirefliesConnection(12-59)
🔇 Additional comments (7)
src/app/_components/ManyChat.tsx (1)
116-157: Well-structured Fireflies integration wizard flow.The wizard instructions provide a clear, secure multi-step flow for the AI agent. The security note on line 133 ("NEVER echo the API key back to the user") is an important safeguard.
A few observations:
- The tool names (
firefliesCheckExisting,firefliesTestApiKey, etc.) align with the backend endpoints inmastra.ts.- The webhook URL in step 5 (line 146) is hardcoded to
https://app.fireflies.ai/integrations/custom- this is correct for Fireflies' external dashboard.src/app/(sidemenu)/privacy/page.tsx (2)
39-199: Comprehensive Google User Data section for API compliance.This section appropriately addresses Google API Services User Data Policy requirements with clear subsections for data access, usage, sharing, storage, retention, and revocation. The structure follows Google's Limited Use requirements well.
Minor note: Line 99 mentions specific service providers (Vercel, Railway). Ensure this list stays current if infrastructure changes.
221-238: Structured rights list improves clarity.The refactored "Your Rights" section with explicit Access, Correction, Deletion, Data Portability, and Withdraw Consent items provides clearer guidance than the previous generic text.
src/server/api/routers/mastra.ts (3)
1334-1377: LGTM - firefliesCheckExisting endpoint.The endpoint correctly checks for existing ACTIVE Fireflies integrations for either the user or their team. The OR clause properly handles both scopes.
1379-1391: LGTM - Secure API key testing.The endpoint correctly validates the API key without persisting or echoing it back. The security comment on line 1389 aligns with the wizard instructions in ManyChat.tsx.
1464-1519: LGTM - Secure webhook token generation.The endpoint properly verifies integration ownership (including team membership check) before generating tokens. The 32-character hex token using
crypto.randomBytes(16)provides sufficient entropy for webhook authentication.content/docs/features/whatsapp-gateway.md (1)
20-20: LGTM - Documentation link update.The link correctly points to the agent chat page at
https://www.exponential.im/agent.
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
| // Natural language action creation - parses dates and matches project names | ||
| quickCreateAction: protectedProcedure | ||
| .input(z.object({ | ||
| text: z.string().min(1), | ||
| })) | ||
| .mutation(async ({ ctx, input }) => { | ||
| const userId = ctx.session.user.id; | ||
|
|
||
| // Use the same parsing logic as action.quickCreate | ||
| const { parseActionInput } = await import("~/server/services/parsing/parseActionInput"); | ||
| const parsed = await parseActionInput(input.text, userId, ctx.db); | ||
|
|
||
| // Get kanban order if project specified | ||
| let kanbanOrder: number | null = null; | ||
| if (parsed.projectId) { | ||
| const highestOrder = await ctx.db.action.findFirst({ | ||
| where: { projectId: parsed.projectId, kanbanOrder: { not: null } }, | ||
| orderBy: { kanbanOrder: 'desc' }, | ||
| select: { kanbanOrder: true }, | ||
| }); | ||
| kanbanOrder = (highestOrder?.kanbanOrder ?? 0) + 1; | ||
| } | ||
|
|
||
| const action = await ctx.db.action.create({ | ||
| data: { | ||
| name: parsed.name, | ||
| projectId: parsed.projectId, | ||
| priority: "Quick", | ||
| status: "ACTIVE", | ||
| createdById: userId, | ||
| dueDate: parsed.dueDate, | ||
| source: "whatsapp", | ||
| kanbanStatus: parsed.projectId ? "TODO" : null, | ||
| kanbanOrder, | ||
| }, | ||
| include: { | ||
| project: { select: { id: true, name: true } }, | ||
| }, | ||
| }); | ||
|
|
||
| return { | ||
| success: true, | ||
| action: { | ||
| id: action.id, | ||
| name: action.name, | ||
| priority: action.priority, | ||
| dueDate: action.dueDate?.toISOString(), | ||
| project: action.project, | ||
| }, | ||
| parsing: parsed.parsingMetadata, | ||
| }; | ||
| }), |
There was a problem hiding this comment.
Source field hardcoded to "whatsapp" may be incorrect.
The quickCreateAction mutation hardcodes source: "whatsapp" on line 807, but this endpoint appears to be a general-purpose natural language action creator. If this endpoint is called from contexts other than WhatsApp (e.g., the chat interface), the source attribution will be misleading.
Suggested fix
Consider accepting source as an optional input parameter:
quickCreateAction: protectedProcedure
.input(z.object({
text: z.string().min(1),
+ source: z.string().optional().default("chat"),
}))
.mutation(async ({ ctx, input }) => {
// ...
const action = await ctx.db.action.create({
data: {
// ...
- source: "whatsapp",
+ source: input.source,
// ...
},
});🤖 Prompt for AI Agents
In `@src/server/api/routers/mastra.ts` around lines 776 - 827, quickCreateAction
currently hardcodes source: "whatsapp", which misattributes actions created from
other UIs; update quickCreateAction to accept an optional source in its input
(e.g., add source: z.string().optional() or a z.enum of allowed sources) and use
that value when creating the action (falling back to a sensible default like
null or "unknown" if not provided). Modify the input schema for
quickCreateAction, propagate input.source into the ctx.db.action.create
data.source field instead of the hardcoded "whatsapp", and ensure any returned
payload types/serializers account for the optional source value.
| // ============================================ | ||
| // FIREFLIES INTEGRATION WIZARD ENDPOINTS | ||
| // These endpoints are designed for the AI agent to guide users through | ||
| // Fireflies configuration via conversational chat | ||
| // ============================================ |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# First, examine the Fireflies endpoints in mastra.ts
echo "=== Examining mastra.ts lines 1320-1350 ==="
sed -n '1320,1350p' src/server/api/routers/mastra.ts
echo ""
echo "=== Checking Integration model in Prisma schema ==="
# Find Prisma schema file
find . -name "*.prisma" -type f | head -5Repository: positonic/exponential
Length of output: 1209
🏁 Script executed:
#!/bin/bash
# Get the Integration model definition from Prisma schema
cat src/prisma/schema.prisma | grep -A 20 "^model Integration"Repository: positonic/exponential
Length of output: 121
🏁 Script executed:
#!/bin/bash
# Check for workspaceId usage pattern in other endpoints of mastra.ts
echo "=== Searching for workspaceId in mastra.ts ==="
rg -n "workspaceId" src/server/api/routers/mastra.ts | head -20
echo ""
echo "=== Searching for teamId in mastra.ts ==="
rg -n "teamId" src/server/api/routers/mastra.ts | head -20Repository: positonic/exponential
Length of output: 614
🏁 Script executed:
#!/bin/bash
# Find Prisma schema file
echo "=== Finding Prisma schema ==="
find . -name "schema.prisma" -o -name "*.prisma" | head -10Repository: positonic/exponential
Length of output: 117
🏁 Script executed:
#!/bin/bash
# Check the directory structure and find where prisma schema is
ls -la | grep -i prisma
find . -path "./node_modules" -prune -o -type d -name "prisma" -print 2>/dev/nullRepository: positonic/exponential
Length of output: 132
🏁 Script executed:
#!/bin/bash
# Search for Integration model definition across the codebase
rg "model Integration" --type-add 'prisma:*.prisma' -A 15Repository: positonic/exponential
Length of output: 3778
🏁 Script executed:
#!/bin/bash
# Check if other routers use workspaceId pattern
echo "=== Checking workspaceId usage in other router files ==="
find src/server/api/routers -name "*.ts" -type f | head -10
rg "workspaceId" src/server/api/routers/ -lRepository: positonic/exponential
Length of output: 906
🏁 Script executed:
#!/bin/bash
# Sample a few router files to understand the workspaceId pattern
echo "=== Checking workspace.ts for workspaceId usage ==="
rg "workspaceId" src/server/api/routers/workspace.ts | head -10
echo ""
echo "=== Checking workflow.ts for workspaceId/teamId usage ==="
rg "workspaceId|teamId" src/server/api/routers/workflow.ts -A 2 | head -20
echo ""
echo "=== Checking project.ts ==="
rg "workspaceId|teamId" src/server/api/routers/project.ts -A 2 | head -20Repository: positonic/exponential
Length of output: 1509
🏁 Script executed:
#!/bin/bash
# Look for Team/Workspace model relationship
echo "=== Checking Team model definition ==="
rg "^model (Team|Workspace)" prisma/schema.prisma -A 10Repository: positonic/exponential
Length of output: 2819
🏁 Script executed:
#!/bin/bash
# Check if there's a Workspace model at all
rg "model Workspace" prisma/schema.prisma -A 15Repository: positonic/exponential
Length of output: 1789
Add workspaceId support to Fireflies integration endpoints.
The Fireflies endpoints use teamId for scoping, but per the workspace scoping pattern used throughout the codebase (workflow.ts, project.ts, etc.), these endpoints should also accept optional workspaceId in their input and filter queries accordingly. Currently, the Integration model only supports teamId, not workspaceId, which limits workspace-level access patterns. Update the endpoints to align with the architectural pattern where workspaceId is the primary scope for multi-tenant filtering.
| // Create the credential | ||
| await ctx.db.integrationCredential.create({ | ||
| data: { | ||
| key: input.apiKey, | ||
| keyType: 'API_KEY', | ||
| isEncrypted: false, // TODO: implement encryption | ||
| integrationId: integration.id, | ||
| }, | ||
| }); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Read the specific file and lines mentioned in the review
head -1454 src/server/api/routers/mastra.ts | tail -20Repository: positonic/exponential
Length of output: 712
🏁 Script executed:
# Check file size first to understand scope
wc -l src/server/api/routers/mastra.tsRepository: positonic/exponential
Length of output: 102
🏁 Script executed:
# Search for encryption-related functions or utilities in the codebase
rg -i "encrypt|cipher|aes" --type ts --type tsx -A 3Repository: positonic/exponential
Length of output: 92
🏁 Script executed:
# Search for other integrationCredential.create calls to see if pattern is consistent
rg "integrationCredential\.create" --type ts --type tsx -B 3 -A 5Repository: positonic/exponential
Length of output: 92
🏁 Script executed:
# Check if there's any encryption implementation in server directory
fd -t f "encrypt\|cipher\|secret" src/server/Repository: positonic/exponential
Length of output: 47
🏁 Script executed:
# Look for environment-based secrets or key management patterns
rg "process\.env\|SECRET|API_KEY" src/server/api/routers/mastra.ts -A 2 -B 2Repository: positonic/exponential
Length of output: 713
🏁 Script executed:
# Search with correct ts file type for encryption
rg -i "encrypt|decrypt|cipher" --type ts -A 2 -B 1Repository: positonic/exponential
Length of output: 25251
🏁 Script executed:
# Find Prisma schema file to understand integrationCredential structure
find . -name "*.prisma" -o -name "schema.prisma"Repository: positonic/exponential
Length of output: 87
🏁 Script executed:
# Check package.json for encryption libraries
cat package.json | grep -A 20 -B 5 "dependencies\|devDependencies" | head -50Repository: positonic/exponential
Length of output: 1737
🏁 Script executed:
# Search for how credentials are retrieved/used to see if decryption is expected
rg "integrationCredential" --type ts -B 2 -A 2 | head -60Repository: positonic/exponential
Length of output: 3749
🏁 Script executed:
# Check if there's any crypto or encryption usage in the codebase
rg "require.*crypto|import.*crypto|require.*bcrypt|import.*bcrypt" --type tsRepository: positonic/exponential
Length of output: 1118
🏁 Script executed:
# Search for credential retrieval patterns in mastra.ts
rg "integrationCredential" src/server/api/routers/mastra.ts -B 3 -A 3 | head -80Repository: positonic/exponential
Length of output: 231
🏁 Script executed:
# Check the Prisma schema for integrationCredential structure
grep -A 20 "model IntegrationCredential" prisma/schema.prismaRepository: positonic/exponential
Length of output: 795
🏁 Script executed:
# Look for how fireflies credentials are used
rg -i "fireflies" src/server/api/routers/mastra.ts -B 2 -A 5 | head -100Repository: positonic/exponential
Length of output: 3358
Fireflies API key stored without encryption—use the existing encryption infrastructure.
The Fireflies API key is stored with isEncrypted: false, storing it as plaintext in the database. While the TODO acknowledges this gap, the codebase already has production-ready AES-256-GCM encryption (via src/server/utils/encryption.ts) that's actively used for other sensitive data like PII in contacts. The database schema itself defaults to isEncrypted: true, and other integrations like Slack and Notion correctly set this to true.
Either remove the explicit isEncrypted: false flag to use the default, or encrypt the key using encryptString() before storage. The same issue exists in multiple other integration endpoints (e.g., integration.ts, projectWorkflow.ts), so consider a broader audit.
🤖 Prompt for AI Agents
In `@src/server/api/routers/mastra.ts` around lines 1446 - 1454, The Fireflies
integration is saving the API key as plaintext by passing isEncrypted: false to
ctx.db.integrationCredential.create; replace this with the existing encryption
flow: call encryptString(...) from src/server/utils/encryption.ts on
input.apiKey, store the encrypted value as the key, and set isEncrypted: true
(or simply remove the explicit isEncrypted flag to rely on the DB default),
updating the create call in mastra.ts (ctx.db.integrationCredential.create)
accordingly; apply the same fix to other endpoints that set isEncrypted: false
(e.g., integration.ts, projectWorkflow.ts).
There was a problem hiding this comment.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (5)
src/server/services/github-integration.ts (1)
36-48: Critical: Missing decryption when retrieving access token.The
getAccessTokenmethod returnstokenCredential.keydirectly without decrypting it. Since tokens are now stored encrypted, this will return the base64-encoded ciphertext instead of the actual token, causing all GitHub API requests to fail with authentication errors.Proposed fix
+import { encryptCredential, getDecryptedKey } from "~/server/utils/credentialHelper"; -import { encryptCredential } from "~/server/utils/credentialHelper"; private async getAccessToken( integration: GitHubIntegration, ): Promise<string> { const tokenCredential = integration.credentials.find( (cred) => cred.keyType === "access_token", ); if (!tokenCredential) { throw new Error("GitHub access token not found"); } - return tokenCredential.key; + const decryptedKey = getDecryptedKey(tokenCredential); + if (!decryptedKey) { + throw new Error("Failed to decrypt GitHub access token"); + } + return decryptedKey; }src/server/services/notion-integration.ts (1)
28-38: Critical: Missing decryption when retrieving access token.Same issue as in
github-integration.ts— thegetAccessTokenmethod returnstokenCredential.keydirectly without decrypting it. This will cause all Notion API requests to fail with authentication errors since the encrypted ciphertext will be sent instead of the actual token.Proposed fix
+import { encryptCredential, getDecryptedKey } from '~/server/utils/credentialHelper'; -import { encryptCredential } from '~/server/utils/credentialHelper'; private async getAccessToken(integration: NotionIntegration): Promise<string> { const tokenCredential = integration.credentials.find( cred => cred.keyType === 'access_token' ); if (!tokenCredential) { throw new Error('Notion access token not found'); } - return tokenCredential.key; + const decryptedKey = getDecryptedKey(tokenCredential); + if (!decryptedKey) { + throw new Error('Failed to decrypt Notion access token'); + } + return decryptedKey; }src/server/api/routers/integration.ts (3)
1782-1800: Critical: Missing decryption when using stored credentials.The
testConnectionprocedure retrieves credentials and uses them directly without decryption. Since credentials are now encrypted before storage, this will cause all connection tests to fail with invalid/garbled API keys.The same issue affects:
- Fireflies (line 1794)
- Slack (line 1813)
- Notion (line 1833)
- Monday.com (lines 1866, 1876)
🔧 Proposed fix using getDecryptedKey helper
Import the decryption helper at the top of the file:
-import { encryptCredential } from "~/server/utils/credentialHelper"; +import { encryptCredential, getDecryptedKey } from "~/server/utils/credentialHelper";Then decrypt credentials before use (example for Fireflies):
const apiKeyCredential = integration.credentials.find( (c) => c.keyType === "API_KEY", ); if (!apiKeyCredential) { throw new TRPCError({ code: "NOT_FOUND", message: "No API key found for this integration", }); } - const result = await testFirefliesConnection(apiKeyCredential.key); + const decryptedKey = getDecryptedKey(apiKeyCredential); + if (!decryptedKey) { + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: "Failed to decrypt API key", + }); + } + const result = await testFirefliesConnection(decryptedKey);Apply similar changes for Slack, Notion, and Monday.com credential usage.
1954-1956: Critical: Missing decryption in refreshSlackIntegration.The
botTokenCredential.keyis used directly without decryption. This will break Slack connection testing for integrations with encrypted bot tokens.🔧 Proposed fix
+ const decryptedBotToken = getDecryptedKey(botTokenCredential); + if (!decryptedBotToken) { + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: "Failed to decrypt bot token", + }); + } + // Test the connection and get latest team info - const testResult = await testSlackConnection(botTokenCredential.key); + const testResult = await testSlackConnection(decryptedBotToken);
2060-2065: Critical: getFirefliesApiKey returns encrypted value without decryption.This procedure returns the raw credential key which may be encrypted, causing downstream consumers (webhook handlers) to receive garbled API keys.
🔧 Proposed fix
if (!integration || integration.credentials.length === 0) { return null; } - return integration.credentials[0]!.key; + const credential = integration.credentials[0]!; + return getDecryptedKey(credential); }),
🧹 Nitpick comments (2)
src/server/utils/encryption.ts (1)
55-68: Misleading comment about legacy plaintext fallback.The comment on line 65 suggests returning null allows for "legacy plaintext fallback," but this function actually returns
nullon failure rather than returning the originalbase64Encryptedvalue as plaintext. The actual fallback logic is handled incredentialHelper.tsvia theisEncryptedflag.Consider clarifying the comment:
Suggested comment update
} catch { - // If decryption fails, the value might be stored in plaintext (legacy) + // Decryption failed - caller should handle fallback based on isEncrypted flag return null; }src/server/utils/credentialHelper.ts (1)
31-49: Redundant try-catch wrapper.The
decryptFromBase64function already catches exceptions internally and returnsnullon failure. The outer try-catch here will never catch anything thrown fromdecryptFromBase64. While this doesn't cause bugs, it adds unnecessary complexity.Suggested simplification
export function decryptCredential(key: string, isEncrypted: boolean): string | null { if (!isEncrypted) { // Plaintext credential (legacy or encryption unavailable) return key; } - try { - const decrypted = decryptFromBase64(key); - if (decrypted === null) { - // Decryption failed - might be corrupted or wrong key - console.error('Failed to decrypt credential - returning null'); - return null; - } - return decrypted; - } catch (error) { - console.error('Credential decryption error:', error); + const decrypted = decryptFromBase64(key); + if (decrypted === null) { + console.error('Failed to decrypt credential - returning null'); return null; } + return decrypted; }
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
src/server/api/routers/integration.tssrc/server/api/routers/projectWorkflow.tssrc/server/services/FirefliesSyncService.tssrc/server/services/github-integration.tssrc/server/services/notion-integration.tssrc/server/utils/credentialHelper.tssrc/server/utils/encryption.ts
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{css,scss,sass,pcss,ts,tsx,jsx,js}
📄 CodeRabbit inference engine (.cursorrules)
**/*.{css,scss,sass,pcss,ts,tsx,jsx,js}: NEVER use hardcoded hex colors (#123456,#fff, etc.)
NEVER use RGB/RGBA colors (rgb(), rgba())
ALWAYS use Tailwind classes or CSS variables for colors
Files:
src/server/services/github-integration.tssrc/server/utils/encryption.tssrc/server/services/notion-integration.tssrc/server/utils/credentialHelper.tssrc/server/api/routers/projectWorkflow.tssrc/server/api/routers/integration.tssrc/server/services/FirefliesSyncService.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursorrules)
**/*.{ts,tsx,js,jsx}: Add JSDoc/documentation comments for public functions
Include example usage in documentation/comments where appropriate
Explain non-obvious implementation details in comments
Use the function keyword for pure functions
Avoid unnecessary curly braces in simple conditionals; prefer concise syntax
**/*.{ts,tsx,js,jsx}: Use nullish coalescing operator (??) instead of logical OR (||) operator
All promises must be awaited or explicitly handled with .catch() or .then()
Only await actual promises, not non-promise values
Do not have unused variables or imports
Use descriptive variable names with auxiliary verbs like isLoading, hasError, canSubmit
Files:
src/server/services/github-integration.tssrc/server/utils/encryption.tssrc/server/services/notion-integration.tssrc/server/utils/credentialHelper.tssrc/server/api/routers/projectWorkflow.tssrc/server/api/routers/integration.tssrc/server/services/FirefliesSyncService.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursorrules)
**/*.{ts,tsx}: Verify import/export syntax compatibility (ESM)
Check for proper TypeScript types throughout the codebase
Implement proper type definitions for variables, functions, and return types
Avoid type assertions unless necessary
Use proper generic constraints on TypeScript generics
Use proper TypeScript types for all variables, functions, and return types
Prefer interfaces over type aliases for object shapes
Avoid enums; use maps/objects instead
Use 'nuqs' for URL search parameter state management
**/*.{ts,tsx}: Use Tailwind CSS classes and CSS variables for styling instead of inline styles with hardcoded colors
Do not use any types - use proper TypeScript types instead
Use proper type imports with import type {} syntax for type-only imports
Prefer interfaces over types for defining object shapes in TypeScript
Use Mantine v7 components with Tailwind CSS for styling
Prefer Next.js Link over useRouter.push() for navigation
Use component={Link} with Mantine components for navigation instead of separate Link wrappers
Mantine components should use theme configuration from /src/styles/mantineTheme.ts
Files:
src/server/services/github-integration.tssrc/server/utils/encryption.tssrc/server/services/notion-integration.tssrc/server/utils/credentialHelper.tssrc/server/api/routers/projectWorkflow.tssrc/server/api/routers/integration.tssrc/server/services/FirefliesSyncService.ts
**/*.{ts,tsx,css,js,jsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx,css,js,jsx}: NEVER USE HARDCODED HEX COLORS like#262626,#C1C2C5,#373A40,#1a1b1e. Use Tailwind classes (e.g., bg-background-primary) or CSS variables (var(--background-primary)) instead
NEVER USE RGB/RGBA COLORS like rgb(26, 27, 30) or rgba(0, 0, 0, 0.5). Use Tailwind classes or CSS variables instead
Use semantic color names for different UI elements (e.g., bg-background-primary, text-text-primary, border-border-primary)
Use CSS variables from /src/styles/globals.css configured for theme switching
Files:
src/server/services/github-integration.tssrc/server/utils/encryption.tssrc/server/services/notion-integration.tssrc/server/utils/credentialHelper.tssrc/server/api/routers/projectWorkflow.tssrc/server/api/routers/integration.tssrc/server/services/FirefliesSyncService.ts
src/server/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use Prisma with proper error handling for database operations
Files:
src/server/services/github-integration.tssrc/server/utils/encryption.tssrc/server/services/notion-integration.tssrc/server/utils/credentialHelper.tssrc/server/api/routers/projectWorkflow.tssrc/server/api/routers/integration.tssrc/server/services/FirefliesSyncService.ts
src/server/api/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
src/server/api/**/*.{ts,tsx}: All API logic must use tRPC for end-to-end type safety instead of other API patterns
Use authentication middleware to protect authenticated endpoints in tRPC routers
Files:
src/server/api/routers/projectWorkflow.tssrc/server/api/routers/integration.ts
src/server/api/routers/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Accept optional workspaceId in tRPC router inputs and filter database queries accordingly
Files:
src/server/api/routers/projectWorkflow.tssrc/server/api/routers/integration.ts
🧠 Learnings (3)
📚 Learning: 2026-01-13T14:19:07.763Z
Learnt from: CR
Repo: positonic/exponential PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-13T14:19:07.763Z
Learning: Applies to src/server/**/*.{ts,tsx} : Use Prisma with proper error handling for database operations
Applied to files:
src/server/services/notion-integration.ts
📚 Learning: 2026-01-13T14:19:07.763Z
Learnt from: CR
Repo: positonic/exponential PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-13T14:19:07.763Z
Learning: Applies to src/server/api/**/*.{ts,tsx} : Use authentication middleware to protect authenticated endpoints in tRPC routers
Applied to files:
src/server/api/routers/projectWorkflow.tssrc/server/api/routers/integration.ts
📚 Learning: 2026-01-13T14:19:07.763Z
Learnt from: CR
Repo: positonic/exponential PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-13T14:19:07.763Z
Learning: Applies to src/server/api/routers/**/*.{ts,tsx} : Accept optional workspaceId in tRPC router inputs and filter database queries accordingly
Applied to files:
src/server/api/routers/projectWorkflow.ts
🧬 Code graph analysis (6)
src/server/services/github-integration.ts (2)
src/server/utils/credentialHelper.ts (1)
encryptCredential(12-25)src/server/db.ts (1)
db(14-14)
src/server/services/notion-integration.ts (2)
src/server/utils/credentialHelper.ts (1)
encryptCredential(12-25)src/server/db.ts (1)
db(14-14)
src/server/utils/credentialHelper.ts (1)
src/server/utils/encryption.ts (3)
isEncryptionAvailable(74-76)encryptToBase64(50-53)decryptFromBase64(59-68)
src/server/api/routers/projectWorkflow.ts (1)
src/server/utils/credentialHelper.ts (1)
encryptCredential(12-25)
src/server/api/routers/integration.ts (1)
src/server/utils/credentialHelper.ts (1)
encryptCredential(12-25)
src/server/services/FirefliesSyncService.ts (1)
src/server/utils/credentialHelper.ts (1)
getDecryptedKey(55-57)
🔇 Additional comments (15)
src/server/utils/encryption.ts (2)
46-53: LGTM!Clean wrapper function with appropriate JSDoc documentation. Correctly delegates to the existing
encryptStringutility.
70-76: LGTM!Simple utility to check encryption availability. Correctly returns a boolean based on the presence of
KEY_ENV.src/server/utils/credentialHelper.ts (3)
1-7: LGTM!Good module-level documentation explaining the purpose and backward compatibility strategy for credential handling.
12-25: LGTM!Solid implementation with appropriate fallback behavior when encryption is unavailable. The error logging provides visibility for debugging encryption failures.
51-57: LGTM!Clean convenience wrapper for Prisma credential objects.
src/server/services/github-integration.ts (1)
8-8: Encryption applied correctly for storage.The access token is now properly encrypted before being stored in the database.
Also applies to: 109-117
src/server/services/FirefliesSyncService.ts (2)
3-3: LGTM!Correct implementation of credential decryption. The
getDecryptedKeycall properly handles both encrypted and plaintext credentials, with appropriate error handling and logging.Also applies to: 54-59
35-38: Team-based access expansion is consistent with the codebase pattern.The OR condition allowing access via direct ownership or team membership is used consistently for shared resources. Similar patterns appear in SlackChannelResolver (line 128) and TranscriptionProcessingService (line 434) for project queries. This is an appropriate authorization model for team-based integration access.
src/server/services/notion-integration.ts (1)
3-3: Encryption applied correctly for storage.The Notion access token is properly encrypted before being persisted to the database.
Also applies to: 102-110
src/server/api/routers/integration.ts (4)
10-10: LGTM!Import of
encryptCredentialhelper is correctly added for credential encryption support.
498-507: LGTM - encryption properly applied to API keys.The credential encryption implementation is correct—sensitive API keys are encrypted before storage with the
isEncryptedflag properly set from the encryption result.
634-677: Good classification of sensitive vs non-sensitive credentials.The implementation correctly:
- Encrypts sensitive values:
BOT_TOKEN,SIGNING_SECRET,USER_TOKEN- Stores identifiers in plaintext:
TEAM_ID,APP_ID
779-809: LGTM - WhatsApp credential encryption correctly implemented.Properly encrypts sensitive tokens (
ACCESS_TOKEN,WEBHOOK_VERIFY_TOKEN) while leaving identifiers (PHONE_NUMBER_ID,BUSINESS_ACCOUNT_ID) in plaintext.src/server/api/routers/projectWorkflow.ts (2)
7-7: LGTM!Import correctly added for credential encryption support.
449-458: LGTM - credential encryption properly implemented.The API key is correctly encrypted before storage using
encryptCredential, with theisEncryptedflag properly propagated from the encryption result.
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
Description
Brief description of what this PR does.
Type of Change
Database Changes
If migrations are included:
Testing
Checklist
Deployment Notes
Any special deployment considerations?
Related Issues
Closes #
Summary by CodeRabbit
Documentation
New Features
Privacy & Policy
Security
✏️ Tip: You can customize this high-level summary in your review settings.