Source audit · 22 Sep 2026

TopFans frontend audit

Angular 21 NgModule app that still works. The security story does not. Do not commit the current environment.ts dump.

Snapshot

~1,360TypeScript files under src/app
0Unit or e2e tests in the repo
~90Components on OnPush, mostly marketing layouts
5,356Lines in profile.component.ts

Read from source. No production build, no live exploit attempt.

Fix this week

Critical Revert the environment dump

The working copy of src/environments/environment.ts is production: true pointed at QA, plus a baked-in whiteLabelConfig that is a real creator record. Email, home address, birth date, phone, IPs, FCM tokens, OTP values, and live Google Calendar access plus refresh tokens.

The file is already tracked. If this lands in git, the leak is permanent. Set whiteLabelConfig back to null. Revoke the calendar tokens. Treat the OTP and FCM values as burned.

Critical AWS keys ship in the browser

sKey in every environment file is an access key and secret. topFanD in src/app/utils/common.utils.ts is a 9-letter Caesar shift. That is not encryption.

UploadService, the fan-import modal, and the form file uploader all build an aws-sdk v2 S3 client on the client. Anyone with DevTools can write to topfanlite-prod-s3-backend.

Rotate the keys. Uploads should go through a backend that issues short-lived, prefix-scoped credentials.

Critical Shared livestream secret

SECRET_KEY_IVS is the same string in QA, staging, and prod. Livestream code reads it from environment. Rotate it and stop sending it to the client.

Critical encKey is a keyboard walk

The value is qwertyuiopasdfghjklzxcvbnm. Whatever it wraps, assume it is public.

High Admin impersonation puts the JWT in the URL

/access-user reads token and admin_name from query params, then logs in. Browser history, proxies, and Referer all see it. Trade that token for a one-time server exchange.

High User HTML skips the sanitizer

Article posts call bypassSecurityTrustHtml on the API description. Same pattern in SEO landing, text editors, embed URLs, and ticket views.

DOMPurify is imported, then used only in app.component to strip tags from some inputs. The isURL pipe calls bypassSecurityTrustHtml, throws away the return value, and hands a raw string to [innerHTML] in messages, comments, posts, and profile.

Quill 1.3.7 is on the page. That line has known XSS bugs. Sanitize with DOMPurify before any innerHTML. Upgrade or drop Quill.

High Tokens in storage, headers shared across requests

Auth tokens live in localStorage and IndexedDB. XSS on this origin becomes account takeover.

HttpService.setHeader mutates module-level httpOptions. Two overlapping requests can attach the wrong Authorization value.

High Helmet with CSP off

server.ts disables contentSecurityPolicy, crossOriginEmbedderPolicy, and crossOriginResourcePolicy. The Express /api/client-ip helper trusts x-forwarded-for as written.

What the app is

NgModules, HttpClientModule, and a 650-line SharedModule imported by AppModule. Feature routes use loadChildren. The shared barrel sits in the main bundle, so the split is thinner than the routing file suggests.

NgRx is a global bag. The root store is userProfile, otherUserProfile, refreshKeys, plus two cache slices. Immutability checks are off. There are no effects. Components inject Store and treat it like a service.

Guards are uneven. Monetize, wallet, messages, and insights stack AuthGuard, PermissionGuard, CreatorApprovalGuard, sometimes WlsOwnerGuard. The dashboard parent has no AuthGuard. A public home feed may be intentional. AuthGuard returns true during prerender, so protected URLs can render as if someone is logged in.

tsconfig has strict: false and strictTemplates: false. noUnusedLocals is on. Hundreds of : any annotations sit next to that. profile.component.ts alone has 94.

Files that are too big to own

File Lines
src/app/modules/shared/components/profile/profile.component.ts5,356
src/app/modules/shared/components/profile/profile.component.html4,422
src/app/modules/messages/components/message-container/message-container.component.ts3,876
src/app/modules/dashboard/components/home/home.component.ts3,227
src/app/modules/dashboard/components/live-stream/active-livestream/active-livestream.component.ts3,178
src/app/modules/shared/services/common.service.ts2,888
src/app/services/seo.service.ts2,718
src/app/utils/common.utils.ts2,523
src/app/core/api-url.enum.ts978
src/app/constants/marketing-page-data.ts7,429

The marketing data file is generated. Leave it. The rest are where bugs hide.

document.getElementById and querySelector show up in messages, text editors, livestream, sidebar, and header. The project rule says not to do that. The code does it anyway.

Performance

OnPush exists on about 90 files, almost all marketing layouts. Wallet, messages, dashboard home, forum, and profile stay on the default strategy. Those are the screens that hurt.

Vendor CSS is already split into lazy bundles. Keep that. The remaining weight is the shared module, aws-sdk v2, aws-amplify 4, moment plus moment-timezone plus luxon, three video players, and two datepickers.

UI kits overlap. PrimeNG, ng-bootstrap, ngx-bootstrap, and Angular Material all ship. Highcharts lives in devDependencies and is imported by insights, so production builds still pull it.

package.json still asks for --max_old_space_size=10048. That is a smell, not a badge.

Rules the repo wrote down, then ignored

Tests

Zero *.spec.ts files. No e2e folder. ng test is wired to Karma and has nothing to run. A 5k-line profile component and a client-side S3 uploader have no automated check that they still work.

Leave alone for now

Keep White-label project generation, prerender route generation, and the marketing or comparison page pipeline. Ugly, and doing a specific job.

Keep The lazy vendor CSS split.

Later Route-level loadChildren. The next move is shrinking SharedModule, not rewriting the router.

Order of work

  1. Revert the environment.ts user dump. Rotate S3, IVS, and the Google calendar tokens that were in it.
  2. Move uploads off browser AWS credentials.
  3. Sanitize user HTML with DOMPurify before any innerHTML. Upgrade or drop Quill 1.3.7.
  4. Take the admin token out of the query string.
  5. Stop importing SharedModule from AppModule. Split header, footer, and pipes from checkout, video, and IVS.
  6. Turn on OnPush for profile, messages, home, and wallet. Those four files are the change-detection bill.
  7. Add tests around auth, upload, and the isURL pipe before anyone refactors them.