From d826743fe05bf22f7350efb3f6995b53dc3c81c3 Mon Sep 17 00:00:00 2001 From: geir-waagboe Date: Fri, 13 Sep 2024 13:57:22 +0200 Subject: [PATCH] IS-2300: internarbeidsflate-decorator-v3 --- .env.template | 1 - .nais/naiserator-dev.yaml | 5 -- .nais/naiserator-prod.yaml | 5 -- public/index.html | 8 +-- server/authUtils.ts | 5 +- server/config.ts | 15 +---- server/proxy.ts | 23 ------- src/api/axios.ts | 20 ++++++ src/api/constants.ts | 1 + src/data/modiacontext/useAktivBruker.ts | 14 ++++ src/decorator/Decorator.tsx | 15 +++-- src/decorator/decoratorProps.ts | 87 +++++++++++++++---------- src/decorator/decoratorconfig.ts | 68 +++++++++++-------- src/faro.ts | 4 +- src/utils/fnrValideringUtil.ts | 4 +- src/utils/miljoUtil.ts | 24 +++++-- 16 files changed, 171 insertions(+), 128 deletions(-) create mode 100644 src/data/modiacontext/useAktivBruker.ts diff --git a/.env.template b/.env.template index 46c84eb..234b9d3 100644 --- a/.env.template +++ b/.env.template @@ -1,7 +1,6 @@ NODE_ENV=development SESSION_KEY=dette-er-en-lang-og-sikker-streng-for-signering-av-session-cookies AUTH_REDIRECT_URI=http://localhost:8080/oauth2/callback -INTERNARBEIDSFLATEDECORATOR_HOST=internarbeidsflatedecorator-q1.dev.intern.nav.no MODIACONTEXTHOLDER_HOST=modiacontextholder.dev.intern.nav.no MODIACONTEXTHOLDER_AAD_APP_CLIENT_ID=dev-gcp.personoversikt.modiacontextholder FASTLEGEREST_HOST=https://fastlegerest.intern.dev.nav.no diff --git a/.nais/naiserator-dev.yaml b/.nais/naiserator-dev.yaml index 05375dc..37af269 100644 --- a/.nais/naiserator-dev.yaml +++ b/.nais/naiserator-dev.yaml @@ -31,12 +31,8 @@ spec: value: production - name: REDIS_HOST value: finnfastlege-redis - - name: FINNFASTLEGE_URL - value: "http://finnfastlege" - name: AUTH_REDIRECT_URI value: "https://finnfastlege.intern.dev.nav.no/oauth2/callback" - - name: INTERNARBEIDSFLATEDECORATOR_HOST - value: "internarbeidsflatedecorator-q1.dev-fss-pub.nais.io" - name: FASTLEGEREST_AAD_APP_CLIENT_ID value: "dev-gcp.teamsykefravr.fastlegerest" - name: SYFOPERSON_AAD_APP_CLIENT_ID @@ -63,7 +59,6 @@ spec: outbound: external: - host: "login.microsoftonline.com" - - host: "internarbeidsflatedecorator-q1.dev-fss-pub.nais.io" rules: - application: fastlegerest - application: istilgangskontroll diff --git a/.nais/naiserator-prod.yaml b/.nais/naiserator-prod.yaml index ea8a573..939f9b5 100644 --- a/.nais/naiserator-prod.yaml +++ b/.nais/naiserator-prod.yaml @@ -31,12 +31,8 @@ spec: value: production - name: REDIS_HOST value: finnfastlege-redis - - name: FINNFASTLEGE_URL - value: "http://finnfastlege" - name: AUTH_REDIRECT_URI value: "https://finnfastlege.intern.nav.no/oauth2/callback" - - name: INTERNARBEIDSFLATEDECORATOR_HOST - value: "internarbeidsflatedecorator.prod-fss-pub.nais.io" - name: FASTLEGEREST_AAD_APP_CLIENT_ID value: "prod-gcp.teamsykefravr.fastlegerest" - name: SYFOPERSON_AAD_APP_CLIENT_ID @@ -63,7 +59,6 @@ spec: outbound: external: - host: "login.microsoftonline.com" - - host: "internarbeidsflatedecorator.prod-fss-pub.nais.io" rules: - application: fastlegerest - application: istilgangskontroll diff --git a/public/index.html b/public/index.html index 400c6e1..13800fc 100644 --- a/public/index.html +++ b/public/index.html @@ -6,16 +6,16 @@ <% if (process.env.NODE_ENV !== 'development') { %> - + <% } %> <% if (process.env.NODE_ENV === 'development') { %> - + <% } %> diff --git a/server/authUtils.ts b/server/authUtils.ts index 0631df3..301c41a 100644 --- a/server/authUtils.ts +++ b/server/authUtils.ts @@ -34,7 +34,7 @@ async function initJWKSet() { _remoteJWKSet = await createRemoteJWKSet(new URL(Config.auth.jwksUri)); } -const retrieveToken = async ( +const retrieveAndValidateToken = async ( req: Request, azureAdIssuer: OpenIdClient.Issuer ): Promise => { @@ -95,7 +95,7 @@ export const getOrRefreshOnBehalfOfToken = async ( req: Request, clientId: string ): Promise => { - const token = await retrieveToken(req, issuer); + const token = await retrieveAndValidateToken(req, issuer); if (!token) { throw Error( "Could not get on-behalf-of token because the token was undefined" @@ -104,6 +104,7 @@ export const getOrRefreshOnBehalfOfToken = async ( if (req.session.tokenCache === undefined) { req.session.tokenCache = {}; } + let cachedOboToken = req.session.tokenCache[clientId]; if (cachedOboToken && isNotExpired(cachedOboToken)) { return cachedOboToken.token; diff --git a/server/config.ts b/server/config.ts index ec6ad27..627a3b7 100644 --- a/server/config.ts +++ b/server/config.ts @@ -43,16 +43,6 @@ export interface ExternalAppConfig { export const server = { host: envVar({ name: "HOST", defaultValue: "localhost" }), port: Number.parseInt(envVar({ name: "PORT", defaultValue: "8080" })), - finnfastlegeUrl: envVar({ - name: "FINNFASTLEGE_URL", - defaultValue: "http://localhost:8080", - }), - - frontendDir: envVar({ - name: "FRONTEND_DIR", - defaultValue: path.join(__dirname, "frontend"), - }), - sessionKey: envVar({ name: "SESSION_KEY" }), sessionCookieName: envVar({ name: "SESSION_COOKIE_NAME", @@ -109,10 +99,6 @@ export const auth = { responseMode: "query", tokenEndpointAuthSigningAlg: "RS256", - internarbeidsflatedecoratorHost: envVar({ - name: "INTERNARBEIDSFLATEDECORATOR_HOST", - }), - modiacontextholder: { applicationName: "modiacontextholder", clientId: envVar({ @@ -121,6 +107,7 @@ export const auth = { host: envVar({ name: "MODIACONTEXTHOLDER_HOST", }), + removePathPrefix: true, }, fastlegerest: { diff --git a/server/proxy.ts b/server/proxy.ts index 5162ee8..6e5f5c0 100644 --- a/server/proxy.ts +++ b/server/proxy.ts @@ -168,28 +168,5 @@ export const setupProxy = ( ); } ); - - router.use( - "/internarbeidsflatedecorator", - expressHttpProxy(Config.auth.internarbeidsflatedecoratorHost, { - https: true, - proxyReqPathResolver: (req) => { - return `/internarbeidsflatedecorator${req.url}`; - }, - proxyErrorHandler: (err, res, next) => { - console.log( - `Error in proxy for internarbeidsflatedecorator ${err.message}, ${err.code}` - ); - if (err && err.code === "ECONNREFUSED") { - console.log("proxyErrorHandler: Got ECONNREFUSED"); - return res - .status(503) - .send({ message: `Could not contact internarbeidsflatedecorator` }); - } - next(err); - }, - }) - ); - return router; }; diff --git a/src/api/axios.ts b/src/api/axios.ts index 0a4a57d..8639cce 100644 --- a/src/api/axios.ts +++ b/src/api/axios.ts @@ -75,3 +75,23 @@ export const get = ( } }); }; + +export const post = ( + url: string, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + data: Record | Record[], + personIdent?: string +): Promise => { + return axios + .post(url, data, { + headers: defaultRequestHeaders(personIdent), + }) + .then((response) => response.data) + .catch(function (error) { + if (axios.isAxiosError(error)) { + handleAxiosError(error); + } else { + throw new ApiErrorException(generalError(error), error.code); + } + }); +}; diff --git a/src/api/constants.ts b/src/api/constants.ts index fb4430d..ce47fd6 100644 --- a/src/api/constants.ts +++ b/src/api/constants.ts @@ -1,3 +1,4 @@ +export const MODIACONTEXTHOLDER_ROOT = "/modiacontextholder/api"; export const SYFOPERSON_ROOT = "/syfoperson/api/v2"; export const FASTLEGEREST_ROOT = "/fastlegerest/api/v2"; export const TILGANGSKONTROLL_AD_PATH = diff --git a/src/data/modiacontext/useAktivBruker.ts b/src/data/modiacontext/useAktivBruker.ts new file mode 100644 index 0000000..60c7714 --- /dev/null +++ b/src/data/modiacontext/useAktivBruker.ts @@ -0,0 +1,14 @@ +import { post } from "@/api/axios"; +import { MODIACONTEXTHOLDER_ROOT } from "@/api/constants"; +import { useMutation } from "@tanstack/react-query"; + +const NY_AKTIV_BRUKER = "NY_AKTIV_BRUKER"; + +export const useAktivBruker = () => + useMutation({ + mutationFn: (fnr: string) => + post(`${MODIACONTEXTHOLDER_ROOT}/context`, { + verdi: fnr, + eventType: NY_AKTIV_BRUKER, + }), + }); diff --git a/src/decorator/Decorator.tsx b/src/decorator/Decorator.tsx index 787b313..1b1e203 100644 --- a/src/decorator/Decorator.tsx +++ b/src/decorator/Decorator.tsx @@ -3,16 +3,23 @@ import NAVSPA from "@navikt/navspa"; import { DecoratorProps } from "./decoratorProps"; import decoratorConfig from "./decoratorconfig"; import { fullNaisUrlDefault } from "@/utils/miljoUtil"; +import { useAktivBruker } from "@/data/modiacontext/useAktivBruker"; const InternflateDecorator = NAVSPA.importer( - "internarbeidsflatefs" + "internarbeidsflate-decorator-v3" ); const Decorator = () => { + const aktivBruker = useAktivBruker(); + const handlePersonsokSubmit = (nyttFnr: string) => { - const host = "syfomodiaperson"; - const path = `/sykefravaer/${nyttFnr}`; - window.location.href = fullNaisUrlDefault(host, path); + aktivBruker.mutate(nyttFnr, { + onSuccess: () => { + const host = "syfomodiaperson"; + const path = `/sykefravaer`; + window.location.href = fullNaisUrlDefault(host, path); + }, + }); }; const config = useCallback(decoratorConfig, [handlePersonsokSubmit])( diff --git a/src/decorator/decoratorProps.ts b/src/decorator/decoratorProps.ts index c10492a..625c897 100644 --- a/src/decorator/decoratorProps.ts +++ b/src/decorator/decoratorProps.ts @@ -1,48 +1,67 @@ -interface TogglesConfig { - visVeileder: boolean; +export interface DecoratorProps { + enhet?: string | undefined; // Konfigurasjon av enhet-kontekst + accessToken?: string | undefined; // Manuell innsending av JWT, settes som Authorization-header. Om null sendes cookies vha credentials: 'include' + fnr?: string | undefined; // Konfigurasjon av fødselsnummer-kontekst + userKey?: string | undefined; // Om man ikke ønsker å bruke fnr i urler, kan andre apper kalle contextholder for å generere en midlertidig kode. Hvis App A skal navigere til App B som har dekoratøren, må App A først sende en post request til /fnr-code/generate med {fnr: string} i bodyen, dette returnerer {fnr: string, code: string} til App A. App A kan så navigere til App B og sende med denne koden. App B kan så sende den koden inn til dekoratøren i userKey propen og så henter dekoratøren fnr for den koden fra contextholderen. + enableHotkeys?: boolean | undefined; // Aktivere hurtigtaster + fetchActiveEnhetOnMount?: boolean | undefined; // Om enhet er undefined fra container appen, og denne er satt til true, henter den sist aktiv enhet og bruker denne. + fetchActiveUserOnMount?: boolean | undefined; // Om fnr er undefined fra container appen, og denne er satt til true for at den skal hente siste aktiv fnr. + onEnhetChanged: (enhetId?: string | null, enhet?: Enhet) => void; // Kalles når enheten endres + onFnrChanged: (fnr?: string | null) => void; // Kalles når fnr enheten endres + onLinkClick?: (link: { text: string; url: string }) => void; // Kan brukes for å legge til callbacks ved klikk på lenker i menyen. Merk at callbacken ikke kan awaites og man må selv håndtere at siden lukkes. Nyttig for å f.eks tracke navigasjon events i amplitude + appName: string; // Navn på applikasjonen + hotkeys?: Hotkey[]; // Konfigurasjon av hurtigtaster + markup?: Markup; // Egen HTML + showEnheter: boolean; // Vis enheter + showSearchArea: boolean; // Vis søkefelt + showHotkeys: boolean; // Vis hurtigtaster + environment: Environment; // Miljø som skal brukes. + urlFormat: UrlFormat; // URL format + proxy?: string | undefined; // Manuell overstyring av urlene til BFFs. Gjør alle kall til relativt path hvis true, og bruker verdien som domene om satt til en string. Default: false } -interface Markup { - etterSokefelt?: string; +export interface Markup { + etterSokefelt?: string; // Gir muligheten for sende inn egen html som blir en del av dekoratøren } -export interface ControlledContextvalue extends BaseContextvalue { - value?: string; -} -interface UncontrolledContextvalue extends BaseContextvalue { - initialValue?: string; +export interface Enhet { + readonly enhetId: string; + readonly navn: string; } -interface BaseContextvalue { - display: T; - onChange(value?: string): void; - skipModal?: boolean; - ignoreWsEvents?: boolean; -} +// Miljø +export type Environment = + | "q0" + | "q1" + | "q2" + | "q3" + | "q4" + | "prod" + | "local" + | "mock"; -export type Contextvalue = - | ControlledContextvalue - | UncontrolledContextvalue; +export type UrlFormat = "LOCAL" | "NAV_NO" | "ANSATT"; // UrlFormat. Brukes om proxy ikke er satt & i url til websocket. -export enum EnhetDisplay { - ENHET = "ENHET", - ENHET_VALG = "ENHET_VALG", +export interface HotkeyObject { + char: string; + altKey?: boolean; + ctrlKey?: boolean; + metaKey?: boolean; + shiftKey?: boolean; } -export enum FnrDisplay { - SOKEFELT = "SOKEFELT", +export interface HotkeyDescription { + key: HotkeyObject; + description: string; + forceOverride?: boolean; } -type EnhetContextvalue = Contextvalue; -type FnrContextvalue = Contextvalue; -type ProxyConfig = boolean | string; +export interface ActionHotKey extends HotkeyDescription { + action(event: KeyboardEvent): void; +} -export interface DecoratorProps { - appname: string; - fnr?: FnrContextvalue; - enhet?: EnhetContextvalue; - toggles?: TogglesConfig; - markup?: Markup; - useProxy?: ProxyConfig; - accessToken?: string; +export interface DocumentingHotKey extends HotkeyDescription { + documentationOnly: boolean; } + +export type Hotkey = ActionHotKey | DocumentingHotKey; diff --git a/src/decorator/decoratorconfig.ts b/src/decorator/decoratorconfig.ts index 13f97e2..cf6ac3a 100644 --- a/src/decorator/decoratorconfig.ts +++ b/src/decorator/decoratorconfig.ts @@ -1,34 +1,48 @@ -import { DecoratorProps, EnhetDisplay, FnrDisplay } from "./decoratorProps"; +import { + DecoratorProps, + Enhet, + Environment, + UrlFormat, +} from "./decoratorProps"; +import { erAnsattDev, erDev, erLokal, erProd } from "@/utils/miljoUtil"; -const RESET_VALUE = "\u0000"; - -const decoratorconfig = (setFnr: (fnr: string) => void): DecoratorProps => { +const decoratorConfig = (setFnr: (fnr: string) => void): DecoratorProps => { return { - appname: "Sykefraværsoppfølging", - fnr: { - initialValue: RESET_VALUE, - display: FnrDisplay.SOKEFELT, - ignoreWsEvents: true, - skipModal: true, - onChange: (value) => { - if (value) { - setFnr(value); - } - }, - }, - enhet: { - initialValue: undefined, - display: EnhetDisplay.ENHET_VALG, - onChange(): void { - /* Do nothing */ - }, - skipModal: true, + appName: "Sykefraværsoppfølging", + fetchActiveEnhetOnMount: false, + onEnhetChanged: (enhetId?: string | null, enhet?: Enhet) => { + // do nothing }, - toggles: { - visVeileder: true, + onFnrChanged: (fnr?: string | null) => { + if (fnr) { + setFnr(fnr); + } }, - useProxy: true, + showEnheter: true, + showSearchArea: true, + showHotkeys: false, + environment: getEnvironment(), + urlFormat: getUrlFormat(), + proxy: "/modiacontextholder", }; }; -export default decoratorconfig; +const getEnvironment = (): Environment => { + if (erProd()) { + return "prod"; + } else if (erDev()) { + return "q2"; + } else { + return "local"; + } +}; + +const getUrlFormat = (): UrlFormat => { + if (erAnsattDev()) { + return "ANSATT"; + } else if (erLokal()) { + return "LOCAL"; + } else return "NAV_NO"; +}; + +export default decoratorConfig; diff --git a/src/faro.ts b/src/faro.ts index 9c1ec11..4c50ae8 100644 --- a/src/faro.ts +++ b/src/faro.ts @@ -1,11 +1,11 @@ import { getWebInstrumentations, initializeFaro } from "@grafana/faro-react"; import { TracingInstrumentation } from "@grafana/faro-web-tracing"; -import { erLokal, erPreProd } from "@/utils/miljoUtil"; +import { erLokal, erDev } from "@/utils/miljoUtil"; const getUrl = () => { if (erLokal()) { return "/collect"; - } else if (erPreProd()) { + } else if (erDev()) { return "https://telemetry.ekstern.dev.nav.no/collect"; } else { return "https://telemetry.nav.no/collect"; diff --git a/src/utils/fnrValideringUtil.ts b/src/utils/fnrValideringUtil.ts index f32c0b5..23478e7 100644 --- a/src/utils/fnrValideringUtil.ts +++ b/src/utils/fnrValideringUtil.ts @@ -1,4 +1,4 @@ -import { erPreProd } from "@/utils/miljoUtil"; +import { erDev } from "@/utils/miljoUtil"; const kontrollRekke1 = [3, 7, 6, 1, 8, 9, 4, 5, 2]; const kontrollRekke2 = [5, 4, 3, 2, 7, 6, 5, 4, 3, 2]; @@ -35,7 +35,7 @@ const erGyldigSkatteetatenTestdato = (dag: number, maned: number) => { const erGyldigTestdato = (dag: number, maned: number) => { return ( - erPreProd() && + erDev() && (erGyldigNavTestdato(dag, maned) || erGyldigSkatteetatenTestdato(dag, maned)) ); diff --git a/src/utils/miljoUtil.ts b/src/utils/miljoUtil.ts index 911a27d..a26c335 100644 --- a/src/utils/miljoUtil.ts +++ b/src/utils/miljoUtil.ts @@ -1,16 +1,30 @@ -export const erPreProd = () => { +export const erDev = (): boolean => { return ( + window.location.href.indexOf("dev.intern.nav.no") > -1 || window.location.href.indexOf("intern.dev.nav.no") > -1 || - window.location.href.indexOf("dev.intern.nav.no") > -1 + erAnsattDev() ); }; -export const erLokal = () => { +export const erAnsattDev = (): boolean => { + return window.location.href.indexOf("ansatt.dev.nav.no") > -1; +}; + +export const erLokal = (): boolean => { return window.location.host.indexOf("localhost") > -1; }; +export function erProd(): boolean { + return window.location.href.indexOf("finnfastlege.intern.nav.no") > -1; +} -export const finnNaisUrlDefault = () => { - return erPreProd() ? ".dev.intern.nav.no" : ".intern.nav.no"; +const finnNaisUrlDefault = (): string => { + if (erAnsattDev()) { + return ".ansatt.dev.nav.no"; + } else if (erDev()) { + return ".intern.dev.nav.no"; + } else { + return ".intern.nav.no"; + } }; export const fullNaisUrlDefault = (host: string, path: string) => {