fix: single identity owner, hardened recovery/throttling, firestore parity, release gate - #38
Open
tnramalho wants to merge 6 commits into
Open
Conversation
userMetadata, repository, and resources are not mergeable defaults — they are a singular ownership claim over the one user space the whole adapter chain authenticates into. Move them from the per-integration contributes bag to AuthBootstrap.identity, and enforce a single identity owner per app at composition time (two owners, or ownership sliced across integrations, now throw naming the adapters involved). contributes keeps only the guard preferences (enableGlobalGuard, providesAppGuard); the guard-swap invariant is unchanged. defineRocketsAuth input API is untouched — only its returned bootstrap shape moves. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…emantics - firestoreValuesEqual: NaN matches NaN (server compiles == NaN to the IS_NAN unary filter) and +0 equals -0, instead of Object.is on both. - compareStrings compares by code points (UTF-8 byte order), matching server sort for astral characters. - getAll is chunked (300/batch), so id lists over 500 work again instead of throwing; the borrowed batch-write cap is removed. - create() rejects duplicate ids through one typed FirestoreDuplicateIdException in the backend contract (both backends), extending RepositoryQueryException so the permeator maps it to 409 instead of a generic 500. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Password/login recovery respond immediately and run notification work as a logged floating promise: uniform response timing removes the account- enumeration side channel, and a slow mailer no longer blocks the request. Swallowed recovery errors now log the error object with context. - Replace the app-wide APP_GUARD throttler with a guard scoped to the auth- owned public routes (signup, login, recovery, otp, invitation acceptance), so the library no longer rate-limits the host app's own routes. - Throttle on two dimensions at once: a coarse per-IP ceiling (1000/min, the prior default — account rotation cannot exceed it) plus fine per-(ip,account) limits, so an attacker only throttles themselves and cannot lock a victim out of login. throttling: false disables both. - Add a boot test asserting the upstream string DI tokens still resolve. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- resolveRocketsComposition folds an integration's identity.resources into the app resources: the same reference is skipped (idempotent), the same entity defined twice with different config throws naming both sides, instead of a later opaque 'registered twice' boot failure. - The server strips identity/contributes before handing bootstraps to core, and RocketsCoreModule now rejects a bootstrap that still carries them — core never silently drops integration-owned defaults. - Document the boundary on the core README auth-option row. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- verify-package-artifacts.mjs checks every main/types/exports/bin/files target against the npm pack --dry-run tarball file list (plus a source-path guard, as npm auto-includes main even from src/), so a manifest pointing at unpublished source fails the gate. - firebase-tools drops from root devDependencies; the emulator runs via pinned npx in one place, and locally SKIP_EMULATOR skips it (never in CI). - release-readiness workflow keeps only the publish-consumption gates that ci-pr-test does not already run. - Fix stale rule docs: editing-guidelines (deleted swagger.json) and build-test-lint (vitest .ts -> .mts). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…wnership * agent/rockets-auth-hardening: fix: match Firestore scalar range semantics feat: define server composition APIs (#34) Conflicts: packages/rockets-core/src/domain/interfaces/auth-bootstrap.interface.ts packages/rockets-server/src/rockets.module-definition.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes the open review findings from the four-PR stack (#34/#36/#37/#35) as one reviewable slice on top of
agent/rockets-auth-hardening. Every fix is the "best-for-the-case" option agreed during triage, not the cheapest patch. Three cold-review rounds (fresh agents, no context) ran against the diff; their findings are folded in.What changed, by area
Auth composition — single identity owner + enforced boundary
AuthBootstrapsplits intoidentity(the persistence the whole adapter chain authenticates into — at most one owner per app) andcontributes(guard preferences that may coexist). Two owners, or ownership sliced across integrations, now throw at composition naming the adapters.identity.resourcesare folded into the app'sresources[]: the same reference is skipped (idempotent), the same entity with a different config throws naming both sides — instead of the opaqueregistered twiceboot failure a migrating app used to hit.identity/contributesbefore handing bootstraps to core;RocketsCoreModulenow rejects a bootstrap that still carries them, so core never silently drops integration-owned defaults.enableGlobalGuard: falseis honored only with a declared replacement (providesAppGuard) or an explicit app opt-out — otherwise it fails loudly.Auth recovery + throttling
APP_GUARDthrottler is replaced by a guard scoped to the auth-owned public routes (signup, login, recovery, otp, invitation acceptance) — the library no longer rate-limits the host app's own routes.(ip, account)limits, so an attacker only ever throttles themselves and cannot lock a victim out of login.throttling: falsedisables both.Firestore repository parity
firestoreValuesEqual: NaN matches NaN (the server compiles== NaNto theIS_NANunary filter) and+0 === -0— verified against@google-cloud/firestore.compareStringscompares by code points (UTF-8 byte order), matching server sort for astral characters.getAllis chunked (300/batch): id lists over 500 work again instead of throwing.create()throws one typedFirestoreDuplicateIdExceptionfrom the backend contract (both backends), extendingRepositoryQueryExceptionso the permeator maps it to 409, not a generic 500.Release tooling
verify-package-artifacts.mjsvalidates everymain/types/exports/bin/filestarget against thenpm pack --dry-runtarball (plus a source-path guard, since npm auto-includesmaineven fromsrc/).firebase-toolsleaves root devDependencies (~398 packages); the emulator runs via pinnednpxin one place, andSKIP_EMULATORskips it locally (never in CI).release-readiness.ymlkeeps only the gatesci-pr-testdoes not already run.swagger.json,vitest .ts → .mts).Reviewer notes / decisions worth confirming
throttling: falseand the per-IP ceiling — a consumer who passes a customthrottlingarray without agetTrackeron their ownipentry loses the volume ceiling (their config choice). Only the built-in default is protected.release-readinesson push to main now runs only the gates, not unit/e2e (those ran at PR time viaci-pr-test).Verification
yarn build,yarn typecheck:spec,yarn test(642),yarn test:e2e(169),yarn lint— all green.password used too recently/ rotating victim) is the documented host memory-pressure pattern (CHANGELOG.md), confirmed by a clean 169/169 re-run — no assertions weakened.🤖 Generated with Claude Code