Fix case where user can get stuck in a deleted organization if they were prev logged into it

This commit is contained in:
Tuan Dang
2025-02-18 18:35:22 -08:00
parent 8518fed726
commit 823bf134dd
8 changed files with 114 additions and 81 deletions
+27 -24
View File
@@ -24,10 +24,10 @@ import {
User,
UserEnc
} from "./types";
import { queryClient } from "@app/hooks/api/reactQuery";
export const fetchUserDetails = async () => {
const { data } = await apiRequest.get<{ user: User & UserEnc }>("/api/v1/user");
return data.user;
};
@@ -278,30 +278,33 @@ export const useRegisterUserAction = () => {
});
};
export const useLogoutUser = (keepQueryClient?: boolean) => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async () => {
await apiRequest.post("/api/v1/auth/logout");
},
onSuccess: () => {
setAuthToken("");
// Delete the cookie by not setting a value; Alternatively clear the local storage
localStorage.removeItem("protectedKey");
localStorage.removeItem("protectedKeyIV");
localStorage.removeItem("protectedKeyTag");
localStorage.removeItem("publicKey");
localStorage.removeItem("encryptedPrivateKey");
localStorage.removeItem("iv");
localStorage.removeItem("tag");
localStorage.removeItem("PRIVATE_KEY");
localStorage.removeItem("orgData.id");
sessionStorage.removeItem(SessionStorageKeys.CLI_TERMINAL_TOKEN);
export const logoutUser = async () => {
await apiRequest.post("/api/v1/auth/logout");
};
if (!keepQueryClient) {
queryClient.clear();
}
}
// Utility function to clear session storage and query cache
export const clearSession = (keepQueryClient?: boolean) => {
setAuthToken(""); // Clear authentication token
localStorage.removeItem("protectedKey");
localStorage.removeItem("protectedKeyIV");
localStorage.removeItem("protectedKeyTag");
localStorage.removeItem("publicKey");
localStorage.removeItem("encryptedPrivateKey");
localStorage.removeItem("iv");
localStorage.removeItem("tag");
localStorage.removeItem("PRIVATE_KEY");
localStorage.removeItem("orgData.id");
sessionStorage.removeItem(SessionStorageKeys.CLI_TERMINAL_TOKEN);
if (!keepQueryClient) {
queryClient.clear(); // Clear React Query cache
}
};
export const useLogoutUser = (keepQueryClient?: boolean) => {
return useMutation({
mutationFn: logoutUser,
onSuccess: () => clearSession(keepQueryClient)
});
};
@@ -33,6 +33,7 @@ export const LoginPage = () => {
console.log("Error - Not logged in yet");
}
};
if (isLoggedIn()) {
handleRedirects();
}
@@ -250,20 +250,24 @@ export const SignupInvitePage = () => {
setStep(2);
} else {
const redirectExistingUser = async () => {
const { token: mfaToken, isMfaEnabled } = await selectOrganization({
organizationId
});
try {
const { token: mfaToken, isMfaEnabled } = await selectOrganization({
organizationId
});
if (isMfaEnabled) {
SecurityClient.setMfaToken(mfaToken);
toggleShowMfa.on();
setMfaSuccessCallback(() => redirectExistingUser);
return;
if (isMfaEnabled) {
SecurityClient.setMfaToken(mfaToken);
toggleShowMfa.on();
setMfaSuccessCallback(() => redirectExistingUser);
return;
}
// user will be redirected to dashboard
// if not logged in gets kicked out to login
await navigateUserToOrg(navigate, organizationId);
} catch (err) {
navigate({ to: "/login" });
}
// user will be redirected to dashboard
// if not logged in gets kicked out to login
await navigateUserToOrg(navigate, organizationId);
};
await redirectExistingUser();
@@ -5,12 +5,15 @@ import { ROUTE_PATHS } from "@app/const/routes";
import { userKeys } from "@app/hooks/api";
import { authKeys, fetchAuthToken } from "@app/hooks/api/auth/queries";
import { fetchUserDetails } from "@app/hooks/api/users/queries";
import { AxiosError } from "axios";
import { clearSession, logoutUser } from "@app/hooks/api/users/queries";
export const Route = createFileRoute("/_authenticate")({
beforeLoad: async ({ context, location }) => {
if (!context.serverConfig.initialized) {
throw redirect({ to: "/admin/signup" });
}
const data = await context.queryClient
.ensureQueryData({
queryKey: authKeys.getAuthToken,
@@ -27,14 +30,31 @@ export const Route = createFileRoute("/_authenticate")({
});
});
if (!data.organizationId && location.pathname !== ROUTE_PATHS.Auth.PasswordSetupPage.path && location.pathname !== "/organization/none") {
if (
!data.organizationId &&
location.pathname !== ROUTE_PATHS.Auth.PasswordSetupPage.path &&
location.pathname !== "/organization/none"
) {
throw redirect({ to: "/login/select-organization" });
}
const user = await context.queryClient.ensureQueryData({
queryKey: userKeys.getUser,
queryFn: fetchUserDetails
});
const user = await context.queryClient
.ensureQueryData({
queryKey: userKeys.getUser,
queryFn: fetchUserDetails
})
.catch(async (error) => {
const err = error as AxiosError;
if (err.response?.status === 403) {
// (dangtony98): this edge-case can occur if the user's token corresponds to an organization
// that has been deleted for which we must clear the refresh token in http-only cookie
clearSession(true);
await logoutUser();
throw redirect({
to: "/login"
});
}
});
return { organizationId: data.organizationId as string, isAuthenticated: true, user };
}