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.
1. Open Questions
Заголовок раздела «1. Open Questions»Each question follows the format: context, options with trade-offs, a recommended default, and the files affected.
OQ-001: Should admin-api be a separate NX app or a module within auth-api?
Заголовок раздела «OQ-001: Should admin-api be a separate NX app or a module within auth-api?»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.
| # | Option | Pros | Cons |
|---|---|---|---|
| A | Separate admin-api app (current ADR-1) | Clean separation of concerns; independently deployable; follows existing monorepo conventions | Extra infra surface; admin auth and admin CRUD live in different apps requiring shared guard/JWT libs |
| B | Module within auth-api | Single deployment; admin auth + CRUD co-located; fewer moving parts | Bloats auth-api beyond its authentication responsibility; harder to isolate admin traffic |
| C | Module within core-api | Core already hosts Prisma; admin CRUD is essentially data management | Conflates 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.
| # | Option | Pros | Cons |
|---|---|---|---|
| A | Hardcoded UUID in migration SQL | Deterministic; identical across all environments; can be referenced in constants file | Magic value; must be tracked in source code |
| B | Auto-generated via gen_random_uuid() in migration | No magic constants | UUID differs per environment; code that references the role must query by type name, not ID |
| C | Seed script (not migration) with hardcoded UUID | Separates schema DDL from data seeding; consistent UUID | Seed 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/
OQ-003: Should soft-deleted projects be restorable by admin?
Заголовок раздела «OQ-003: Should soft-deleted projects be restorable by admin?»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.
| # | Option | Pros | Cons |
|---|---|---|---|
| A | No restore endpoint (current spec) | Smaller API surface; simpler to implement; avoids edge cases around re-activation of related resources | No programmatic recovery; operational risk if admin deletes by mistake |
| B | Add PATCH /admin/projects/:id/restore | Safety 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 |
| C | Add restore but restrict to a time window (e.g., 30 days) | Balances safety and data hygiene; auto-purge after window | Adds 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.
| # | Option | Pros | Cons |
|---|---|---|---|
| A | Extend PrismaExceptionFilter to map P2025 to 404 | Consistent error responses; covers all services, not just admin | Must verify no existing code relies on P2025 producing a 500; global change |
| B | Catch P2025 in AdminProjectService / AdminUserService only | Scoped change; no risk to existing services | Duplicates catch logic; other services may later hit the same gap |
| C | Use findFirst + null check before every update/delete | Explicit control; avoids relying on Prisma exceptions for flow control | Extra 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
OQ-005: How should admin email verification templates be styled?
Заголовок раздела «OQ-005: How should admin email verification templates be styled?»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.
| # | Option | Pros | Cons |
|---|---|---|---|
| A | Clone and minimally adapt customer template | Fast; consistent brand feel | May send admin codes in a template that looks like a customer email, causing confusion |
| B | Create a distinct admin-branded template | Clear visual distinction between admin and customer emails | Requires design input; delays implementation if design is not ready |
| C | Plain-text email (no HTML template) | Simplest; no design dependency; works everywhere | Looks 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/
OQ-006: Should there be a rate limiter middleware beyond Redis cooldown?
Заголовок раздела «OQ-006: Should there be a rate limiter middleware beyond Redis cooldown?»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.
| # | Option | Pros | Cons |
|---|---|---|---|
| A | No additional rate limiter (current design) | Simpler stack; Redis cooldown covers the resend vector | Verification endpoint is vulnerable to brute-force attempts on the code value |
| B | Add @nestjs/throttler to admin-auth endpoints | Protects against brute-force on code verification; configurable per-route | Additional dependency; must configure Redis-backed ThrottlerStorage for multi-instance deployments |
| C | Add attempt counter in Redis (lock after N failures) | Targeted protection; no new dependency; lock + alert on suspicious activity | Custom 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.
| # | Option | Pros | Cons |
|---|---|---|---|
| A | Extend UniversalAccessTokenGuard to include AdminAccessTokenStrategy | Single /auth/me endpoint for all roles; DRY | Increases complexity of the universal guard; admin tokens validated in auth-api which already imports admin-auth |
| B | Create /admin/auth/me in auth-api with AdminAccessTokenGuard | Clean separation; admin-specific response shape | Two “me” endpoints to maintain; admin client must know a different URL |
| C | Create /admin/me in admin-api | Co-locates with admin CRUD; admin-api is self-contained for admin UI | Splits 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?
Заголовок раздела «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.
| # | Option | Pros | Cons |
|---|---|---|---|
| A | Cookie-based (match existing pattern) | Consistent with customer/employee flows; HTTP-only cookie is XSS-resistant | Requires CORS setup for admin frontend domain; cookies may complicate API-only testing |
| B | Header-based (Authorization: Bearer <refresh>) | Simpler for API clients and testing; no CORS cookie issues | Refresh token stored in JS-accessible memory; higher XSS risk |
| C | Hybrid (cookie default, header fallback) | Flexibility for both browser and API clients | More 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
OQ-009: Should deletedAt have a database index?
Заголовок раздела «OQ-009: Should deletedAt have a database index?»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.
| # | Option | Pros | Cons |
|---|---|---|---|
| A | No index (current design) | Simpler schema; deletedAt IS NULL filters benefit from existing indexes when combined with other conditions | Full scan on deletedAt alone; may degrade as table grows |
| B | Partial index: WHERE deletedAt IS NOT NULL | Small index; fast lookup of soft-deleted projects for admin restore or audit | Does not help the common IS NULL query path |
| C | Partial index: WHERE deletedAt IS NULL | Directly 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.
| # | Option | Pros | Cons |
|---|---|---|---|
| A | No immediate invalidation (current design) | Simplest; access tokens are short-lived (15-30 min); isBlocked check on refresh prevents new tokens | Window of continued access after block; potential security/compliance concern |
| B | Delete all tokens/sessions from DB on block | Immediate revocation; refresh tokens stop working instantly | Access tokens are stateless JWTs — they remain valid until expiry even if DB records are deleted; does not solve the access token window |
| C | Add blocked user IDs to a Redis blacklist checked by guards | True immediate revocation for both access and refresh tokens | Adds 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/
2. Consolidated Assumptions
Заголовок раздела «2. Consolidated Assumptions»All assumptions gathered across architecture documents 01 through 10, with their source reference.
| # | Assumption | Source |
|---|---|---|
| A-01 | Admin 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-02 | All 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-03 | The 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-04 | Making 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-05 | All 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-06 | Verification 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-07 | The 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-08 | Admin 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-09 | Email 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-10 | The 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-11 | The 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-12 | No 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-13 | An 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-14 | Soft-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-15 | The 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-16 | Admin 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 |
3. Risks
Заголовок раздела «3. Risks»| Risk ID | Description | Probability | Impact | Mitigation |
|---|---|---|---|---|
| R-001 | Nullable 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. | Low | High | Audit 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-002 | Missed 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. | Medium | High | Use 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-003 | SMTP 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. | Medium | High | Implement 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-004 | No 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. | High | Medium | Accept 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-005 | Redis 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. | Low | High | Ensure 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-006 | Corporate 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. | Medium | Low | For 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-007 | Token/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. | Low | Low | The 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-008 | Verification 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. | Medium | High | Implement 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-009 | Inconsistent 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). | Low | Medium | Pin 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-010 | Soft 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. | Low | Medium | For 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. |
4. Suggested Review Order
Заголовок раздела «4. Suggested Review Order»The architecture documents should be reviewed in the following order to build understanding incrementally, starting with foundational decisions and progressing to implementation details.
| Order | Document | Rationale |
|---|---|---|
| 1 | 01_overview.md | Contains 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. |
| 2 | 07_database_schema.md | Schema 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. |
| 3 | 05_api_contracts.md | Defines 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. |
| 4 | 03_module_architecture.md | Details the NX library structure, module boundaries, and dependency graph. Validates that the implementation structure aligns with ADR-1 and the NX monorepo conventions. |
| 5 | 04_data_flow.md | Traces 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. |
| 6 | 06_domain_model.md | Defines 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. |
| 7 | 10_error_handling.md | Maps business errors to HTTP responses and exception classes. Review after domain model to ensure all error scenarios are covered and error codes are consistent. |
| 8 | 08_nx_structure.md | NX workspace configuration, library generators, and build targets. Primarily relevant to developers setting up the feature. |
| 9 | 02_system_context.md | System context diagram and external dependencies (PostgreSQL, Redis, SMTP). Useful for infrastructure and DevOps review but largely stable. |
| 10 | 09_metrics.md | Observability: counters, histograms, and alerts. Review last, as metric definitions depend on understanding all flows and error scenarios. |
| 11 | 11_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. |
5. Next Steps
Заголовок раздела «5. Next Steps»- 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.
- Validate assumptions. Confirm assumptions A-04 (nullable password safety) and A-05 (deletedAt filter coverage) with code-level audit before implementation begins.
- 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.
- 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.