Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: remove terra functionality from authprovider into its own provider (#178) #240

Merged
merged 24 commits into from
Jan 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
c60f81c
feat: remove terra functionality from authprovider into its own provi…
Oct 24, 2024
e9d8af4
refactor: google sign in and next auth directory structure (#178)
Oct 30, 2024
7fe5695
refactor: google sign in with active terra profile status (#178)
Oct 30, 2024
50da406
feat: update profile hook (#178)
Oct 30, 2024
f02edff
feat: updated authentication pending status (#178)
Oct 31, 2024
7816b47
feat: updated route history hook (#178)
Oct 31, 2024
9487249
feat: enable more reliable timeout handling with nextauth (#178)
hunterckx Oct 31, 2024
66373fe
fix: jsdoc profile hook (#178)
Nov 4, 2024
337ef5f
refactor: auth status settled value (#178)
Nov 4, 2024
f82d40e
refactor: rename DONE authentication status to SETTLED (#178)
hunterckx Nov 4, 2024
8e4cccb
test: add tests for `useSessionAuth` and `getProfileStatus` (#178)
hunterckx Nov 5, 2024
3505d81
refactor: linting (#178)
Nov 5, 2024
00a89d1
feat: added test for use route history hook (#178)
Nov 6, 2024
1ca672c
feat: test linting (#178)
Nov 6, 2024
0de5eb6
feat: added test use session active (#178)
Nov 6, 2024
78fdcc9
feat: added terra profile provider test (#178)
Nov 6, 2024
f450367
feat: linting (#178)
Nov 6, 2024
54c41d7
feat: fix use route history test (#178)
Nov 7, 2024
a383043
fix: audit fix (#178)
Nov 21, 2024
05fa1ed
feat: added session controller to google sign in for non-terra relate…
Jan 4, 2025
c076da6
feat: updated google sign in terra related authentication with auth s…
Jan 4, 2025
1ecf195
fix: logout; entity detail page needs the page to refresh due to comp…
Jan 5, 2025
25ede59
feat: updated next auth session controller to handle both auth and pr…
Jan 5, 2025
3a63bfa
feat: linting (#178)
Jan 6, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
277 changes: 255 additions & 22 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,12 @@
"isomorphic-dompurify": "0.24.0",
"ky": "^1.7.2",
"next": "^14.1.0",
"next-auth": "^4.24.7",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-dropzone": "^14.2.3",
"react-gtm-module": "2.0.11",
"react-idle-timer": "^5.6.2",
"react-idle-timer": "^5.7.2",
"react-window": "1.8.9",
"uuid": "8.3.2",
"validate.js": "^0.13.1"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React, { Fragment, useEffect } from "react";
import { authComplete } from "../../../../../../providers/authentication/auth/dispatch";
import { useAuth } from "../../../../../../providers/authentication/auth/hook";
import { authenticationComplete } from "../../../../../../providers/authentication/authentication/dispatch";
import { useAuthentication } from "../../../../../../providers/authentication/authentication/hook";
import { updateCredentials } from "../../../../../../providers/authentication/credentials/dispatch";
import { useCredentials } from "../../../../../../providers/authentication/credentials/hook";
import { SessionControllerProps } from "./types";

export function SessionController({
children,
token,
}: SessionControllerProps): JSX.Element {
const { authDispatch } = useAuth();
const {
authenticationDispatch,
authenticationState: { profile },
} = useAuthentication();
const { credentialsDispatch } = useCredentials();

useEffect(() => {
// Dispatch only when profile is available:
// - Login errors are managed by the login service.
// - Logout operations handle resetting credentials, authentication and auth state.
if (!profile) return;
credentialsDispatch?.(updateCredentials(token)); // Release credentials.
authenticationDispatch?.(authenticationComplete()); // Authentication `status` is "SETTLED".
authDispatch?.(authComplete({ isAuthenticated: true })); // Auth `status` is "SETTLED", and `isAuthenticated` is "true".
}, [
authDispatch,
authenticationDispatch,
credentialsDispatch,
profile,
token,
]);

return <Fragment>{children}</Fragment>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { ReactNode } from "react";
import { TokenState } from "../../../../../../providers/authentication/token/types";

export interface SessionControllerProps {
children: ReactNode | ReactNode[];
token: TokenState["token"];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { useSession } from "next-auth/react";
import React, { Fragment, useEffect } from "react";
import { updateAuthState } from "../../../../../../providers/authentication/auth/dispatch";
import { useAuth } from "../../../../../../providers/authentication/auth/hook";
import { updateAuthentication } from "../../../../../../providers/authentication/authentication/dispatch";
import { useAuthentication } from "../../../../../../providers/authentication/authentication/hook";
import { SessionControllerProps } from "./types";
import { mapAuth, mapAuthentication } from "./utils";

export function SessionController({
children,
}: SessionControllerProps): JSX.Element {
const { authDispatch } = useAuth();
const { authenticationDispatch } = useAuthentication();
const session = useSession();

useEffect(() => {
authDispatch?.(updateAuthState(mapAuth(session)));
authenticationDispatch?.(updateAuthentication(mapAuthentication(session)));
}, [authDispatch, authenticationDispatch, session]);

return <Fragment>{children}</Fragment>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { SessionContextValue } from "next-auth/react";
import { AUTH_STATUS } from "../../../../../../providers/authentication/auth/types";
import { AUTHENTICATION_STATUS } from "../../../../../../providers/authentication/authentication/types";

export const AUTH_STATUS_MAP: Record<
SessionContextValue["status"],
AUTH_STATUS
> = {
authenticated: AUTH_STATUS.SETTLED,
loading: AUTH_STATUS.PENDING,
unauthenticated: AUTH_STATUS.SETTLED,
};

export const AUTHENTICATION_STATUS_MAP: Record<
SessionContextValue["status"],
AUTHENTICATION_STATUS
> = {
authenticated: AUTHENTICATION_STATUS.SETTLED,
loading: AUTHENTICATION_STATUS.PENDING,
unauthenticated: AUTHENTICATION_STATUS.SETTLED,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { ReactNode } from "react";

export interface SessionControllerProps {
children: ReactNode | ReactNode[];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { Session } from "next-auth";
import { SessionContextValue } from "next-auth/react";
import {
AUTH_STATUS,
UpdateAuthStatePayload,
} from "../../../../../../providers/authentication/auth/types";
import {
AUTHENTICATION_STATUS,
UpdateAuthenticationPayload,
UserProfile,
} from "../../../../../../providers/authentication/authentication/types";
import { AUTH_STATUS_MAP, AUTHENTICATION_STATUS_MAP } from "./constants";

/**
* Returns the auth state from the session context.
* @param session - Session context value.
* @returns auth state.
*/
export function mapAuth(session: SessionContextValue): UpdateAuthStatePayload {
return {
isAuthenticated: session.status === "authenticated",
status: mapStatus(session.status, AUTH_STATUS_MAP, AUTH_STATUS.SETTLED),
};
}

/**
* Returns the authentication profile and status from the session context.
* @param session - Session context value.
* @returns authentication profile and status.
*/
export function mapAuthentication(
session: SessionContextValue
): UpdateAuthenticationPayload {
return {
profile: mapProfile(session.data),
status: mapStatus(
session.status,
AUTHENTICATION_STATUS_MAP,
AUTHENTICATION_STATUS.SETTLED
),
};
}

/**
* Maps the session data to a user profile.
* @param sessionData - Session data.
* @returns user profile.
*/
function mapProfile(sessionData: Session | null): UserProfile | undefined {
if (!sessionData) return;
const { user } = sessionData;
if (!user) return;
const { email, image, name } = user;
return {
email: email || "",
image: image || "",
name: name || "",
};
}

/**
* Returns the auth or authentication status <S> based on the session status.
* @param status - Session status.
* @param statusBySessionStatus - Map of session status to auth or authentication status.
* @param defaultStatus - Default auth or authentication status.
* @returns auth or authentication status.
*/
function mapStatus<S>(
status: SessionContextValue["status"],
statusBySessionStatus: Record<SessionContextValue["status"], S>,
defaultStatus: S
): S {
return statusBySessionStatus[status] || defaultStatus;
}
7 changes: 4 additions & 3 deletions src/components/ComponentCreator/ComponentCreator.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React from "react";
import { ComponentsConfig, ViewContext } from "../../config/entities";
import { useAuthentication } from "../../hooks/useAuthentication/useAuthentication";
import { useConfig } from "../../hooks/useConfig";
import { useExploreState } from "../../hooks/useExploreState";
import { useFileManifestState } from "../../hooks/useFileManifestState";
import { useSystemStatus } from "../../hooks/useSystemStatus";
import { useAuth } from "../../providers/authentication/auth/hook";

export interface ComponentCreatorProps<T> {
components: ComponentsConfig;
Expand All @@ -27,7 +27,9 @@ export const ComponentCreator = <T,>({
response,
viewContext,
}: ComponentCreatorProps<T>): JSX.Element => {
const { authenticationStatus, isAuthenticated } = useAuthentication();
const {
authState: { isAuthenticated },
} = useAuth();
const { config, entityConfig } = useConfig();
const { exploreState } = useExploreState();
const { fileManifestState } = useFileManifestState();
Expand All @@ -53,7 +55,6 @@ export const ComponentCreator = <T,>({
? c.viewBuilder(response, {
...viewContext,
authState: {
authenticationStatus,
isAuthenticated,
},
entityConfig,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React, { ReactNode } from "react";
import { LoginStatus } from "../../../../../../../../../../hooks/useAuthentication/common/entities";
import { useAuthentication } from "../../../../../../../../../../hooks/useAuthentication/useAuthentication";
import { TerraResponse } from "../../../../../../../../../../hooks/useAuthentication/useFetchTerraProfile";
import { useConfig } from "../../../../../../../../../../hooks/useConfig";
import { useTerraProfile } from "../../../../../../../../../../providers/authentication/terra/hook";
import { LoginStatus } from "../../../../../../../../../../providers/authentication/terra/hooks/common/entities";
import { TerraResponse } from "../../../../../../../../../../providers/authentication/terra/hooks/useFetchTerraProfile";
import { ButtonPrimary } from "../../../../../../../../../common/Button/components/ButtonPrimary/buttonPrimary";
import {
ANCHOR_TARGET,
Expand All @@ -23,7 +23,7 @@ export const AcceptTerraTOS = ({
}: AcceptTerraTOSProps): JSX.Element | null => {
const { config } = useConfig();
const { exportToTerraUrl } = config;
const { terraProfileLoginStatus } = useAuthentication();
const { terraProfileLoginStatus } = useTerraProfile();
const isTOSAccepted = isTermsOfServiceAccepted(terraProfileLoginStatus);

const onOpenTerra = (): void => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from "react";
import {
expireTimeInSeconds,
useAuthenticationNIHExpiry,
} from "../../../../../../../../hooks/useAuthentication/useAuthenticationNIHExpiry";
} from "../../../../../../../../hooks/authentication/terra/useAuthenticationNIHExpiry";
import { Alert } from "../../../../../../../common/Alert/alert";
import { ALERT_PROPS } from "../../../../../../../common/Alert/constants";
import { FluidPaper } from "../../../../../../../common/Paper/paper.styles";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import {
ONBOARDING_STEP,
OnboardingStatus,
useAuthenticationForm,
} from "../../../../../../hooks/useAuthentication/useAuthenticationForm";
} from "../../../../../../hooks/authentication/terra/useAuthenticationForm";
import { useAuth } from "../../../../../../providers/authentication/auth/hook";
import { AUTH_STATUS } from "../../../../../../providers/authentication/auth/types";
import { TEXT_BODY_400_2_LINES } from "../../../../../../theme/common/typography";
import {
FluidPaper,
Expand All @@ -17,11 +19,12 @@ import { CreateTerraAccount } from "./components/FormStep/components/CreateTerra
import { Section, SectionContent } from "./terraSetUpForm.styles";

export const TerraSetUpForm = (): JSX.Element | null => {
const { isComplete, isReady, onboardingStatusByStep } =
useAuthenticationForm();

if (!isReady) return null;

const {
authState: { isAuthenticated, status },
} = useAuth();
const { isComplete, onboardingStatusByStep } = useAuthenticationForm();
if (!isAuthenticated) return null;
if (status === AUTH_STATUS.PENDING) return null;
return isComplete ? null : (
<FluidPaper>
<GridPaper>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ import {
ButtonProps as MButtonProps,
IconButton as MIconButton,
IconButtonProps as MIconButtonProps,
Skeleton,
} from "@mui/material";
import { useRouter } from "next/router";
import React, { ElementType, useCallback } from "react";
import { useAuthentication } from "../../../../../../../../../../hooks/useAuthentication/useAuthentication";
import Router from "next/router";
import React, { ElementType } from "react";
import { useProfile } from "../../../../../../../../../../hooks/authentication/profile/useProfile";
import { ROUTE } from "../../../../../../../../../../routes/constants";
import { isNavigationLinkSelected } from "../../../Navigation/common/utils";
import { AuthenticationMenu } from "./components/AuthenticationMenu/authenticationMenu";
import { StyledButton } from "./components/Button/button.styles";

Expand All @@ -21,39 +24,38 @@ export const Authentication = ({
Button,
closeMenu,
}: AuthenticationProps): JSX.Element | null => {
const { isAuthenticated, requestAuthentication, userProfile } =
useAuthentication();
const router = useRouter();
const onLogout = useCallback((): void => {
location.href = router.basePath;
}, [router]);

const { isLoading, profile } = useProfile();
if (!authenticationEnabled) return null;

if (isLoading) return <Skeleton height={32} variant="circular" width={32} />;
if (profile) return <AuthenticationMenu profile={profile} />;
return (
<>
{isAuthenticated && userProfile ? (
<AuthenticationMenu onLogout={onLogout} userProfile={userProfile} />
) : (
<Button
onClick={(): void => {
requestAuthentication();
closeMenu();
}}
/>
)}
</>
<Button
onClick={async (): Promise<void> => {
await Router.push(ROUTE.LOGIN);
closeMenu();
}}
/>
);
};

/**
* Renders authentication button.
* @param props - Button props.
* @param pathname - Pathname.
* @returns button.
*/
export function renderButton(props: MButtonProps): JSX.Element {
export function renderButton(
props: MButtonProps,
pathname: string
): JSX.Element {
return (
<StyledButton startIcon={<LoginRoundedIcon />} variant="nav" {...props}>
<StyledButton
startIcon={<LoginRoundedIcon />}
variant={
isNavigationLinkSelected(pathname, [ROUTE.LOGIN]) ? "activeNav" : "nav"
}
{...props}
>
Sign in
</StyledButton>
);
Expand Down

This file was deleted.

Loading
Loading