Skip to content

Commit

Permalink
Add isCypress condition in RequireAuth
Browse files Browse the repository at this point in the history
  • Loading branch information
louptheron committed Oct 16, 2024
1 parent d2c3206 commit 8194f0c
Show file tree
Hide file tree
Showing 13 changed files with 83 additions and 29 deletions.
14 changes: 12 additions & 2 deletions frontend/cypress/e2e/external_monitorfish.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
context('External MonitorFish', () => {
it('Should redirect to /', () => {
// Given
cy.intercept('/bff/v1/authorization/current', { statusCode: 401 }).as('getIsSuperUser')
cy.intercept('/bff/v1/authorization/current', {
body: {
isSuperUser: false
},
statusCode: 200
}).as('getIsSuperUser')
cy.visit('/ext#@-824534.42,6082993.21,8.70')
cy.wait('@getIsSuperUser')

Expand All @@ -10,7 +15,12 @@ context('External MonitorFish', () => {

it('Should have some features removed When not logged as super user', () => {
// Given
cy.intercept('/bff/v1/authorization/current', { statusCode: 401 }).as('getIsSuperUser')
cy.intercept('/bff/v1/authorization/current', {
body: {
isSuperUser: false
},
statusCode: 200
}).as('getIsSuperUser')
cy.visit('/#@-824534.42,6082993.21,8.70')
cy.wait('@getIsSuperUser')
cy.wait(200)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ context('Offline management', () => {
path:
'/bff/v1/vessels/find?vesselId=1&internalReferenceNumber=FAK000999999&externalReferenceNumber=DONTSINK' +
'&IRCS=CALLME&vesselIdentifier=INTERNAL_REFERENCE_NUMBER&trackDepth=TWELVE_HOURS&afterDateTime=&beforeDateTime=',
times: 1
times: 2
},
{ statusCode: 400 }
).as('openVesselStubbed')
Expand Down Expand Up @@ -117,7 +117,7 @@ context('Offline management', () => {
{
method: 'GET',
path: '/bff/v1/vessels/logbook/find?internalReferenceNumber=FAK000999999&voyageRequest=LAST&tripNumber=',
times: 1
times: 2
},
{ statusCode: 400 }
).as('getLogbookStubbed')
Expand All @@ -135,8 +135,8 @@ context('Offline management', () => {
cy.intercept(
{
method: 'GET',
pathname: '/bff/v1/vessels/reportings',
times: 3
pathname: '/bff/v1/vessels/reporting',
times: 2
},
{ statusCode: 400 }
).as('getReportingsStubbed')
Expand Down
7 changes: 6 additions & 1 deletion frontend/cypress/e2e/nav_monitorfish.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
context('Light MonitorFish', () => {
it('Should have some features removed When not logged as super user', () => {
// Given
cy.intercept('/bff/v1/authorization/current', { statusCode: 401 }).as('getIsSuperUser')
cy.intercept('/bff/v1/authorization/current', {
body: {
isSuperUser: false
},
statusCode: 200
}).as('getIsSuperUser')
cy.visit('/light#@-824534.42,6082993.21,8.70')
cy.wait('@getIsSuperUser')
cy.wait(200)
Expand Down
11 changes: 10 additions & 1 deletion frontend/cypress/e2e/side_window/utils.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
import { SideWindowMenuLabel } from '../../../src/domain/entities/sideWindow/constants'

export const openSideWindowAsUser = () => {
cy.intercept('/bff/v1/authorization/current', { statusCode: 401 }).as('getIsSuperUser')
cy.intercept('/bff/v1/authorization/current', {
body: {
isSuperUser: false
},
statusCode: 200
}).as('getIsSuperUser')

cy.viewport(1920, 1080)
cy.visit('/side_window')
cy.wait(500)
if (document.querySelector('[data-cy="first-loader"]')) {
cy.getDataCy('first-loader').should('not.be.visible')
}
cy.clickButton(SideWindowMenuLabel.PRIOR_NOTIFICATION_LIST)
}

export const openSideWindowAsSuperUser = () => {
Expand All @@ -16,4 +24,5 @@ export const openSideWindowAsSuperUser = () => {
if (document.querySelector('[data-cy="first-loader"]')) {
cy.getDataCy('first-loader').should('not.be.visible')
}
cy.clickButton(SideWindowMenuLabel.PRIOR_NOTIFICATION_LIST)
}
2 changes: 1 addition & 1 deletion frontend/cypress/support/e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ Cypress.on('uncaught:exception', err => {

// Run before each spec
beforeEach(() => {
// We use a Cypress session to inject inject a Local Storage key
// We use a Cypress session to inject a Local Storage key
// so that we can detect when the browser app is running in Cypress.
// https://docs.cypress.io/faq/questions/using-cypress-faq#How-do-I-preserve-cookies--localStorage-in-between-my-tests
cy.session('cypress', () => {
Expand Down
16 changes: 13 additions & 3 deletions frontend/src/api/api.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
// https://redux-toolkit.js.org/rtk-query/usage/cache-behavior
// https://redux-toolkit.js.org/rtk-query/usage/automated-refetching#cache-tags

import { isUnauthorizedOrForbidden } from '@api/utils'
import { FrontendApiError } from '@libs/FrontendApiError'
import { createApi, fetchBaseQuery, retry } from '@reduxjs/toolkit/query/react'
import { setMeasurement, startSpan } from '@sentry/react'
import { normalizeRtkBaseQuery } from '@utils/normalizeRtkBaseQuery'
import { sha256 } from '@utils/sha256'
import ky, { HTTPError } from 'ky'

import { RTK_MAX_RETRIES, RtkCacheTagType } from './constants'
import { getOIDCConfig } from '../auth/getOIDCConfig'
import { getOIDCUser } from '../auth/getOIDCUser'
import { redirectToLoginIfUnauthorized } from '../auth/utils'
import { normalizeRtkBaseQuery } from '../utils/normalizeRtkBaseQuery'

import type { BackendApi } from './BackendApi.types'
import type { CustomResponseError, RTKBaseQueryArgs } from './types'
Expand Down Expand Up @@ -243,7 +244,14 @@ export const monitorfishApiKy = ky.extend({
}
],
beforeRetry: [
async ({ request }) => {
async ({ error, request }) => {
if (error) {
// Retry is not necessary when request is unauthorized
if (isUnauthorizedOrForbidden((error as HTTPError).response?.status)) {
return ky.stop
}
}

const user = getOIDCUser()
const token = user?.access_token

Expand All @@ -257,8 +265,10 @@ export const monitorfishApiKy = ky.extend({
request.headers.set(CORRELATION_HEADER, hashedToken)
}
}

return undefined
}
]
},
retry: RTK_MAX_RETRIES + 1
retry: RTK_MAX_RETRIES
})
5 changes: 3 additions & 2 deletions frontend/src/api/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { FIVE_MINUTES, ONE_MINUTE, THIRTY_SECONDS } from '../constants'
import type { RefetchConfigOptions } from '@reduxjs/toolkit'
import type { StartQueryActionCreatorOptions, SubscriptionOptions } from '@reduxjs/toolkit/query'

export const RTK_MAX_RETRIES = 2
export const RTK_MAX_RETRIES = 1

export const RTK_THIRTY_SECONDS_POLLING_QUERY_OPTIONS: SubscriptionOptions & Partial<RefetchConfigOptions> = {
pollingInterval: THIRTY_SECONDS,
Expand Down Expand Up @@ -32,7 +32,8 @@ export enum HttpStatusCode {
CREATED = 201,
ACCEPTED = 202,
NOT_FOUND = 404,
UNAUTHORIZED = 401
UNAUTHORIZED = 401,
FORBIDDEN = 403
}

export enum RtkCacheTagType {
Expand Down
10 changes: 10 additions & 0 deletions frontend/src/api/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { HttpStatusCode } from '@api/constants'
import { isString } from 'lodash'

export function isUnauthorizedOrForbidden(httpStatus: number | string | undefined) {
if (!httpStatus || isString(httpStatus)) {
return false
}

return [HttpStatusCode.FORBIDDEN, HttpStatusCode.UNAUTHORIZED].includes(httpStatus)
}
5 changes: 1 addition & 4 deletions frontend/src/auth/getOIDCConfig.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import { isCypress } from '@utils/isCypress'
import { WebStorageStateStore } from 'oidc-client-ts'

const IS_CYPRESS = isCypress()

export function getOIDCConfig() {
const IS_OIDC_ENABLED = import.meta.env.FRONTEND_OIDC_ENABLED === 'true'
const OIDC_REDIRECT_URI = import.meta.env.FRONTEND_OIDC_REDIRECT_URI
Expand Down Expand Up @@ -30,7 +27,7 @@ export function getOIDCConfig() {

return {
// eslint-disable-next-line @typescript-eslint/naming-convention
IS_OIDC_ENABLED: IS_CYPRESS ? false : IS_OIDC_ENABLED,
IS_OIDC_ENABLED,
oidcConfig
}
}
26 changes: 20 additions & 6 deletions frontend/src/auth/hooks/useGetUserAccount.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
import { useTracking } from '@hooks/useTracking'
import { setUser } from '@sentry/react'
import { isCypress } from '@utils/isCypress'
import { useCallback, useEffect, useMemo } from 'react'
import { type AuthContextProps, useAuth } from 'react-oidc-context'

import { useGetCurrentUserAuthorizationQueryOverride } from './useGetCurrentUserAuthorizationQueryOverride'

import type { UserAccountContextType } from '../../context/UserAccountContext'

const IS_CYPRESS = isCypress() || true

/**
* When using Cypress, we stub `useAuth()`
*/
export function useGetUserAccount(): UserAccountContextType {
// `| undefined` because it's undefined if the OIDC is disabled which is the case for Cypress tests
const auth = useAuth() as AuthContextProps | undefined
const { trackUserId } = useTracking()
const { data: user } = useGetCurrentUserAuthorizationQueryOverride({ skip: !auth?.isAuthenticated })
const { data: user } = useGetCurrentUserAuthorizationQueryOverride({ skip: !IS_CYPRESS && !auth?.isAuthenticated })

useEffect(() => {
if (auth?.user?.profile?.email) {
Expand All @@ -31,15 +37,23 @@ export function useGetUserAccount(): UserAccountContextType {
auth.signoutRedirect({ id_token_hint: idTokenHint ?? '' })
}, [auth])

const userAccount = useMemo(
() => ({
const userAccount = useMemo(() => {
if (IS_CYPRESS) {
return {
email: 'dummy@cypress.test',
isAuthenticated: true,
isSuperUser: user?.isSuperUser ?? false,
logout
}
}

return {
email: auth?.user?.profile?.email,
isAuthenticated: auth?.isAuthenticated ?? false,
isSuperUser: user?.isSuperUser ?? false,
logout
}),
[logout, user, auth?.isAuthenticated, auth?.user?.profile?.email]
)
}
}, [logout, user, auth?.isAuthenticated, auth?.user?.profile?.email])

useEffect(
() =>
Expand Down
2 changes: 0 additions & 2 deletions frontend/src/auth/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
export type UserAuthorization = {
isLogged: boolean | undefined
isSuperUser: boolean | undefined
mustReload: boolean | undefined
}
4 changes: 2 additions & 2 deletions frontend/src/auth/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { HttpStatusCode } from '@api/constants'
import { isUnauthorizedOrForbidden } from '@api/utils'

import { paths } from '../paths'
import { router } from '../router'
Expand All @@ -9,7 +9,7 @@ import type { CustomResponseError } from '@api/types'
* Redirect to Login page if any HTTP request in Unauthorized
*/
export function redirectToLoginIfUnauthorized(error: CustomResponseError) {
if (!error.path.includes(paths.backendForFrontend) || error.status !== HttpStatusCode.UNAUTHORIZED) {
if (!error.path.includes(paths.backendForFrontend) || !isUnauthorizedOrForbidden(error.status)) {
return
}

Expand Down
2 changes: 1 addition & 1 deletion frontend/src/utils/isCypress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* Detects whether the browser app is running in Cypress.
*
* @description
* We use a Cypress session to inject inject a Local Storage key
* We use a Cypress session to inject a Local Storage key
* so that we can detect when the browser app is running in Cypress.
*
* @see https://docs.cypress.io/faq/questions/using-cypress-faq#How-do-I-preserve-cookies--localStorage-in-between-my-tests
Expand Down

0 comments on commit 8194f0c

Please sign in to comment.