feat: add multi-BU tenancy with per-user team scoping (Option B)

- Add bu_teams column to users table (migration + fresh schema)
- Create shared KNOWN_TEAMS constant and validateTeams helper
- Expose user teams in auth middleware, login, and /me responses
- Add bu_teams CRUD to user management routes with audit logging
- Make Ivanti FINDINGS_FILTERS configurable via IVANTI_BU_FILTER env var
- Add query-time team filtering to GET /findings and /findings/counts
- Update AuthContext with teams helpers and admin scope toggle
- Create AdminScopeToggle component (My Teams / All BUs)
- Scope ReportingPage findings fetch by user teams
- Scope CompliancePage team selector by user teams
- Scope ExportsPage findings exports by user teams
- Add BU teams multi-select to UserManagement create/edit forms
- Display team badges in user list table
This commit is contained in:
Jordan Ramos
2026-05-05 11:04:53 -06:00
parent af951fdc12
commit 2656df94d3
24 changed files with 999 additions and 127 deletions

View File

@@ -19,9 +19,9 @@ All API calls are made from a single Node.js backend process. The integration us
| Inter-request delay — writes | 2 seconds minimum between PUT/POST/DELETE requests |
| Explicit field lists | Every GET includes `?fields=` parameter; `/rest/api/2/field` is blocked |
| No bulk updates | Issues are updated one at a time; `/rest/api/2/issue/bulk` is blocked |
| Bulk reads via JQL | Multi-ticket sync uses a single `GET /rest/api/2/search` with JQL query parameters, not per-issue GETs |
| Bulk reads via JQL | Multi-ticket sync uses a single `GET /rest/api/2/search` with predefined key-based JQL query parameters, not per-issue GETs; no arbitrary JQL passthrough |
| Single-issue fetch via JQL | `GET /rest/api/2/search?jql=key="KEY" AND project=<KEY>&fields=...&maxResults=1` |
| JQL scoping | All recurring JQL queries include `updated >= -Xh` clause and `project = <KEY>` scoping |
| JQL scoping | All recurring JQL queries use predefined scoped patterns with `updated >= -72h` clause and `project = <KEY>` scoping; no arbitrary JQL passthrough |
| `maxResults` cap | Search queries capped at 1 000 results per page |
---
@@ -96,7 +96,7 @@ All API calls are made from a single Node.js backend process. The integration us
| **Frequency** | Manual, estimated 510 per day |
| **Purpose** | Move ticket through workflow states (e.g., Open to In Progress to Closed) |
### 8. JQL Search (Bulk Sync)
### 8. Scoped Bulk Sync via JQL
| | |
|---|---|
@@ -104,10 +104,10 @@ All API calls are made from a single Node.js backend process. The integration us
| **Trigger** | Admin clicks "Sync All" on the Jira tickets panel |
| **Frequency** | Manual, estimated 13 times per day |
| **Purpose** | Bulk-refresh all tracked tickets in a single request instead of per-issue GETs |
| **JQL pattern** | `key in ("KEY-1", "KEY-2", ...) AND updated >= -24h AND project = <KEY>` |
| **JQL pattern** | `key in ("KEY-1", "KEY-2", ...) AND updated >= -72h AND project = <KEY>` |
| **Fields requested** | `summary, status, assignee, created, updated, priority, issuetype, project, resolution` |
| **Batch size** | 100 keys per JQL query; multiple batches if needed |
| **Notes** | Uses GET with URL-encoded query parameters per Charter compliance. Stops early if rate limit budget is running low (burst remaining <= 5 or daily remaining <= 10) |
| **Notes** | Uses GET with URL-encoded query parameters per Charter compliance. JQL is predefined and scoped — constructed from known tracked issue keys, a fixed 72-hour window, and the configured project key. No arbitrary JQL is accepted from the frontend. Stops early if rate limit budget is running low (burst remaining <= 5 or daily remaining <= 10) |
### 9. Issue Lookup
@@ -131,7 +131,7 @@ All API calls are made from a single Node.js backend process. The integration us
| Add comment | 515 | POST | 2s |
| Get transitions | 510 | GET | 1s |
| Transition issue | 510 | POST | 2s |
| JQL search (sync) | 15 | GET | 1s |
| Scoped bulk sync | 15 | GET | 1s |
| Issue lookup | 515 | GET | 1s |
| **Total estimated** | **43120** | | |
@@ -145,6 +145,7 @@ The integration explicitly blocks these endpoints to comply with Charter policy:
- `/rest/api/2/field` — field metadata is never queried; fields are specified in code
- `/rest/api/2/issue/bulk` — bulk updates are not used; issues are updated individually
- `POST /rest/api/2/search` — arbitrary JQL search via POST is not used; all searches use `GET /rest/api/2/search` with URL-encoded query parameters and predefined scoped JQL patterns
---