Подход 2 (Эпик 3): Восстановление доступа пайщика — CoopID #127
Closed
claude
wants to merge 11 commits from
feat/coopid-epic-3 into feat/coopid-epic-1
pull from: feat/coopid-epic-3
merge into: C9S:feat/coopid-epic-1
C9S:main
C9S:dev
C9S:testnet
C9S:marketplace2
C9S:fix/598-51-membership-fee-and-type-cleanup
C9S:feat/capital-canon
C9S:feat/598-supplier-registry
C9S:feature/capital-cpp-private-data-templates
C9S:fix/soviet-install-validation
C9S:feat/ku-selforg
C9S:feat/membership-exit
C9S:feat/marketplace-admin-orders
C9S:fix/capital-share-onproject-event
C9S:ci/independent-env-deploy
C9S:fix/registration-pending-display
C9S:coopID
C9S:feat/expense-chassis-mvp
C9S:fix/zip-utf8-flag
C9S:feat/coopid-auth-ui
C9S:feat/coopid-7-signing-bridge
C9S:feat/coopid-12-recovery-account-from-confirm
C9S:feat/coopid-epic-9-metrics
C9S:feat/coopid-epic-6-access-rules-cleanup
C9S:feat/coopid-epic-12
C9S:feat/coopid-epic-11
C9S:feat/coopid-epic-7
C9S:feat/coopid-epic-9
C9S:feat/coopid-epic-6
C9S:feat/marketplace-process-registry
C9S:feat/marketplace-notifications-map
C9S:feat/marketplace-ku-economy
C9S:savanna
C9S:feat/apps-catalog-finish
C9S:feat/epic-13-billing-package-powerup
C9S:feat/coopid-epic-4
C9S:feat/blagorost-on-chassis
C9S:feat/coopid-epic-1
C9S:feat/marketplace-coop-stock
C9S:feat/marketplace-live-catalog
C9S:feat/coop-invest-blagorost
C9S:feat/ff-release-flow
C9S:fix/marketplace-offer-instance-not-template
C9S:fix/extension-config-lost-update
C9S:feature/C28-28-email-relay
C9S:feat/E16-onsite-sign-gate
C9S:feat/pwa-version-watch
C9S:fix/participants-registry-status-badges
C9S:feat/E16-coop-categories
C9S:fix/sanitize-blockchain-error-toast
C9S:fix/wallet-withdraw-no-phantom-payment
C9S:fix/agenda-program-offer-links
C9S:feat/notification-center-dc-v3
C9S:fix/chatcoop-livekit-room-name
C9S:feat/delete-inactive-participant
C9S:feat/registration-ledger2-suspense
C9S:feat/registration-refund
C9S:feat/registration-payment-dedup
C9S:perf/factory-parallel-fetch
C9S:feat/E16-sklad-rework
C9S:fix/decision-createproject-tsc
C9S:perf/factory-weasyprint-warm-pool
C9S:feat/E16-korzina-zakaz-agregat
C9S:fix/agenda-pending-new-question
C9S:feat/soviet-negative-consensus
C9S:feat/registration-payment-decline
C9S:feat/coop-standards-audit
C9S:parser2
C9S:feat/mkt-return-chairman-cosign
C9S:feat/registration-fsm
C9S:feat/pwa-reliable-update
C9S:parser2-block-time-and-version
C9S:fix/gorozhane-phantom-cleanup
C9S:fix/remove-bank-kpp
C9S:feat/E9-3b-sub-subscribe
C9S:feat/E9-9-approve-moderation
C9S:feat/E10-5-on-chain-watcher
C9S:feat/E10-4-install-pipeline
C9S:feat/E9-3b-ui-publish-package
C9S:feat/E1-tspp-docs-real-text
C9S:feat/E10-3b-dynamic-supergraph
C9S:apps-catalog
C9S:feat/E9-9-3-b-publish-mutation
C9S:feat/sig-v2-export
C9S:feat/E10-1-federation-subgraph
C9S:parser2-epic-4-forks
C9S:feat/decision-authorize-backend
C9S:feat/blagorost-program-expenses-ui
C9S:parser2-epic-6-canonical-storage
C9S:wip/pre-e16-desktop-docs
C9S:feat/E15-min-volume-per-ku
C9S:feat/blagorost-E2-E5-program-expenses
C9S:feat/epic-14-billing-notifications
C9S:blagorost
C9S:feat/E14-explicit-shipment
C9S:feat/marketplace-collective-supply
C9S:feat/blagorost-E1-contract
C9S:feat/C28-21-pg-signed-documents
C9S:provider-billing
C9S:fix/desktop-format-asset-errors
C9S:feat/marketplace-docs
C9S:feat/epic-12-billing-subscriptions
C9S:fix/min-soviet-members-3
C9S:parser2-epic-3-transport
C9S:feat/marketplace2-drop-blocked
C9S:worktree-design-wave1
C9S:feat/notif-mobile-canon
C9S:fix/vue-tsc-desktop
C9S:worktree-marketplace-docs-impl
C9S:feat/chatcoop-hide-matrix-room-id
C9S:feat/epic-8-coop-registry-provider
C9S:parser2-epic-2-idempotency
C9S:feat/chatcoop-secretary-rooms
C9S:feat/remove-l3-blocked
C9S:ci-trial-typecheck-2026-05-23
C9S:fix/controller-revert-transpileonly
C9S:chore/mongo-standalone-local
C9S:chore/ci-unify-release-workflows
C9S:feat/controller-swc-builder
C9S:feat/chatcoop-transcription-memo
C9S:fix/mono-base-ca-certificates
C9S:fix/wallet-withdraw-gateway-timing
C9S:feat/ledger2-burn-blocked-withdraw
C9S:fix/boot-tests-wallet-signagree-v2
C9S:chore/trustees-ownership
C9S:feat/capital-w-cap-gen-cooperative
C9S:chore/re-review-E5-apl-fail-fast
C9S:chore/re-review-E4-fail-fast-tx-hash
C9S:chore/re-review-E3-fix-withdraw-stub
C9S:chore/review-E3-vitrina-fixes
C9S:chore/review-E10-design-fixes
C9S:chore/review-E8-spisanie-fixes
C9S:chore/review-E7-vozvrat-fixes
C9S:chore/review-E6-vydacha-fixes
C9S:chore/review-E5-postavka-fixes
C9S:chore/review-E4-order-fixes
C9S:feat/epic0-onboarding-harness
C9S:chore/marketplace2-sync-dev-and-controller-di
C9S:feat/E11-stol-zakazov-frontend-and-tests
C9S:feat/E11-stol-zakazov-final
C9S:feat/capital-artifacts-access
C9S:fix/floor-asset-display
C9S:feat/epic-9-warehouse-reporting
C9S:feat/598-11-epic-8-writeoff
C9S:feat/E7-garantiynyy-vozvrat
C9S:design
C9S:fix/capital-revert-394-generation-convert-refs
C9S:fix/is-unioned-zod-string-parse
C9S:fix/capital-rename-generation-convert-to-money-invest
C9S:feat/E6-vydacha-payshchiku
C9S:worktree-remove-1081-1082-rename-1080
C9S:feat/init-by-server-from-provider
C9S:feat/E5-postavka-priemka
C9S:feat/E11-techdebt-L12-split-payout
C9S:feat/E4-zakaz
C9S:gh-pages
C9S:feat/E3-vitrina-canon
C9S:feat/E11-S1-ledger2-marketplace-ops
C9S:file-storage
C9S:feat/1-10-marketplace-registration-offer-status
C9S:feat/1-9-marketplace-board-acceptance
C9S:feat/1-8-marketplace-access-matrix
C9S:feat/1-7-marketplace-template-registry
C9S:feat/1-6-marketplace-role-guard
C9S:feat/1-5-marketplace-member-wallet
C9S:feat/1-4-marketplace-onboarding-gate
C9S:feat/1-3-marketplace-access-by-membership
C9S:feat/1-1-install-from-catalog
C9S:ci/lock-tags-only
C9S:onboarding
C9S:reports
C9S:ledger3
C9S:graph-standarts
C9S:feat/ledger-double-entry
C9S:feat/docs-enrichment
C9S:feat/documentation
C9S:feat/marketplace-orders
C9S:feat/interops-extension-system
C9S:cursor/development-environment-setup-2882
C9S:feat/unified-test-pipeline
C9S:fix/security-vulnerabilities
C9S:audio-stream
C9S:project-authorization-on-create
C9S:refactoring
C9S:capital
C9S:element
C9S:ledger
C9S:marketplace
C9S:notifications2
C9S:notifications
C9S:document2
C9S:TSK-900
C9S:TSK-878
C9S:dacom-dark-sun/sprint-4
C9S:uniconstructor/sprint-5
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Delete Branch "feat/coopid-epic-3"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Подход 2 «Восстановление доступа» — Эпик 3 (CoopID, компонент 42)
Stacked-PR поверх
feat/coopid-epic-1(Подход 1, зонтичный PR #123 → dev). После merge #123 в dev база этого PR будет переключена на dev.Источник AC: project 13
epics.md, Epic 3 (issue 105-6). Спеки реализации —_bmad-output/implementation-artifacts/spec-3-*.md.Истории Эпика 3
TooManyRecoveryAttempts)/coop/recovery/{confirm,cancel}: peek токена → проверка TOTP (TWO_FACTOR_VERIFIER) → атомарный consume (claim) → порт финализации → audit; пароль на финальном шаге и не хранится; cancel сжигает токен; кодInvalidRecoveryToken(400); rate-limit per-IP/per-token. Финализация (ротация ключа) вынесена заRECOVERY_FINALIZATION_PORT— сейм Story 3.3RECOVERY_FINALIZATION_PORT. ⚠️ открытый вопрос владельцу: источник admin-токена authentik для смены пароля (секреты сейчас vs провижн плейбуком)offline_recovery_code, keyed-hash HMAC, single-use → тот же recovery-токен → confirm 3.2; кодInvalidOfflineCode(400))recovery_strategy(email_magic_link по умолчанию | offline_code | council) на пайщика гейтит входные каналы:requestByEmail(3.1) тихо no-op'ит,requestByOfflineCode(3.4) бросаетInvalidOfflineCode, если их канал не выбран;councilотключает оба (восстановление только через approval-flow 6.9). GET/POST/coop/recovery/strategyпод JWT-guard; смена — step-up TOTP (вместо пароля: в authentik нет password-verify, паттерн 2FA-disable) + auditcoopid.recovery.strategy_changed; таблицаrecovery_strategy(V2.4.4); 19 unit-тестов зелёныеtwo_factorв coop_domain_db (секрет шифрован server-key), enroll/activate/disable под JWT-guard,TWO_FACTOR_VERIFIERдля recovery/входа, коды 401/400verify-timestamp1.7, его же отзывает logout 1.10);GET/DELETE /coop/sessionsпод JWT-guard; отзыв =deleteByIdстроки токена (реально инвалидирует refresh). Метаданные device/IP — Redis side-store заSESSION_METADATA_PORT(ключsha256(refresh), TTL=refresh lifetime, сам токен не хранится), запись best-effort в verify-timestamp. Дрейф AC↔код: authentikoauth_tokens//oauth/revoke→ наш refresh-токен ⇒ история НЕ заблокирована на authentik admin-токене (в отличие от 3.3/3.10/3.12). Отступления: geo по IP отложено (нет провайдера),lastSeenAt=createdAtдо шва refresh-touch. 27 unit-тестов зелёныеverify-timestamp, финализация входа) пишется auditcoopid.login.successful(subject_id, ip, user_agent, accept_language, признак нового устройства) + обновляется Redis-набор известных устройств пайщика (coopid:devices:<subjectId>, HASH, sliding-TTL). Fingerprint =sha256(user_agent + Accept-Language);isNewDevice— основа уведомления 3.9. Tracking best-effort (сбой не валит вход). Отступления: screen-resolution (нет в контракте входа) и geo-IP (нет провайдера) отложены. 17 unit-тестов зелёныеDeviceTrackingServiceприisNewDevice→ workflownew-device-login(email + in-app) в@coopenomics/notifications; bundling NFR10 = Redis-замокSET NX EX 12h(портNEW_DEVICE_NOTIFICATION_THROTTLE), занимается до notify (защита от гонки); получатель поsubjectId, email только подтверждённый, in-app поsubscriber_id; всё best-effort (сбой не валит вход). Отступления: ссылка «это не я» →/settings/security(выделенный one-click endpoint — Story 3.10), каналы email+in_app (push/Matrix отложены). 11 unit-тестов зелёныеSecurityIncidentService.report/reportByTokenпереиспользуетSessionsService.revokeAll(3.7) + auditcoopid.security.suspicious_login_reported. Два входа:POST /coop/security/not-me(JWT, из настроек) иnot-me/:token(one-click из письма, без auth — своя сессия скомпрометирована; авторизация single-use 256-бит токеномNOT_ME_TOKEN_STORE, TTL 7д, consume Lua GET→DEL). Письмо new-device (3.9) теперь несётnotMeUrl— закрыт отложенный one-click endpoint. Отложено в 3.3 (owner-blocked authentik-admin): force password change при следующем входе + ротация ключа COOPOS (AC сам относит rotation к 3.3; флаг без enforcement = мёртвое состояние). 15 unit-тестов зелёныеsecurity-event(email + in-app) в@coopenomics/notifications+SecurityEventNotificationService(best-effort, без троттла — каждое событие уведомляет); enumSecurityEventKind(5 событий) + карта заголовков. Подключены 3 live-события: 2FAenabled/disabled(Story 3.6),recovery.strategy_changed(Story 3.5).PasswordChanged/KeyRotatedв enum, проводка отложена до Story 3.3 (нет live-события ротации). Отступления как в 3.9 (каналы email+in_app, ссылка →/settings/security). 23 unit-теста зелёныеincrementEscalating(персистентный страйк → тир блока 1ч→4ч→12ч→24ч, NFR13; память страйков 24ч, после простоя сброс) +IEscalatingRateLimitStorage;EscalationPolicy/ESCALATING_LOCKOUT. Guard идёт через escalation-путь приrule.escalating, пишет best-effort auditcoopid.security.account_lockedна переходе в блок (трекер хэшируется sha256 — recovery-токен/email не утекают), одноразово на окно; defensive-откат наincrement. Пресет навешан на 4 recovery-эндпоинта (ip+account) — живой 24h-cooldown. Граница (owner-config authentik, без admin-токена): перебор пароля блокируется штатной brute-force-защитой authentik (наш этап-2 идёт ПОСЛЕ пароля — перебор не наблюдаем); NFR10 independent-channel для входа — authentik, для recovery email-magic-link уже штатный. 20 unit-тестов зелёныеРешения и поправки AC (дрейф AC↔код — прав код)
UserDomainService); таблицыparticipantsиз AC в brownfield-mono нет.RedisThrottlerStorage/guard (Story 9.1); recovery — отдельный кодTooManyRecoveryAttempts.node:crypto(RFC 6238), без npm-зависимости; секрет шифрован server-key (не ключ пайщика — инвариант vault цел).🤖 Generated with Claude Code
Схлопнут в единый PR #148 (coopID → dev). Ветка сохранена как резерв, ничего не потеряно.
Pull request closed