From 13ff8093cdc44f0ec199e7085e1d775cfba21df0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eir=C3=ADkur=20Hei=C3=B0ar=20Nilsson?= Date: Sun, 9 Jan 2022 10:28:10 +0000 Subject: [PATCH] chore(assets): Configure token exchange for Properties API (#6131) * Refactor assets configuration * Configure token exchange in properties api * Remove properties scope from service portal * Charts * Feedback * Fixes * Format Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- apps/api/src/app/app.module.ts | 17 +++--- apps/api/src/app/environments/environment.ts | 15 ------ apps/service-portal/src/auth.ts | 1 - charts/islandis/values.dev.yaml | 2 + charts/islandis/values.prod.yaml | 2 + charts/islandis/values.staging.yaml | 2 + infra/src/dsl/xroad.ts | 10 ++++ .../src/lib/api-domains-assets.module.ts | 43 +++------------ .../src/lib/api-domains-assets.service.ts | 2 - .../lib/authorization-identity.middleware.ts | 21 -------- libs/clients/assets/src/index.ts | 6 +-- .../assets/src/lib/PropertiesApiProvider.ts | 48 +++++++++++++++++ libs/clients/assets/src/lib/assets.config.ts | 53 +++++++++++++++++++ libs/clients/assets/src/lib/assets.module.ts | 48 +++-------------- libs/nest/config/src/index.ts | 3 +- .../nest/config/src/lib/LazyDuringDevScope.ts | 11 ++++ 16 files changed, 154 insertions(+), 130 deletions(-) delete mode 100644 libs/api/domains/assets/src/lib/authorization-identity.middleware.ts create mode 100644 libs/clients/assets/src/lib/PropertiesApiProvider.ts create mode 100644 libs/clients/assets/src/lib/assets.config.ts create mode 100644 libs/nest/config/src/lib/LazyDuringDevScope.ts diff --git a/apps/api/src/app/app.module.ts b/apps/api/src/app/app.module.ts index f961133e405e..94dd8581c765 100644 --- a/apps/api/src/app/app.module.ts +++ b/apps/api/src/app/app.module.ts @@ -35,15 +35,16 @@ import { ApiDomainsPaymentModule } from '@island.is/api/domains/payment' import { LicenseServiceModule } from '@island.is/api/domains/license-service' import { IslykillModule } from '@island.is/api/domains/islykill' import { PaymentScheduleModule } from '@island.is/api/domains/payment-schedule' +import { AssetsClientConfig } from '@island.is/clients/assets' +import { AuthPublicApiClientConfig } from '@island.is/clients/auth-public-api' import { NationalRegistryClientConfig } from '@island.is/clients/national-registry-v2' import { AuditModule } from '@island.is/nest/audit' import { ConfigModule, XRoadConfig } from '@island.is/nest/config' +import { FeatureFlagConfig } from '@island.is/nest/feature-flags' import { ProblemModule } from '@island.is/nest/problem' import { CriminalRecordModule } from '@island.is/api/domains/criminal-record' import { maskOutFieldsMiddleware } from './graphql.middleware' -import { AuthPublicApiClientConfig } from '@island.is/clients/auth-public-api' -import { FeatureFlagConfig } from '@island.is/nest/feature-flags' const debug = process.env.NODE_ENV === 'development' const playground = debug || process.env.GQL_PLAYGROUND_ENABLED === 'true' @@ -202,12 +203,7 @@ const autoSchemaFile = environment.production xroadBaseUrl: environment.xroad.baseUrl, xroadClientId: environment.xroad.clientId, }), - AssetsModule.register({ - xRoadBasePathWithEnv: environment.propertiesXRoad.url, - xRoadAssetsMemberCode: environment.propertiesXRoad.memberCode, - xRoadAssetsApiPath: environment.propertiesXRoad.apiPath, - xRoadClientId: environment.propertiesXRoad.clientId, - }), + AssetsModule, NationalRegistryXRoadModule, ApiDomainsPaymentModule.register({ xRoadProviderId: environment.paymentDomain.xRoadProviderId, @@ -258,11 +254,12 @@ const autoSchemaFile = environment.production ConfigModule.forRoot({ isGlobal: true, load: [ - XRoadConfig, - NationalRegistryClientConfig, + AssetsClientConfig, AuthPublicApiClientConfig, FeatureFlagConfig, + NationalRegistryClientConfig, SyslumennClientConfig, + XRoadConfig, ], }), ], diff --git a/apps/api/src/app/environments/environment.ts b/apps/api/src/app/environments/environment.ts index e2a18a4f4a5c..c34613e86434 100644 --- a/apps/api/src/app/environments/environment.ts +++ b/apps/api/src/app/environments/environment.ts @@ -105,12 +105,6 @@ const prodConfig = () => ({ endorsementSystem: { baseApiUrl: process.env.ENDORSEMENT_SYSTEM_BASE_API_URL, }, - propertiesXRoad: { - url: process.env.XROAD_BASE_PATH_WITH_ENV, - memberCode: process.env.XROAD_TJODSKRA_MEMBER_CODE, - apiPath: process.env.XROAD_PROPERTIES_API_PATH, - clientId: process.env.XROAD_CLIENT_ID, - }, paymentDomain: { xRoadBaseUrl: process.env.XROAD_BASE_PATH, xRoadProviderId: process.env.XROAD_PAYMENT_PROVIDER_ID, @@ -273,15 +267,6 @@ const devConfig = () => ({ endorsementSystem: { baseApiUrl: 'http://localhost:4246', }, - propertiesXRoad: { - url: - process.env.XROAD_BASE_PATH_WITH_ENV ?? 'http://localhost:8081/r1/IS-DEV', - memberCode: process.env.XROAD_TJODSKRA_MEMBER_CODE ?? '10001', - apiPath: - process.env.XROAD_PROPERTIES_API_PATH ?? '/SKRA-Protected/Fasteignir-v1', - clientId: - process.env.XROAD_CLIENT_ID ?? 'IS-DEV/GOV/10000/island-is-client', - }, paymentDomain: { xRoadBaseUrl: process.env.XROAD_BASE_PATH, xRoadProviderId: diff --git a/apps/service-portal/src/auth.ts b/apps/service-portal/src/auth.ts index a42947f01908..aa6904160902 100644 --- a/apps/service-portal/src/auth.ts +++ b/apps/service-portal/src/auth.ts @@ -44,7 +44,6 @@ if (userMocked) { AuthScope.readDelegations, AuthScope.writeDelegations, NationalRegistryScope.individuals, - NationalRegistryScope.properties, DocumentsScope.main, EndorsementsScope.main, EndorsementsScope.admin, diff --git a/charts/islandis/values.dev.yaml b/charts/islandis/values.dev.yaml index dbe829be354a..cac02ffda8ac 100644 --- a/charts/islandis/values.dev.yaml +++ b/charts/islandis/values.dev.yaml @@ -206,6 +206,7 @@ api: XROAD_PAYMENT_BASE_CALLBACK_URL: 'XROAD:/IS-DEV/GOV/10000/island-is/application-payment-v1/' XROAD_PAYMENT_PROVIDER_ID: 'IS-DEV/GOV/10021/FJS-Public' XROAD_PROPERTIES_API_PATH: '/SKRA-Protected/Fasteignir-v1' + XROAD_PROPERTIES_SERVICE_PATH: 'IS-DEV/GOV/10001/SKRA-Protected/Fasteignir-v1' XROAD_TJODSKRA_API_PATH: '/SKRA-Protected/Einstaklingar-v1' XROAD_TJODSKRA_MEMBER_CODE: '10001' XROAD_TLS_BASE_PATH: 'https://securityserver.dev01.devland.is' @@ -300,6 +301,7 @@ api: XROAD_HEALTH_INSURANCE_V2_XROAD_USERNAME: '/k8s/api/HEALTH_INSURANCE_V2_XROAD_USERNAME' XROAD_PAYMENT_PASSWORD: '/k8s/application-system-api/PAYMENT_PASSWORD' XROAD_PAYMENT_USER: '/k8s/application-system-api/PAYMENT_USER' + XROAD_PROPERTIES_CLIENT_SECRET: '/k8s/xroad/client/NATIONAL-REGISTRY/IDENTITYSERVER_SECRET' XROAD_VMST_API_KEY: '/k8s/vmst-client/VMST_API_KEY' ZENDESK_CONTACT_FORM_EMAIL: '/k8s/api/ZENDESK_CONTACT_FORM_EMAIL' ZENDESK_CONTACT_FORM_TOKEN: '/k8s/api/ZENDESK_CONTACT_FORM_TOKEN' diff --git a/charts/islandis/values.prod.yaml b/charts/islandis/values.prod.yaml index 8c7f3c4fd87e..6bfa8ad096f5 100644 --- a/charts/islandis/values.prod.yaml +++ b/charts/islandis/values.prod.yaml @@ -198,6 +198,7 @@ api: XROAD_PAYMENT_BASE_CALLBACK_URL: 'XROAD:/IS/GOV/5501692829/island-is/application-payment-v1/' XROAD_PAYMENT_PROVIDER_ID: 'IS/GOV/5402697509/FJS-Public' XROAD_PROPERTIES_API_PATH: '/SKRA-Protected/Fasteignir-v1' + XROAD_PROPERTIES_SERVICE_PATH: 'IS/GOV/6503760649/SKRA-Protected/Fasteignir-v1' XROAD_TJODSKRA_API_PATH: '/SKRA-Protected/Einstaklingar-v1' XROAD_TJODSKRA_MEMBER_CODE: '6503760649' XROAD_TLS_BASE_PATH: 'https://securityserver.island.is' @@ -292,6 +293,7 @@ api: XROAD_HEALTH_INSURANCE_V2_XROAD_USERNAME: '/k8s/api/HEALTH_INSURANCE_V2_XROAD_USERNAME' XROAD_PAYMENT_PASSWORD: '/k8s/application-system-api/PAYMENT_PASSWORD' XROAD_PAYMENT_USER: '/k8s/application-system-api/PAYMENT_USER' + XROAD_PROPERTIES_CLIENT_SECRET: '/k8s/xroad/client/NATIONAL-REGISTRY/IDENTITYSERVER_SECRET' XROAD_VMST_API_KEY: '/k8s/vmst-client/VMST_API_KEY' ZENDESK_CONTACT_FORM_EMAIL: '/k8s/api/ZENDESK_CONTACT_FORM_EMAIL' ZENDESK_CONTACT_FORM_TOKEN: '/k8s/api/ZENDESK_CONTACT_FORM_TOKEN' diff --git a/charts/islandis/values.staging.yaml b/charts/islandis/values.staging.yaml index a5493a9ba8db..126eda50f4e3 100644 --- a/charts/islandis/values.staging.yaml +++ b/charts/islandis/values.staging.yaml @@ -206,6 +206,7 @@ api: XROAD_PAYMENT_BASE_CALLBACK_URL: 'XROAD:' XROAD_PAYMENT_PROVIDER_ID: 'IS-TEST/GOV/10021/FJS-DEV-Public' XROAD_PROPERTIES_API_PATH: '/SKRA-Protected/Fasteignir-v1' + XROAD_PROPERTIES_SERVICE_PATH: 'IS-TEST/GOV/6503760649/SKRA-Protected/Fasteignir-v1' XROAD_TJODSKRA_API_PATH: '/SKRA-Protected/Einstaklingar-v1' XROAD_TJODSKRA_MEMBER_CODE: '6503760649' XROAD_TLS_BASE_PATH: 'https://securityserver.staging01.devland.is' @@ -298,6 +299,7 @@ api: XROAD_HEALTH_INSURANCE_V2_XROAD_USERNAME: '/k8s/api/HEALTH_INSURANCE_V2_XROAD_USERNAME' XROAD_PAYMENT_PASSWORD: '/k8s/application-system-api/PAYMENT_PASSWORD' XROAD_PAYMENT_USER: '/k8s/application-system-api/PAYMENT_USER' + XROAD_PROPERTIES_CLIENT_SECRET: '/k8s/xroad/client/NATIONAL-REGISTRY/IDENTITYSERVER_SECRET' XROAD_VMST_API_KEY: '/k8s/vmst-client/VMST_API_KEY' ZENDESK_CONTACT_FORM_EMAIL: '/k8s/api/ZENDESK_CONTACT_FORM_EMAIL' ZENDESK_CONTACT_FORM_TOKEN: '/k8s/api/ZENDESK_CONTACT_FORM_TOKEN' diff --git a/infra/src/dsl/xroad.ts b/infra/src/dsl/xroad.ts index 8a12b2004303..73fdd465127b 100644 --- a/infra/src/dsl/xroad.ts +++ b/infra/src/dsl/xroad.ts @@ -158,8 +158,18 @@ export const Finance = new XroadConf({ export const Properties = new XroadConf({ env: { + XROAD_PROPERTIES_SERVICE_PATH: { + dev: 'IS-DEV/GOV/10001/SKRA-Protected/Fasteignir-v1', + staging: 'IS-TEST/GOV/6503760649/SKRA-Protected/Fasteignir-v1', + prod: 'IS/GOV/6503760649/SKRA-Protected/Fasteignir-v1', + }, + // Deprecated: XROAD_PROPERTIES_API_PATH: '/SKRA-Protected/Fasteignir-v1', }, + secrets: { + XROAD_PROPERTIES_CLIENT_SECRET: + '/k8s/xroad/client/NATIONAL-REGISTRY/IDENTITYSERVER_SECRET', + }, }) export const Education = new XroadConf({ diff --git a/libs/api/domains/assets/src/lib/api-domains-assets.module.ts b/libs/api/domains/assets/src/lib/api-domains-assets.module.ts index 8a041f1bf437..3c5b0c642864 100644 --- a/libs/api/domains/assets/src/lib/api-domains-assets.module.ts +++ b/libs/api/domains/assets/src/lib/api-domains-assets.module.ts @@ -1,42 +1,13 @@ -import { DynamicModule, Module } from '@nestjs/common' -import fetch from 'isomorphic-fetch' +import { Module } from '@nestjs/common' import { AuthModule } from '@island.is/auth-nest-tools' import { AssetsClientModule } from '@island.is/clients/assets' -import { FasteignirApi, Configuration } from '@island.is/clients/assets' -import { - createXRoadAPIPath, - XRoadMemberClass, -} from '@island.is/shared/utils/server' import { AssetsXRoadResolver } from './api-domains-assets.resolver' import { AssetsXRoadService } from './api-domains-assets.service' -export interface AssetsXRoadConfig { - xRoadBasePathWithEnv: string - xRoadAssetsMemberCode: string - xRoadAssetsApiPath: string - xRoadClientId: string -} - -@Module({}) -export class AssetsModule { - static register(config: AssetsXRoadConfig): DynamicModule { - return { - module: AssetsModule, - providers: [AssetsXRoadResolver, AssetsXRoadService], - imports: [ - AssetsClientModule.register({ - xRoadPath: createXRoadAPIPath( - config.xRoadBasePathWithEnv, - XRoadMemberClass.GovernmentInstitution, - config.xRoadAssetsMemberCode, - config.xRoadAssetsApiPath, - ), - xRoadClient: config.xRoadClientId, - }), - AuthModule, - ], - exports: [AssetsXRoadService], - } - } -} +@Module({ + providers: [AssetsXRoadResolver, AssetsXRoadService], + imports: [AssetsClientModule, AuthModule], + exports: [AssetsXRoadService], +}) +export class AssetsModule {} diff --git a/libs/api/domains/assets/src/lib/api-domains-assets.service.ts b/libs/api/domains/assets/src/lib/api-domains-assets.service.ts index fc9fc1ecc444..fd1b90ee48ca 100644 --- a/libs/api/domains/assets/src/lib/api-domains-assets.service.ts +++ b/libs/api/domains/assets/src/lib/api-domains-assets.service.ts @@ -5,7 +5,6 @@ import { AuthMiddleware } from '@island.is/auth-nest-tools' import type { Auth, User } from '@island.is/auth-nest-tools' import type { Logger } from '@island.is/logging' import { LOGGER_PROVIDER } from '@island.is/logging' -import { AuthorizationIdentityMiddleware } from './authorization-identity.middleware' const getAssetString = (str: string) => str.charAt(0).toLowerCase() === 'f' ? str.substring(1) : str @@ -30,7 +29,6 @@ export class AssetsXRoadService { private getRealEstatesWithAuth(auth: Auth) { return this.FasteignirApi.withMiddleware( new AuthMiddleware(auth, { forwardUserInfo: true }), - new AuthorizationIdentityMiddleware(auth.authorization), ) } diff --git a/libs/api/domains/assets/src/lib/authorization-identity.middleware.ts b/libs/api/domains/assets/src/lib/authorization-identity.middleware.ts deleted file mode 100644 index 54ad6feb3b74..000000000000 --- a/libs/api/domains/assets/src/lib/authorization-identity.middleware.ts +++ /dev/null @@ -1,21 +0,0 @@ -interface FetchParams { - url: string - init: RequestInit -} - -interface RequestContext { - init: RequestInit -} - -interface Middleware { - pre?(context: RequestContext): Promise -} -export class AuthorizationIdentityMiddleware implements Middleware { - constructor(private bearerToken: string) {} - - async pre(context: RequestContext) { - context.init.headers = Object.assign({}, context.init.headers, { - ['Authorization-Identity']: this.bearerToken, - }) - } -} diff --git a/libs/clients/assets/src/index.ts b/libs/clients/assets/src/index.ts index 405c9dbdcc46..94cd1f92b58e 100644 --- a/libs/clients/assets/src/index.ts +++ b/libs/clients/assets/src/index.ts @@ -1,5 +1,3 @@ -export { - AssetsClientModule, - ModuleConfig as AssetsModuleConfig, -} from './lib/assets.module' +export { AssetsClientModule } from './lib/assets.module' +export { AssetsClientConfig } from './lib/assets.config' export * from '../gen/fetch' diff --git a/libs/clients/assets/src/lib/PropertiesApiProvider.ts b/libs/clients/assets/src/lib/PropertiesApiProvider.ts new file mode 100644 index 000000000000..6f3a1f30cbd4 --- /dev/null +++ b/libs/clients/assets/src/lib/PropertiesApiProvider.ts @@ -0,0 +1,48 @@ +import { Provider } from '@nestjs/common/interfaces/modules/provider.interface' +import nodeFetch, { Request } from 'node-fetch' + +import { + createEnhancedFetch, + EnhancedFetchOptions, +} from '@island.is/clients/middlewares' +import { + ConfigType, + LazyDuringDevScope, + XRoadConfig, +} from '@island.is/nest/config' + +import { Configuration, FasteignirApi } from '../../gen/fetch' +import { AssetsClientConfig } from './assets.config' + +export const PropertiesApiProvider: Provider = { + provide: FasteignirApi, + scope: LazyDuringDevScope, + useFactory: ( + xroadConfig: ConfigType, + config: ConfigType, + ) => + new FasteignirApi( + new Configuration({ + fetchApi: createEnhancedFetch({ + name: 'clients-assets', + ...config.fetch, + fetch: (url, init) => { + // The Properties API expects two different authorization headers for some reason. + const request = new Request(url, init) + request.headers.set( + 'authorization-identity', + request.headers.get('authorization') ?? '', + ) + return nodeFetch(request) + }, + } as EnhancedFetchOptions), + // TODO: Remove ^ "as EnhancedFetchOptions" after making API projects strict TS. + basePath: `${xroadConfig.xRoadBasePath}/r1/${config.xRoadServicePath}`, + headers: { + 'X-Road-Client': xroadConfig.xRoadClient, + Accept: 'application/json', + }, + }), + ), + inject: [XRoadConfig.KEY, AssetsClientConfig.KEY], +} diff --git a/libs/clients/assets/src/lib/assets.config.ts b/libs/clients/assets/src/lib/assets.config.ts new file mode 100644 index 000000000000..fe955878549b --- /dev/null +++ b/libs/clients/assets/src/lib/assets.config.ts @@ -0,0 +1,53 @@ +import { NationalRegistryScope } from '@island.is/auth/scopes' +import { defineConfig } from '@island.is/nest/config' +import * as z from 'zod' + +const schema = z.object({ + xRoadServicePath: z.string(), + fetch: z.object({ + timeout: z.number().int(), + autoAuth: z + .object({ + mode: z.enum(['token', 'tokenExchange', 'auto']), + issuer: z.string(), + clientId: z.string(), + clientSecret: z.string(), + scope: z.array(z.string()), + }) + .optional(), + }), +}) + +export const AssetsClientConfig = defineConfig>({ + name: 'AssetsClient', + schema, + load(env) { + const clientSecret = env.optional('XROAD_PROPERTIES_CLIENT_SECRET') + return { + xRoadServicePath: env.required( + 'XROAD_PROPERTIES_SERVICE_PATH', + 'IS-DEV/GOV/10001/SKRA-Protected/Fasteignir-v1', + ), + fetch: { + timeout: env.optionalJSON('XROAD_PROPERTIES_TIMEOUT') ?? 10000, + autoAuth: clientSecret + ? { + mode: 'tokenExchange', + issuer: env.required( + 'IDENTITY_SERVER_ISSUER_URL', + 'https://identity-server.dev01.devland.is', + ), + clientId: + env.optional('XROAD_PROPERTIES_CLIENT_ID') ?? + '@island.is/clients/national-registry', + clientSecret, + scope: env.optionalJSON('XROAD_PROPERTIES_SCOPE') ?? [ + NationalRegistryScope.properties, + 'api_resource.scope', + ], + } + : undefined, + }, + } + }, +}) diff --git a/libs/clients/assets/src/lib/assets.module.ts b/libs/clients/assets/src/lib/assets.module.ts index 1bd2f0ae01cd..dd76e1a48f7c 100644 --- a/libs/clients/assets/src/lib/assets.module.ts +++ b/libs/clients/assets/src/lib/assets.module.ts @@ -1,40 +1,8 @@ -import { DynamicModule } from '@nestjs/common' -import { createEnhancedFetch } from '@island.is/clients/middlewares' -import type { EnhancedFetchOptions } from '@island.is/clients/middlewares' - -import { Configuration, FasteignirApi } from '../../gen/fetch' - -export interface ModuleConfig { - xRoadPath?: string - xRoadClient: string - userAuth?: any - fetch?: Partial -} - -export class AssetsClientModule { - static register(config: ModuleConfig): DynamicModule { - const headers = { - 'X-Road-Client': config.xRoadClient, - Accept: 'application/json', - } - const providerConfiguration = new Configuration({ - fetchApi: createEnhancedFetch({ - name: 'clients-assets', - ...config.fetch, - }), - basePath: config.xRoadPath, - headers, - }) - - const exportedApis = [FasteignirApi] - - return { - module: AssetsClientModule, - providers: exportedApis.map((Api) => ({ - provide: Api, - useFactory: () => new Api(providerConfiguration), - })), - exports: exportedApis, - } - } -} +import { Module } from '@nestjs/common' +import { PropertiesApiProvider } from './PropertiesApiProvider' + +@Module({ + providers: [PropertiesApiProvider], + exports: [PropertiesApiProvider], +}) +export class AssetsClientModule {} diff --git a/libs/nest/config/src/index.ts b/libs/nest/config/src/index.ts index 6f14ff716fe2..b6051eec5d1c 100644 --- a/libs/nest/config/src/index.ts +++ b/libs/nest/config/src/index.ts @@ -1,4 +1,5 @@ export { ConfigModule } from '@nestjs/config' export { XRoadConfig } from './lib/configurations/XRoadConfig' -export * from './lib/defineConfig' +export { defineConfig } from './lib/defineConfig' +export { LazyDuringDevScope } from './lib/LazyDuringDevScope' export * from './lib/types' diff --git a/libs/nest/config/src/lib/LazyDuringDevScope.ts b/libs/nest/config/src/lib/LazyDuringDevScope.ts new file mode 100644 index 000000000000..d97956a4b514 --- /dev/null +++ b/libs/nest/config/src/lib/LazyDuringDevScope.ts @@ -0,0 +1,11 @@ +import { Scope } from '@nestjs/common' + +/** + * An injection scope that is REQUEST-scoped during local development ( and + * DEFAULT-scoped (singleton, constructed at process start-up) in production. + * This is useful to lazily construct providers that depend on incomplete + * configuration, so you can start your APIs in development without having + * all the modules configured. + */ +export const LazyDuringDevScope = + process.env.NODE_ENV !== 'production' ? Scope.REQUEST : Scope.DEFAULT