From 82a5bd595d4e0bf9514e86d45b7cc51551b01855 Mon Sep 17 00:00:00 2001 From: GaelFerrand Date: Thu, 7 Nov 2024 09:06:39 +0100 Subject: [PATCH 1/9] feat: started looking at bug. WIP --- back/src/server.ts | 2 +- front/src/graphql-client.ts | 10 ++++++++++ front/src/login/Login.tsx | 11 +++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/back/src/server.ts b/back/src/server.ts index aaef6f8ab3..a31a8dadcf 100644 --- a/back/src/server.ts +++ b/back/src/server.ts @@ -269,7 +269,7 @@ export const sess: session.SessionOptions = { secure: false, httpOnly: true, domain: SESSION_COOKIE_HOST || UI_HOST, - maxAge: 24 * 3600 * 1000 + maxAge: 3000 } }; diff --git a/front/src/graphql-client.ts b/front/src/graphql-client.ts index ad40326b1a..6ae833bd67 100644 --- a/front/src/graphql-client.ts +++ b/front/src/graphql-client.ts @@ -7,6 +7,7 @@ import { import { onError } from "@apollo/client/link/error"; import { relayStylePagination } from "@apollo/client/utilities"; import { removeOrgId } from "./common/helper"; +import { localAuthService } from "./login/auth.service"; /** * Automatically erase `__typename` from variables @@ -48,6 +49,15 @@ const errorLink = onError(({ response, graphQLErrors }) => { // modify the response context to ignore the error // cf. https://www.apollographql.com/docs/react/data/error-handling/#ignoring-errors response!.errors = undefined; + + if ( + window.location.pathname !== "/" && + window.location.pathname !== "/login" + ) { + // Logout + localAuthService.locallySignOut(); + document?.forms["logout"]?.submit(); + } } } } diff --git a/front/src/login/Login.tsx b/front/src/login/Login.tsx index f18fe9b45e..cda10471b5 100644 --- a/front/src/login/Login.tsx +++ b/front/src/login/Login.tsx @@ -100,6 +100,16 @@ export default function Login() { ) : null; + const disconectedAlert = ( +
+ +
+ ); + return (
{createdAlert} + {disconectedAlert} {alert}
From 93c8e48ab1a8129393c9be1274d2a098b8e828a6 Mon Sep 17 00:00:00 2001 From: GaelFerrand Date: Thu, 7 Nov 2024 13:36:11 +0100 Subject: [PATCH 2/9] feat: added redirection to login page & displaying message --- front/src/graphql-client.ts | 5 ++++- front/src/login/Login.tsx | 19 ++++++++++--------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/front/src/graphql-client.ts b/front/src/graphql-client.ts index 6ae833bd67..336d5f276b 100644 --- a/front/src/graphql-client.ts +++ b/front/src/graphql-client.ts @@ -50,13 +50,16 @@ const errorLink = onError(({ response, graphQLErrors }) => { // cf. https://www.apollographql.com/docs/react/data/error-handling/#ignoring-errors response!.errors = undefined; + // If we catch an UNAUTHENTICATED exception at this point, + // the user session has probably expired. Redirect to login + // page with a hint in the URL to display a message if ( window.location.pathname !== "/" && window.location.pathname !== "/login" ) { // Logout localAuthService.locallySignOut(); - document?.forms["logout"]?.submit(); + window.location.href = "/login?session=expired"; } } } diff --git a/front/src/login/Login.tsx b/front/src/login/Login.tsx index cda10471b5..591281ec54 100644 --- a/front/src/login/Login.tsx +++ b/front/src/login/Login.tsx @@ -100,15 +100,16 @@ export default function Login() {
) : null; - const disconectedAlert = ( -
- -
- ); + const disconectedAlert = + queries["session"] === "expired" ? ( +
+ +
+ ) : null; return (
From 6dd3d8ef6f082ecd471285c9edce3ec77a9de3a5 Mon Sep 17 00:00:00 2001 From: GaelFerrand Date: Thu, 7 Nov 2024 13:40:30 +0100 Subject: [PATCH 3/9] fix: reverting session modification --- back/src/server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/back/src/server.ts b/back/src/server.ts index a31a8dadcf..aaef6f8ab3 100644 --- a/back/src/server.ts +++ b/back/src/server.ts @@ -269,7 +269,7 @@ export const sess: session.SessionOptions = { secure: false, httpOnly: true, domain: SESSION_COOKIE_HOST || UI_HOST, - maxAge: 3000 + maxAge: 24 * 3600 * 1000 } }; From 1350f958901ff63d4e9a8c259daa8c4ff225d1f5 Mon Sep 17 00:00:00 2001 From: GaelFerrand Date: Thu, 7 Nov 2024 14:41:32 +0100 Subject: [PATCH 4/9] fix: fixed typo --- front/src/login/Login.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/front/src/login/Login.tsx b/front/src/login/Login.tsx index 591281ec54..34a1c45080 100644 --- a/front/src/login/Login.tsx +++ b/front/src/login/Login.tsx @@ -100,7 +100,7 @@ export default function Login() {
) : null; - const disconectedAlert = + const disconnectedAlert = queries["session"] === "expired" ? (
{createdAlert} - {disconectedAlert} + {disconnectedAlert} {alert}
From 715a0f819e1c187602ce075488ff421405e239e9 Mon Sep 17 00:00:00 2001 From: GaelFerrand Date: Thu, 7 Nov 2024 15:55:11 +0100 Subject: [PATCH 5/9] fix: wording --- front/src/login/Login.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/front/src/login/Login.tsx b/front/src/login/Login.tsx index 34a1c45080..ef7e8ffdf9 100644 --- a/front/src/login/Login.tsx +++ b/front/src/login/Login.tsx @@ -104,8 +104,8 @@ export default function Login() { queries["session"] === "expired" ? (
From a4803000935fa68a67f1fcc39aad65e0a292065d Mon Sep 17 00:00:00 2001 From: Julien Seren-Rosso Date: Tue, 17 Dec 2024 10:55:59 +0100 Subject: [PATCH 6/9] Prevents client from performing requests on logout --- front/src/login/auth.service.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/front/src/login/auth.service.ts b/front/src/login/auth.service.ts index c5e565bc9a..672d0e1db2 100644 --- a/front/src/login/auth.service.ts +++ b/front/src/login/auth.service.ts @@ -3,7 +3,8 @@ import { SIRET_STORAGE_KEY } from "../Apps/common/Components/CompanySwitcher/Com export const localAuthService = { locallySignOut() { - client.resetStore(); + client.stop(); + client.clearStore(); window.localStorage.removeItem(SIRET_STORAGE_KEY); } }; From fc73b9f2e9b99e6d27ae67451210b8d8ddac9922 Mon Sep 17 00:00:00 2001 From: Julien Seren-Rosso Date: Thu, 9 Jan 2025 15:57:40 +0100 Subject: [PATCH 7/9] Reworks routing and auth routes --- .../Apps/common/Components/layout/Header.tsx | 28 ++--- .../Apps/common/Components/layout/Layout.tsx | 19 +-- .../Components/layout/LayoutContainer.tsx | 110 +++++------------- front/src/Apps/utils/routerUtils.tsx | 47 +++++++- 4 files changed, 89 insertions(+), 115 deletions(-) diff --git a/front/src/Apps/common/Components/layout/Header.tsx b/front/src/Apps/common/Components/layout/Header.tsx index 9b5fa7dd31..881d0b5359 100644 --- a/front/src/Apps/common/Components/layout/Header.tsx +++ b/front/src/Apps/common/Components/layout/Header.tsx @@ -33,12 +33,15 @@ import { import routes from "../../../routes"; import styles from "./Header.module.scss"; -import CompanySwitcher from "../CompanySwitcher/CompanySwitcher"; +import CompanySwitcher, { + getDefaultOrgId +} from "../CompanySwitcher/CompanySwitcher"; export const GET_ME = gql` { me { id + isAdmin companies { id name @@ -581,22 +584,12 @@ const getDesktopMenuEntries = ( return [...(isAuthenticated ? connected : []), ...(isAdmin ? admin : [])]; }; -type HeaderProps = { - isAuthenticated: boolean; - isAdmin: boolean; - defaultOrgId?: string; -}; - /** * Main nav * Contains External and internal links * On mobile appear as a sliding panel and includes other items */ -export default function Header({ - isAuthenticated, - isAdmin, - defaultOrgId -}: HeaderProps) { +export default function Header() { const { VITE_API_ENDPOINT } = import.meta.env; const location = useLocation(); const { updatePermissions, role, permissions } = usePermissions(); @@ -611,11 +604,16 @@ export default function Header({ location.pathname ); + const { data, loading } = useQuery>(GET_ME); + + const isAuthenticated = !loading && data != null; + const isAdmin = isAuthenticated && Boolean(data?.me?.isAdmin); + + const defaultOrgId = getDefaultOrgId(data?.me.companies ?? []); + // Catching siret from url when not available from props (just after login) const currentSiret = matchDashboard?.params["siret"] || defaultOrgId; - const { data } = useQuery>(GET_ME); - useEffect(() => { if (isAuthenticated && data && currentSiret) { const companies = data.me.companies; @@ -648,6 +646,8 @@ export default function Header({ [navigate, role] ); + if (loading) return null; + const showRegistry = permissions.includes(UserPermission.RegistryCanRead) && [UserRole.Admin, UserRole.Member].includes(role!); diff --git a/front/src/Apps/common/Components/layout/Layout.tsx b/front/src/Apps/common/Components/layout/Layout.tsx index a75472ba4d..ebd578f7a9 100644 --- a/front/src/Apps/common/Components/layout/Layout.tsx +++ b/front/src/Apps/common/Components/layout/Layout.tsx @@ -11,9 +11,7 @@ import PageTitle from "../PageTitle/PageTitle"; import A11ySkipLinks from "../A11ySkipLinks/A11ySkipLinks"; interface AuthProps { - isAuthenticated: boolean; - isAdmin: boolean; - defaultOrgId?: string; + v2banner?: JSX.Element; } const { VITE_WARNING_MESSAGE, VITE_DOWNTIME_MESSAGE, VITE_API_ENDPOINT } = import.meta.env; @@ -27,14 +25,7 @@ const GET_WARNING_MESSAGE = gql` /** * Layout with common elements to all routes */ -export default function Layout({ - isAuthenticated, - isAdmin, - v2banner, - defaultOrgId -}: AuthProps & { - v2banner?: JSX.Element; -}) { +export default function Layout({ v2banner }: AuthProps) { const { data } = useQuery>(GET_WARNING_MESSAGE); const isIE11 = !!navigator.userAgent.match(/Trident.*rv:11\./); @@ -104,11 +95,7 @@ export default function Layout({
)} -
+
diff --git a/front/src/Apps/common/Components/layout/LayoutContainer.tsx b/front/src/Apps/common/Components/layout/LayoutContainer.tsx index 3422460e37..2dcc3edd25 100644 --- a/front/src/Apps/common/Components/layout/LayoutContainer.tsx +++ b/front/src/Apps/common/Components/layout/LayoutContainer.tsx @@ -1,11 +1,8 @@ import React, { lazy, Suspense } from "react"; -import { Route, Routes, Navigate, generatePath } from "react-router-dom"; -import * as Sentry from "@sentry/browser"; +import { Route, Routes } from "react-router-dom"; import Loader from "../Loader/Loaders"; import Layout from "./Layout"; import routes from "../../../routes"; -import { useQuery, gql } from "@apollo/client"; -import { Query, UserRole } from "@td/codegen-ui"; import ResendActivationEmail from "../../../../login/ResendActivationEmail"; import Login from "../../../../login/Login"; import SurveyBanner from "../SurveyBanner/SurveyBanner"; @@ -53,45 +50,13 @@ const Signup = lazy(() => import("../../../../login/Signup")); const Company = lazy(() => import("../../../../company/Company")); const WasteTree = lazy(() => import("../search/WasteTree")); -const GET_ME = gql` - query GetMe { - me { - id - email - isAdmin - companies { - orgId - siret - securityCode - } - featureFlags - } - } -`; - const BANNER_MESSAGES = [ `Abonnez-vous à notre lettre d'information mensuelle pour suivre les nouveautés de la plateforme, la programmation des formations, des conseils pratiques, ainsi que les évolutions réglementaires liées à la traçabilité des déchets.` ]; export default function LayoutContainer() { - const { orgId } = usePermissions(); - const { data, loading } = useQuery>(GET_ME, { - onCompleted: ({ me }) => { - if (import.meta.env.VITE_SENTRY_DSN && me.email) { - Sentry.setUser({ email: me.email }); - } - } - }); - - const isAuthenticated = !loading && data != null; - const isAdmin = isAuthenticated && Boolean(data?.me?.isAdmin); - - if (loading) { - return ; - } - - const defaultOrgId = - isAuthenticated && data && (orgId ?? getDefaultOrgId(data.me.companies)); + // const { orgId } = usePermissions(); + // const defaultOrgId = orgId ?? getDefaultOrgId([]); return ( }> @@ -99,7 +64,7 @@ export default function LayoutContainer() { + } @@ -108,7 +73,7 @@ export default function LayoutContainer() { + } @@ -116,8 +81,6 @@ export default function LayoutContainer() { } - defaultOrgId={defaultOrgId} /> } > - {isAdmin ? ( - - ) : ( -
Vous n'êtes pas autorisé à consulter cette page
- )} + + } /> @@ -179,7 +137,7 @@ export default function LayoutContainer() { + } @@ -193,7 +151,7 @@ export default function LayoutContainer() { + } @@ -202,7 +160,7 @@ export default function LayoutContainer() { + } @@ -211,7 +169,7 @@ export default function LayoutContainer() { + } @@ -220,7 +178,7 @@ export default function LayoutContainer() { + } @@ -229,7 +187,7 @@ export default function LayoutContainer() { + } @@ -238,7 +196,7 @@ export default function LayoutContainer() { + } @@ -247,7 +205,7 @@ export default function LayoutContainer() { + } @@ -256,7 +214,7 @@ export default function LayoutContainer() { + } @@ -265,7 +223,7 @@ export default function LayoutContainer() { + } @@ -274,7 +232,7 @@ export default function LayoutContainer() { + } @@ -283,7 +241,7 @@ export default function LayoutContainer() { + } @@ -292,7 +250,7 @@ export default function LayoutContainer() { + } @@ -301,7 +259,7 @@ export default function LayoutContainer() { + } @@ -310,7 +268,7 @@ export default function LayoutContainer() { + } @@ -319,7 +277,7 @@ export default function LayoutContainer() { + } @@ -328,25 +286,9 @@ export default function LayoutContainer() { 0 - ? generatePath( - data?.me.companies[0].userRole?.includes( - UserRole.Driver - ) - ? routes.dashboard.transport.toCollect - : routes.dashboard.index, - { - siret: defaultOrgId - } - ) - : routes.companies.index - : routes.login - } - replace - /> + + + } /> diff --git a/front/src/Apps/utils/routerUtils.tsx b/front/src/Apps/utils/routerUtils.tsx index 69b0b678a4..4a28a202dc 100644 --- a/front/src/Apps/utils/routerUtils.tsx +++ b/front/src/Apps/utils/routerUtils.tsx @@ -5,16 +5,61 @@ import { useLocation, useParams } from "react-router-dom"; +import { useQuery, gql } from "@apollo/client"; +import * as Sentry from "@sentry/browser"; +import { Query } from "@td/codegen-ui"; +import Loader from "../common/Components/Loader/Loaders"; import routes from "../routes"; -export function RequireAuth({ children, isAuthenticated }) { +const GET_ME = gql` + query GetMe { + me { + id + email + isAdmin + companies { + orgId + siret + securityCode + } + featureFlags + } + } +`; + +export function RequireAuth({ + children, + needsAdminPrivilege = false, + replace = false +}) { const location = useLocation(); + + const { data, loading } = useQuery>(GET_ME, { + onCompleted: ({ me }) => { + if (import.meta.env.VITE_SENTRY_DSN && me.email) { + Sentry.setUser({ email: me.email }); + } + } + }); + + const isAuthenticated = !loading && data != null; + const isAdmin = isAuthenticated && Boolean(data?.me?.isAdmin); + + if (loading) { + return ; + } + + if (needsAdminPrivilege && !isAdmin) { + return
Vous n'êtes pas autorisé à consulter cette page
; + } + return isAuthenticated ? ( children ) : ( ); } From 44fcaaa1503a53b8417cacae750461fe218f10ed Mon Sep 17 00:00:00 2001 From: Julien Seren-Rosso Date: Thu, 9 Jan 2025 16:59:41 +0100 Subject: [PATCH 8/9] Reworks Header and routing --- back/src/__tests__/auth.integration.ts | 2 +- back/src/__tests__/captcha.integration.ts | 2 +- back/src/routers/auth-router.ts | 2 +- front/src/Apps/Account/Account.tsx | 2 +- front/src/Apps/Companies/CompaniesRoutes.tsx | 2 +- front/src/Apps/Dashboard/DashboardRoutes.tsx | 4 +- .../Apps/common/Components/layout/Header.tsx | 285 +++++++++--------- .../Apps/common/Components/layout/Layout.tsx | 10 +- .../Components/layout/LayoutContainer.tsx | 64 ++-- front/src/graphql-client.ts | 10 +- front/src/login/Login.tsx | 2 +- 11 files changed, 203 insertions(+), 182 deletions(-) diff --git a/back/src/__tests__/auth.integration.ts b/back/src/__tests__/auth.integration.ts index f3bc4844cf..78da1198c8 100644 --- a/back/src/__tests__/auth.integration.ts +++ b/back/src/__tests__/auth.integration.ts @@ -35,7 +35,7 @@ describe("POST /login", () => { // should redirect to / expect(login.status).toBe(302); - expect(login.header.location).toBe(`http://${UI_HOST}/`); + expect(login.header.location).toBe(`http://${UI_HOST}/dashboard`); const cookieValue = sessionCookie.match(cookieRegExp)[1]; diff --git a/back/src/__tests__/captcha.integration.ts b/back/src/__tests__/captcha.integration.ts index d8ec9737ee..636de291a9 100644 --- a/back/src/__tests__/captcha.integration.ts +++ b/back/src/__tests__/captcha.integration.ts @@ -187,7 +187,7 @@ describe("POST /login", () => { // should redirect to / expect(login.status).toBe(302); - expect(login.header.location).toBe(`http://${UI_HOST}/`); + expect(login.header.location).toBe(`http://${UI_HOST}/dashboard`); const cookieValue = sessionCookie.match(cookieRegExp)[1]; diff --git a/back/src/routers/auth-router.ts b/back/src/routers/auth-router.ts index a99ff2431f..2f9e15fa99 100644 --- a/back/src/routers/auth-router.ts +++ b/back/src/routers/auth-router.ts @@ -44,7 +44,7 @@ authRouter.post( } req.logIn(user, () => { storeUserSessionsId(user.id, req.session.id); - const returnTo = req.body.returnTo || "/"; + const returnTo = req.body.returnTo || "/dashboard"; return res.redirect(`${UI_BASE_URL}${returnTo}`); }); })(req, res, next); diff --git a/front/src/Apps/Account/Account.tsx b/front/src/Apps/Account/Account.tsx index acc509fb7f..5625880abf 100644 --- a/front/src/Apps/Account/Account.tsx +++ b/front/src/Apps/Account/Account.tsx @@ -34,7 +34,7 @@ export default function Account() { const isMobile = useMedia(`(max-width: ${MEDIA_QUERIES.handHeld})`); - if (loading) return ; + if (loading || data?.me == null) return ; if (error) return ; diff --git a/front/src/Apps/Companies/CompaniesRoutes.tsx b/front/src/Apps/Companies/CompaniesRoutes.tsx index 3a5201ca35..d4ead3bd73 100644 --- a/front/src/Apps/Companies/CompaniesRoutes.tsx +++ b/front/src/Apps/Companies/CompaniesRoutes.tsx @@ -28,7 +28,7 @@ export default function CompaniesRoutes() { const isMobile = useMedia(`(max-width: ${MEDIA_QUERIES.handHeld})`); - if (loading) return ; + if (loading || data?.me == null) return ; if (error) return ; diff --git a/front/src/Apps/Dashboard/DashboardRoutes.tsx b/front/src/Apps/Dashboard/DashboardRoutes.tsx index db9c7eb08d..342cd2652d 100644 --- a/front/src/Apps/Dashboard/DashboardRoutes.tsx +++ b/front/src/Apps/Dashboard/DashboardRoutes.tsx @@ -65,7 +65,7 @@ const toRelative = route => { function DashboardRoutes() { const { siret } = useParams<{ siret: string }>(); - const { data } = useQuery>(GET_ME); + const { data, loading } = useQuery>(GET_ME); const { updatePermissions } = usePermissions(); const navigate = useNavigate(); @@ -108,7 +108,7 @@ function DashboardRoutes() { } }, [updatePermissions, data, siret]); - if (data?.me == null) { + if (loading || data?.me == null) { return ; } diff --git a/front/src/Apps/common/Components/layout/Header.tsx b/front/src/Apps/common/Components/layout/Header.tsx index 881d0b5359..756c083434 100644 --- a/front/src/Apps/common/Components/layout/Header.tsx +++ b/front/src/Apps/common/Components/layout/Header.tsx @@ -673,7 +673,153 @@ export default function Header() { canViewNewRegistry ); - return !isAuthenticated ? ( + return ( + <> + + + {/* Company switcher on top of the page */} + {!!matchDashboard && companies && currentCompany && ( +
+
+ +
+
+ )} + + ); +} + +/** + * Main nav when logged out + * Contains External and internal links + * On mobile appear as a sliding panel and includes other items + */ +export function UnauthenticatedHeader() { + return (
- ) : ( - <> - - - {/* Company switcher on top of the page */} - {!!matchDashboard && companies && currentCompany && ( -
-
- -
-
- )} - ); } diff --git a/front/src/Apps/common/Components/layout/Layout.tsx b/front/src/Apps/common/Components/layout/Layout.tsx index ebd578f7a9..1577f25130 100644 --- a/front/src/Apps/common/Components/layout/Layout.tsx +++ b/front/src/Apps/common/Components/layout/Layout.tsx @@ -3,7 +3,7 @@ import { gql, useQuery } from "@apollo/client"; import Button from "@codegouvfr/react-dsfr/Button"; import { Query } from "@td/codegen-ui"; import { Outlet } from "react-router-dom"; -import Header from "./Header"; +import Header, { UnauthenticatedHeader } from "./Header"; import { Toaster } from "react-hot-toast"; import sandboxIcon from "./assets/code-sandbox.svg"; import downtimeIcon from "./assets/code-downtime.svg"; @@ -12,6 +12,7 @@ import A11ySkipLinks from "../A11ySkipLinks/A11ySkipLinks"; interface AuthProps { v2banner?: JSX.Element; + unauthenticatedRoutes?: boolean; } const { VITE_WARNING_MESSAGE, VITE_DOWNTIME_MESSAGE, VITE_API_ENDPOINT } = import.meta.env; @@ -25,7 +26,10 @@ const GET_WARNING_MESSAGE = gql` /** * Layout with common elements to all routes */ -export default function Layout({ v2banner }: AuthProps) { +export default function Layout({ + v2banner, + unauthenticatedRoutes = false +}: AuthProps) { const { data } = useQuery>(GET_WARNING_MESSAGE); const isIE11 = !!navigator.userAgent.match(/Trident.*rv:11\./); @@ -95,7 +99,7 @@ export default function Layout({ v2banner }: AuthProps) {
)} -
+ {unauthenticatedRoutes ? :
} diff --git a/front/src/Apps/common/Components/layout/LayoutContainer.tsx b/front/src/Apps/common/Components/layout/LayoutContainer.tsx index 2dcc3edd25..3e95cadd27 100644 --- a/front/src/Apps/common/Components/layout/LayoutContainer.tsx +++ b/front/src/Apps/common/Components/layout/LayoutContainer.tsx @@ -7,8 +7,6 @@ import ResendActivationEmail from "../../../../login/ResendActivationEmail"; import Login from "../../../../login/Login"; import SurveyBanner from "../SurveyBanner/SurveyBanner"; import { RequireAuth, Redirect } from "../../../utils/routerUtils"; -import { getDefaultOrgId } from "../CompanySwitcher/CompanySwitcher"; -import { usePermissions } from "../../../../common/contexts/PermissionsContext"; import Exports from "../../../../dashboard/exports/Registry"; import { Oauth2Dialog, OidcDialog } from "../../../../oauth/AuthDialog"; import { MyImports } from "../../../../dashboard/registry/MyImports"; @@ -55,9 +53,6 @@ const BANNER_MESSAGES = [ ]; export default function LayoutContainer() { - // const { orgId } = usePermissions(); - // const defaultOrgId = orgId ?? getDefaultOrgId([]); - return ( }> @@ -78,6 +73,33 @@ export default function LayoutContainer() { } /> + + }> + } /> + + } /> + + } /> + + } /> + + } /> + + } + /> + + } /> + + } /> + + } + /> + + - } /> - - } /> - - } /> - - } /> - } + path={routes.company} + element={ + + + + } /> - } /> - - } /> - } + path={routes.wasteTree} + element={ + + + + } /> - } /> - - } /> - } diff --git a/front/src/graphql-client.ts b/front/src/graphql-client.ts index 336d5f276b..f633915730 100644 --- a/front/src/graphql-client.ts +++ b/front/src/graphql-client.ts @@ -53,14 +53,8 @@ const errorLink = onError(({ response, graphQLErrors }) => { // If we catch an UNAUTHENTICATED exception at this point, // the user session has probably expired. Redirect to login // page with a hint in the URL to display a message - if ( - window.location.pathname !== "/" && - window.location.pathname !== "/login" - ) { - // Logout - localAuthService.locallySignOut(); - window.location.href = "/login?session=expired"; - } + localAuthService.locallySignOut(); + window.location.href = "/login?session=expired"; } } } diff --git a/front/src/login/Login.tsx b/front/src/login/Login.tsx index ef7e8ffdf9..653e9830f0 100644 --- a/front/src/login/Login.tsx +++ b/front/src/login/Login.tsx @@ -76,7 +76,7 @@ export default function Login() { return ; } - const { returnTo, errorCode, username } = location.state || {}; + const { returnTo, errorCode, username = "" } = location.state || {}; const showCaptcha = displayCaptcha(errorCode); From 6bfc43520e66e1a645a3fd48fcffaeed3fefe772 Mon Sep 17 00:00:00 2001 From: GaelFerrand Date: Thu, 23 Jan 2025 10:48:57 +0100 Subject: [PATCH 9/9] feat: added endpoint isAuthenticated to know whether a user is authenticated or not --- back/src/users/resolvers/Query.ts | 2 + .../__tests__/isAuthenticated.integration.ts | 59 +++++++++++++++++++ .../resolvers/queries/isAuthenticated.ts | 20 +++++++ .../typeDefs/private/user.queries.graphql | 6 ++ 4 files changed, 87 insertions(+) create mode 100644 back/src/users/resolvers/queries/__tests__/isAuthenticated.integration.ts create mode 100644 back/src/users/resolvers/queries/isAuthenticated.ts diff --git a/back/src/users/resolvers/Query.ts b/back/src/users/resolvers/Query.ts index bb6dba0abd..aa41b17665 100644 --- a/back/src/users/resolvers/Query.ts +++ b/back/src/users/resolvers/Query.ts @@ -11,9 +11,11 @@ import passwordResetRequest from "./queries/passwordResetRequest"; import warningMessage from "./queries/warningMessage"; import myCompaniesCsv from "./queries/myCompaniesCsv"; import myCompaniesXls from "./queries/myCompaniesXls"; +import isAuthenticated from "./queries/isAuthenticated"; const Query: QueryResolvers = { me, + isAuthenticated, apiKey, invitation, membershipRequest, diff --git a/back/src/users/resolvers/queries/__tests__/isAuthenticated.integration.ts b/back/src/users/resolvers/queries/__tests__/isAuthenticated.integration.ts new file mode 100644 index 0000000000..c110a2ce9f --- /dev/null +++ b/back/src/users/resolvers/queries/__tests__/isAuthenticated.integration.ts @@ -0,0 +1,59 @@ +import { resetDatabase } from "../../../../../integration-tests/helper"; +import makeClient from "../../../../__tests__/testClient"; +import { userFactory } from "../../../../__tests__/factories"; +import type { Query } from "@td/codegen-back"; +import { AuthType } from "../../../../auth"; + +const IS_AUTHENTICATED = ` + query IsAuthenticated { + isAuthenticated + } +`; + +describe("query isAuthenticated", () => { + afterAll(resetDatabase); + + it("should return true if user is authenticated", async () => { + // Given + const user = await userFactory(); + + // When + const { query } = makeClient(user); + const { data, errors } = await query>( + IS_AUTHENTICATED + ); + + // Then + expect(errors).toBeUndefined(); + expect(data.isAuthenticated).toEqual(true); + }); + + it("should return false if user is not authenticated", async () => { + // Given + + // When + const { query } = makeClient(); + const { data, errors } = await query>( + IS_AUTHENTICATED + ); + + // Then + expect(errors).toBeUndefined(); + expect(data.isAuthenticated).toEqual(false); + }); + + it("should return false if not SESSION", async () => { + // Given + const user = await userFactory(); + + // When + const { query } = makeClient({ ...user, auth: AuthType.Bearer }); + const { errors, data } = await query>( + IS_AUTHENTICATED + ); + + // Then + expect(errors).toBeUndefined(); + expect(data.isAuthenticated).toEqual(false); + }); +}); diff --git a/back/src/users/resolvers/queries/isAuthenticated.ts b/back/src/users/resolvers/queries/isAuthenticated.ts new file mode 100644 index 0000000000..0e718be231 --- /dev/null +++ b/back/src/users/resolvers/queries/isAuthenticated.ts @@ -0,0 +1,20 @@ +import { applyAuthStrategies, AuthType } from "../../../auth"; +import { checkIsAuthenticated } from "../../../common/permissions"; +import type { QueryResolvers } from "@td/codegen-back"; + +const isAuthenticatedResolver: QueryResolvers["isAuthenticated"] = async ( + _, + __, + context +) => { + applyAuthStrategies(context, [AuthType.Session]); + + try { + checkIsAuthenticated(context); + return true; + } catch (_) { + return false; + } +}; + +export default isAuthenticatedResolver; diff --git a/back/src/users/typeDefs/private/user.queries.graphql b/back/src/users/typeDefs/private/user.queries.graphql index 5502a1e939..ebd5b116f1 100644 --- a/back/src/users/typeDefs/private/user.queries.graphql +++ b/back/src/users/typeDefs/private/user.queries.graphql @@ -46,4 +46,10 @@ type Query { skip: Int first: Int ): MembershipRequestsConnection! + + """ + Permet de savoir si l'utilisateur est authentifié ou non. + Ne retourne pas d'erreur si l'utilisateur n'est pas authentifié + """ + isAuthenticated: Boolean! }