Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion apps/mail/components/mail/mail-display.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -643,7 +643,8 @@ const MoreAboutPerson = ({
} = useMutation(trpc.ai.webSearch.mutationOptions());
const handleSearch = useCallback(() => {
doSearch({
query: `In 100 words or less: What is the background of ${person.name} & ${person.email}, of ${person.email.split('@')[1]}. `,
query: `In 100 words or less: What is the background of ${person.name} & ${person.email}, of ${person.email.split('@')[1]}.
This could be a phishing email address, indicate if the domain is suspicious, example: x.io is not a valid domain for x.com | example: x.com is a valid domain for x.com | example: paypalcom.com is not a valid domain for paypal.com`,
});
}, [person.name]);

Expand Down
43 changes: 36 additions & 7 deletions apps/mail/components/ui/nav-main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@ import { Collapsible, CollapsibleTrigger } from '@/components/ui/collapsible';
import { useActiveConnection, useConnections } from '@/hooks/use-connections';
import { type MessageKey, type NavItem } from '@/config/navigation';
import { LabelDialog } from '@/components/labels/label-dialog';
import { useMutation, useQuery } from '@tanstack/react-query';
import Intercom, { show } from '@intercom/messenger-js-sdk';
import { CurvedArrow, MessageSquare } from '../icons/icons';
import { useSearchValue } from '@/hooks/use-search-value';
import { useSidebar } from '../context/sidebar-context';
import { useTRPC } from '@/providers/query-provider';
import { RecursiveFolder } from './recursive-folder';
import { useMutation } from '@tanstack/react-query';
import type { Label as LabelType } from '@/types';
import { Link, useLocation } from 'react-router';
import { Button } from '@/components/ui/button';
Expand All @@ -32,7 +34,6 @@ import { Badge } from '@/components/ui/badge';
import { Input } from '@/components/ui/input';
import { useStats } from '@/hooks/use-stats';
import SidebarLabels from './sidebar-labels';
import { CurvedArrow } from '../icons/icons';
import { Command, Plus } from 'lucide-react';
import { Tree } from '../magicui/file-tree';
import { useCallback, useRef } from 'react';
Expand Down Expand Up @@ -76,14 +77,20 @@ export function NavMain({ items }: NavMainProps) {
const pathname = location.pathname;
const searchParams = new URLSearchParams();
const [category] = useQueryState('category');

const [isDialogOpen, setIsDialogOpen] = React.useState(false);
const { data: session } = useSession();
const { data: connections } = useConnections();
const { data: stats } = useStats();
const { data: activeConnection } = useActiveConnection();

const trpc = useTRPC();
const { data: intercomToken } = useQuery(trpc.user.getIntercomToken.queryOptions());

React.useEffect(() => {
if (intercomToken) {
Intercom({
app_id: 'aavenrba',
intercom_user_jwt: intercomToken,
});
}
}, [intercomToken]);
Comment on lines +84 to +93
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Move hardcoded configuration to environment variables and add error handling

The Intercom integration has several issues:

  1. Hardcoded app_id: The Intercom app ID 'aavenrba' should be an environment variable
  2. No error handling: Missing error handling for token fetch failures
  3. No loading state: No handling for when the token is still loading

Consider this improved implementation:

- const { data: intercomToken } = useQuery(trpc.user.getIntercomToken.queryOptions());
+ const { data: intercomToken, error: tokenError, isLoading: tokenLoading } = useQuery(
+   trpc.user.getIntercomToken.queryOptions()
+ );

  React.useEffect(() => {
-   if (intercomToken) {
+   if (intercomToken && !tokenError && !tokenLoading) {
      Intercom({
-       app_id: 'aavenrba',
+       app_id: process.env.NEXT_PUBLIC_INTERCOM_APP_ID || 'aavenrba',
        intercom_user_jwt: intercomToken,
      });
+   } else if (tokenError) {
+     console.error('Failed to load Intercom token:', tokenError);
    }
- }, [intercomToken]);
+ }, [intercomToken, tokenError, tokenLoading]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const { data: intercomToken } = useQuery(trpc.user.getIntercomToken.queryOptions());
React.useEffect(() => {
if (intercomToken) {
Intercom({
app_id: 'aavenrba',
intercom_user_jwt: intercomToken,
});
}
}, [intercomToken]);
const { data: intercomToken, error: tokenError, isLoading: tokenLoading } = useQuery(
trpc.user.getIntercomToken.queryOptions()
);
React.useEffect(() => {
if (intercomToken && !tokenError && !tokenLoading) {
Intercom({
app_id: process.env.NEXT_PUBLIC_INTERCOM_APP_ID || 'aavenrba',
intercom_user_jwt: intercomToken,
});
} else if (tokenError) {
console.error('Failed to load Intercom token:', tokenError);
}
}, [intercomToken, tokenError, tokenLoading]);
🤖 Prompt for AI Agents
In apps/mail/components/ui/nav-main.tsx around lines 84 to 93, replace the
hardcoded Intercom app_id 'aavenrba' with a value read from an environment
variable. Add error handling to manage failures when fetching the intercomToken,
such as logging or displaying an error message. Also, implement a loading state
to handle the period when the token is being fetched before it is available.
Update the React.useEffect to only initialize Intercom when the token is
successfully fetched and handle cases where the token fetch fails or is loading.


const { mutateAsync: createLabel } = useMutation(trpc.labels.create.mutationOptions());

Expand Down Expand Up @@ -189,9 +196,10 @@ export function NavMain({ items }: NavMainProps) {
},
[pathname, searchParams],
);
const t = useTranslations();

const onSubmit = async (data: LabelType) => {
await toast.promise(createLabel(data), {
toast.promise(createLabel(data), {
loading: 'Creating label...',
success: 'Label created successfully',
error: 'Failed to create label',
Expand All @@ -201,6 +209,27 @@ export function NavMain({ items }: NavMainProps) {
return (
<SidebarGroup className={`${state !== 'collapsed' ? '' : 'mt-1'} space-y-2.5 py-0 md:px-0`}>
<SidebarMenu>
{isBottomNav ? (
<>
<SidebarMenuButton
onClick={() => show()}
tooltip={state === 'collapsed' ? t('help' as MessageKey) : undefined}
className="flex cursor-pointer items-center"
>
<MessageSquare className="relative mr-2.5 h-3 w-3.5" />
<p className="mt-0.5 truncate text-[13px]">Help</p>
</SidebarMenuButton>
<NavItem
key={'feedback'}
isActive={isUrlActive('https://feedback.0.email')}
href={'https://feedback.0.email'}
url={'https://feedback.0.email'}
icon={MessageSquare}
target={'_blank'}
title={'navigation.sidebar.feedback'}
/>
</>
) : null}
Comment on lines +212 to +232
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve consistency and configuration management in bottom navigation

Several inconsistencies and hardcoded values in the new bottom navigation:

  1. Inconsistent translation usage: "Help" is hardcoded while "Feedback" uses translation key
  2. Hardcoded feedback URL: Should be configurable
  3. No error handling: show() call could fail

Apply this diff to improve consistency:

            <SidebarMenuButton
              onClick={() => show()}
              tooltip={state === 'collapsed' ? t('help' as MessageKey) : undefined}
              className="flex cursor-pointer items-center"
            >
              <MessageSquare className="relative mr-2.5 h-3 w-3.5" />
-             <p className="mt-0.5 truncate text-[13px]">Help</p>
+             <p className="mt-0.5 truncate text-[13px]">{t('navigation.sidebar.help' as MessageKey)}</p>
            </SidebarMenuButton>
            <NavItem
              key={'feedback'}
-             isActive={isUrlActive('https://feedback.0.email')}
-             href={'https://feedback.0.email'}
-             url={'https://feedback.0.email'}
+             isActive={isUrlActive(process.env.NEXT_PUBLIC_FEEDBACK_URL || 'https://feedback.0.email')}
+             href={process.env.NEXT_PUBLIC_FEEDBACK_URL || 'https://feedback.0.email'}
+             url={process.env.NEXT_PUBLIC_FEEDBACK_URL || 'https://feedback.0.email'}
              icon={MessageSquare}
              target={'_blank'}
              title={'navigation.sidebar.feedback'}
            />

Also consider wrapping the show() call in error handling:

-             onClick={() => show()}
+             onClick={() => {
+               try {
+                 show();
+               } catch (error) {
+                 console.error('Failed to show Intercom:', error);
+               }
+             }}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{isBottomNav ? (
<>
<SidebarMenuButton
onClick={() => show()}
tooltip={state === 'collapsed' ? t('help' as MessageKey) : undefined}
className="flex cursor-pointer items-center"
>
<MessageSquare className="relative mr-2.5 h-3 w-3.5" />
<p className="mt-0.5 truncate text-[13px]">Help</p>
</SidebarMenuButton>
<NavItem
key={'feedback'}
isActive={isUrlActive('https://feedback.0.email')}
href={'https://feedback.0.email'}
url={'https://feedback.0.email'}
icon={MessageSquare}
target={'_blank'}
title={'navigation.sidebar.feedback'}
/>
</>
) : null}
{isBottomNav ? (
<>
<SidebarMenuButton
- onClick={() => show()}
+ onClick={() => {
+ try {
+ show();
+ } catch (error) {
+ console.error('Failed to show Intercom:', error);
+ }
+ }}
tooltip={state === 'collapsed' ? t('help' as MessageKey) : undefined}
className="flex cursor-pointer items-center"
>
<MessageSquare className="relative mr-2.5 h-3 w-3.5" />
- <p className="mt-0.5 truncate text-[13px]">Help</p>
+ <p className="mt-0.5 truncate text-[13px]">
+ {t('navigation.sidebar.help' as MessageKey)}
+ </p>
</SidebarMenuButton>
<NavItem
key={'feedback'}
- isActive={isUrlActive('https://feedback.0.email')}
- href={'https://feedback.0.email'}
- url={'https://feedback.0.email'}
+ isActive={isUrlActive(process.env.NEXT_PUBLIC_FEEDBACK_URL || 'https://feedback.0.email')}
+ href={process.env.NEXT_PUBLIC_FEEDBACK_URL || 'https://feedback.0.email'}
+ url={process.env.NEXT_PUBLIC_FEEDBACK_URL || 'https://feedback.0.email'}
icon={MessageSquare}
target={'_blank'}
title={'navigation.sidebar.feedback'}
/>
</>
) : null}
🤖 Prompt for AI Agents
In apps/mail/components/ui/nav-main.tsx around lines 212 to 232, fix
inconsistencies by replacing the hardcoded "Help" text with a translation key
similar to "Feedback" using the t() function. Make the feedback URL configurable
by moving it to a constant or config variable instead of hardcoding it. Wrap the
show() call inside a try-catch block to handle potential errors gracefully,
logging or managing errors as appropriate.

{items.map((section) => (
<Collapsible
key={section.title}
Expand Down
7 changes: 0 additions & 7 deletions apps/mail/config/navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,13 +219,6 @@ export const bottomNavItems = [
{
title: '',
items: [
{
id: 'feedback',
title: 'navigation.sidebar.feedback',
url: 'https://feedback.0.email',
icon: MessageSquare,
target: '_blank',
},
{
id: 'settings',
title: 'navigation.sidebar.settings',
Expand Down
1 change: 1 addition & 0 deletions apps/mail/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"@fontsource-variable/geist": "^5.2.6",
"@fontsource-variable/geist-mono": "^5.2.6",
"@hookform/resolvers": "4.1.2",
"@intercom/messenger-js-sdk": "0.0.14",
"@react-email/components": "^0.0.36",
"@react-email/html": "^0.0.11",
"@react-email/render": "^1.1.2",
Expand Down
1 change: 1 addition & 0 deletions apps/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"@react-email/render": "^1.1.0",
"@trpc/client": "catalog:",
"@trpc/server": "catalog:",
"@tsndr/cloudflare-worker-jwt": "3.2.0",
"@upstash/ratelimit": "^2.0.5",
"@upstash/redis": "^1.34.9",
"agents": "0.0.93",
Expand Down
11 changes: 11 additions & 0 deletions apps/server/src/trpc/routes/user.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { privateProcedure, router } from '../trpc';
import jwt from '@tsndr/cloudflare-worker-jwt';

export const userRouter = router({
delete: privateProcedure.mutation(async ({ ctx }) => {
Expand All @@ -11,4 +12,14 @@ export const userRouter = router({
});
return { success, message };
}),
getIntercomToken: privateProcedure.query(async ({ ctx }) => {
const token = await jwt.sign(
{
user_id: ctx.session.user.id,
email: ctx.session.user.email,
},
ctx.c.env.JWT_SECRET,
);
return token;
}),
Comment on lines +15 to +24
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling and security improvements for JWT generation

The JWT token generation implementation needs several improvements:

  1. Missing token expiration: JWT tokens should have an expiration time for security
  2. No error handling: JWT signing could fail and should be wrapped in try-catch
  3. Environment variable validation: Should validate that JWT_SECRET exists

Apply this diff to improve security and error handling:

  getIntercomToken: privateProcedure.query(async ({ ctx }) => {
+   if (!ctx.c.env.JWT_SECRET) {
+     throw new TRPCError({
+       code: 'INTERNAL_SERVER_ERROR',
+       message: 'JWT_SECRET not configured',
+     });
+   }
+   
+   try {
      const token = await jwt.sign(
        {
          user_id: ctx.session.user.id,
          email: ctx.session.user.email,
+         exp: Math.floor(Date.now() / 1000) + (60 * 60), // 1 hour expiration
        },
        ctx.c.env.JWT_SECRET,
      );
      return token;
+   } catch (error) {
+     throw new TRPCError({
+       code: 'INTERNAL_SERVER_ERROR',
+       message: 'Failed to generate Intercom token',
+     });
+   }
  }),

Don't forget to import TRPCError at the top of the file:

-import { privateProcedure, router } from '../trpc';
+import { privateProcedure, router } from '../trpc';
+import { TRPCError } from '@trpc/server';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
getIntercomToken: privateProcedure.query(async ({ ctx }) => {
const token = await jwt.sign(
{
user_id: ctx.session.user.id,
email: ctx.session.user.email,
},
ctx.c.env.JWT_SECRET,
);
return token;
}),
// At the top of apps/server/src/trpc/routes/user.ts
import { privateProcedure, router } from '../trpc';
import { TRPCError } from '@trpc/server';
getIntercomToken: privateProcedure.query(async ({ ctx }) => {
// Validate that the secret is present
if (!ctx.c.env.JWT_SECRET) {
throw new TRPCError({
code: 'INTERNAL_SERVER_ERROR',
message: 'JWT_SECRET not configured',
});
}
try {
const token = await jwt.sign(
{
user_id: ctx.session.user.id,
email: ctx.session.user.email,
// 1 hour expiration
exp: Math.floor(Date.now() / 1000) + 60 * 60,
},
ctx.c.env.JWT_SECRET,
);
return token;
} catch (error) {
throw new TRPCError({
code: 'INTERNAL_SERVER_ERROR',
message: 'Failed to generate Intercom token',
});
}
}),
🤖 Prompt for AI Agents
In apps/server/src/trpc/routes/user.ts around lines 15 to 24, improve the JWT
token generation by adding an expiration time to the token payload, wrapping the
signing process in a try-catch block to handle potential errors, and validating
that the JWT_SECRET environment variable exists before signing. If JWT_SECRET is
missing, throw a TRPCError with an appropriate message. Also, ensure TRPCError
is imported at the top of the file.

});
1 change: 1 addition & 0 deletions apps/server/wrangler.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
"COOKIE_DOMAIN": "localhost",
"VITE_PUBLIC_BACKEND_URL": "http://localhost:8787",
"VITE_PUBLIC_APP_URL": "http://localhost:3000",
"JWT_SECRET": "secret",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Critical security issue: weak JWT secret and missing production configuration.

Two critical issues identified:

  1. Security vulnerability: Using "secret" as the JWT secret is extremely insecure and predictable.
  2. Missing configuration: JWT_SECRET is only defined for the local environment but missing from staging and production.

For the local environment, use a stronger secret:

-        "JWT_SECRET": "secret",
+        "JWT_SECRET": "your-strong-local-jwt-secret-here",

You also need to add JWT_SECRET to staging and production environments with appropriate secure values. Consider using environment-specific secrets management.

🤖 Prompt for AI Agents
In apps/server/wrangler.jsonc at line 69, replace the weak JWT_SECRET value
"secret" with a strong, complex secret for the local environment. Additionally,
add JWT_SECRET entries with secure, environment-specific values for the staging
and production configurations to ensure proper security across all environments.
Use environment-specific secrets management practices to handle these sensitive
values safely.

},
"kv_namespaces": [
{
Expand Down
16 changes: 16 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.