231 lines
6.9 KiB
Markdown
231 lines
6.9 KiB
Markdown
|
|
# Keycloak Club UUID Update Guide
|
||
|
|
|
||
|
|
## Overview
|
||
|
|
|
||
|
|
This guide documents the process of updating Keycloak user attributes to replace placeholder club UUIDs with real database UUIDs. This ensures JWT tokens contain the correct tenant identifiers for API access.
|
||
|
|
|
||
|
|
## Blocker Resolution
|
||
|
|
|
||
|
|
**Blocker #2 (Critical)**: JWT `clubs` claim uses placeholder strings instead of real database UUIDs
|
||
|
|
|
||
|
|
### Before
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"clubs": {
|
||
|
|
"club-1-uuid": "admin",
|
||
|
|
"club-2-uuid": "member"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### After
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"clubs": {
|
||
|
|
"afa8daf3-5cfa-4589-9200-b39a538a12de": "admin",
|
||
|
|
"a1952a72-2e13-4a4e-87dd-821847b58698": "member"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## Real Club UUIDs
|
||
|
|
|
||
|
|
From PostgreSQL `clubs` table:
|
||
|
|
|
||
|
|
| UUID | Name | Sport Type |
|
||
|
|
|------|------|-----------|
|
||
|
|
| `afa8daf3-5cfa-4589-9200-b39a538a12de` | Sunrise Tennis Club | Tennis (0) |
|
||
|
|
| `a1952a72-2e13-4a4e-87dd-821847b58698` | Valley Cycling Club | Cycling (1) |
|
||
|
|
|
||
|
|
## Test Users Configuration
|
||
|
|
|
||
|
|
After update, the 5 test users have these club assignments:
|
||
|
|
|
||
|
|
```
|
||
|
|
admin@test.com → Admin in Sunrise Tennis + Member in Valley Cycling
|
||
|
|
manager@test.com → Manager in Sunrise Tennis + Member in Valley Cycling
|
||
|
|
member1@test.com → Member in Sunrise Tennis + Member in Valley Cycling
|
||
|
|
member2@test.com → Member in Valley Cycling
|
||
|
|
viewer@test.com → Viewer in Sunrise Tennis
|
||
|
|
```
|
||
|
|
|
||
|
|
## Update Methods
|
||
|
|
|
||
|
|
### Method 1: Automated Script (Recommended)
|
||
|
|
|
||
|
|
Run the complete update script:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
python3 .sisyphus/scripts/update-keycloak-club-uuids.py
|
||
|
|
```
|
||
|
|
|
||
|
|
**What it does:**
|
||
|
|
1. ✓ Updates clubs attributes in PostgreSQL database
|
||
|
|
2. ✓ Restarts Keycloak to clear caches
|
||
|
|
3. ✓ Verifies JWT tokens contain real UUIDs
|
||
|
|
4. ✓ Tests API endpoints with real UUIDs
|
||
|
|
|
||
|
|
### Method 2: Manual Database Update
|
||
|
|
|
||
|
|
If you only need to update the database (without Keycloak restart):
|
||
|
|
|
||
|
|
```bash
|
||
|
|
docker exec workclub_postgres psql -U postgres -d keycloak << 'SQL'
|
||
|
|
UPDATE user_attribute SET value = '{"afa8daf3-5cfa-4589-9200-b39a538a12de": "admin", "a1952a72-2e13-4a4e-87dd-821847b58698": "member"}' WHERE user_id = 'bf5adcfb-0978-4beb-8e02-7577f0ded47f' AND name = 'clubs';
|
||
|
|
|
||
|
|
UPDATE user_attribute SET value = '{"afa8daf3-5cfa-4589-9200-b39a538a12de": "manager", "a1952a72-2e13-4a4e-87dd-821847b58698": "member"}' WHERE user_id = 'aa5270a3-633a-4d89-a3b4-a467b08cbb55' AND name = 'clubs';
|
||
|
|
|
||
|
|
UPDATE user_attribute SET value = '{"afa8daf3-5cfa-4589-9200-b39a538a12de": "member", "a1952a72-2e13-4a4e-87dd-821847b58698": "member"}' WHERE user_id = '60c0d8b9-6354-4ad3-bfac-9547c68c069b' AND name = 'clubs';
|
||
|
|
|
||
|
|
UPDATE user_attribute SET value = '{"a1952a72-2e13-4a4e-87dd-821847b58698": "member"}' WHERE user_id = '294a2086-cf2f-43cc-9bc6-2a8a7d325b9a' AND name = 'clubs';
|
||
|
|
|
||
|
|
UPDATE user_attribute SET value = '{"afa8daf3-5cfa-4589-9200-b39a538a12de": "viewer"}' WHERE user_id = 'f4890d47-ba6c-4691-9d7b-4f656c60f232' AND name = 'clubs';
|
||
|
|
SQL
|
||
|
|
```
|
||
|
|
|
||
|
|
Then restart Keycloak:
|
||
|
|
```bash
|
||
|
|
docker restart workclub_keycloak && sleep 10
|
||
|
|
```
|
||
|
|
|
||
|
|
### Method 3: Keycloak Admin API
|
||
|
|
|
||
|
|
⚠️ **Note**: The Keycloak Admin API (PUT /admin/realms/workclub/users/{id}) returns 204 No Content but doesn't persist attribute changes in this setup. Database updates are required.
|
||
|
|
|
||
|
|
## Verification
|
||
|
|
|
||
|
|
### 1. Check Database
|
||
|
|
|
||
|
|
```bash
|
||
|
|
docker exec workclub_postgres psql -U postgres -d keycloak -c "SELECT user_id, value FROM user_attribute WHERE name = 'clubs';"
|
||
|
|
```
|
||
|
|
|
||
|
|
Expected output shows real UUIDs:
|
||
|
|
```
|
||
|
|
afa8daf3-5cfa-4589-9200-b39a538a12de": "admin"
|
||
|
|
a1952a72-2e13-4a4e-87dd-821847b58698": "member"
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2. Check JWT Token
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Get token
|
||
|
|
TOKEN=$(curl -s -X POST http://localhost:8080/realms/workclub/protocol/openid-connect/token \
|
||
|
|
-d "client_id=workclub-app" \
|
||
|
|
-d "grant_type=password" \
|
||
|
|
-d "username=admin@test.com" \
|
||
|
|
-d "password=testpass123" | jq -r '.access_token')
|
||
|
|
|
||
|
|
# Decode and check clubs claim
|
||
|
|
PAYLOAD=$(echo $TOKEN | cut -d'.' -f2)
|
||
|
|
PADDING=$((4 - ${#PAYLOAD} % 4))
|
||
|
|
if [ $PADDING -ne 4 ]; then
|
||
|
|
PAYLOAD="${PAYLOAD}$(printf '%*s' $PADDING | tr ' ' '=')"
|
||
|
|
fi
|
||
|
|
echo "$PAYLOAD" | base64 -d 2>/dev/null | jq '.clubs'
|
||
|
|
```
|
||
|
|
|
||
|
|
Expected output:
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"afa8daf3-5cfa-4589-9200-b39a538a12de": "admin",
|
||
|
|
"a1952a72-2e13-4a4e-87dd-821847b58698": "member"
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 3. Test API Endpoint
|
||
|
|
|
||
|
|
```bash
|
||
|
|
TOKEN=$(curl -s -X POST http://localhost:8080/realms/workclub/protocol/openid-connect/token \
|
||
|
|
-d "client_id=workclub-app" \
|
||
|
|
-d "grant_type=password" \
|
||
|
|
-d "username=admin@test.com" \
|
||
|
|
-d "password=testpass123" | jq -r '.access_token')
|
||
|
|
|
||
|
|
curl -X GET \
|
||
|
|
-H "Authorization: Bearer $TOKEN" \
|
||
|
|
-H "X-Tenant-Id: afa8daf3-5cfa-4589-9200-b39a538a12de" \
|
||
|
|
http://localhost:5001/api/clubs/me
|
||
|
|
```
|
||
|
|
|
||
|
|
Expected: `200 OK` response (should return `[]` or list of clubs)
|
||
|
|
|
||
|
|
## Technical Details
|
||
|
|
|
||
|
|
### How it Works
|
||
|
|
|
||
|
|
1. **User Attributes Storage**: Keycloak stores user attributes in PostgreSQL `user_attribute` table
|
||
|
|
2. **Mapper Configuration**: The `club-membership` mapper (oidc-usermodel-attribute-mapper) reads the `clubs` attribute and includes it in the JWT token
|
||
|
|
3. **Token Claim**: The JWT `clubs` claim is generated from the `clubs` user attribute
|
||
|
|
4. **Caching**: Keycloak caches user data in memory, so a restart is needed after database updates
|
||
|
|
|
||
|
|
### Mapper Details
|
||
|
|
|
||
|
|
Client: `workclub-app`
|
||
|
|
Mapper: `club-membership`
|
||
|
|
Type: `oidc-usermodel-attribute-mapper`
|
||
|
|
|
||
|
|
Configuration:
|
||
|
|
```
|
||
|
|
- user.attribute: clubs
|
||
|
|
- claim.name: clubs
|
||
|
|
- jsonType.label: JSON
|
||
|
|
- id.token.claim: true
|
||
|
|
- access.token.claim: true
|
||
|
|
- introspection.token.claim: true
|
||
|
|
- userinfo.token.claim: true
|
||
|
|
```
|
||
|
|
|
||
|
|
### User IDs in Database
|
||
|
|
|
||
|
|
```
|
||
|
|
admin@test.com → bf5adcfb-0978-4beb-8e02-7577f0ded47f
|
||
|
|
manager@test.com → aa5270a3-633a-4d89-a3b4-a467b08cbb55
|
||
|
|
member1@test.com → 60c0d8b9-6354-4ad3-bfac-9547c68c069b
|
||
|
|
member2@test.com → 294a2086-cf2f-43cc-9bc6-2a8a7d325b9a
|
||
|
|
viewer@test.com → f4890d47-ba6c-4691-9d7b-4f656c60f232
|
||
|
|
```
|
||
|
|
|
||
|
|
## Troubleshooting
|
||
|
|
|
||
|
|
### JWT still shows old UUIDs
|
||
|
|
|
||
|
|
**Problem**: Database is updated but JWT token still has placeholder UUIDs
|
||
|
|
|
||
|
|
**Solution**:
|
||
|
|
1. Restart Keycloak: `docker restart workclub_keycloak`
|
||
|
|
2. Wait 10 seconds for Keycloak to boot
|
||
|
|
3. Generate new token
|
||
|
|
|
||
|
|
### API still rejects real UUID
|
||
|
|
|
||
|
|
**Problem**: API returns 401 or 403 with real UUID in X-Tenant-Id
|
||
|
|
|
||
|
|
**Solution**:
|
||
|
|
1. Ensure Keycloak has restarted and token is fresh
|
||
|
|
2. Check JWT token contains correct real UUIDs: `jq '.clubs' <<< decoded_payload`
|
||
|
|
3. Verify database has correct values
|
||
|
|
|
||
|
|
### Cannot connect to database
|
||
|
|
|
||
|
|
**Problem**: `docker exec workclub_postgres` fails
|
||
|
|
|
||
|
|
**Solution**:
|
||
|
|
1. Check container is running: `docker ps | grep postgres`
|
||
|
|
2. If container doesn't exist, start the workclub environment: `docker-compose up -d`
|
||
|
|
3. Verify database: `docker exec workclub_postgres psql -U postgres -d keycloak -c "SELECT 1"`
|
||
|
|
|
||
|
|
## Impact
|
||
|
|
|
||
|
|
This update resolves **Blocker #2 (Critical)** and unblocks:
|
||
|
|
- All 46 remaining QA scenarios
|
||
|
|
- Tenant resolution logic
|
||
|
|
- Multi-club user workflows
|
||
|
|
- API integration tests
|
||
|
|
|
||
|
|
## Related Documentation
|
||
|
|
|
||
|
|
- [Keycloak Admin API](http://localhost:8080/admin)
|
||
|
|
- [Keycloak Realm Configuration](http://localhost:8080/admin/master/console/#/realms/workclub)
|
||
|
|
- Issue: `Blocker #2 - Club UUIDs in JWT tokens`
|