From 2ae8f9f4262b2b86497e2e32a1b8ddbefb46359c Mon Sep 17 00:00:00 2001 From: Daniel Haselhan Date: Tue, 30 Apr 2024 16:24:44 -0700 Subject: [PATCH 1/4] Allow Snowplow via CSP * Track page changes --- alcs-frontend/nginx.conf | 2 +- alcs-frontend/src/app/app.component.ts | 11 +++++++++-- alcs-frontend/src/app/app.module.ts | 2 ++ .../app/services/analytics/analytics.service.ts | 17 +++++++++++++++++ portal-frontend/nginx.conf | 2 +- portal-frontend/src/app/app.component.ts | 1 + 6 files changed, 31 insertions(+), 4 deletions(-) create mode 100644 alcs-frontend/src/app/services/analytics/analytics.service.ts diff --git a/alcs-frontend/nginx.conf b/alcs-frontend/nginx.conf index 6ccc98d57e..b7847867d7 100644 --- a/alcs-frontend/nginx.conf +++ b/alcs-frontend/nginx.conf @@ -19,7 +19,7 @@ http { add_header 'X-XSS-Protection' '1; mode=block'; add_header 'Strict-Transport-Security' 'max-age=31536000; includeSubDomains; preload'; add_header 'Cache-control' 'no-cache'; - add_header 'Content-Security-Policy' "default-src 'self';img-src 'self';style-src 'unsafe-inline' 'self';connect-src $ENABLED_CONNECT_SRC; font-src 'self' https://fonts.gstatic.com https://fonts.googleapis.com; base-uri 'self'; object-src https://nrs.objectstore.gov.bc.ca; frame-src https://alcs-metabase-test.apps.silver.devops.gov.bc.ca https://alcs-metabase-prod.apps.silver.devops.gov.bc.ca https://nrs.objectstore.gov.bc.ca;"; + add_header 'Content-Security-Policy' "default-src 'self'; img-src 'self'; style-src 'unsafe-inline' 'self'; connect-src $ENABLED_CONNECT_SRC; font-src 'self' https://fonts.gstatic.com https://fonts.googleapis.com; base-uri 'self'; object-src https://nrs.objectstore.gov.bc.ca; frame-src https://alcs-metabase-test.apps.silver.devops.gov.bc.ca https://alcs-metabase-prod.apps.silver.devops.gov.bc.ca https://nrs.objectstore.gov.bc.ca; script-src 'self' https://www2.gov.bc.ca"; add_header 'Permissions-Policy' 'camera=(), geolocation=(), microphone=()'; add_header 'Referrer-Policy' 'same-origin'; diff --git a/alcs-frontend/src/app/app.component.ts b/alcs-frontend/src/app/app.component.ts index fd3a584643..295e4b5efb 100644 --- a/alcs-frontend/src/app/app.component.ts +++ b/alcs-frontend/src/app/app.component.ts @@ -1,10 +1,17 @@ -import { Component } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; +import { AnalyticsService } from './services/analytics/analytics.service'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'], }) -export class AppComponent { +export class AppComponent implements OnInit { title = 'alcs-frontend'; + + constructor(private analyticsService: AnalyticsService) {} + + ngOnInit(): void { + this.analyticsService.init(); + } } diff --git a/alcs-frontend/src/app/app.module.ts b/alcs-frontend/src/app/app.module.ts index dda3216465..f6f7d1d014 100644 --- a/alcs-frontend/src/app/app.module.ts +++ b/alcs-frontend/src/app/app.module.ts @@ -12,6 +12,7 @@ import { AuthorizationComponent } from './features/authorization/authorization.c import { NotFoundComponent } from './features/errors/not-found/not-found.component'; import { LoginComponent } from './features/login/login.component'; import { ProvisionComponent } from './features/provision/provision.component'; +import { AnalyticsService } from './services/analytics/analytics.service'; import { AuthInterceptor } from './services/authentication/auth.interceptor'; import { TokenRefreshService } from './services/authentication/token-refresh.service'; import { UnauthorizedInterceptor } from './services/authentication/unauthorized.interceptor'; @@ -37,6 +38,7 @@ import { SharedModule } from './shared/shared.module'; ], imports: [BrowserModule, BrowserAnimationsModule, SharedModule.forRoot(), AppRoutingModule, MomentDateModule], providers: [ + AnalyticsService, { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }, { provide: HTTP_INTERCEPTORS, useClass: UnauthorizedInterceptor, multi: true }, { provide: MAT_DIALOG_DEFAULT_OPTIONS, useValue: { panelClass: 'mat-dialog-override' } }, diff --git a/alcs-frontend/src/app/services/analytics/analytics.service.ts b/alcs-frontend/src/app/services/analytics/analytics.service.ts new file mode 100644 index 0000000000..107e47bf05 --- /dev/null +++ b/alcs-frontend/src/app/services/analytics/analytics.service.ts @@ -0,0 +1,17 @@ +import { Injectable } from '@angular/core'; +import { NavigationEnd, Router } from '@angular/router'; + +@Injectable({ + providedIn: 'root', +}) +export class AnalyticsService { + constructor(private router: Router) {} + + init() { + this.router.events.subscribe((event) => { + if (event instanceof NavigationEnd) { + (window as any).snowplow('trackPageView'); + } + }); + } +} diff --git a/portal-frontend/nginx.conf b/portal-frontend/nginx.conf index 8db9efc810..09766604d8 100644 --- a/portal-frontend/nginx.conf +++ b/portal-frontend/nginx.conf @@ -19,7 +19,7 @@ http { add_header 'X-XSS-Protection' '1; mode=block'; add_header 'Strict-Transport-Security' 'max-age=31536000; includeSubDomains; preload'; add_header 'Cache-control' 'no-cache'; - add_header 'Content-Security-Policy' "default-src 'self';img-src 'self';style-src 'unsafe-inline' 'self';connect-src $ENABLED_CONNECT_SRC; font-src 'self' https://fonts.gstatic.com https://fonts.googleapis.com; base-uri 'self'; object-src https://nrs.objectstore.gov.bc.ca; frame-src https://nrs.objectstore.gov.bc.ca;"; + add_header 'Content-Security-Policy' "default-src 'self'; img-src 'self'; style-src 'unsafe-inline' 'self'; connect-src $ENABLED_CONNECT_SRC; font-src 'self' https://fonts.gstatic.com https://fonts.googleapis.com; base-uri 'self'; object-src https://nrs.objectstore.gov.bc.ca; frame-src https://nrs.objectstore.gov.bc.ca; script-src 'self' https://www2.gov.bc.ca;"; add_header 'Permissions-Policy' 'camera=(), geolocation=(), microphone=()'; add_header 'Referrer-Policy' 'same-origin'; diff --git a/portal-frontend/src/app/app.component.ts b/portal-frontend/src/app/app.component.ts index a0fdf755fe..b3aa9e931c 100644 --- a/portal-frontend/src/app/app.component.ts +++ b/portal-frontend/src/app/app.component.ts @@ -17,6 +17,7 @@ export class AppComponent implements OnInit { this.router.events.subscribe((event) => { if (event instanceof NavigationEnd) { this.showHeaderFooter = !event.url.includes('/alcs/'); + (window as any).snowplow('trackPageView'); } }); } From fa771d6eec605ae2a08af83b356976c2a2d08d3f Mon Sep 17 00:00:00 2001 From: Daniel Haselhan Date: Tue, 30 Apr 2024 16:53:53 -0700 Subject: [PATCH 2/4] Move snowplow to its own script --- portal-frontend/src/assets/snowplow.js | 30 +++++++++++++++++++++ portal-frontend/src/index.html | 36 +------------------------- 2 files changed, 31 insertions(+), 35 deletions(-) create mode 100644 portal-frontend/src/assets/snowplow.js diff --git a/portal-frontend/src/assets/snowplow.js b/portal-frontend/src/assets/snowplow.js new file mode 100644 index 0000000000..d87b492873 --- /dev/null +++ b/portal-frontend/src/assets/snowplow.js @@ -0,0 +1,30 @@ +// +(function(p, l, o, w, i, n, g) { + if (!p[i]) { + p.GlobalSnowplowNamespace = p.GlobalSnowplowNamespace || []; + p.GlobalSnowplowNamespace.push(i); + p[i] = function() { + (p[i].q = p[i].q || []).push(arguments); + }; + p[i].q = p[i].q || []; + n = l.createElement(o); + g = l.getElementsByTagName(o)[0]; + n.async = 1; + n.src = w; + g.parentNode.insertBefore(n, g); + } +})(window, document, "script", "https://www2.gov.bc.ca/StaticWebResources/static/sp/sp-2-14-0.js", "snowplow"); +var collector = "spm.apps.gov.bc.ca"; +window.snowplow("newTracker", "rt", collector, { + appId: "Snowplow_standalone", + cookieLifetime: 86400 * 548, + platform: "web", + post: true, + forceSecureTracker: true, + contexts: { + webPage: true, performanceTiming: true + } +}); +window.snowplow("enableActivityTracking", 30, 30); // Ping every 30 seconds after 30 seconds +window.snowplow("enableLinkClickTracking"); +// diff --git a/portal-frontend/src/index.html b/portal-frontend/src/index.html index ca34a10771..8f28f22c83 100644 --- a/portal-frontend/src/index.html +++ b/portal-frontend/src/index.html @@ -9,41 +9,7 @@ - - + From 878c4cc5be4a8bebf5dd921a4a5031c4644904dc Mon Sep 17 00:00:00 2001 From: Daniel Haselhan Date: Wed, 1 May 2024 10:40:35 -0700 Subject: [PATCH 3/4] More Snowplow * Add script hash to enable it to load inline * Remove analytics service from ALCS, we are only doing tracking on portal --- alcs-frontend/src/app/app.component.ts | 11 +++-------- alcs-frontend/src/app/app.module.ts | 2 -- .../app/services/analytics/analytics.service.ts | 17 ----------------- portal-frontend/nginx.conf | 2 +- 4 files changed, 4 insertions(+), 28 deletions(-) delete mode 100644 alcs-frontend/src/app/services/analytics/analytics.service.ts diff --git a/alcs-frontend/src/app/app.component.ts b/alcs-frontend/src/app/app.component.ts index 295e4b5efb..b9e3e83ade 100644 --- a/alcs-frontend/src/app/app.component.ts +++ b/alcs-frontend/src/app/app.component.ts @@ -1,17 +1,12 @@ -import { Component, OnInit } from '@angular/core'; -import { AnalyticsService } from './services/analytics/analytics.service'; +import { Component } from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'], }) -export class AppComponent implements OnInit { +export class AppComponent { title = 'alcs-frontend'; - constructor(private analyticsService: AnalyticsService) {} - - ngOnInit(): void { - this.analyticsService.init(); - } + constructor() {} } diff --git a/alcs-frontend/src/app/app.module.ts b/alcs-frontend/src/app/app.module.ts index f6f7d1d014..dda3216465 100644 --- a/alcs-frontend/src/app/app.module.ts +++ b/alcs-frontend/src/app/app.module.ts @@ -12,7 +12,6 @@ import { AuthorizationComponent } from './features/authorization/authorization.c import { NotFoundComponent } from './features/errors/not-found/not-found.component'; import { LoginComponent } from './features/login/login.component'; import { ProvisionComponent } from './features/provision/provision.component'; -import { AnalyticsService } from './services/analytics/analytics.service'; import { AuthInterceptor } from './services/authentication/auth.interceptor'; import { TokenRefreshService } from './services/authentication/token-refresh.service'; import { UnauthorizedInterceptor } from './services/authentication/unauthorized.interceptor'; @@ -38,7 +37,6 @@ import { SharedModule } from './shared/shared.module'; ], imports: [BrowserModule, BrowserAnimationsModule, SharedModule.forRoot(), AppRoutingModule, MomentDateModule], providers: [ - AnalyticsService, { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }, { provide: HTTP_INTERCEPTORS, useClass: UnauthorizedInterceptor, multi: true }, { provide: MAT_DIALOG_DEFAULT_OPTIONS, useValue: { panelClass: 'mat-dialog-override' } }, diff --git a/alcs-frontend/src/app/services/analytics/analytics.service.ts b/alcs-frontend/src/app/services/analytics/analytics.service.ts deleted file mode 100644 index 107e47bf05..0000000000 --- a/alcs-frontend/src/app/services/analytics/analytics.service.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Injectable } from '@angular/core'; -import { NavigationEnd, Router } from '@angular/router'; - -@Injectable({ - providedIn: 'root', -}) -export class AnalyticsService { - constructor(private router: Router) {} - - init() { - this.router.events.subscribe((event) => { - if (event instanceof NavigationEnd) { - (window as any).snowplow('trackPageView'); - } - }); - } -} diff --git a/portal-frontend/nginx.conf b/portal-frontend/nginx.conf index 09766604d8..24ca3ba89c 100644 --- a/portal-frontend/nginx.conf +++ b/portal-frontend/nginx.conf @@ -19,7 +19,7 @@ http { add_header 'X-XSS-Protection' '1; mode=block'; add_header 'Strict-Transport-Security' 'max-age=31536000; includeSubDomains; preload'; add_header 'Cache-control' 'no-cache'; - add_header 'Content-Security-Policy' "default-src 'self'; img-src 'self'; style-src 'unsafe-inline' 'self'; connect-src $ENABLED_CONNECT_SRC; font-src 'self' https://fonts.gstatic.com https://fonts.googleapis.com; base-uri 'self'; object-src https://nrs.objectstore.gov.bc.ca; frame-src https://nrs.objectstore.gov.bc.ca; script-src 'self' https://www2.gov.bc.ca;"; + add_header 'Content-Security-Policy' "default-src 'self'; img-src 'self'; style-src 'unsafe-inline' 'self'; connect-src $ENABLED_CONNECT_SRC; font-src 'self' https://fonts.gstatic.com https://fonts.googleapis.com; base-uri 'self'; object-src https://nrs.objectstore.gov.bc.ca; frame-src https://nrs.objectstore.gov.bc.ca; script-src 'self' https://www2.gov.bc.ca sha256-evje5KswYvntfuZqc5jmvUSANhIntI7Or6vVnjxGGQE=;"; add_header 'Permissions-Policy' 'camera=(), geolocation=(), microphone=()'; add_header 'Referrer-Policy' 'same-origin'; From d354fb3a4b21300f760b7d873437c8af544d4d41 Mon Sep 17 00:00:00 2001 From: Daniel Haselhan Date: Wed, 1 May 2024 11:40:56 -0700 Subject: [PATCH 4/4] Add connect-src for snowplower, fix main docker --- alcs-frontend/Dockerfile | 6 ++++-- portal-frontend/Dockerfile | 2 ++ portal-frontend/nginx.conf | 2 +- services/config/default.json | 12 ++++++++---- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/alcs-frontend/Dockerfile b/alcs-frontend/Dockerfile index 8b3552cf30..0e4e9569d5 100644 --- a/alcs-frontend/Dockerfile +++ b/alcs-frontend/Dockerfile @@ -5,14 +5,16 @@ FROM node:20-alpine AS build WORKDIR /app # Copy package.json file -COPY package.json . +COPY package.json package-lock.json ./ # Install dependencies -RUN npm install +RUN npm ci # Copy the source code to the /app directory COPY . . +ENV NODE_OPTIONS="--max-old-space-size=2048" + # Build the application RUN npm run build -- --output-path=dist --output-hashing=all diff --git a/portal-frontend/Dockerfile b/portal-frontend/Dockerfile index 8b3f351595..212720b0e3 100644 --- a/portal-frontend/Dockerfile +++ b/portal-frontend/Dockerfile @@ -13,6 +13,8 @@ RUN npm ci # Copy the source code to the /app directory COPY . . +ENV NODE_OPTIONS="--max-old-space-size=2048" + # Build the application RUN npm run build -- --output-path=dist --output-hashing=all diff --git a/portal-frontend/nginx.conf b/portal-frontend/nginx.conf index 24ca3ba89c..4f9a35f8a8 100644 --- a/portal-frontend/nginx.conf +++ b/portal-frontend/nginx.conf @@ -19,7 +19,7 @@ http { add_header 'X-XSS-Protection' '1; mode=block'; add_header 'Strict-Transport-Security' 'max-age=31536000; includeSubDomains; preload'; add_header 'Cache-control' 'no-cache'; - add_header 'Content-Security-Policy' "default-src 'self'; img-src 'self'; style-src 'unsafe-inline' 'self'; connect-src $ENABLED_CONNECT_SRC; font-src 'self' https://fonts.gstatic.com https://fonts.googleapis.com; base-uri 'self'; object-src https://nrs.objectstore.gov.bc.ca; frame-src https://nrs.objectstore.gov.bc.ca; script-src 'self' https://www2.gov.bc.ca sha256-evje5KswYvntfuZqc5jmvUSANhIntI7Or6vVnjxGGQE=;"; + add_header 'Content-Security-Policy' "default-src 'self'; img-src 'self'; style-src 'unsafe-inline' 'self'; connect-src $ENABLED_CONNECT_SRC https://spm.apps.gov.bc.ca; font-src 'self' https://fonts.gstatic.com https://fonts.googleapis.com; base-uri 'self'; object-src https://nrs.objectstore.gov.bc.ca; frame-src https://nrs.objectstore.gov.bc.ca; script-src 'self' https://www2.gov.bc.ca 'sha256-evje5KswYvntfuZqc5jmvUSANhIntI7Or6vVnjxGGQE=';"; add_header 'Permissions-Policy' 'camera=(), geolocation=(), microphone=()'; add_header 'Referrer-Policy' 'same-origin'; diff --git a/services/config/default.json b/services/config/default.json index f4b411c42c..6cfa5e4351 100644 --- a/services/config/default.json +++ b/services/config/default.json @@ -32,7 +32,9 @@ "AUTH_SERVER": "test.loginproxy.gov.bc.ca", "AUTH_SERVER_URL": "https://test.loginproxy.gov.bc.ca/auth", "AUTH_TOKEN_URL": "https://test.loginproxy.gov.bc.ca/auth/realms/standard/protocol/openid-connect/token", - "SCOPES": ["openid"], + "SCOPES": [ + "openid" + ], "REALM": "standard" }, "SITEMINDER": { @@ -61,12 +63,14 @@ "MAX_FILE_SIZE": 104857600 }, "REDIS": { - "HOST": "localhost", + "HOST": "redis", "PORT": "6379", - "PASSWORD": "" + "PASSWORD": "redis" }, "EMAIL": { - "DEFAULT_ADMINS": [""] + "DEFAULT_ADMINS": [ + "" + ] }, "GRPC": { "BIND_URL": "localhost:50057",