From 2ee7a1c2aaf1f4fe66b6340922f100377099458b Mon Sep 17 00:00:00 2001 From: Dave Liu <7david12liu@gmail.com> Date: Wed, 26 Aug 2026 13:39:21 -0700 Subject: [PATCH] feat(firebase): guard staging email-password auth (#671) --- .github/workflows/ci.yml | 2 +- GITHUB_ISSUES.md | 2 +- IMPLEMENTATION_PLAN.md | 2 + OFFICER_START_HERE.md | 2 +- OPERATIONS_RUNBOOK.md | 9 +- README.md | 7 +- SECURITY.md | 4 +- SYSTEM_DESIGN.md | 20 ++- docs/officers/PUBLISH_AND_CHECK.md | 38 +++++ docs/officers/README.md | 2 +- docs/officers/SYSTEM_MAPS.md | 7 +- firebase.json | 5 + package.json | 3 + scripts/firebase-auth-staging-contract.js | 120 +++++++++++++++ tests/firebase-auth-staging-contract.test.js | 151 +++++++++++++++++++ tests/test-artifact-safety.test.js | 9 +- 16 files changed, 365 insertions(+), 18 deletions(-) create mode 100644 scripts/firebase-auth-staging-contract.js create mode 100644 tests/firebase-auth-staging-contract.test.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index eb545b4..c24e312 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,7 +39,7 @@ jobs: ruby -e 'require "yaml"; YAML.safe_load(File.read(".github/workflows/deploy.yml"), permitted_classes: [], permitted_symbols: [], aliases: true)' ruby -e 'require "yaml"; YAML.safe_load(File.read(".github/workflows/verify-staging-authority.yml"), permitted_classes: [], permitted_symbols: [], aliases: true)' ruby -e 'require "yaml"; YAML.safe_load(File.read(".github/workflows/ci.yml"), permitted_classes: [], permitted_symbols: [], aliases: true)' - node --test tests/ci-workflow.test.js tests/release-workflow.test.js tests/firebase-release-verification.test.js tests/firebase-hosting-foundation.test.js tests/staging-authority-workflow.test.js tests/test-artifact-safety.test.js tests/root-dependency-security.test.js + node --test tests/ci-workflow.test.js tests/release-workflow.test.js tests/firebase-release-verification.test.js tests/firebase-hosting-foundation.test.js tests/firebase-auth-staging-contract.test.js tests/staging-authority-workflow.test.js tests/test-artifact-safety.test.js tests/root-dependency-security.test.js - name: Build env: CI: 'true' diff --git a/GITHUB_ISSUES.md b/GITHUB_ISSUES.md index 0f9c0a2..af8af49 100644 --- a/GITHUB_ISSUES.md +++ b/GITHUB_ISSUES.md @@ -146,7 +146,7 @@ Do not redesign routing or hosting in this issue; WEB-001 owns that. WEB-001A1 [ **Current CI-001C boundary:** CI-001C1 [#667](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/667) supplies the staging-only protected read-only OIDC proof: exact immutable repository/owner IDs, `main`, the `staging` environment, one verifier workflow, two named individual reviewers, and a keyless principal with only project-identity read permission. Parent #133 still owns Firebase deployment roles and production protection; #136 still owns an actual backend release. -**Current CI-001D boundary:** CI-001D1 [#669](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/669) supplies the first staging data boundary: empty Native/Standard `(default)` Firestore in `us-west2`, delete protection enabled, PITR disabled, and exact Rules/indexes from `ee16bd16220ab58bd3a2add80dd2f39a1d514dd7`. The 418-case Rules suite passed, provider definitions matched, fixed anonymous server-only reads were denied, and root collection count remained zero. This does not provision Auth, Functions, App Check, provider sandboxes, reusable deploy authority, application records, or production behavior. Parent #133 still owns keyless Firebase deployment roles and production protection; #136 still owns an actual synthetic backend release. +**Current CI-001D boundary:** CI-001D1 [#669](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/669) supplies the first staging data boundary: empty Native/Standard `(default)` Firestore in `us-west2`, delete protection enabled, PITR disabled, and exact Rules/indexes from `ee16bd16220ab58bd3a2add80dd2f39a1d514dd7`. The 418-case Rules suite passed, provider definitions matched, fixed anonymous server-only reads were denied, and root collection count remained zero. CI-001D2 [#671](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/671) adds an email/password-only Auth source contract plus an exact staging/account/scope deployment guard; its first phase does not initialize Auth or create a user. Functions, App Check, provider sandboxes, reusable deploy authority, application records, and production behavior remain absent. Parent #133 still owns keyless Firebase deployment roles and production protection; #136 still owns the compatible synthetic backend release. ### Problem diff --git a/IMPLEMENTATION_PLAN.md b/IMPLEMENTATION_PLAN.md index 446ad0c..f0b6574 100644 --- a/IMPLEMENTATION_PLAN.md +++ b/IMPLEMENTATION_PLAN.md @@ -44,6 +44,8 @@ MPRC can open a race or merchandise item for sale only when the platform can: **CI-001D1 empty staging Firestore boundary:** [#669](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/669) provisions `(default)` as an empty Native/Standard database in `us-west2`, enables delete protection, keeps PITR disabled, and deploys only exact Rules/indexes from `ee16bd16220ab58bd3a2add80dd2f39a1d514dd7` with Firebase CLI 15.24.0. The Node 20/Java 21 Rules suite passes 418/418; provider readback matches the active Rules digest, four composite indexes, and 28 field overrides; fixed anonymous server-only reads are denied; root collection count remains zero. This completes only the first staging data boundary. Auth test configuration, Functions, App Check, outside-provider sandboxes, keyless Firebase deployment roles, synthetic signed-in flows, website republication, production protection, and production behavior remain later children of #105/#133/#136. +**CI-001D2 staging Auth source contract:** [#671](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/671) adds only `emailPassword: true` under the Firebase Auth provider source, preserves the `demo-mprc-local` default alias, and pins a guarded staging-only `--only auth` command to Firebase CLI 15.24.0. The guard requires the exact staging project, quota project, club-account declaration, Auth-only scope, and short-lived token path while rejecting an ADC override. This source phase does not initialize Auth, enable a provider, create a user, send email, configure Google sign-in, change billing, deploy Functions, or prove signed-in behavior. Provider deployment and disposable create/sign-in/delete evidence follow only after this source reaches exact `main`. + ## 3. Dependency map ```mermaid diff --git a/OFFICER_START_HERE.md b/OFFICER_START_HERE.md index 46318ac..2a0f9e4 100644 --- a/OFFICER_START_HERE.md +++ b/OFFICER_START_HERE.md @@ -50,7 +50,7 @@ As of **2026-08-13**, a merge runs checks but does not start the GitHub release. As of **2026-08-14**, [#659](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/659) completed one accessibility-only Netlify release. Deploy `6a7ece87c5ca4d0007c1a3fc` is live from frozen source `7496fe0881fb52908c4ff2f40f488df09c94c908`; its exact 62-file marker and signed-out desktop/phone route-focus and menu checks passed. Repause merge `3138a00c1c48e1d5d1dcda0b44722b09a2194ff7` made the manifest inactive. Its attempt `6a7ed0ddb00a46000818878d` published nothing and retained the verified deploy. #623 deploy `6a7e072f8f346b0008510d29` is the recorded rollback, not current production. #659 changed no Firebase, provider configuration, account, sign-in state, production data, payment, or connected directory behavior. Officers do not run commands, sign in, enter data, or approve this as a reusable release. Follow the completed record in [Review, merge, release, and check a change](./docs/officers/PUBLISH_AND_CHECK.md). -As of **2026-08-26**, WEB-001A1 [#663](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/663) is merged, and WEB-001A2 [#665](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/665) created the club-owned `run-mprc-staging` project and published exact source `a614c68d9a1a2be631a3be874a686d61e5d170a0` to [run-mprc-staging.web.app](https://run-mprc-staging.web.app). CI-001C1 [#667](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/667) adds a separate protected read-only identity check: Dave Liu or Jeff Chang reviews exact `main`, and a keyless five-minute identity may read only the staging project identity. It cannot deploy Firebase. CI-001D1 [#669](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/669) adds an empty, delete-protected staging Firestore database and the exact reviewed Rules/index definitions. Anonymous reads of three fixed server-only paths were denied, and the database still had zero root collections after verification. This remains an **ENGINEERING STAGING SITE ONLY**. It has no Auth test setup, Functions, App Check, provider sandbox, protected automatic release, release marker, tested predecessor rollback, security-header policy, custom domain, or officer publish control. Officers do not sign in, enter data, run commands, or treat the empty database as a usable backend. Netlify still answers `runmprc.com`; production Firebase Hosting remains empty, and no DNS, GitHub Pages, Netlify, production Firebase, production data, or live website behavior changed. +As of **2026-08-26**, WEB-001A1 [#663](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/663) is merged, and WEB-001A2 [#665](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/665) created the club-owned `run-mprc-staging` project and published exact source `a614c68d9a1a2be631a3be874a686d61e5d170a0` to [run-mprc-staging.web.app](https://run-mprc-staging.web.app). CI-001C1 [#667](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/667) adds a separate protected read-only identity check: Dave Liu or Jeff Chang reviews exact `main`, and a keyless five-minute identity may read only the staging project identity. It cannot deploy Firebase. CI-001D1 [#669](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/669) adds an empty, delete-protected staging Firestore database and the exact reviewed Rules/index definitions. Anonymous reads of three fixed server-only paths were denied, and the database still had zero root collections after verification. CI-001D2 [#671](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/671) first adds only the reviewed email/password Auth source and a fixed staging-only guard; that source does not initialize Auth or create a user. This remains an **ENGINEERING STAGING SITE ONLY**. It has no deployed Auth proof, Functions, App Check, provider sandbox, protected automatic release, release marker, tested predecessor rollback, security-header policy, custom domain, or officer publish control. Officers do not sign in, enter data, run commands, or treat the empty database or Auth source as a usable backend. Netlify still answers `runmprc.com`; production Firebase Hosting remains empty, and no DNS, GitHub Pages, Netlify, production Firebase, production data, or live website behavior changed. The optional profile-photo and officer People-finder functions are still **NOT AVAILABLE YET**. #621 makes the frontend default an inert preview: My Account shows the future photo and separate finder-choice controls disabled, while the People finder stays behind the administrator guard and shows its name field and Search button disabled. The preview reads no saved photo or setting, accepts or uploads no photo, searches no name, and saves nothing; it shows no people or sample results. #623 published exactly that disabled interface as deploy `6a7e072f8f346b0008510d29`. Officers inspect the protected layouts only in synthetic local artifacts. The completed signed-out production review proved only the exact revision, normal sign-in and administrator guards, and absence of a member-directory network request. Do not sign in to production, choose a real photo, enter a real name, or treat the preview as a directory. #623 changed no Firebase, provider configuration, account, sign-in, or production data. #507 must later prove the privacy, authorization, staging, backend-first deployment, and readback gates before a separate reviewed source change may connect it. Follow the preview and source-review procedure in [Events, shop, members, and money](./docs/officers/EVENTS_SHOP_MEMBERS.md). diff --git a/OPERATIONS_RUNBOOK.md b/OPERATIONS_RUNBOOK.md index fc99b11..29704a6 100644 --- a/OPERATIONS_RUNBOOK.md +++ b/OPERATIONS_RUNBOOK.md @@ -63,7 +63,7 @@ Maintain an internal inventory with project/account IDs and console links: | --- | --- | --- | --- | --- | --- | | Local source runtime | `demo-mprc-local` emulators | Providers remain separately gated | `localhost:3000` | None | Available for synthetic Firebase work after all three loopback emulators report ready | | CI | Separate named demo/emulator projects | Fixtures/test only | None | CI | Frontend, Functions, Rules, SPA, and commerce-journal checks run; broader gates remain partial | -| Staging | `run-mprc-staging` | Empty Firestore Rules/index boundary only; no Auth, Functions, App Check, or provider sandbox | `run-mprc-staging.web.app`; `dev.runmprc.com` not configured | `staging`: two reviewers, exact `main`, read-only OIDC verifier | Static signed-out Hosting, protected project-identity proof, and empty delete-protected Firestore are available; usable backend/provider staging remains a launch blocker | +| Staging | `run-mprc-staging` | Empty Firestore Rules/index boundary plus an email/password-only Auth source contract that is not deployed; no Functions, App Check, or provider sandbox | `run-mprc-staging.web.app`; `dev.runmprc.com` not configured | `staging`: two reviewers, exact `main`, read-only OIDC verifier | Static signed-out Hosting, protected project-identity proof, empty delete-protected Firestore, and reviewed Auth source are available; usable backend/provider staging remains a launch blocker | | Production | `mid-peninsula-running-club` (verify) | Live + unique endpoint | `runmprc.com` | `production` | Informational site only until gates close | WEB-001A2 [#665](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/665) created and provider-read back `run-mprc-staging`, one web app, and Hosting version `3ffadcf2ac8cc760` from source `a614c68d9a1a2be631a3be874a686d61e5d170a0` on 2026-08-26. Always name that project explicitly; `.firebaserc` still defaults to the demo namespace. The static host is not complete environment isolation. @@ -72,6 +72,8 @@ CI-001C1 [#667](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/667) adds CI-001D1 [#669](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/669) provisioned only the staging `(default)` Firestore database and deployed exact Rules/indexes from source `ee16bd16220ab58bd3a2add80dd2f39a1d514dd7`. Readback on 2026-08-26 showed Native mode, Standard edition, `us-west2`, delete protection enabled, PITR disabled, Rules SHA-256 `fe3f53302db76df5febf7e9d242d82a6b6bec0c0b574ecc8253f1034f1a77dba`, four matching composite indexes, 28 matching field overrides, and zero root collections. Anonymous reads of fixed nonexistent `mail`, `stripeEvents`, and `auditEvents` documents returned `403`. The Rules emulator suite passed 418/418 before provider mutation. No application record, Auth provider/test user, Function, App Check setting, Storage data, outside provider, Hosting version, production Firebase surface, or website changed. +CI-001D2 [#671](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/671) first adds the source contract for standard Firebase Authentication: only `emailPassword: true`, with no anonymous, Google, or phone provider in source. `npm run deploy:staging-auth` is fixed to Firebase CLI 15.24.0, `--only auth`, and `run-mprc-staging`; its lifecycle guard also requires that exact cloud/quota project, the declared `runmprc@gmail.com` account, a short-lived `FIREBASE_TOKEN`, no ADC file override, and the unchanged demo default alias. Do not run that command from a branch. Merge and verify the exact source first. The source phase does not initialize Auth, configure a provider, create a user, send email, change billing, or authorize sign-in. + The repository's only `.firebaserc` default is now `demo-mprc-local`, a deliberately non-deployable local namespace. It is an accidental-deploy brake, not staging. Every future deployment must still pass one exact privately approved project explicitly. Do not infer backend/provider isolation or release authority from the static host; #113/#133 must protect, map, and verify those remaining boundaries. ## 4. Configuration and secrets @@ -99,7 +101,7 @@ REACT_APP_STRAVA_CLIENT_ID The Firebase web configuration is public application configuration. It must still be environment-specific and paired with Auth restrictions, Firestore rules, and App Check. WEB-001A1 [#663](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/663) makes controlled optimized builds choose `staging` or `production`; staging project IDs must include a distinct `staging` segment and the complete artifact check rejects known production identities. Production accepts only the exact reviewed public values. These values are not server credentials, but do not paste provider-console screenshots, private links, tokens, or any server credential into source or an issue. -`firebase.json` has a Hosting block for `build/` and missing-path SPA rewrites. Its predeploy chain rejects a Firebase CLI project that differs from the selected browser project, then rebuilds and scans executable JavaScript before upload. **Static engineering staging, a protected read-only authority check, and an empty Rules-protected Firestore boundary are available; a usable backend and production publication are NOT AVAILABLE YET.** #665 used that chain for the first `run-mprc-staging` Hosting release. #667's WIF identity can read only the project identity; it cannot deploy. #669 was a separate bounded database/Rules/index operation and did not grant reusable authority. No protected Firebase deploy role, Auth test setup, Function, App Check enforcement, provider sandbox, production authority, custom domain, release marker, tested rollback, production release, or DNS/TLS change exists. +`firebase.json` has a Hosting block for `build/` and missing-path SPA rewrites. Its predeploy chain rejects a Firebase CLI project that differs from the selected browser project, then rebuilds and scans executable JavaScript before upload. It also has the #671 email/password-only Auth source block; that block is configuration intent, not provider evidence. **Static engineering staging, a protected read-only authority check, an empty Rules-protected Firestore boundary, and the reviewed Auth source contract are available; a usable backend and production publication are NOT AVAILABLE YET.** #665 used the Hosting chain for the first `run-mprc-staging` release. #667's WIF identity can read only the project identity; it cannot deploy. #669 was a separate bounded database/Rules/index operation and did not grant reusable authority. #671 provider deployment must wait for exact-main source and use its guarded Auth-only command. No protected Firebase deploy role, deployed Auth proof, Function, App Check enforcement, provider sandbox, production authority, custom domain, release marker, tested rollback, production release, or DNS/TLS change exists. ### App Check release state — NOT AVAILABLE YET @@ -279,11 +281,12 @@ npm --prefix functions run lint npm --prefix functions run test:run -- --runInBand CI=true npm test -- --watchAll=false --runInBand npm run test:spa-navigation +npm run test:firebase-auth-staging node .github/scripts/check-frontend-lint.cjs node --test tests/test-artifact-safety.test.js ``` -The frontend Jest command is a dependable local baseline and hosted CI runs it as the blocking `Run frontend Jest tests` step under [#124](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/124). Hosted CI also runs the separate Node SPA suite as `Run SPA callback safety tests` under [#126](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/126). CI-001B3 [#167](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/167) adds the blocking `Commerce command journal emulator` job. That job uses exact demo project `demo-pay002b2-test`, Firestore only, an explicit opt-in, and the focused transaction suite. CI-001B4 [#186](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/186) merged through [PR #189](https://github.com/Run-MPRC/Run-MPRC.github.io/pull/189) as `bec7d5e365eacb418563a172029f241f660d9768`. It adds the named blocking `Run non-mutating frontend lint` step using the checker directly, before application tests can change its process environment. Its trusted prefix pins the hosted runner, read-only job permission, credential-free checkout, Node 20 setup, install with lifecycle scripts disabled, lint, and the immediately following clean check. With WEB-SUGGESTIONS-001A [#618](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/618) and MEMBERS-DIRECTORY-001D [#621](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/621), the checker covers 120 frontend files: 12 JS, 35 JSX, 43 TS, and 30 TSX. The lint process disables the repository-wide `eslint-plugin-only-warn` hook so the original configured severities remain visible. CI-001B4A [#227](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/227) removes one reviewed `arrow-body-style` record, CI-001B4B [#239](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/239) removes one stale `AdminMembers` unknown-rule suppression record, and PAY-004C1 [#359](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/359) removes one prompt warning plus two label-association errors with the unsafe reusable-link controls. Reviewed functional changes in [PR #391](https://github.com/Run-MPRC/Run-MPRC.github.io/pull/391) and [PR #392](https://github.com/Run-MPRC/Run-MPRC.github.io/pull/392) add three TypeScript files and retire four TSX errors. CI-001B4C [#449](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/449) replaces exactly four stale `react-hooks/exhaustive-deps` directives with ordinary comments on the same lines and changes no executable code. MEMBERS-CONTENT-001B [#492](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/492) removes one finding-free dormant JSX file, #505 adds four finding-free TypeScript/TSX files, #506 adds four more finding-free TypeScript/TSX files, #618 adds one finding-free JSX page, and #621 adds one finding-free TypeScript availability module, leaving 113 configured errors and 6 warnings. This is accepted debt, not a clean-lint claim. Any finding added, removed, moved, or changed must be understood and the exact record updated in the same reviewed pull request; a fatal parser, configuration, or unexpected process failure always stops with one fixed diagnostic. Never regenerate the baseline only to make a check green. The clean-checkout step compares tracked files with `HEAD` and rejects non-ignored untracked files. Ignored dependency and build paths are outside this proof. The original #186 PR run `29291402007` and post-merge run `29291515653` passed the install, lint, clean, test, workflow-validation, build, Functions, Rules, commerce, and artifact jobs; each later focused issue records its separate source and hosted evidence. These runs prove source execution only: branch protection, production publication, Firebase, Stripe, and provider state remain unchanged. TEST-001A2 [#177](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/177) adds the exact `Test artifact scrubber` job. It deliberately creates bounded synthetic output and proves that the dependency-free scanner accepts reserved examples, rejects the documented recognizable sensitive patterns with fixed messages, ignores source outside an explicit output root, and protects both release job lists. Current CI uploads no test report. Later report-producing work must scan its explicit output before upload. A green scrubber proves only that this deliberate source test ran. It is not a general data-loss-prevention guarantee or proof that an unknown or newly obfuscated sensitive value is absent, that an artifact was uploaded, that all runtime logs are safe, or that Firebase, Stripe, the website, or another provider changed. The manual-release source invariants remain under [#135](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/135). Protected environment/OIDC configuration, actual staged/live deployment, branch-required-check configuration, and controlled Netlify publication remain **NOT AVAILABLE YET** under [#105](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/105), [#133](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/133), [#136](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/136), and WEB-001. +The frontend Jest command is a dependable local baseline and hosted CI runs it as the blocking `Run frontend Jest tests` step under [#124](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/124). Hosted CI also runs the separate Node SPA suite as `Run SPA callback safety tests` under [#126](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/126), and #671 adds the dependency-free Auth source/deploy-contract suite to the protected workflow-validation command. CI-001B3 [#167](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/167) adds the blocking `Commerce command journal emulator` job. That job uses exact demo project `demo-pay002b2-test`, Firestore only, an explicit opt-in, and the focused transaction suite. CI-001B4 [#186](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/186) merged through [PR #189](https://github.com/Run-MPRC/Run-MPRC.github.io/pull/189) as `bec7d5e365eacb418563a172029f241f660d9768`. It adds the named blocking `Run non-mutating frontend lint` step using the checker directly, before application tests can change its process environment. Its trusted prefix pins the hosted runner, read-only job permission, credential-free checkout, Node 20 setup, install with lifecycle scripts disabled, lint, and the immediately following clean check. With WEB-SUGGESTIONS-001A [#618](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/618) and MEMBERS-DIRECTORY-001D [#621](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/621), the checker covers 120 frontend files: 12 JS, 35 JSX, 43 TS, and 30 TSX. The lint process disables the repository-wide `eslint-plugin-only-warn` hook so the original configured severities remain visible. CI-001B4A [#227](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/227) removes one reviewed `arrow-body-style` record, CI-001B4B [#239](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/239) removes one stale `AdminMembers` unknown-rule suppression record, and PAY-004C1 [#359](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/359) removes one prompt warning plus two label-association errors with the unsafe reusable-link controls. Reviewed functional changes in [PR #391](https://github.com/Run-MPRC/Run-MPRC.github.io/pull/391) and [PR #392](https://github.com/Run-MPRC/Run-MPRC.github.io/pull/392) add three TypeScript files and retire four TSX errors. CI-001B4C [#449](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/449) replaces exactly four stale `react-hooks/exhaustive-deps` directives with ordinary comments on the same lines and changes no executable code. MEMBERS-CONTENT-001B [#492](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/492) removes one finding-free dormant JSX file, #505 adds four finding-free TypeScript/TSX files, #506 adds four more finding-free TypeScript/TSX files, #618 adds one finding-free JSX page, and #621 adds one finding-free TypeScript availability module, leaving 113 configured errors and 6 warnings. This is accepted debt, not a clean-lint claim. Any finding added, removed, moved, or changed must be understood and the exact record updated in the same reviewed pull request; a fatal parser, configuration, or unexpected process failure always stops with one fixed diagnostic. Never regenerate the baseline only to make a check green. The clean-checkout step compares tracked files with `HEAD` and rejects non-ignored untracked files. Ignored dependency and build paths are outside this proof. The original #186 PR run `29291402007` and post-merge run `29291515653` passed the install, lint, clean, test, workflow-validation, build, Functions, Rules, commerce, and artifact jobs; each later focused issue records its separate source and hosted evidence. These runs prove source execution only: branch protection, production publication, Firebase, Stripe, and provider state remain unchanged. TEST-001A2 [#177](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/177) adds the exact `Test artifact scrubber` job. It deliberately creates bounded synthetic output and proves that the dependency-free scanner accepts reserved examples, rejects the documented recognizable sensitive patterns with fixed messages, ignores source outside an explicit output root, and protects both release job lists. Current CI uploads no test report. Later report-producing work must scan its explicit output before upload. A green scrubber proves only that this deliberate source test ran. It is not a general data-loss-prevention guarantee or proof that an unknown or newly obfuscated sensitive value is absent, that an artifact was uploaded, that all runtime logs are safe, or that Firebase, Stripe, the website, or another provider changed. The manual-release source invariants remain under [#135](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/135). Protected environment/OIDC configuration, actual staged/live deployment, branch-required-check configuration, and controlled Netlify publication remain **NOT AVAILABLE YET** under [#105](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/105), [#133](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/133), [#136](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/136), and WEB-001. ### CI-001B5 Functions job credential boundary — SOURCE ONLY diff --git a/README.md b/README.md index 62a2d95..daadfe0 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ Historical developer/content/LLM guides remain under [`docs/`](./docs/README.md) **Deployment reality checked 2026-08-14:** merges run CI but do not start the manual release workflow. The protected gate accepts one exact current merged commit, rechecks its newest CI run after approval, uses one fixed Firebase target set, fails when protected authority/configuration is missing, verifies Firebase before publishing GitHub Pages, and gives no server credential to website preparation or publication. Ordinary Git-triggered Netlify production builds are paused. WEB-002D [#659](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/659) completed one bounded accessibility release: exact merge `46e23647d8e0bf9fa3a574ea5c5f993be10a419d` published deploy `6a7ece87c5ca4d0007c1a3fc` from source `7496fe0881fb52908c4ff2f40f488df09c94c908`, tree `ccac4c189c195db8ab594e0eefe256ea9fa04996`, 62 files, and artifact digest `e4c26e6f0fbcd086663d86238675f0be228fb649a00628c1c97d1166612f49c7`. Signed-out desktop and phone route-focus/menu checks passed. Repause merge `3138a00c1c48e1d5d1dcda0b44722b09a2194ff7` passed exact-main CI run `31783808994`; attempt `6a7ed0ddb00a46000818878d` published nothing, deploy `6a7ece87c5ca4d0007c1a3fc` remained live, and the manifest is inactive. #623 deploy `6a7e072f8f346b0008510d29` remains rollback history and its inert directory interface is unchanged beneath the accessibility delta. No Firebase, Rules, Functions, indexes, provider configuration, account, sign-in state, role, member data, payment, or connected directory backend changed. The prior bounded #473 deploy `6a6dc9ea588b0c0008036312` remains older history. The source stops adding a Pages `CNAME`, but GitHub Pages currently still claims `runmprc.com` and its default URL redirects there; only a controlled #136/WEB-001 publication and provider readback can clear that conflict. Reusable protected publication to the live Netlify-served `runmprc.com` is not configured yet. Treat GitHub Pages, Netlify, `runmprc.com`, Firebase, and outside providers as separate states. -**Owned staging checked 2026-08-26:** WEB-001A2 [#665](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/665) created the isolated `run-mprc-staging` Firebase project under the club account and published exact source `a614c68d9a1a2be631a3be874a686d61e5d170a0` as Hosting version `3ffadcf2ac8cc760` at [run-mprc-staging.web.app](https://run-mprc-staging.web.app). Signed-out desktop, phone, and direct `/events` checks passed. CI-001C1 [#667](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/667) adds a protected staging-only OIDC verifier with two named reviewers and a keyless principal that can read only the exact project identity. CI-001D1 [#669](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/669) adds the empty delete-protected `(default)` Firestore database in `us-west2` and deploys exact Rules/indexes from source `ee16bd16220ab58bd3a2add80dd2f39a1d514dd7`; provider definitions matched, fixed anonymous server-only reads were denied, and the root collection count remained zero. This is still incomplete engineering staging: Auth test configuration, Functions, App Check enforcement, outside-provider sandboxes, reusable Firebase deployment authority, application security headers, a release marker, and a tested predecessor rollback are not configured. Do not sign in or use real data there. Production Firebase Hosting remains empty and `runmprc.com` remains on its existing Netlify deployment. +**Owned staging checked 2026-08-26:** WEB-001A2 [#665](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/665) created the isolated `run-mprc-staging` Firebase project under the club account and published exact source `a614c68d9a1a2be631a3be874a686d61e5d170a0` as Hosting version `3ffadcf2ac8cc760` at [run-mprc-staging.web.app](https://run-mprc-staging.web.app). Signed-out desktop, phone, and direct `/events` checks passed. CI-001C1 [#667](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/667) adds a protected staging-only OIDC verifier with two named reviewers and a keyless principal that can read only the exact project identity. CI-001D1 [#669](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/669) adds the empty delete-protected `(default)` Firestore database in `us-west2` and deploys exact Rules/indexes from source `ee16bd16220ab58bd3a2add80dd2f39a1d514dd7`; provider definitions matched, fixed anonymous server-only reads were denied, and the root collection count remained zero. CI-001D2 [#671](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/671) adds a source-controlled email/password-only Auth contract and a staging/account/scope guard, but this source phase does not initialize Auth or create a user. This is still incomplete engineering staging: Auth provider deployment and synthetic proof, Functions, App Check enforcement, outside-provider sandboxes, reusable Firebase deployment authority, application security headers, a release marker, and a tested predecessor rollback are not configured. Do not sign in or use real data there. Production Firebase Hosting remains empty and `runmprc.com` remains on its existing Netlify deployment. ## Local setup status @@ -68,7 +68,7 @@ npm start Open only `http://localhost:3000`. Stop if Firebase traffic uses a non-loopback host. Use synthetic records only. This does **not** make checkout, refunds, email, Strava, or other outside-provider calls safe; follow [OPERATIONS_RUNBOOK.md](./OPERATIONS_RUNBOOK.md) before any provider test. -Do not use a Netlify preview, locally served optimized `build/`, or `run-mprc-staging` for sign-in, private pages, admin work, or provider testing. WEB-001A1 [#663](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/663) makes optimized builds choose `staging` or `production` explicitly. WEB-001A2 [#665](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/665) verifies the owned static stage, and CI-001D1 [#669](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/669) verifies only its empty Firestore Rules/index boundary. Auth, Functions, App Check, and outside providers are still not isolated for end-to-end use. +Do not use a Netlify preview, locally served optimized `build/`, or `run-mprc-staging` for sign-in, private pages, admin work, or provider testing. WEB-001A1 [#663](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/663) makes optimized builds choose `staging` or `production` explicitly. WEB-001A2 [#665](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/665) verifies the owned static stage, CI-001D1 [#669](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/669) verifies only its empty Firestore Rules/index boundary, and CI-001D2 [#671](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/671) initially supplies only the reviewed Auth source contract. Auth provider behavior, Functions, App Check, and outside providers are still not isolated for end-to-end use. ## Verification @@ -77,6 +77,7 @@ npm --prefix functions run lint npm --prefix functions run test:run -- --runInBand CI=true npm test -- --watchAll=false --runInBand npm run test:spa-navigation +npm run test:firebase-auth-staging npm run test:rules CI=true DISABLE_ESLINT_PLUGIN=true \ REACT_APP_FIREBASE_ENVIRONMENT=staging \ @@ -90,7 +91,7 @@ npx --no-install react-scripts build node scripts/firebase-hosting-contract.js verify build ``` -Rules and commerce-emulator tests require Java 21. The repository pins Firebase CLI 15.24.0; use only that lockfile copy with Node 20 and explicit `demo-*` projects. The synthetic values above authorize no provider call or deployment. The direct `react-scripts build` command avoids regenerating `public/sitemap.xml`; the following verifier proves that executable JavaScript selected the synthetic staging project and contains no known production Firebase identity. The normal `npm run build` validates its selected hosted environment and runs the sitemap generator. Hosted CI runs the frontend Jest suite under [#124](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/124), the SPA callback suite under [#126](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/126), and the release-gate source tests under [#135](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/135). #667 supplies a separate protected staging project-identity check only. #669 proves the exact Rules/index files on an empty staging database, but grants no reusable deploy authority and proves no signed-in behavior. Parent [#133](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/133) still owns Firebase deployment roles and production protection; [#136](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/136) owns the actual staged profile-recovery release. Required branch checks, complete backend/provider isolation, protected publication, security headers, rollback proof, and hosting consolidation remain open under [#105](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/105) and their atomic children. +Rules and commerce-emulator tests require Java 21. The repository pins Firebase CLI 15.24.0; use only that lockfile copy with Node 20 and explicit `demo-*` projects. The synthetic values above authorize no provider call or deployment. The direct `react-scripts build` command avoids regenerating `public/sitemap.xml`; the following verifier proves that executable JavaScript selected the synthetic staging project and contains no known production Firebase identity. The normal `npm run build` validates its selected hosted environment and runs the sitemap generator. Hosted CI runs the frontend Jest suite under [#124](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/124), the SPA callback suite under [#126](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/126), and the release-gate source tests under [#135](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/135). #667 supplies a separate protected staging project-identity check only. #669 proves the exact Rules/index files on an empty staging database, but grants no reusable deploy authority and proves no signed-in behavior. #671 adds a focused source test and an Auth-only deploy guard; source and CI do not prove that staging Auth is initialized. Parent [#133](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/133) still owns Firebase deployment roles and production protection; [#136](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/136) owns the actual staged profile-recovery release. Required branch checks, complete backend/provider isolation, protected publication, security headers, rollback proof, and hosting consolidation remain open under [#105](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/105) and their atomic children. These safety changes do not repair a missing member profile or prove deployed Firebase Rules/Functions. The reported profile-save failure remains [#118](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/118). diff --git a/SECURITY.md b/SECURITY.md index 364723b..fbb2196 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -47,7 +47,7 @@ The findings below describe the repository at the start of the 2026-07-12 assess | RISK-007 | Repository source rejects unverified targets at the two existing role-grant endpoints. AUTH-001B [#196](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/196) adds exact verified-email checks to role-based Firestore Rules, and AUTH-001C [#209](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/209) tracks the matching gate at current Functions role consumers. Firebase deployment, verification-mirror refresh, revocation, authoritative membership, scoped capability/recent-auth controls, and replacement of the legacy static-key endpoint remain unproven. | Older deployed code or a remaining role-only server guard can let an unverified known-email account use member/admin authority. CSV export also retains separate minimization, recent-auth, and audit risks. | Deploy and verify the exact Rules/Functions through #105, complete the remaining AUTH-001 mirror/revocation work, require authoritative membership and scoped privileged access, harden roster export under #116, and retire the static-key endpoint. | | RISK-008 | `requireAppCheck` is controlled by optional `ENFORCE_APP_CHECK`; repository/deployment config does not prove it is true. Missing site key also disables client App Check. | Public callable functions may be scripted directly, enabling abuse and cloud/Stripe cost amplification. | Use Firebase runtime `enforceAppCheck: true` for sensitive callables, reCAPTCHA Enterprise, staged metrics, and no environment fail-open. | | RISK-009 | #99 source preserves and validates the complete same-origin Pages callback route, applies `strict-origin` before subresources, and suppresses initial callback App Check/telemetry startup; #126 adds that standalone suite as a blocking hosted CI step. #443 adds only the source/test Strava same-document handoff after the native and Router callback locations are clean. No production Stripe/Strava callback, provider-backed App Check token behavior, or live custom-domain handoff has been rehearsed, and the live custom domain uses a different host. | Hosting, provider, configuration, or release drift can still break return links or the App Check handoff even when source tests pass. Enforcing a callback callable without its matching released client handoff and staged provider evidence can also break confirmation or Strava exchange. | Keep the hosted callback gate blocking; keep `lookupRegistration`/`lookupOrder` deferred until DATA-001A; keep native enforcement for `stravaExchangeCode` deferred to ABUSE-001A2 after the exact #443 website source and provider-backed Enterprise token behavior are proven in protected staging; verify test-mode callbacks only after provider gates; consolidate hosting under WEB-001. | -| RISK-010 | Development/test use synthetic loopback Auth, Firestore, and Functions. WEB-001A1 [#663](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/663) binds controlled Hosting builds to an explicit environment, and WEB-001A2 [#665](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/665) supplies owned static staging. CI-001D1 [#669](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/669) adds an empty delete-protected Firestore database with exact Rules/indexes. App Check, Auth test configuration, Functions, Storage data, provider sandboxes, protected Firebase deployment authority, and a release marker remain absent; emulated Functions can still call outside providers. | A partially provisioned stage, bypassed build path, or provider-capable emulator can be mistaken for complete staging and affect production/provider systems. | Keep private actions out of previews; preserve the fixed project and exact Rules boundary; complete #113/#133/#136 Auth, Functions, App Check, short-lived deploy authority, and provider isolation; prove test keys, sinks, exact marker readback, and rollback before end-to-end flows. | +| RISK-010 | Development/test use synthetic loopback Auth, Firestore, and Functions. WEB-001A1 [#663](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/663) binds controlled Hosting builds to an explicit environment, and WEB-001A2 [#665](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/665) supplies owned static staging. CI-001D1 [#669](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/669) adds an empty delete-protected Firestore database with exact Rules/indexes. CI-001D2 [#671](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/671) adds email/password-only Auth source and a narrow staging deployment guard, but its source phase does not initialize Auth. App Check, deployed Auth proof, Functions, Storage data, provider sandboxes, protected Firebase deployment authority, and a release marker remain absent; emulated Functions can still call outside providers. | A partially provisioned stage, bypassed build path, or provider-capable emulator can be mistaken for complete staging and affect production/provider systems. | Keep private actions out of previews; preserve the fixed project, exact Rules, and Auth-only source guard; complete #671 provider proof plus #113/#133/#136 Functions, App Check, short-lived deploy authority, and provider isolation; prove test keys, sinks, exact marker readback, and rollback before end-to-end flows. | | RISK-011 | Local cancellation only changes Firestore; it does not expire the active Stripe Session. The webhook can later move a cancelled record back to paid. | Customers can pay a supposedly cancelled registration/order; capacity/inventory and support state diverge. | Add explicit cancellation saga with Stripe Session expiry and allowed-state transitions. | | RISK-012 | Repository source now denies every admitted positive late-registration amount before identifier/token allocation, registration writes, Stripe construction, or Product/Price/Payment Link calls, and the Admin screen exposes only the exact-zero compatibility path. Firebase deployment is unproven, and legacy reusable Payment Links may still exist. | Older deployed code or an existing link can still create unreconciled or repeated charges/Sessions for one logical registration. The exact-zero local record is also marked paid without Stripe and is not payment, free, comp, or membership authority. | Keep paid late registration unavailable; inventory, deactivate, and reconcile legacy links privately; then replace them with one-off idempotent Checkout Sessions through the normal registration/payment lifecycle. | | RISK-013 | The assessment baseline permitted Stripe promotions while Firestore stored base price. Repository source now disables promotion entry and automatic tax, requires a complete adjustment breakdown, and quarantines unknown or nonzero discount/tax/shipping amounts; deployment and provider state are unproven. | An older open Session, provider drift, skipped backend release, or later guard regression could still make reporting, refunds, and local payment confirmation disagree. | Privately inventory pre-change Sessions/provider settings, release through the protected gate, verify Firebase and Stripe separately, and keep every adjustment disabled until an approved authoritative model exists. | @@ -330,6 +330,8 @@ CI-001C1 [#667](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/667) estab CI-001D1 [#669](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/669) used only the club account and an explicitly named staging project for the one-time database provision and bounded Rules/index deploy. Firebase CLI 15.24.0 JSON account diagnostics can include token-bearing account objects; never run `firebase login:list --json` in CI, a captured terminal, an issue, or an AI transcript. Use non-token identity checks, revoke any accidentally exposed OAuth grant immediately, and prefer the protected keyless deployment path once #133 grants the exact reviewed staging roles. +CI-001D2 [#671](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/671) source permits only email/password Auth, keeps the unqualified Firebase alias in `demo-mprc-local`, and pins the provider command to `--only auth --project run-mprc-staging`. Its predeploy guard requires the exact quota project, declared club account, and a short-lived Firebase token while rejecting an ADC file override. This source and its CI test grant no authority and make no provider change; provider initialization must use exact merged source and must clean the disposable identity back to zero users. + ## 10. Incident response ### Severity examples diff --git a/SYSTEM_DESIGN.md b/SYSTEM_DESIGN.md index 14d5460..299a6eb 100644 --- a/SYSTEM_DESIGN.md +++ b/SYSTEM_DESIGN.md @@ -261,9 +261,21 @@ flowchart LR Text alternative: exact reviewed Rules and indexes pass the emulator suite before a staging-only deploy; active Rules deny anonymous server-only reads, and the protected staging database remains empty. +**CI-001D2 staging Auth source boundary — not deployed:** [#671](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/671) adds a reviewable provider and deploy contract before any Auth provider mutation. + +```mermaid +flowchart LR + Source["Exact main firebase.json\nemail/password only"] --> Guard["Staging project, club account,\nshort-lived token, Auth-only scope"] + Guard --> Future["Future exact-main Auth deploy"] + Broader["Anonymous, Google, phone,\nproduction, ADC, or broad scope"] --> Stop["Reject"] + Guard -. "source phase only" .-> Stop +``` + +Text alternative: the source permits only email/password Auth and a guarded future Auth-only deployment to the exact staging project; broader providers, production, ADC, or a broad deploy reject, and this source phase changes no provider. + ## 4. Target deployment topology -The target keeps React, Firebase, and Stripe, but places stronger boundaries around them. Migrating the frontend from GitHub Pages/Netlify to Firebase Hosting is recommended before live commerce because it supports controlled SPA rewrites, preview channels, and security headers. #663 supplies the fail-closed build selector and inert Hosting rewrite configuration. #665 owns the isolated static staging host. CI-001C1 [#667](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/667) adds only a protected, read-only staging identity proof; it grants no Firebase deployment permission. CI-001D1 [#669](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/669) adds only the empty staging Firestore database and exact Rules/index boundary. Backend Functions, Auth configuration, deployment authority, automatic exact-commit release, full readback, rollback, headers, production protection, outside-provider isolation, and DNS/TLS cutover remain separate work under #113/#133/#136/#460. The migration is not live merely because one staging layer exists. +The target keeps React, Firebase, and Stripe, but places stronger boundaries around them. Migrating the frontend from GitHub Pages/Netlify to Firebase Hosting is recommended before live commerce because it supports controlled SPA rewrites, preview channels, and security headers. #663 supplies the fail-closed build selector and inert Hosting rewrite configuration. #665 owns the isolated static staging host. CI-001C1 [#667](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/667) adds only a protected, read-only staging identity proof; it grants no Firebase deployment permission. CI-001D1 [#669](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/669) adds only the empty staging Firestore database and exact Rules/index boundary. CI-001D2 [#671](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/671) initially adds only the email/password Auth source and guarded staging-only command; provider configuration remains uninitialized until the exact-main deployment phase. Backend Functions, Auth provider proof, deployment authority, automatic exact-commit release, full readback, rollback, headers, production protection, outside-provider isolation, and DNS/TLS cutover remain separate work under #113/#133/#136/#460. The migration is not live merely because one staging layer exists. ```mermaid flowchart TB @@ -1748,7 +1760,7 @@ Returning a `2xx` response to Stripe means the event has been durably accepted o | --- | --- | --- | --- | --- | | Local source runtime | Firebase Emulator Suite under `demo-mprc-local` | Not safe by Firebase emulation alone | `localhost:3000` | Synthetic data only; browser Firebase traffic is loopback-only | | CI | Ephemeral emulators and mocks | Stripe fixtures/signature tests; optional isolated test account | None | Synthetic data, no production secrets | -| Staging | `run-mprc-staging`; static Hosting, one web app, protected read-only OIDC proof, and empty delete-protected Firestore with exact Rules/indexes | Not configured; no provider testing authorized | `run-mprc-staging.web.app`; `dev.runmprc.com` not configured | Signed-out public visual review and fixed anonymous denial checks only; no sign-in or real data | +| Staging | `run-mprc-staging`; static Hosting, one web app, protected read-only OIDC proof, empty delete-protected Firestore with exact Rules/indexes, and an email/password-only Auth source contract not yet deployed | Not configured; no provider testing authorized | `run-mprc-staging.web.app`; `dev.runmprc.com` not configured | Signed-out public visual review and fixed anonymous denial checks only; no sign-in or real data | | Production | Dedicated production Firebase project | Live restricted keys and production webhook secret | `runmprc.com` | Real data under documented retention and access policies | ```mermaid @@ -1769,9 +1781,9 @@ Text alternative: the local browser uses only the three loopback Firebase emulat #99 source selects a fully synthetic Firebase configuration for development/test, connects Auth, Firestore, and Functions to the loopback ports above, stops startup when connection setup fails, and leaves App Check, Analytics, and Sentry off locally. #139 additionally keeps Firebase Analytics off in every source environment and removes the direct Waiver SDK bypass pending an approved #110 policy. The Firebase CLI must still report all three emulators ready before the app is opened. Mocked endpoint tests alone do not prove listening processes, website publication, provider behavior, or deletion of earlier provider data/cookies. -Optimized builds must select `staging` or `production` explicitly. WEB-001A1 #663 rejects missing, local, incomplete, or cross-environment Firebase web configuration, and its artifact check rejects known production identities from a staging executable. Ordinary Netlify deploy previews receive and scan only the reserved synthetic configuration; `run-mprc-staging.web.app` receives and scans the real isolated staging web-app identity; the pinned release builder receives and scans the exact reviewed public production web configuration. The owned staging project now has an empty Rules-protected Firestore boundary, but it still lacks Auth test configuration, Functions, App Check, and outside-provider isolation. Both preview types remain restricted to public, signed-out, read-only visual review until #105/CONFIG supplies complete backend/provider evidence. Do not sign in, open private/admin pages, or test Firebase/provider actions in those previews. +Optimized builds must select `staging` or `production` explicitly. WEB-001A1 #663 rejects missing, local, incomplete, or cross-environment Firebase web configuration, and its artifact check rejects known production identities from a staging executable. Ordinary Netlify deploy previews receive and scan only the reserved synthetic configuration; `run-mprc-staging.web.app` receives and scans the real isolated staging web-app identity; the pinned release builder receives and scans the exact reviewed public production web configuration. The owned staging project now has an empty Rules-protected Firestore boundary and #671 source describes email/password-only Auth, but Auth remains uninitialized until exact-main provider deployment and disposable proof finish. It still lacks Functions, App Check, and outside-provider isolation. Both preview types remain restricted to public, signed-out, read-only visual review until #105/CONFIG supplies complete backend/provider evidence. Do not sign in, open private/admin pages, or test Firebase/provider actions in those previews. -`SITE_ORIGIN`, Firebase project IDs, App Check keys, Stripe keys, webhook endpoints, Sentry environments, and email providers must be environment-scoped. Live and test webhook secrets are never interchangeable. `run-mprc-staging` now supplies the public web-app, Hosting, and empty Firestore Rules/index boundary; end-to-end rollout still needs protected Auth, Functions, App Check, deployment authority, and outside-provider configuration and evidence. +`SITE_ORIGIN`, Firebase project IDs, App Check keys, Stripe keys, webhook endpoints, Sentry environments, and email providers must be environment-scoped. Live and test webhook secrets are never interchangeable. `run-mprc-staging` now supplies the public web-app, Hosting, empty Firestore Rules/index boundary, and Auth source contract; end-to-end rollout still needs deployed and verified Auth, Functions, App Check, deployment authority, and outside-provider configuration and evidence. CONFIG-001A [#149](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/149) adds the source boundary for current commerce and confirmation-mail Functions: local is exact loopback, tests use HTTPS `.test`, staging is exactly `https://dev.runmprc.com`, and production is exactly `https://runmprc.com`. Test/live expected mode must match the server-key marker on the four key-bound Functions. Webhook and mail receive no Stripe API key, and invalid settings stop before rate-limit, business, mail, event-ledger, or Stripe writes. This source boundary does not create staging, configure Secret Manager or Stripe, deploy Firebase, enable commerce, or prove live behavior. diff --git a/docs/officers/PUBLISH_AND_CHECK.md b/docs/officers/PUBLISH_AND_CHECK.md index be08931..0ef3718 100644 --- a/docs/officers/PUBLISH_AND_CHECK.md +++ b/docs/officers/PUBLISH_AND_CHECK.md @@ -488,6 +488,44 @@ Text alternative: exact reviewed Rules and indexes protect the empty staging dat **Escalation:** platform owner first, security owner second. Use the private incident path for unexpected records, access, or credential output. +## Review the staging Auth source contract — SOURCE ONLY, NOT DEPLOYED + +**Purpose:** let a backup officer confirm that the reviewed source permits only a narrow future staging Auth change, without signing in, creating an account, running a command, or treating source as provider proof. + +**Approver:** platform owner plus the named security reviewer for #671. + +**Prerequisites:** the #671 source pull request is merged, its exact-main CI is green, and the provider phase is still recorded separately as not run. + +```mermaid +flowchart LR + Source["firebase.json\nemail/password only"] --> Guard["Exact staging project, club account,\nshort-lived token, Auth-only scope"] + Guard --> Future["Future reviewed provider step"] + Broad["Anonymous, Google, phone, production,\nADC, or broad deploy"] --> Stop["Stop"] + Future -. "not performed by source" .-> Stop +``` + +Text alternative: reviewed source allows only email/password and a future Auth-only staging operation; broader providers, production, ADC, or broader deployment stop, and no provider change occurs from source or merge. + +1. Open issue [#671](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/671) and its source pull request. +2. Confirm the pull request says `Officer impact`, `Officer documentation`, and `Deployment evidence` separately. +3. Confirm the source names only email/password and does not name anonymous, Google, or phone sign-in as enabled. +4. Confirm the unqualified Firebase project remains the local demo project. +5. Confirm the future command names only Auth and `run-mprc-staging`. +6. Confirm the guard requires the club account declaration, exact staging and quota project, short-lived token path, and no ADC file override. +7. Confirm the five focused source tests and all five hosted CI jobs passed on the exact merge. +8. Confirm provider deployment, a test user, email, billing, Functions, App Check, Hosting, production Firebase, production data, and `runmprc.com` are each recorded as not changed. +9. Record the issue, pull request, exact merge, check date, approver, and provider status. Do not paste a token, console response, account record, or private screenshot. + +**Expected result:** a backup officer can distinguish a narrow reviewed Auth source contract from initialized or usable staging Auth. The staging site remains signed-out engineering review only. + +**Stop conditions:** stop if source enables another provider, changes the demo default, permits another project or deployment scope, accepts ADC, lacks the focused test, claims provider success from source/CI, or asks an officer to sign in, create a user, run a command, handle a credential, or inspect an account. + +**Success proof:** keep the #671 and pull-request links, exact merge, green focused/hosted tests, reviewed source summary, approver, date, and explicit provider-not-deployed statement. + +**Undo:** before provider deployment, use one reviewed revert or safe roll-forward pull request. Do not edit Firebase Console, delete a Firebase project, or create an account as an undo test. + +**Escalation:** platform owner first, security owner second. Use the private incident path if credential-shaped output or an unexpected account/provider appears. + ## Before a protected release — NOT AVAILABLE YET Do not use this section until #133 records that both GitHub environments are protected and tested. diff --git a/docs/officers/README.md b/docs/officers/README.md index f47eb70..de1c783 100644 --- a/docs/officers/README.md +++ b/docs/officers/README.md @@ -69,7 +69,7 @@ WEB-002D [#659](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/659) compl The general protected GitHub release is still **NOT AVAILABLE YET**. #667 configures only a separate staging read-only identity check: Dave Liu or Jeff Chang reviews exact `main`, and a keyless five-minute identity may read only the staging project identity. It cannot deploy Firebase or publish a website. #133 still owns backend deployment roles and production protection. Missing authority stops the release with a red failure before Firebase or website publication. -The empty staging Firestore boundary is available after #669. Project `run-mprc-staging` has a delete-protected empty database with the exact reviewed Rules and indexes. Fixed signed-out requests to server-only paths were denied, and the final root collection count was zero. This is not a usable backend: Auth test setup, Functions, App Check, provider sandboxes, reusable deployment authority, and signed-in testing remain **NOT AVAILABLE YET**. Officers do not sign in, enter records, run commands, or edit the database. Use the no-terminal [staging Firestore review](./PUBLISH_AND_CHECK.md#review-the-empty-staging-firestore-boundary--available-after-669). +The empty staging Firestore boundary is available after #669. Project `run-mprc-staging` has a delete-protected empty database with the exact reviewed Rules and indexes. Fixed signed-out requests to server-only paths were denied, and the final root collection count was zero. #671 first adds only a reviewed email/password Auth source contract and a staging-only deployment guard; it does not initialize Auth or create a user. This is not a usable backend: deployed Auth proof, Functions, App Check, provider sandboxes, reusable deployment authority, and signed-in testing remain **NOT AVAILABLE YET**. Officers do not sign in, enter records, run commands, edit the database, or deploy Auth. Use the no-terminal [staging Firestore review](./PUBLISH_AND_CHECK.md#review-the-empty-staging-firestore-boundary--available-after-669) and [Auth source review](./PUBLISH_AND_CHECK.md#review-the-staging-auth-source-contract--source-only-not-deployed). GitHub Pages currently still claims `runmprc.com`, so its normal address redirects to the Netlify-served name. The source stops adding that claim, but #136/WEB-001 must publish and verify the provider setting before officers call Pages an independent copy. diff --git a/docs/officers/SYSTEM_MAPS.md b/docs/officers/SYSTEM_MAPS.md index 3d42005..b4c679d 100644 --- a/docs/officers/SYSTEM_MAPS.md +++ b/docs/officers/SYSTEM_MAPS.md @@ -88,7 +88,10 @@ flowchart TD Main -. "Ordinary Git production build paused" .-> Netlify Main --> HostingSource["#663 Firebase Hosting source checks"] HostingSource --> StagingHosting["run-mprc-staging.web.app\nengineering staging only"] - StagingHosting -. "No backend/provider test estate, protected authority, marker, or rollback" .-> Stop + Main --> AuthSource["#671 email/password Auth source\nnot deployed"] + AuthSource --> FutureAuth["Future exact-main staging Auth-only deploy"] + FutureAuth -. "not run; no test user" .-> Stop + StagingHosting -. "No complete backend/provider test estate, protected deploy authority, marker, or rollback" .-> Stop HostingSource -. "Production branch, headers, authority, and cutover missing" .-> FutureHosting["Production Firebase Hosting — NOT AVAILABLE YET"] Main -. "Completed #659 exact release; manifest inactive" .-> WebGate{"Temporary authority active?"} WebGate -- "No" --> Stop @@ -98,7 +101,7 @@ flowchart TD Dev["dev — legacy branch"] -. "do not use for new release work" .-> PR ``` -In words: merge, release request, and protected approval are separate; #663 supplies the build checks and #665 publishes only the club-owned signed-out engineering staging site; that staging site cannot release production or authorize backend/provider testing. Ordinary merges cannot publish Netlify and the completed #659 exception is inactive; its deploy `6a7ece87c5ca4d0007c1a3fc` remains live while completed #623 deploy `6a7e072f8f346b0008510d29` is the immediate rollback and completed #473 deploy `6a6dc9ea588b0c0008036312` is older history; the future Pages branch must stop claiming the Netlify domain, and every host still needs separate proof. +In words: merge, release request, and protected approval are separate; #663 supplies the build checks, #665 publishes only the club-owned signed-out engineering staging site, and #671 first supplies only an email/password Auth source contract. Neither source nor the staging site initializes Auth, creates a user, releases production, or authorizes backend/provider testing. Ordinary merges cannot publish Netlify and the completed #659 exception is inactive; its deploy `6a7ece87c5ca4d0007c1a3fc` remains live while completed #623 deploy `6a7e072f8f346b0008510d29` is the immediate rollback and completed #473 deploy `6a6dc9ea588b0c0008036312` is older history; the future Pages branch must stop claiming the Netlify domain, and every host still needs separate proof. ## Account and permission ownership diff --git a/firebase.json b/firebase.json index 0dae235..53acbc0 100644 --- a/firebase.json +++ b/firebase.json @@ -16,6 +16,11 @@ "rules": "firestore.rules", "indexes": "firestore.indexes.json" }, + "auth": { + "providers": { + "emailPassword": true + } + }, "hosting": { "public": "build", "predeploy": [ diff --git a/package.json b/package.json index 4081568..6264f2e 100644 --- a/package.json +++ b/package.json @@ -31,12 +31,15 @@ "emulators": "firebase emulators:start --project demo-mprc-local --only auth,firestore,functions", "lint:ci": "node .github/scripts/check-frontend-lint.cjs", "lint:fix": "eslint --fix --ext .js,.jsx .", + "test:firebase-auth-staging": "node --test tests/firebase-auth-staging-contract.test.js", "test:firebase-hosting": "node --test tests/firebase-hosting-foundation.test.js", "test:staging-authority": "node --test tests/staging-authority-workflow.test.js", "test": "react-scripts test", "test:spa-navigation": "node --test tests/spa-navigation.test.js", "test:rules": "firebase emulators:exec --only firestore --project demo-rules-test \"npx --no-install jest --config tests/firestore-rules/jest.config.js\"", "test:rules:watch": "firebase emulators:exec --only firestore --project demo-rules-test \"npx --no-install jest --config tests/firestore-rules/jest.config.js --watch\"", + "predeploy:staging-auth": "node scripts/firebase-auth-staging-contract.js validate-deploy", + "deploy:staging-auth": "npx --no-install firebase deploy --only auth --project run-mprc-staging --non-interactive", "eject": "react-scripts eject", "lint:all": "find . -name '*.{js,jsx,tsx,ts}' -exec pretty-quick {} + && eslint --fix ." }, diff --git a/scripts/firebase-auth-staging-contract.js b/scripts/firebase-auth-staging-contract.js new file mode 100644 index 0000000..7febeb0 --- /dev/null +++ b/scripts/firebase-auth-staging-contract.js @@ -0,0 +1,120 @@ +'use strict'; + +const fs = require('node:fs'); +const path = require('node:path'); + +const REJECTION = 'firebase_auth_staging_contract_rejected'; +const DEFAULT_PROJECT_ID = 'demo-mprc-local'; +const STAGING_PROJECT_ID = 'run-mprc-staging'; +const DEPLOY_SCOPE = 'auth'; +const CLUB_ACCOUNT = 'runmprc@gmail.com'; + +function reject() { + throw new Error(REJECTION); +} + +function isPlainObject(value) { + return value !== null + && typeof value === 'object' + && !Array.isArray(value) + && Object.getPrototypeOf(value) === Object.prototype; +} + +function hasExactKeys(value, expected) { + return isPlainObject(value) + && JSON.stringify(Object.keys(value).sort()) + === JSON.stringify([...expected].sort()); +} + +function requiredValue(environment, key) { + const value = environment?.[key]; + if (typeof value !== 'string' + || value.length === 0 + || value.length > 8192 + || value.trim() !== value) { + reject(); + } + return value; +} + +function validateSourceContract(firebaseConfig, aliases) { + if (!isPlainObject(firebaseConfig) + || !hasExactKeys(firebaseConfig.auth, ['providers']) + || !hasExactKeys(firebaseConfig.auth.providers, ['emailPassword']) + || firebaseConfig.auth.providers.emailPassword !== true) { + reject(); + } + if (!hasExactKeys(aliases, ['projects']) + || !hasExactKeys(aliases.projects, ['default']) + || aliases.projects.default !== DEFAULT_PROJECT_ID) { + reject(); + } + + return Object.freeze({ + defaultProject: DEFAULT_PROJECT_ID, + provider: 'emailPassword', + }); +} + +function validateDeployEnvironment(environment = process.env) { + if (requiredValue(environment, 'GCLOUD_PROJECT') !== STAGING_PROJECT_ID + || requiredValue(environment, 'GOOGLE_CLOUD_QUOTA_PROJECT') + !== STAGING_PROJECT_ID + || requiredValue(environment, 'FIREBASE_DEPLOY_SCOPE') !== DEPLOY_SCOPE + || requiredValue(environment, 'RUN_MPRC_FIREBASE_ACCOUNT') !== CLUB_ACCOUNT + || requiredValue(environment, 'FIREBASE_TOKEN').length < 16 + || (environment.GOOGLE_APPLICATION_CREDENTIALS !== undefined + && environment.GOOGLE_APPLICATION_CREDENTIALS !== '')) { + reject(); + } + + return Object.freeze({ + account: CLUB_ACCOUNT, + projectId: STAGING_PROJECT_ID, + scope: DEPLOY_SCOPE, + }); +} + +function readJson(root, relativePath) { + try { + return JSON.parse(fs.readFileSync(path.join(root, relativePath), 'utf8')); + } catch { + return reject(); + } +} + +function validateRepository(root = path.resolve(__dirname, '..')) { + return validateSourceContract( + readJson(root, 'firebase.json'), + readJson(root, '.firebaserc'), + ); +} + +function runCli(args = process.argv.slice(2)) { + if (args.length === 1 && args[0] === 'validate') { + validateRepository(); + process.stdout.write('firebase_auth_staging_contract_valid\n'); + return; + } + if (args.length === 1 && args[0] === 'validate-deploy') { + validateRepository(); + validateDeployEnvironment(); + process.stdout.write('firebase_auth_staging_deploy_contract_valid\n'); + return; + } + reject(); +} + +if (require.main === module) { + try { + runCli(); + } catch { + process.stderr.write(`${REJECTION}\n`); + process.exitCode = 1; + } +} + +module.exports = { + validateDeployEnvironment, + validateSourceContract, +}; diff --git a/tests/firebase-auth-staging-contract.test.js b/tests/firebase-auth-staging-contract.test.js new file mode 100644 index 0000000..46e2818 --- /dev/null +++ b/tests/firebase-auth-staging-contract.test.js @@ -0,0 +1,151 @@ +'use strict'; + +const assert = require('node:assert/strict'); +const { spawnSync } = require('node:child_process'); +const fs = require('node:fs'); +const path = require('node:path'); +const test = require('node:test'); + +const ROOT = path.resolve(__dirname, '..'); +const CONTRACT_PATH = path.join( + ROOT, + 'scripts/firebase-auth-staging-contract.js', +); +const VALID_DEPLOY_ENVIRONMENT = Object.freeze({ + GCLOUD_PROJECT: 'run-mprc-staging', + GOOGLE_CLOUD_QUOTA_PROJECT: 'run-mprc-staging', + FIREBASE_DEPLOY_SCOPE: 'auth', + RUN_MPRC_FIREBASE_ACCOUNT: 'runmprc@gmail.com', + FIREBASE_TOKEN: 'synthetic-short-lived-credential', +}); + +function read(relativePath) { + return fs.readFileSync(path.join(ROOT, relativePath), 'utf8'); +} + +function clone(value) { + return JSON.parse(JSON.stringify(value)); +} + +function loadContract() { + // eslint-disable-next-line global-require, import/no-unresolved + return require('../scripts/firebase-auth-staging-contract'); +} + +test('CI-001D2 source enables only password-required email Auth', () => { + const { validateSourceContract } = loadContract(); + const firebaseConfig = JSON.parse(read('firebase.json')); + const aliases = JSON.parse(read('.firebaserc')); + + assert.deepEqual(firebaseConfig.auth, { + providers: { + emailPassword: true, + }, + }); + assert.deepEqual(validateSourceContract(firebaseConfig, aliases), { + defaultProject: 'demo-mprc-local', + provider: 'emailPassword', + }); +}); + +test('CI-001D2 rejects broader providers and deploy aliases', () => { + const { validateSourceContract } = loadContract(); + const firebaseConfig = JSON.parse(read('firebase.json')); + const aliases = JSON.parse(read('.firebaserc')); + const invalidConfigs = [ + { ...clone(firebaseConfig), auth: { providers: { emailPassword: false } } }, + { ...clone(firebaseConfig), auth: { providers: { emailPassword: true, anonymous: true } } }, + { ...clone(firebaseConfig), auth: { providers: { emailPassword: true, googleSignIn: {} } } }, + { ...clone(firebaseConfig), auth: { providers: { emailPassword: true, phone: {} } } }, + { ...clone(firebaseConfig), auth: { providers: { emailPassword: true }, extra: true } }, + ]; + + invalidConfigs.forEach((invalid) => { + assert.throws( + () => validateSourceContract(invalid, aliases), + /firebase_auth_staging_contract_rejected/u, + ); + }); + + [ + 'run-mprc-staging', + 'mid-peninsula-running-club', + 'unexpected-project', + ].forEach((defaultProject) => { + assert.throws( + () => validateSourceContract(firebaseConfig, { + projects: { default: defaultProject }, + }), + /firebase_auth_staging_contract_rejected/u, + ); + }); +}); + +test('CI-001D2 deploy context is exact, Auth-only, and token-bound', () => { + const { validateDeployEnvironment } = loadContract(); + + assert.deepEqual(validateDeployEnvironment(VALID_DEPLOY_ENVIRONMENT), { + account: 'runmprc@gmail.com', + projectId: 'run-mprc-staging', + scope: 'auth', + }); + + const invalidEnvironments = [ + { ...VALID_DEPLOY_ENVIRONMENT, GCLOUD_PROJECT: 'mid-peninsula-running-club' }, + { ...VALID_DEPLOY_ENVIRONMENT, GOOGLE_CLOUD_QUOTA_PROJECT: 'another-project' }, + { ...VALID_DEPLOY_ENVIRONMENT, FIREBASE_DEPLOY_SCOPE: 'auth,firestore' }, + { ...VALID_DEPLOY_ENVIRONMENT, RUN_MPRC_FIREBASE_ACCOUNT: 'another@example.invalid' }, + { ...VALID_DEPLOY_ENVIRONMENT, FIREBASE_TOKEN: '' }, + { ...VALID_DEPLOY_ENVIRONMENT, GOOGLE_APPLICATION_CREDENTIALS: '/tmp/credential.json' }, + ]; + + invalidEnvironments.forEach((invalid) => { + assert.throws( + () => validateDeployEnvironment(invalid), + /firebase_auth_staging_contract_rejected/u, + ); + }); +}); + +test('CI-001D2 pins the focused test and guarded deploy command', () => { + const packageJson = JSON.parse(read('package.json')); + const ci = read('.github/workflows/ci.yml'); + + assert.equal( + packageJson.scripts['test:firebase-auth-staging'], + 'node --test tests/firebase-auth-staging-contract.test.js', + ); + assert.equal( + packageJson.scripts['predeploy:staging-auth'], + 'node scripts/firebase-auth-staging-contract.js validate-deploy', + ); + assert.equal( + packageJson.scripts['deploy:staging-auth'], + 'npx --no-install firebase deploy --only auth --project run-mprc-staging --non-interactive', + ); + assert.match(ci, /tests\/firebase-auth-staging-contract\.test\.js/u); + assert.doesNotMatch(ci, /deploy:staging-auth|FIREBASE_TOKEN/u); +}); + +test('CI-001D2 CLI failures are fixed and do not echo supplied context', () => { + const canary = 'private-auth-context-canary'; + const result = spawnSync( + process.execPath, + [CONTRACT_PATH, 'validate-deploy'], + { + cwd: ROOT, + encoding: 'utf8', + env: { + ...process.env, + ...VALID_DEPLOY_ENVIRONMENT, + GCLOUD_PROJECT: canary, + FIREBASE_TOKEN: canary, + }, + }, + ); + + assert.equal(result.status, 1); + assert.equal(result.stdout, ''); + assert.equal(result.stderr, 'firebase_auth_staging_contract_rejected\n'); + assert.doesNotMatch(`${result.stdout}${result.stderr}`, new RegExp(canary, 'u')); +}); diff --git a/tests/test-artifact-safety.test.js b/tests/test-artifact-safety.test.js index a55e244..825c185 100644 --- a/tests/test-artifact-safety.test.js +++ b/tests/test-artifact-safety.test.js @@ -26,7 +26,7 @@ const temporaryDirectories = []; const JOB_ID = 'test-artifact-scrubber'; const JOB_NAME = 'Test artifact scrubber'; const TEST_COMMAND = 'node --test tests/test-artifact-safety.test.js'; -const FRONTEND_TEST_COMMAND = 'node --test tests/ci-workflow.test.js tests/release-workflow.test.js tests/firebase-release-verification.test.js tests/firebase-hosting-foundation.test.js tests/staging-authority-workflow.test.js tests/test-artifact-safety.test.js tests/root-dependency-security.test.js'; +const FRONTEND_TEST_COMMAND = 'node --test tests/ci-workflow.test.js tests/release-workflow.test.js tests/firebase-release-verification.test.js tests/firebase-hosting-foundation.test.js tests/firebase-auth-staging-contract.test.js tests/staging-authority-workflow.test.js tests/test-artifact-safety.test.js tests/root-dependency-security.test.js'; const NEVER_RUN = ['$', '{{ false }}'].join(''); const ALWAYS_TRUE = ['$', '{{ true }}'].join(''); const BRACKET_SECRET = ['$', "{{ secrets['SYNTHETIC_CANARY'] }}"].join(''); @@ -1317,6 +1317,13 @@ test('frontend independently runs the exact workflow safety suite without a skip assert.notEqual(missingHostingTest, ciWorkflow); assert.notDeepEqual(frontendValidationErrors(missingHostingTest), []); + const missingAuthTest = ciWorkflow.replace( + ' tests/firebase-auth-staging-contract.test.js', + '', + ); + assert.notEqual(missingAuthTest, ciWorkflow); + assert.notDeepEqual(frontendValidationErrors(missingAuthTest), []); + const swallowedValidation = ciWorkflow.replace( FRONTEND_TEST_COMMAND, `${FRONTEND_TEST_COMMAND} || true`,