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