@tideorg/mcp 1.4.2 → 1.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/CHANGELOG.md +88 -0
  2. package/GAP_REGISTER.md +18 -8
  3. package/PRIVACY.md +41 -0
  4. package/README.md +207 -197
  5. package/adapters/AGENTS.md +3 -2
  6. package/adapters/CLAUDE.md +1 -1
  7. package/canon/anti-patterns.md +55 -62
  8. package/canon/concepts.md +46 -34
  9. package/canon/custom-contracts.md +12 -8
  10. package/canon/feature-mapping.md +27 -75
  11. package/canon/framework-matrix.md +69 -22
  12. package/canon/hosting-options.md +111 -0
  13. package/canon/iga-change-requests-api.md +211 -0
  14. package/canon/invariants.md +33 -35
  15. package/canon/security-gap-mapping.md +506 -0
  16. package/canon/security-runtime-probes.md +177 -0
  17. package/canon/tidecloak-bootstrap.md +21 -16
  18. package/canon/tidecloak-endpoints.md +60 -98
  19. package/canon/troubleshooting.md +115 -53
  20. package/canon/version-policy.md +8 -12
  21. package/mcp-server/dist/server.js +221 -19
  22. package/package.json +48 -45
  23. package/playbooks/add-auth-nextjs-existing.md +33 -17
  24. package/playbooks/add-auth-nextjs-fresh.md +1 -1
  25. package/playbooks/add-rbac-nextjs.md +7 -2
  26. package/playbooks/bootstrap-realm-from-template.md +33 -18
  27. package/playbooks/deploy-tidecloak-docker.md +31 -28
  28. package/playbooks/diagnose-missing-roles-or-claims.md +2 -2
  29. package/playbooks/initialize-admin-and-link-account.md +22 -19
  30. package/playbooks/protect-api-nextjs.md +40 -1
  31. package/playbooks/protect-aspnet-core-asgard.md +313 -0
  32. package/playbooks/protect-routes-nextjs.md +24 -10
  33. package/playbooks/provision-tidecloak-skycloak.md +213 -0
  34. package/playbooks/setup-forseti-e2ee.md +32 -50
  35. package/playbooks/setup-iga-admin-panel.md +113 -171
  36. package/playbooks/verify-jwt-server-side.md +20 -1
  37. package/prompts/build-private-customer-portal.md +1 -1
  38. package/prompts/security-gap-analysis.md +55 -0
  39. package/reference-apps/encrypted-communication/anti-patterns.md +3 -3
  40. package/reference-apps/encrypted-communication/bootstrap-sequence.md +2 -2
  41. package/reference-apps/encrypted-communication/scenario.md +2 -2
  42. package/reference-apps/git-pr-signing-service/bootstrap-sequence.md +8 -8
  43. package/reference-apps/iga-admin-governance/anti-patterns.md +18 -16
  44. package/reference-apps/iga-admin-governance/bootstrap-sequence.md +10 -5
  45. package/reference-apps/iga-admin-governance/role-policy-matrix.md +12 -13
  46. package/reference-apps/iga-admin-governance/scenario.md +15 -13
  47. package/reference-apps/organisation-password-manager/bootstrap-sequence.md +5 -6
  48. package/reference-apps/policy-governed-signing/bootstrap-sequence.md +9 -9
  49. package/skills/tide-diagnostics/SKILL.md +1 -1
  50. package/skills/tide-integration/SKILL.md +9 -18
  51. package/skills/tide-mcp-qa/SKILL.md +139 -0
  52. package/skills/tide-rbac-and-e2ee/SKILL.md +5 -5
  53. package/skills/tide-reviewer/SKILL.md +1 -1
  54. package/skills/tide-security-analyst/SKILL.md +182 -0
  55. package/skills/tide-setup/SKILL.md +1 -1
  56. package/canon/delegation.md +0 -195
  57. package/playbooks/setup-server-delegation.md +0 -142
@@ -12,6 +12,13 @@ Build an admin UI for managing IGA change requests with quorum governance.
12
12
 
13
13
  **Do not use** if you only need end-user authentication. See [add-auth-nextjs-fresh.md](add-auth-nextjs-fresh.md) instead.
14
14
 
15
+ **A custom panel is optional.** The stack already ships two working admin consoles that manage IGA change requests out of the box:
16
+
17
+ - **tide-console SPA** — a built Vite/React admin UI bundled inside the `tidecloak-key-provider` jar and served by TideCloak.
18
+ - **Legacy keycloak.v2 console** — the stock Keycloak admin UI, which on the shipped build carries a native `tide-change-requests` section.
19
+
20
+ Build a custom panel only when you need to embed CR governance inside your own product UI. Otherwise use one of the shipped consoles.
21
+
15
22
  ---
16
23
 
17
24
  ## Prerequisites
@@ -34,190 +41,136 @@ All endpoints below are relative to:
34
41
 
35
42
  Example: `http://localhost:8080/admin/realms/myrealm`
36
43
 
44
+ The consolidated IGA REST surface is rooted at `/admin/realms/{realm}/iga` (server class `IgaAdminResource`). Change requests live under `/iga/change-requests`.
45
+
46
+ > **Legacy bootstrap-compat surface.** The canonical bootstrap script (`localtest/init/init_tidecloak.sh`) and the `tidecloak-js` `AdminAPI` still call an older `/tide-admin/change-set/{type}/requests`, `/tide-admin/change-set/sign`, and `/tide-admin/change-set/commit` surface. That path still exists for backward compatibility and is fine for scripted bootstrap approval, but the `/iga/*` surface documented below is the current API for a live admin panel. Do not mix the two.
47
+
37
48
  ---
38
49
 
39
50
  ## Change Request Lifecycle
40
51
 
52
+ Current `/iga/change-requests/...` surface (replaces legacy `/tide-admin/change-set/...`, confirmed by Tide 2026-07-07). **Full spec: `canon/iga-change-requests-api.md`.**
53
+
41
54
  ```
42
- 1. Admin creates change (role assignment, group change, etc.)
43
- -> Draft created automatically (DRAFT/PENDING)
44
- 2. Admin reviews: GET /tide-admin/change-set/all/requests
45
- 3. Admin approves: POST /tide-admin/change-set/sign/batch
46
- -> If Tide multi-admin: returns signing challenge for enclave
47
- -> Submit signed result: POST /tideAdminResources/add-review
48
- 4. Once enough approvals: POST /tide-admin/change-set/commit/batch
49
- 5. Or cancel: POST /tide-admin/change-set/cancel/batch
55
+ 1. Admin performs a governed change (role assignment, group change, etc.)
56
+ -> The write returns 202 Accepted; a change request is created (PENDING)
57
+ 2. Admin reviews: GET /iga/change-requests?status=PENDING
58
+ 3. Admin approves: POST /iga/change-requests/{id}/authorize (body {} optional)
59
+ -> Tide MultiAdmin mode: sign via the enclave two-phase exchange
60
+ GET/POST /iga/change-requests/{id}/approval-model
61
+ -> FirstAdmin / Tideless mode: authorize signs server-side, no popup
62
+ 4. Once threshold met: POST /iga/change-requests/{id}/commit (412 if under threshold)
63
+ 5. Or reject: POST /iga/change-requests/{id}/deny
50
64
  ```
51
65
 
66
+ `authorize` (simple lane: firstAdmin/Tideless) records an approval and **never commits** — it refuses tide-multiAdmin CRs. `approve` is the multiAdmin enclave lane: it approves AND auto-commits once quorum is met (this is the deployed console "Authorize" button). `commit` is the explicit apply-only step for a CR that already has enough approvals (412 if sub-quorum). Use `bulk-authorize` to act on many at once.
67
+
52
68
  ---
53
69
 
54
70
  ## Endpoints
55
71
 
56
- ### Counts (Lightweight)
72
+ ### List / Get
57
73
 
58
74
  ```
59
- GET /tide-admin/change-set/counts
75
+ GET /iga/change-requests?status=PENDING # PENDING|APPROVED|DENIED|CANCELLED
76
+ GET /iga/change-requests/{id} # single CR (keyed by `id`)
60
77
  ```
61
78
 
62
- Use this for dashboard badges and summary views. Avoids fetching full request payloads.
79
+ Each CR carries `id`, `entityType`, `actionType`, `status`, `readyToCommit`, `threshold`, `authorizers[]`, `dependsOn`/`blocked`. Derive dashboard counts from the list (there is no separate counts endpoint on the new surface; no per-type request endpoints and no `all/requests` path either).
63
80
 
64
- ### List Requests
81
+ ### Authorize (Sign)
65
82
 
66
83
  ```
67
- GET /tide-admin/change-set/all/requests # All change requests (single call)
68
- GET /tide-admin/change-set/users/requests # User changes only
69
- GET /tide-admin/change-set/roles/requests # Role changes only
70
- GET /tide-admin/change-set/clients/requests # Client changes only
71
- GET /tide-admin/change-set/groups/requests # Group changes only
84
+ POST /iga/change-requests/{id}/authorize # body {} optional → 200/403/409 (409 = four-eyes)
85
+ POST /iga/change-requests/bulk-authorize # {"actionTypeIn":["CREATE","DELETE"],"limit":100} 429 if a bulk run is active
72
86
  ```
73
87
 
74
- ### Sign / Approve
88
+ ### Commit / Deny
75
89
 
76
90
  ```
77
- POST /tide-admin/change-set/sign/batch # Approve (sign) changes
91
+ POST /iga/change-requests/{id}/commit # 200 (APPROVED) / 412 (under threshold or unmet dependency)
92
+ POST /iga/change-requests/{id}/deny # → 204
78
93
  ```
79
94
 
80
- ### Commit / Cancel
95
+ ### Enclave Review (Tide MultiAdmin)
81
96
 
82
97
  ```
83
- POST /tide-admin/change-set/commit/batch # Commit approved changes
84
- POST /tide-admin/change-set/cancel/batch # Cancel pending changes
98
+ GET /iga/change-requests/{id}/approval-model # { changeRequestId, requestModel(base64) }
99
+ POST /iga/change-requests/{id}/approval-model # { "requestModel": "<base64 doken>" } → { recorded, authCount, threshold }
85
100
  ```
86
101
 
87
- ### Enclave Review and Rejection
102
+ The two-phase `approval-model` exchange replaces the legacy `tideAdminResources/add-review`. In FirstAdmin / Tideless mode, `authorize` signs server-side and no enclave step is needed.
103
+
104
+ ### Deprecated: add-review / add-rejection
88
105
 
89
106
  ```
90
- POST /tideAdminResources/add-review # Submit Tide enclave signed review (multipart/form-data)
91
- POST /tideAdminResources/add-rejection # Reject a change (multipart/form-data)
107
+ POST /tideAdminResources/add-review # 410 Gone (deprecated)
108
+ POST /tideAdminResources/add-rejection # 410 Gone (deprecated)
92
109
  ```
93
110
 
94
- **Content-type warning**: Both `add-review` and `add-rejection` use `multipart/form-data`, NOT JSON. All other mutation endpoints (`sign/batch`, `commit/batch`, `cancel/batch`) use `application/json`. If you send JSON to `add-rejection`, the server silently ignores the payload.
111
+ Both endpoints were removed as part of the IGA decoupling and now return **`410 Gone`**. Approval goes through `/iga/change-requests/{id}/authorize` (plus the `approval-model` enclave exchange in Tide MultiAdmin mode) and rejection through `/iga/change-requests/{id}/deny`.
95
112
 
96
- ```typescript
97
- // Rejection example — must use FormData, not JSON
98
- const formData = new FormData();
99
- formData.append("changeSetId", draftRecordId);
100
- formData.append("actionType", actionType);
101
- formData.append("changeSetType", changeSetType);
102
-
103
- await fetch(`${TC_URL}/admin/realms/${realm}/tideAdminResources/add-rejection`, {
104
- method: 'POST',
105
- headers: { 'Authorization': `Bearer ${adminToken}` },
106
- body: formData // NOT JSON.stringify
107
- });
108
- ```
109
-
110
- ### Activity and Comments
113
+ ### Comments
111
114
 
112
115
  ```
113
- GET /tide-admin/change-set/{id}/activity # Activity log + comments
114
- POST /tide-admin/change-set/{id}/comments # Add comment to change request
116
+ GET/POST /iga/change-requests/{id}/comments # { "comment": "..." } (≤2000 chars)
117
+ PUT/DELETE /iga/change-requests/{id}/comments/{commentId}
115
118
  ```
116
119
 
117
- ---
118
-
119
- ## Approve (Sign) Request -- Code Example
120
-
121
- ```typescript
122
- const response = await fetch(`${TC_URL}/admin/realms/${realm}/tide-admin/change-set/sign/batch`, {
123
- method: 'POST',
124
- headers: {
125
- 'Authorization': `Bearer ${adminToken}`,
126
- 'Content-Type': 'application/json'
127
- },
128
- body: JSON.stringify({
129
- changeSets: [{
130
- changeSetId: "draft-record-id",
131
- changeSetType: "USER_ROLE",
132
- actionType: "CREATE",
133
- policyRoleId: "optional-role-uuid", // omit to use tide-realm-admin policy
134
- dynamicData: ["optional-base64"] // custom Forseti contract input
135
- }]
136
- })
137
- });
138
-
139
- // If Tide multi-admin mode:
140
- // response[0].requiresApprovalPopup === true
141
- // -> decode changeSetDraftRequests (base64)
142
- // -> present to Tide enclave for admin signing
143
- // -> submit via POST /tideAdminResources/add-review
144
- ```
120
+ There is no per-request `/activity` endpoint on the current surface; comments are the audit thread.
145
121
 
146
122
  ---
147
123
 
148
- ## TypeScript Client (KcAdminClient) Usage
124
+ ## TypeScript Client Usage
125
+
126
+ There is no `@keycloak/keycloak-admin-client` `KcAdminClient.tideUsersExt` extension on `tidecloak-js` main. The change-request surface is a hand-rolled fetch client, `AdminAPI` (`packages/tidecloak-js/src/AdminAPI.js`), exposed through `@tidecloak/react`. It targets the legacy bootstrap-compat `/tide-admin/change-set/*` endpoints:
149
127
 
150
128
  ```typescript
151
- import KcAdminClient from "@keycloak/keycloak-admin-client";
129
+ import { AdminAPI } from "@tidecloak/react";
152
130
 
153
- const client = new KcAdminClient({ baseUrl: "http://localhost:8080" });
154
- await client.auth({
155
- username: "admin",
156
- password: "admin",
157
- grantType: "password",
158
- clientId: "admin-cli"
159
- });
131
+ const api = new AdminAPI({ baseUrl, realm, token });
160
132
 
161
- // Counts
162
- const counts = await client.tideUsersExt.getChangeSetCounts();
133
+ // List (combines user + role change requests)
134
+ const pending = await api.getPendingChangeSets();
135
+ const userCrs = await api.getUserChangeRequests();
136
+ const roleCrs = await api.getRoleChangeRequests();
163
137
 
164
- // All requests
165
- const all = await client.tideUsersExt.getAllChangeSetRequests();
138
+ // Approve a single change set (sent as FormData, single object — not { changeSets: [...] })
139
+ await api.approveChangeSet({ changeSetId, actionType, changeSetType });
166
140
 
167
- // Approve
168
- await client.tideUsersExt.approveDraftChangeSet({
169
- changeSets: [{ changeSetId: "id", changeSetType: "USER_ROLE", actionType: "CREATE" }]
170
- });
141
+ // Multi-admin: approve with an enclave signature
142
+ await api.approveChangeSetWithSignature(/* signed payload */);
171
143
 
172
144
  // Commit
173
- await client.tideUsersExt.commitDraftChangeSet({
174
- changeSets: [{ changeSetId: "id", changeSetType: "USER_ROLE", actionType: "CREATE" }]
175
- });
176
-
177
- // Activity and comments
178
- const activity = await client.tideUsersExt.getChangeSetActivity({ id: "draft-id" });
179
- await client.tideUsersExt.addChangeSetComment({ id: "draft-id", comment: "LGTM" });
145
+ await api.commitChangeSet({ changeSetId, actionType, changeSetType });
180
146
  ```
181
147
 
148
+ There are no `getChangeSetCounts`, `getAllChangeSetRequests`, `getChangeSetActivity`, or `addChangeSetComment` methods. For a panel built on the current `/iga/*` surface, call the `/iga/change-requests/*` endpoints (see the **Endpoints** section above and `canon/iga-change-requests-api.md`) directly with `fetch` rather than via this SDK.
149
+
182
150
  ---
183
151
 
184
152
  ## Policy Management Endpoints
185
153
 
186
154
  ```
187
- PUT /tide-admin/forseti-contracts # Create/update Forseti contract
188
- GET /tide-admin/forseti-contracts # List contracts
189
- POST /tide-admin/policy-templates # Create policy template
190
- GET /tide-admin/policy-templates # List templates
191
- PUT /tide-admin/ssh-policies # Attach policy to role
192
- GET /tide-admin/ssh-policies # List role policies
193
- POST /tide-iga-provider/role-policy/{roleId}/init-cert # Store signed policy on role (JSON: initCert required, initCertSig optional; no doken field)
194
- GET /tide-admin/role-policies # Roles with policy status
155
+ GET /iga/forseti-contracts # List contracts
156
+ GET /iga/forseti-contracts/{id} # Get one contract
157
+ POST /iga/forseti-contracts # Create a Forseti contract (POST, not PUT)
158
+ DELETE /iga/forseti-contracts/{id} # Delete a contract
159
+ GET /iga/role-policies # Roles with policy status
195
160
  ```
196
161
 
162
+ Create is `POST /iga/forseti-contracts` (there is no PUT). Policy-template, SSH-policy, and `role-policy/{roleId}/init-cert` endpoints are not part of the current surface.
163
+
197
164
  ---
198
165
 
199
- ## ChangeSetType Values
200
-
201
- **VERIFIED** (vendor confirmation, GAP-041 resolved). Full enum (14 values):
202
-
203
- | Value | Description | Draft Status |
204
- |-------|-------------|-------------|
205
- | `DEFAULT_ROLES` | Default role assignment | ACTIVE (auto) |
206
- | `USER` | User creation | DRAFT |
207
- | `USER_ROLE` | User-to-role assignment | DRAFT |
208
- | `USER_GROUP_MEMBERSHIP` | User-to-group membership | DRAFT |
209
- | `ROLE` | Role definition change | ACTIVE (auto, creation) / DRAFT (deletion if users assigned) |
210
- | `COMPOSITE_ROLE` | Composite role change | DRAFT |
211
- | `CLIENT` | Client configuration change | DRAFT (when users exist) |
212
- | `CLIENT_DEFAULT_USER_CONTEXT` | Client scope mappings | DRAFT |
213
- | `CLIENT_FULLSCOPE` | Client full-scope toggle | DRAFT |
214
- | `GROUP` | Group definition change | — |
215
- | `GROUP_ROLE` | Group-to-role assignment | DRAFT |
216
- | `GROUP_MOVE` | Group hierarchy change | DRAFT |
217
- | `REALM_LICENSING` | Realm license change | DRAFT |
218
- | `POLICY` | Policy change (derived from USER_ROLE) | DRAFT |
219
-
220
- **ACTIVE vs DRAFT**: Role creation and default-role assignment get ACTIVE status (recorded for audit but non-blocking — no sign/commit needed). All other actions get DRAFT status requiring quorum sign/commit. Master realm and IGA-disabled realms are always exempt.
166
+ ## Change Request Taxonomy
167
+
168
+ The current surface does not use a `ChangeSetType` enum. Each change request carries two free-form String columns on `IgaChangeRequestEntity`:
169
+
170
+ - **`entityType`** one of: `USER`, `REALM`, `ROLE`, `GROUP`, `CLIENT`, `CLIENT_SCOPE`, `ORGANIZATION`, `COMPOSITE_ROLE`, `CLIENT_SCOPE_CLIENT`, `CLIENT_SCOPE_ROLE`, `PROTOCOL_MAPPER`, `REALM_DEFAULT_SCOPE`, `SCOPE_MAPPING`.
171
+ - **`actionType`** — a free-form verb such as `CREATE`, `SET`, `UPDATE`, `ASSIGN`, or an `ADOPT_*` variant.
172
+
173
+ Read both values back from the `GET /iga/change-requests` response; do not hardcode a fixed enum. Some change requests (e.g. role creation and default-role adoption) commit automatically as part of the login-closure converge and never need an explicit authorize/commit; the rest require quorum authorize/commit. The master realm and IGA-disabled realms are exempt from governance entirely.
221
174
 
222
175
  ## Status Values
223
176
 
@@ -235,13 +188,13 @@ GET /tide-admin/role-policies # Roles with policy status
235
188
 
236
189
  A working IGA admin panel must include:
237
190
 
238
- 1. **Pending list** -- Display all change requests in `DRAFT` or `PENDING` status. Use `GET /tide-admin/change-set/all/requests` and filter by status.
191
+ 1. **Pending list** -- Display change requests via `GET /iga/change-requests?status=PENDING` (keyed by `id`).
239
192
 
240
- 2. **Approve button** -- Triggers `POST /tide-admin/change-set/sign/batch` with selected change set IDs.
193
+ 2. **Approve button** -- Triggers `POST /iga/change-requests/{id}/authorize` per selected CR (or `POST /iga/change-requests/bulk-authorize` for a batch).
241
194
 
242
- 3. **Enclave popup** -- When `requiresApprovalPopup === true` in the sign response, decode `changeSetDraftRequests` (base64) and present the Tide enclave iframe for the admin to complete threshold signing. Submit the signed result via `POST /tideAdminResources/add-review`.
195
+ 3. **Enclave popup** -- In Tide MultiAdmin mode, complete the two-phase enclave exchange: `GET /iga/change-requests/{id}/approval-model` sign in the enclave `POST /iga/change-requests/{id}/approval-model` with the base64 doken. In FirstAdmin/Tideless mode, `authorize` signs server-side and no popup is needed.
243
196
 
244
- 4. **Batch support** -- The sign, commit, and cancel endpoints accept arrays. The UI should support selecting multiple change requests and acting on them in a single call.
197
+ 4. **Batch support** -- Use `POST /iga/change-requests/bulk-authorize` (`{"actionTypeIn":[...],"limit":N}`) to approve many CRs at once; commit each ready CR (`readyToCommit === true`).
245
198
 
246
199
  5. **Role check** -- Only users with admin roles should see the panel. Verify the admin token includes the required realm-admin or delegated role before rendering the UI. This is UI gating only; the server enforces authorization on every API call.
247
200
 
@@ -251,22 +204,21 @@ A working IGA admin panel must include:
251
204
 
252
205
  ### Lifecycle
253
206
 
254
- - [ ] Creating a role assignment produces a DRAFT change request visible via `GET .../all/requests`
255
- - [ ] Signing a change request transitions status from DRAFT/PENDING toward APPROVED
256
- - [ ] In multi-admin mode, signing returns `requiresApprovalPopup === true` and enclave flow completes
257
- - [ ] Committing an APPROVED change request applies the change and sets status to ACTIVE
258
- - [ ] Cancelling a PENDING change request removes it from the pending list
207
+ - [ ] A governed write returns 202 and a change request appears via `GET /iga/change-requests?status=PENDING`
208
+ - [ ] `POST .../{id}/authorize` records an approval (409 if the same admin re-signs)
209
+ - [ ] In Tide MultiAdmin mode, the `approval-model` enclave exchange completes and increments `authCount`
210
+ - [ ] Committing a ready CR (`readyToCommit`) applies the change (APPROVED); 412 while under threshold
211
+ - [ ] `POST .../{id}/deny` removes it from the pending list (DENIED)
259
212
 
260
213
  ### Endpoints
261
214
 
262
- - [ ] `GET .../counts` returns correct totals matching the full request list
263
- - [ ] `GET .../all/requests` returns all change types (user, role, client, group)
264
- - [ ] `POST .../sign/batch` accepts an array and processes all items
265
- - [ ] `POST .../commit/batch` and `POST .../cancel/batch` work with arrays
266
- - [ ] `POST /tideAdminResources/add-review` accepts the enclave-signed payload
267
- - [ ] `POST /tideAdminResources/add-rejection` rejects and sets status to DENIED
268
- - [ ] `GET .../{id}/activity` returns audit trail with timestamps
269
- - [ ] `POST .../{id}/comments` persists and appears in activity
215
+ - [ ] `GET /iga/change-requests?status=PENDING` returns all pending CRs across types
216
+ - [ ] `POST /iga/change-requests/{id}/authorize` records the approval
217
+ - [ ] `POST /iga/change-requests/bulk-authorize` authorizes a batch (429 if a bulk run is active)
218
+ - [ ] `POST /iga/change-requests/{id}/commit` applies the change; 412 under threshold
219
+ - [ ] `POST /iga/change-requests/{id}/deny` sets status to DENIED
220
+ - [ ] `GET/POST /iga/change-requests/{id}/comments` persist and read back
221
+ - [ ] `POST /tideAdminResources/add-review` and `/add-rejection` return `410 Gone` (deprecated — do not use)
270
222
 
271
223
  ### Authorization
272
224
 
@@ -278,33 +230,33 @@ A working IGA admin panel must include:
278
230
 
279
231
  ## Common Failures
280
232
 
281
- ### Sign Response Ignored in Multi-Admin Mode
233
+ ### Enclave Step Skipped in Tide MultiAdmin Mode
282
234
 
283
- **Symptom**: Approve button appears to succeed but change request stays in PENDING.
235
+ **Symptom**: Approve appears to succeed but the change request stays in PENDING and `authCount` does not increase.
284
236
 
285
- **Cause**: The `requiresApprovalPopup` flag was not checked. In multi-admin mode, signing returns a challenge that must be completed through the Tide enclave.
237
+ **Cause**: In Tide MultiAdmin mode, a bare `authorize` is not enough the admin must sign the enclave challenge via the two-phase `approval-model` exchange.
286
238
 
287
- **Fix**: After calling `sign/batch`, check `response[0].requiresApprovalPopup`. If `true`, decode `changeSetDraftRequests`, present the enclave iframe, and submit the signed result via `POST /tideAdminResources/add-review`.
239
+ **Fix**: `GET /iga/change-requests/{id}/approval-model`, decode `requestModel`, sign it in the Tide enclave, then `POST /iga/change-requests/{id}/approval-model` with `{ "requestModel": "<base64 doken>" }`. (FirstAdmin/Tideless mode signs server-side on `authorize` — no enclave step.)
288
240
 
289
241
  ---
290
242
 
291
- ### Committing Before Enough Approvals
243
+ ### Committing Before Enough Approvals (412)
292
244
 
293
- **Symptom**: `POST .../commit/batch` returns an error or silently fails.
245
+ **Symptom**: `POST /iga/change-requests/{id}/commit` returns **412 Precondition Failed**.
294
246
 
295
- **Cause**: The change request has not reached APPROVED status. Quorum threshold not met.
247
+ **Cause**: The CR is under threshold, or a dependency has not committed yet.
296
248
 
297
- **Fix**: Check the status before committing. Only commit change requests with status `APPROVED`. If still `PENDING`, more admin approvals are needed.
249
+ **Fix**: Only commit CRs where `readyToCommit === true`. If still short, collect more approvals; if `blocked`, commit the CR named in `dependsOn` first (see the commit-in-passes loop in `canon/iga-change-requests-api.md`).
298
250
 
299
251
  ---
300
252
 
301
- ### Wrong ChangeSetType in Payload
253
+ ### Re-signing the Same CR (409)
302
254
 
303
- **Symptom**: 400 Bad Request or unexpected behavior when signing/committing.
255
+ **Symptom**: `POST /iga/change-requests/{id}/authorize` returns **409 Conflict**.
304
256
 
305
- **Cause**: The `changeSetType` in the request body does not match the actual type of the draft record.
257
+ **Cause**: Four-eyes enforcement the same admin cannot sign a CR twice, or the CR is no longer PENDING.
306
258
 
307
- **Fix**: Read the `changeSetType` from the `GET .../all/requests` response and pass it back exactly. Do not hardcode a type.
259
+ **Fix**: Have a *different* admin authorize, or refresh the list; do not retry the same admin's signature.
308
260
 
309
261
  ---
310
262
 
@@ -314,31 +266,21 @@ A working IGA admin panel must include:
314
266
 
315
267
  **Cause**: Admin token not included or expired.
316
268
 
317
- **Fix**: Obtain a fresh admin token before each batch of calls. If using `KcAdminClient`, call `client.auth(...)` and handle token refresh.
318
-
319
- ---
320
-
321
- ### Rejection Silently Fails
322
-
323
- **Symptom**: Clicking "Reject" appears to succeed, but the change request stays in PENDING. No error returned.
324
-
325
- **Cause**: The rejection endpoint (`POST /tideAdminResources/add-rejection`) expects `multipart/form-data`. If you send `Content-Type: application/json`, the server accepts the request but ignores the payload fields.
326
-
327
- **Fix**: Use `FormData`, not `JSON.stringify`. Do not set `Content-Type` manually — the browser sets the correct multipart boundary automatically. See the code example in the Endpoints section above.
269
+ **Fix**: Obtain a fresh admin token before each batch of calls and handle token refresh.
328
270
 
329
271
  ---
330
272
 
331
- ### Counts Out of Sync with List
273
+ ### Calling a Deprecated add-review / add-rejection Endpoint
332
274
 
333
- **Symptom**: Dashboard badge shows a different number than the actual list.
275
+ **Symptom**: `POST /tideAdminResources/add-review` or `/add-rejection` returns `410 Gone`.
334
276
 
335
- **Cause**: `GET .../counts` and `GET .../all/requests` called at different times; a change was created or committed between calls.
277
+ **Cause**: Both endpoints were removed in the IGA decoupling. They are no longer functional.
336
278
 
337
- **Fix**: Refresh counts after any mutation (sign, commit, cancel). Do not cache counts across operations.
279
+ **Fix**: Approve via `POST /iga/change-requests/{id}/authorize` (passing the enclave-signed approval in the body) and reject via `POST /iga/change-requests/{id}/deny`.
338
280
 
339
281
  ---
340
282
 
341
283
  ## References
342
284
 
343
- - Source: `sources/docs/CHANGE_REQUEST_API.md` (normative), operational exemplars (keylessh, forseti-crypto-quickstart, tidecloak-test-cases)
344
- - Content-type asymmetry confirmed by: CHANGE_REQUEST_API.md, forseti-crypto-quickstart `tidecloakApi.ts` (uses FormData for `add-review`)
285
+ - Current surface: `IgaAdminResource` (`tidecloak-iga-extensions` iga-core) `/iga/change-requests/*`, `/iga/forseti-contracts`, `/iga/role-policies`.
286
+ - Legacy bootstrap-compat surface: `/tide-admin/change-set/*`, still used by `localtest/init/init_tidecloak.sh` and the `tidecloak-js` `AdminAPI` client.
@@ -21,7 +21,26 @@ Complete guide to implementing server-side JWT verification for Tide.
21
21
 
22
22
  ---
23
23
 
24
- ## Complete Implementation
24
+ ## Recommended First: the Shipped `verifyTideCloakToken` Helper
25
+
26
+ Before hand-rolling anything, know that Tide ships a one-call verifier. `@tidecloak/verify` exports `verifyTideCloakToken`, re-exported via `@tidecloak/nextjs/server`:
27
+
28
+ ```typescript
29
+ import { verifyTideCloakToken } from '@tidecloak/nextjs/server'; // or '@tidecloak/verify'
30
+ import tcConfig from '../data/tidecloak.json';
31
+
32
+ // Arg order: (config, token, allowedRoles=[]) — config FIRST.
33
+ const payload = await verifyTideCloakToken(tcConfig, token, ['admin']);
34
+ // payload = decoded claims on success, `false` on any failure.
35
+ ```
36
+
37
+ It does local-JWKS signature verification (embedded `jwk`), issuer + `azp` validation, time-claim checks with `clockTolerance`, and optional role enforcement across realm and client roles. For the majority of API-protection needs this is all you require — see [protect-api-nextjs.md](protect-api-nextjs.md).
38
+
39
+ The complete manual implementation below is the **"under the hood" alternative**. Reach for it when you need something `verifyTideCloakToken` does not do: app-side DPoP proof re-verification, distinct 401 vs 403 responses, `iat` future-dating rejection, or an Express integration.
40
+
41
+ ---
42
+
43
+ ## Complete Implementation (Manual)
25
44
 
26
45
  If you already created `lib/auth/tidecloakConfig.ts`, `lib/auth/tideJWT.ts`, and `lib/auth/protect.ts` from [protect-api-nextjs.md](protect-api-nextjs.md), the versions below **replace** them. This playbook adds DPoP verification, client-role checking in `hasRole`, `extractToken` (supports both Bearer and DPoP schemes), and `iat` future-validation.
27
46
 
@@ -48,7 +48,7 @@ Copy-paste this prompt to an AI coding agent to scaffold a customer portal with
48
48
  > **If TideCloak is running but checks 2-4 fail**, follow the `tide-setup` skill to complete setup. Use playbook `add-auth-nextjs-fresh`.
49
49
  >
50
50
  > **Package versions** (before writing package.json):
51
- > 1. Run `npm view @tidecloak/nextjs version` to get the current stable version. Pin it exactly (e.g., `"0.13.26"`). Do not use `"latest"` or `"^"` for `@tidecloak/*` packages.
51
+ > 1. Run `npm view @tidecloak/nextjs version` to get the current stable version. Pin it exactly (e.g., `"0.13.33"`). Do not use `"latest"` or `"^"` for `@tidecloak/*` packages.
52
52
  > 2. If the resolved version is 0.99.x, skip it. Use the highest non-0.99.x version instead. If unsure, fall back to the versions in `canon/version-policy.md`.
53
53
  > 3. Run `npm view next version` and `npm view react version` to get current stable framework versions. Use caret ranges (e.g., `"^15.0.0"`). Align React and React DOM to the same major.
54
54
  > 4. Do not use `--force` or `--legacy-peer-deps` as the default install strategy. If peer dependency conflicts arise, resolve them by aligning versions.
@@ -0,0 +1,55 @@
1
+ # Prompt: Security Gap Analysis of an Existing System
2
+
3
+ Copy-paste this to an AI coding agent to audit an existing application for identity, key-management, authorization, and data-exposure gaps — and map each gap to the Tide capability that removes it. Works on systems with no Tide involvement at all.
4
+
5
+ This is an **advisory, read-only** analysis. It does not modify the target.
6
+
7
+ ---
8
+
9
+ ## The Prompt
10
+
11
+ > Perform a security gap analysis of this system. You are acting as the **Security Analyst** role from the Tide agent pack. Do not modify any code. Produce a report; do not begin remediation until I approve it.
12
+ >
13
+ > **First, confirm scope**: state exactly what you will inspect (repo, running instance, config) and that this is my own system or an authorized engagement. Read-only.
14
+ >
15
+ > **Load the doctrine**: read `canon/security-gap-mapping.md` (the SG-01 … SG-18 gap table) and `skills/tide-security-analyst/SKILL.md`. If you have the Tide MCP server, call `tide_security_analysis` to load them at once.
16
+ >
17
+ > **Two tiers**: run the **static** sweep (code/config/schema) always. Only run the **runtime confirmation** tier — sending live probes to confirm findings — if I confirm I own or am authorized to test the target, and then strictly per `canon/security-runtime-probes.md` (non-destructive, no exploitation, my own test account only). If I have not authorized live testing, stay static-only and keep findings tagged INFERRED.
18
+ >
19
+ > **Inventory the trust architecture first** (before pattern-matching), answering from the code/config:
20
+ > 1. Identity — how are users authenticated, where do credentials live?
21
+ > 2. Authority — how are tokens/sessions signed, who holds the signing key?
22
+ > 3. Authorization — where is the access decision made: client, server, or both?
23
+ > 4. Data — what sensitive data exists, and who can read it in the clear?
24
+ >
25
+ > **Run the full gap sweep**: work through SG-01 … SG-12 exhaustively. For each, run the detection commands from `canon/security-gap-mapping.md` against the target and record hits. Do NOT sample APIs (SG-05) — enumerate every route and diff against the routes that actually verify. Also collect anything matching the "Out of scope" table.
26
+ >
27
+ > **Every finding must have**:
28
+ > - A named **trust concentration** — the single party or artifact that, once compromised, defeats the control. "Uses passwords" is not a finding; "password verification collapses to one database + one server process" is.
29
+ > - **Evidence** — a file:line, config value, schema column, or HTTP observation — tagged VERIFIED (observed), INFERRED (implied by code), or ASSUMED (my statement, unconfirmed).
30
+ > - A **severity** (CRITICAL / HIGH / MEDIUM / INFO) based on blast radius under a single compromise.
31
+ > - The **Tide replacement**, cited from `canon/feature-mapping.md` or `canon/invariants.md`, with its sourcing tag.
32
+ > - The **remediation playbook sequence** (verify each playbook exists via `tide_list playbooks`).
33
+ > - The **limits** — the honesty note: what the Tide replacement does NOT cover.
34
+ >
35
+ > **Be honest**: include a non-empty "Not Addressed by Tide" section for anything you find that Tide doesn't fix (injection, XSS, CSRF, vulnerable dependencies, rate limiting, infra hardening). Never present Tide as fixing these. An honest analysis is more credible than an inflated one.
36
+ >
37
+ > **Report in the format** defined in `skills/tide-security-analyst/SKILL.md` (Report Format section): trust-architecture summary, findings most-severe-first, gap interactions, out-of-scope section, then a recommended remediation sequence with the execution role to engage first (per I-18).
38
+ >
39
+ > **Do not**: modify code, run TideCloak bootstrap, install the SDK, or start fixing anything. Report only. When I approve findings, hand off to the execution roles (Setup → Application → Security → IAM/Policy → Reviewer).
40
+
41
+ ---
42
+
43
+ ## Acceptance Criteria
44
+
45
+ - [ ] Scope and authorization confirmed before inspection
46
+ - [ ] Trust architecture (identity / authority / authorization / data) documented
47
+ - [ ] All of SG-01 … SG-18 checked, not sampled (or non-applicability stated per SG)
48
+ - [ ] Every finding names a trust concentration and carries evidence with a confidence tag
49
+ - [ ] Runtime tier run only if authorized; probed findings carry a Runtime confirmation line; unprobed findings stay INFERRED
50
+ - [ ] Every Tide-replacement claim cites `canon/feature-mapping.md` or `canon/invariants.md`
51
+ - [ ] Every remediation names a playbook that exists in `playbooks/`
52
+ - [ ] Non-empty "Not Addressed by Tide" section (or explicit "none found in scope")
53
+ - [ ] No finding claims Tide fixes an out-of-scope gap class
54
+ - [ ] Remediation sequence provided with first execution role named
55
+ - [ ] No code was modified; the analysis is read-only
@@ -10,7 +10,7 @@ Scenario-specific mistakes for encrypted communication apps using the hybrid Tid
10
10
 
11
11
  **Why it fails**: The private key is the root of trust for all E2E operations. If stored in plaintext, a database breach or XSS attack exposes all encrypted communications.
12
12
 
13
- **Fix**: Always encrypt the private key with `doEncrypt(privateKeyBytes, tag)` before storing. Always decrypt with `doDecrypt()` on login. Never persist the decrypted private key outside of in-memory variables.
13
+ **Fix**: Always encrypt the private key with `doEncrypt([{ data: privateKeyBytes, tags: [tag] }])` before storing. Always decrypt with `doDecrypt()` on login. Never persist the decrypted private key outside of in-memory variables.
14
14
 
15
15
  ---
16
16
 
@@ -118,6 +118,6 @@ Scenario-specific mistakes for encrypted communication apps using the hybrid Tid
118
118
 
119
119
  **Mistake**: Installing a 0.99.x pre-release version of the Tide SDK.
120
120
 
121
- **Why it fails**: 0.99.x versions are unstable pre-releases. The stable version is 0.13.26 (per pack version policy).
121
+ **Why it fails**: 0.99.x versions are unstable pre-releases. The stable version is 0.13.33 (per pack version policy).
122
122
 
123
- **Fix**: Pin `@tidecloak/react` to `0.13.26`. See `canon/version-policy.md`.
123
+ **Fix**: Pin `@tidecloak/react` to `0.13.33`. See `canon/version-policy.md`.
@@ -117,12 +117,12 @@
117
117
 
118
118
  23. Implement first-login flow:
119
119
  - Generate keypair client-side
120
- - Encrypt private key with `doEncrypt(privateKeyBytes, tag)`
120
+ - Encrypt private key with `doEncrypt([{ data: privateKeyBytes, tags: [tag] }])`
121
121
  - Store encrypted private key + public key via protected API
122
122
 
123
123
  24. Implement returning-login flow:
124
124
  - Fetch encrypted key material via protected API
125
- - Decrypt private key with `doDecrypt(encryptedPrivateKey, tag)`
125
+ - Decrypt private key with `doDecrypt([{ encrypted: encryptedPrivateKey, tags: [tag] }])`
126
126
  - Private key available in memory for runtime E2E operations
127
127
 
128
128
  25. Implement runtime E2E operations using the external crypto library (not Tide).
@@ -77,14 +77,14 @@ On **first login** for a new user:
77
77
  1. User authenticates via TideCloak SSO
78
78
  2. App checks database for existing encrypted key material
79
79
  3. No keys found → app generates a new asymmetric keypair client-side (e.g., `crypto_box_keypair()`)
80
- 4. App encrypts the private key using `doEncrypt(privateKeyBytes, tag)` (Tide self-encryption)
80
+ 4. App encrypts the private key using `doEncrypt([{ data: privateKeyBytes, tags: [tag] }])` (Tide self-encryption)
81
81
  5. App stores the encrypted private key and the public key in the database via a protected API endpoint
82
82
  6. Private key is now available in memory for runtime E2E operations
83
83
 
84
84
  On **returning login**:
85
85
  1. User authenticates via TideCloak SSO
86
86
  2. App fetches encrypted key material from database
87
- 3. App decrypts the private key using `doDecrypt(encryptedPrivateKey, tag)` (Tide self-encryption)
87
+ 3. App decrypts the private key using `doDecrypt([{ encrypted: encryptedPrivateKey, tags: [tag] }])` (Tide self-encryption)
88
88
  4. Private key is now available in memory for runtime E2E operations
89
89
 
90
90
  **Runtime E2E operations** (not Tide — handled by external crypto library):
@@ -27,11 +27,11 @@ All steps must complete before the service can sign merge commits. Order matters
27
27
  - `POST /admin/realms/{realm}/tide-admin/toggle-iga` with form-urlencoded `isIGAEnabled=true`.
28
28
  - Must happen after licensing.
29
29
 
30
- ## Phase 3: Approve initial change-sets
30
+ ## Phase 3: Approve initial change requests
31
31
 
32
- 6. **Approve client change-sets**
33
- - List: `GET /admin/realms/{realm}/tide-admin/change-set/clients/requests`.
34
- - For each: sign (`POST .../change-set/sign`) then commit (`POST .../change-set/commit`).
32
+ 6. **Approve initial change requests** (current `/iga/change-requests/...` surface — see `canon/iga-change-requests-api.md`)
33
+ - List: `GET /admin/realms/{realm}/iga/change-requests?status=PENDING` (objects keyed by `id`).
34
+ - Approve: `POST .../iga/change-requests/bulk-authorize` (`{"actionTypeIn":["CREATE","DELETE"],"limit":100}`), then `POST .../iga/change-requests/{id}/commit` for each `readyToCommit` CR. FirstAdmin bootstrap signs server-side.
35
35
 
36
36
  ## Phase 4: Admin user setup
37
37
 
@@ -50,11 +50,11 @@ All steps must complete before the service can sign merge commits. Order matters
50
50
  10. **Wait for account linking**
51
51
  - Poll `GET /admin/realms/{realm}/users/{userId}` until `attributes.tideUserKey` and `attributes.vuid` are present for each admin.
52
52
 
53
- ## Phase 5: Approve user change-sets
53
+ ## Phase 5: Approve user change requests
54
54
 
55
- 11. **Approve user change-sets**
56
- - List: `GET /admin/realms/{realm}/tide-admin/change-set/users/requests`.
57
- - For each: sign then commit.
55
+ 11. **Approve user change requests**
56
+ - List: `GET /admin/realms/{realm}/iga/change-requests?status=PENDING`.
57
+ - Authorize then commit (see Phase 3 / `canon/iga-change-requests-api.md`).
58
58
 
59
59
  ## Phase 6: IDP settings and adapter export
60
60