refactor(frontend): improve waitlist error detection with centralized utilities

- Created utility functions for robust waitlist error detection
- Added multiple fallback checks: P0001 error code, message text, and table reference
- Centralized logic in utils.ts to avoid duplication
- Added privacy-conscious logging that sanitizes email addresses
- More resilient detection that handles various Supabase error formats

The error detection now checks for:
1. PostgreSQL P0001 error code (primary indicator)
2. "not allowed to register" message from trigger
3. Reference to "allowed_users" table

This makes the waitlist check more reliable even if Supabase changes
how it formats PostgreSQL trigger errors.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Nicholas Tindle
2025-10-17 13:20:26 -05:00
parent ae20da8aaa
commit 85e2aef6ad
6 changed files with 111 additions and 95 deletions

View File

@@ -5,6 +5,7 @@ import { useEffect, useState } from "react";
import { Button } from "@/components/atoms/Button/Button";
import { Text } from "@/components/atoms/Text/Text";
import { Card } from "@/components/atoms/Card/Card";
import { WaitlistErrorContent } from "@/components/auth/WaitlistErrorContent";
import { useRouter } from "next/navigation";
export default function AuthErrorPage() {
@@ -46,54 +47,10 @@ export default function AuthErrorPage() {
return (
<div className="flex h-screen items-center justify-center">
<Card className="w-full max-w-md p-8">
<div className="flex flex-col items-center gap-6">
<Text variant="h3">Join the Waitlist</Text>
<div className="flex flex-col gap-4 text-center">
<Text variant="body">
AutoGPT Platform is currently in closed beta. Your email address
isn&apos;t on our current allowlist for early access.
</Text>
<Text variant="small" className="text-muted-foreground">
Join our waitlist to get notified when we open up access!
</Text>
</div>
<div className="flex gap-3">
<Button
variant="secondary"
onClick={() => {
window.open("https://agpt.co/waitlist", "_blank");
}}
>
Join Waitlist
</Button>
<Button variant="primary" onClick={() => router.push("/login")}>
Back to Login
</Button>
</div>
<div className="flex flex-col gap-2">
<Text
variant="small"
className="text-center text-muted-foreground"
>
Already signed up for the waitlist? Make sure you&apos;re using
the exact same email address you used when signing up.
</Text>
<Text
variant="small"
className="text-center text-muted-foreground"
>
If you&apos;re not sure which email you used or need help,{" "}
<a
href="https://discord.gg/autogpt"
target="_blank"
rel="noopener noreferrer"
className="underline hover:text-foreground"
>
reach out on Discord
</a>
</Text>
</div>
</div>
<WaitlistErrorContent
onClose={() => router.push("/login")}
closeButtonText="Back to Login"
/>
</Card>
</div>
);

View File

@@ -1,6 +1,7 @@
import { getServerSupabase } from "@/lib/supabase/server/getServerSupabase";
import { NextResponse } from "next/server";
import { LoginProvider } from "@/types/auth";
import { isWaitlistError, logWaitlistError } from "../utils";
export async function POST(request: Request) {
try {
@@ -31,8 +32,9 @@ export async function POST(request: Request) {
});
if (error) {
// FIXME: supabase doesn't return the correct error message for this case
if (error.message.includes("P0001")) {
// Check for waitlist/allowlist error
if (isWaitlistError(error)) {
logWaitlistError("OAuth Provider", error.message);
return NextResponse.json({ error: "not_allowed" }, { status: 403 });
}

View File

@@ -4,6 +4,7 @@ import { getServerSupabase } from "@/lib/supabase/server/getServerSupabase";
import { verifyTurnstileToken } from "@/lib/turnstile";
import { signupFormSchema } from "@/types/auth";
import { shouldShowOnboarding } from "../../helpers";
import { isWaitlistError, logWaitlistError } from "./utils";
export async function POST(request: Request) {
try {
@@ -47,16 +48,19 @@ export async function POST(request: Request) {
const { data, error } = await supabase.auth.signUp(parsed.data);
if (error) {
// FIXME: supabase doesn't return the correct error message for this case
if (error.message.includes("P0001")) {
// Check for waitlist/allowlist error
if (isWaitlistError(error)) {
logWaitlistError("Signup", error.message);
return NextResponse.json({ error: "not_allowed" }, { status: 403 });
}
if ((error as any).code === "user_already_exists") {
return NextResponse.json(
{ error: "user_already_exists" },
{ status: 409 },
);
}
return NextResponse.json({ error: error.message }, { status: 400 });
}

View File

@@ -0,0 +1,34 @@
/**
* Checks if a Supabase auth error is related to the waitlist/allowlist
*
* The PostgreSQL trigger raises P0001 with message format:
* "The email address "email" is not allowed to register. Please contact support for assistance."
*
* @param error - The error object from Supabase auth operations
* @returns true if this is a waitlist/allowlist error
*/
export function isWaitlistError(error: any): boolean {
if (!error?.message) return false;
return (
error.message.includes("P0001") || // PostgreSQL custom error code
error.message.includes("not allowed to register") || // Trigger message
error.message.toLowerCase().includes("allowed_users") // Table reference
);
}
/**
* Logs a waitlist error for debugging purposes
* Does not expose user email in logs for privacy
*
* @param context - Where the error occurred (e.g., "Signup", "OAuth Provider")
* @param errorMessage - The full error message
*/
export function logWaitlistError(context: string, errorMessage: string): void {
// Only log the error code and general message, not the email
const sanitizedMessage = errorMessage.replace(
/"[^"]+@[^"]+"/g, // Matches email addresses in quotes
'"[email]"'
);
console.log(`[${context}] Waitlist check failed:`, sanitizedMessage);
}

View File

@@ -1,6 +1,5 @@
import { Button } from "../atoms/Button/Button";
import { Text } from "../atoms/Text/Text";
import { Dialog } from "../molecules/Dialog/Dialog";
import { WaitlistErrorContent } from "./WaitlistErrorContent";
interface Props {
isOpen: boolean;
@@ -14,47 +13,8 @@ export function EmailNotAllowedModal({ isOpen, onClose }: Props) {
styling={{ maxWidth: "35rem" }}
>
<Dialog.Content>
<div className="flex flex-col items-center gap-8 py-4">
<Text variant="h3">Join the Waitlist</Text>
<div className="flex flex-col gap-4">
<Text variant="large-medium" className="text-center">
AutoGPT Platform is currently in closed beta. Your email address
isn&apos;t on our current allowlist for early access.
</Text>
<Text variant="body" className="text-center">
Join our waitlist to get notified when we open up access!
</Text>
</div>
<div className="flex gap-3">
<Button
variant="secondary"
onClick={() => {
window.open("https://agpt.co/waitlist", "_blank");
}}
>
Join Waitlist
</Button>
<Button variant="primary" onClick={onClose}>
Close
</Button>
</div>
<div className="flex flex-col gap-2">
<Text variant="small" className="text-center text-muted-foreground">
Already signed up for the waitlist? Make sure you&apos;re using
the exact same email address you used when signing up.
</Text>
<Text variant="small" className="text-center text-muted-foreground">
If you&apos;re not sure which email you used or need help,{" "}
<a
href="https://discord.gg/autogpt"
target="_blank"
rel="noopener noreferrer"
className="underline hover:text-foreground"
>
reach out on Discord
</a>
</Text>
</div>
<div className="py-4">
<WaitlistErrorContent onClose={onClose} />
</div>
</Dialog.Content>
</Dialog>

View File

@@ -0,0 +1,59 @@
import { Button } from "../atoms/Button/Button";
import { Text } from "../atoms/Text/Text";
interface WaitlistErrorContentProps {
onClose: () => void;
closeButtonText?: string;
closeButtonVariant?: "primary" | "secondary";
}
export function WaitlistErrorContent({
onClose,
closeButtonText = "Close",
closeButtonVariant = "primary",
}: WaitlistErrorContentProps) {
return (
<div className="flex flex-col items-center gap-6">
<Text variant="h3">Join the Waitlist</Text>
<div className="flex flex-col gap-4 text-center">
<Text variant="large-medium" className="text-center">
AutoGPT Platform is currently in closed beta. Your email address
isn&apos;t on our current allowlist for early access.
</Text>
<Text variant="body" className="text-center">
Join our waitlist to get notified when we open up access!
</Text>
</div>
<div className="flex gap-3">
<Button
variant="secondary"
onClick={() => {
window.open("https://agpt.co/waitlist", "_blank");
}}
>
Join Waitlist
</Button>
<Button variant={closeButtonVariant} onClick={onClose}>
{closeButtonText}
</Button>
</div>
<div className="flex flex-col gap-2">
<Text variant="small" className="text-center text-muted-foreground">
Already signed up for the waitlist? Make sure you&apos;re using the
exact same email address you used when signing up.
</Text>
<Text variant="small" className="text-center text-muted-foreground">
If you&apos;re not sure which email you used or need help,{" "}
<a
href="https://discord.gg/autogpt"
target="_blank"
rel="noopener noreferrer"
className="underline hover:text-foreground"
>
reach out on Discord
</a>
</Text>
</div>
</div>
);
}