diff --git a/server/package-lock.json b/server/package-lock.json index 31351520f..2f236a186 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -10,14 +10,14 @@ "dependencies": { "@navikt/arbeidsgiver-notifikasjoner-brukerapi-mock": "^6.2.2", "@navikt/nav-dekoratoren-moduler": "^2.1.5", + "@navikt/oasis": "^3.2.2", "axios": "^1.6.7", "cookie-parser": "^1.4.6", "express-async-handler": "1.2.0", "express-http-proxy": "^2.0.0", "helmet": "^7.1.0", "http-proxy-middleware": "^2.0.1", - "jsdom": "^24.0.0", - "openid-client": "^5.4.0" + "jsdom": "^24.0.0" }, "devDependencies": { "@types/cookie-parser": "^1.4.7", @@ -701,6 +701,33 @@ "react": "17.x || 18.x" } }, + "node_modules/@navikt/oasis": { + "version": "3.2.2", + "resolved": "https://npm.pkg.github.com/download/@navikt/oasis/3.2.2/388817639184717074cfa3a83cdf085b17193eea", + "integrity": "sha512-RQ6xf222NODKsNqPY7CqjQzPJhFi924vyf9lQ/YEd/xkPiFjM3NVpvMLfxtvK/7nPTSh1N4jHanjrmIOpybKLg==", + "license": "MIT", + "dependencies": { + "jose": "^5.2.2", + "openid-client": "^5.6.4", + "prom-client": "^15.1.0" + } + }, + "node_modules/@navikt/oasis/node_modules/jose": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-5.2.3.tgz", + "integrity": "sha512-KUXdbctm1uHVL8BYhnyHkgp3zDX5KW8ZhAKVFEfUbU2P8Alpzjb+48hHvjOdQIyPshoblhzsuqOwEEAbtHVirA==", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/@opentelemetry/api": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.8.0.tgz", + "integrity": "sha512-I/s6F7yKUDdtMsoBWXJe8Qz40Tui5vsuKCWJEWVL+5q9sSWRzzx6v2KeNsOBEwd94j0eWkpWCH4yB6rZg9Mf0w==", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -1219,6 +1246,11 @@ "proxy-from-env": "^1.1.0" } }, + "node_modules/bintrees": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bintrees/-/bintrees-1.0.2.tgz", + "integrity": "sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==" + }, "node_modules/body-parser": { "version": "1.20.2", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", @@ -2564,6 +2596,18 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/prom-client": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/prom-client/-/prom-client-15.1.0.tgz", + "integrity": "sha512-cCD7jLTqyPdjEPBo/Xk4Iu8jxjuZgZJ3e/oET3L+ZwOuap/7Cw3dH/TJSsZKs1TQLZ2IHpIlRAKw82ef06kmMw==", + "dependencies": { + "@opentelemetry/api": "^1.4.0", + "tdigest": "^0.1.1" + }, + "engines": { + "node": "^16 || ^18 || >=20" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -2847,6 +2891,14 @@ "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" }, + "node_modules/tdigest": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/tdigest/-/tdigest-0.1.2.tgz", + "integrity": "sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA==", + "dependencies": { + "bintrees": "1.0.2" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", diff --git a/server/package.json b/server/package.json index ffbbd0f1b..f5369ee89 100644 --- a/server/package.json +++ b/server/package.json @@ -13,14 +13,14 @@ "dependencies": { "@navikt/arbeidsgiver-notifikasjoner-brukerapi-mock": "^6.2.2", "@navikt/nav-dekoratoren-moduler": "^2.1.5", + "@navikt/oasis": "^3.2.2", "axios": "^1.6.7", "cookie-parser": "^1.4.6", "express-async-handler": "1.2.0", "express-http-proxy": "^2.0.0", "helmet": "^7.1.0", "http-proxy-middleware": "^2.0.1", - "jsdom": "^24.0.0", - "openid-client": "^5.4.0" + "jsdom": "^24.0.0" }, "devDependencies": { "@types/cookie-parser": "^1.4.7", diff --git a/server/src/login/azure.ts b/server/src/login/azure.ts deleted file mode 100644 index c9f516bb5..000000000 --- a/server/src/login/azure.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { BaseClient, ClientAuthMethod, Issuer } from 'openid-client'; - -const client = async (): Promise => { - const azureConfig = { - discoveryUrl: process.env.AZURE_APP_WELL_KNOWN_URL, - clientID: process.env.AZURE_APP_CLIENT_ID, - privateJwk: process.env.AZURE_APP_JWKS, - tokenEndpointAuthMethod: 'private_key_jwt', - tokenEndpointAuthSigningAlg: 'RS256', - redirectUri: process.env.AAD_REDIRECT_URL, - }; - const provider: Issuer = await Issuer.discover(azureConfig.discoveryUrl as string); - const jwk = JSON.parse(azureConfig.privateJwk as string); - - console.log(`Discovered issuer ${provider.issuer}`); - - return new provider.Client( - { - client_id: azureConfig.clientID as string, - token_endpoint_auth_method: azureConfig.tokenEndpointAuthMethod as ClientAuthMethod | undefined, - token_endpoint_auth_signing_alg: azureConfig.tokenEndpointAuthSigningAlg, - redirect_uris: [azureConfig.redirectUri as string], - }, - jwk, - ); -}; - -const azureTokenEndpoint = async (): Promise => { - const azureConfig = { - discoveryUrl: process.env.AZURE_APP_WELL_KNOWN_URL, - clientID: process.env.AZURE_APP_CLIENT_ID, - privateJwk: process.env.AZURE_APP_JWKS, - tokenEndpointAuthMethod: 'private_key_jwt', - }; - const provider: Issuer = await Issuer.discover(azureConfig.discoveryUrl as string); - - console.log(`Discovered issuer ${provider.issuer}`); - - return provider.token_endpoint; -}; - -const getOnBehalfOfAccessToken = async (azureClient: any, azureEndpointToken: any, req: any) => { - const bearerToken = req.headers.authorization.replace('Bearer', '').trim(); - const backendTokenSet = await azureClient.grant( - { - grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer', - client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer', - requested_token_use: 'on_behalf_of', - scope: process.env.API_SCOPE, - assertion: bearerToken, - }, - { - clientAssertionPayload: { - aud: [azureEndpointToken], - }, - }, - ); - return backendTokenSet.access_token; -}; -export default { client, azureTokenEndpoint, getOnBehalfOfAccessToken }; diff --git a/server/src/login/loginProvider.ts b/server/src/login/loginProvider.ts deleted file mode 100644 index 4626288f9..000000000 --- a/server/src/login/loginProvider.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { getMiljo, Miljo } from '../paths/miljo'; -import labsProxy from '../proxy/labs-proxy'; -import { BaseClient } from 'openid-client'; -import azure from './azure'; -import apiProxy from '../proxy/api-proxy'; -import decoratorInternProxy from '../proxy/decorator-intern-proxy'; -import tokenx from './tokenx'; -import notifikasjonProxy from '../proxy/notifikasjoner-proxy'; -import decoratorEksternProxy from '../proxy/decorator-ekstern-proxy'; -import { Express } from 'express'; - -const miljo: Miljo = getMiljo(); -const setupOauth2Clients = async (server: Express) => { - if (miljo === Miljo.DEV_GCP || miljo === Miljo.PROD_GCP) { - if (process.env.INTERN_INGRESS) { - return await initLoginAndProxyForInternal(server); - } - return await initLoginAndProxyForExternal(server); - } - return labsProxy.setup(server); -}; - -async function initLoginAndProxyForInternal(server: Express): Promise { - const client: BaseClient = await azure.client(); - const tokenEndpoint = await azure.azureTokenEndpoint(); - - apiProxy.azureSetup(server, client, tokenEndpoint); - decoratorInternProxy.setup(server, client, tokenEndpoint); - - console.log('Satt opp api-proxy med azure obh'); -} - -async function initLoginAndProxyForExternal(server: Express): Promise { - const tokenxAuthClient = await tokenx.client(); - - apiProxy.tokenxSetup(server, tokenxAuthClient); - notifikasjonProxy.setup(server, tokenxAuthClient); - decoratorEksternProxy.setup(server); - - console.log('Satt opp api-proxy med tokenx'); -} - -export default { setupOauth2Clients }; diff --git a/server/src/login/tokenx.ts b/server/src/login/tokenx.ts deleted file mode 100644 index 80cb122db..000000000 --- a/server/src/login/tokenx.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { BaseClient, ClientAuthMethod, Issuer } from 'openid-client'; - -interface TokenxConfig { - discoveryUrl: string; - clientID: string; - privateJwk: string; - tokenEndpointAuthMethod: ClientAuthMethod; -} - -const client = async (): Promise => { - const tokenxConfig: TokenxConfig = { - discoveryUrl: process.env.TOKEN_X_WELL_KNOWN_URL as string, - clientID: process.env.TOKEN_X_CLIENT_ID as string, - privateJwk: process.env.TOKEN_X_PRIVATE_JWK as string, - tokenEndpointAuthMethod: 'private_key_jwt', - }; - - const issuer: Issuer = await Issuer.discover(tokenxConfig.discoveryUrl); - const jwk = JSON.parse(tokenxConfig.privateJwk); - - console.log(`Discovered issuer ${issuer.issuer}`); - - return new issuer.Client( - { - client_id: tokenxConfig.clientID as string, - token_endpoint_auth_method: tokenxConfig.tokenEndpointAuthMethod, - }, - { keys: [jwk] }, - ); -}; - -const getTokenExchangeAccessToken = async (tokenxClient: any, audience: any, req: any) => { - const now: number = Math.floor(Date.now() / 1000); - const additionalClaims = { - clientAssertionPayload: { - nbf: now, - // Må overstyre openid-client sånn at den ikke legger til to url her, kun selve tokenendepunktet - aud: [tokenxClient?.issuer.metadata.token_endpoint], - }, - }; - const bearerToken = req.headers['authorization'].replace('Bearer', '').trim(); - const backendTokenSet = await tokenxClient.grant( - { - grant_type: 'urn:ietf:params:oauth:grant-type:token-exchange', - client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer', - subject_token_type: 'urn:ietf:params:oauth:token-type:jwt', - audience: audience, - subject_token: bearerToken, - }, - additionalClaims, - ); - - return backendTokenSet.access_token; -}; -export default { client, getTokenExchangeAccessToken }; diff --git a/server/src/proxy/api-proxy.ts b/server/src/proxy/api-proxy.ts index e5b897141..41dc30cf7 100644 --- a/server/src/proxy/api-proxy.ts +++ b/server/src/proxy/api-proxy.ts @@ -1,57 +1,21 @@ -import tokenx from '../login/tokenx'; -import azure from '../login/azure'; +import { requestOboToken } from '../auth'; import { Express } from 'express'; -import { BaseClient } from 'openid-client'; import { Request } from 'express-serve-static-core'; import { createProxyMiddleware } from 'http-proxy-middleware'; import proxy from 'express-http-proxy'; import { ParsedQs } from 'qs'; -const tokenxSetup = (app: Express, tokenxClient: BaseClient): void => { +const tokenxSetup = (app: Express): void => { console.log('api-proxy setup for tokenx'); - - setupPath(app); - - app.use( - '/tiltaksgjennomforing/api', - proxy(process.env.APIGW_URL as string, { - proxyReqPathResolver: (req: Request<{}, any, any, ParsedQs, Record>) => { - return req.originalUrl.replace('/tiltaksgjennomforing/api', '/tiltaksgjennomforing-api'); - }, - proxyReqOptDecorator: async (options: any, req: Request<{}, any, any, ParsedQs, Record>) => { - const accessToken = await tokenx.getTokenExchangeAccessToken( - tokenxClient, - process.env.API_AUDIENCE, - req, - ); - options.headers.Authorization = `Bearer ${accessToken}`; - return options; - }, - }), - ); + setup(app, process.env.API_AUDIENCE!); }; -const azureSetup = (app: Express, azureClient: BaseClient, azureTokenEndpoint: any): void => { +const azureSetup = (app: Express): void => { console.log('api-proxy setup for azure'); - - setupPath(app); - - app.use( - '/tiltaksgjennomforing/api', - proxy(process.env.APIGW_URL as string, { - proxyReqPathResolver: (req: Request<{}, any, any, ParsedQs, Record>) => { - return req.originalUrl.replace('/tiltaksgjennomforing/api', '/tiltaksgjennomforing-api'); - }, - proxyReqOptDecorator: async (options: any, req: Request<{}, any, any, ParsedQs, Record>) => { - const accessToken = await azure.getOnBehalfOfAccessToken(azureClient, azureTokenEndpoint, req); - options.headers.Authorization = `Bearer ${accessToken}`; - return options; - }, - }), - ); + setup(app, process.env.API_SCOPE!); }; -function setupPath(app: Express) { +const setup = (app: Express, audience: string) => { app.use('/tiltaksgjennomforing/api/internal', (req, res) => { res.status(401).send(); }); @@ -74,6 +38,20 @@ function setupPath(app: Express) { proxyTimeout: 10000, }), ); -} + + app.use( + '/tiltaksgjennomforing/api', + proxy(process.env.APIGW_URL as string, { + proxyReqPathResolver: (req: Request<{}, any, any, ParsedQs, Record>) => { + return req.originalUrl.replace('/tiltaksgjennomforing/api', '/tiltaksgjennomforing-api'); + }, + proxyReqOptDecorator: async (options: any, req: Request<{}, any, any, ParsedQs, Record>) => { + const accessToken = await requestOboToken(audience, req); + options.headers.Authorization = `Bearer ${accessToken}`; + return options; + }, + }), + ); +}; export default { tokenxSetup, azureSetup }; diff --git a/server/src/proxy/decorator-intern-proxy.ts b/server/src/proxy/decorator-intern-proxy.ts index 697fea2d6..e26c8fda3 100644 --- a/server/src/proxy/decorator-intern-proxy.ts +++ b/server/src/proxy/decorator-intern-proxy.ts @@ -1,12 +1,11 @@ import proxy from 'express-http-proxy'; -import onbehalfof from '../login/azure'; -import { BaseClient } from 'openid-client'; import { Express, Response } from 'express'; import { Request } from 'express-serve-static-core'; import { ParsedQs } from 'qs'; import { IncomingMessage, RequestOptions } from 'http'; +import { requestOboToken } from '../auth'; -const setup = (app: Express, azureClient: BaseClient, azureTokenEndpoint: BaseClient) => { +const setup = (app: Express) => { app.use( '/modiacontextholder/api/decorator', proxy(process.env.APIGW_URL as string, { @@ -20,7 +19,7 @@ const setup = (app: Express, azureClient: BaseClient, azureTokenEndpoint: BaseCl options: RequestOptions, req: Request<{}, any, any, ParsedQs, Record>, ) => { - const accessToken = await onbehalfof.getOnBehalfOfAccessToken(azureClient, azureTokenEndpoint, req); + const accessToken = await requestOboToken(process.env.API_SCOPE!, req); if (options?.headers) { options.headers.Authorization = `Bearer ${accessToken}`; let cookies = options.headers.cookie; diff --git a/server/src/proxy/notifikasjoner-proxy.ts b/server/src/proxy/notifikasjoner-proxy.ts index 367138f3d..906d259d2 100644 --- a/server/src/proxy/notifikasjoner-proxy.ts +++ b/server/src/proxy/notifikasjoner-proxy.ts @@ -1,12 +1,11 @@ -import { BaseClient } from 'openid-client'; import { Express, NextFunction } from 'express'; import proxy from 'express-http-proxy'; -import tokenx from '../login/tokenx'; +import { requestOboToken } from '../auth'; import { Request } from 'express-serve-static-core'; import { ParsedQs } from 'qs'; import { RequestOptions } from 'http'; -const setup = (app: Express, tokenxClient: BaseClient): void => { +const setup = (app: Express): void => { app.use('/tiltaksgjennomforing/notifikasjon-bruker-api', (req, res, next: NextFunction) => { if (!req.headers['authorization']) { res.status(401).send(); @@ -26,11 +25,7 @@ const setup = (app: Express, tokenxClient: BaseClient): void => { req: Request<{}, any, any, ParsedQs, Record>, ) => { if (options.headers) { - const accessToken = await tokenx.getTokenExchangeAccessToken( - tokenxClient, - process.env.NOTIFIKASJON_AUDIENCE, - req, - ); + const accessToken = await requestOboToken(process.env.NOTIFIKASJON_AUDIENCE!, req); options.headers.Authorization = `Bearer ${accessToken}`; } return options; diff --git a/server/src/server.ts b/server/src/server.ts index ac0a151f4..4989baf25 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -8,7 +8,7 @@ import cookieParser from 'cookie-parser'; import appMedModiaDekoratoren from './dekorator/appMedModiaDekoratoren'; import appMedNavDekoratoren from './dekorator/appMedNavDekoratoren'; -import loginProvider from './login/loginProvider'; +import { setupRoutes } from './routes'; import setupPath, { BASEPATH, STATIC_PATHS } from './paths/setupPath'; import { getEnv } from './paths/miljo'; @@ -54,7 +54,7 @@ async function startServer(): Promise { setupPath.initializePath(node); setStaticPath(); - await loginProvider.setupOauth2Clients(node); + setupRoutes(node); console.log('ferdig med oauth client setup.');