From e69cc1a49fb9b918f6bfea380082eac6b5ba0fe0 Mon Sep 17 00:00:00 2001 From: Nessar Date: Tue, 14 Jul 2026 19:18:17 +0200 Subject: [PATCH] Remove config.json, install @ngx-env/builder to load OIDC configuration from env variables/files --- .gitignore | 1 + compose.yml | 3 ++ frontend/Dockerfile | 8 +++++ frontend/angular.json | 33 +++++++++----------- frontend/src/app/config.service.spec.ts | 40 ++++++++++--------------- frontend/src/app/config.service.ts | 21 +++---------- frontend/src/env.d.ts | 22 ++++++++++++++ frontend/src/styles.css | 3 ++ 8 files changed, 71 insertions(+), 60 deletions(-) create mode 100644 .gitignore create mode 100644 frontend/src/env.d.ts diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4c49bd7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.env diff --git a/compose.yml b/compose.yml index 2c1f6fc..09b8396 100644 --- a/compose.yml +++ b/compose.yml @@ -24,6 +24,9 @@ services: build: context: ./frontend dockerfile: Dockerfile + args: + - OIDC_ISSUER_URI=${OIDC_ISSUER_URI:-http://localhost:8090/realms/voidAuth} + - OIDC_CLIENT_ID=${OIDC_CLIENT_ID:-lmnp-client} container_name: lmnp-frontend ports: - "80:80" diff --git a/frontend/Dockerfile b/frontend/Dockerfile index ec9a157..5533a9c 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -11,6 +11,13 @@ RUN bun install --frozen-lockfile COPY . . +# Accept build arguments for environment variables +ARG OIDC_ISSUER_URI +ARG OIDC_CLIENT_ID + +# Set build-time environment variables for @ngx-env/builder +ENV OIDC_ISSUER_URI=$OIDC_ISSUER_URI +ENV OIDC_CLIENT_ID=$OIDC_CLIENT_ID ENV CI=true ENV NG_CLI_ANALYTICS=false @@ -22,6 +29,7 @@ RUN bun run build FROM nginx:alpine RUN rm -rf /usr/share/nginx/html/* COPY --from=build /app/dist/lmnp-frontend/browser /usr/share/nginx/html +COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80 CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/frontend/angular.json b/frontend/angular.json index d5a441d..8cc12b7 100644 --- a/frontend/angular.json +++ b/frontend/angular.json @@ -3,9 +3,7 @@ "version": 1, "cli": { "packageManager": "npm", - "schematicCollections": [ - "angular-eslint" - ] + "schematicCollections": ["angular-eslint"] }, "newProjectRoot": "projects", "projects": { @@ -17,7 +15,7 @@ "prefix": "app", "architect": { "build": { - "builder": "@angular/build:application", + "builder": "@ngx-env/builder:application", "options": { "browser": "src/main.ts", "tsConfig": "tsconfig.app.json", @@ -27,9 +25,10 @@ "input": "public" } ], - "styles": [ - "src/styles.css" - ] + "styles": ["src/styles.css"], + "ngxEnv": { + "prefix": "OIDC_" + } }, "configurations": { "production": { @@ -56,7 +55,7 @@ "defaultConfiguration": "production" }, "serve": { - "builder": "@angular/build:dev-server", + "builder": "@ngx-env/builder:dev-server", "configurations": { "production": { "buildTarget": "lmnp-frontend:build:production" @@ -65,28 +64,24 @@ "buildTarget": "lmnp-frontend:build:development" } }, - "defaultConfiguration": "development" + "defaultConfiguration": "development", + "options": {} }, "test": { - "builder": "@angular/build:unit-test", + "builder": "@ngx-env/builder:unit-test", "options": { "tsConfig": "tsconfig.spec.json", - "coverageExclude": [ - "**/*.html", - "src/main.ts" - ] + "coverage": true, + "coverageExclude": ["**/*.html", "src/main.ts"] } }, "lint": { "builder": "@angular-eslint/builder:lint", "options": { - "lintFilePatterns": [ - "src/**/*.ts", - "src/**/*.html" - ] + "lintFilePatterns": ["src/**/*.ts", "src/**/*.html"] } } } } } -} \ No newline at end of file +} diff --git a/frontend/src/app/config.service.spec.ts b/frontend/src/app/config.service.spec.ts index a3c7bfd..49fd46c 100644 --- a/frontend/src/app/config.service.spec.ts +++ b/frontend/src/app/config.service.spec.ts @@ -1,23 +1,17 @@ import { TestBed } from '@angular/core/testing'; -import { HttpClient } from '@angular/common/http'; import { ConfigService } from './config.service'; -import { of, throwError } from 'rxjs'; import { vi } from 'vitest'; describe('ConfigService', () => { let service: ConfigService; - let mockHttpClient: any; beforeEach(() => { - mockHttpClient = { - get: () => of({ oidcIssuer: 'http://custom-issuer', clientId: 'custom-client' }) - }; + // Reset env variables before each test + vi.stubEnv('OIDC_ISSUER_URI', ''); + vi.stubEnv('OIDC_CLIENT_ID', ''); TestBed.configureTestingModule({ - providers: [ - ConfigService, - { provide: HttpClient, useValue: mockHttpClient } - ] + providers: [ConfigService] }); service = TestBed.inject(ConfigService); @@ -27,22 +21,20 @@ describe('ConfigService', () => { expect(service).toBeTruthy(); }); - it('should load config successfully', async () => { - await service.loadConfig(); - expect(service.oidcIssuer).toBe('http://custom-issuer'); - expect(service.clientId).toBe('custom-client'); - }); - - it('should use default values on load error', async () => { - mockHttpClient.get = () => throwError(() => new Error('Load failed')); - - // Silence warning log - const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); - - await service.loadConfig(); + it('should return default values when env variables are not set', () => { expect(service.oidcIssuer).toBe('http://localhost:8090/realms/voidAuth'); expect(service.clientId).toBe('lmnp-client'); + }); - warnSpy.mockRestore(); + it('should return env values when env variables are set', () => { + vi.stubEnv('OIDC_ISSUER_URI', 'http://env-issuer'); + vi.stubEnv('OIDC_CLIENT_ID', 'env-client'); + + expect(service.oidcIssuer).toBe('http://env-issuer'); + expect(service.clientId).toBe('env-client'); + }); + + it('should resolve loadConfig successfully', async () => { + await expect(service.loadConfig()).resolves.toBeUndefined(); }); }); diff --git a/frontend/src/app/config.service.ts b/frontend/src/app/config.service.ts index fec6671..4a0488f 100644 --- a/frontend/src/app/config.service.ts +++ b/frontend/src/app/config.service.ts @@ -1,31 +1,18 @@ -import { Injectable, inject } from '@angular/core'; -import { HttpClient } from '@angular/common/http'; -import { lastValueFrom } from 'rxjs'; +import { Injectable } from '@angular/core'; @Injectable({ providedIn: 'root' }) export class ConfigService { - private config: Record | null = null; - private readonly http = inject(HttpClient); - async loadConfig(): Promise { - try { - this.config = await lastValueFrom(this.http.get('/config.json')) as Record; - } catch (e) { - console.warn('Could not load /config.json, using default dev values', e); - this.config = { - oidcIssuer: 'http://localhost:8090/realms/voidAuth', - clientId: 'lmnp-client' - }; - } + return Promise.resolve(); } get oidcIssuer(): string { - return this.config ? this.config['oidcIssuer'] : 'http://localhost:8090/realms/voidAuth'; + return import.meta.env['OIDC_ISSUER_URI'] || 'http://localhost:8090/realms/voidAuth'; } get clientId(): string { - return this.config ? this.config['clientId'] : 'lmnp-client'; + return import.meta.env['OIDC_CLIENT_ID'] || 'lmnp-client'; } } diff --git a/frontend/src/env.d.ts b/frontend/src/env.d.ts new file mode 100644 index 0000000..7be7f8a --- /dev/null +++ b/frontend/src/env.d.ts @@ -0,0 +1,22 @@ +// Define the type of the environment variables. +declare interface Env { + readonly NODE_ENV: string; + readonly OIDC_ISSUER_URI?: string; + readonly OIDC_CLIENT_ID?: string; + [key: string]: string | undefined; +} + +// Choose how to access the environment variables. +// Remove the unused options. + +// 1. Use import.meta.env.YOUR_ENV_VAR in your code. (conventional) +declare interface ImportMeta { + readonly env: Env; +} + +// 2. Use _NGX_ENV_.YOUR_ENV_VAR in your code. (customizable) +// You can modify the name of the variable in angular.json. +// ngxEnv: { +// define: '_NGX_ENV_', +// } +declare const _NGX_ENV_: Env; diff --git a/frontend/src/styles.css b/frontend/src/styles.css index 90d4ee0..d37d038 100644 --- a/frontend/src/styles.css +++ b/frontend/src/styles.css @@ -1 +1,4 @@ /* You can add global styles to this file, and also import other style files */ +body { + margin: 0; +} \ No newline at end of file