@tideorg/mcp 1.4.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/GAP_REGISTER.md +117 -0
- package/README.md +38 -0
- package/adapters/AGENTS.md +241 -0
- package/adapters/CLAUDE.md +146 -0
- package/adapters/replit.md +224 -0
- package/canon/anti-patterns.md +2133 -0
- package/canon/concepts.md +816 -0
- package/canon/custom-contracts.md +533 -0
- package/canon/delegation.md +195 -0
- package/canon/feature-mapping.md +637 -0
- package/canon/framework-matrix.md +1125 -0
- package/canon/invariants.md +851 -0
- package/canon/redirect-handler.md +254 -0
- package/canon/tidecloak-bootstrap.md +287 -0
- package/canon/tidecloak-endpoints.md +294 -0
- package/canon/troubleshooting.md +1494 -0
- package/canon/version-policy.md +89 -0
- package/mcp-server/dist/index.d.ts +2 -0
- package/mcp-server/dist/index.js +605 -0
- package/package.json +45 -0
- package/playbooks/add-auth-nextjs-existing.md +799 -0
- package/playbooks/add-auth-nextjs-fresh.md +654 -0
- package/playbooks/add-rbac-nextjs.md +190 -0
- package/playbooks/bootstrap-realm-from-template.md +170 -0
- package/playbooks/configure-e2ee-roles-and-policies.md +196 -0
- package/playbooks/deploy-tidecloak-docker.md +792 -0
- package/playbooks/diagnose-broken-login.md +234 -0
- package/playbooks/diagnose-missing-roles-or-claims.md +253 -0
- package/playbooks/initialize-admin-and-link-account.md +235 -0
- package/playbooks/migrate-from-existing-auth.md +198 -0
- package/playbooks/protect-api-nextjs.md +451 -0
- package/playbooks/protect-routes-nextjs.md +544 -0
- package/playbooks/setup-forseti-e2ee.md +756 -0
- package/playbooks/setup-iga-admin-panel.md +344 -0
- package/playbooks/setup-server-delegation.md +142 -0
- package/playbooks/start-tidecloak-dev.md +130 -0
- package/playbooks/verify-jwt-server-side.md +439 -0
- package/prompts/add-admin-approval-flow.md +50 -0
- package/prompts/build-private-customer-portal.md +85 -0
- package/prompts/migrate-generic-auth-to-tide.md +67 -0
- package/prompts/secure-existing-app.md +80 -0
- package/reference-apps/INDEX.md +87 -0
- package/reference-apps/encrypted-communication/anti-patterns.md +123 -0
- package/reference-apps/encrypted-communication/bootstrap-sequence.md +152 -0
- package/reference-apps/encrypted-communication/manifest.yaml +100 -0
- package/reference-apps/encrypted-communication/role-policy-matrix.md +80 -0
- package/reference-apps/encrypted-communication/scenario.md +134 -0
- package/reference-apps/git-pr-signing-service/anti-patterns.md +61 -0
- package/reference-apps/git-pr-signing-service/bootstrap-sequence.md +157 -0
- package/reference-apps/git-pr-signing-service/manifest.yaml +80 -0
- package/reference-apps/git-pr-signing-service/role-policy-matrix.md +99 -0
- package/reference-apps/git-pr-signing-service/scenario.md +169 -0
- package/reference-apps/iga-admin-governance/anti-patterns.md +133 -0
- package/reference-apps/iga-admin-governance/bootstrap-sequence.md +153 -0
- package/reference-apps/iga-admin-governance/manifest.yaml +87 -0
- package/reference-apps/iga-admin-governance/role-policy-matrix.md +67 -0
- package/reference-apps/iga-admin-governance/scenario.md +126 -0
- package/reference-apps/organisation-password-manager/anti-patterns.md +71 -0
- package/reference-apps/organisation-password-manager/bootstrap-sequence.md +127 -0
- package/reference-apps/organisation-password-manager/manifest.yaml +86 -0
- package/reference-apps/organisation-password-manager/role-policy-matrix.md +59 -0
- package/reference-apps/organisation-password-manager/scenario.md +107 -0
- package/reference-apps/policy-governed-signing/anti-patterns.md +67 -0
- package/reference-apps/policy-governed-signing/bootstrap-sequence.md +128 -0
- package/reference-apps/policy-governed-signing/manifest.yaml +78 -0
- package/reference-apps/policy-governed-signing/role-policy-matrix.md +62 -0
- package/reference-apps/policy-governed-signing/scenario.md +113 -0
- package/skills/tide-diagnostics/SKILL.md +99 -0
- package/skills/tide-integration/SKILL.md +136 -0
- package/skills/tide-learning-capture/SKILL.md +174 -0
- package/skills/tide-rbac-and-e2ee/SKILL.md +214 -0
- package/skills/tide-reviewer/SKILL.md +133 -0
- package/skills/tide-route-and-api-protection/SKILL.md +201 -0
- package/skills/tide-scenario-resolver/SKILL.md +81 -0
- package/skills/tide-setup/SKILL.md +218 -0
- package/skills/tide-solutions-architect/SKILL.md +130 -0
|
@@ -0,0 +1,1494 @@
|
|
|
1
|
+
# Tide Troubleshooting Guide
|
|
2
|
+
|
|
3
|
+
Symptom-led debugging for common Tide implementation failures. Each entry includes diagnostic steps, fixes, and verification.
|
|
4
|
+
|
|
5
|
+
**Format**: Symptom → Possible Causes → Diagnostics → Fix → Verification
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## T-01: Login Hangs Indefinitely
|
|
10
|
+
|
|
11
|
+
**Symptom**: User clicks login, redirects to TideCloak, then hangs with spinner. No error visible. Browser tab never completes loading.
|
|
12
|
+
|
|
13
|
+
**Possible Causes**:
|
|
14
|
+
1. CSP blocks SWE iframe (most common)
|
|
15
|
+
2. SWE domain unreachable
|
|
16
|
+
3. Corrupted localStorage auth state
|
|
17
|
+
4. Missing `silent-check-sso.html`
|
|
18
|
+
|
|
19
|
+
**Diagnostics**:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# 1. Check browser console for CSP violations
|
|
23
|
+
# Open DevTools → Console
|
|
24
|
+
# Look for: "Refused to frame 'https://...' because it violates the following Content Security Policy directive"
|
|
25
|
+
|
|
26
|
+
# 2. Check CSP headers
|
|
27
|
+
curl -I https://app.example.com | grep -i content-security-policy
|
|
28
|
+
|
|
29
|
+
# 3. Check silent-check-sso.html exists
|
|
30
|
+
curl https://app.example.com/silent-check-sso.html
|
|
31
|
+
# Should return HTML with postMessage script
|
|
32
|
+
|
|
33
|
+
# 4. Check localStorage for stale state
|
|
34
|
+
# DevTools → Application → Local Storage
|
|
35
|
+
# Look for keys containing 'iam', 'auth', 'kc-', 'tc-'
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
**Fix**:
|
|
39
|
+
|
|
40
|
+
For CSP violation (Cause 1):
|
|
41
|
+
```typescript
|
|
42
|
+
// Next.js: next.config.js
|
|
43
|
+
module.exports = {
|
|
44
|
+
async headers() {
|
|
45
|
+
return [{
|
|
46
|
+
source: '/:path*',
|
|
47
|
+
headers: [{
|
|
48
|
+
key: 'Content-Security-Policy',
|
|
49
|
+
value: "frame-src 'self' *"
|
|
50
|
+
}]
|
|
51
|
+
}]
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Express: server/index.ts
|
|
56
|
+
import helmet from 'helmet';
|
|
57
|
+
app.use(helmet({
|
|
58
|
+
contentSecurityPolicy: {
|
|
59
|
+
directives: {
|
|
60
|
+
frameSrc: ["'self'", '*']
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}));
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
For corrupted state (Cause 3):
|
|
67
|
+
```typescript
|
|
68
|
+
// Add reset handler in app initialization
|
|
69
|
+
const params = new URLSearchParams(window.location.search);
|
|
70
|
+
if (params.get('reset') === 'true') {
|
|
71
|
+
Object.keys(localStorage).forEach(key => {
|
|
72
|
+
if (key.includes('iam') || key.includes('auth') || key.startsWith('kc-') || key.startsWith('tc-')) {
|
|
73
|
+
localStorage.removeItem(key);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
window.location.href = '/';
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
For missing silent SSO file (Cause 4):
|
|
81
|
+
```html
|
|
82
|
+
<!-- Create public/silent-check-sso.html -->
|
|
83
|
+
<html>
|
|
84
|
+
<body>
|
|
85
|
+
<script>
|
|
86
|
+
parent.postMessage(location.href, location.origin);
|
|
87
|
+
</script>
|
|
88
|
+
</body>
|
|
89
|
+
</html>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
**Verification**:
|
|
93
|
+
```bash
|
|
94
|
+
# Check CSP includes Tide domains
|
|
95
|
+
curl -I https://app.example.com | grep frame-src
|
|
96
|
+
# Should include frame-src '*' (required for ORK re-homing)
|
|
97
|
+
|
|
98
|
+
# Test login flow completes
|
|
99
|
+
# Login should redirect to TideCloak and back within 5-10 seconds
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**Related**: Anti-Pattern AP-07, Invariants I-06, I-07
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## T-02: JWT Verification Fails (401 Unauthorized)
|
|
107
|
+
|
|
108
|
+
**Symptom**: API calls return 401 Unauthorized despite successful login. Token appears valid in DevTools but server rejects it.
|
|
109
|
+
|
|
110
|
+
**Possible Causes**:
|
|
111
|
+
1. Adapter JSON missing `jwk` field (IGA not enabled or wrong export)
|
|
112
|
+
2. Using `createRemoteJWKSet` instead of embedded JWKS
|
|
113
|
+
3. Wrong `issuer` or `audience` in verification
|
|
114
|
+
4. Token expired (clock skew)
|
|
115
|
+
5. Generic Keycloak adapter instead of Tide adapter
|
|
116
|
+
|
|
117
|
+
**Diagnostics**:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
# 1. Check adapter JSON has Tide extensions
|
|
121
|
+
jq 'has("jwk") and has("vendorId") and has("homeOrkUrl")' data/tidecloak.json
|
|
122
|
+
# Should output: true
|
|
123
|
+
|
|
124
|
+
jq '.jwk.keys | length' data/tidecloak.json
|
|
125
|
+
# Should output: 1 or more
|
|
126
|
+
|
|
127
|
+
# 2. Check JWT verification uses local JWKS only
|
|
128
|
+
grep -r "createLocalJWKSet\|config.jwk" lib/auth/ src/lib/auth/
|
|
129
|
+
# Should find local JWKS usage
|
|
130
|
+
|
|
131
|
+
# 2b. Check code does NOT use remote JWKS (forbidden for this pack)
|
|
132
|
+
grep -r "createRemoteJWKSet" lib/auth/ src/lib/auth/
|
|
133
|
+
# Should find ZERO matches. Any match is AP-01.
|
|
134
|
+
|
|
135
|
+
# 3. Decode token and check claims
|
|
136
|
+
# Copy token from browser DevTools → Network → request headers
|
|
137
|
+
echo "YOUR_TOKEN_HERE" | base64 -d | jq .
|
|
138
|
+
# Check iss, azp, exp, iat
|
|
139
|
+
|
|
140
|
+
# 4. Check server issuer/audience config
|
|
141
|
+
grep -r "issuer.*audience" server/lib/auth/
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**Fix**:
|
|
145
|
+
|
|
146
|
+
For missing Tide adapter (Cause 5):
|
|
147
|
+
```bash
|
|
148
|
+
# Re-export adapter with Tide extensions
|
|
149
|
+
curl -X GET \
|
|
150
|
+
"${TIDECLOAK_URL}/admin/realms/${REALM}/clients/${CLIENT_ID}/vendorResources/get-installations-provider?providerId=keycloak-oidc-keycloak-json" \
|
|
151
|
+
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
|
|
152
|
+
> data/tidecloak.json
|
|
153
|
+
|
|
154
|
+
# Verify Tide fields present
|
|
155
|
+
jq '.jwk, .vendorId, .homeOrkUrl' data/tidecloak.json
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
For wrong JWKS source (Cause 2):
|
|
159
|
+
```typescript
|
|
160
|
+
// ❌ WRONG: remote JWKS (forbidden in this pack)
|
|
161
|
+
const JWKS = createRemoteJWKSet(new URL(`${issuer}/protocol/openid-connect/certs`));
|
|
162
|
+
|
|
163
|
+
// ❌ ALSO WRONG: remote as fallback
|
|
164
|
+
// Do not add createRemoteJWKSet as a fallback. Fix the adapter export instead.
|
|
165
|
+
|
|
166
|
+
// ✅ CORRECT: local JWKS only, fail if missing
|
|
167
|
+
import { loadTideConfig } from './tidecloakConfig';
|
|
168
|
+
const config = loadTideConfig();
|
|
169
|
+
if (!config.jwk) {
|
|
170
|
+
throw new Error('Missing jwk in tidecloak.json. Re-export adapter with IGA enabled.');
|
|
171
|
+
}
|
|
172
|
+
const JWKS = createLocalJWKSet(config.jwk);
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
For missing `jwk` field (Cause 1):
|
|
176
|
+
```bash
|
|
177
|
+
# Re-export adapter with Tide extensions (IGA must be enabled)
|
|
178
|
+
curl -X GET \
|
|
179
|
+
"${TIDECLOAK_URL}/admin/realms/${REALM}/clients/${CLIENT_ID}/vendorResources/get-installations-provider?providerId=keycloak-oidc-keycloak-json" \
|
|
180
|
+
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
|
|
181
|
+
> data/tidecloak.json
|
|
182
|
+
|
|
183
|
+
# Verify jwk is present
|
|
184
|
+
jq '.jwk.keys | length' data/tidecloak.json
|
|
185
|
+
# If 0 or error: IGA is not enabled on the realm. Enable it first.
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
For wrong issuer/audience (Cause 3):
|
|
189
|
+
```typescript
|
|
190
|
+
const { payload } = await jwtVerify(token, JWKS, {
|
|
191
|
+
issuer: `${config['auth-server-url'].replace(/\/+$/, '')}/realms/${config.realm}`, // Must match token iss
|
|
192
|
+
});
|
|
193
|
+
// TideCloak uses azp (not aud) for client ID. aud is typically "account".
|
|
194
|
+
if (payload.azp !== config.resource) throw new Error('Token azp mismatch');
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
**Verification**:
|
|
198
|
+
```bash
|
|
199
|
+
# Test API call with valid token
|
|
200
|
+
TOKEN=$(curl -s ... | jq -r '.access_token')
|
|
201
|
+
curl -H "Authorization: Bearer $TOKEN" https://app.example.com/api/protected
|
|
202
|
+
# Should return 200, not 401
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
**Related**: Anti-Pattern AP-01, AP-13, Invariants I-03, I-04
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## T-03: E2EE Operations Timeout
|
|
210
|
+
|
|
211
|
+
**Symptom**: `doEncrypt()` or `doDecrypt()` calls hang and eventually timeout. No error message or generic timeout error.
|
|
212
|
+
|
|
213
|
+
**Possible Causes**:
|
|
214
|
+
1. User missing E2EE role (`_tide_<tag>.selfencrypt` or `_tide_<tag>.selfdecrypt`)
|
|
215
|
+
2. Fabric connectivity issues (homeOrkUrl unreachable)
|
|
216
|
+
3. SWE iframe blocked (CSP issue)
|
|
217
|
+
4. Tag mismatch (encrypt with 'ssn', decrypt with 'SSN')
|
|
218
|
+
5. Offline (E2EE requires online Fabric access)
|
|
219
|
+
|
|
220
|
+
**Diagnostics**:
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
# 1. Check user has E2EE roles
|
|
224
|
+
curl "${TIDECLOAK_URL}/admin/realms/${REALM}/users/${USER_ID}/role-mappings/realm" | \
|
|
225
|
+
jq '.[] | select(.name | startswith("_tide_"))'
|
|
226
|
+
# Should include _tide_<tag>.selfencrypt and _tide_<tag>.selfdecrypt
|
|
227
|
+
|
|
228
|
+
# 2. Check Fabric connectivity
|
|
229
|
+
jq -r '.homeOrkUrl' data/tidecloak.json
|
|
230
|
+
# Test ORK endpoint
|
|
231
|
+
curl https://sork1.tideprotocol.com/health # or homeOrkUrl from adapter
|
|
232
|
+
|
|
233
|
+
# 3. Check browser console for E2EE errors
|
|
234
|
+
# DevTools → Console
|
|
235
|
+
# Look for: "Failed to load resource", "CORS error", "Timeout"
|
|
236
|
+
|
|
237
|
+
# 4. Check browser network tab for ORK requests
|
|
238
|
+
# DevTools → Network → Filter by tideprotocol.com
|
|
239
|
+
# Should see requests to ORK endpoints during encrypt/decrypt
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
**Fix**:
|
|
243
|
+
|
|
244
|
+
For missing roles (Cause 1):
|
|
245
|
+
```bash
|
|
246
|
+
# Create E2EE roles in TideCloak admin console
|
|
247
|
+
# Roles → Create Role
|
|
248
|
+
# Name: _tide_ssn.selfencrypt, _tide_ssn.selfdecrypt
|
|
249
|
+
|
|
250
|
+
# Assign to user via IGA
|
|
251
|
+
# Users → {user} → Role Mappings → Assign role
|
|
252
|
+
# (Creates change-set requiring quorum approval if IGA enabled)
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
For Fabric connectivity (Cause 2):
|
|
256
|
+
```bash
|
|
257
|
+
# Check firewall allows outbound to Tide ORKs
|
|
258
|
+
# Check homeOrkUrl is correct
|
|
259
|
+
jq -r '.homeOrkUrl' data/tidecloak.json
|
|
260
|
+
|
|
261
|
+
# Test from client environment
|
|
262
|
+
curl -I https://sork1.tideprotocol.com
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
For CSP issue (Cause 3):
|
|
266
|
+
```typescript
|
|
267
|
+
// Add Tide domains to CSP (see T-01)
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
For offline (Cause 5):
|
|
271
|
+
```typescript
|
|
272
|
+
// E2EE requires online access; no offline fallback
|
|
273
|
+
if (!navigator.onLine) {
|
|
274
|
+
throw new Error('E2EE requires online connection to Fabric');
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
**Verification**:
|
|
279
|
+
```javascript
|
|
280
|
+
// Test encrypt/decrypt round-trip
|
|
281
|
+
const plaintext = 'test data';
|
|
282
|
+
const ciphertext = await iam.doEncrypt('test', plaintext);
|
|
283
|
+
console.log('Encrypted:', ciphertext); // Should complete within 2-5 seconds
|
|
284
|
+
|
|
285
|
+
const decrypted = await iam.doDecrypt('test', ciphertext);
|
|
286
|
+
console.log('Decrypted:', decrypted); // Should match plaintext
|
|
287
|
+
assert(decrypted === plaintext);
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
**Related**: Anti-Pattern AP-04, Invariants I-11, [Concepts](concepts.md#threshold-e2ee-hermetic-e2ee)
|
|
291
|
+
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
## T-04: Silent Token Refresh Fails
|
|
295
|
+
|
|
296
|
+
**Symptom**: User forced to re-login every 10 minutes (or token expiration). No silent refresh. Session appears to expire prematurely.
|
|
297
|
+
|
|
298
|
+
**Possible Causes**:
|
|
299
|
+
1. Missing `silent-check-sso.html`
|
|
300
|
+
2. CSP blocks silent SSO iframe
|
|
301
|
+
3. Corrupted localStorage auth state
|
|
302
|
+
4. Silent SSO URL misconfigured in SDK init
|
|
303
|
+
|
|
304
|
+
**Diagnostics**:
|
|
305
|
+
|
|
306
|
+
```bash
|
|
307
|
+
# 1. Check silent-check-sso.html exists and is accessible
|
|
308
|
+
curl https://app.example.com/silent-check-sso.html
|
|
309
|
+
# Should return HTML with postMessage script
|
|
310
|
+
|
|
311
|
+
# 2. Check browser console during refresh
|
|
312
|
+
# Look for: 404 on silent-check-sso.html, CSP violation, postMessage errors
|
|
313
|
+
|
|
314
|
+
# 3. Check SDK init includes silentCheckSsoRedirectUri
|
|
315
|
+
grep -r "silentCheckSsoRedirectUri" src/ client/
|
|
316
|
+
# Should be set to {origin}/silent-check-sso.html
|
|
317
|
+
|
|
318
|
+
# 4. Check browser network tab around token expiration
|
|
319
|
+
# DevTools → Network → around 10-min mark
|
|
320
|
+
# Should see iframe request to TideCloak → redirect to silent-check-sso.html
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
**Fix**:
|
|
324
|
+
|
|
325
|
+
For missing file (Cause 1):
|
|
326
|
+
```html
|
|
327
|
+
<!-- Create public/silent-check-sso.html -->
|
|
328
|
+
<html>
|
|
329
|
+
<body>
|
|
330
|
+
<script>
|
|
331
|
+
parent.postMessage(location.href, location.origin);
|
|
332
|
+
</script>
|
|
333
|
+
</body>
|
|
334
|
+
</html>
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
For SDK config (Cause 4) — pass the full adapter JSON from `tidecloak.json`, not destructured fields (AP-52):
|
|
338
|
+
```typescript
|
|
339
|
+
const config = await fetch('/api/config').then(r => r.json()); // full tidecloak.json
|
|
340
|
+
await iam.initIAM({
|
|
341
|
+
...config,
|
|
342
|
+
silentCheckSsoRedirectUri: `${window.location.origin}/silent-check-sso.html`,
|
|
343
|
+
});
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
For corrupted state (Cause 3):
|
|
347
|
+
```typescript
|
|
348
|
+
// Implement reset handler (see T-01)
|
|
349
|
+
// Or: localStorage.clear() and reload
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
**Verification**:
|
|
353
|
+
```javascript
|
|
354
|
+
// Test silent refresh
|
|
355
|
+
// 1. Login successfully
|
|
356
|
+
// 2. Wait for token to approach expiration (check DevTools → Application → Local Storage for token exp)
|
|
357
|
+
// 3. Silent refresh should happen automatically without visible redirect
|
|
358
|
+
// 4. Check console for 'authRefreshSuccess' event
|
|
359
|
+
|
|
360
|
+
iam.on('authRefreshSuccess', () => {
|
|
361
|
+
console.log('Silent refresh succeeded');
|
|
362
|
+
});
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
**Related**: Anti-Pattern AP-14, AP-15, Invariants I-07
|
|
366
|
+
|
|
367
|
+
---
|
|
368
|
+
|
|
369
|
+
## T-05: DPoP Verification Fails (401 or 403)
|
|
370
|
+
|
|
371
|
+
**Symptom**: API calls return 401 or 403 despite valid JWT. Error message mentions DPoP or token binding.
|
|
372
|
+
|
|
373
|
+
**Possible Causes**:
|
|
374
|
+
1. DPoP header missing or malformed
|
|
375
|
+
2. DPoP proof signature verification fails
|
|
376
|
+
3. `htm` or `htu` mismatch (method or URL wrong)
|
|
377
|
+
4. `iat` timestamp out of freshness window (>120s)
|
|
378
|
+
5. `jti` replayed (duplicate nonce)
|
|
379
|
+
6. `cnf.jkt` mismatch (token not bound to proof)
|
|
380
|
+
|
|
381
|
+
**Diagnostics**:
|
|
382
|
+
|
|
383
|
+
```bash
|
|
384
|
+
# 1. Check request includes DPoP header
|
|
385
|
+
# DevTools → Network → request → Headers
|
|
386
|
+
# Should have: DPoP: eyJ...
|
|
387
|
+
|
|
388
|
+
# 2. Decode DPoP proof
|
|
389
|
+
echo "DPOP_HEADER_VALUE" | base64 -d | jq .
|
|
390
|
+
# Check: typ="dpop+jwt", alg="ES256" or "EdDSA", htm, htu, iat, jti, jwk
|
|
391
|
+
|
|
392
|
+
# 3. Check server DPoP verification logic
|
|
393
|
+
grep -r "verifyDPoP\|DPoP.*htm\|DPoP.*htu" server/
|
|
394
|
+
|
|
395
|
+
# 4. Check server logs for DPoP error details
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
**Fix**:
|
|
399
|
+
|
|
400
|
+
For missing header (Cause 1):
|
|
401
|
+
```typescript
|
|
402
|
+
// Use SDK's secureFetch (automatically adds DPoP)
|
|
403
|
+
// PREREQUISITE: useDPoP must be configured on TideCloakProvider.
|
|
404
|
+
// Without useDPoP, secureFetch does not attach valid DPoP headers and requests fail with 401.
|
|
405
|
+
const response = await iam.secureFetch('/api/protected');
|
|
406
|
+
|
|
407
|
+
// Or manually add DPoP if not using SDK
|
|
408
|
+
const dpopProof = await generateDPoPProof('GET', '/api/protected');
|
|
409
|
+
const response = await fetch('/api/protected', {
|
|
410
|
+
headers: {
|
|
411
|
+
'Authorization': `DPoP ${token}`,
|
|
412
|
+
'DPoP': dpopProof
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
For htm/htu mismatch (Cause 3):
|
|
418
|
+
```typescript
|
|
419
|
+
// Ensure DPoP proof matches request
|
|
420
|
+
const method = 'POST'; // Must match actual request method
|
|
421
|
+
const url = 'https://app.example.com/api/users'; // Must match actual URL (no query string)
|
|
422
|
+
|
|
423
|
+
const dpopProof = await generateDPoPProof(method, url);
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
For iat freshness (Cause 4):
|
|
427
|
+
```typescript
|
|
428
|
+
// Generate fresh proof per request; do not cache
|
|
429
|
+
// Check server and client clocks are synchronized (NTP)
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
For jti replay (Cause 5):
|
|
433
|
+
```typescript
|
|
434
|
+
// Do not reuse DPoP proofs across requests
|
|
435
|
+
// Each request needs fresh proof with unique jti
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
For cnf.jkt mismatch (Cause 6):
|
|
439
|
+
```typescript
|
|
440
|
+
// Token and DPoP proof must use same key pair
|
|
441
|
+
// If token renewed, generate new DPoP proofs
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
**Verification**:
|
|
445
|
+
```bash
|
|
446
|
+
# Test DPoP request
|
|
447
|
+
TOKEN="..."
|
|
448
|
+
DPOP="..."
|
|
449
|
+
curl -H "Authorization: DPoP $TOKEN" -H "DPoP: $DPOP" https://app.example.com/api/protected
|
|
450
|
+
# Should return 200, not 401/403
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
**Related**: Anti-Pattern AP-06, AP-12, Invariants I-12
|
|
454
|
+
|
|
455
|
+
---
|
|
456
|
+
|
|
457
|
+
## T-06: IGA Changes Don't Apply
|
|
458
|
+
|
|
459
|
+
**Symptom**: Admin creates user or modifies role, but change doesn't take effect. No error visible.
|
|
460
|
+
|
|
461
|
+
**Possible Causes**:
|
|
462
|
+
1. IGA not enabled on realm
|
|
463
|
+
2. Change-set created but not approved
|
|
464
|
+
3. Change-set approved but not committed
|
|
465
|
+
4. Quorum not reached
|
|
466
|
+
|
|
467
|
+
**Possible Causes** (added):
|
|
468
|
+
5. Token not refreshed after commit — roles take up to 120s to appear in doken
|
|
469
|
+
|
|
470
|
+
**Diagnostics**:
|
|
471
|
+
|
|
472
|
+
```bash
|
|
473
|
+
# 1. Check IGA is enabled
|
|
474
|
+
curl "${TIDECLOAK_URL}/admin/realms/${REALM}" | jq '.igaEnabled // "not enabled"'
|
|
475
|
+
# Should output: true
|
|
476
|
+
|
|
477
|
+
# 2. Check pending change-sets
|
|
478
|
+
curl "${TIDECLOAK_URL}/admin/realms/${REALM}/tide-admin/change-set/all/requests" \
|
|
479
|
+
-H "Authorization: Bearer ${ADMIN_TOKEN}" | jq '.'
|
|
480
|
+
# Look for change-sets in 'PENDING' or 'APPROVED' status
|
|
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}
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
**Fix**:
|
|
489
|
+
|
|
490
|
+
For IGA not enabled (Cause 1):
|
|
491
|
+
```bash
|
|
492
|
+
# Enable IGA
|
|
493
|
+
curl -X POST "${TIDECLOAK_URL}/admin/realms/${REALM}/tide-admin/toggle-iga" \
|
|
494
|
+
-H "Authorization: Bearer ${ADMIN_TOKEN}"
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
For change-set not approved (Cause 2):
|
|
498
|
+
```bash
|
|
499
|
+
# Sign (approve) the change-set
|
|
500
|
+
curl -X POST "${TIDECLOAK_URL}/admin/realms/${REALM}/tide-admin/change-set/sign/batch" \
|
|
501
|
+
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
|
|
502
|
+
-H "Content-Type: application/json" \
|
|
503
|
+
-d '{"changeSets":[{"changeSetId":"DRAFT_RECORD_ID","changeSetType":"USER_ROLE","actionType":"CREATE"}]}'
|
|
504
|
+
|
|
505
|
+
# If MultiAdmin: response has requiresApprovalPopup=true — complete enclave signing
|
|
506
|
+
# Repeat with different admin tokens until quorum reached
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
For change-set not committed (Cause 3):
|
|
510
|
+
```bash
|
|
511
|
+
# Commit after quorum approval
|
|
512
|
+
curl -X POST "${TIDECLOAK_URL}/admin/realms/${REALM}/tide-admin/change-set/commit/batch" \
|
|
513
|
+
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
|
|
514
|
+
-H "Content-Type: application/json" \
|
|
515
|
+
-d '{"changeSets":[{"changeSetId":"DRAFT_RECORD_ID","changeSetType":"USER_ROLE","actionType":"CREATE"}]}'
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
For token not refreshed (Cause 5):
|
|
519
|
+
```
|
|
520
|
+
User must refresh token (logout/login or forceUpdateToken()) after commit.
|
|
521
|
+
New roles may take up to 120 seconds to appear in doken.
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
**Verification**:
|
|
525
|
+
```bash
|
|
526
|
+
# After commit, check change applied
|
|
527
|
+
# For user creation:
|
|
528
|
+
curl "${TIDECLOAK_URL}/admin/realms/${REALM}/users?username=${NEW_USERNAME}" | jq '.'
|
|
529
|
+
# User should exist
|
|
530
|
+
|
|
531
|
+
# For role assignment:
|
|
532
|
+
curl "${TIDECLOAK_URL}/admin/realms/${REALM}/users/${USER_ID}/role-mappings/realm" | jq '.[] | .name'
|
|
533
|
+
# Role should be in list
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
**Related**: Anti-Pattern AP-05, AP-10, Invariants I-10
|
|
537
|
+
|
|
538
|
+
---
|
|
539
|
+
|
|
540
|
+
## T-07: Account Linking Stuck
|
|
541
|
+
|
|
542
|
+
**Symptom**: New admin user clicks account linking URL, completes linking in browser, but backend poll never detects `tideUserKey` or `vuid`.
|
|
543
|
+
|
|
544
|
+
**Possible Causes**:
|
|
545
|
+
1. Linking URL expired (default 12h lifespan)
|
|
546
|
+
2. Poll timeout too short
|
|
547
|
+
3. Wrong user ID in poll
|
|
548
|
+
4. TideCloak/Fabric connectivity issue during linking
|
|
549
|
+
|
|
550
|
+
**Diagnostics**:
|
|
551
|
+
|
|
552
|
+
```bash
|
|
553
|
+
# 1. Check linking URL was generated correctly
|
|
554
|
+
# Look for lifespan parameter
|
|
555
|
+
echo $LINK_URL | grep -o "lifespan=[0-9]*"
|
|
556
|
+
# Default: 43200 (12 hours)
|
|
557
|
+
|
|
558
|
+
# 2. Check user attributes
|
|
559
|
+
curl "${TIDECLOAK_URL}/admin/realms/${REALM}/users/${USER_ID}" | \
|
|
560
|
+
jq '.attributes.tideUserKey, .attributes.vuid'
|
|
561
|
+
# Should show values after linking completes
|
|
562
|
+
|
|
563
|
+
# 3. Check poll logic
|
|
564
|
+
grep -A 10 "poll.*tideUserKey" script/
|
|
565
|
+
# Verify poll interval and timeout
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
**Fix**:
|
|
569
|
+
|
|
570
|
+
For expired URL (Cause 1):
|
|
571
|
+
```bash
|
|
572
|
+
# Generate new linking URL with longer lifespan
|
|
573
|
+
curl -X POST \
|
|
574
|
+
"${TIDECLOAK_URL}/admin/realms/${REALM}/tideAdminResources/get-required-action-link?userId=${USER_ID}&lifespan=86400" \
|
|
575
|
+
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
|
|
576
|
+
-H "Content-Type: application/json" \
|
|
577
|
+
-d '["link-tide-account-action"]'
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
For poll timeout (Cause 2):
|
|
581
|
+
```bash
|
|
582
|
+
# Increase poll timeout in init script
|
|
583
|
+
# Example from keylessh:
|
|
584
|
+
for i in {1..60}; do # Poll for up to 5 minutes (60 * 5s = 300s)
|
|
585
|
+
USER_KEY=$(curl -s ... | jq -r '.attributes.tideUserKey[0]')
|
|
586
|
+
if [ -n "$USER_KEY" ]; then
|
|
587
|
+
echo "Linked successfully"
|
|
588
|
+
break
|
|
589
|
+
fi
|
|
590
|
+
sleep 5
|
|
591
|
+
done
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
**Verification**:
|
|
595
|
+
```bash
|
|
596
|
+
# After linking, verify attributes
|
|
597
|
+
curl "${TIDECLOAK_URL}/admin/realms/${REALM}/users/${USER_ID}" | \
|
|
598
|
+
jq '.attributes | {tideUserKey, vuid}'
|
|
599
|
+
# Both should be non-null
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
**Related**: [Concepts](concepts.md#account-linking-onboarding), GAP-030
|
|
603
|
+
|
|
604
|
+
---
|
|
605
|
+
|
|
606
|
+
## T-08: Forseti Signing Fails
|
|
607
|
+
|
|
608
|
+
**Symptom**: `executeSignRequest()` throws error or rejects. Contract evaluation fails.
|
|
609
|
+
|
|
610
|
+
**Possible Causes**:
|
|
611
|
+
1. Contract compilation error (C# syntax)
|
|
612
|
+
2. Doken missing or expired
|
|
613
|
+
3. Challenge data format mismatch
|
|
614
|
+
4. ORK majority rejected policy
|
|
615
|
+
5. Gas limit exceeded (undocumented)
|
|
616
|
+
|
|
617
|
+
**Diagnostics**:
|
|
618
|
+
|
|
619
|
+
```bash
|
|
620
|
+
# 1. Check contract syntax
|
|
621
|
+
# Copy C# contract code and validate locally or via online compiler
|
|
622
|
+
|
|
623
|
+
# 2. Check doken is present
|
|
624
|
+
console.log(tc.doken);
|
|
625
|
+
# Should output JWT-shaped string, not null/undefined
|
|
626
|
+
|
|
627
|
+
# 3. Check browser console for Forseti errors
|
|
628
|
+
# DevTools → Console
|
|
629
|
+
# Look for: "Contract compilation failed", "Policy rejected", "Timeout"
|
|
630
|
+
|
|
631
|
+
# 4. Check challengeData structure matches contract expectations
|
|
632
|
+
console.log(signRequest.challengeData);
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
**Fix**:
|
|
636
|
+
|
|
637
|
+
For contract error (Cause 1):
|
|
638
|
+
```typescript
|
|
639
|
+
// Validate C# syntax
|
|
640
|
+
const contract = `
|
|
641
|
+
using System;
|
|
642
|
+
public class Policy {
|
|
643
|
+
public static bool Evaluate(dynamic challenge) {
|
|
644
|
+
// Ensure valid C# syntax
|
|
645
|
+
return challenge.user == "admin";
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
`;
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
For missing doken (Cause 2):
|
|
652
|
+
```typescript
|
|
653
|
+
// Ensure user is authenticated before creating request
|
|
654
|
+
if (!tc.doken) {
|
|
655
|
+
await iam.doLogin({ redirectUri: window.location.origin });
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
const signRequest = await tc.createTideRequest({
|
|
659
|
+
authorizer: tc.doken, // Should be present after login
|
|
660
|
+
...
|
|
661
|
+
});
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
For policy rejection (Cause 4):
|
|
665
|
+
```typescript
|
|
666
|
+
// Review contract logic; ORKs may be correctly rejecting based on policy
|
|
667
|
+
// Example: User lacks required role, destination is blocked, etc.
|
|
668
|
+
// Check challengeData matches policy expectations
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
**Verification**:
|
|
672
|
+
```typescript
|
|
673
|
+
// Test successful signing
|
|
674
|
+
const signRequest = await tc.createTideRequest({
|
|
675
|
+
contract: simplePolicyContract,
|
|
676
|
+
modelName: 'BasicCustom<TEST>:BasicCustom<1>',
|
|
677
|
+
authFlow: 'Policy:1',
|
|
678
|
+
authorizer: tc.doken,
|
|
679
|
+
challengeData: { test: true }
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
const result = await tc.executeSignRequest(signRequest);
|
|
683
|
+
console.log('Signature:', result.signature); // Should be present
|
|
684
|
+
```
|
|
685
|
+
|
|
686
|
+
**Related**: [Concepts](concepts.md#forseti-policy-engine), Invariants I-15, GAP-007, GAP-008, GAP-018
|
|
687
|
+
|
|
688
|
+
---
|
|
689
|
+
|
|
690
|
+
## T-09: Token Expired Errors Despite Recent Login
|
|
691
|
+
|
|
692
|
+
**Symptom**: User logs in successfully, but API calls return "Token expired" within minutes. Token lifetime appears much shorter than expected.
|
|
693
|
+
|
|
694
|
+
**Possible Causes**:
|
|
695
|
+
1. Server and client clock skew
|
|
696
|
+
2. Token lifetime misconfigured in realm
|
|
697
|
+
3. Refresh logic not working (see T-04)
|
|
698
|
+
4. Token verification using wrong timezone
|
|
699
|
+
|
|
700
|
+
**Diagnostics**:
|
|
701
|
+
|
|
702
|
+
```bash
|
|
703
|
+
# 1. Check token claims
|
|
704
|
+
# Extract token from browser, decode
|
|
705
|
+
echo "TOKEN" | base64 -d | jq '.iat, .exp'
|
|
706
|
+
# iat = issued-at timestamp, exp = expiration timestamp
|
|
707
|
+
|
|
708
|
+
# Compare to current time
|
|
709
|
+
date +%s
|
|
710
|
+
# Should be between iat and exp
|
|
711
|
+
|
|
712
|
+
# 2. Check realm token lifetime
|
|
713
|
+
curl "${TIDECLOAK_URL}/admin/realms/${REALM}" | jq '.accessTokenLifespan'
|
|
714
|
+
# keylessh default: 600 (10 minutes)
|
|
715
|
+
|
|
716
|
+
# 3. Check server clock
|
|
717
|
+
date -u
|
|
718
|
+
# Should match client clock within ~60 seconds
|
|
719
|
+
```
|
|
720
|
+
|
|
721
|
+
**Fix**:
|
|
722
|
+
|
|
723
|
+
For clock skew (Cause 1):
|
|
724
|
+
```bash
|
|
725
|
+
# Sync server clock with NTP
|
|
726
|
+
sudo ntpdate -s time.nist.gov
|
|
727
|
+
|
|
728
|
+
# Or use systemd-timesyncd
|
|
729
|
+
sudo timedatectl set-ntp true
|
|
730
|
+
```
|
|
731
|
+
|
|
732
|
+
For token lifetime (Cause 2):
|
|
733
|
+
```bash
|
|
734
|
+
# Adjust realm token lifetime if needed
|
|
735
|
+
curl -X PUT "${TIDECLOAK_URL}/admin/realms/${REALM}" \
|
|
736
|
+
-H "Content-Type: application/json" \
|
|
737
|
+
-d '{"accessTokenLifespan": 1800}' # 30 minutes
|
|
738
|
+
|
|
739
|
+
# Note: keylessh uses 600s; adjust based on app needs
|
|
740
|
+
```
|
|
741
|
+
|
|
742
|
+
For refresh logic (Cause 3):
|
|
743
|
+
```typescript
|
|
744
|
+
// See T-04 (Silent Token Refresh Fails)
|
|
745
|
+
```
|
|
746
|
+
|
|
747
|
+
**Verification**:
|
|
748
|
+
```bash
|
|
749
|
+
# Login and check token lifespan
|
|
750
|
+
TOKEN=$(curl -s ... | jq -r '.access_token')
|
|
751
|
+
echo $TOKEN | base64 -d | jq '.exp - .iat'
|
|
752
|
+
# Should output token lifetime in seconds (e.g., 600 for 10 min)
|
|
753
|
+
```
|
|
754
|
+
|
|
755
|
+
**Related**: T-04, [Concepts](concepts.md#required-roles-and-configuration)
|
|
756
|
+
|
|
757
|
+
---
|
|
758
|
+
|
|
759
|
+
## T-10: Realm Setup Fails
|
|
760
|
+
|
|
761
|
+
**Symptom**: Realm initialization via `setUpTideRealm` endpoint fails or realm missing Tide configuration.
|
|
762
|
+
|
|
763
|
+
**Possible Causes**:
|
|
764
|
+
1. Endpoint not available (wrong TideCloak version)
|
|
765
|
+
2. Missing request body fields (email, terms)
|
|
766
|
+
3. IGA toggle fails
|
|
767
|
+
4. Protocol mappers not created
|
|
768
|
+
5. `tidebrowser` flow not created
|
|
769
|
+
|
|
770
|
+
**Diagnostics**:
|
|
771
|
+
|
|
772
|
+
```bash
|
|
773
|
+
# 1. Check TideCloak version supports Tide endpoints
|
|
774
|
+
curl "${TIDECLOAK_URL}/admin/realms/${REALM}/vendorResources/setUpTideRealm" \
|
|
775
|
+
-X POST -I
|
|
776
|
+
# Should return 200 or 400, not 404
|
|
777
|
+
|
|
778
|
+
# 2. Check request body (must be form-urlencoded, NOT JSON)
|
|
779
|
+
curl -X POST "${TIDECLOAK_URL}/admin/realms/${REALM}/vendorResources/setUpTideRealm" \
|
|
780
|
+
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
|
|
781
|
+
-H "Content-Type: application/x-www-form-urlencoded" \
|
|
782
|
+
-d "email=admin@example.com&isRagnarokEnabled=true"
|
|
783
|
+
|
|
784
|
+
# 3. Check realm configuration after setup
|
|
785
|
+
curl "${TIDECLOAK_URL}/admin/realms/${REALM}" | jq '.browserFlow, .defaultRoles'
|
|
786
|
+
# browserFlow should be 'tidebrowser'
|
|
787
|
+
# defaultRoles should include '_tide_enabled'
|
|
788
|
+
|
|
789
|
+
# 4. Check protocol mappers
|
|
790
|
+
curl "${TIDECLOAK_URL}/admin/realms/${REALM}/client-scopes" | \
|
|
791
|
+
jq '.[] | select(.name=="roles") | .protocolMappers[] | select(.name | contains("tide"))'
|
|
792
|
+
```
|
|
793
|
+
|
|
794
|
+
**Fix**:
|
|
795
|
+
|
|
796
|
+
For endpoint unavailable (Cause 1):
|
|
797
|
+
```bash
|
|
798
|
+
# Verify TideCloak version
|
|
799
|
+
curl "${TIDECLOAK_URL}/admin/serverinfo" | jq '.systemInfo.version'
|
|
800
|
+
# Should be TideCloak version, not vanilla Keycloak
|
|
801
|
+
```
|
|
802
|
+
|
|
803
|
+
For manual realm configuration:
|
|
804
|
+
```json
|
|
805
|
+
// Import realm JSON with Tide defaults (based on keylessh realm.json)
|
|
806
|
+
{
|
|
807
|
+
"realm": "myrealm",
|
|
808
|
+
"browserFlow": "tidebrowser",
|
|
809
|
+
"defaultRoles": ["_tide_enabled", "offline_access", "uma_authorization"],
|
|
810
|
+
"accessTokenLifespan": 600,
|
|
811
|
+
"ssoSessionIdleTimeout": 1800,
|
|
812
|
+
"ssoSessionMaxLifespan": 36000,
|
|
813
|
+
"roles": {
|
|
814
|
+
"realm": [
|
|
815
|
+
{"name": "_tide_enabled"}
|
|
816
|
+
]
|
|
817
|
+
},
|
|
818
|
+
"protocolMappers": [
|
|
819
|
+
{
|
|
820
|
+
"name": "Tide User Key Mapper",
|
|
821
|
+
"protocol": "openid-connect",
|
|
822
|
+
"protocolMapper": "oidc-usermodel-attribute-mapper",
|
|
823
|
+
"config": {
|
|
824
|
+
"user.attribute": "tideUserKey",
|
|
825
|
+
"claim.name": "tideuserkey",
|
|
826
|
+
"jsonType.label": "String",
|
|
827
|
+
"id.token.claim": "true",
|
|
828
|
+
"access.token.claim": "true"
|
|
829
|
+
}
|
|
830
|
+
},
|
|
831
|
+
{
|
|
832
|
+
"name": "VUID Mapper",
|
|
833
|
+
"protocol": "openid-connect",
|
|
834
|
+
"protocolMapper": "oidc-usermodel-attribute-mapper",
|
|
835
|
+
"config": {
|
|
836
|
+
"user.attribute": "vuid",
|
|
837
|
+
"claim.name": "vuid",
|
|
838
|
+
"jsonType.label": "String",
|
|
839
|
+
"id.token.claim": "true",
|
|
840
|
+
"access.token.claim": "true"
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
]
|
|
844
|
+
}
|
|
845
|
+
```
|
|
846
|
+
|
|
847
|
+
**Verification**:
|
|
848
|
+
```bash
|
|
849
|
+
# Check realm has Tide defaults
|
|
850
|
+
curl "${TIDECLOAK_URL}/admin/realms/${REALM}" | \
|
|
851
|
+
jq '{browserFlow, defaultRoles, accessTokenLifespan}'
|
|
852
|
+
# Should match Tide configuration
|
|
853
|
+
```
|
|
854
|
+
|
|
855
|
+
**Related**: [Concepts](concepts.md#realm-initialization), Invariants I-14
|
|
856
|
+
|
|
857
|
+
---
|
|
858
|
+
|
|
859
|
+
## T-10b: Init Script Fails with "Invalid json representation for RoleRepresentation"
|
|
860
|
+
|
|
861
|
+
**Symptom**: `npm run init` (or `bootstrap-tidecloak.sh`) fails with:
|
|
862
|
+
```
|
|
863
|
+
{"error":"Invalid json representation for RoleRepresentation. Unrecognized field \"error\" at line 1 column 12."}
|
|
864
|
+
```
|
|
865
|
+
|
|
866
|
+
**Cause**: The script tries to assign `tide-realm-admin` to the admin user before the user creation change request is approved. With IGA enabled, the user is not queryable until committed. The role lookup returns `{"error":"..."}`, which is then passed as the body of the role mapping POST.
|
|
867
|
+
|
|
868
|
+
**Diagnostics**:
|
|
869
|
+
```bash
|
|
870
|
+
# Check if user exists
|
|
871
|
+
TOKEN="$(get_token)"
|
|
872
|
+
curl -s "$TIDECLOAK_URL/admin/realms/$REALM_NAME/users?username=admin" \
|
|
873
|
+
-H "Authorization: Bearer $TOKEN" | jq '.[0].id'
|
|
874
|
+
# If null: user change request was not approved
|
|
875
|
+
|
|
876
|
+
# Check pending user change requests
|
|
877
|
+
curl -s "$TIDECLOAK_URL/admin/realms/$REALM_NAME/tide-admin/change-set/users/requests" \
|
|
878
|
+
-H "Authorization: Bearer $TOKEN" | jq length
|
|
879
|
+
# If > 0: approve them first
|
|
880
|
+
```
|
|
881
|
+
|
|
882
|
+
**Fix**: Approve user creation change request between user creation and role assignment:
|
|
883
|
+
```bash
|
|
884
|
+
# After creating user, BEFORE looking up user ID:
|
|
885
|
+
sleep 2
|
|
886
|
+
approve_and_commit users
|
|
887
|
+
|
|
888
|
+
# Then proceed with user ID lookup and role assignment
|
|
889
|
+
```
|
|
890
|
+
|
|
891
|
+
Also validate the role lookup result before using it:
|
|
892
|
+
```bash
|
|
893
|
+
ROLE_JSON=$(curl -s "$TIDECLOAK_URL/admin/realms/$REALM_NAME/clients/$CLIENT_UUID/roles/tide-realm-admin" \
|
|
894
|
+
-H "Authorization: Bearer $TOKEN")
|
|
895
|
+
if echo "$ROLE_JSON" | jq -e '.error' > /dev/null 2>&1; then
|
|
896
|
+
echo "ERROR: tide-realm-admin role not found: $ROLE_JSON"
|
|
897
|
+
exit 1
|
|
898
|
+
fi
|
|
899
|
+
```
|
|
900
|
+
|
|
901
|
+
**Verification**: Re-run `npm run init` after fixing. The role assignment step should succeed without errors.
|
|
902
|
+
|
|
903
|
+
**Related**: [initialize-admin-and-link-account](../playbooks/initialize-admin-and-link-account.md) Step 1b
|
|
904
|
+
|
|
905
|
+
---
|
|
906
|
+
|
|
907
|
+
## T-11: Adapter JSON Export Fails
|
|
908
|
+
|
|
909
|
+
**Symptom**: Adapter JSON downloaded from TideCloak missing `jwk`, `vendorId`, or `homeOrkUrl` fields.
|
|
910
|
+
|
|
911
|
+
**Possible Causes**:
|
|
912
|
+
1. Using generic Keycloak adapter endpoint instead of Tide endpoint
|
|
913
|
+
2. Wrong `providerId` parameter
|
|
914
|
+
3. Client not properly configured in TideCloak
|
|
915
|
+
|
|
916
|
+
**Diagnostics**:
|
|
917
|
+
|
|
918
|
+
```bash
|
|
919
|
+
# 1. Check adapter JSON contents
|
|
920
|
+
jq 'keys' data/tidecloak.json
|
|
921
|
+
# Should include: jwk, vendorId, homeOrkUrl
|
|
922
|
+
|
|
923
|
+
jq 'has("jwk") and has("vendorId") and has("homeOrkUrl")' data/tidecloak.json
|
|
924
|
+
# Should output: true
|
|
925
|
+
|
|
926
|
+
# 2. Check export endpoint used
|
|
927
|
+
# Correct endpoint:
|
|
928
|
+
# GET /admin/realms/{realm}/clients/{client-id}/vendorResources/get-installations-provider?providerId=keycloak-oidc-keycloak-json
|
|
929
|
+
```
|
|
930
|
+
|
|
931
|
+
**Fix**:
|
|
932
|
+
|
|
933
|
+
```bash
|
|
934
|
+
# Use correct Tide adapter export endpoint
|
|
935
|
+
curl -X GET \
|
|
936
|
+
"${TIDECLOAK_URL}/admin/realms/${REALM}/clients/${CLIENT_ID}/vendorResources/get-installations-provider?providerId=keycloak-oidc-keycloak-json" \
|
|
937
|
+
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
|
|
938
|
+
-o data/tidecloak.json
|
|
939
|
+
|
|
940
|
+
# Verify Tide extensions present
|
|
941
|
+
jq '{jwk: (.jwk.keys | length), vendorId, homeOrkUrl}' data/tidecloak.json
|
|
942
|
+
# Should show all three fields
|
|
943
|
+
```
|
|
944
|
+
|
|
945
|
+
**Verification**:
|
|
946
|
+
```bash
|
|
947
|
+
# Validate adapter JSON structure
|
|
948
|
+
jq '
|
|
949
|
+
if (has("jwk") and has("vendorId") and has("homeOrkUrl")) then
|
|
950
|
+
"Valid Tide adapter"
|
|
951
|
+
else
|
|
952
|
+
"Invalid: missing Tide extensions"
|
|
953
|
+
end
|
|
954
|
+
' data/tidecloak.json
|
|
955
|
+
```
|
|
956
|
+
|
|
957
|
+
**Related**: Anti-Pattern AP-13, Invariants I-05, I-13
|
|
958
|
+
|
|
959
|
+
---
|
|
960
|
+
|
|
961
|
+
## T-12: Policy Signing / Forseti Errors
|
|
962
|
+
|
|
963
|
+
**Symptom**: ORK rejects with "Index out of range", "Policy supplied has not been signed", "Policy refers to wrong contract", or similar errors during policy operations.
|
|
964
|
+
|
|
965
|
+
**Possible Causes**:
|
|
966
|
+
1. Destructuring `{ ApprovalType, ExecutionType }` from `Models.Policy` instead of `Models` (`Models.Policy` is the Policy class, not a namespace)
|
|
967
|
+
2. Building `PolicySignRequest` manually with `BaseTideRequest` + `TideMemory` instead of using `PolicySignRequest.New(policy)` from `heimdall-tide`
|
|
968
|
+
3. Not decoding `createTideRequest` result via `BaseTideRequest.decode()` before passing to approval
|
|
969
|
+
4. Calling `addPolicy(policy.toBytes())` during `BaseTideRequest` construction (attaching unsigned policy)
|
|
970
|
+
5. Using `modelId: "any"` instead of specific IDs like `["PolicyEnabledEncryption:1", "PolicyEnabledDecryption:1"]`
|
|
971
|
+
6. Admin policy bytes are base64 text passed as raw bytes instead of decoded
|
|
972
|
+
7. Contract hash mismatch (`contractId` doesn't match deployed contract source)
|
|
973
|
+
8. Contract hash is lowercase but ORK stores uppercase (case-sensitive comparison)
|
|
974
|
+
|
|
975
|
+
**Diagnostics**:
|
|
976
|
+
|
|
977
|
+
```bash
|
|
978
|
+
# 1. Check imports are correct
|
|
979
|
+
grep -n "Models.Policy" src/
|
|
980
|
+
# If you see: const { ApprovalType } = Models.Policy → WRONG
|
|
981
|
+
# Should be: const { Policy, ApprovalType, ExecutionType, BaseTideRequest } = Models;
|
|
982
|
+
|
|
983
|
+
# 2. Check PolicySignRequest source
|
|
984
|
+
grep -n "PolicySignRequest" src/
|
|
985
|
+
# Should use: PolicySignRequest.New(policy) from 'heimdall-tide'
|
|
986
|
+
# NOT: manual BaseTideRequest + TideMemory construction
|
|
987
|
+
|
|
988
|
+
# 3. Check BaseTideRequest.decode is called after createTideRequest
|
|
989
|
+
grep -A 2 "createTideRequest" src/
|
|
990
|
+
# Should see: BaseTideRequest.decode(await tc.createTideRequest(...))
|
|
991
|
+
|
|
992
|
+
# 4. Check addPolicy is NOT called before approval
|
|
993
|
+
grep -B 5 "addPolicy" src/
|
|
994
|
+
# addPolicy should appear AFTER requestTideOperatorApproval, not before
|
|
995
|
+
|
|
996
|
+
# 5. Check modelId in Policy constructor
|
|
997
|
+
grep -A 5 "new Policy" src/
|
|
998
|
+
# Should see: modelId: ["PolicyEnabledEncryption:1", "PolicyEnabledDecryption:1"]
|
|
999
|
+
# NOT: modelId: "any"
|
|
1000
|
+
|
|
1001
|
+
# 6. Check admin policy bytes are properly decoded
|
|
1002
|
+
grep -A 3 "adminPolicy\|policyBytes" src/
|
|
1003
|
+
# If bytes start with [65, 81, 65, 65, ...] (ASCII for "AQAA..."),
|
|
1004
|
+
# you are passing base64 text as byte values instead of decoding
|
|
1005
|
+
```
|
|
1006
|
+
|
|
1007
|
+
**Fix**:
|
|
1008
|
+
|
|
1009
|
+
For wrong imports (Cause 1):
|
|
1010
|
+
```typescript
|
|
1011
|
+
// ❌ WRONG — Models.Policy is the class itself, not a namespace
|
|
1012
|
+
const { ApprovalType, ExecutionType } = Models.Policy;
|
|
1013
|
+
// Results in: Cannot read properties of undefined (reading 'IMPLICIT')
|
|
1014
|
+
|
|
1015
|
+
// ✅ CORRECT
|
|
1016
|
+
const { Models } = await import("@tideorg/js");
|
|
1017
|
+
const { Policy, ApprovalType, ExecutionType, BaseTideRequest } = Models;
|
|
1018
|
+
```
|
|
1019
|
+
|
|
1020
|
+
For manual request construction (Cause 2):
|
|
1021
|
+
```typescript
|
|
1022
|
+
// ❌ WRONG — manual construction produces bytes ORK cannot parse
|
|
1023
|
+
const request = new BaseTideRequest();
|
|
1024
|
+
request.setDraft(TideMemory.CreateFromArray([policy.toBytes(), contractBytes]));
|
|
1025
|
+
|
|
1026
|
+
// ✅ CORRECT — use PolicySignRequest.New from heimdall-tide
|
|
1027
|
+
const { PolicySignRequest } = await import("heimdall-tide");
|
|
1028
|
+
const policyRequest = PolicySignRequest.New(policy);
|
|
1029
|
+
policyRequest.addForsetiContractToUpload(CONTRACT_SOURCE);
|
|
1030
|
+
policyRequest.setCustomExpiry(604800);
|
|
1031
|
+
```
|
|
1032
|
+
|
|
1033
|
+
For missing decode (Cause 3):
|
|
1034
|
+
```typescript
|
|
1035
|
+
// ❌ WRONG — passing raw bytes without decode
|
|
1036
|
+
const raw = await tc.createTideRequest(policyRequest.encode());
|
|
1037
|
+
const approvals = await tc.requestTideOperatorApproval([
|
|
1038
|
+
{ id: "policy", request: raw } // raw bytes cause serialization errors
|
|
1039
|
+
]);
|
|
1040
|
+
|
|
1041
|
+
// ✅ CORRECT — decode then re-encode
|
|
1042
|
+
const initialized = BaseTideRequest.decode(
|
|
1043
|
+
await tc.createTideRequest(policyRequest.encode())
|
|
1044
|
+
);
|
|
1045
|
+
const approvals = await tc.requestTideOperatorApproval([
|
|
1046
|
+
{ id: "policy", request: initialized.encode() }
|
|
1047
|
+
]);
|
|
1048
|
+
```
|
|
1049
|
+
|
|
1050
|
+
For premature addPolicy (Cause 4):
|
|
1051
|
+
```typescript
|
|
1052
|
+
// ❌ WRONG — attaching unsigned policy before approval
|
|
1053
|
+
tideRequest.addPolicy(policy.toBytes());
|
|
1054
|
+
const result = await tc.createTideRequest(tideRequest.encode());
|
|
1055
|
+
|
|
1056
|
+
// ✅ CORRECT — attach signed admin policy AFTER approval
|
|
1057
|
+
const approved = BaseTideRequest.decode(approvals[0].request);
|
|
1058
|
+
const adminPolicyBytes = await fetchAdminPolicyFromTideCloak(token);
|
|
1059
|
+
approved.addPolicy(adminPolicyBytes);
|
|
1060
|
+
const signatures = await tc.executeSignRequest(approved.encode(), true);
|
|
1061
|
+
```
|
|
1062
|
+
|
|
1063
|
+
For wrong modelId (Cause 5):
|
|
1064
|
+
```typescript
|
|
1065
|
+
// ❌ WRONG
|
|
1066
|
+
const policy = new Policy({
|
|
1067
|
+
modelId: "any", // ORK fails with "Index out of range" in Policy.From
|
|
1068
|
+
// ...
|
|
1069
|
+
});
|
|
1070
|
+
|
|
1071
|
+
// ✅ CORRECT
|
|
1072
|
+
const policy = new Policy({
|
|
1073
|
+
modelId: ["PolicyEnabledEncryption:1", "PolicyEnabledDecryption:1"],
|
|
1074
|
+
// ...
|
|
1075
|
+
});
|
|
1076
|
+
```
|
|
1077
|
+
|
|
1078
|
+
For base64 admin policy bytes (Cause 6):
|
|
1079
|
+
```typescript
|
|
1080
|
+
// ❌ WRONG — passing base64 string characters as byte values
|
|
1081
|
+
const policyBytes = new TextEncoder().encode(base64String);
|
|
1082
|
+
// Results in bytes like [65, 81, 65, 65, ...] (ASCII for "AQAA...")
|
|
1083
|
+
|
|
1084
|
+
// ✅ CORRECT — decode base64 to actual bytes
|
|
1085
|
+
const policyBytes = Uint8Array.from(atob(base64String), c => c.charCodeAt(0));
|
|
1086
|
+
```
|
|
1087
|
+
|
|
1088
|
+
**Verification**:
|
|
1089
|
+
```typescript
|
|
1090
|
+
// Test the full policy signing flow
|
|
1091
|
+
const { Models } = await import("@tideorg/js");
|
|
1092
|
+
const { Policy, ApprovalType, ExecutionType, BaseTideRequest } = Models;
|
|
1093
|
+
const { PolicySignRequest } = await import("heimdall-tide");
|
|
1094
|
+
|
|
1095
|
+
const policy = new Policy({
|
|
1096
|
+
version: "3",
|
|
1097
|
+
contractId: contractHash,
|
|
1098
|
+
modelId: ["PolicyEnabledEncryption:1", "PolicyEnabledDecryption:1"],
|
|
1099
|
+
keyId: vendorId,
|
|
1100
|
+
approvalType: ApprovalType.IMPLICIT,
|
|
1101
|
+
executionType: ExecutionType.EXECUTE_ON_APPROVE,
|
|
1102
|
+
params: new Map([["Role", roleName]]),
|
|
1103
|
+
});
|
|
1104
|
+
|
|
1105
|
+
const policyRequest = PolicySignRequest.New(policy);
|
|
1106
|
+
policyRequest.addForsetiContractToUpload(CONTRACT_SOURCE);
|
|
1107
|
+
const initialized = BaseTideRequest.decode(
|
|
1108
|
+
await tc.createTideRequest(policyRequest.encode())
|
|
1109
|
+
);
|
|
1110
|
+
// Should not throw "Index out of range" or other serialization errors
|
|
1111
|
+
|
|
1112
|
+
const approvals = await tc.requestTideOperatorApproval([
|
|
1113
|
+
{ id: "enc-policy", request: initialized.encode() }
|
|
1114
|
+
]);
|
|
1115
|
+
console.log('Approval status:', approvals[0].status);
|
|
1116
|
+
// Should be "approved", not an error
|
|
1117
|
+
```
|
|
1118
|
+
|
|
1119
|
+
**Anti-pattern**: Do NOT try to debug ORK byte serialization errors by inspecting raw bytes. Instead, copy the exact working pattern from the forseti-crypto-quickstart reference implementation. If the flow deviates from the 5-step pattern (create policy, create request, approve via popup, attach admin policy, execute), start over from the reference.
|
|
1120
|
+
|
|
1121
|
+
**Related**: T-08, Anti-Pattern AP-04, Invariants I-15, [Concepts](concepts.md#forseti-policy-engine)
|
|
1122
|
+
|
|
1123
|
+
---
|
|
1124
|
+
|
|
1125
|
+
## T-15: Package-Boundary Import Failure (Models/PolicySignRequest from Wrong Package)
|
|
1126
|
+
|
|
1127
|
+
**Symptom**: `Cannot destructure property 'Policy' of 'Models' as it is undefined` or similar destructuring failures when setting up policy signing.
|
|
1128
|
+
|
|
1129
|
+
**Root Cause**: `Models`, `PolicySignRequest`, `BaseTideRequest`, `ApprovalType`, and `ExecutionType` were imported from `@tidecloak/nextjs`. That package is the Next.js hooks/provider layer. It does NOT export these symbols. They are `undefined` at runtime.
|
|
1130
|
+
|
|
1131
|
+
**Diagnostics**:
|
|
1132
|
+
|
|
1133
|
+
```bash
|
|
1134
|
+
# Check for wrong import source
|
|
1135
|
+
grep -n "@tidecloak/nextjs" src/ | grep -i "Models\|PolicySignRequest\|BaseTideRequest\|ApprovalType\|ExecutionType"
|
|
1136
|
+
# ANY match = wrong package
|
|
1137
|
+
|
|
1138
|
+
# Check dynamic imports
|
|
1139
|
+
grep -n 'import("@tidecloak/nextjs")' src/
|
|
1140
|
+
# If this appears in policy-signing code, it's the problem
|
|
1141
|
+
```
|
|
1142
|
+
|
|
1143
|
+
**Fix**: Import `Models` from `@tideorg/js` and `PolicySignRequest` from `heimdall-tide`:
|
|
1144
|
+
|
|
1145
|
+
```typescript
|
|
1146
|
+
// ❌ WRONG — @tidecloak/nextjs does not export these
|
|
1147
|
+
const { Models, PolicySignRequest } = await import("@tidecloak/nextjs");
|
|
1148
|
+
|
|
1149
|
+
// ✅ CORRECT — use the source packages
|
|
1150
|
+
const { Models } = await import("@tideorg/js");
|
|
1151
|
+
const { PolicySignRequest } = await import("heimdall-tide");
|
|
1152
|
+
const { Policy, ApprovalType, ExecutionType, BaseTideRequest } = Models;
|
|
1153
|
+
```
|
|
1154
|
+
|
|
1155
|
+
**Package-boundary rule**: `@tidecloak/nextjs` exports `TideCloakProvider`, `useTideCloak`, `useAuthCallback`, guard components. It does NOT export `Models`, `PolicySignRequest`, or any policy/model helpers. Those come from `@tideorg/js` and `heimdall-tide`.
|
|
1156
|
+
|
|
1157
|
+
**Related**: [Concepts: SDK Internals](concepts.md#sdk-internals)
|
|
1158
|
+
|
|
1159
|
+
---
|
|
1160
|
+
|
|
1161
|
+
## T-16: `IAMService.createTideRequest is not a function`
|
|
1162
|
+
|
|
1163
|
+
**Symptom**: `IAMService.createTideRequest is not a function` (or same for `requestTideOperatorApproval`, `executeSignRequest`) during policy signing.
|
|
1164
|
+
|
|
1165
|
+
**Root Cause**: `createTideRequest`, `requestTideOperatorApproval`, and `executeSignRequest` are methods on the underlying **TideCloak** instance, not on `IAMService` directly. The TideCloak instance is accessible at `IAMService._tc`.
|
|
1166
|
+
|
|
1167
|
+
**Diagnostics**:
|
|
1168
|
+
|
|
1169
|
+
```bash
|
|
1170
|
+
# Check for direct IAMService calls that should go through _tc
|
|
1171
|
+
grep -n "IAMService\.createTideRequest\|IAMService\.requestTideOperatorApproval\|IAMService\.executeSignRequest" src/
|
|
1172
|
+
# ANY match = wrong call target
|
|
1173
|
+
```
|
|
1174
|
+
|
|
1175
|
+
**Fix**:
|
|
1176
|
+
|
|
1177
|
+
```typescript
|
|
1178
|
+
// ❌ WRONG — these methods do not exist on IAMService
|
|
1179
|
+
await IAMService.createTideRequest(request);
|
|
1180
|
+
await IAMService.requestTideOperatorApproval(requests);
|
|
1181
|
+
await IAMService.executeSignRequest(request, true);
|
|
1182
|
+
|
|
1183
|
+
// ✅ CORRECT — access the underlying TideCloak instance via _tc
|
|
1184
|
+
const tc = IAMService._tc;
|
|
1185
|
+
await tc.createTideRequest(request);
|
|
1186
|
+
await tc.requestTideOperatorApproval(requests);
|
|
1187
|
+
await tc.executeSignRequest(request, true);
|
|
1188
|
+
```
|
|
1189
|
+
|
|
1190
|
+
**Method locations**:
|
|
1191
|
+
|
|
1192
|
+
| Method | Lives on | NOT on |
|
|
1193
|
+
|--------|----------|--------|
|
|
1194
|
+
| `createTideRequest()` | `IAMService._tc` (TideCloak) | `IAMService` |
|
|
1195
|
+
| `requestTideOperatorApproval()` | `IAMService._tc` (TideCloak) | `IAMService` |
|
|
1196
|
+
| `executeSignRequest()` | `IAMService._tc` (TideCloak) | `IAMService` |
|
|
1197
|
+
| `doEncrypt()` / `doDecrypt()` | `IAMService` | — |
|
|
1198
|
+
| `getToken()` / `getUserInfo()` | `IAMService` | — |
|
|
1199
|
+
|
|
1200
|
+
**Related**: [Playbook: setup-forseti-e2ee](../playbooks/setup-forseti-e2ee.md), [Concepts: SDK Internals](concepts.md#sdk-internals)
|
|
1201
|
+
|
|
1202
|
+
---
|
|
1203
|
+
|
|
1204
|
+
## Common Error Messages
|
|
1205
|
+
|
|
1206
|
+
| Error | Cause |
|
|
1207
|
+
|-------|-------|
|
|
1208
|
+
| "Policy supplied has not been signed" | Admin policy not attached in step 3, or policy bytes are unsigned |
|
|
1209
|
+
| "Policy refers to wrong contract" | contractId hash doesn't match deployed contract source. Hash comparison is case-sensitive — ORK stores uppercase hex. Use `.toUpperCase()` if computing locally, or fetch `contractHash` from the API. |
|
|
1210
|
+
| "Model does not have a policy passed with it" | `addPolicy()` was skipped or admin policy bytes are null |
|
|
1211
|
+
| "Index out of range" in `Policy.From` | Admin policy bytes are base64 text passed as raw bytes instead of decoded |
|
|
1212
|
+
| "IAccessPolicy could not be found" | Contract missing `using Ork.Forseti.Sdk;` |
|
|
1213
|
+
| "vendordecrypt voucher not allowed" | User missing `_tide_{tag}.selfdecrypt` role |
|
|
1214
|
+
| "Missing role 'group-member:...'" | Doken doesn't have the role, need logout/login or `forceUpdateToken()` after IGA approval |
|
|
1215
|
+
| "Unexpected token '<'" on config load | `configFilePath` used instead of `configUrl`, or `tidecloak.json` not in `public/` |
|
|
1216
|
+
| "TideCloak client not initialized" | `login()` called before SDK finished initializing, check `isInitializing` before guards |
|
|
1217
|
+
| `requiresApprovalPopup: true` | Backend cannot auto-approve, must open approval enclave in admin's browser |
|
|
1218
|
+
| `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
|
+
| `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` is CJS, use defensive interop pattern |
|
|
1221
|
+
| "Could not create tide realm" (email is null) | `setUpTideRealm` expects `Content-Type: application/x-www-form-urlencoded` with `email`, not JSON |
|
|
1222
|
+
| "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
|
+
| AccessDeniedException on H2 | Data directory mounted as project root or wrong permissions; mount `./data` subdirectory and `chown 1000:1000` |
|
|
1224
|
+
| `"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. |
|
|
1225
|
+
| `"[TIDECLOAK] No doken found"` | Missing delegated token for encryption. Retryable — re-auth may fix. |
|
|
1226
|
+
| `"enclave.networkFailure"` | ORK unreachable. Retryable — transient network issue. |
|
|
1227
|
+
| `"enclave.throttled"` | Rate limited by ORK. Retryable — backoff and retry. |
|
|
1228
|
+
| `"enclave.thresholdTimeoutFailure"` | Not enough ORK responses before timeout. Retryable — transient. |
|
|
1229
|
+
| `"Mismatch between session key private and Doken session key public"` | Session key mismatch. Fatal — re-login required. |
|
|
1230
|
+
| `"DPoP is set to strict mode but the server does not advertise DPoP support"` | DPoP strict mode enabled but server lacks `dpop_signing_alg_values_supported`. Fatal — fix server config or DPoP mode. |
|
|
1231
|
+
| `"DPoP requires IndexedDB for secure key storage"` | IndexedDB unavailable in browser environment. Fatal — environment limitation. |
|
|
1232
|
+
| `NetworkError` (with `.response`) | Server responded with invalid HTTP status. Retryable — transient. Only custom error class in SDK. |
|
|
1233
|
+
|
|
1234
|
+
---
|
|
1235
|
+
|
|
1236
|
+
## T-13: Self-Encryption Fails — Do Not Rename Roles
|
|
1237
|
+
|
|
1238
|
+
**Symptom**: `doEncrypt` or `doDecrypt` fails with "User has not been given any access" or similar. Agent considers renaming `_tide_{tag}.selfencrypt` to `_tide_{tag}.encrypt`.
|
|
1239
|
+
|
|
1240
|
+
**Root cause**: This is a self-encryption setup problem, not a signal to switch encryption models.
|
|
1241
|
+
|
|
1242
|
+
**Diagnostics**:
|
|
1243
|
+
```bash
|
|
1244
|
+
# 1. Check self-encryption roles exist
|
|
1245
|
+
curl -s "$TIDECLOAK_URL/admin/realms/$REALM/roles" \
|
|
1246
|
+
-H "Authorization: Bearer $TOKEN" | jq '.[].name' | grep '_tide_'
|
|
1247
|
+
|
|
1248
|
+
# 2. Check roles are in default composite
|
|
1249
|
+
curl -s "$TIDECLOAK_URL/admin/realms/$REALM/roles/default-roles-$REALM" \
|
|
1250
|
+
-H "Authorization: Bearer $TOKEN" | jq '.composites.realm'
|
|
1251
|
+
|
|
1252
|
+
# 3. Check IGA change requests are approved
|
|
1253
|
+
curl -s "$TIDECLOAK_URL/admin/realms/$REALM/tide-admin/change-set/roles/requests" \
|
|
1254
|
+
-H "Authorization: Bearer $TOKEN" | jq length
|
|
1255
|
+
# Should be 0. If > 0, approve and commit.
|
|
1256
|
+
|
|
1257
|
+
# 4. Check user's JWT has the roles
|
|
1258
|
+
# Decode token and check realm_access.roles
|
|
1259
|
+
```
|
|
1260
|
+
|
|
1261
|
+
**Fix**: Fix the self-encryption setup. Do NOT rename roles.
|
|
1262
|
+
|
|
1263
|
+
1. Create missing roles: `_tide_{tag}.selfencrypt`, `_tide_{tag}.selfdecrypt`
|
|
1264
|
+
2. Add to default composite
|
|
1265
|
+
3. Approve IGA change requests
|
|
1266
|
+
4. User must re-login (roles propagate on next token refresh, up to 120s)
|
|
1267
|
+
|
|
1268
|
+
**Forbidden**: Renaming `selfencrypt` to `encrypt` or `selfdecrypt` to `decrypt`. The role suffix does not change the encryption model. The SDK call path (`doEncrypt` from `useTideCloak()` vs `IAMService.doEncrypt(data, policyBytes)`) determines self vs shared. See AP-26.
|
|
1269
|
+
|
|
1270
|
+
**When shared encryption IS needed**: If multiple users must decrypt the same ciphertext, this is a different encryption model entirely. Route to playbook `setup-forseti-e2ee`. It requires a Forseti contract, policy signing, and `IAMService.doEncrypt(data, signedPolicyBytes)`.
|
|
1271
|
+
|
|
1272
|
+
---
|
|
1273
|
+
|
|
1274
|
+
## T-14: Policy Commit Fails Due to Missing Admin Policy
|
|
1275
|
+
|
|
1276
|
+
**Symptom**: `executeSignRequest` or policy commit fails with "Policy supplied has not been signed" or "Model does not have a policy passed with it".
|
|
1277
|
+
|
|
1278
|
+
**Root cause**: The admin authorizer policy was not attached to the request before commit.
|
|
1279
|
+
|
|
1280
|
+
**Diagnostics**:
|
|
1281
|
+
```bash
|
|
1282
|
+
# 1. Check admin-policy endpoint is reachable
|
|
1283
|
+
curl -s "${TIDECLOAK_URL}/realms/${REALM}/tide-policy-resources/admin-policy"
|
|
1284
|
+
# Should return base64-encoded bytes, not HTML or error JSON
|
|
1285
|
+
|
|
1286
|
+
# 2. Check URL construction (common: double slash from trailing slash in auth-server-url)
|
|
1287
|
+
echo "${TIDECLOAK_URL}/realms/${REALM}/tide-policy-resources/admin-policy"
|
|
1288
|
+
# Must not contain // between host and path (except after protocol)
|
|
1289
|
+
|
|
1290
|
+
# 3. Check if addPolicy was called AFTER approval, not before
|
|
1291
|
+
grep -B 5 "addPolicy" src/
|
|
1292
|
+
# addPolicy must appear after requestTideOperatorApproval, not during request construction
|
|
1293
|
+
```
|
|
1294
|
+
|
|
1295
|
+
**Fix**:
|
|
1296
|
+
|
|
1297
|
+
1. **Fetch admin policy from the correct endpoint** (no auth required):
|
|
1298
|
+
```
|
|
1299
|
+
GET /realms/{realm}/tide-policy-resources/admin-policy
|
|
1300
|
+
```
|
|
1301
|
+
Not: `GET /admin/realms/{realm}/tide-admin/realm-policy` (returns `{ status: "none" }`).
|
|
1302
|
+
|
|
1303
|
+
2. **Decode base64 response**:
|
|
1304
|
+
```js
|
|
1305
|
+
const b64 = await res.text();
|
|
1306
|
+
const raw = Buffer.from(b64, "base64");
|
|
1307
|
+
const bytes = new Uint8Array(raw);
|
|
1308
|
+
```
|
|
1309
|
+
If bytes start with `[65, 81, 65, 65, ...]` (ASCII for "AQAA"), you are passing base64 text as raw bytes.
|
|
1310
|
+
|
|
1311
|
+
3. **Attach after approval, not during construction**:
|
|
1312
|
+
```js
|
|
1313
|
+
// Step 3 of 5-step flow: AFTER requestTideOperatorApproval
|
|
1314
|
+
const approved = BaseTideRequest.decode(approvalResults[0].request);
|
|
1315
|
+
approved.addPolicy(adminPolicyBytes); // HERE — after approval
|
|
1316
|
+
```
|
|
1317
|
+
|
|
1318
|
+
4. **URL normalization**: Strip trailing slashes from `auth-server-url` before constructing endpoint URLs:
|
|
1319
|
+
```js
|
|
1320
|
+
const baseUrl = config["auth-server-url"].replace(/\/+$/, "");
|
|
1321
|
+
const adminPolicyUrl = `${baseUrl}/realms/${config.realm}/tide-policy-resources/admin-policy`;
|
|
1322
|
+
```
|
|
1323
|
+
|
|
1324
|
+
**This is a bootstrap/policy-flow failure, not a role issue.** Do not attempt to fix by renaming roles or switching encryption models.
|
|
1325
|
+
|
|
1326
|
+
---
|
|
1327
|
+
|
|
1328
|
+
## Common Diagnostic Commands
|
|
1329
|
+
|
|
1330
|
+
### Check Auth State
|
|
1331
|
+
```javascript
|
|
1332
|
+
// Browser console
|
|
1333
|
+
console.log('Authenticated:', iam.authenticated);
|
|
1334
|
+
console.log('User:', await iam.getUserInfo());
|
|
1335
|
+
console.log('Token:', await iam.getToken());
|
|
1336
|
+
console.log('Doken:', tc?.doken);
|
|
1337
|
+
```
|
|
1338
|
+
|
|
1339
|
+
### Check Roles
|
|
1340
|
+
```bash
|
|
1341
|
+
# Server-side
|
|
1342
|
+
curl "${TIDECLOAK_URL}/admin/realms/${REALM}/users/${USER_ID}/role-mappings/realm" | \
|
|
1343
|
+
jq '.[] | .name'
|
|
1344
|
+
```
|
|
1345
|
+
|
|
1346
|
+
### Check Fabric Connectivity
|
|
1347
|
+
```bash
|
|
1348
|
+
# From adapter JSON
|
|
1349
|
+
HOME_ORK=$(jq -r '.homeOrkUrl' data/tidecloak.json)
|
|
1350
|
+
curl -I $HOME_ORK
|
|
1351
|
+
# Should return 200 or 30x
|
|
1352
|
+
```
|
|
1353
|
+
|
|
1354
|
+
### Check CSP Headers
|
|
1355
|
+
```bash
|
|
1356
|
+
curl -I https://app.example.com | grep -i content-security-policy
|
|
1357
|
+
```
|
|
1358
|
+
|
|
1359
|
+
### Check Browser Network Tab
|
|
1360
|
+
```text
|
|
1361
|
+
DevTools → Network → Filter:
|
|
1362
|
+
- "tidecloak" - Auth requests to TideCloak
|
|
1363
|
+
- "tideprotocol.com" - ORK/Fabric requests
|
|
1364
|
+
- "dauth.me" - SWE iframe requests
|
|
1365
|
+
|
|
1366
|
+
Look for: 404s, CORS errors, timeouts
|
|
1367
|
+
```
|
|
1368
|
+
|
|
1369
|
+
### Check Browser Console
|
|
1370
|
+
```text
|
|
1371
|
+
DevTools → Console → Filter errors
|
|
1372
|
+
Common Tide errors:
|
|
1373
|
+
- "CSP violation" → T-01
|
|
1374
|
+
- "Failed to verify JWT" → T-02
|
|
1375
|
+
- "E2EE timeout" → T-03
|
|
1376
|
+
- "DPoP verification failed" → T-05
|
|
1377
|
+
- "Contract compilation failed" → T-08
|
|
1378
|
+
```
|
|
1379
|
+
|
|
1380
|
+
---
|
|
1381
|
+
|
|
1382
|
+
## Symptom Index
|
|
1383
|
+
|
|
1384
|
+
| Symptom | Troubleshooting Entry |
|
|
1385
|
+
|---------|----------------------|
|
|
1386
|
+
| Login hangs indefinitely | T-01 |
|
|
1387
|
+
| 401 Unauthorized on API calls | T-02, T-05 |
|
|
1388
|
+
| E2EE encrypt/decrypt timeouts | T-03 |
|
|
1389
|
+
| Silent refresh fails, frequent re-login | T-04 |
|
|
1390
|
+
| DPoP verification errors | T-05 |
|
|
1391
|
+
| Admin changes don't apply | T-06 |
|
|
1392
|
+
| Account linking stuck | T-07 |
|
|
1393
|
+
| Forseti signing fails | T-08 |
|
|
1394
|
+
| Token expired errors | T-09 |
|
|
1395
|
+
| Realm setup fails | T-10 |
|
|
1396
|
+
| Adapter JSON missing fields | T-11 |
|
|
1397
|
+
| Policy signing / Forseti ORK rejections | T-12 |
|
|
1398
|
+
|
|
1399
|
+
---
|
|
1400
|
+
|
|
1401
|
+
## When to Check Source Material
|
|
1402
|
+
|
|
1403
|
+
If troubleshooting entries don't resolve the issue:
|
|
1404
|
+
|
|
1405
|
+
1. Check [Gap Register](../GAP_REGISTER.md) for known unresolved questions
|
|
1406
|
+
2. Review [Anti-Patterns](anti-patterns.md) for common mistakes
|
|
1407
|
+
3. Verify [Invariants](invariants.md) are not violated
|
|
1408
|
+
4. Consult keylessh exemplar for implementation patterns
|
|
1409
|
+
5. Check TideCloak Admin Console audit trail for event errors
|
|
1410
|
+
|
|
1411
|
+
---
|
|
1412
|
+
|
|
1413
|
+
## Escalation Path
|
|
1414
|
+
|
|
1415
|
+
If issue persists after troubleshooting:
|
|
1416
|
+
|
|
1417
|
+
1. Collect diagnostics:
|
|
1418
|
+
- Browser console errors
|
|
1419
|
+
- Network tab filtered by "tidecloak", "tideprotocol"
|
|
1420
|
+
- Server logs around failure time
|
|
1421
|
+
- Adapter JSON contents (redact sensitive fields)
|
|
1422
|
+
- Realm configuration (defaultRoles, browserFlow, token lifetimes)
|
|
1423
|
+
|
|
1424
|
+
2. Check if gap is documented:
|
|
1425
|
+
- Search [Gap Register](../GAP_REGISTER.md) for similar issue
|
|
1426
|
+
- Review [Assumptions](../notes/assumptions.md) for related uncertainties
|
|
1427
|
+
|
|
1428
|
+
3. Create minimal reproduction:
|
|
1429
|
+
- Simplest code that triggers issue
|
|
1430
|
+
- Environment details (framework, TideCloak version, browser)
|
|
1431
|
+
|
|
1432
|
+
4. Document as new gap if unresolved
|
|
1433
|
+
|
|
1434
|
+
---
|
|
1435
|
+
|
|
1436
|
+
## T-17: pnpm `ERR_PNPM_NO_MATCHING_VERSION` for Tide Packages
|
|
1437
|
+
|
|
1438
|
+
**Symptom**: `pnpm add @tidecloak/react` or `pnpm install` fails with `ERR_PNPM_NO_MATCHING_VERSION`.
|
|
1439
|
+
|
|
1440
|
+
**Root cause**: pnpm's `minimum-release-age` setting in `.npmrc` blocks recently-published packages. Tide's entire dependency tree (`@tidecloak/*`, `heimdall-tide`, `@tideorg/*`) is published on the same cadence and all hit this gate.
|
|
1441
|
+
|
|
1442
|
+
**Fix**: Add exclusions to `.npmrc`:
|
|
1443
|
+
```ini
|
|
1444
|
+
minimum-release-age-exclude[]=@tidecloak/*
|
|
1445
|
+
minimum-release-age-exclude[]=heimdall-tide
|
|
1446
|
+
minimum-release-age-exclude[]=@tideorg/*
|
|
1447
|
+
minimum-release-age-exclude[]=asgard-tide
|
|
1448
|
+
```
|
|
1449
|
+
|
|
1450
|
+
**Verification**: `pnpm add @tidecloak/react` resolves without version errors.
|
|
1451
|
+
|
|
1452
|
+
VERIFIED (TIDE_LEARNINGS-001 L-05).
|
|
1453
|
+
|
|
1454
|
+
---
|
|
1455
|
+
|
|
1456
|
+
## T-18: IGA Change-Set Approval May Require Admin Console UI
|
|
1457
|
+
|
|
1458
|
+
**Symptom**: REST-based `POST /tide-admin/change-set/sign` succeeds but `POST /tide-admin/change-set/commit` fails with "Could not find authorization signature."
|
|
1459
|
+
|
|
1460
|
+
**Root cause**: IGA approval is a cryptographic operation requiring the admin's Tide doken and ORK network interaction. In some configurations, the REST-based sign endpoint auto-signs but the commit requires the browser-based SDK (`approveTideRequests()` via the admin console UI).
|
|
1461
|
+
|
|
1462
|
+
**When this happens**: Typically when the admin has a linked Tide identity and IGA requires the cryptographic round-trip. The bootstrap script's REST-based `approve_and_commit` may work for the initial bootstrap (master admin token) but fail for subsequent operations.
|
|
1463
|
+
|
|
1464
|
+
**Fix**: If REST commit fails, approve and commit change requests from the TideCloak admin console UI: `http://localhost:8080/admin/master/console/#/{realm}`. Navigate to the change request section and approve from the UI.
|
|
1465
|
+
|
|
1466
|
+
**Verification**: After approval, `GET /tide-admin/change-set/counts` returns `total: 0`.
|
|
1467
|
+
|
|
1468
|
+
VERIFIED (TIDE_LEARNINGS-001 L-11).
|
|
1469
|
+
|
|
1470
|
+
---
|
|
1471
|
+
|
|
1472
|
+
## T-19: TideCloak 500 Error Masked as CORS Error in Browser
|
|
1473
|
+
|
|
1474
|
+
**Symptom**: Browser console shows "CORS header 'Access-Control-Allow-Origin' missing" on a TideCloak request (typically the token endpoint). The app appears to have a CORS misconfiguration.
|
|
1475
|
+
|
|
1476
|
+
**Root cause**: TideCloak (Keycloak-based) does not include CORS headers on error responses (especially 500 Internal Server Error). The browser blocks the response due to missing CORS headers and reports it as a CORS error, hiding the real server-side failure.
|
|
1477
|
+
|
|
1478
|
+
**Diagnostics**:
|
|
1479
|
+
1. Open DevTools → Network tab
|
|
1480
|
+
2. Find the failing request to TideCloak
|
|
1481
|
+
3. Check the actual HTTP status code (ignore the console error message)
|
|
1482
|
+
4. If status is 500: the issue is server-side, not CORS
|
|
1483
|
+
5. Check TideCloak container logs: `docker logs <container>` for the real error
|
|
1484
|
+
|
|
1485
|
+
**Common 500 causes**:
|
|
1486
|
+
- ORK PreSign failure (`Provided TideAuthData OR PreviousTokenAuthorization`) — stale realm, recreate it
|
|
1487
|
+
- Database connection failure
|
|
1488
|
+
- Realm misconfiguration after incomplete bootstrap
|
|
1489
|
+
|
|
1490
|
+
**Fix**: Resolve the underlying server-side error. CORS headers will appear on successful responses. Do not add CORS workarounds for what is actually a 500 error.
|
|
1491
|
+
|
|
1492
|
+
**Verification**: After fixing the server error, the request returns 200 with proper CORS headers.
|
|
1493
|
+
|
|
1494
|
+
VERIFIED (LEARNINGS-ratidefy-batch-001 L-15).
|