Skip to content

Commit 47adab5

Browse files
authored
fix(frontend): Fix login/logout actions; update credentials cache on state change (#10017)
- Resolves #10008 ### Changes 🏗️ - Update `useSupabase` hook to propagate auth state changes - Refresh `CredentialsProvider` whenever the user login state changes - Add `logOut` callback to `useSupabase` hook that handles (client-side) logout - Remove server-side `logout` action: the Supabase reference implementation does it client-side, and doing both causes a race condition Refactorings to aid implementation of the above: - Move `@/hooks/useSupabase` -> `@/lib/supabase/useSupabase` Other improvements: - Clean up `login` server action based on reference implementation - Make `BackendAPI.isAuthenticated()` more efficient and faster - Remove unused `ProfileDropdown` component - Improve logic and debug logging in `tests/pages/login.page.ts` - Improve playwright test output logging ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [x] I have tested my changes according to the test plan: - Log out from account (A) - Log in to other account (B) - Open builder, add a block for which account B has (multiple) credentials - [x] Credentials for account B are shown - [x] Credentials for account A are *not* shown **Note: do not reload the page** while going through these steps
1 parent fa7fcb3 commit 47adab5

File tree

16 files changed

+161
-202
lines changed

16 files changed

+161
-202
lines changed

autogpt_platform/frontend/src/app/(platform)/login/actions.ts

Lines changed: 3 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -8,30 +8,6 @@ import BackendAPI from "@/lib/autogpt-server-api";
88
import { loginFormSchema, LoginProvider } from "@/types/auth";
99
import { verifyTurnstileToken } from "@/lib/turnstile";
1010

11-
export async function logout() {
12-
return await Sentry.withServerActionInstrumentation(
13-
"logout",
14-
{},
15-
async () => {
16-
const supabase = getServerSupabase();
17-
18-
if (!supabase) {
19-
redirect("/error");
20-
}
21-
22-
const { error } = await supabase.auth.signOut();
23-
24-
if (error) {
25-
console.error("Error logging out", error);
26-
return error.message;
27-
}
28-
29-
revalidatePath("/", "layout");
30-
redirect("/login");
31-
},
32-
);
33-
}
34-
3511
async function shouldShowOnboarding() {
3612
const api = new BackendAPI();
3713
return (
@@ -59,23 +35,21 @@ export async function login(
5935
}
6036

6137
// We are sure that the values are of the correct type because zod validates the form
62-
const { data, error } = await supabase.auth.signInWithPassword(values);
38+
const { error } = await supabase.auth.signInWithPassword(values);
6339

6440
if (error) {
65-
console.error("Error logging in", error);
41+
console.error("Error logging in:", error);
6642
return error.message;
6743
}
6844

6945
await api.createUser();
46+
7047
// Don't onboard if disabled or already onboarded
7148
if (await shouldShowOnboarding()) {
7249
revalidatePath("/onboarding", "layout");
7350
redirect("/onboarding");
7451
}
7552

76-
if (data.session) {
77-
await supabase.auth.setSession(data.session);
78-
}
7953
revalidatePath("/", "layout");
8054
redirect("/");
8155
});

autogpt_platform/frontend/src/app/(platform)/login/page.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { zodResolver } from "@hookform/resolvers/zod";
1515
import { useCallback, useState } from "react";
1616
import { useRouter } from "next/navigation";
1717
import Link from "next/link";
18-
import useSupabase from "@/hooks/useSupabase";
18+
import useSupabase from "@/lib/supabase/useSupabase";
1919
import LoadingBox from "@/components/ui/loading";
2020
import {
2121
AuthCard,
@@ -80,6 +80,7 @@ export default function LoginPage() {
8080
}
8181

8282
const error = await login(data, turnstile.token as string);
83+
await supabase?.auth.refreshSession();
8384
setIsLoading(false);
8485
if (error) {
8586
setFeedback(error);
@@ -89,7 +90,7 @@ export default function LoginPage() {
8990
}
9091
setFeedback(null);
9192
},
92-
[form, turnstile],
93+
[form, turnstile, supabase],
9394
);
9495

9596
if (user) {

autogpt_platform/frontend/src/app/(platform)/profile/(user)/dashboard/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
StoreSubmissionsResponse,
1212
StoreSubmissionRequest,
1313
} from "@/lib/autogpt-server-api/types";
14-
import useSupabase from "@/hooks/useSupabase";
14+
import useSupabase from "@/lib/supabase/useSupabase";
1515
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
1616

1717
export default function Page({}: {}) {

autogpt_platform/frontend/src/app/(platform)/profile/(user)/integrations/page.tsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"use client";
22
import { Button } from "@/components/ui/button";
33
import { useRouter } from "next/navigation";
4-
import { useCallback, useContext, useMemo, useState } from "react";
4+
import { useCallback, useContext, useEffect, useMemo, useState } from "react";
55
import { useToast } from "@/components/ui/use-toast";
66
import { IconKey, IconUser } from "@/components/ui/icons";
77
import { Trash2Icon } from "lucide-react";
@@ -26,10 +26,10 @@ import {
2626
AlertDialogHeader,
2727
AlertDialogTitle,
2828
} from "@/components/ui/alert-dialog";
29-
import useSupabase from "@/hooks/useSupabase";
29+
import useSupabase from "@/lib/supabase/useSupabase";
3030
import LoadingBox from "@/components/ui/loading";
3131

32-
export default function PrivatePage() {
32+
export default function UserIntegrationsPage() {
3333
const { supabase, user, isUserLoading } = useSupabase();
3434
const router = useRouter();
3535
const providers = useContext(CredentialsProvidersContext);
@@ -122,15 +122,15 @@ export default function PrivatePage() {
122122
[],
123123
);
124124

125+
useEffect(() => {
126+
if (isUserLoading) return;
127+
if (!user || !supabase) router.push("/login");
128+
}, [isUserLoading, user, supabase, router]);
129+
125130
if (isUserLoading) {
126131
return <LoadingBox className="h-[80vh]" />;
127132
}
128133

129-
if (!user || !supabase) {
130-
router.push("/login");
131-
return null;
132-
}
133-
134134
const allCredentials = providers
135135
? Object.values(providers).flatMap((provider) =>
136136
provider.savedCredentials

autogpt_platform/frontend/src/app/(platform)/reset_password/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {
1717
FormMessage,
1818
} from "@/components/ui/form";
1919
import { Input } from "@/components/ui/input";
20-
import useSupabase from "@/hooks/useSupabase";
20+
import useSupabase from "@/lib/supabase/useSupabase";
2121
import { sendEmailFormSchema, changePasswordFormSchema } from "@/types/auth";
2222
import { zodResolver } from "@hookform/resolvers/zod";
2323
import { useCallback, useState } from "react";

autogpt_platform/frontend/src/app/(platform)/signup/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { useCallback, useState } from "react";
1717
import { useRouter } from "next/navigation";
1818
import Link from "next/link";
1919
import { Checkbox } from "@/components/ui/checkbox";
20-
import useSupabase from "@/hooks/useSupabase";
20+
import useSupabase from "@/lib/supabase/useSupabase";
2121
import LoadingBox from "@/components/ui/loading";
2222
import {
2323
AuthCard,

autogpt_platform/frontend/src/components/ProfileDropdown.tsx

Lines changed: 0 additions & 55 deletions
This file was deleted.

autogpt_platform/frontend/src/components/RoleBasedAccess.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// components/RoleBasedAccess.tsx
2-
import useSupabase from "@/hooks/useSupabase";
2+
import useSupabase from "@/lib/supabase/useSupabase";
33
import React from "react";
44

55
interface RoleBasedAccessProps {

autogpt_platform/frontend/src/components/agptui/ProfileInfoForm.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { Button } from "./Button";
99
import { IconPersonFill } from "@/components/ui/icons";
1010
import { ProfileDetails } from "@/lib/autogpt-server-api/types";
1111
import { Separator } from "@/components/ui/separator";
12-
import useSupabase from "@/hooks/useSupabase";
12+
import useSupabase from "@/lib/supabase/useSupabase";
1313
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
1414

1515
export const ProfileInfoForm = ({ profile }: { profile: ProfileDetails }) => {

autogpt_platform/frontend/src/components/agptui/ProfilePopoutMenuLogoutButton.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
"use client";
2-
3-
import { logout } from "@/app/(platform)/login/actions";
2+
import useSupabase from "@/lib/supabase/useSupabase";
43
import { IconLogOut } from "@/components/ui/icons";
54

65
export const ProfilePopoutMenuLogoutButton = () => {
6+
const supabase = useSupabase();
77
return (
88
<div
99
className="inline-flex w-full items-center justify-start gap-2.5"
10-
onClick={() => logout()}
10+
onClick={() => supabase.logOut()}
1111
role="button"
1212
tabIndex={0}
1313
>

autogpt_platform/frontend/src/components/integrations/credentials-provider.tsx

Lines changed: 41 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { createContext, useCallback, useEffect, useState } from "react";
2+
import useSupabase from "@/lib/supabase/useSupabase";
13
import {
24
APIKeyCredentials,
35
CredentialsDeleteNeedConfirmationResponse,
@@ -8,7 +10,6 @@ import {
810
UserPasswordCredentials,
911
} from "@/lib/autogpt-server-api";
1012
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
11-
import { createContext, useCallback, useEffect, useState } from "react";
1213

1314
// Get keys from CredentialsProviderName type
1415
const CREDENTIALS_PROVIDER_NAMES = Object.values(
@@ -102,6 +103,7 @@ export default function CredentialsProvider({
102103
}) {
103104
const [providers, setProviders] =
104105
useState<CredentialsProvidersContextType | null>(null);
106+
const { isLoggedIn } = useSupabase();
105107
const api = useBackendAPI();
106108

107109
const addCredentials = useCallback(
@@ -202,48 +204,50 @@ export default function CredentialsProvider({
202204
);
203205

204206
useEffect(() => {
205-
api.isAuthenticated().then((isAuthenticated) => {
206-
if (!isAuthenticated) return;
207+
if (!isLoggedIn) {
208+
if (isLoggedIn == false) setProviders(null);
209+
return;
210+
}
207211

208-
api.listCredentials().then((response) => {
209-
const credentialsByProvider = response.reduce(
210-
(acc, cred) => {
211-
if (!acc[cred.provider]) {
212-
acc[cred.provider] = [];
213-
}
214-
acc[cred.provider].push(cred);
215-
return acc;
216-
},
217-
{} as Record<CredentialsProviderName, CredentialsMetaResponse[]>,
218-
);
212+
api.listCredentials().then((response) => {
213+
const credentialsByProvider = response.reduce(
214+
(acc, cred) => {
215+
if (!acc[cred.provider]) {
216+
acc[cred.provider] = [];
217+
}
218+
acc[cred.provider].push(cred);
219+
return acc;
220+
},
221+
{} as Record<CredentialsProviderName, CredentialsMetaResponse[]>,
222+
);
219223

220-
setProviders((prev) => ({
221-
...prev,
222-
...Object.fromEntries(
223-
CREDENTIALS_PROVIDER_NAMES.map((provider) => [
224+
setProviders((prev) => ({
225+
...prev,
226+
...Object.fromEntries(
227+
CREDENTIALS_PROVIDER_NAMES.map((provider) => [
228+
provider,
229+
{
224230
provider,
225-
{
226-
provider,
227-
providerName: providerDisplayNames[provider],
228-
savedCredentials: credentialsByProvider[provider] ?? [],
229-
oAuthCallback: (code: string, state_token: string) =>
230-
oAuthCallback(provider, code, state_token),
231-
createAPIKeyCredentials: (
232-
credentials: APIKeyCredentialsCreatable,
233-
) => createAPIKeyCredentials(provider, credentials),
234-
createUserPasswordCredentials: (
235-
credentials: UserPasswordCredentialsCreatable,
236-
) => createUserPasswordCredentials(provider, credentials),
237-
deleteCredentials: (id: string, force: boolean = false) =>
238-
deleteCredentials(provider, id, force),
239-
} satisfies CredentialsProviderData,
240-
]),
241-
),
242-
}));
243-
});
231+
providerName: providerDisplayNames[provider],
232+
savedCredentials: credentialsByProvider[provider] ?? [],
233+
oAuthCallback: (code: string, state_token: string) =>
234+
oAuthCallback(provider, code, state_token),
235+
createAPIKeyCredentials: (
236+
credentials: APIKeyCredentialsCreatable,
237+
) => createAPIKeyCredentials(provider, credentials),
238+
createUserPasswordCredentials: (
239+
credentials: UserPasswordCredentialsCreatable,
240+
) => createUserPasswordCredentials(provider, credentials),
241+
deleteCredentials: (id: string, force: boolean = false) =>
242+
deleteCredentials(provider, id, force),
243+
} satisfies CredentialsProviderData,
244+
]),
245+
),
246+
}));
244247
});
245248
}, [
246249
api,
250+
isLoggedIn,
247251
createAPIKeyCredentials,
248252
createUserPasswordCredentials,
249253
deleteCredentials,

autogpt_platform/frontend/src/components/onboarding/onboarding-provider.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"use client";
2-
import useSupabase from "@/hooks/useSupabase";
2+
import useSupabase from "@/lib/supabase/useSupabase";
33
import { OnboardingStep, UserOnboarding } from "@/lib/autogpt-server-api";
44
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
55
import { usePathname, useRouter } from "next/navigation";

0 commit comments

Comments
 (0)