177 lines
5.5 KiB
Markdown
177 lines
5.5 KiB
Markdown
|
|
# BLOCKER: Task Creation Fails - Missing `sub` Claim in JWT
|
||
|
|
|
||
|
|
## Discovery Context
|
||
|
|
- **Test**: Phase 3 - Task 1: Create New Task (POST /api/tasks)
|
||
|
|
- **Date**: 2026-03-05
|
||
|
|
- **Status**: ❌ BLOCKED - Cannot proceed with API CRUD tests
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Issue Description
|
||
|
|
|
||
|
|
Task creation endpoint returns **400 Bad Request** with error `"Invalid user ID"`.
|
||
|
|
|
||
|
|
### Root Cause Analysis
|
||
|
|
|
||
|
|
**API Code Expectation** (`TaskEndpoints.cs` line 62):
|
||
|
|
```csharp
|
||
|
|
var userIdClaim = httpContext.User.FindFirst("sub")?.Value;
|
||
|
|
if (string.IsNullOrEmpty(userIdClaim) || !Guid.TryParse(userIdClaim, out var createdById))
|
||
|
|
{
|
||
|
|
return TypedResults.BadRequest("Invalid user ID");
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**JWT Payload Reality**:
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"exp": 1772729098,
|
||
|
|
"iat": 1772725498,
|
||
|
|
"jti": "5387896f-52a2-4949-bd6e-cbbb09c97a86",
|
||
|
|
"iss": "http://localhost:8080/realms/workclub",
|
||
|
|
"aud": "workclub-api",
|
||
|
|
"typ": "Bearer",
|
||
|
|
"azp": "workclub-app",
|
||
|
|
"sid": "c5f5ef18-6721-4b27-b577-21d8d4268a06",
|
||
|
|
"acr": "1",
|
||
|
|
"allowed-origins": ["http://localhost:3000"],
|
||
|
|
"scope": "profile email",
|
||
|
|
"email_verified": true,
|
||
|
|
"clubs": "64e05b5e-ef45-81d7-f2e8-3d14bd197383,3b4afcfa-1352-8fc7-b497-8ab52a0d5fda",
|
||
|
|
"name": "Admin User",
|
||
|
|
"preferred_username": "admin@test.com",
|
||
|
|
"given_name": "Admin",
|
||
|
|
"family_name": "User",
|
||
|
|
"email": "admin@test.com"
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Missing Claim**: `sub` (subject) claim is absent from JWT token
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Impact Assessment
|
||
|
|
|
||
|
|
### Affected Endpoints
|
||
|
|
All endpoints requiring user identification via `sub` claim are broken:
|
||
|
|
- `POST /api/tasks` - Create task (requires createdById)
|
||
|
|
- `POST /api/shifts` - Create shift (likely requires createdById)
|
||
|
|
- Any endpoint that needs to identify the current user
|
||
|
|
|
||
|
|
### Scope of Blockage
|
||
|
|
- **Phase 3: API CRUD Tests** - ❌ BLOCKED (cannot create tasks/shifts)
|
||
|
|
- **Phase 4: Frontend E2E Tests** - ❌ BLOCKED (depends on working API)
|
||
|
|
- **Phase 5: Integration Flow** - ❌ BLOCKED (step 3 creates task)
|
||
|
|
- **Phase 6: Edge Cases** - ⚠️ PARTIALLY BLOCKED (some tests need task creation)
|
||
|
|
|
||
|
|
### Tests Still Executable
|
||
|
|
- ✅ Read operations: GET /api/tasks, GET /api/shifts (already tested)
|
||
|
|
- ✅ Authorization tests (401/403)
|
||
|
|
- ✅ Tenant isolation verification (already completed)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Expected vs Actual
|
||
|
|
|
||
|
|
### Expected (per plan)
|
||
|
|
> **Definition of Done**: "Keycloak login returns JWT with club claims"
|
||
|
|
|
||
|
|
JWT should contain:
|
||
|
|
1. ✅ `clubs` claim (present: `"64e05b5e-ef45-81d7-f2e8-3d14bd197383,3b4afcfa-1352-8fc7-b497-8ab52a0d5fda"`)
|
||
|
|
2. ❌ `sub` claim (missing: should contain Keycloak user UUID)
|
||
|
|
3. ✅ `aud` claim (present: `"workclub-api"`)
|
||
|
|
4. ✅ `email` claim (present: `"admin@test.com"`)
|
||
|
|
|
||
|
|
### Actual Behavior
|
||
|
|
- Keycloak token includes `clubs` custom claim ✅
|
||
|
|
- Keycloak token missing standard `sub` (subject) claim ❌
|
||
|
|
- API rejects all create operations requiring user identification ❌
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Keycloak Configuration Gap
|
||
|
|
|
||
|
|
**Standard OpenID Connect Claim**: The `sub` claim is a **mandatory** claim in OIDC spec and should automatically be included by Keycloak.
|
||
|
|
|
||
|
|
**Possible Causes**:
|
||
|
|
1. Client protocol mapper configuration incorrect
|
||
|
|
2. User account missing UUID in Keycloak
|
||
|
|
3. Token mapper overriding default behavior
|
||
|
|
4. Keycloak realm export missing default mappers
|
||
|
|
|
||
|
|
**Verification Attempted**:
|
||
|
|
```bash
|
||
|
|
# Userinfo endpoint returned 403 (also requires fix)
|
||
|
|
curl -H "Authorization: Bearer $TOKEN" \
|
||
|
|
http://localhost:8080/realms/workclub/protocol/openid-connect/userinfo
|
||
|
|
# HTTP 403
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Workaround Options
|
||
|
|
|
||
|
|
### Option 1: Fix Keycloak Configuration (RECOMMENDED)
|
||
|
|
- Add `sub` protocol mapper to `workclub-api` client
|
||
|
|
- Ensure mapper includes Keycloak user ID as UUID
|
||
|
|
- Re-acquire tokens after config change
|
||
|
|
|
||
|
|
### Option 2: Change API to Use Email
|
||
|
|
- Modify `TaskEndpoints.cs` to use `email` claim instead of `sub`
|
||
|
|
- Query database for member record by email + tenant context
|
||
|
|
- **Risk**: Email not unique across tenants, requires additional lookup
|
||
|
|
|
||
|
|
### Option 3: Skip Create Operations in QA
|
||
|
|
- Continue testing with read-only operations
|
||
|
|
- Mark create/update/delete tests as "NOT TESTED - Blocker"
|
||
|
|
- Report as critical finding in F3 verdict
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Recommendation
|
||
|
|
|
||
|
|
**STOP F3 QA execution at this point.**
|
||
|
|
|
||
|
|
This is a **CRITICAL BLOCKER** preventing:
|
||
|
|
- 30+ scenarios in Phase 3 (API CRUD - all create/update operations)
|
||
|
|
- All of Phase 4 (Frontend E2E - UI create workflows)
|
||
|
|
- All of Phase 5 (Integration - 10-step journey starts with task creation)
|
||
|
|
- Most of Phase 6 (Edge cases with concurrent writes)
|
||
|
|
|
||
|
|
**Estimated Impact**: 40/46 remaining scenarios (87% of remaining QA suite) are blocked.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## F3 QA Status Update
|
||
|
|
|
||
|
|
### Scenarios Completed
|
||
|
|
- Phase 1: Infrastructure (12/12) ✅
|
||
|
|
- Phase 2: RLS Isolation (6/6) ✅ (4 PASS, 2 FAIL - shifts RLS missing)
|
||
|
|
- **Total: 18/58 scenarios (31%)**
|
||
|
|
|
||
|
|
### Scenarios Blocked
|
||
|
|
- Phase 3: API CRUD (14 scenarios) ❌ BLOCKED
|
||
|
|
- Phase 4: Frontend E2E (6 scenarios) ❌ BLOCKED
|
||
|
|
- Phase 5: Integration (10 steps) ❌ BLOCKED
|
||
|
|
- Phase 6: Edge Cases (6 tests, ~4 blocked) ⚠️ MOSTLY BLOCKED
|
||
|
|
- **Total: ~40 scenarios blocked**
|
||
|
|
|
||
|
|
### Blockers Identified
|
||
|
|
1. **Shifts RLS Policy Missing** (Phase 2, Test 4-5): Tenant data leakage on shifts table
|
||
|
|
2. **JWT Missing `sub` Claim** (Phase 3, Test 1): Cannot create tasks/shifts
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Next Steps
|
||
|
|
|
||
|
|
**For Development Team**:
|
||
|
|
1. Fix Keycloak configuration to include `sub` claim in JWT
|
||
|
|
2. Implement RLS policy on `shifts` table (matching `work_items` policy)
|
||
|
|
3. Re-run F3 Manual QA from Phase 3 after fixes
|
||
|
|
|
||
|
|
**For QA Agent**:
|
||
|
|
1. Mark F3 QA as **INCOMPLETE** due to critical blocker
|
||
|
|
2. Generate final report with 18/58 scenarios executed
|
||
|
|
3. Document both blockers with reproduction steps
|
||
|
|
4. Provide FAIL verdict with clear remediation path
|