mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-04-08 03:00:28 -04:00
Compare commits
19 Commits
ntindle/op
...
hotfix/wai
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d89b84ba2b | ||
|
|
4619b07945 | ||
|
|
d43535e491 | ||
|
|
a35914889a | ||
|
|
7c248f2d6e | ||
|
|
d4a7ce3846 | ||
|
|
605a198c09 | ||
|
|
a3389485a7 | ||
|
|
cd439e912a | ||
|
|
7b32290582 | ||
|
|
e3137382c3 | ||
|
|
097a19141d | ||
|
|
65f2c04ef1 | ||
|
|
865abdb9e0 | ||
|
|
b59b200bd6 | ||
|
|
e7fb4cce5a | ||
|
|
85e2aef6ad | ||
|
|
85a8fb598e | ||
|
|
ae20da8aaa |
@@ -2,11 +2,18 @@
|
||||
|
||||
import { isServerSide } from "@/lib/utils/is-server-side";
|
||||
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 { isWaitlistError } from "@/app/api/auth/utils";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
export default function AuthErrorPage() {
|
||||
const [errorType, setErrorType] = useState<string | null>(null);
|
||||
const [errorCode, setErrorCode] = useState<string | null>(null);
|
||||
const [errorDescription, setErrorDescription] = useState<string | null>(null);
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
// This code only runs on the client side
|
||||
@@ -23,15 +30,57 @@ export default function AuthErrorPage() {
|
||||
}, []);
|
||||
|
||||
if (!errorType && !errorCode && !errorDescription) {
|
||||
return <div>Loading...</div>;
|
||||
return (
|
||||
<div className="flex h-screen items-center justify-center">
|
||||
<Text variant="body">Loading...</Text>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Check if this is a waitlist/not allowed error
|
||||
const isWaitlistErr = isWaitlistError(errorCode, errorDescription);
|
||||
|
||||
if (isWaitlistErr) {
|
||||
return (
|
||||
<div className="flex h-screen items-center justify-center">
|
||||
<Card className="w-full max-w-md p-8">
|
||||
<WaitlistErrorContent
|
||||
onClose={() => router.push("/login")}
|
||||
closeButtonText="Back to Login"
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Default error display for other types of errors
|
||||
return (
|
||||
<div>
|
||||
<h1>Authentication Error</h1>
|
||||
{errorType && <p>Error Type: {errorType}</p>}
|
||||
{errorCode && <p>Error Code: {errorCode}</p>}
|
||||
{errorDescription && <p>Error Description: {errorDescription}</p>}
|
||||
<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">Authentication Error</Text>
|
||||
<div className="flex flex-col gap-2 text-center">
|
||||
{errorType && (
|
||||
<Text variant="body">
|
||||
<strong>Error Type:</strong> {errorType}
|
||||
</Text>
|
||||
)}
|
||||
{errorCode && (
|
||||
<Text variant="body">
|
||||
<strong>Error Code:</strong> {errorCode}
|
||||
</Text>
|
||||
)}
|
||||
{errorDescription && (
|
||||
<Text variant="body">
|
||||
<strong>Description:</strong> {errorDescription}
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
<Button variant="primary" onClick={() => router.push("/login")}>
|
||||
Back to Login
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,11 @@ export async function GET(request: Request) {
|
||||
|
||||
const { error } = await supabase.auth.exchangeCodeForSession(code);
|
||||
|
||||
// Keep minimal error logging for OAuth debugging if needed
|
||||
if (error) {
|
||||
console.error("OAuth code exchange failed:", error.message);
|
||||
}
|
||||
|
||||
if (!error) {
|
||||
try {
|
||||
const api = new BackendAPI();
|
||||
|
||||
@@ -67,7 +67,7 @@ export function useLoginPage() {
|
||||
|
||||
if (!response.ok) {
|
||||
const { error } = await response.json();
|
||||
if (typeof error === "string" && error.includes("not_allowed")) {
|
||||
if (error === "not_allowed") {
|
||||
setShowNotAllowedModal(true);
|
||||
} else {
|
||||
setFeedback(error || "Failed to start OAuth flow");
|
||||
|
||||
@@ -59,6 +59,7 @@ export function useSignupPage() {
|
||||
resetCaptcha();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/auth/provider", {
|
||||
method: "POST",
|
||||
@@ -70,6 +71,13 @@ export function useSignupPage() {
|
||||
const { error } = await response.json();
|
||||
setIsGoogleLoading(false);
|
||||
resetCaptcha();
|
||||
|
||||
// Check for waitlist error
|
||||
if (error === "not_allowed") {
|
||||
setShowNotAllowedModal(true);
|
||||
return;
|
||||
}
|
||||
|
||||
toast({
|
||||
title: error || "Failed to start OAuth flow",
|
||||
variant: "destructive",
|
||||
@@ -142,6 +150,7 @@ export function useSignupPage() {
|
||||
setShowNotAllowedModal(true);
|
||||
return;
|
||||
}
|
||||
|
||||
toast({
|
||||
title: result?.error || "Signup failed",
|
||||
variant: "destructive",
|
||||
|
||||
@@ -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?.code, error?.message)) {
|
||||
logWaitlistError("OAuth Provider", error.message);
|
||||
return NextResponse.json({ error: "not_allowed" }, { status: 403 });
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
@@ -29,6 +30,7 @@ export async function POST(request: Request) {
|
||||
turnstileToken ?? "",
|
||||
"signup",
|
||||
);
|
||||
|
||||
if (!captchaOk) {
|
||||
return NextResponse.json(
|
||||
{ error: "CAPTCHA verification failed. Please try again." },
|
||||
@@ -47,16 +49,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?.code, error?.message)) {
|
||||
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 });
|
||||
}
|
||||
|
||||
|
||||
60
autogpt_platform/frontend/src/app/api/auth/utils.ts
Normal file
60
autogpt_platform/frontend/src/app/api/auth/utils.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Checks if an error is related to the waitlist/allowlist
|
||||
*
|
||||
* Can be used with either:
|
||||
* - Error objects from Supabase auth operations: `isWaitlistError(error?.code, error?.message)`
|
||||
* - URL parameters from OAuth callbacks: `isWaitlistError(errorCode, errorDescription)`
|
||||
*
|
||||
* The PostgreSQL trigger raises P0001 with message format:
|
||||
* "The email address "email" is not allowed to register. Please contact support for assistance."
|
||||
*
|
||||
* @param code - Error code (e.g., "P0001", "unexpected_failure") or null
|
||||
* @param message - Error message/description or null
|
||||
* @returns true if this appears to be a waitlist/allowlist error
|
||||
*/
|
||||
export function isWaitlistError(
|
||||
code?: string | null,
|
||||
message?: string | null,
|
||||
): boolean {
|
||||
// Check for explicit PostgreSQL trigger error code
|
||||
if (code === "P0001") return true;
|
||||
|
||||
if (!message) return false;
|
||||
|
||||
const lowerMessage = message.toLowerCase();
|
||||
|
||||
// Check for the generic database error that occurs during waitlist check
|
||||
// This happens when Supabase wraps the PostgreSQL trigger error
|
||||
if (
|
||||
code === "unexpected_failure" &&
|
||||
message === "Database error saving new user"
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for various waitlist-related patterns in the message
|
||||
return (
|
||||
lowerMessage.includes("p0001") || // PostgreSQL error code in message
|
||||
lowerMessage.includes("not allowed") || // Common waitlist message
|
||||
lowerMessage.includes("waitlist") || // Explicit waitlist mention
|
||||
lowerMessage.includes("allowlist") || // Explicit allowlist mention
|
||||
lowerMessage.includes("allowed_users") || // Database table reference
|
||||
lowerMessage.includes("not allowed to register") // Full trigger message
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
@@ -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,18 +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">Access Restricted</Text>
|
||||
<Text variant="large-medium" className="text-center">
|
||||
We're currently in a limited access phase. Your email address
|
||||
isn't on our current allowlist for early access. If you believe
|
||||
this is an error or would like to request access, please contact us.
|
||||
</Text>
|
||||
<div className="flex justify-end pt-4">
|
||||
<Button variant="primary" onClick={onClose}>
|
||||
I understand
|
||||
</Button>
|
||||
</div>
|
||||
<div className="py-4">
|
||||
<WaitlistErrorContent onClose={onClose} />
|
||||
</div>
|
||||
</Dialog.Content>
|
||||
</Dialog>
|
||||
|
||||
@@ -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">
|
||||
The AutoGPT Platform is currently in closed beta. Your email address
|
||||
isn'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're using the
|
||||
exact same email address you used when signing up.
|
||||
</Text>
|
||||
<Text variant="small" className="text-center text-muted-foreground">
|
||||
If you'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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user