Skip to content

Commit

Permalink
Merge pull request #341 from jeafreezy/fix/auth-flow
Browse files Browse the repository at this point in the history
Fix/auth flow
  • Loading branch information
kshitijrajsharma authored Feb 25, 2025
2 parents 9b048c5 + c91af6b commit 3bfe980
Show file tree
Hide file tree
Showing 94 changed files with 1,782 additions and 924 deletions.
2 changes: 2 additions & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"@eslint/js": "^9.9.0",
"@tailwindcss/typography": "^0.5.15",
"@tanstack/eslint-plugin-query": "^5.58.1",
"@testing-library/react": "^16.2.0",
"@types/geojson": "^7946.0.14",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
Expand All @@ -64,6 +65,7 @@
"eslint-plugin-react-refresh": "^0.4.9",
"eslint-plugin-tailwindcss": "^3.17.5",
"globals": "^15.9.0",
"jsdom": "^26.0.0",
"postcss": "^8.4.47",
"prettier": "3.3.3",
"tailwindcss": "^3.4.12",
Expand Down
417 changes: 415 additions & 2 deletions frontend/pnpm-lock.yaml

Large diffs are not rendered by default.

41 changes: 18 additions & 23 deletions frontend/src/app/providers/auth-provider.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,15 @@
import React, {
createContext,
useContext,
useEffect,
useState
} from 'react';
import { apiClient } from '@/services/api-client';
import { authService } from '@/services';
import { HOT_FAIR_LOCAL_STORAGE_ACCESS_TOKEN_KEY, HOT_FAIR_LOGIN_SUCCESSFUL_SESSION_KEY, HOT_FAIR_SESSION_REDIRECT_KEY } from '@/config';
import { showErrorToast, showSuccessToast } from '@/utils';
import { TUser } from '@/types/api';
import { useLocalStorage, useSessionStorage } from '@/hooks/use-storage';
import React, { createContext, useContext, useEffect, useState } from "react";
import { apiClient } from "@/services/api-client";
import { authService } from "@/services";
import {
TOAST_NOTIFICATIONS,
} from "@/constants";


HOT_FAIR_LOCAL_STORAGE_ACCESS_TOKEN_KEY,
HOT_FAIR_LOGIN_SUCCESSFUL_SESSION_KEY,
HOT_FAIR_SESSION_REDIRECT_KEY,
} from "@/config";
import { showErrorToast, showSuccessToast } from "@/utils";
import { TUser } from "@/types/api";
import { useLocalStorage, useSessionStorage } from "@/hooks/use-storage";
import { APPLICATION_ROUTES, TOAST_NOTIFICATIONS } from "@/constants";

type TAuthContext = {
token: string;
Expand All @@ -41,11 +36,8 @@ type AuthProviderProps = {

export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
const { getValue, setValue, removeValue } = useLocalStorage();
const {
getSessionValue,
removeSessionValue,
setSessionValue,
} = useSessionStorage();
const { getSessionValue, removeSessionValue, setSessionValue } =
useSessionStorage();

const [token, setToken] = useState<string | undefined>(
getValue(HOT_FAIR_LOCAL_STORAGE_ACCESS_TOKEN_KEY),
Expand All @@ -58,7 +50,6 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
// Set token globally to eliminate the need to rewrite it
apiClient.defaults.headers.common["access-token"] = token ? `${token}` : null;


const handleRedirection = () => {
const redirectTo = getSessionValue(HOT_FAIR_SESSION_REDIRECT_KEY);
if (redirectTo) {
Expand Down Expand Up @@ -113,7 +104,6 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
}
}, [token]);


/**
* Clean up and logout.
*/
Expand All @@ -136,6 +126,11 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
setToken(data.access_token);
} catch (error) {
showErrorToast(error, TOAST_NOTIFICATIONS.authenticationFailed);
// Delay for 3 seconds, incase it's the network speed.
// Otherwise, redirect the user back to the home page.
setTimeout(() => {
window.location.href = APPLICATION_ROUTES.HOMEPAGE;
}, 3000);
}
};

Expand Down
49 changes: 26 additions & 23 deletions frontend/src/app/providers/models-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@ import {
APPLICATION_ROUTES,
MODELS_BASE,
MODELS_ROUTES,
TOAST_NOTIFICATIONS
} from '@/constants';
import { BASE_MODELS, TrainingDatasetOption, TrainingType } from '@/enums';
import { HOT_FAIR_MODEL_CREATION_SESSION_STORAGE_KEY } from '@/config';
import { LngLatBoundsLike } from 'maplibre-gl';
import { useCreateTrainingDataset } from '@/features/model-creation/hooks/use-training-datasets';
import { useGetTrainingDataset } from '@/features/models/hooks/use-dataset';
import { useLocation, useNavigate, useParams } from 'react-router-dom';
import { useModelDetails } from '@/features/models/hooks/use-models';
import { UseMutationResult } from '@tanstack/react-query';
import { useSessionStorage } from '@/hooks/use-storage';
TOAST_NOTIFICATIONS,
} from "@/constants";
import { BASE_MODELS, TrainingDatasetOption, TrainingType } from "@/enums";
import { HOT_FAIR_MODEL_CREATION_SESSION_STORAGE_KEY } from "@/config";
import { LngLatBoundsLike } from "maplibre-gl";
import { useCreateTrainingDataset } from "@/features/model-creation/hooks/use-training-datasets";
import { useGetTrainingDataset } from "@/features/models/hooks/use-dataset";
import { useLocation, useNavigate, useParams } from "react-router-dom";
import { useModelDetails } from "@/features/models/hooks/use-models";
import { UseMutationResult } from "@tanstack/react-query";
import { useSessionStorage } from "@/hooks/use-storage";

import {
TTrainingAreaFeature,
Expand Down Expand Up @@ -231,8 +231,8 @@ const ModelsContext = createContext<{
validateEditMode: boolean;
}>({
formData: initialFormState,
setFormData: () => { },
handleChange: () => { },
setFormData: () => {},
handleChange: () => {},
createNewTrainingDatasetMutation: {} as UseMutationResult<
TTrainingDataset,
Error,
Expand All @@ -247,13 +247,13 @@ const ModelsContext = createContext<{
>,
hasLabeledTrainingAreas: false,
hasAOIsWithGeometry: false,
resetState: () => { },
resetState: () => {},
isEditMode: false,
modelId: "",
getFullPath: () => "",
handleModelCreationAndUpdate: () => { },
handleModelCreationAndUpdate: () => {},
trainingDatasetCreationInProgress: false,
handleTrainingDatasetCreation: () => { },
handleTrainingDatasetCreation: () => {},
validateEditMode: false,
});

Expand All @@ -263,14 +263,16 @@ export const ModelsProvider: React.FC<{
const navigate = useNavigate();
const { pathname } = useLocation();
const { modelId } = useParams();
const { getSessionValue, setSessionValue, removeSessionValue } = useSessionStorage();
const { getSessionValue, setSessionValue, removeSessionValue } =
useSessionStorage();

const storedFormData = getSessionValue(HOT_FAIR_MODEL_CREATION_SESSION_STORAGE_KEY);
const storedFormData = getSessionValue(
HOT_FAIR_MODEL_CREATION_SESSION_STORAGE_KEY,
);
const [formData, setFormData] = useState<typeof initialFormState>(
storedFormData ? JSON.parse(storedFormData) : initialFormState
storedFormData ? JSON.parse(storedFormData) : initialFormState,
);


const handleChange = (
field: string,
value:
Expand All @@ -283,7 +285,10 @@ export const ModelsProvider: React.FC<{
) => {
setFormData((prev) => {
const updatedData = { ...prev, [field]: value };
setSessionValue(HOT_FAIR_MODEL_CREATION_SESSION_STORAGE_KEY, JSON.stringify(updatedData));
setSessionValue(
HOT_FAIR_MODEL_CREATION_SESSION_STORAGE_KEY,
JSON.stringify(updatedData),
);
return updatedData;
});
};
Expand Down Expand Up @@ -362,7 +367,6 @@ export const ModelsProvider: React.FC<{
};
}, []);


const resetState = () => {
removeSessionValue(HOT_FAIR_MODEL_CREATION_SESSION_STORAGE_KEY);
setFormData(initialFormState);
Expand Down Expand Up @@ -456,7 +460,6 @@ export const ModelsProvider: React.FC<{
(aoi: TTrainingAreaFeature) => aoi.geometry === null,
).length === 0;


const handleTrainingDatasetCreation = () => {
createNewTrainingDatasetMutation.mutate({
source_imagery: formData.tmsURL,
Expand Down
21 changes: 17 additions & 4 deletions frontend/src/app/router.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { APPLICATION_ROUTES } from '@/constants';
import { MainErrorFallback } from '@/components/errors';
import { ModelFormsLayout, RootLayout } from '@/layouts';
import { ProtectedRoute } from '@/app/routes/protected-route';
import { APPLICATION_ROUTES } from "@/constants";
import { MainErrorFallback } from "@/components/errors";
import { ModelFormsLayout, RootLayout } from "@/layouts";
import { ProtectedRoute } from "@/app/routes/protected-route";
import {
Navigate,
RouterProvider,
Expand Down Expand Up @@ -321,6 +321,19 @@ const router = createBrowserRouter([
* User account routes ends
*/

/**
* Auth route
*/
{
path: APPLICATION_ROUTES.AUTH_CALLBACK,
lazy: async () => {
const { AuthenticationCallbackPage } = await import(
"@/app/routes/authenticate"
);
return { Component: AuthenticationCallbackPage };
},
},

/**
* 404 route
*/
Expand Down
32 changes: 32 additions & 0 deletions frontend/src/app/routes/authenticate.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { AuthenticationModal } from "@/components/auth";
import { Head } from "@/components/seo";
import { AUTH_PAGE_AND_MODAL_CONTENT } from "@/constants/ui-contents/auth-content";
import { useEffect } from "react";
import { useNavigate } from "react-router-dom";
import { useAuth } from "@/app/providers/auth-provider";
import { APPLICATION_ROUTES } from "@/constants";

export const AuthenticationCallbackPage = () => {
const navigate = useNavigate();
const { isAuthenticated } = useAuth();

useEffect(() => {
const params = new URLSearchParams(window.location.search);
const code = params.get("code");
const state = params.get("state");
/**
* Redirect any visit to this page back to the homepage,
* if there is no code and state in the url, or if the user is authenticated already.
*/
if (isAuthenticated || !code || !state) {
navigate(APPLICATION_ROUTES.HOMEPAGE);
}
}, [isAuthenticated]);

return (
<>
<Head title={AUTH_PAGE_AND_MODAL_CONTENT.pageTitle}></Head>
<AuthenticationModal callbackPage isOpen />
</>
);
};
2 changes: 1 addition & 1 deletion frontend/src/app/routes/learn.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { PageUnderConstruction } from '@/components/errors';
import { PageUnderConstruction } from "@/components/errors";
// import { Button } from "@/components/ui/button";
// import { ExternalLinkIcon, YouTubePlayCircleIcon } from "@/components/ui/icons";
// import { fAIrValues } from "@/assets/svgs";
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/app/routes/models/model-details-form.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ModelDetailsForm } from '@/features/model-creation/components/';
import { ModelDetailsForm } from "@/features/model-creation/components/";

export const ModelDetailsFormPage = () => {
return (
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/app/routes/models/training-dataset.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { TrainingDatasetForm } from '@/features/model-creation/components';
import { TrainingDatasetForm } from "@/features/model-creation/components";

export const ModelTrainingDatasetPage = () => {
return (
Expand Down
18 changes: 10 additions & 8 deletions frontend/src/app/routes/protected-route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@ import { Head } from "@/components/seo";
import { SHARED_CONTENT } from "@/constants";
import { ShieldIcon } from "@/components/ui/icons";
import { useAuth } from "@/app/providers/auth-provider";
import { useLogin } from "@/hooks/use-login";
import { useLocation, useNavigate } from "react-router-dom";

type ProtectedRouteProps = {
children: React.ReactNode;
};

export const ProtectedRoute: React.FC<ProtectedRouteProps> = ({ children }) => {
const { isAuthenticated } = useAuth();
const { handleLogin, loading } = useLogin();

const navigate = useNavigate();
const location = useLocation();
if (!isAuthenticated) {
return (
<>
Expand All @@ -33,13 +33,15 @@ export const ProtectedRoute: React.FC<ProtectedRouteProps> = ({ children }) => {
</div>
<Button
variant="primary"
onClick={handleLogin}
onClick={() => {
/*
* Set the `backgroundLocation` in location state so that when we open the authentication modal we still see the current page in the background.
*/
navigate(location, { state: { backgroundLocation: location } });
}}
className="max-w-[300px]"
spinner={loading}
>
{loading
? SHARED_CONTENT.loginButtonLoading
: SHARED_CONTENT.protectedPage.ctaButton}
{SHARED_CONTENT.protectedPage.ctaButton}
</Button>
</section>
</>
Expand Down
3 changes: 1 addition & 2 deletions frontend/src/app/routes/resources.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { PageUnderConstruction } from '@/components/errors';
import { PageUnderConstruction } from "@/components/errors";
// import { ChevronDownIcon } from "@/components/ui/icons";
// import { FAQs, SectionHeader } from "@/components/shared";
// import { Head } from "@/components/seo";
Expand All @@ -9,7 +9,6 @@ import { PageUnderConstruction } from '@/components/errors';
// import { TArticle } from "@/types";
// import { truncateString } from "@/utils";


export const ResourcesPage = () => {
return (
<PageUnderConstruction />
Expand Down
Loading

0 comments on commit 3bfe980

Please sign in to comment.