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

SSO - Correction des erreurs de connexion #3763

Merged
merged 9 commits into from
Oct 17, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
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
2 changes: 1 addition & 1 deletion frontend/.env.local.defaults
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ FRONTEND_OIDC_AUTHORITY=http://localhost:8085/realms/monitor
FRONTEND_OIDC_CLIENT_ID=monitorfish
FRONTEND_OIDC_ENABLED=true
FRONTEND_OIDC_REDIRECT_URI=http://localhost:3000
FRONTEND_OIDC_LOGOUT_REDIRECT_URI=http://localhost:3000
FRONTEND_OIDC_LOGOUT_REDIRECT_URI=http://localhost:3000/login

################################################################################
# Sentry
Expand Down
17 changes: 17 additions & 0 deletions frontend/cypress/e2e/authorization/authorization.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/* eslint-disable no-undef */

context('Authorization', () => {
beforeEach(() => {
cy.loadPath('/#@-824534.42,6082993.21,8.70')
})

it('Should redirect to login page if an API request is Unauthorized', () => {
// When
cy.intercept('GET', `/bff/v1/vessels/search*`, { statusCode: 401 }).as('searchVessel')
cy.get('*[data-cy^="vessel-search-input"]', { timeout: 10000 }).type('Pheno')
cy.wait('@searchVessel')

// Then
cy.location('pathname').should('eq', '/login')
})
})
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
35 changes: 10 additions & 25 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,48 +1,33 @@
import { CustomGlobalStyle } from '@components/CustomGlobalStyle'
import { FrontendErrorBoundary } from '@components/FrontendErrorBoundary'
import { GlobalStyle, THEME, ThemeProvider } from '@mtes-mct/monitor-ui'
import { LandingPage } from '@pages/LandingPage'
import { UnsupportedBrowserPage } from '@pages/UnsupportedBrowserPage'
import { isBrowserSupported } from '@utils/isBrowserSupported'
import { UserAccountContext } from 'context/UserAccountContext'
import countries from 'i18n-iso-countries'
import COUNTRIES_FR from 'i18n-iso-countries/langs/fr.json'
import { RouterProvider } from 'react-router-dom'
import { CustomProvider as RsuiteCustomProvider } from 'rsuite'
import rsuiteFrFr from 'rsuite/locales/fr_FR'

import { useCustomAuth } from './auth/hooks/useCustomAuth'
import { router } from './router'

countries.registerLocale(COUNTRIES_FR)

export function App() {
const { isAuthorized, isLoading, userAccount } = useCustomAuth()

if (isLoading) {
return <LandingPage />
}

if (!isAuthorized || !userAccount) {
return <LandingPage hasInsufficientRights />
}

if (!isBrowserSupported()) {
return <UnsupportedBrowserPage />
}

return (
<UserAccountContext.Provider value={userAccount}>
<ThemeProvider theme={THEME}>
<GlobalStyle />
<CustomGlobalStyle />

<RsuiteCustomProvider locale={rsuiteFrFr}>
<FrontendErrorBoundary>
<RouterProvider router={router} />
</FrontendErrorBoundary>
</RsuiteCustomProvider>
</ThemeProvider>
</UserAccountContext.Provider>
<ThemeProvider theme={THEME}>
<GlobalStyle />
<CustomGlobalStyle />

<RsuiteCustomProvider locale={rsuiteFrFr}>
<FrontendErrorBoundary>
<RouterProvider router={router} />
</FrontendErrorBoundary>
</RsuiteCustomProvider>
</ThemeProvider>
)
}
8 changes: 8 additions & 0 deletions frontend/src/api/BackendApi.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,13 @@ export namespace BackendApi {

export interface ResponseBodyError {
code: ErrorCode | null
data: any
type: ErrorCode | null
}

// Don't forget to mirror any update here in the backend enum.
export enum ErrorCode {
AUTHENTICATION_REQUIRED = 'AUTHENTICATION_REQUIRED',
EXISTING_MISSION_ACTION = 'EXISTING_MISSION_ACTION',
/** Thrown when attempting to delete an entity which has to non-archived children. */
FOREIGN_KEY_CONSTRAINT = 'FOREIGN_KEY_CONSTRAINT',
Expand All @@ -59,3 +61,9 @@ export namespace BackendApi {
UNARCHIVED_CHILD = 'UNARCHIVED_CHILD'
}
}

export interface Meta {
response?: {
headers: Headers
}
}
27 changes: 14 additions & 13 deletions frontend/src/api/alert.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
// TODO We could remove the type discrimation normalization step if we had it done on API side.

import { FrontendApiError } from '@libs/FrontendApiError'

import { monitorfishApi, monitorfishApiKy } from './api'
import { ApiError } from '../libs/ApiError'

import type {
SilencedAlertData,
LEGACY_PendingAlert,
LEGACY_SilencedAlert,
PendingAlert,
SilencedAlert,
SilencedAlertData,
SilencedAlertPeriodRequest
} from '../domain/entities/alerts/types'

Expand Down Expand Up @@ -39,7 +40,7 @@ export const alertApi = monitorfishApi.injectEndpoints({
method: 'POST',
url: `/operational_alerts/silenced`
}),
transformErrorResponse: response => new ApiError(CREATE_SILENCED_ALERT_ERROR_MESSAGE, response)
transformErrorResponse: response => new FrontendApiError(CREATE_SILENCED_ALERT_ERROR_MESSAGE, response)
})
})
})
Expand All @@ -49,35 +50,35 @@ export const { useCreateSilencedAlertMutation } = alertApi
/**
* Get operational alerts
*
* @throws {@link ApiError}
* @throws {@link FrontendApiError}
*/
async function getOperationalAlertsFromAPI(): Promise<LEGACY_PendingAlert[]> {
try {
const data = await monitorfishApiKy.get('/bff/v1/operational_alerts').json<PendingAlert[]>()

return data.map(normalizePendingAlert)
} catch (err) {
throw new ApiError(ALERTS_ERROR_MESSAGE, err)
throw new FrontendApiError(ALERTS_ERROR_MESSAGE, (err as FrontendApiError).originalError)
}
}

/**
* Validate an alert
*
* @throws {@link ApiError}
* @throws {@link FrontendApiError}
*/
async function validateAlertFromAPI(id: string): Promise<void> {
try {
await monitorfishApiKy.put(`/bff/v1/operational_alerts/${id}/validate`)
} catch (err) {
throw new ApiError(VALIDATE_ALERT_ERROR_MESSAGE, err)
throw new FrontendApiError(VALIDATE_ALERT_ERROR_MESSAGE, (err as FrontendApiError).originalError)
}
}

/**
* Silence an alert and returns the saved silenced alert
*
* @throws {@link ApiError}
* @throws {@link FrontendApiError}
*/
async function silenceAlertFromAPI(
id: string,
Expand All @@ -97,33 +98,33 @@ async function silenceAlertFromAPI(
})
.json<SilencedAlert>()
} catch (err) {
throw new ApiError(SILENCE_ALERT_ERROR_MESSAGE, err)
throw new FrontendApiError(SILENCE_ALERT_ERROR_MESSAGE, (err as FrontendApiError).originalError)
}
}

/**
* Get silenced alerts
*
* @throws {@link ApiError}
* @throws {@link FrontendApiError}
*/
async function getSilencedAlertsFromAPI(): Promise<LEGACY_SilencedAlert[]> {
try {
return await monitorfishApiKy.get('/bff/v1/operational_alerts/silenced').json<SilencedAlert[]>()
} catch (err) {
throw new ApiError(ALERTS_ERROR_MESSAGE, err)
throw new FrontendApiError(ALERTS_ERROR_MESSAGE, (err as FrontendApiError).originalError)
}
}

/**
* Delete a silenced alert
*
* @throws {@link ApiError}
* @throws {@link FrontendApiError}
*/
async function deleteSilencedAlertFromAPI(id: string): Promise<void> {
try {
await monitorfishApiKy.delete(`/bff/v1/operational_alerts/silenced/${id}`)
} catch (err) {
throw new ApiError(DELETE_SILENCED_ALERT_ERROR_MESSAGE, err)
throw new FrontendApiError(DELETE_SILENCED_ALERT_ERROR_MESSAGE, (err as FrontendApiError).originalError)
}
}

Expand Down
Loading
Loading