diff --git a/apps/mail/app/(auth)/login/error-message.tsx b/apps/mail/app/(auth)/login/error-message.tsx new file mode 100644 index 000000000..e89472151 --- /dev/null +++ b/apps/mail/app/(auth)/login/error-message.tsx @@ -0,0 +1,26 @@ +'use client'; + +import { TriangleAlert } from 'lucide-react'; +import { useQueryState } from 'nuqs'; + +const errorMessages: Record = { + 'required-scopes-missing': + 'We’re missing the permissions needed to craft your full experience. Please sign in again and allow the requested access.', +}; + +const ErrorMessage = () => { + const [error] = useQueryState('error'); + + if (!error || !(error in errorMessages)) return null; + + return ( +
+
+ +

{errorMessages[error]}

+
+
+ ); +}; + +export default ErrorMessage; diff --git a/apps/mail/app/(auth)/login/login-client.tsx b/apps/mail/app/(auth)/login/login-client.tsx index 440785a86..5f6407d9a 100644 --- a/apps/mail/app/(auth)/login/login-client.tsx +++ b/apps/mail/app/(auth)/login/login-client.tsx @@ -3,9 +3,11 @@ import { useEffect, type ReactNode, useState, Suspense } from 'react'; import type { EnvVarInfo } from '@zero/server/auth-providers'; import { useRouter, useSearchParams } from 'next/navigation'; +import ErrorMessage from '@/app/(auth)/login/error-message'; import { signIn, useSession } from '@/lib/auth-client'; import { Google } from '@/components/icons/icons'; import { Button } from '@/components/ui/button'; +import { TriangleAlert } from 'lucide-react'; import Image from 'next/image'; import { toast } from 'sonner'; import Link from 'next/link'; @@ -157,20 +159,7 @@ function LoginClientContent({ providers, isProd }: LoginClientProps) {
- - - +

Configuration Required

@@ -295,20 +284,7 @@ function LoginClientContent({ providers, isProd }: LoginClientProps) { {shouldShowSimplifiedMessage && (
- - - +

Authentication service unavailable

@@ -316,6 +292,8 @@ function LoginClientContent({ providers, isProd }: LoginClientProps) {
)} + + {!hasMissingRequiredProviders && (
{sortedProviders.map( diff --git a/apps/mail/providers/query-provider.tsx b/apps/mail/providers/query-provider.tsx index b8337b343..f5a062c8a 100644 --- a/apps/mail/providers/query-provider.tsx +++ b/apps/mail/providers/query-provider.tsx @@ -6,8 +6,8 @@ import { } from '@tanstack/react-query-persist-client'; import { createTRPCClient, httpBatchLink, loggerLink } from '@trpc/client'; import { QueryCache, QueryClient, hashKey } from '@tanstack/react-query'; +import { useSession, type Session, signOut } from '@/lib/auth-client'; import { createTRPCContext } from '@trpc/tanstack-react-query'; -import { useSession, type Session } from '@/lib/auth-client'; import type { AppRouter } from '@zero/server/trpc'; import { CACHE_BURST_KEY } from '@/lib/constants'; import type { PropsWithChildren } from 'react'; @@ -35,7 +35,15 @@ export const makeQueryClient = (session: Session | null) => onError: (err, { meta }) => { if (meta && meta.noGlobalError === true) return; if (meta && typeof meta.customError === 'string') toast.error(meta.customError); - else toast.error(err.message || 'Something went wrong'); + else if (err.message === 'Required scopes missing') { + signOut({ + fetchOptions: { + onSuccess: () => { + window.location.href = '/login?error=required-scopes-missing'; + }, + }, + }); + } else toast.error(err.message || 'Something went wrong'); }, }), defaultOptions: { diff --git a/apps/server/src/lib/driver/google.ts b/apps/server/src/lib/driver/google.ts index 7b483f1f0..26bcd7a2d 100644 --- a/apps/server/src/lib/driver/google.ts +++ b/apps/server/src/lib/driver/google.ts @@ -1002,6 +1002,7 @@ export class GoogleMailManager implements MailManager { return await Promise.resolve(fn()); // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (error: any) { + console.error(error); const isFatal = FatalErrors.includes(error.message); console.error( `[${isFatal ? 'FATAL_ERROR' : 'ERROR'}] [Gmail Driver] Operation: ${operation}`, diff --git a/apps/server/src/lib/driver/utils.ts b/apps/server/src/lib/driver/utils.ts index f94952cf7..3586eff31 100644 --- a/apps/server/src/lib/driver/utils.ts +++ b/apps/server/src/lib/driver/utils.ts @@ -7,10 +7,10 @@ import { and, eq } from 'drizzle-orm'; export const FatalErrors = ['invalid_grant']; export const deleteActiveConnection = async (c: HonoContext) => { + console.log('DELETEME'); const session = await c.var.auth.api.getSession({ headers: c.req.raw.headers }); if (!session?.connectionId) return console.log('No connection ID found'); try { - await c.var.auth.api.signOut({ headers: c.req.raw.headers }); await c.var.db .delete(connection) .where(and(eq(connection.userId, session.user.id), eq(connection.id, session.connectionId))); diff --git a/apps/server/src/trpc/routes/mail.ts b/apps/server/src/trpc/routes/mail.ts index 89984e861..56df080e5 100644 --- a/apps/server/src/trpc/routes/mail.ts +++ b/apps/server/src/trpc/routes/mail.ts @@ -48,12 +48,14 @@ export const mailRouter = router({ const drafts = await driver.listDrafts({ q, maxResults: max, pageToken: cursor }); return drafts; } + console.log('tr123'); const threadsResponse = await driver.list({ folder, query: q, maxResults: max, pageToken: cursor, }); + console.log('tr246'); return threadsResponse; }), markAsRead: activeDriverProcedure diff --git a/apps/server/src/trpc/trpc.ts b/apps/server/src/trpc/trpc.ts index 50c88fed4..33811a2a4 100644 --- a/apps/server/src/trpc/trpc.ts +++ b/apps/server/src/trpc/trpc.ts @@ -1,6 +1,7 @@ import { connectionToDriver, getActiveConnection } from '../lib/server-utils'; import { Ratelimit, type RatelimitConfig } from '@upstash/ratelimit'; import type { HonoContext, HonoVariables } from '../ctx'; +import { StandardizedError } from '../lib/driver/utils'; import { initTRPC, TRPCError } from '@trpc/server'; import { env } from 'cloudflare:workers'; import { redis } from '../lib/services'; @@ -35,7 +36,18 @@ export const activeConnectionProcedure = privateProcedure.use(async ({ ctx, next export const activeDriverProcedure = activeConnectionProcedure.use(async ({ ctx, next }) => { const { activeConnection } = ctx; const driver = connectionToDriver(activeConnection, ctx.c); - return next({ ctx: { ...ctx, driver } }); + const res = await next({ ctx: { ...ctx, driver } }); + + // This is for when the user has not granted the required scopes for GMail + if (!res.ok && res.error.message === 'Precondition check failed.') { + throw new TRPCError({ + code: 'BAD_REQUEST', + message: 'Required scopes missing', + cause: res.error, + }); + } + + return res; }); export const brainServerAvailableMiddleware = t.middleware(async ({ next, ctx }) => {