Configuration & Security
Two things you'll touch often: constants.js (app-wide config and feature flags) and the security headers / CSP that wrap every response.
constants.js
Location: libs/shared/src/constants/constants.js. It exports a single constants object of build-time configuration and feature flags, imported anywhere via @aquagen-mf-webapp/shared/constants/constants.
Environment & mode
| Constant | Default | Purpose |
|---|---|---|
env | map | The known environments: prod, dev, local, ngrok, demo |
currentEnv | prod | Which environment the app runs as (drives isDemo, base URLs, etc.) |
azureAdEnv | prod | Which Azure AD (MSAL) config to use |
aiAgentEnv | prod | Which AI-agent backend to use |
isDemo | derived | true when currentEnv === 'demo' |
isPreProd | false | Set true for pre-prod/dev deploys (flipped by deploy.sh) |
isMobile | derived | Capacitor.isNativePlatform() — true inside the native iOS/Android shell |
const development = process.env.NODE_ENV !== 'production' is the one value derived from the build itself; several flags below default to it.
Analytics & logging flags
| Constant | Default | Purpose |
|---|---|---|
analyticsEnv | development | Disable-analytics flag — see the caution below |
logAnalyticsEvent | false | Console-log analytics events (debugging) |
logRequest / logResponse | false | Console-log API requests / responses |
logCURL | false | Print each request as a copy-pasteable cURL |
analyticsSecret | — | GA measurement ID + Mixpanel project token (client-side keys) |
analyticsEnv is a "disable analytics" flag — it reads backwardsThe analytics service does if (constants.analyticsEnv) return;, so truthy = analytics OFF. It defaults to development (→ false in a production build, so production records analytics), and deploy.sh sets it true for every dev deploy (dev/pre-prod do not record analytics). Full detail in Deployment → Environment flags.
Auth & timing
| Constant | Default | Purpose |
|---|---|---|
microsoftSignInEnabled | true | Show the Microsoft (Azure AD) sign-in option |
googleSignInEnabled | true | Show the Google sign-in option |
refreshDuration | 5 * 60 * 1000 | 5-minute duration constant used by the auth/session flow |
resendOtpTime | 30 | Seconds before "resend OTP" is offered |
Requests
constants.request centralises HTTP config used by the API client:
defaultTimeout— 50 000 ms (50 s) on every Axios call.contentType—application/jsonandmultipart/form-data.error— the canonical status-code → message map the app shows users, covering400,401,402,404,440, and500.
Misc
sidebarDisabledPages (routes that hide the sidebar), demoVideoUrl, and feedbackForm (a ClickUp form URL).
deploy.sh rewrites analyticsEnv, currentEnv, and isPreProd in this file before building, then reverts them — which is why the build scripts use --skip-nx-cache. See Deployment & Environments.
Auth & backend URLs
The API base URL and the OAuth redirect URI are computed at runtime from constants.currentEnv and the current domain, in libs/shared/src/services/api/urls.js and libs/shared/src/constants/authConfig.js.
API base URL — Urls.baseUrl
Chosen from constants.currentEnv:
| Env | Base URL |
|---|---|
prod | https://prod-aquagen.azurewebsites.net/api/user/ |
dev | https://dev2-aquagenapi.azurewebsites.net/api/user/ |
local | http://localhost:5001/api/user/ |
demo | https://aqua-demo-api-….azurewebsites.net/api/user/ |
There are also separate AI-agent backends (getAIAgentBaseUrl(), uwiAIBaseUrl), each keyed off constants.aiAgentEnv / constants.currentEnv.
OAuth redirect URI — redirectUri / Urls.azureADRedirectUrl
authConfig.js sets MSAL's redirectUri to Urls.azureADRedirectUrl. In the browser that is simply the current origin:
// libs/shared/src/services/api/urls.js
get azureADRedirectUrl() {
if (typeof window !== 'undefined') return `${window.location.origin}/`;
// per-env fallback for non-browser contexts:
// prod → https://web.aquagen.co.in/ , dev → https://dev-aquagen.web.app/ , local → http://localhost:4200/
}
So the redirect URI auto-adapts to whatever domain the app is served from — https://web.aquagen.co.in/ in production, https://dev-aquagen.web.app/ on the dev site, http://localhost:4200/ locally, and so on. This is what makes one build work across all the product/dev URLs.
Because the redirect URI is just the current origin, each domain the app runs on has to be added to the Azure AD app registration's Redirect URIs (and to the Google OAuth authorized origins). Add a new hosting URL there or Microsoft/Google sign-in will fail on that domain.
MSAL & Google config — authConfig.js
| Config | Key values | Toggle |
|---|---|---|
msalConfig | authority: https://login.microsoftonline.com/organizations, clientId, redirectUri (above) | constants.microsoftSignInEnabled |
googleConfig | Google OAuth clientId | constants.googleSignInEnabled |
Both providers are wired at startup in each app's bootstrap.jsx (<MsalProvider> and <GoogleOAuthProvider>).
Security headers & CSP
Every response ships a strict set of security headers, including a Content-Security-Policy. They are defined in three places so the app is protected in dev, in production, and even before JavaScript runs:
| Where | File | Applies to |
|---|---|---|
| Production hosting | firebase.json → hosting[].headers (source: "**") | every response served by Firebase |
| Dev server | apps/production/rspack.config.js → devServer.headers | http://localhost:4200 |
| Clickjacking guard | apps/production/src/index.html | the HTML shell, before the app loads |
The Firebase header block is essentially the same across all hosting targets (with minor per-target variation), and the dev-server block mirrors it (slightly more permissive — it also allows localhost:8000 and Google Maps frames, and sets Cross-Origin-Embedder-Policy: unsafe-none).
The headers
| Header | Value (summary) | What it does |
|---|---|---|
Content-Security-Policy | see below | Restricts where scripts, styles, images, connections, and frames may come from |
X-Frame-Options | DENY | Page cannot be put in an <iframe> |
Strict-Transport-Security | max-age=31536000; includeSubDomains; preload | Forces HTTPS for a year (HSTS) |
X-Content-Type-Options | nosniff | No MIME-type sniffing |
X-XSS-Protection | 1; mode=block | Legacy browser XSS filter |
Referrer-Policy | strict-origin-when-cross-origin | Limits referrer leakage |
Permissions-Policy | accelerometer=(), camera=(), geolocation=(), microphone=(), payment=(), usb=() … | Disables sensitive browser APIs |
Cross-Origin-Opener-Policy | same-origin-allow-popups | Isolates the browsing context (allows OAuth popups) |
Cross-Origin-Resource-Policy | same-origin | Blocks cross-origin resource embedding |
X-Permitted-Cross-Domain-Policies | none | Blocks Adobe/Flash cross-domain policies |
Cache-Control | no-store, no-cache, must-revalidate, proxy-revalidate | Prevents caching of authenticated pages |
Content-Security-Policy (directive by directive)
| Directive | Allows | Notes |
|---|---|---|
default-src | 'self' | Everything not listed below defaults to same-origin |
script-src | 'self' 'unsafe-inline' 'unsafe-eval' + Google APIs, ClickUp CDN | The two 'unsafe-*' are required by current dependencies |
style-src | 'self' 'unsafe-inline' blob: + Google Fonts/Accounts | |
img-src | 'self' data: https: blob: | Images from any HTTPS host |
font-src | 'self' data: + Google Fonts | |
connect-src | Azure Web Apps + Web PubSub (wss:), Firebase, Google, Microsoft login, Mixpanel, ClickUp, Iconify, localhost:5001 | The API + realtime + analytics allowlist |
frame-src | 'self' blob: + Azure, ClickUp forms | Embeddable iframes |
frame-ancestors | 'none' | This app may not be iframed by anyone |
base-uri / form-action | 'self' | Locks <base> and form targets to same-origin |
Clickjacking protection (belt & braces)
index.html ships a small script that hides <body> by default and only reveals it once JavaScript confirms the page is the top-level window (self === top); otherwise it breaks out of the frame (top.location = self.location). This backs up frame-ancestors 'none' and X-Frame-Options: DENY for browsers that miss either.
If you integrate a new API, CDN, font host, analytics provider, or embeddable widget, the browser will block it until you add its origin to the matching CSP directive in both firebase.json and apps/production/rspack.config.js (dev). A request that works locally but fails in production is almost always a missing connect-src/frame-src entry in firebase.json.
Related
- API Call Flow — how the API client uses
constants.request. - Architecture Diagrams → §14 Security Layers — the security model at a glance.
- Deployment & Environments — how
deploy.shflips these constants per environment.