- users module: profile + address CRUD behind use cases (users_profiles, users_addresses) - roles customer/admin on identity_users; role resolved from DB per request - shared auth contract (Authenticate, requireRole, requireOwnerOrAdmin) injected from composition root; users never imports identity - authorization runs before existence checks; address SQL scoped by user_id - @fastify/cookie registered once at app root (cross-module) - migrations 003_identity_roles + 004_users (reversible) - no new npm dependencies; tests: unit 52, integration 22 Gates: reviewer/security/qa APPROVED; verify.sh green
36 lines
2.3 KiB
JSON
36 lines
2.3 KiB
JSON
{
|
|
"feature_id": "F-006",
|
|
"agent": "reviewer",
|
|
"verdict": "APPROVED",
|
|
"reviewed_files": [
|
|
"project/migrations/003_identity_roles.js",
|
|
"project/migrations/004_users.js",
|
|
"project/src/shared/auth.ts",
|
|
"project/src/shared/tests/auth.test.ts",
|
|
"project/src/modules/identity/infrastructure/session-authenticator.ts",
|
|
"project/src/modules/identity/infrastructure/pg-user-repository.ts",
|
|
"project/src/modules/identity/api/identity.routes.ts",
|
|
"project/src/modules/identity/index.ts",
|
|
"project/src/modules/users/domain/*",
|
|
"project/src/modules/users/application/*",
|
|
"project/src/modules/users/infrastructure/pg-profile-repository.ts",
|
|
"project/src/modules/users/infrastructure/pg-address-repository.ts",
|
|
"project/src/modules/users/api/users.routes.ts",
|
|
"project/src/app/build-app.ts",
|
|
"project/src/app/tests/users.itest.ts",
|
|
"project/src/infrastructure/db/tests/migrations.itest.ts"
|
|
],
|
|
"checks": [
|
|
"Module boundaries: users never imports identity; auth arrives by injection through shared/auth.ts (boundary lint green, 52 files)",
|
|
"Authorization order: every users route runs authenticate -> param validation -> requireOwnerOrAdmin BEFORE any data access; non-owner always gets 403",
|
|
"SQL ownership: address operations are scoped by user_id in every query (list/create/update/delete), so a known foreign addressId cannot be touched",
|
|
"Dynamic UPDATE built only from a hardcoded column whitelist; all values parameterized",
|
|
"Migrations additive and reversible: 003 adds role with CHECK + default, 004 owns users_* tables with FKs and ON DELETE CASCADE; full rollback verified in itest",
|
|
"Acceptance criteria AC1-AC4 each exercised by a dedicated integration test; AC5 via verify.sh"
|
|
],
|
|
"non_blocking_observations": [
|
|
"GET /users lists rows from users_profiles, i.e. users who have touched their profile at least once; users without a profile row are not listed. Acceptable for F-006 (AC only requires admin-only access semantics); a future admin-listing ticket should decide between an identity-owned user listing port or renaming the endpoint."
|
|
],
|
|
"evidence": "npm test: 52 passed; npm run test:integration: 22 passed against PostgreSQL 16; lint, boundary check, typecheck, build clean; live smoke exercised all authorization paths"
|
|
}
|