@tideorg/mcp 1.4.1 → 1.9.0
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.
- package/CHANGELOG.md +81 -0
- package/GAP_REGISTER.md +19 -9
- package/PRIVACY.md +41 -0
- package/README.md +204 -120
- package/adapters/AGENTS.md +3 -2
- package/adapters/CLAUDE.md +1 -1
- package/adapters/replit.md +0 -0
- package/canon/anti-patterns.md +55 -62
- package/canon/concepts.md +46 -34
- package/canon/custom-contracts.md +12 -8
- package/canon/feature-mapping.md +27 -75
- package/canon/framework-matrix.md +69 -22
- package/canon/hosting-options.md +111 -0
- package/canon/iga-change-requests-api.md +211 -0
- package/canon/invariants.md +33 -35
- package/canon/redirect-handler.md +0 -0
- package/canon/security-gap-mapping.md +506 -0
- package/canon/security-runtime-probes.md +177 -0
- package/canon/tidecloak-bootstrap.md +84 -15
- package/canon/tidecloak-endpoints.md +60 -98
- package/canon/troubleshooting.md +201 -53
- package/canon/version-policy.md +8 -12
- package/mcp-server/dist/http.d.ts +2 -0
- package/mcp-server/dist/http.js +46 -0
- package/mcp-server/dist/index.d.ts +0 -0
- package/mcp-server/dist/index.js +2 -601
- package/mcp-server/dist/server.d.ts +2 -0
- package/mcp-server/dist/server.js +617 -0
- package/package.json +48 -45
- package/playbooks/add-auth-nextjs-existing.md +33 -17
- package/playbooks/add-auth-nextjs-fresh.md +1 -1
- package/playbooks/add-rbac-nextjs.md +7 -2
- package/playbooks/bootstrap-realm-from-template.md +33 -18
- package/playbooks/configure-e2ee-roles-and-policies.md +0 -0
- package/playbooks/deploy-tidecloak-docker.md +31 -28
- package/playbooks/diagnose-broken-login.md +0 -0
- package/playbooks/diagnose-missing-roles-or-claims.md +2 -2
- package/playbooks/initialize-admin-and-link-account.md +22 -19
- package/playbooks/migrate-from-existing-auth.md +0 -0
- package/playbooks/protect-api-nextjs.md +40 -1
- package/playbooks/protect-aspnet-core-asgard.md +313 -0
- package/playbooks/protect-routes-nextjs.md +24 -10
- package/playbooks/provision-tidecloak-skycloak.md +213 -0
- package/playbooks/setup-forseti-e2ee.md +32 -50
- package/playbooks/setup-iga-admin-panel.md +113 -171
- package/playbooks/start-tidecloak-dev.md +0 -0
- package/playbooks/verify-jwt-server-side.md +20 -1
- package/prompts/add-admin-approval-flow.md +0 -0
- package/prompts/build-private-customer-portal.md +1 -1
- package/prompts/migrate-generic-auth-to-tide.md +0 -0
- package/prompts/secure-existing-app.md +0 -0
- package/prompts/security-gap-analysis.md +55 -0
- package/reference-apps/INDEX.md +0 -0
- package/reference-apps/encrypted-communication/anti-patterns.md +3 -3
- package/reference-apps/encrypted-communication/bootstrap-sequence.md +2 -2
- package/reference-apps/encrypted-communication/manifest.yaml +0 -0
- package/reference-apps/encrypted-communication/role-policy-matrix.md +0 -0
- package/reference-apps/encrypted-communication/scenario.md +2 -2
- package/reference-apps/git-pr-signing-service/anti-patterns.md +0 -0
- package/reference-apps/git-pr-signing-service/bootstrap-sequence.md +8 -8
- package/reference-apps/git-pr-signing-service/manifest.yaml +0 -0
- package/reference-apps/git-pr-signing-service/role-policy-matrix.md +0 -0
- package/reference-apps/git-pr-signing-service/scenario.md +0 -0
- package/reference-apps/iga-admin-governance/anti-patterns.md +18 -16
- package/reference-apps/iga-admin-governance/bootstrap-sequence.md +10 -5
- package/reference-apps/iga-admin-governance/manifest.yaml +0 -0
- package/reference-apps/iga-admin-governance/role-policy-matrix.md +12 -13
- package/reference-apps/iga-admin-governance/scenario.md +15 -13
- package/reference-apps/organisation-password-manager/anti-patterns.md +0 -0
- package/reference-apps/organisation-password-manager/bootstrap-sequence.md +5 -6
- package/reference-apps/organisation-password-manager/manifest.yaml +0 -0
- package/reference-apps/organisation-password-manager/role-policy-matrix.md +0 -0
- package/reference-apps/organisation-password-manager/scenario.md +0 -0
- package/reference-apps/policy-governed-signing/anti-patterns.md +0 -0
- package/reference-apps/policy-governed-signing/bootstrap-sequence.md +9 -9
- package/reference-apps/policy-governed-signing/manifest.yaml +0 -0
- package/reference-apps/policy-governed-signing/role-policy-matrix.md +0 -0
- package/reference-apps/policy-governed-signing/scenario.md +0 -0
- package/skills/tide-diagnostics/SKILL.md +1 -1
- package/skills/tide-integration/SKILL.md +9 -18
- package/skills/tide-learning-capture/SKILL.md +0 -0
- package/skills/tide-mcp-qa/SKILL.md +139 -0
- package/skills/tide-rbac-and-e2ee/SKILL.md +5 -5
- package/skills/tide-reviewer/SKILL.md +1 -1
- package/skills/tide-route-and-api-protection/SKILL.md +0 -0
- package/skills/tide-scenario-resolver/SKILL.md +0 -0
- package/skills/tide-security-analyst/SKILL.md +182 -0
- package/skills/tide-setup/SKILL.md +1 -1
- package/skills/tide-solutions-architect/SKILL.md +0 -0
- package/canon/delegation.md +0 -195
- package/playbooks/setup-server-delegation.md +0 -142
package/canon/troubleshooting.md
CHANGED
|
@@ -147,7 +147,7 @@ For missing Tide adapter (Cause 5):
|
|
|
147
147
|
```bash
|
|
148
148
|
# Re-export adapter with Tide extensions
|
|
149
149
|
curl -X GET \
|
|
150
|
-
"${TIDECLOAK_URL}/admin/realms/${REALM}/
|
|
150
|
+
"${TIDECLOAK_URL}/admin/realms/${REALM}/vendorResources/get-installations-provider?clientId=${CLIENT_ID}&providerId=keycloak-oidc-keycloak-json" \
|
|
151
151
|
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
|
|
152
152
|
> data/tidecloak.json
|
|
153
153
|
|
|
@@ -176,7 +176,7 @@ For missing `jwk` field (Cause 1):
|
|
|
176
176
|
```bash
|
|
177
177
|
# Re-export adapter with Tide extensions (IGA must be enabled)
|
|
178
178
|
curl -X GET \
|
|
179
|
-
"${TIDECLOAK_URL}/admin/realms/${REALM}/
|
|
179
|
+
"${TIDECLOAK_URL}/admin/realms/${REALM}/vendorResources/get-installations-provider?clientId=${CLIENT_ID}&providerId=keycloak-oidc-keycloak-json" \
|
|
180
180
|
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
|
|
181
181
|
> data/tidecloak.json
|
|
182
182
|
|
|
@@ -279,10 +279,10 @@ if (!navigator.onLine) {
|
|
|
279
279
|
```javascript
|
|
280
280
|
// Test encrypt/decrypt round-trip
|
|
281
281
|
const plaintext = 'test data';
|
|
282
|
-
const ciphertext = await iam.doEncrypt('test'
|
|
282
|
+
const [ciphertext] = await iam.doEncrypt([{ data: plaintext, tags: ['test'] }]);
|
|
283
283
|
console.log('Encrypted:', ciphertext); // Should complete within 2-5 seconds
|
|
284
284
|
|
|
285
|
-
const decrypted = await iam.doDecrypt('test'
|
|
285
|
+
const [decrypted] = await iam.doDecrypt([{ encrypted: ciphertext, tags: ['test'] }]);
|
|
286
286
|
console.log('Decrypted:', decrypted); // Should match plaintext
|
|
287
287
|
assert(decrypted === plaintext);
|
|
288
288
|
```
|
|
@@ -474,15 +474,10 @@ curl -H "Authorization: DPoP $TOKEN" -H "DPoP: $DPOP" https://app.example.com/ap
|
|
|
474
474
|
curl "${TIDECLOAK_URL}/admin/realms/${REALM}" | jq '.igaEnabled // "not enabled"'
|
|
475
475
|
# Should output: true
|
|
476
476
|
|
|
477
|
-
# 2. Check pending change-
|
|
478
|
-
curl "${TIDECLOAK_URL}/admin/realms/${REALM}/
|
|
477
|
+
# 2. Check pending change requests (current /iga/ surface — see canon/iga-change-requests-api.md)
|
|
478
|
+
curl "${TIDECLOAK_URL}/admin/realms/${REALM}/iga/change-requests?status=PENDING" \
|
|
479
479
|
-H "Authorization: Bearer ${ADMIN_TOKEN}" | jq '.'
|
|
480
|
-
#
|
|
481
|
-
|
|
482
|
-
# 3. Check counts (lightweight)
|
|
483
|
-
curl "${TIDECLOAK_URL}/admin/realms/${REALM}/tide-admin/change-set/counts" \
|
|
484
|
-
-H "Authorization: Bearer ${ADMIN_TOKEN}"
|
|
485
|
-
# Returns: {"users":N,"roles":N,"clients":N,"groups":N,"total":N}
|
|
480
|
+
# Objects keyed by .id, with .status, .readyToCommit
|
|
486
481
|
```
|
|
487
482
|
|
|
488
483
|
**Fix**:
|
|
@@ -494,25 +489,20 @@ curl -X POST "${TIDECLOAK_URL}/admin/realms/${REALM}/tide-admin/toggle-iga" \
|
|
|
494
489
|
-H "Authorization: Bearer ${ADMIN_TOKEN}"
|
|
495
490
|
```
|
|
496
491
|
|
|
497
|
-
For change
|
|
492
|
+
For a change request not approved (Cause 2):
|
|
498
493
|
```bash
|
|
499
|
-
#
|
|
500
|
-
curl -X POST "${TIDECLOAK_URL}/admin/realms/${REALM}/
|
|
501
|
-
-H "Authorization: Bearer ${ADMIN_TOKEN}"
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
# If MultiAdmin: response has requiresApprovalPopup=true — complete enclave signing
|
|
506
|
-
# Repeat with different admin tokens until quorum reached
|
|
494
|
+
# Authorize (approve) — CR_ID is the .id from the list above; body {} optional
|
|
495
|
+
curl -X POST "${TIDECLOAK_URL}/admin/realms/${REALM}/iga/change-requests/${CR_ID}/authorize" \
|
|
496
|
+
-H "Authorization: Bearer ${ADMIN_TOKEN}" -H "Content-Type: application/json" -d '{}'
|
|
497
|
+
# 409 = you already signed (four-eyes). Tide MultiAdmin: use the {id}/approval-model enclave exchange.
|
|
498
|
+
# Repeat with different admin tokens until threshold reached.
|
|
507
499
|
```
|
|
508
500
|
|
|
509
|
-
For change
|
|
501
|
+
For a change request not committed (Cause 3):
|
|
510
502
|
```bash
|
|
511
|
-
# Commit after
|
|
512
|
-
curl -X POST "${TIDECLOAK_URL}/admin/realms/${REALM}/
|
|
513
|
-
-H "Authorization: Bearer ${ADMIN_TOKEN}"
|
|
514
|
-
-H "Content-Type: application/json" \
|
|
515
|
-
-d '{"changeSets":[{"changeSetId":"DRAFT_RECORD_ID","changeSetType":"USER_ROLE","actionType":"CREATE"}]}'
|
|
503
|
+
# Commit after threshold reached (readyToCommit==true); 412 if still short
|
|
504
|
+
curl -X POST "${TIDECLOAK_URL}/admin/realms/${REALM}/iga/change-requests/${CR_ID}/commit" \
|
|
505
|
+
-H "Authorization: Bearer ${ADMIN_TOKEN}"
|
|
516
506
|
```
|
|
517
507
|
|
|
518
508
|
For token not refreshed (Cause 5):
|
|
@@ -873,10 +863,10 @@ curl -s "$TIDECLOAK_URL/admin/realms/$REALM_NAME/users?username=admin" \
|
|
|
873
863
|
-H "Authorization: Bearer $TOKEN" | jq '.[0].id'
|
|
874
864
|
# If null: user change request was not approved
|
|
875
865
|
|
|
876
|
-
# Check pending
|
|
877
|
-
curl -s "$TIDECLOAK_URL/admin/realms/$REALM_NAME/
|
|
866
|
+
# Check pending change requests (current /iga/ surface)
|
|
867
|
+
curl -s "$TIDECLOAK_URL/admin/realms/$REALM_NAME/iga/change-requests?status=PENDING" \
|
|
878
868
|
-H "Authorization: Bearer $TOKEN" | jq length
|
|
879
|
-
# If > 0: approve them first
|
|
869
|
+
# If > 0: approve them first (authorize + commit — see canon/iga-change-requests-api.md)
|
|
880
870
|
```
|
|
881
871
|
|
|
882
872
|
**Fix**: Approve user creation change request between user creation and role assignment:
|
|
@@ -925,7 +915,7 @@ jq 'has("jwk") and has("vendorId") and has("homeOrkUrl")' data/tidecloak.json
|
|
|
925
915
|
|
|
926
916
|
# 2. Check export endpoint used
|
|
927
917
|
# Correct endpoint:
|
|
928
|
-
# GET /admin/realms/{realm}/
|
|
918
|
+
# GET /admin/realms/{realm}/vendorResources/get-installations-provider?clientId={clientId}&providerId=keycloak-oidc-keycloak-json (realm-level; client is a query param)
|
|
929
919
|
```
|
|
930
920
|
|
|
931
921
|
**Fix**:
|
|
@@ -933,7 +923,7 @@ jq 'has("jwk") and has("vendorId") and has("homeOrkUrl")' data/tidecloak.json
|
|
|
933
923
|
```bash
|
|
934
924
|
# Use correct Tide adapter export endpoint
|
|
935
925
|
curl -X GET \
|
|
936
|
-
"${TIDECLOAK_URL}/admin/realms/${REALM}/
|
|
926
|
+
"${TIDECLOAK_URL}/admin/realms/${REALM}/vendorResources/get-installations-provider?clientId=${CLIENT_ID}&providerId=keycloak-oidc-keycloak-json" \
|
|
937
927
|
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
|
|
938
928
|
-o data/tidecloak.json
|
|
939
929
|
|
|
@@ -1217,8 +1207,8 @@ await tc.executeSignRequest(request, true);
|
|
|
1217
1207
|
| `requiresApprovalPopup: true` | Backend cannot auto-approve, must open approval enclave in admin's browser |
|
|
1218
1208
|
| `Cannot destructure property 'Policy' of 'Models' as it is undefined` | `Models` imported from `@tidecloak/nextjs` (wrong package). Import from `@tideorg/js` instead. See T-15. |
|
|
1219
1209
|
| `IAMService.createTideRequest is not a function` | `createTideRequest` (and `requestTideOperatorApproval`, `executeSignRequest`) live on `IAMService._tc` (TideCloak), not `IAMService`. See T-16. |
|
|
1220
|
-
| `Cannot destructure 'verifyTideCloakToken'` | `@tidecloak/verify`
|
|
1221
|
-
| "Could not
|
|
1210
|
+
| `Cannot destructure 'verifyTideCloakToken'` | Very old `@tidecloak/verify` (single-export build); upgrade — current versions ship dual CJS/ESM exports and a plain named import works. Note arg order is `(config, token, allowedRoles?)`. |
|
|
1211
|
+
| "Tide realm setup failed" / "Could not set up the Tide realm" | `setUpTideRealm` expects `Content-Type: application/x-www-form-urlencoded` with `email` (`@FormParam`), not JSON. (There is no explicit email-null guard; a missing/blank email surfaces as a downstream setup failure.) |
|
|
1222
1212
|
| "Could not find configuration for Required Action" | Missing Tide env vars (`SYSTEM_HOME_ORK`, `USER_HOME_ORK`, `THRESHOLD_T`, `THRESHOLD_N`, `PAYER_PUBLIC`) from Docker |
|
|
1223
1213
|
| AccessDeniedException on H2 | Data directory mounted as project root or wrong permissions; mount `./data` subdirectory and `chown 1000:1000` |
|
|
1224
1214
|
| `"User has not been given any access to '${tag}'"` | User lacks `_tide_{tag}.selfencrypt` or `.selfdecrypt` role for self-encryption. Fatal — assign role, then re-login. |
|
|
@@ -1250,9 +1240,9 @@ curl -s "$TIDECLOAK_URL/admin/realms/$REALM/roles/default-roles-$REALM" \
|
|
|
1250
1240
|
-H "Authorization: Bearer $TOKEN" | jq '.composites.realm'
|
|
1251
1241
|
|
|
1252
1242
|
# 3. Check IGA change requests are approved
|
|
1253
|
-
curl -s "$TIDECLOAK_URL/admin/realms/$REALM/
|
|
1243
|
+
curl -s "$TIDECLOAK_URL/admin/realms/$REALM/iga/change-requests?status=PENDING" \
|
|
1254
1244
|
-H "Authorization: Bearer $TOKEN" | jq length
|
|
1255
|
-
# Should be 0. If > 0,
|
|
1245
|
+
# Should be 0. If > 0, authorize and commit (canon/iga-change-requests-api.md).
|
|
1256
1246
|
|
|
1257
1247
|
# 4. Check user's JWT has the roles
|
|
1258
1248
|
# Decode token and check realm_access.roles
|
|
@@ -1279,12 +1269,15 @@ curl -s "$TIDECLOAK_URL/admin/realms/$REALM/tide-admin/change-set/roles/requests
|
|
|
1279
1269
|
|
|
1280
1270
|
**Diagnostics**:
|
|
1281
1271
|
```bash
|
|
1282
|
-
# 1. Check
|
|
1283
|
-
curl -s "${
|
|
1284
|
-
|
|
1272
|
+
# 1. Check the role-policies endpoint is reachable (admin bearer required)
|
|
1273
|
+
curl -s -H "Authorization: Bearer ${ADMIN_TOKEN}" \
|
|
1274
|
+
"${TIDECLOAK_URL}/admin/realms/${REALM}/iga/role-policies"
|
|
1275
|
+
# Should return a JSON array of records; each carries the signed bytes in its `policy` field.
|
|
1276
|
+
# NOTE: the old public GET /realms/{realm}/tide-policy-resources/admin-policy endpoint
|
|
1277
|
+
# is NOT present on current main; do not rely on it.
|
|
1285
1278
|
|
|
1286
1279
|
# 2. Check URL construction (common: double slash from trailing slash in auth-server-url)
|
|
1287
|
-
echo "${TIDECLOAK_URL}/realms/${REALM}/
|
|
1280
|
+
echo "${TIDECLOAK_URL}/admin/realms/${REALM}/iga/role-policies"
|
|
1288
1281
|
# Must not contain // between host and path (except after protocol)
|
|
1289
1282
|
|
|
1290
1283
|
# 3. Check if addPolicy was called AFTER approval, not before
|
|
@@ -1294,15 +1287,18 @@ grep -B 5 "addPolicy" src/
|
|
|
1294
1287
|
|
|
1295
1288
|
**Fix**:
|
|
1296
1289
|
|
|
1297
|
-
1. **Fetch admin policy from the correct endpoint** (
|
|
1290
|
+
1. **Fetch the signed admin policy from the correct endpoint** (server-side, admin bearer token):
|
|
1298
1291
|
```
|
|
1299
|
-
GET /realms/{realm}/
|
|
1292
|
+
GET /admin/realms/{realm}/iga/role-policies
|
|
1300
1293
|
```
|
|
1301
|
-
|
|
1294
|
+
The signed policy bytes live in each record's `policy` field. Not: the public
|
|
1295
|
+
`GET /realms/{realm}/tide-policy-resources/admin-policy` endpoint (not on current main),
|
|
1296
|
+
nor `GET /admin/realms/{realm}/tide-admin/realm-policy` (returns `{ status: "none" }`).
|
|
1302
1297
|
|
|
1303
|
-
2. **Decode base64
|
|
1298
|
+
2. **Decode the base64 `policy` field**:
|
|
1304
1299
|
```js
|
|
1305
|
-
const
|
|
1300
|
+
const records = await res.json();
|
|
1301
|
+
const b64 = records[0].policy; // signed bytes, base64-encoded
|
|
1306
1302
|
const raw = Buffer.from(b64, "base64");
|
|
1307
1303
|
const bytes = new Uint8Array(raw);
|
|
1308
1304
|
```
|
|
@@ -1318,7 +1314,7 @@ grep -B 5 "addPolicy" src/
|
|
|
1318
1314
|
4. **URL normalization**: Strip trailing slashes from `auth-server-url` before constructing endpoint URLs:
|
|
1319
1315
|
```js
|
|
1320
1316
|
const baseUrl = config["auth-server-url"].replace(/\/+$/, "");
|
|
1321
|
-
const
|
|
1317
|
+
const rolePoliciesUrl = `${baseUrl}/admin/realms/${config.realm}/iga/role-policies`;
|
|
1322
1318
|
```
|
|
1323
1319
|
|
|
1324
1320
|
**This is a bootstrap/policy-flow failure, not a role issue.** Do not attempt to fix by renaming roles or switching encryption models.
|
|
@@ -1453,17 +1449,17 @@ VERIFIED (TIDE_LEARNINGS-001 L-05).
|
|
|
1453
1449
|
|
|
1454
1450
|
---
|
|
1455
1451
|
|
|
1456
|
-
## T-18: IGA Change-
|
|
1452
|
+
## T-18: IGA Change-Request Approval May Require Admin Console UI (Tide MultiAdmin)
|
|
1457
1453
|
|
|
1458
|
-
**Symptom**: REST-based `POST /
|
|
1454
|
+
**Symptom**: REST-based `POST /iga/change-requests/{id}/authorize` succeeds but `commit` fails (or the CR never reaches `readyToCommit`) with "Could not find authorization signature."
|
|
1459
1455
|
|
|
1460
|
-
**Root cause**:
|
|
1456
|
+
**Root cause**: In Tide MultiAdmin mode, approval is a cryptographic operation requiring the admin's Tide doken and ORK interaction — a bare `authorize` is not enough; the admin must complete the two-phase `{id}/approval-model` enclave exchange. FirstAdmin/Tideless mode signs server-side on `authorize`, so bootstrap works headless.
|
|
1461
1457
|
|
|
1462
|
-
**When this happens**: Typically
|
|
1458
|
+
**When this happens**: Typically once the realm is in Tide MultiAdmin mode (a `tide-realm-admin` exists and IGA requires the cryptographic round-trip). The bootstrap script's `approve_all_pending` works during initial FirstAdmin bootstrap but a headless `authorize` fails for subsequent MultiAdmin operations.
|
|
1463
1459
|
|
|
1464
|
-
**Fix**:
|
|
1460
|
+
**Fix**: Complete the enclave step — `GET`/`POST /iga/change-requests/{id}/approval-model` with the SDK/enclave, or approve from the TideCloak admin console UI (`http://localhost:8080/admin/master/console/#/{realm}` → Change Requests).
|
|
1465
1461
|
|
|
1466
|
-
**Verification**: After approval, `GET /
|
|
1462
|
+
**Verification**: After approval, `GET /iga/change-requests?status=PENDING` returns `[]`.
|
|
1467
1463
|
|
|
1468
1464
|
VERIFIED (TIDE_LEARNINGS-001 L-11).
|
|
1469
1465
|
|
|
@@ -1483,7 +1479,7 @@ VERIFIED (TIDE_LEARNINGS-001 L-11).
|
|
|
1483
1479
|
5. Check TideCloak container logs: `docker logs <container>` for the real error
|
|
1484
1480
|
|
|
1485
1481
|
**Common 500 causes**:
|
|
1486
|
-
- ORK PreSign failure (`Provided TideAuthData OR PreviousTokenAuthorization`
|
|
1482
|
+
- ORK PreSign failure — on a stale realm, recreate it. Actual PreSign error strings on current ORK include: `"Tide user authentication data provided but no valid authentication method found within"` and `"DPoP thumbprint in requested token does not match with what the tide user approved"` (`ork: TidecloakSessionStartTokenSignRequest.cs`). (The older `Provided TideAuthData OR PreviousTokenAuthorization` string is no longer emitted.)
|
|
1487
1483
|
- Database connection failure
|
|
1488
1484
|
- Realm misconfiguration after incomplete bootstrap
|
|
1489
1485
|
|
|
@@ -1492,3 +1488,155 @@ VERIFIED (TIDE_LEARNINGS-001 L-11).
|
|
|
1492
1488
|
**Verification**: After fixing the server error, the request returns 200 with proper CORS headers.
|
|
1493
1489
|
|
|
1494
1490
|
VERIFIED (LEARNINGS-ratidefy-batch-001 L-15).
|
|
1491
|
+
|
|
1492
|
+
---
|
|
1493
|
+
|
|
1494
|
+
## T-20: Admin API Returns 401 Despite Valid Roles (Missing JWT kid)
|
|
1495
|
+
|
|
1496
|
+
**Symptom**: Token has all admin roles (`realm-admin`, `manage-users`, `view-users`, etc.), token is not expired, but admin REST API returns `{"error":"HTTP 401 Unauthorized"}`. TideCloak logs show `kid is null, cant find public key: realm={0}`.
|
|
1497
|
+
|
|
1498
|
+
**Possible Causes**:
|
|
1499
|
+
1. JWT header has no `kid` field — realm's EdDSA signing key configuration is broken
|
|
1500
|
+
2. Token signed with ORK threshold EdDSA but no `kid` reference for admin API to look up the verification key
|
|
1501
|
+
|
|
1502
|
+
**Diagnostics**:
|
|
1503
|
+
|
|
1504
|
+
```bash
|
|
1505
|
+
# Decode the JWT header
|
|
1506
|
+
echo '<token>' | cut -d. -f1 | base64 -d 2>/dev/null | jq .
|
|
1507
|
+
# If output shows {"alg":"EdDSA","typ":"JWT"} with NO "kid" field — that's the problem
|
|
1508
|
+
```
|
|
1509
|
+
|
|
1510
|
+
**Fix**: Recreate the TideCloak realm. The `kid` issue is a realm-level configuration problem that cannot be fixed on existing tokens. A new realm will issue tokens with proper `kid` in the JWT header.
|
|
1511
|
+
|
|
1512
|
+
**Verification**: After recreating the realm, decode a new token's header — it should include `"kid": "some-key-id"`.
|
|
1513
|
+
|
|
1514
|
+
VERIFIED (Ratidefy SaaS — ratidefy-sign realm had missing kid, recreated as ratidefy-sign-2).
|
|
1515
|
+
|
|
1516
|
+
---
|
|
1517
|
+
|
|
1518
|
+
## T-21: Enclave Error "Client origin could not be verified"
|
|
1519
|
+
|
|
1520
|
+
**Symptom**: Tide enclave popup appears and immediately closes. Browser console shows `[HEIMDALL] Recieved enclave error: Client origin could not be verified`.
|
|
1521
|
+
|
|
1522
|
+
**Possible Causes**:
|
|
1523
|
+
1. `CustomAdminUIDomain` not set on Tide IDP
|
|
1524
|
+
2. IDP settings not signed after setting `CustomAdminUIDomain`
|
|
1525
|
+
3. `client-origin-auth` key in adapter config is malformed or missing the origin URL
|
|
1526
|
+
|
|
1527
|
+
**Diagnostics**:
|
|
1528
|
+
|
|
1529
|
+
```bash
|
|
1530
|
+
# Check CustomAdminUIDomain
|
|
1531
|
+
TOKEN=$(curl -s -X POST "$TC_URL/realms/master/protocol/openid-connect/token" \
|
|
1532
|
+
-d "username=admin&password=$PASS&grant_type=password&client_id=admin-cli" | jq -r .access_token)
|
|
1533
|
+
curl -s "$TC_URL/admin/realms/$REALM/identity-provider/instances/tide" \
|
|
1534
|
+
-H "Authorization: Bearer $TOKEN" | jq '.config.CustomAdminUIDomain'
|
|
1535
|
+
|
|
1536
|
+
# Check adapter config for client-origin-auth key
|
|
1537
|
+
# The key MUST include the origin URL: "client-origin-auth-https://myapp.com"
|
|
1538
|
+
# NOT a hash/signature as the key suffix
|
|
1539
|
+
```
|
|
1540
|
+
|
|
1541
|
+
**Fix**:
|
|
1542
|
+
1. Set `CustomAdminUIDomain` to the app origin (e.g., `https://myapp.com`)
|
|
1543
|
+
2. Sign the IDP settings: `POST /admin/realms/{realm}/vendorResources/sign-idp-settings`
|
|
1544
|
+
3. Ensure adapter config `client-origin-auth` key includes the origin URL, not just the signature value
|
|
1545
|
+
|
|
1546
|
+
**Verification**: Enclave popup appears and stays open for user interaction.
|
|
1547
|
+
|
|
1548
|
+
VERIFIED (Ratidefy SaaS — IDP settings unsigned, client-origin-auth key had signature instead of origin URL).
|
|
1549
|
+
|
|
1550
|
+
---
|
|
1551
|
+
|
|
1552
|
+
## T-22: IGA Role Assignment Stuck in DRAFT (Unsignable Change Request)
|
|
1553
|
+
|
|
1554
|
+
**Symptom**: Role assigned to user via admin API, change request created, but sign/commit returns 200 without actually signing. Role remains in DRAFT state permanently.
|
|
1555
|
+
|
|
1556
|
+
**Possible Causes**:
|
|
1557
|
+
1. Role assigned before the user linked their Tide account — IGA requires a Tide doken for change request signing
|
|
1558
|
+
2. The master admin token can create change requests but cannot sign them without a Tide identity
|
|
1559
|
+
|
|
1560
|
+
**Diagnostics**:
|
|
1561
|
+
1. Check TideCloak admin → realm → Users → target user → Role Mapping tab
|
|
1562
|
+
2. If role shows "DRAFT" badge, the change request was never signed
|
|
1563
|
+
3. Check if user has a federated identity (Identity Provider Links tab)
|
|
1564
|
+
|
|
1565
|
+
**Fix**:
|
|
1566
|
+
1. Delete the DRAFT change request
|
|
1567
|
+
2. Ensure the user links their Tide account first (via invite link)
|
|
1568
|
+
3. Then assign the role — the change request can now be signed
|
|
1569
|
+
|
|
1570
|
+
**Prevention**: In automated provisioning, split into two phases:
|
|
1571
|
+
- Phase A: Create realm, user, generate invite link (no role assignment)
|
|
1572
|
+
- Phase B: After user links Tide account, assign role and approve change request
|
|
1573
|
+
|
|
1574
|
+
**Verification**: Role shows "ACTIVE" status in TideCloak admin after sign+commit.
|
|
1575
|
+
|
|
1576
|
+
VERIFIED (Ratidefy SaaS — provisioner assigned tide-realm-admin before account linking, creating permanent DRAFT).
|
|
1577
|
+
|
|
1578
|
+
---
|
|
1579
|
+
|
|
1580
|
+
## T-23: ASP.NET Core JWT Validation Fails on Tidecloak EdDSA Tokens
|
|
1581
|
+
|
|
1582
|
+
**Symptoms:**
|
|
1583
|
+
- `IDX10503: Signature validation failed`
|
|
1584
|
+
- `IDX10500: Signature validation failed. No security keys were provided`
|
|
1585
|
+
- 401 on every request despite a valid `Authorization: Bearer <token>`
|
|
1586
|
+
|
|
1587
|
+
**Cause:** `Microsoft.IdentityModel.Tokens` does not ship EdDSA. The `Tide.Asgard.AspNetCore.Authentication` SDK ships an EdDSA `SignatureProvider` in `Tide.Asgard.Core`, but you have to wire it in by setting `IssuerSigningKey = Utils.GetEd25519IssuerKey(builder.Configuration)`.
|
|
1588
|
+
|
|
1589
|
+
The asgard `Example/Program.cs` has that exact line **commented out** at line 17. Copying the Example verbatim leaves you with no EdDSA support; the README form at lines 117-139 is the correct wiring.
|
|
1590
|
+
|
|
1591
|
+
**Fix:**
|
|
1592
|
+
```csharp
|
|
1593
|
+
builder.Services.AddKeycloakWebApiAuthentication(builder.Configuration, options =>
|
|
1594
|
+
{
|
|
1595
|
+
options.RequireHttpsMetadata = false;
|
|
1596
|
+
options.TokenValidationParameters.IssuerSigningKey =
|
|
1597
|
+
Utils.GetEd25519IssuerKey(builder.Configuration);
|
|
1598
|
+
});
|
|
1599
|
+
```
|
|
1600
|
+
|
|
1601
|
+
**Verification:** Tokens with `alg=EdDSA` validate. Logs at Debug for `Keycloak.AuthServices` show the validation chain completing.
|
|
1602
|
+
|
|
1603
|
+
VERIFIED (asgard `README.md:117-139` vs `Example/Program.cs:17`).
|
|
1604
|
+
|
|
1605
|
+
See [playbooks/protect-aspnet-core-asgard.md](../playbooks/protect-aspnet-core-asgard.md).
|
|
1606
|
+
|
|
1607
|
+
---
|
|
1608
|
+
|
|
1609
|
+
## T-24: ASP.NET Core Token Exchange Throws NotImplementedException
|
|
1610
|
+
|
|
1611
|
+
**Symptoms:**
|
|
1612
|
+
- `ITokenExchangeService.ExchangeToken(...)` throws `NotImplementedException` from the asgard SDK.
|
|
1613
|
+
- Worked before; broke after enabling DPoP on the browser SPA.
|
|
1614
|
+
|
|
1615
|
+
**Cause:** `TokenExchangeService.ExchangeToken` inspects the inbound `Authorization` header. Bearer tokens are routed to a fully-implemented path. DPoP-tagged requests are routed to `ExchangeDPoPToken`, which is a stub (`throw new NotImplementedException()` at the top of the method). The same applies to the "Doken" auth scheme (`ExchangeTideDoken` stub). The internal `ValidateExchangeProof` helper is also a stub.
|
|
1616
|
+
|
|
1617
|
+
The most common trigger is uncommenting `useDPoP: { mode: "strict", alg: "EdDSA" }` in the SPA's `kc.init({...})` call. The moment the SPA starts sending `Authorization: DPoP <token>` + `DPoP: <proof>` headers, every controller that calls `ExchangeToken` 500s.
|
|
1618
|
+
|
|
1619
|
+
**Fix:** Disable browser DPoP until the SDK ships a DPoP-aware exchange path. The DPoP proof-validation service (`DPoPProofValidationService`) is fully implemented for **inbound** DPoP, but its registration helper `.WithDPoP(...)` is commented out — so DPoP today requires manual service wiring on inbound requests AND no DPoP on token exchange.
|
|
1620
|
+
|
|
1621
|
+
**Verification:** With `useDPoP` disabled, exchange succeeds. With DPoP on, the 500 is reproducible.
|
|
1622
|
+
|
|
1623
|
+
VERIFIED (asgard `TokenExchangeService.cs:28-35` routes to stub at line 93).
|
|
1624
|
+
|
|
1625
|
+
---
|
|
1626
|
+
|
|
1627
|
+
## T-25: ASP.NET Core 401 with Audience Mismatch
|
|
1628
|
+
|
|
1629
|
+
**Symptoms:**
|
|
1630
|
+
- 401 on every browser-issued request despite a valid token.
|
|
1631
|
+
- Token decodes correctly but `aud` claim does not include the backend client ID.
|
|
1632
|
+
|
|
1633
|
+
**Cause:** The browser's public client (`browser-login-page`) issues tokens with itself as the audience. The .NET backend's confidential client (`backend`) rejects tokens that do not list `backend` in `aud`.
|
|
1634
|
+
|
|
1635
|
+
**Fix:** Add an audience mapper to the browser client's dedicated client scope.
|
|
1636
|
+
|
|
1637
|
+
- Realm -> Clients -> `browser-login-page` -> Client scopes -> `browser-login-page-dedicated` -> Add mapper -> By configuration -> Audience.
|
|
1638
|
+
- Name: `backend-mapper`. Included Client Audience: `backend`. Save.
|
|
1639
|
+
|
|
1640
|
+
**Verification:** Decode an SPA-issued token (e.g. at jwt.io); confirm `aud` array contains `backend`.
|
|
1641
|
+
|
|
1642
|
+
VERIFIED (asgard `README.md:55-62`).
|
package/canon/version-policy.md
CHANGED
|
@@ -8,22 +8,20 @@ All other pack files point here. Do not duplicate version numbers in playbooks o
|
|
|
8
8
|
|
|
9
9
|
## Tide SDK Packages
|
|
10
10
|
|
|
11
|
-
Resolved from npm registry on 2026-
|
|
11
|
+
Resolved from npm registry on 2026-07-07. npm is the release source of truth. Repo `package.json` version fields lag npm (e.g. `tidecloak-js` source on `main` reads 0.12.15); always confirm against npm, not the checked-out source tree.
|
|
12
12
|
|
|
13
13
|
| Package | Stable version | Range for templates | Purpose |
|
|
14
14
|
|---------|---------------|--------------------| --------|
|
|
15
|
-
| `@tidecloak/js` | 0.13.
|
|
16
|
-
| `@tidecloak/react` | 0.13.
|
|
17
|
-
| `@tidecloak/nextjs` | 0.13.
|
|
18
|
-
| `@tidecloak/verify` | 0.13.
|
|
19
|
-
| `heimdall-tide` | 0.13.
|
|
20
|
-
| `@tideorg/js` | 0.13.
|
|
21
|
-
| `asgard-tide` | 0.13.
|
|
15
|
+
| `@tidecloak/js` | 0.13.33 | `0.13.33` | Core SDK (vanilla JS, IAMService) |
|
|
16
|
+
| `@tidecloak/react` | 0.13.33 | `0.13.33` | React hooks and guard components |
|
|
17
|
+
| `@tidecloak/nextjs` | 0.13.33 | `0.13.33` | Next.js provider and hooks |
|
|
18
|
+
| `@tidecloak/verify` | 0.13.33 | `0.13.33` | Server-side JWT verification (CJS) |
|
|
19
|
+
| `heimdall-tide` | 0.13.33 | `0.13.33` | Policy signing, BasicCustomRequest |
|
|
20
|
+
| `@tideorg/js` | 0.13.33 | `0.13.33` | Models, Contracts (Forseti). `tide-js` `main` package.json reads 0.13.32; npm latest is 0.13.33. |
|
|
21
|
+
| `asgard-tide` | 0.13.33 | `0.13.33` | Signing request builders (`BasicCustomRequest`, `DynamicPayloadCustomRequest`) |
|
|
22
22
|
|
|
23
23
|
**Pin to exact version in templates.** Tide packages are pre-1.0. Minor bumps can break. Run `npm view <package> version` before installing to confirm the latest stable.
|
|
24
24
|
|
|
25
|
-
**Exclude all 0.99.x versions.** If any `@tidecloak/*` package publishes a 0.99.x release, do not adopt it in pack guidance until it is confirmed stable.
|
|
26
|
-
|
|
27
25
|
---
|
|
28
26
|
|
|
29
27
|
## Framework Packages
|
|
@@ -75,7 +73,6 @@ npm view jose version
|
|
|
75
73
|
|
|
76
74
|
- **Do not use `latest` tag in package.json.** Pin Tide packages to exact versions. Use caret ranges only for framework packages. This applies to all Tide packages: `@tidecloak/*`, `@tideorg/js`, `heimdall-tide`, `asgard-tide`.
|
|
77
75
|
- **Do not use `-staging` npm tags.** Tags like `0.13.24-staging` are pre-release builds. Always use the stable version listed above. "Use staging" refers to the Docker image (`tideorg/tidecloak-stg-dev`), never to npm package tags. VERIFIED (LEARNINGS-batch-005 L-01, L-02).
|
|
78
|
-
- **Do not use 0.99.x versions** of any `@tidecloak/*` package in templates or guidance.
|
|
79
76
|
- **Do not assume 1.0.0.** All Tide packages are pre-1.0. Do not write `"@tidecloak/nextjs": "^1.0.0"`.
|
|
80
77
|
- **Do not hardcode versions in playbook install snippets.** Playbooks should say `npm install @tidecloak/nextjs` (installs latest) and then instruct the builder to verify with `npm view`. Templates pin the version explicitly.
|
|
81
78
|
- **Docker image and npm versions are independent.** Switching to the staging Docker image (`tidecloak-stg-dev`) does NOT mean switching to staging npm packages. npm packages stay at stable versions.
|
|
@@ -86,4 +83,3 @@ npm view jose version
|
|
|
86
83
|
|
|
87
84
|
- When a new stable Tide SDK release is published.
|
|
88
85
|
- When a framework major version ships that affects template compatibility.
|
|
89
|
-
- When a 0.99.x Tide package appears and a decision is needed.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import express from "express";
|
|
3
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
4
|
+
import { createServer } from "./server.js";
|
|
5
|
+
const app = express();
|
|
6
|
+
app.use(express.json());
|
|
7
|
+
// Optional Bearer token auth
|
|
8
|
+
const API_TOKEN = process.env.API_TOKEN;
|
|
9
|
+
if (API_TOKEN) {
|
|
10
|
+
app.use("/mcp", (req, res, next) => {
|
|
11
|
+
const auth = req.headers.authorization;
|
|
12
|
+
if (!auth || auth !== `Bearer ${API_TOKEN}`) {
|
|
13
|
+
res.status(401).json({ error: "Unauthorized" });
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
next();
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
// MCP endpoint — stateless (new server + transport per request)
|
|
20
|
+
app.post("/mcp", async (req, res) => {
|
|
21
|
+
const server = createServer();
|
|
22
|
+
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
|
|
23
|
+
res.on("close", () => {
|
|
24
|
+
transport.close();
|
|
25
|
+
server.close();
|
|
26
|
+
});
|
|
27
|
+
await server.connect(transport);
|
|
28
|
+
await transport.handleRequest(req, res, req.body);
|
|
29
|
+
});
|
|
30
|
+
// Handle GET and DELETE for SSE streams (required by protocol)
|
|
31
|
+
app.get("/mcp", async (req, res) => {
|
|
32
|
+
res.status(405).json({ error: "Method not allowed. Use POST for stateless MCP." });
|
|
33
|
+
});
|
|
34
|
+
app.delete("/mcp", async (req, res) => {
|
|
35
|
+
res.status(405).json({ error: "Method not allowed. Stateless server — no sessions to delete." });
|
|
36
|
+
});
|
|
37
|
+
// Health check for Azure / load balancers
|
|
38
|
+
app.get("/health", (_, res) => {
|
|
39
|
+
res.status(200).json({ status: "ok", name: "@tideorg/mcp" });
|
|
40
|
+
});
|
|
41
|
+
const port = parseInt(process.env.PORT || "3000", 10);
|
|
42
|
+
app.listen(port, "0.0.0.0", () => {
|
|
43
|
+
console.log(`@tideorg/mcp HTTP server listening on port ${port}`);
|
|
44
|
+
console.log(`MCP endpoint: http://0.0.0.0:${port}/mcp`);
|
|
45
|
+
console.log(`Health check: http://0.0.0.0:${port}/health`);
|
|
46
|
+
});
|
|
File without changes
|