mirror of
https://github.com/Mail-0/Zero.git
synced 2026-06-29 07:16:19 +00:00
Handle missing OAuth scopes gracefully with error messaging
Implemented error handling for missing OAuth scopes, displaying user-friendly messages in the login flow. Enhanced TRPC middleware and client logic to trigger logout and redirect users to a dedicated error state. Refactored error indicator components to ensure UI consistency.
This commit is contained in:
26
apps/mail/app/(auth)/login/error-message.tsx
Normal file
26
apps/mail/app/(auth)/login/error-message.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
'use client';
|
||||
|
||||
import { TriangleAlert } from 'lucide-react';
|
||||
import { useQueryState } from 'nuqs';
|
||||
|
||||
const errorMessages: Record<string, string> = {
|
||||
'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 (
|
||||
<div className="border-red/10 bg-red/5 min-w-0 max-w-fit shrink overflow-hidden break-words rounded-lg border p-4 dark:border-white/10 dark:bg-white/5">
|
||||
<div className="flex items-center">
|
||||
<TriangleAlert size={28} />
|
||||
<p className="ml-2 text-sm text-black/80 dark:text-white/80">{errorMessages[error]}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ErrorMessage;
|
||||
@@ -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) {
|
||||
<div className="rounded-lg border border-black/10 bg-black/5 p-5 dark:border-white/10 dark:bg-white/5">
|
||||
<div className="flex flex-col space-y-4">
|
||||
<div className="flex items-center">
|
||||
<svg
|
||||
className="h-5 w-5 text-black dark:text-white"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
|
||||
/>
|
||||
</svg>
|
||||
<TriangleAlert size={28} />
|
||||
<h3 className="ml-2 text-base font-medium text-black dark:text-white">
|
||||
Configuration Required
|
||||
</h3>
|
||||
@@ -295,20 +284,7 @@ function LoginClientContent({ providers, isProd }: LoginClientProps) {
|
||||
{shouldShowSimplifiedMessage && (
|
||||
<div className="rounded-lg border border-black/10 bg-black/5 p-4 dark:border-white/10 dark:bg-white/5">
|
||||
<div className="flex items-center">
|
||||
<svg
|
||||
className="h-5 w-5 text-black dark:text-white"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
|
||||
/>
|
||||
</svg>
|
||||
<TriangleAlert size={28} />
|
||||
<p className="ml-2 text-sm text-black/80 dark:text-white/80">
|
||||
Authentication service unavailable
|
||||
</p>
|
||||
@@ -316,6 +292,8 @@ function LoginClientContent({ providers, isProd }: LoginClientProps) {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<ErrorMessage />
|
||||
|
||||
{!hasMissingRequiredProviders && (
|
||||
<div className="relative z-10 mx-auto flex w-full flex-col items-center justify-center gap-2">
|
||||
{sortedProviders.map(
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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}`,
|
||||
|
||||
@@ -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)));
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 }) => {
|
||||
|
||||
Reference in New Issue
Block a user