From 3ace2e06d2adbb1ce96175ec1cb43904f2fd0a0e Mon Sep 17 00:00:00 2001 From: Johannes Date: Wed, 8 Jul 2026 15:41:36 -0400 Subject: [PATCH] docs(guide): scrub decommissioned QDS_ADMIN role from KB content Follow-up to hiveops-incident#253 (QDS_ADMIN removed from all 5 backends' SecurityConfig/InstitutionContext, and from the hiveops-auth Role enum so no JWT can carry it). The guide KB still quoted QDS_ADMIN in role/allow-list descriptions, which no longer matched the code. Removed QDS_ADMIN from 26 references across 23 content files; surviving roles (USER, CUSTOMER, ADMIN, MSP_ADMIN, BCOS_ADMIN, AGENT, SYSTEM) left unchanged. No frontmatter touched -> zero module-coverage impact. Doc-accuracy only. Closes #10 Co-Authored-By: Claude Opus 4.8 --- .../src/main/resources/content/analytics/incidents/claude.md | 2 +- .../main/resources/content/analytics/incidents/internal.md | 2 +- .../src/main/resources/content/dashboard/cash-stats/claude.md | 2 +- .../main/resources/content/dashboard/cash-stats/internal.md | 4 ++-- .../src/main/resources/content/devices/config-audit/claude.md | 2 +- .../main/resources/content/devices/config-audit/internal.md | 2 +- .../resources/content/incident/incident-tracker/claude.md | 2 +- .../resources/content/incident/incident-tracker/internal.md | 2 +- .../resources/content/incident/journal-events/architect.md | 2 +- .../main/resources/content/incident/journal-events/claude.md | 2 +- .../resources/content/incident/journal-events/internal.md | 2 +- .../src/main/resources/content/msp/institution-keys/claude.md | 2 +- .../main/resources/content/msp/institution-keys/internal.md | 4 ++-- backend/src/main/resources/content/platform/auth/overview.md | 2 +- .../resources/content/recon/cash-reconciliation/claude.md | 2 +- .../resources/content/recon/cash-reconciliation/internal.md | 2 +- .../main/resources/content/recon/device-cycle-mgmt/claude.md | 2 +- .../resources/content/recon/device-cycle-mgmt/internal.md | 2 +- .../main/resources/content/technical/transactions/overview.md | 4 ++-- .../resources/content/transactions/customer-journey/claude.md | 2 +- .../content/transactions/customer-journey/internal.md | 2 +- .../content/transactions/journal-parsing/architect.md | 2 +- .../main/resources/content/transactions/reparse/internal.md | 2 +- 23 files changed, 26 insertions(+), 26 deletions(-) diff --git a/backend/src/main/resources/content/analytics/incidents/claude.md b/backend/src/main/resources/content/analytics/incidents/claude.md index b29aef4..629483e 100644 --- a/backend/src/main/resources/content/analytics/incidents/claude.md +++ b/backend/src/main/resources/content/analytics/incidents/claude.md @@ -38,7 +38,7 @@ audience: dev ## Roles / scoping - `CUSTOMER` / `MSP_ADMIN` → scoped to granted institution keys (downstream `InstitutionContext.resolveScope()`). -- `BCOS_ADMIN` / `ADMIN` / `QDS_ADMIN` / `USER` → unrestricted. +- `BCOS_ADMIN` / `ADMIN` / `USER` → unrestricted. ## Config - `INCIDENT_SERVICE_URL` default `http://hiveops-incident-backend:8080`. diff --git a/backend/src/main/resources/content/analytics/incidents/internal.md b/backend/src/main/resources/content/analytics/incidents/internal.md index 440b209..5984ec3 100644 --- a/backend/src/main/resources/content/analytics/incidents/internal.md +++ b/backend/src/main/resources/content/analytics/incidents/internal.md @@ -25,7 +25,7 @@ Browser → GET /api/analytics/incident-trends (hiveops-analytics) - The endpoint has **no `@PreAuthorize`** — any authenticated JWT can call it. There is no MSP_ADMIN/BCOS_ADMIN gate on Incident Trends. - **Data is institution-scoped downstream.** `hiveops-incident` calls `InstitutionContext.resolveScope()` on the forwarded token: - `ROLE_CUSTOMER` / `ROLE_MSP_ADMIN` → scoped to their granted institution keys (sees only their ATMs). - - `BCOS_ADMIN` / `ADMIN` / `QDS_ADMIN` / `USER` → unrestricted (all institutions). + - `BCOS_ADMIN` / `ADMIN` / `USER` → unrestricted (all institutions). - So a support user seeing "less data than expected" is usually a **scoped role**, not a bug. ## First things to check when it misbehaves diff --git a/backend/src/main/resources/content/dashboard/cash-stats/claude.md b/backend/src/main/resources/content/dashboard/cash-stats/claude.md index afcdd9a..a31dc4f 100644 --- a/backend/src/main/resources/content/dashboard/cash-stats/claude.md +++ b/backend/src/main/resources/content/dashboard/cash-stats/claude.md @@ -34,7 +34,7 @@ Terse agent reference for `dashboard.cash-stats`. Read view over cassette bill c - No topic is consumed/produced by the dashboard itself. ## Roles -- Read endpoints: `USER, CUSTOMER, ADMIN, MSP_ADMIN, QDS_ADMIN, BCOS_ADMIN` (incident `/api/**`). +- Read endpoints: `USER, CUSTOMER, ADMIN, MSP_ADMIN, BCOS_ADMIN` (incident `/api/**`). - Scope: `InstitutionContext.resolveScope()` → `CUSTOMER`/`MSP_ADMIN` scoped to JWT institution keys; others unrestricted. - Journal admin: `hasAnyRole('MSP_ADMIN','BCOS_ADMIN')`. diff --git a/backend/src/main/resources/content/dashboard/cash-stats/internal.md b/backend/src/main/resources/content/dashboard/cash-stats/internal.md index 3c7cc69..a252b63 100644 --- a/backend/src/main/resources/content/dashboard/cash-stats/internal.md +++ b/backend/src/main/resources/content/dashboard/cash-stats/internal.md @@ -20,8 +20,8 @@ The page makes exactly two calls on load and every 60 s: ## Roles required -- Both endpoints fall under incident's `/api/**` rule → **any authenticated app user**: `USER, CUSTOMER, ADMIN, MSP_ADMIN, QDS_ADMIN, BCOS_ADMIN`. (`/api/journal-events` POST also allows `AGENT`, but that's ingest, not this page.) -- **Institution scoping is automatic** (`InstitutionContext.resolveScope`): `CUSTOMER`/`MSP_ADMIN` see only their granted institution keys; `BCOS_ADMIN/ADMIN/QDS_ADMIN/USER` see the whole fleet. If a customer says "I only see some ATMs," that's correct scoping, not a bug. +- Both endpoints fall under incident's `/api/**` rule → **any authenticated app user**: `USER, CUSTOMER, ADMIN, MSP_ADMIN, BCOS_ADMIN`. (`/api/journal-events` POST also allows `AGENT`, but that's ingest, not this page.) +- **Institution scoping is automatic** (`InstitutionContext.resolveScope`): `CUSTOMER`/`MSP_ADMIN` see only their granted institution keys; `BCOS_ADMIN/ADMIN/USER` see the whole fleet. If a customer says "I only see some ATMs," that's correct scoping, not a bug. - **Admin backfill/sync is `MSP_ADMIN` or `BCOS_ADMIN` only** (journal `AdminController`). ## First things to check when it misbehaves diff --git a/backend/src/main/resources/content/devices/config-audit/claude.md b/backend/src/main/resources/content/devices/config-audit/claude.md index 5786cac..b63e207 100644 --- a/backend/src/main/resources/content/devices/config-audit/claude.md +++ b/backend/src/main/resources/content/devices/config-audit/claude.md @@ -31,7 +31,7 @@ audience: dev ## Roles / scoping (`InstitutionContext.resolveScope()`) -- `null` (unrestricted): `BCOS_ADMIN`, `ADMIN`, `QDS_ADMIN`, `USER` → `institutionKey` param honored (blank = all). +- `null` (unrestricted): `BCOS_ADMIN`, `ADMIN`, `USER` → `institutionKey` param honored (blank = all). - scoped list: `CUSTOMER`, `MSP_ADMIN` → forced to `scopedKeys.get(0)`; **param ignored**. ## Gotchas diff --git a/backend/src/main/resources/content/devices/config-audit/internal.md b/backend/src/main/resources/content/devices/config-audit/internal.md index 916e041..ea692a3 100644 --- a/backend/src/main/resources/content/devices/config-audit/internal.md +++ b/backend/src/main/resources/content/devices/config-audit/internal.md @@ -28,7 +28,7 @@ audience: internal Scope comes from `InstitutionContext.resolveScope()` (institution keys are carried in the JWT): -- **BCOS_ADMIN / ADMIN / QDS_ADMIN / USER** → unrestricted. The `institutionKey` query param is honored (blank = all institutions). +- **BCOS_ADMIN / ADMIN / USER** → unrestricted. The `institutionKey` query param is honored (blank = all institutions). - **CUSTOMER / MSP_ADMIN** → scoped. The backend **ignores the query param** and forces the log to the caller's **first** scoped institution key only. ## First things to check when it misbehaves diff --git a/backend/src/main/resources/content/incident/incident-tracker/claude.md b/backend/src/main/resources/content/incident/incident-tracker/claude.md index a6673a1..124ffb1 100644 --- a/backend/src/main/resources/content/incident/incident-tracker/claude.md +++ b/backend/src/main/resources/content/incident/incident-tracker/claude.md @@ -39,7 +39,7 @@ Constants in `config/DeviceEventKafkaConfig`: `TOPIC`, `CONSUMER_GROUP`. ## Roles - Viewing tracker: **any valid JWT**. Read endpoints have **no `@PreAuthorize`**; scoped by `InstitutionContext.resolveScope()`: - CUSTOMER / MSP_ADMIN → scoped to JWT-granted institution keys. - - BCOS_ADMIN / ADMIN / QDS_ADMIN / USER → `null` = all institutions. + - BCOS_ADMIN / ADMIN / USER → `null` = all institutions. - Mutations (create/assign/transition/bulk): `hasAnyRole('MSP_ADMIN','BCOS_ADMIN')`. ## Gotchas diff --git a/backend/src/main/resources/content/incident/incident-tracker/internal.md b/backend/src/main/resources/content/incident/incident-tracker/internal.md index 5ce87e7..49f314f 100644 --- a/backend/src/main/resources/content/incident/incident-tracker/internal.md +++ b/backend/src/main/resources/content/incident/incident-tracker/internal.md @@ -23,7 +23,7 @@ Rows are joined **in JS** on the ATM business key: `incident.atmName` ↔ `devic - **Viewing the tracker** needs only a **valid JWT** — the two read endpoints (`/api/device-summary`, `/api/incidents/paginated`) carry **no `@PreAuthorize`**. Access is scoped by `InstitutionContext.resolveScope()`: - `ROLE_CUSTOMER` / `ROLE_MSP_ADMIN` → **scoped** to their granted institution keys (list comes from the JWT credentials). - - `BCOS_ADMIN` / `ADMIN` / `QDS_ADMIN` / `USER` → `null` scope = **sees all institutions**. + - `BCOS_ADMIN` / `ADMIN` / `USER` → `null` scope = **sees all institutions**. - **Acting on an incident** (the "View Incidents" drill-down leads to create/assign/transition) requires `hasAnyRole('MSP_ADMIN','BCOS_ADMIN')` — enforced on `POST /api/incidents`, `POST /api/incident-management/{id}/assign`, `/transition`, `/bulk/update`. ## First things to check when it misbehaves diff --git a/backend/src/main/resources/content/incident/journal-events/architect.md b/backend/src/main/resources/content/incident/journal-events/architect.md index 91b6ab2..bea53d7 100644 --- a/backend/src/main/resources/content/incident/journal-events/architect.md +++ b/backend/src/main/resources/content/incident/journal-events/architect.md @@ -25,7 +25,7 @@ Agents never call incident directly — see [platform.service-ownership]. 2. `JournalEventController.getEventsPaginated` → `JournalEventService.getEventsPaginated` builds a JPA `Specification` (`JournalEventSpecification.withFilters`) filtering on `atm.id`, `eventType IN (...)`, and `eventTime >= now-hoursBack`. 3. `journalEventRepository.findAll(spec, pageable)` → mapped to `JournalEventDTO`, returned as a Spring `Page` (`{content, page:{totalElements,totalPages,number}}`). -Aggregate reads (`/activity`, `/fleet/cassette-inventory`) additionally resolve institution scope via `InstitutionContext.resolveScope()` (null = unrestricted for `USER`/`ADMIN`/`QDS_ADMIN`/`BCOS_ADMIN`; scoped list for `CUSTOMER`/`MSP_ADMIN`). The per-ATM read endpoints do **not** apply that scope (they filter by `atmId` only). +Aggregate reads (`/activity`, `/fleet/cassette-inventory`) additionally resolve institution scope via `InstitutionContext.resolveScope()` (null = unrestricted for `USER`/`ADMIN`/`BCOS_ADMIN`; scoped list for `CUSTOMER`/`MSP_ADMIN`). The per-ATM read endpoints do **not** apply that scope (they filter by `atmId` only). ## Data flow — write/ingest path diff --git a/backend/src/main/resources/content/incident/journal-events/claude.md b/backend/src/main/resources/content/incident/journal-events/claude.md index f7156cd..87e8506 100644 --- a/backend/src/main/resources/content/incident/journal-events/claude.md +++ b/backend/src/main/resources/content/incident/journal-events/claude.md @@ -32,7 +32,7 @@ Act-without-guessing reference. Everything below is confirmed in code. - Paginated query params: `page`, `size`, `sort=eventTime,desc`, `hoursBack`, `eventTypes` (repeated key, e.g. `eventTypes=CARD_READER_FAIL&eventTypes=...`). ## Roles -- Read `/api/journal-events/**`: `hasAnyRole('USER','CUSTOMER','ADMIN','MSP_ADMIN','QDS_ADMIN','BCOS_ADMIN')`. NO `@PreAuthorize`; NOT admin-only. +- Read `/api/journal-events/**`: `hasAnyRole('USER','CUSTOMER','ADMIN','MSP_ADMIN','BCOS_ADMIN')`. NO `@PreAuthorize`; NOT admin-only. - Ingest internal: `hasRole('INTERNAL')` (header `X-Internal-Secret` = env `INTERNAL_SERVICE_SECRET`). - Agent path `POST /api/journal-events` also allows `ROLE_AGENT`. diff --git a/backend/src/main/resources/content/incident/journal-events/internal.md b/backend/src/main/resources/content/incident/journal-events/internal.md index c5fba39..658d76e 100644 --- a/backend/src/main/resources/content/incident/journal-events/internal.md +++ b/backend/src/main/resources/content/incident/journal-events/internal.md @@ -10,7 +10,7 @@ Ops/support view of the **Event Journal** screen (frontend component `JournalEve ## Who can see it -- Read endpoints under `/api/journal-events/**` fall through to the `/api/**` rule in incident's `SecurityConfig` → `hasAnyRole('USER','CUSTOMER','ADMIN','MSP_ADMIN','QDS_ADMIN','BCOS_ADMIN')`. +- Read endpoints under `/api/journal-events/**` fall through to the `/api/**` rule in incident's `SecurityConfig` → `hasAnyRole('USER','CUSTOMER','ADMIN','MSP_ADMIN','BCOS_ADMIN')`. - **Not admin-gated.** There is no `@PreAuthorize` on `JournalEventController` — any authenticated browser (`USER`) or JWT user can read journal events. This is intentional (it is a diagnostic view), unlike `/api/incident-management/**` which is `MSP_ADMIN`/`BCOS_ADMIN` only. - `USER` = HiveIQ Browser session; `CUSTOMER`/`MSP_ADMIN` = customer JWT. diff --git a/backend/src/main/resources/content/msp/institution-keys/claude.md b/backend/src/main/resources/content/msp/institution-keys/claude.md index 320625a..7e69dfa 100644 --- a/backend/src/main/resources/content/msp/institution-keys/claude.md +++ b/backend/src/main/resources/content/msp/institution-keys/claude.md @@ -48,7 +48,7 @@ audience: dev | — | none | **institution-key CRUD emits NO Kafka event** | ## Roles -- Read (`getAll`/`accessible`/`config-*`): no `@PreAuthorize`; scoped by `InstitutionContext.resolveScope()`. `null` (BCOS_ADMIN/ADMIN/QDS_ADMIN/USER) = all; list (CUSTOMER/MSP_ADMIN) = JWT keys. +- Read (`getAll`/`accessible`/`config-*`): no `@PreAuthorize`; scoped by `InstitutionContext.resolveScope()`. `null` (BCOS_ADMIN/ADMIN/USER) = all; list (CUSTOMER/MSP_ADMIN) = JWT keys. - Write (create/update/delete/save-agent-props): `hasAnyRole('MSP_ADMIN','BCOS_ADMIN')` — plain `ADMIN` is **excluded**. ## Gotchas diff --git a/backend/src/main/resources/content/msp/institution-keys/internal.md b/backend/src/main/resources/content/msp/institution-keys/internal.md index 38ba344..f468f86 100644 --- a/backend/src/main/resources/content/msp/institution-keys/internal.md +++ b/backend/src/main/resources/content/msp/institution-keys/internal.md @@ -15,10 +15,10 @@ Owning service is **hiveops-devices** (NOT hiveops-msp — the MSP SPA has no ba ## Roles required - **Read** (list/search): any valid JWT. `GET` endpoints have **no `@PreAuthorize`**; results are scoped by `InstitutionContext.resolveScope()`. - - BCOS_ADMIN / ADMIN / QDS_ADMIN / USER → unrestricted (sees all). + - BCOS_ADMIN / ADMIN / USER → unrestricted (sees all). - CUSTOMER / MSP_ADMIN → only their JWT-granted keys. - **Create / Edit / Delete**: `hasAnyRole('MSP_ADMIN','BCOS_ADMIN')` only. - - Note: a plain `ADMIN` (or QDS_ADMIN/USER) can *read all* but **cannot mutate** — the write `@PreAuthorize` does not include `ADMIN`. If a "why can't I add a key?" ticket comes in, check the caller's role first. + - Note: a plain `ADMIN` (or USER) can *read all* but **cannot mutate** — the write `@PreAuthorize` does not include `ADMIN`. If a "why can't I add a key?" ticket comes in, check the caller's role first. ## First things to check when it misbehaves 1. **New device won't come online / not visible.** The device's `deviceType` must be in `supported_device_types` for its key, and the key must exist. Registration (agent-proxy → devices `POST /api/internal/atms/register`) returns **403 `{linked:false, error:...}`** when the device type isn't supported. Add the missing type on the Edit panel. diff --git a/backend/src/main/resources/content/platform/auth/overview.md b/backend/src/main/resources/content/platform/auth/overview.md index 7f4a677..cdf6211 100644 --- a/backend/src/main/resources/content/platform/auth/overview.md +++ b/backend/src/main/resources/content/platform/auth/overview.md @@ -22,7 +22,7 @@ audience: dev Each service's `JwtAuthenticationFilter` sets a single authority `ROLE_`. ## Roles -Admin roles are **`MSP_ADMIN`, `BCOS_ADMIN`** (and `QDS_ADMIN`). Most `@PreAuthorize` uses +Admin roles are **`MSP_ADMIN`, `BCOS_ADMIN`**. Most `@PreAuthorize` uses `hasAnyRole('MSP_ADMIN','BCOS_ADMIN')`. Audience gating (e.g. the guide's internal tabs) keys off these admin roles. diff --git a/backend/src/main/resources/content/recon/cash-reconciliation/claude.md b/backend/src/main/resources/content/recon/cash-reconciliation/claude.md index c24e61b..56c9993 100644 --- a/backend/src/main/resources/content/recon/cash-reconciliation/claude.md +++ b/backend/src/main/resources/content/recon/cash-reconciliation/claude.md @@ -44,7 +44,7 @@ Admin/write (not on grid load, `@PreAuthorize hasAnyRole('MSP_ADMIN','BCOS_ADMIN ## Scoping (JWT) -- `InstitutionContext.resolveScope()`: `null` for BCOS_ADMIN/ADMIN/QDS_ADMIN (all ATMs); institution-key list for MSP_ADMIN; single key for CUSTOMER. +- `InstitutionContext.resolveScope()`: `null` for BCOS_ADMIN/ADMIN (all ATMs); institution-key list for MSP_ADMIN; single key for CUSTOMER. - Admin (`null`) grid = recon positions **merged with all `device_summary` ids**. Scoped users = only ATMs that already have a recon position. - All endpoints need a valid JWT (`anyRequest().authenticated()`; only `/actuator/health*` public). diff --git a/backend/src/main/resources/content/recon/cash-reconciliation/internal.md b/backend/src/main/resources/content/recon/cash-reconciliation/internal.md index d25d4de..165a4f1 100644 --- a/backend/src/main/resources/content/recon/cash-reconciliation/internal.md +++ b/backend/src/main/resources/content/recon/cash-reconciliation/internal.md @@ -26,7 +26,7 @@ audience: internal | Role | Scope | What appears in the grid | |---|---|---| -| BCOS_ADMIN / ADMIN / QDS_ADMIN | `null` (unrestricted) | All recon ATMs **plus** every ATM in `device_summary` (merged in so the whole fleet shows even before first replenishment) | +| BCOS_ADMIN / ADMIN | `null` (unrestricted) | All recon ATMs **plus** every ATM in `device_summary` (merged in so the whole fleet shows even before first replenishment) | | MSP_ADMIN | list of granted institution keys | Only ATMs that already have a recon position in those institutions | | CUSTOMER (BANK) | single institution key | Only that institution's ATMs that already have a recon position | diff --git a/backend/src/main/resources/content/recon/device-cycle-mgmt/claude.md b/backend/src/main/resources/content/recon/device-cycle-mgmt/claude.md index 4cf02f8..67a514f 100644 --- a/backend/src/main/resources/content/recon/device-cycle-mgmt/claude.md +++ b/backend/src/main/resources/content/recon/device-cycle-mgmt/claude.md @@ -37,7 +37,7 @@ audience: dev ## Scope (from JWT, `InstitutionContext`) -- BCOS_ADMIN / ADMIN / QDS_ADMIN → scope `null` → reads/writes `institution IS NULL` rows; `all` = every `device_summary` row. +- BCOS_ADMIN / ADMIN → scope `null` → reads/writes `institution IS NULL` rows; `all` = every `device_summary` row. - CUSTOMER → single institution key. - MSP_ADMIN → list of granted institution keys. - GET `selected`: `null`→`findByInstitutionIsNull()`; scoped→`findByInstitutionIn(scope)` (union). diff --git a/backend/src/main/resources/content/recon/device-cycle-mgmt/internal.md b/backend/src/main/resources/content/recon/device-cycle-mgmt/internal.md index f5644ea..d8f38de 100644 --- a/backend/src/main/resources/content/recon/device-cycle-mgmt/internal.md +++ b/backend/src/main/resources/content/recon/device-cycle-mgmt/internal.md @@ -33,7 +33,7 @@ audience: internal Scope is resolved from the JWT by `InstitutionContext`: -- **BCOS_ADMIN / ADMIN / QDS_ADMIN** → *unrestricted* (scope = null). They read/write the **global** selection rows (`institution IS NULL`) and see **all** ATMs from `device_summary`. +- **BCOS_ADMIN / ADMIN** → *unrestricted* (scope = null). They read/write the **global** selection rows (`institution IS NULL`) and see **all** ATMs from `device_summary`. - **CUSTOMER (BANK)** → scoped to their single institution key. - **MSP_ADMIN** → scoped to their granted institution keys (a list). diff --git a/backend/src/main/resources/content/technical/transactions/overview.md b/backend/src/main/resources/content/technical/transactions/overview.md index 07c9b61..4156fae 100644 --- a/backend/src/main/resources/content/technical/transactions/overview.md +++ b/backend/src/main/resources/content/technical/transactions/overview.md @@ -22,7 +22,7 @@ No Kafka. No producers, consumers, or `@KafkaListener` in the codebase. ## API surface -Server port **8098** (`application.properties`). All routes require a valid JWT (`hasAnyRole(USER, CUSTOMER, ADMIN, MSP_ADMIN, QDS_ADMIN, BCOS_ADMIN)`) except `/actuator/health` and `/error`. +Server port **8098** (`application.properties`). All routes require a valid JWT (`hasAnyRole(USER, CUSTOMER, ADMIN, MSP_ADMIN, BCOS_ADMIN)`) except `/actuator/health` and `/error`. | Method | Path | Purpose | |--------|------|---------| @@ -42,7 +42,7 @@ The three proxy controllers accept any HTTP method (`@RequestMapping` without a ## Gotchas - **`services.ai.url` points at incident, not the AI service.** Default is `http://hiveiq-incident:8081`; incident owns the `/api/adoons/**` endpoints (per the comment in `AdoonsProxyController`). Don't assume Adoons traffic hits hiveops-ai directly. -- **Role check is broad.** `SecurityConfig` gates `/api/**` with `hasAnyRole("USER","CUSTOMER","ADMIN","MSP_ADMIN","QDS_ADMIN","BCOS_ADMIN")` — not the `MSP_ADMIN`/`BCOS_ADMIN`-only pattern used elsewhere. Authorization beyond role is delegated to the upstream services. +- **Role check is broad.** `SecurityConfig` gates `/api/**` with `hasAnyRole("USER","CUSTOMER","ADMIN","MSP_ADMIN","BCOS_ADMIN")` — not the `MSP_ADMIN`/`BCOS_ADMIN`-only pattern used elsewhere. Authorization beyond role is delegated to the upstream services. - **Only `Authorization` and `Content-Type` headers are forwarded.** `ProxyService` rebuilds a fresh `HttpHeaders` — other inbound headers (custom `X-*`, cookies) are dropped. The `transfer-encoding` response header is stripped from replies. - **CORS is disabled at the app** (`.cors(AbstractHttpConfigurer::disable)`); nginx is the CORS authority. Allowed origins config key (`cors.allowed-origins`, default `http://localhost:5178`) exists but is not wired into the disabled security chain. - **JWT secret is required** — `jwt.secret=${JWT_SECRET}` has no default; the service will fail to start without it. diff --git a/backend/src/main/resources/content/transactions/customer-journey/claude.md b/backend/src/main/resources/content/transactions/customer-journey/claude.md index 6ada3f6..6cd1afe 100644 --- a/backend/src/main/resources/content/transactions/customer-journey/claude.md +++ b/backend/src/main/resources/content/transactions/customer-journey/claude.md @@ -42,7 +42,7 @@ Request body for analyze: `{transactionId, rawLines, atmId, atmVendor}`. Save PA ## Roles -- Proxy `/api/**`: `hasAnyRole('USER','CUSTOMER','ADMIN','MSP_ADMIN','QDS_ADMIN','BCOS_ADMIN')`. +- Proxy `/api/**`: `hasAnyRole('USER','CUSTOMER','ADMIN','MSP_ADMIN','BCOS_ADMIN')`. - Journal endpoints: `@PreAuthorize("isAuthenticated()")`; institution isolation via `InstitutionContext.resolveScope()` in service layer. - incident→ai hop: `X-Internal-Secret`, not user JWT. diff --git a/backend/src/main/resources/content/transactions/customer-journey/internal.md b/backend/src/main/resources/content/transactions/customer-journey/internal.md index 3e88753..37b498a 100644 --- a/backend/src/main/resources/content/transactions/customer-journey/internal.md +++ b/backend/src/main/resources/content/transactions/customer-journey/internal.md @@ -10,7 +10,7 @@ Ops/support view of the Customer Journey drawer (the per-transaction replay in t ## Who can open it -- Any authenticated Transactions user. The proxy (`hiveops-transactions` `SecurityConfig`) gates `/api/**` with `hasAnyRole('USER','CUSTOMER','ADMIN','MSP_ADMIN','QDS_ADMIN','BCOS_ADMIN')` — **not** the `MSP_ADMIN`/`BCOS_ADMIN`-only pattern used elsewhere. +- Any authenticated Transactions user. The proxy (`hiveops-transactions` `SecurityConfig`) gates `/api/**` with `hasAnyRole('USER','CUSTOMER','ADMIN','MSP_ADMIN','BCOS_ADMIN')` — **not** the `MSP_ADMIN`/`BCOS_ADMIN`-only pattern used elsewhere. - Journal endpoints behind it require only `isAuthenticated()`. - **Institution scoping is enforced downstream** in journal: cheque lookups (`ChequeImageService.listByTxnId`) and the Adoons-save PATCH filter by the caller's institution scope. A user only sees journeys/cheques for their own institution(s). diff --git a/backend/src/main/resources/content/transactions/journal-parsing/architect.md b/backend/src/main/resources/content/transactions/journal-parsing/architect.md index 42bd7d3..80f9dd0 100644 --- a/backend/src/main/resources/content/transactions/journal-parsing/architect.md +++ b/backend/src/main/resources/content/transactions/journal-parsing/architect.md @@ -66,4 +66,4 @@ Transactions has **no database**; devices/ATM identity lives elsewhere. See [pla - Admin endpoints: `@PreAuthorize("hasAnyRole('MSP_ADMIN','BCOS_ADMIN')")`. - `GET /api/journal/files`: `@PreAuthorize("isAuthenticated()")`, scoped by `InstitutionContext.resolveScope()` in the default branch. -- The transactions proxy gates `/api/**` broadly (`USER,CUSTOMER,ADMIN,MSP_ADMIN,QDS_ADMIN,BCOS_ADMIN`); the real authorization is journal's `@PreAuthorize`. See [platform.service-ownership]. +- The transactions proxy gates `/api/**` broadly (`USER,CUSTOMER,ADMIN,MSP_ADMIN,BCOS_ADMIN`); the real authorization is journal's `@PreAuthorize`. See [platform.service-ownership]. diff --git a/backend/src/main/resources/content/transactions/reparse/internal.md b/backend/src/main/resources/content/transactions/reparse/internal.md index 5b5732e..5e8d604 100644 --- a/backend/src/main/resources/content/transactions/reparse/internal.md +++ b/backend/src/main/resources/content/transactions/reparse/internal.md @@ -15,7 +15,7 @@ Ops / support / admin view of **Journal Reparse** (Transactions SPA → Settings | View parse status / file list | Refresh, tab switch, ATM filter | `GET /api/journal/files` | `isAuthenticated()` — any logged-in user | | Trigger bulk reparse | **Reparse All** button | `POST /api/journal/admin/reparse-all` | **`hasAnyRole('MSP_ADMIN','BCOS_ADMIN')`** | -So a plain user can *see* status but only **MSP_ADMIN / BCOS_ADMIN** can trigger a reparse. If a non-admin clicks Reparse All they get a 403 from the journal service (surfaced as the red error alert). The transactions proxy in front is deliberately loose (`hasAnyRole(USER,CUSTOMER,ADMIN,MSP_ADMIN,QDS_ADMIN,BCOS_ADMIN)` on `/api/**`); the real authorization is enforced downstream by journal's `AdminController`. +So a plain user can *see* status but only **MSP_ADMIN / BCOS_ADMIN** can trigger a reparse. If a non-admin clicks Reparse All they get a 403 from the journal service (surfaced as the red error alert). The transactions proxy in front is deliberately loose (`hasAnyRole(USER,CUSTOMER,ADMIN,MSP_ADMIN,BCOS_ADMIN)` on `/api/**`); the real authorization is enforced downstream by journal's `AdminController`. ## What "Reparse All" actually does