@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/package.json
CHANGED
|
@@ -1,45 +1,48 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@tideorg/mcp",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "MCP server exposing Tide operational guidance — canon, playbooks, skills, prompts, adapters, and scenarios for AI coding agents",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"bin": {
|
|
7
|
-
"tide-agent-pack": "mcp-server/dist/index.js"
|
|
8
|
-
},
|
|
9
|
-
"scripts": {
|
|
10
|
-
"build": "cd mcp-server && npm run build",
|
|
11
|
-
"
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
"
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
"
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
"
|
|
44
|
-
}
|
|
45
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "@tideorg/mcp",
|
|
3
|
+
"version": "1.9.0",
|
|
4
|
+
"description": "MCP server exposing Tide operational guidance — canon, playbooks, skills, prompts, adapters, and scenarios for AI coding agents",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"tide-agent-pack": "mcp-server/dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "cd mcp-server && npm run build",
|
|
11
|
+
"test": "cd mcp-server && npm test",
|
|
12
|
+
"prepublishOnly": "npm run build"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"mcp-server/dist/",
|
|
16
|
+
"canon/",
|
|
17
|
+
"playbooks/",
|
|
18
|
+
"skills/",
|
|
19
|
+
"prompts/",
|
|
20
|
+
"adapters/",
|
|
21
|
+
"reference-apps/",
|
|
22
|
+
"GAP_REGISTER.md",
|
|
23
|
+
"PRIVACY.md",
|
|
24
|
+
"CHANGELOG.md"
|
|
25
|
+
],
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
28
|
+
"zod": "^3.24.0"
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"mcp",
|
|
32
|
+
"tidecloak",
|
|
33
|
+
"tide",
|
|
34
|
+
"auth",
|
|
35
|
+
"security",
|
|
36
|
+
"threshold-cryptography",
|
|
37
|
+
"ai-agent",
|
|
38
|
+
"coding-agent"
|
|
39
|
+
],
|
|
40
|
+
"license": "MIT",
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "https://github.com/tide-foundation/tide-agent-pack"
|
|
44
|
+
},
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=18"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -298,7 +298,9 @@ export function ProfileButton() {
|
|
|
298
298
|
import { useTideCloak } from '@tidecloak/nextjs';
|
|
299
299
|
|
|
300
300
|
export function ProfileButton() {
|
|
301
|
-
|
|
301
|
+
// useTideCloak() has no `user` object. Read claims via getValueFromIdToken /
|
|
302
|
+
// getValueFromToken (e.g. getValueFromIdToken('preferred_username')).
|
|
303
|
+
const { authenticated, login, logout } = useTideCloak();
|
|
302
304
|
|
|
303
305
|
if (!authenticated) {
|
|
304
306
|
return <button onClick={login}>Login</button>;
|
|
@@ -334,34 +336,37 @@ export async function GET(req) {
|
|
|
334
336
|
}
|
|
335
337
|
```
|
|
336
338
|
|
|
337
|
-
**After** (Tide -
|
|
339
|
+
**After** (Tide - server-side JWT verification):
|
|
338
340
|
|
|
339
|
-
|
|
341
|
+
Tide ships a server-side verification helper. `@tidecloak/nextjs/server` exports `verifyTideCloakToken(config, token, allowedRoles?)` (re-exported from `@tidecloak/verify`). Use it instead of hand-rolling JWKS verification.
|
|
340
342
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
**Quick pattern** (based on keylessh):
|
|
343
|
+
**Quick pattern** (supported helper):
|
|
344
344
|
```typescript
|
|
345
345
|
// app/api/users/route.ts
|
|
346
346
|
import { NextRequest } from 'next/server';
|
|
347
|
-
import {
|
|
347
|
+
import { verifyTideCloakToken } from '@tidecloak/nextjs/server';
|
|
348
|
+
import tcConfig from '../../../data/tidecloak.json';
|
|
348
349
|
|
|
349
350
|
export async function GET(req: NextRequest) {
|
|
350
351
|
const authHeader = req.headers.get('authorization');
|
|
351
352
|
if (!authHeader) {
|
|
352
353
|
return Response.json({ error: 'Unauthorized' }, { status: 401 });
|
|
353
354
|
}
|
|
355
|
+
// Accepts "Bearer <jwt>" or "DPoP <jwt>"
|
|
356
|
+
const token = authHeader.replace(/^(Bearer|DPoP) /, '');
|
|
354
357
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
} catch (err) {
|
|
358
|
+
// Arg order is (config, token, allowedRoles) — config FIRST.
|
|
359
|
+
const payload = await verifyTideCloakToken(tcConfig, token, []);
|
|
360
|
+
if (!payload) {
|
|
359
361
|
return Response.json({ error: 'Invalid token' }, { status: 401 });
|
|
360
362
|
}
|
|
363
|
+
return Response.json({ users: [...] });
|
|
361
364
|
}
|
|
362
365
|
```
|
|
363
366
|
|
|
364
|
-
|
|
367
|
+
`verifyTideCloakToken` verifies the signature against the adapter's embedded `jwk` (local JWKS), checks the issuer/`azp`, and optionally enforces `allowedRoles`. It returns the decoded payload on success and `false` on failure.
|
|
368
|
+
|
|
369
|
+
**If you need the manual version** (or DPoP proof re-verification): see [verify-jwt-server-side.md](verify-jwt-server-side.md), which shows the same checks hand-rolled with `jose` in `lib/auth/tideJWT.ts`.
|
|
365
370
|
|
|
366
371
|
---
|
|
367
372
|
|
|
@@ -390,13 +395,24 @@ export const config = {
|
|
|
390
395
|
};
|
|
391
396
|
```
|
|
392
397
|
|
|
393
|
-
**After** (Tide
|
|
398
|
+
**After** (Tide options):
|
|
399
|
+
|
|
400
|
+
Option A — **use the Tide-provided proxy/middleware helpers.** `@tidecloak/nextjs/server` ships `createTideCloakProxy` (Next.js 16+ `proxy.ts`, Node runtime) and `createTideCloakMiddleware` (Edge `middleware.ts`, still supported). These replace a NextAuth-style middleware export and perform real token verification via `verifyTideCloakToken`:
|
|
401
|
+
```typescript
|
|
402
|
+
// proxy.ts (Next.js 16+) — or middleware.ts on 15 and earlier
|
|
403
|
+
import { createTideCloakProxy } from '@tidecloak/nextjs/server';
|
|
404
|
+
import tcConfig from './data/tidecloak.json';
|
|
394
405
|
|
|
395
|
-
|
|
406
|
+
export default createTideCloakProxy(tcConfig);
|
|
407
|
+
|
|
408
|
+
export const config = {
|
|
409
|
+
matcher: ['/dashboard/:path*', '/admin/:path*'],
|
|
410
|
+
};
|
|
411
|
+
```
|
|
396
412
|
|
|
397
|
-
|
|
413
|
+
Option B — **remove proxy.ts / middleware.ts** and rely on the client-side provider for UX gating plus server-side JWT verification in each API route (Step 8). Do this if you do not want request-interception at all.
|
|
398
414
|
|
|
399
|
-
**Warning**: `proxy.ts`
|
|
415
|
+
**Warning**: A plain, hand-written `proxy.ts` / `middleware.ts` that only checks cookie presence is UI gating only, NOT real authorization. The `createTideCloakProxy` / `createTideCloakMiddleware` helpers above DO verify the token, but you should still verify JWTs inside API routes (Step 8) as the authoritative enforcement point. See [canon/feature-mapping.md](../canon/feature-mapping.md#protected-routes-vs-protected-apis).
|
|
400
416
|
|
|
401
417
|
If you want client-side route guards, implement in component:
|
|
402
418
|
```typescript
|
|
@@ -632,7 +648,7 @@ grep -r "from 'next-auth\|from \"next-auth" . --include="*.tsx" --include="*.ts"
|
|
|
632
648
|
|
|
633
649
|
**Cause**: Old auth proxy or middleware still active.
|
|
634
650
|
|
|
635
|
-
**Fix**: Remove or update `proxy.ts` (or legacy `middleware.ts`). Tide
|
|
651
|
+
**Fix**: Remove or update `proxy.ts` (or legacy `middleware.ts`). Either switch it to Tide's `createTideCloakProxy` / `createTideCloakMiddleware` from `@tidecloak/nextjs/server` (Step 9, Option A), or remove it and rely on client-side route guards plus server-side JWT verification (Step 8).
|
|
636
652
|
|
|
637
653
|
---
|
|
638
654
|
|
|
@@ -83,7 +83,7 @@ ls -la app/ 2>/dev/null && echo "App Router" || echo "Pages Router"
|
|
|
83
83
|
npm install @tidecloak/nextjs
|
|
84
84
|
```
|
|
85
85
|
|
|
86
|
-
**Verify version**:
|
|
86
|
+
**Verify version**: `@tidecloak/*` packages are currently at `0.13.33` (run `npm view @tidecloak/nextjs version` to confirm/pin). Do not assume `1.0.0`.
|
|
87
87
|
|
|
88
88
|
**Next.js 16+ bundler**: Next.js 16 defaults to Turbopack. The Tide SDK requires webpack for `strictExportPresence = false` and `@tidecloak/react` ESM alias. Update `package.json` scripts:
|
|
89
89
|
```json
|
|
@@ -137,13 +137,18 @@ Tide E2EE uses tag-based roles for **self-encryption** (user-bound, private data
|
|
|
137
137
|
// Client-side E2EE with role enforcement (self-encryption)
|
|
138
138
|
const { doEncrypt, doDecrypt } = useTideCloak();
|
|
139
139
|
|
|
140
|
+
// Both take a single argument: an ARRAY of items. Each encrypt item is
|
|
141
|
+
// { data, tags }; the tags drive the _tide_<tag>.self* role check.
|
|
140
142
|
// Requires _tide_ssn.selfencrypt role
|
|
141
|
-
const ciphertext = await doEncrypt(
|
|
143
|
+
const [ciphertext] = await doEncrypt([{ data: '123-45-6789', tags: ['ssn'] }]);
|
|
142
144
|
|
|
145
|
+
// doDecrypt takes the array of encrypted items ({ encrypted, tags }).
|
|
143
146
|
// Requires _tide_ssn.selfdecrypt role
|
|
144
|
-
const plaintext = await doDecrypt('ssn'
|
|
147
|
+
const [plaintext] = await doDecrypt([{ encrypted: ciphertext, tags: ['ssn'] }]);
|
|
145
148
|
```
|
|
146
149
|
|
|
150
|
+
**Signature note**: The `useTideCloak()` `doEncrypt`/`doDecrypt` are single-argument (`data`) wrappers. `data` is an array so you can encrypt/decrypt multiple fields in one call. This matches the shapes in [setup-forseti-e2ee.md](setup-forseti-e2ee.md) and [configure-e2ee-roles-and-policies.md](configure-e2ee-roles-and-policies.md). Do NOT call them with positional `(tag, value)` arguments — that form does not exist.
|
|
151
|
+
|
|
147
152
|
Fabric enforces roles cryptographically. See [canon/concepts.md#tag-based-e2ee-roles](../canon/concepts.md#tag-based-e2ee-roles).
|
|
148
153
|
|
|
149
154
|
**Self-encryption is user-bound**: only the encrypting user can decrypt. Giving another user the `selfdecrypt` role does NOT let them decrypt your data. For shared data between users, use policy-governed VVK encryption with a Forseti contract instead. See [setup-forseti-e2ee.md](setup-forseti-e2ee.md) and [canon/anti-patterns.md AP-24](../canon/anti-patterns.md#ap-24-using-self-encryption-for-shared-data-between-users).
|
|
@@ -79,6 +79,14 @@ This activates the Tide license and generates the VRK. No manual "Manage License
|
|
|
79
79
|
|
|
80
80
|
**Critical:** Content-Type must be `application/x-www-form-urlencoded`. JSON causes failure.
|
|
81
81
|
|
|
82
|
+
**Form parameters** (`setUpTideRealm` on `main`):
|
|
83
|
+
|
|
84
|
+
| Param | Required | Default | Notes |
|
|
85
|
+
|-------|----------|---------|-------|
|
|
86
|
+
| `email` | Yes (for licensing) | — | Used to request the free-tier license. Ignored only when `skipLicense=true`. |
|
|
87
|
+
| `isRagnarokEnabled` | No | `true` | Enables realm offboarding (Ragnarok). The endpoint already defaults it to `true`; passing `isRagnarokEnabled=true` explicitly is harmless and matches the default. |
|
|
88
|
+
| `skipLicense` | No | `false` | When `true`, skips license activation (`email` unused) and returns early. Leave at the default for normal bootstrap. |
|
|
89
|
+
|
|
82
90
|
### Step 5: Enable IGA
|
|
83
91
|
|
|
84
92
|
```bash
|
|
@@ -94,28 +102,35 @@ curl -s -X POST "$TIDECLOAK_URL/admin/realms/$REALM_NAME/tide-admin/toggle-iga"
|
|
|
94
102
|
Realm creation + Tide setup generates change requests that must be approved before proceeding.
|
|
95
103
|
|
|
96
104
|
```bash
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
105
|
+
# Current /iga/change-requests/... surface (replaces legacy /tide-admin/change-set/...).
|
|
106
|
+
# Bootstrap runs in FirstAdmin/Tideless mode, so authorize signs server-side (no enclave).
|
|
107
|
+
# Full spec: canon/iga-change-requests-api.md.
|
|
108
|
+
approve_all_pending() {
|
|
109
|
+
local TOKEN ready id
|
|
102
110
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
111
|
+
# 1. Authorize all pending CREATE/DELETE change requests in one call.
|
|
112
|
+
TOKEN="$(get_token)"
|
|
113
|
+
curl -s -X POST "$TIDECLOAK_URL/admin/realms/$REALM_NAME/iga/change-requests/bulk-authorize" \
|
|
114
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
115
|
+
-H "Content-Type: application/json" \
|
|
116
|
+
-d '{"actionTypeIn":["CREATE","DELETE"],"limit":100}' > /dev/null 2>&1
|
|
108
117
|
|
|
118
|
+
# 2. Commit ready CRs; loop passes so dependent CRs become ready.
|
|
119
|
+
for pass in 1 2 3 4 5; do
|
|
109
120
|
TOKEN="$(get_token)"
|
|
110
|
-
curl -s
|
|
111
|
-
-H "Authorization: Bearer $TOKEN" \
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
-
|
|
121
|
+
ready=$(curl -s "$TIDECLOAK_URL/admin/realms/$REALM_NAME/iga/change-requests?status=PENDING" \
|
|
122
|
+
-H "Authorization: Bearer $TOKEN" 2>/dev/null \
|
|
123
|
+
| jq -r '.[] | select(.readyToCommit==true) | .id' 2>/dev/null)
|
|
124
|
+
[ -z "$ready" ] && break
|
|
125
|
+
for id in $ready; do
|
|
126
|
+
TOKEN="$(get_token)"
|
|
127
|
+
curl -s -X POST "$TIDECLOAK_URL/admin/realms/$REALM_NAME/iga/change-requests/$id/commit" \
|
|
128
|
+
-H "Authorization: Bearer $TOKEN" > /dev/null 2>&1
|
|
129
|
+
done
|
|
117
130
|
done
|
|
118
131
|
}
|
|
132
|
+
# Legacy call sites pass a type arg the new list-all surface ignores.
|
|
133
|
+
approve_and_commit() { approve_all_pending; }
|
|
119
134
|
|
|
120
135
|
approve_and_commit clients
|
|
121
136
|
```
|
|
@@ -135,7 +150,7 @@ approve_and_commit clients
|
|
|
135
150
|
|
|
136
151
|
| Symptom | Cause | Fix |
|
|
137
152
|
|---------|-------|-----|
|
|
138
|
-
| "Could not
|
|
153
|
+
| "Tide realm setup failed" / "Could not set up the Tide realm" | JSON sent to `setUpTideRealm` instead of form data | Use `application/x-www-form-urlencoded` |
|
|
139
154
|
| "email is null or empty" | Missing email parameter | Add `--data-urlencode "email=..."` |
|
|
140
155
|
| 404 on setUpTideRealm | Wrong path (`/tide-admin/setUpTideRealm`) | Correct path: `/vendorResources/setUpTideRealm` |
|
|
141
156
|
| Realm import fails | Placeholders not replaced in template | Verify `sed` replaced all three: `REALM_NAME`, `CLIENT_NAME`, `CLIENT_APP_URL` |
|
|
File without changes
|
|
@@ -372,34 +372,38 @@ curl -s -X POST "$TIDECLOAK_URL/admin/realms/$REALM_NAME/tide-admin/toggle-iga"
|
|
|
372
372
|
-H "Content-Type: application/x-www-form-urlencoded" \
|
|
373
373
|
--data-urlencode "isIGAEnabled=true"
|
|
374
374
|
|
|
375
|
-
# 5. Auto-approve
|
|
376
|
-
|
|
377
|
-
|
|
375
|
+
# 5. Auto-approve change requests created during realm setup.
|
|
376
|
+
# Uses the current /iga/change-requests/... surface (replaces the legacy
|
|
377
|
+
# /tide-admin/change-set/... surface). During bootstrap the realm is in
|
|
378
|
+
# FirstAdmin/Tideless mode, so authorize signs server-side (no enclave).
|
|
379
|
+
# See canon/iga-change-requests-api.md.
|
|
380
|
+
approve_all_pending() {
|
|
381
|
+
local TOKEN ready id
|
|
382
|
+
|
|
383
|
+
# 1. Authorize every pending CREATE/DELETE change request in one call.
|
|
378
384
|
TOKEN="$(get_token)"
|
|
379
|
-
|
|
380
|
-
"$
|
|
381
|
-
-H "
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
--arg at "$(echo "$req" | jq -r .actionType)" \
|
|
388
|
-
'{changeSetId:$id,changeSetType:$cst,actionType:$at}')
|
|
389
|
-
|
|
390
|
-
# Sign then commit
|
|
385
|
+
curl -s -X POST "$TIDECLOAK_URL/admin/realms/$REALM_NAME/iga/change-requests/bulk-authorize" \
|
|
386
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
387
|
+
-H "Content-Type: application/json" \
|
|
388
|
+
-d '{"actionTypeIn":["CREATE","DELETE"],"limit":100}' > /dev/null 2>&1
|
|
389
|
+
|
|
390
|
+
# 2. Commit everything that is ready. Loop passes so dependent CRs
|
|
391
|
+
# (e.g. a role must exist before its assignment) become ready and commit.
|
|
392
|
+
for pass in 1 2 3 4 5; do
|
|
391
393
|
TOKEN="$(get_token)"
|
|
392
|
-
curl -s
|
|
393
|
-
-H "Authorization: Bearer $TOKEN" \
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
-
|
|
394
|
+
ready=$(curl -s "$TIDECLOAK_URL/admin/realms/$REALM_NAME/iga/change-requests?status=PENDING" \
|
|
395
|
+
-H "Authorization: Bearer $TOKEN" 2>/dev/null \
|
|
396
|
+
| jq -r '.[] | select(.readyToCommit==true) | .id' 2>/dev/null)
|
|
397
|
+
[ -z "$ready" ] && break
|
|
398
|
+
for id in $ready; do
|
|
399
|
+
TOKEN="$(get_token)"
|
|
400
|
+
curl -s -X POST "$TIDECLOAK_URL/admin/realms/$REALM_NAME/iga/change-requests/$id/commit" \
|
|
401
|
+
-H "Authorization: Bearer $TOKEN" > /dev/null 2>&1
|
|
402
|
+
done
|
|
399
403
|
done
|
|
400
404
|
}
|
|
401
405
|
|
|
402
|
-
|
|
406
|
+
approve_all_pending
|
|
403
407
|
|
|
404
408
|
# 6. Create admin user
|
|
405
409
|
TOKEN="$(get_token)"
|
|
@@ -410,7 +414,7 @@ curl -s -X POST "$TIDECLOAK_URL/admin/realms/$REALM_NAME/users" \
|
|
|
410
414
|
|
|
411
415
|
# 6b. Approve user creation (user is not queryable until committed with IGA enabled)
|
|
412
416
|
sleep 2
|
|
413
|
-
|
|
417
|
+
approve_all_pending
|
|
414
418
|
|
|
415
419
|
# 6c. Assign tide-realm-admin role
|
|
416
420
|
TOKEN="$(get_token)"
|
|
@@ -461,8 +465,7 @@ done
|
|
|
461
465
|
|
|
462
466
|
# 9. Approve role assignment change requests
|
|
463
467
|
sleep 2
|
|
464
|
-
|
|
465
|
-
approve_and_commit roles
|
|
468
|
+
approve_all_pending
|
|
466
469
|
|
|
467
470
|
# 10. Update CustomAdminUIDomain for enclave approval popups
|
|
468
471
|
# CustomAdminUIDomain is OPTIONAL and APP-SPECIFIC: only needed when a separate
|
|
@@ -539,8 +542,8 @@ curl -s -X GET "$TIDECLOAK_URL/admin/realms/$REALM_NAME/vendorResources/get-inst
|
|
|
539
542
|
| `POST /admin/realms` | Create realm from JSON template |
|
|
540
543
|
| `POST /admin/realms/{realm}/vendorResources/setUpTideRealm` | Initialize Tide licensing + VRK |
|
|
541
544
|
| `POST /admin/realms/{realm}/tide-admin/toggle-iga` | Enable IGA governance |
|
|
542
|
-
| `POST /admin/realms/{realm}/
|
|
543
|
-
| `POST /admin/realms/{realm}/
|
|
545
|
+
| `POST /admin/realms/{realm}/iga/change-requests/{id}/authorize` | Approve (sign) a change request. Batch: `POST /iga/change-requests/bulk-authorize`. Replaces legacy `tide-admin/change-set/sign`. |
|
|
546
|
+
| `POST /admin/realms/{realm}/iga/change-requests/{id}/commit` | Commit an approved change. Replaces legacy `tide-admin/change-set/commit`. See `canon/iga-change-requests-api.md`. |
|
|
544
547
|
| `POST /admin/realms/{realm}/tideAdminResources/get-required-action-link` | Generate admin invite link |
|
|
545
548
|
| `GET /admin/realms/{realm}/vendorResources/get-installations-provider` | Download adapter config |
|
|
546
549
|
| `POST /admin/realms/{realm}/vendorResources/sign-idp-settings` | Sign IdP settings with VRK. **ALWAYS required** after any Tide IDP config change (logo, background, backup toggle, custom admin domain, or client origin changes). Without it, the enclave rejects settings as unsigned. Requires active VRK/license. |
|
|
File without changes
|
|
@@ -93,7 +93,7 @@ function hasAnyRole(user: any, role: string): boolean {
|
|
|
93
93
|
|
|
94
94
|
1. TideCloak Admin → Users → {user} → Role Mappings
|
|
95
95
|
2. Assign role → {role-name}
|
|
96
|
-
3. If IGA enabled:
|
|
96
|
+
3. If IGA enabled: authorize + commit the change request (requires quorum; see `canon/iga-change-requests-api.md`)
|
|
97
97
|
4. User logout/login to get new token
|
|
98
98
|
|
|
99
99
|
**Fix (default roles)**:
|
|
@@ -189,7 +189,7 @@ async function forceTokenRefresh() {
|
|
|
189
189
|
|
|
190
190
|
## Symptom: E2EE Role Not Working
|
|
191
191
|
|
|
192
|
-
**What you see**: `doEncrypt('tag'
|
|
192
|
+
**What you see**: `doEncrypt([{ data, tags: ['tag'] }])` fails even though user has role.
|
|
193
193
|
|
|
194
194
|
**Diagnostic**:
|
|
195
195
|
|
|
@@ -129,29 +129,32 @@ done
|
|
|
129
129
|
### Step 5: Approve user change requests
|
|
130
130
|
|
|
131
131
|
```bash
|
|
132
|
-
# Reuse
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
local
|
|
137
|
-
-H "Authorization: Bearer $TOKEN" 2>/dev/null || echo "[]")
|
|
132
|
+
# Reuse approve_all_pending from bootstrap-realm-from-template.
|
|
133
|
+
# Current /iga/change-requests/... surface (replaces legacy /tide-admin/change-set/...).
|
|
134
|
+
# Bootstrap = FirstAdmin/Tideless: authorize signs server-side. Spec: canon/iga-change-requests-api.md.
|
|
135
|
+
approve_all_pending() {
|
|
136
|
+
local TOKEN ready id
|
|
138
137
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
138
|
+
TOKEN="$(get_token)"
|
|
139
|
+
curl -s -X POST "$TIDECLOAK_URL/admin/realms/$REALM_NAME/iga/change-requests/bulk-authorize" \
|
|
140
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
141
|
+
-H "Content-Type: application/json" \
|
|
142
|
+
-d '{"actionTypeIn":["CREATE","DELETE"],"limit":100}' > /dev/null 2>&1
|
|
144
143
|
|
|
144
|
+
for pass in 1 2 3 4 5; do
|
|
145
145
|
TOKEN="$(get_token)"
|
|
146
|
-
curl -s
|
|
147
|
-
-H "Authorization: Bearer $TOKEN" \
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
-
|
|
146
|
+
ready=$(curl -s "$TIDECLOAK_URL/admin/realms/$REALM_NAME/iga/change-requests?status=PENDING" \
|
|
147
|
+
-H "Authorization: Bearer $TOKEN" 2>/dev/null \
|
|
148
|
+
| jq -r '.[] | select(.readyToCommit==true) | .id' 2>/dev/null)
|
|
149
|
+
[ -z "$ready" ] && break
|
|
150
|
+
for id in $ready; do
|
|
151
|
+
TOKEN="$(get_token)"
|
|
152
|
+
curl -s -X POST "$TIDECLOAK_URL/admin/realms/$REALM_NAME/iga/change-requests/$id/commit" \
|
|
153
|
+
-H "Authorization: Bearer $TOKEN" > /dev/null 2>&1
|
|
154
|
+
done
|
|
153
155
|
done
|
|
154
156
|
}
|
|
157
|
+
approve_and_commit() { approve_all_pending; }
|
|
155
158
|
|
|
156
159
|
approve_and_commit users
|
|
157
160
|
approve_and_commit roles
|
|
@@ -216,7 +219,7 @@ node -e "const c=require('$ADAPTER_OUTPUT'); console.log('jwk:', !!c.jwk, 'vendo
|
|
|
216
219
|
| "Could not find configuration for Required Action" | Tide env vars missing from container | Restart container with all required env vars |
|
|
217
220
|
| `tideUserKey` never appears | Admin didn't complete the linking flow | Open invite link in browser, complete the flow |
|
|
218
221
|
| Enclave popups don't work from app | CustomAdminUIDomain not set | Run Step 6 |
|
|
219
|
-
| Adapter JSON missing `jwk` |
|
|
222
|
+
| Adapter JSON missing `jwk` | `setUpTideRealm` did not run / no `tide-vendor-key` component + EdDSA key on the realm (the `jwk` is gated on those, not on IGA) | Re-run `setUpTideRealm` from `bootstrap-realm-from-template` Step 4 and confirm it returned 200 |
|
|
220
223
|
|
|
221
224
|
---
|
|
222
225
|
|
|
File without changes
|
|
@@ -76,7 +76,46 @@ Any FAIL means the API route is not properly protected. Use the implementation b
|
|
|
76
76
|
|
|
77
77
|
---
|
|
78
78
|
|
|
79
|
-
##
|
|
79
|
+
## Recommended: Use the Shipped `verifyTideCloakToken` Helper
|
|
80
|
+
|
|
81
|
+
Tide ships a server-side verification helper — you do not have to hand-roll JWKS verification. `@tidecloak/nextjs/server` re-exports `verifyTideCloakToken` from `@tidecloak/verify`:
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
// app/api/users/route.ts
|
|
85
|
+
import { NextRequest } from 'next/server';
|
|
86
|
+
import { verifyTideCloakToken } from '@tidecloak/nextjs/server';
|
|
87
|
+
import tcConfig from '../../../data/tidecloak.json';
|
|
88
|
+
|
|
89
|
+
export async function GET(req: NextRequest) {
|
|
90
|
+
const authHeader = req.headers.get('authorization');
|
|
91
|
+
if (!authHeader) {
|
|
92
|
+
return Response.json({ error: 'Unauthorized' }, { status: 401 });
|
|
93
|
+
}
|
|
94
|
+
const token = authHeader.replace(/^(Bearer|DPoP) /, '');
|
|
95
|
+
|
|
96
|
+
// Signature (arg order): verifyTideCloakToken(config, token, allowedRoles=[])
|
|
97
|
+
// config is FIRST. Pass roles to enforce them; [] to just verify the token.
|
|
98
|
+
const payload = await verifyTideCloakToken(tcConfig, token, ['admin']);
|
|
99
|
+
if (!payload) {
|
|
100
|
+
// Returns false on any failure (bad signature, issuer, azp, expiry, or role).
|
|
101
|
+
return Response.json({ error: 'Forbidden' }, { status: 403 });
|
|
102
|
+
}
|
|
103
|
+
return Response.json({ users: [...] });
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
`verifyTideCloakToken`:
|
|
108
|
+
- verifies the signature against the adapter's embedded `jwk` (local JWKS — no network call),
|
|
109
|
+
- validates the issuer (`auth-server-url` + `/realms/<realm>`) and `azp` (against `config.resource`),
|
|
110
|
+
- checks time claims with a small `clockTolerance`,
|
|
111
|
+
- optionally enforces `allowedRoles` (matches across `realm_access.roles` and `resource_access[client].roles`),
|
|
112
|
+
- returns the decoded payload on success, `false` on failure.
|
|
113
|
+
|
|
114
|
+
Prefer this for standard API protection. The manual `jose` implementation below is the **under-the-hood alternative** — use it when you need custom behavior the helper does not cover (e.g. app-side DPoP proof re-verification, separate 401-vs-403 semantics, or `iat` future-validation). See [verify-jwt-server-side.md](verify-jwt-server-side.md).
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Quick Implementation — Manual (App Router)
|
|
80
119
|
|
|
81
120
|
This section creates minimal versions of `lib/auth/tidecloakConfig.ts`, `lib/auth/tideJWT.ts`, and `lib/auth/protect.ts`. If you also follow [verify-jwt-server-side.md](verify-jwt-server-side.md), its richer versions **replace** these files (adds DPoP verification, client-role checking, `extractToken`, and `iat` validation).
|
|
82
121
|
|