Перейти к содержимому

11 -- Open Questions, Assumptions & Risks: Admin Service

This document consolidates all open questions, assumptions, and identified risks from the ten preceding architecture documents (01-10) for the Admin Service feature. It serves as the final synthesis artifact and the starting point for stakeholder review.


Each question follows the format: context, options with trade-offs, a recommended default, and the files affected.


Context. ADR-1 (01_overview.md) decided in favor of a separate apps/admin-api application for CRUD endpoints. However, this adds deployment overhead (new Dockerfile, CI pipeline, Swagger instance, environment config). If the admin CRUD surface remains small (4 project endpoints + 4 user endpoints), co-locating within auth-api under a /admin route prefix could be simpler.

Options.

#OptionProsCons
ASeparate admin-api app (current ADR-1)Clean separation of concerns; independently deployable; follows existing monorepo conventionsExtra infra surface; admin auth and admin CRUD live in different apps requiring shared guard/JWT libs
BModule within auth-apiSingle deployment; admin auth + CRUD co-located; fewer moving partsBloats auth-api beyond its authentication responsibility; harder to isolate admin traffic
CModule within core-apiCore already hosts Prisma; admin CRUD is essentially data managementConflates core infrastructure with admin business logic; core-api currently has no HTTP controllers

Default. Option A — maintain the current ADR-1 decision. The additional infra cost is justified by separation of concerns and the ability to scale or secure the admin surface independently.

Decision. Option A — separate apps/admin-api application confirmed.

Affected Files. architecture/01_overview.md, architecture/03_module_architecture.md, architecture/08_nx_structure.md, apps/admin-api/*


OQ-002: Should the ADMIN role UUID be hardcoded in migration or auto-generated?

Заголовок раздела «OQ-002: Should the ADMIN role UUID be hardcoded in migration or auto-generated?»

Context. The ADMIN role record must be seeded in the database. Existing CUSTOMER and EMPLOYEE roles already have fixed UUIDs in seed scripts. Using a hardcoded UUID ensures consistency across environments (dev, staging, prod), but introduces a magic constant.

Options.

#OptionProsCons
AHardcoded UUID in migration SQLDeterministic; identical across all environments; can be referenced in constants fileMagic value; must be tracked in source code
BAuto-generated via gen_random_uuid() in migrationNo magic constantsUUID differs per environment; code that references the role must query by type name, not ID
CSeed script (not migration) with hardcoded UUIDSeparates schema DDL from data seeding; consistent UUIDSeed scripts may not run in all environments automatically

Default. Option A — hardcoded UUID in migration, matching the existing pattern used for CUSTOMER and EMPLOYEE roles. Store the UUID as a constant in libs/apis/shared.

Decision. Option A — hardcoded UUID in migration confirmed.

Affected Files. architecture/07_database_schema.md, apps/core-api/prisma/migrations/*/, libs/apis/shared/src/lib/constants/


Context. The current specification includes a soft-delete endpoint that sets deletedAt on a project but defines no restore endpoint. Once soft-deleted, a project is invisible to all queries. Without a restore mechanism, the only recovery path is direct database manipulation.

Options.

#OptionProsCons
ANo restore endpoint (current spec)Smaller API surface; simpler to implement; avoids edge cases around re-activation of related resourcesNo programmatic recovery; operational risk if admin deletes by mistake
BAdd PATCH /admin/projects/:id/restoreSafety net for accidental deletion; low implementation effort (set deletedAt = null)Needs additional guard logic; must decide whether restored projects return to original status or go to a review state
CAdd restore but restrict to a time window (e.g., 30 days)Balances safety and data hygiene; auto-purge after windowAdds a scheduled job for permanent deletion; more complexity

Default. Option A for the initial release. Track restore as a follow-up feature. The risk is low given the small number of admins and the ability to recover via direct DB update if needed.

Decision. Option B — add PATCH /admin/projects/:id/restore endpoint. Restored projects return to their original status.

Affected Files. architecture/05_api_contracts.md, architecture/04_data_flow.md, libs/apis/providers/admin-api/features/admin-project/


OQ-004: Should the PrismaExceptionFilter be extended to handle P2025 explicitly?

Заголовок раздела «OQ-004: Should the PrismaExceptionFilter be extended to handle P2025 explicitly?»

Context. Prisma throws error code P2025 (“Record to update/delete not found”) when an update or delete targets a non-existent row. The existing PrismaExceptionFilter handles P2002 (unique constraint) but does not explicitly handle P2025. Without handling, the filter falls through to a generic 500 response.

Options.

#OptionProsCons
AExtend PrismaExceptionFilter to map P2025 to 404Consistent error responses; covers all services, not just adminMust verify no existing code relies on P2025 producing a 500; global change
BCatch P2025 in AdminProjectService / AdminUserService onlyScoped change; no risk to existing servicesDuplicates catch logic; other services may later hit the same gap
CUse findFirst + null check before every update/deleteExplicit control; avoids relying on Prisma exceptions for flow controlExtra DB round-trip per mutation; race condition between find and update

Default. Option A — extend the global PrismaExceptionFilter to handle P2025 as a 404 NotFoundException. This benefits all current and future services.

Decision. Option A — extend PrismaExceptionFilter globally to map P2025 → 404 confirmed.

Affected Files. architecture/10_error_handling.md, libs/apis/shared/src/lib/filters/prisma-exception.filter.ts


Context. The email sender module needs HTML templates (en/ru) for admin verification codes. No design mockup or brand guidelines for admin emails have been provided. Existing customer verification templates exist and can be adapted.

Options.

#OptionProsCons
AClone and minimally adapt customer templateFast; consistent brand feelMay send admin codes in a template that looks like a customer email, causing confusion
BCreate a distinct admin-branded templateClear visual distinction between admin and customer emailsRequires design input; delays implementation if design is not ready
CPlain-text email (no HTML template)Simplest; no design dependency; works everywhereLooks unprofessional; no branding

Default. Option A — clone the existing customer verification template, change the heading to indicate “Admin Verification”, and adjust the accent color if feasible. Replace with a proper design when one is provided.

Decision. Option B — create a distinct admin-branded template. Design input required before implementation.

Affected Files. architecture/04_data_flow.md, libs/apis/providers/email-sender-api/features/sender/src/lib/templates/


Context. The current design relies solely on a Redis-based cooldown (120s between code requests) to prevent abuse of the verification code endpoint. There is no general rate-limiting middleware (e.g., @nestjs/throttler) applied to admin endpoints. A brute-force attempt on the 6-digit code (1,000,000 combinations) is technically possible within the 900s TTL.

Options.

#OptionProsCons
ANo additional rate limiter (current design)Simpler stack; Redis cooldown covers the resend vectorVerification endpoint is vulnerable to brute-force attempts on the code value
BAdd @nestjs/throttler to admin-auth endpointsProtects against brute-force on code verification; configurable per-routeAdditional dependency; must configure Redis-backed ThrottlerStorage for multi-instance deployments
CAdd attempt counter in Redis (lock after N failures)Targeted protection; no new dependency; lock + alert on suspicious activityCustom implementation; must handle counter reset and lockout messaging

Default. Option C — add an attempt counter in Redis for the verify-code endpoint (e.g., max 5 attempts per code, lock for 15 minutes). This directly addresses the brute-force vector without introducing a new dependency.

Decision. Option A — no additional rate limiter beyond the existing Redis cooldown.

Affected Files. architecture/04_data_flow.md, architecture/09_metrics.md, libs/apis/providers/auth-api/features/admin-auth/src/lib/admin-auth.service.ts


OQ-007: Should the UniversalAccessTokenGuard be extended to accept admin tokens for /auth/me?

Заголовок раздела «OQ-007: Should the UniversalAccessTokenGuard be extended to accept admin tokens for /auth/me?»

Context. The existing /auth/me endpoint uses a universal guard that validates customer and employee tokens. Admin users currently have no equivalent “who am I” endpoint. The question is whether to extend the universal guard to also accept admin JWTs, or create a dedicated admin endpoint.

Options.

#OptionProsCons
AExtend UniversalAccessTokenGuard to include AdminAccessTokenStrategySingle /auth/me endpoint for all roles; DRYIncreases complexity of the universal guard; admin tokens validated in auth-api which already imports admin-auth
BCreate /admin/auth/me in auth-api with AdminAccessTokenGuardClean separation; admin-specific response shapeTwo “me” endpoints to maintain; admin client must know a different URL
CCreate /admin/me in admin-apiCo-locates with admin CRUD; admin-api is self-contained for admin UISplits auth concerns into admin-api; requires importing admin JWT validation into admin-api

Default. Option B — create a dedicated /admin/auth/me endpoint in auth-api using AdminAccessTokenGuard. This follows the existing pattern where all auth endpoints live in auth-api and avoids complicating the universal guard.

Decision. Options A + C — extend UniversalAccessTokenGuard to accept admin tokens (so /auth/me works for all roles), AND add a dedicated /admin/me endpoint in admin-api for admin-specific response shape.

Affected Files. architecture/05_api_contracts.md, architecture/03_module_architecture.md, libs/apis/providers/auth-api/features/admin-auth/, libs/apis/shared/src/lib/guards/


Заголовок раздела «OQ-008: Should admin token refresh use cookie-based or header-based refresh token?»

Context. Existing customer and employee auth uses HTTP-only cookies for refresh tokens. The admin service spec does not explicitly state the transport mechanism. Cookie-based is more secure against XSS but requires CORS configuration for the admin frontend domain.

Options.

#OptionProsCons
ACookie-based (match existing pattern)Consistent with customer/employee flows; HTTP-only cookie is XSS-resistantRequires CORS setup for admin frontend domain; cookies may complicate API-only testing
BHeader-based (Authorization: Bearer <refresh>)Simpler for API clients and testing; no CORS cookie issuesRefresh token stored in JS-accessible memory; higher XSS risk
CHybrid (cookie default, header fallback)Flexibility for both browser and API clientsMore complex validation logic; two code paths to maintain and secure

Default. Option A — use cookie-based refresh tokens to match the established pattern. CORS configuration for the admin frontend domain is a one-time setup cost.

Decision. Option A — cookie-based refresh tokens confirmed.

Affected Files. architecture/04_data_flow.md, architecture/05_api_contracts.md, libs/apis/providers/auth-api/features/admin-auth/src/lib/admin-auth.controller.ts


Context. Every project query will now include a WHERE deletedAt IS NULL filter. Without an index, this filter requires a full table scan on the deletedAt column. However, the vast majority of rows will have deletedAt = NULL, making a standard B-tree index inefficient. A partial index on deletedAt IS NOT NULL would be small but only helps queries that look for deleted projects.

Options.

#OptionProsCons
ANo index (current design)Simpler schema; deletedAt IS NULL filters benefit from existing indexes when combined with other conditionsFull scan on deletedAt alone; may degrade as table grows
BPartial index: WHERE deletedAt IS NOT NULLSmall index; fast lookup of soft-deleted projects for admin restore or auditDoes not help the common IS NULL query path
CPartial index: WHERE deletedAt IS NULLDirectly optimizes the hot path (all active project queries)Index covers nearly all rows initially; benefit grows as deletions accumulate

Default. Option A for now. The project table is unlikely to reach a size where this matters in the near term. Revisit when the table exceeds 100k rows or query latency increases. If an index is added later, Option C is preferred.

Decision. Option A — no index for now, revisit at scale confirmed.

Affected Files. architecture/07_database_schema.md, apps/core-api/prisma/schema.prisma, apps/core-api/prisma/migrations/*/


OQ-010: Should blocked user’s existing sessions/tokens be invalidated immediately on block?

Заголовок раздела «OQ-010: Should blocked user’s existing sessions/tokens be invalidated immediately on block?»

Context. When an admin blocks a user, the isBlocked flag is set to true. The isBlocked check occurs in each Passport strategy’s validate() method, meaning existing access tokens remain valid until they expire or are used. A blocked user with a valid access token can continue making requests until token expiration.

Options.

#OptionProsCons
ANo immediate invalidation (current design)Simplest; access tokens are short-lived (15-30 min); isBlocked check on refresh prevents new tokensWindow of continued access after block; potential security/compliance concern
BDelete all tokens/sessions from DB on blockImmediate revocation; refresh tokens stop working instantlyAccess tokens are stateless JWTs — they remain valid until expiry even if DB records are deleted; does not solve the access token window
CAdd blocked user IDs to a Redis blacklist checked by guardsTrue immediate revocation for both access and refresh tokensAdds latency to every authenticated request (Redis lookup); new infrastructure to maintain; must handle blacklist cleanup

Default. Option B — delete token/session records from the database upon block. This immediately invalidates refresh tokens (preventing new access tokens) while accepting the short-lived access token window. If the access token TTL is 15 minutes, the exposure window is acceptable for most use cases.

Decision. Option A — no immediate invalidation; access token expires naturally, refresh token check prevents new tokens.

Affected Files. architecture/04_data_flow.md, architecture/06_domain_model.md, libs/apis/providers/admin-api/features/admin-user/src/lib/admin-user.service.ts, libs/apis/providers/auth-api/features/token/


All assumptions gathered across architecture documents 01 through 10, with their source reference.

#AssumptionSource
A-01Admin registration is restricted to corporate email domains configured via the ADMIN_ALLOWED_DOMAINS environment variable. Domain validation occurs at the service layer, not at the database level.01_overview.md, 04_data_flow.md
A-02All admins are equal — there is no hierarchy, superadmin role, or permission levels within the ADMIN role. Any admin can manage any project or user.01_overview.md, 06_domain_model.md
A-03The existing Token and Session database models are reused for admin tokens. No admin-specific token table is created. Admin tokens are distinguished by the user’s role, not by a separate table.01_overview.md, 07_database_schema.md
A-04Making passwordHash and passwordSalt nullable on the User model will not break existing customer/employee authentication flows, because those flows already have non-null values for these fields.07_database_schema.md, 10_error_handling.md
A-05All existing project queries (customer-facing and employee-facing) will be updated to filter by deletedAt IS NULL. No query should return soft-deleted projects unless explicitly designed for admin audit purposes.01_overview.md, 07_database_schema.md
A-06Verification code TTL (900 seconds) and cooldown period (120 seconds) for admin auth match the existing customer verification values. No admin-specific tuning is required.04_data_flow.md, 09_metrics.md
A-07The admin-api application shares the same PostgreSQL database instance as auth-api, user-api, project-api, and core-api. No separate admin database is provisioned.02_system_context.md, 07_database_schema.md
A-08Admin CRUD operations are synchronous request-response. No message queues, event buses, or async processing are required for project/user management.01_overview.md, 03_module_architecture.md
A-09Email HTML templates for admin verification codes must be created for both en and ru locales, following the existing template structure in the email sender library.04_data_flow.md, 08_nx_structure.md
A-10The ADMIN role record must be seeded with a fixed UUID in the database migration to ensure consistency across environments and allow referencing by constant in application code.07_database_schema.md
A-11The existing PrismaExceptionFilter does not explicitly handle Prisma error code P2025 (“Record not found”). This may result in unhandled 500 errors for update/delete operations on non-existent records.10_error_handling.md
A-12No rate-limiting middleware (e.g., @nestjs/throttler) is applied beyond the Redis-based cooldown for verification code resend requests. Brute-force protection for code verification relies on code expiration and cooldown alone.04_data_flow.md, 09_metrics.md
A-13An admin cannot block another admin user. This constraint is enforced at the service layer (role check before executing block) rather than at the database level.06_domain_model.md, 10_error_handling.md
A-14Soft-deleted projects are invisible to both the admin project list and all customer/employee queries. The admin list endpoint filters by deletedAt IS NULL by default.05_api_contracts.md, 07_database_schema.md
A-15The admin frontend application (consumer of admin-api) is out of scope for this architecture. API contracts are designed for any HTTP client.02_system_context.md
A-16Admin JWT secrets (JWT_ADMIN_ACCESS_SECRET, JWT_ADMIN_REFRESH_SECRET) are separate from customer/employee secrets and must be configured independently in each deployment environment.01_overview.md, 03_module_architecture.md

Risk IDDescriptionProbabilityImpactMitigation
R-001Nullable password fields break existing auth. Making passwordHash/passwordSalt nullable could cause runtime errors in customer/employee login if code performs string operations without null checks.LowHighAudit all references to passwordHash/passwordSalt in CustomerAuthService and EmployeeAuthService. Add explicit null guard with a clear error message. Include regression tests for existing login flows.
R-002Missed deletedAt filter in project queries. Adding deletedAt IS NULL to all project queries is a manual process. Missing even one query could leak soft-deleted projects to end users.MediumHighUse a Prisma middleware or model-level default scope to auto-apply the filter. If middleware is not used, create a checklist of all ProjectService query methods and verify each one. Add an integration test that creates a soft-deleted project and asserts it is absent from every list/find method.
R-003SMTP failure blocks admin auth entirely. The passwordless flow depends on email delivery for every registration and login. If the SMTP server is unavailable, no admin can authenticate.MediumHighImplement retry logic (2-3 attempts with exponential backoff) in the email sender. Add a health check for SMTP connectivity. Consider a fallback channel (e.g., log code to stdout in development) for non-production environments. Monitor SMTP delivery rate via 09_metrics.md counters.
R-004No audit log for admin actions. Admin users can edit projects, soft-delete projects, and block users without any audit trail. This is explicitly out of scope but poses a compliance and forensics risk.HighMediumAccept the risk for the initial release. Track audit logging as a follow-up feature. In the interim, rely on database updatedAt timestamps and application logs (structured JSON with admin user ID) to provide a minimal trail.
R-005Redis failure blocks verification code flow. Verification codes are stored exclusively in Redis. If Redis is unavailable, code storage and retrieval fail, blocking admin auth completely.LowHighEnsure Redis is deployed with persistence (AOF or RDB) and, in production, with a replica for failover. Add a Redis health check endpoint. The existing customer auth flow has the same dependency, so this risk is not new but is now shared by admin auth.
R-006Corporate email domain list requires redeployment. ADMIN_ALLOWED_DOMAINS is read from .env at startup. Adding or removing an allowed domain requires changing the env var and restarting the service.MediumLowFor the initial release, accept the redeployment requirement. If domain changes become frequent, migrate the allowed-domain list to a database table with a cached lookup and an admin endpoint to manage it.
R-007Token/session table growth from admin usage. Admin tokens reuse the existing Token and Session tables. If admin sessions are long-lived or numerous, they contribute to table growth alongside customer/employee tokens. No cleanup mechanism distinguishes admin from non-admin tokens.LowLowThe existing token cleanup job (if any) handles all tokens uniformly. If no cleanup job exists, implement a scheduled task to purge expired token records. Monitor table size via database metrics.
R-008Verification code brute-force within TTL window. A 6-digit numeric code has 1,000,000 combinations. With a 900-second TTL and no per-attempt rate limit, an attacker could attempt approximately 900,000 requests at 1,000 req/s, giving a high probability of guessing the code.MediumHighImplement an attempt counter in Redis (see OQ-006, Option C). Lock the code after 5 failed attempts and require a new code request. Log and alert on lockout events. Consider increasing code length to 8 digits for admin if brute-force risk is deemed critical.
R-009Inconsistent admin-api and auth-api deployment versions. Since admin auth lives in auth-api and admin CRUD lives in admin-api, deploying them independently could lead to version mismatches (e.g., new guard logic in shared lib deployed to one but not the other).LowMediumPin shared library versions and deploy auth-api and admin-api together in the same release cycle. Add contract tests or health check endpoints that verify expected shared library versions.
R-010Soft delete without cascading to related entities. Setting deletedAt on a project does not cascade to related records (e.g., WalriderThread, project files). Orphaned related records may cause confusion or data integrity issues if the project is later hard-deleted.LowMediumFor the initial release, related records remain untouched (they are simply inaccessible because the parent project is filtered out). Document that a future hard-delete or archive process must handle cascading cleanup. Add a database constraint or application-level check before any future permanent deletion.

The architecture documents should be reviewed in the following order to build understanding incrementally, starting with foundational decisions and progressing to implementation details.

OrderDocumentRationale
101_overview.mdContains all ADRs (ADR-1 through ADR-5) that set the architectural foundation. Every subsequent document depends on these decisions. Review ADRs first to validate or challenge them before investing time in implementation details.
207_database_schema.mdSchema changes (ADMIN enum, isBlocked, deletedAt, nullable password fields) affect every layer of the stack. Catching a schema issue early avoids cascading rework across all other documents.
305_api_contracts.mdDefines the external API surface that the admin frontend will consume. API shape drives controller, DTO, and validation design. Stakeholders and frontend developers should validate this early.
403_module_architecture.mdDetails the NX library structure, module boundaries, and dependency graph. Validates that the implementation structure aligns with ADR-1 and the NX monorepo conventions.
504_data_flow.mdTraces end-to-end flows for registration, login, verification, CRUD, and block/unblock. Verify that all flows are complete and consistent with the API contracts and schema.
606_domain_model.mdDefines domain entities, value objects, and business rules (e.g., admin-cannot-block-admin). Review after data flows to confirm the domain model supports all described behaviors.
710_error_handling.mdMaps business errors to HTTP responses and exception classes. Review after domain model to ensure all error scenarios are covered and error codes are consistent.
808_nx_structure.mdNX workspace configuration, library generators, and build targets. Primarily relevant to developers setting up the feature.
902_system_context.mdSystem context diagram and external dependencies (PostgreSQL, Redis, SMTP). Useful for infrastructure and DevOps review but largely stable.
1009_metrics.mdObservability: counters, histograms, and alerts. Review last, as metric definitions depend on understanding all flows and error scenarios.
1111_open_questions.md (this document)Review after all other documents to confirm that open questions, assumptions, and risks are complete and accurately reflect the state of the architecture.

  1. Triage open questions. Schedule a review session to resolve OQ-001 through OQ-010. Prioritize OQ-006 (brute-force protection) and OQ-010 (session invalidation on block) as they have security implications.
  2. Validate assumptions. Confirm assumptions A-04 (nullable password safety) and A-05 (deletedAt filter coverage) with code-level audit before implementation begins.
  3. Accept or mitigate risks. Formally accept R-004 (no audit log) for the initial release with a follow-up ticket. Implement mitigations for R-001, R-002, and R-008 as part of the development work.
  4. Begin implementation following the review order above, starting with database migration (07) and shared library changes (guards, constants, strategies) before building feature libraries and apps.