-
Notifications
You must be signed in to change notification settings - Fork 5
v0.0.5 ~ polished authentication flow, create pod implemented, and re… #5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
roncodes
wants to merge
58
commits into
main
Choose a base branch
from
dev-v0.0.5
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
…source syncing started
This fixes the 401 Unauthorized error when creating pods. The previous version (0.9.10) did not support passing custom headers to requestTokens(), which meant the DPoP header was ignored during token exchange. This resulted in non-DPoP-bound access tokens being issued, which are rejected by Solid servers. Version 1.0.2 includes PR #297 which added support for custom headers, enabling proper DPoP token binding.
- verifyJWTsignature -> verifyJWTSignature with string type hint and bool return - getSessionKey, setSessionKey, unsetSessionKey with string type hints - decodeJWTPublic with string and int type hints These changes ensure compatibility with the parent class method signatures in jumbojett/openid-connect-php v1.0.2.
- Add null checks before calling setClientID() and setClientSecret() - Auto-restore client credentials when identity is provided without clientID - Gracefully handle missing credentials during initial registration This fixes the 'Argument #1 ($clientID) must be of type string, null given' error when checking authentication status.
The parent authenticate() method calls requestTokens() directly without passing headers. By overriding requestTokens(), we ensure the DPoP header is always included in the token endpoint request, which is required for DPoP-bound access tokens. This should fix the 401 Unauthorized error when creating pods.
The requestTokens() override now handles DPoP header injection automatically, so we don't need to create it separately in exchangeCodeForTokens(). The duplicate creation was causing 'invalid DPoP key binding' errors.
Previously, DPoP keys were stored globally while access tokens were stored per-identity. This caused mismatches when multiple users authenticated or when credentials were cleared and regenerated. Now each identity has its own DPoP key pair stored separately, ensuring the access token is always used with the same DPoP key it was bound to. This fixes the 401 Unauthorized errors when creating pods.
Changed createDPoP from static to instance method to work with the per-identity DPoP key management. Updated all callers to use the instance method via $this->oidc->createDPoP(). Fixes 'Non-static method cannot be called statically' error.
- Add migration for CSS credentials storage (email, client_id, client_secret) - Create CssAccountService for account management API integration - Add CssAccountController with endpoints for credential setup - Update PodService to use CSS Account Management API when credentials available - Add fallback to legacy pod creation methods - Implement automated client credentials token generation - Add routes for CSS credentials management This enables seamless pod creation through the Community Solid Server account management system.
- Create setup-css-credentials modal component - Update home controller to check CSS credentials after OIDC auth - Auto-show setup modal if credentials missing - Add serverUrl computed property for CSS account registration link - Integrate with modalsManager for seamless UX Users are now automatically prompted to set up CSS credentials after OIDC authentication, enabling pod creation.
- Use mut helper for proper two-way binding to prevent nested objects - Add validation error display for each field - Show detailed error messages in the modal - Add red border to invalid fields - Display all validation errors in error panel - Clear errors before new submission attempt Users now see clear validation errors without checking network logs.
- Set keepOpen: true to prevent modal from closing on errors - Initialize validationErrors to null in modal options - Users can now see error messages and retry without modal closing
- Use getWebIdFromIdToken() method instead of accessing non-existent array key - Extract issuer from WebID URL instead of token response - Match the same pattern used in PodService for consistency - Add better error messages for missing ID token This fixes the 'WebID not found in token response' error.
… pod creation - Get WebID from ID token using getWebIdFromIdToken() - Parse WebID URL to extract issuer (scheme + host + port) - Prevents fallback to localhost:3000 which fails in Docker - Matches the pattern used in CssAccountController This fixes the 'Failed to connect to localhost port 3000' error during pod creation.
- Change from createDPoP($url, $method) to createDPoP($method, $url) - Fixes 'DPoP proof htm mismatch' error - Method signature is createDPoP(string $method, string $url, ...) This fixes the CSS access token request failure.
- Add await to requestPodCreation.perform() call - Fix modal parameter to access modal.done() - Use pod.name instead of undefined this.podName - Show specific error message from response - Refresh pod list after successful creation - Fixes false 'Failed to create pod' error on success
Frontend:
- Add import-resources modal with checkboxes for resource types
- Update explorer controller with importResources action
- Replace 'Create Something' button with 'Import Resources'
- Show import progress and success/error messages
- Auto-refresh explorer after successful import
Backend:
- Create ResourceSyncService for converting Fleetops data to RDF
- Support Vehicles, Drivers, Contacts, and Orders
- Convert resources to Turtle/RDF format with proper namespaces
- Create containers for each resource type in pods
- Add PodController@importResources endpoint
- Add POST /pods/{podId}/import route
Features:
- Select multiple resource types to import
- Batch import up to 100 resources per type
- Store as .ttl files in pod containers
- Proper RDF formatting with Fleetbase namespace
- Error handling and logging
… better logging for resource import
…pod control URL not found
- Add css_password field to database schema - Store encrypted CSS password during credentials setup - Use email/password login instead of client credentials for pod management - Update pod creation to use CSS-Account-Token from password login - Update pod listing to use password-based authentication - This enables access to controls.account.pod endpoint for pod creation
- Simplify to use authenticated user's pod only - Add getPodUrlFromWebId helper method - Update import resources to not require podId - Add ContainerController for container management - Add container create/delete endpoints - Remove multi-pod complexity - Fix resource sync to work with authenticated pod
- Renamed 'Pods' to 'Data' throughout UI - Renamed 'Container' to 'Folder' for better UX - Simplified routes from multi-pod to single-pod architecture - Removed obsolete PodController and ContainerController - Added DataController for single-pod operations - Added folder management (create/delete) to PodService - Cleaned up 14 obsolete files (12 frontend + 2 backend) - Created 7 new files for data management - Fixed syntax error in PodService.php Breaking Changes: - Routes changed: pods/* -> data/* - API endpoints: /pods -> /data, /containers -> /data/folder - Frontend navigation updated See REFACTORING_SUMMARY.md for complete details.
- DataController was trying to access $identity->webid which doesn't exist - Changed to use $this->podService->getProfileData($identity) to get webid - Applied fix to all methods: index, showFolder, createFolder, deleteItem, importResources - This matches the pattern used in PodService::createPod()
- Modified getPodContents to detect if parameter is a URL or ID - If URL (starts with http:// or https://), use it directly - If ID, look it up in getUserPods() for backward compatibility - This enables single-pod architecture while maintaining old functionality - Updated error logging to use correct variable name
Modal Changes: - Created modals/create-solid-folder.hbs component - Created modals/import-solid-resources.hbs component - Updated data/index controller to use new modal names - Prevents naming collisions with other extensions Backend Fix: - Fixed array_merge error in SolidController::getServerConfig() - Handle null savedConfig with null coalescing operator - Prevents TypeError when solid.server setting doesn't exist
- Created app/components/modals/create-solid-folder.js - Created app/components/modals/import-solid-resources.js - Required for Ember addon component discovery - Allows modalsManager to find the components
Re-export Fixes: - Changed @fleetbase/solid-addon to @fleetbase/solid-engine - Fixed create-solid-folder.js re-export - Fixed import-solid-resources.js re-export Cleanup - Removed Unused Modals: - Deleted modals/backup-pod (multi-pod feature, not used) - Deleted modals/create-pod (multi-pod feature, not used) - Deleted modals/resync-pod (multi-pod feature, not used) - Deleted modals/import-resources (duplicate, replaced by import-solid-resources) - Removed corresponding app/ re-exports Remaining Modals (All Used): - modals/create-solid-folder ✓ - modals/import-solid-resources ✓ - modals/setup-css-credentials ✓
Issue:
- Folder creation was getting 401 Unauthorized errors
- Headers were being passed as $data instead of $options
- This prevented DPoP authentication headers from being added
Fix:
- Changed: request('put', $url, ['headers' => ...])
- To: request('put', $url, '', ['headers' => ...])
- Now passes empty string as body and headers in options
- DPoP authentication now works correctly
Note:
- storeResource() was already correct, no changes needed
- This should fix both folder creation and resource import
Issue:
- Folder URLs had double slashes: http://solid:3000/test//Test/
- This caused DPoP token URL mismatch
- Solid server rejected with 401 Unauthorized
Root Cause:
- When path is empty, the URL construction was:
rtrim(podUrl, '/') . '/' . ltrim('', '/') . '/' . folderName
- This resulted in: podUrl/ + / + folderName = podUrl//folderName
Fix:
- Only add path segment if path is not empty
- Properly trim slashes to avoid doubles
- Now correctly generates: http://solid:3000/test/Test/
Example:
- Before: http://solid:3000/test//Test/
- After: http://solid:3000/test/Test/
Issue:
- Folder creation still getting 401 errors
- Empty string '' was being sent as string body
- Laravel HTTP client sent it with withBody('', 'text/turtle')
- Solid server rejected empty string body
Root Cause:
- authenticatedRequest() checks: is_string($data)
- Empty string '' is a string, so it uses withBody()
- This sends Content-Length: 0 with text/turtle content type
- Solid server expects either no body or valid Turtle
Fix:
- Changed: request('put', $url, '', [...])
- To: request('put', $url, [], [...])
- Empty array [] triggers array path in authenticatedRequest()
- Uses Http::put($url, []) which sends proper empty PUT request
- Solid server should now accept the container creation
Debug Logging Added: 1. DPoP Token Details: - Full payload logging (jti, htm, htu, iat, ath) - Access token hash (ath) presence check - Helps verify DPoP proof correctness 2. Access Token Details: - Decoded JWT payload (webid, sub, client_id, scope) - Token expiration and issue time - CNF (confirmation) jkt claim - Helps verify token validity and scopes 3. HTTP Response Details: - Response status code - Response headers (from CSS) - Response body (error details) - Helps understand why CSS rejects requests Purpose: - Diagnose why folder creation and resource import fail with 401 - Verify DPoP authentication is correct - Check if access token has proper scopes - Identify CSS-specific requirements All logging uses Log::debug() to avoid cluttering production logs.
Root Cause Found: - CSS requires access tokens to have 'openid webid' scopes - Our access tokens had empty scope: "" - CSS was rejecting requests with WWW-Authenticate: Bearer scope="openid webid" Issue: - During OIDC client registration, we weren't specifying allowed scopes - CSS then didn't grant any scopes to the access token - Even though we requested scopes during authentication, the client wasn't registered to use them Fix: - Added 'scope' parameter to client registration request - Scope: 'openid profile webid offline_access solid' - This tells CSS that the client is allowed to request these scopes - Future access tokens will include the granted scopes Impact: - Users need to logout and login again to re-register the client - New access tokens will have proper scopes - Folder creation and resource import should work Note: - This matches CSS documentation for dynamic client registration - The scope parameter is optional but recommended for proper authorization
- Log CSS response status and body when registration fails - Log the request data being sent - Helps diagnose why CSS is rejecting the registration
CSS Error: - "scope must only contain Authorization Server supported scope values" - CSS rejected: 'openid profile webid offline_access solid' Root Cause: - CSS doesn't support 'profile' and 'solid' scopes - These are optional OIDC scopes not implemented by CSS Fix: - Removed 'profile' and 'solid' from scope list - Kept only CSS-supported scopes: 'openid webid offline_access' - Updated both registration and authentication scope requests Impact: - Client registration should now succeed - Access tokens will have 'openid webid offline_access' scopes - This matches CSS requirements for write operations
Root Cause: - OIDC tokens had empty scope: "" - CSS requires 'openid webid' scopes for write operations - CSS client credentials flow was only requesting 'webid' scope Solution: 1. Updated CssAccountService to request 'openid webid' scopes 2. Modified SolidIdentity::getAccessToken() to prefer CSS token 3. CSS token is obtained via client_credentials grant 4. Falls back to OIDC token if CSS credentials unavailable How It Works: - When CSS credentials are set up (email/password) - System uses css_client_id + css_client_secret - Gets fresh access token with proper scopes - This token has 'openid webid' and works for writes Benefits: - No need for manual credential tokens - Uses existing CSS account setup - Proper OAuth2 client_credentials flow - Token automatically refreshed on each request Impact: - Folder creation should now work - Resource import should now work - All write operations should succeed
Error: - CSS TOKEN FAILED: "The payload is invalid." - Decryption was failing Root Cause: - css_client_id is stored as PLAIN TEXT (line 238) - css_client_secret is stored ENCRYPTED (line 239) - But getAccessToken() was trying to decrypt BOTH - decrypt() on plain text = "The payload is invalid." Fix: - Only decrypt css_client_secret - Use css_client_id as-is (plain text) - Added better error logging with trace Impact: - CSS token should now be obtained successfully - Access token will have 'openid webid' scopes - Write operations should work
Error: - Argument #1 ($issuer) must be of type string, null given - config('solid.server.issuer') returned null - token_response.issuer didn't exist Fix: - Try token_response.iss (standard OIDC claim) - Fallback to token_response.issuer - Default to 'http://solid:3000/' if both are null Impact: - CSS token should now be obtained successfully - No more type errors
Added logs to show: - Whether css_client_id is set - Whether css_client_secret is set - When CSS token path is attempted - Helps diagnose why CSS token isn't being used This will show if CSS credentials are missing from database or if there's another issue preventing CSS token generation
Error: - [CSS ACCOUNT] Failed to get OIDC configuration Root Cause: - $issuer = 'http://solid:3000/' (with trailing slash) - Concatenated with '/.well-known/...' - Result: double slash breaks the URL Fix: - rtrim($issuer, '/') before concatenation - URL now: http://solid:3000/.well-known/openid-configuration ✓ Impact: - OIDC configuration should fetch successfully - CSS token should be obtained - Access token will have 'openid webid' scopes - Folder creation should finally work!
Based on deep-dive analysis, applied these critical fixes: Fix #1: Correct scope for client_credentials - Changed from 'scope' => 'openid webid' - To 'scope' => 'webid' - CSS v7.x client_credentials expects 'webid' only Fix #2: Send empty string body (not array) - Changed from request('put', $url, [], ...) - To request('put', $url, '', ...) - Arrays go through JSON/form branch - Empty string sends clean Turtle body Fix #3: Absolute URL handling - Already correct in createRequestUrl() - Returns absolute URLs as-is - Prevents DPoP htu mismatch Why these matter: - Wrong scope = token not accepted by CSS - Array body = malformed Turtle request - URL mangling = DPoP validation fails Credit: GPT-4 deep-dive analysis See: css-folder-creation-401-deep-dive.md
Critical change based on Solid Protocol specification: Before (WRONG): - PUT http://solid:3000/test/test/ - Creates resource at non-existent URI - CSS rejects: parent only allows POST After (CORRECT): - POST http://solid:3000/test/ - Slug: test - Link: <...#BasicContainer>; rel="type" - CSS creates container and returns Location header Why this matters: 1. WAC-Allow showed only 'read' access 2. Parent container Allow header: GET, POST (no PUT) 3. Solid Protocol requires POST for container creation 4. PUT only works when Accept-Put is advertised Changes: - PodService::createFolder() signature changed - Now takes parentUrl + folderName separately - Uses POST instead of PUT - Adds Slug header with folder name - Gets created URL from Location header References: - Solid Protocol §5.2: Container creation - response.md analysis - css-folder-creation-401-deep-dive.md
Critical fix based on GPT analysis: Problem: - CSS client_credentials tokens have scope: null - No write permissions granted to automation tokens - WAC-Allow shows only 'read' access - All write operations fail with 401 Root Cause: - getAccessToken() preferred CSS client credentials - These tokens authenticate as the application - Not bound to user's WebID - CSS treats them as anonymous for writes Solution: - Reversed preference order - Now uses user's OIDC token FIRST - Falls back to CSS credentials only if no OIDC token - OIDC token has proper WebID binding and permissions Why this works: 1. User's OIDC token from authorization code flow 2. Bound to user's WebID 3. Has proper permissions on user's pod 4. WAC allows writes for authenticated user Changes: - SolidIdentity::getAccessToken() logic reversed - Logs show [USING OIDC TOKEN] instead of [USING CSS TOKEN] - CSS credentials now only for automation scenarios Impact: - Folder creation should work - Resource import should work - All write operations should succeed - User acts as themselves, not as application References: - GPT analysis: CSS client tokens lack write permissions - WAC-Allow header showed only read access - Solid Protocol: user tokens have pod permissions
THE REAL ROOT CAUSE - ACL Permissions After extensive debugging, the actual issue was NOT authentication or token problems. CSS default root ACL only grants acl:Read to authenticated users. Without acl:Write or acl:Append permissions, ALL write operations fail with 401 Unauthorized. Evidence: - WAC-Allow header consistently showed: user="read",public="read" - No 'write' or 'append' in the permissions - CSS applies root ACL to all descendants - Must update ACL before any write operations Solution Implemented: 1. AclService - New service for ACL management - hasWritePermissions() - Check WAC-Allow header - grantWritePermissions() - PUT ACL document to <podRoot>/.acl - ensureWritePermissions() - Check and update if needed 2. ACL Document Format (Turtle) - Owner gets: acl:Read, acl:Write, acl:Control - Fleetbase gets: acl:Append, acl:Read - Uses acl:default for inheritance to descendants 3. Integration - DataController checks ACL before folder creation - Automatically updates ACL if lacking write permissions - Returns 403 if ACL update fails How It Works: 1. User attempts to create folder 2. System checks pod root WAC-Allow header 3. If no write/append, PUT new ACL to <podRoot>/.acl 4. ACL grants acl:Append + acl:Read to user's WebID 5. Subsequent operations succeed with proper permissions Why This Is Correct: - CSS has no built-in UI for ACL management - Each pod needs explicit ACL configuration - Root ACL inherits to all children - This is expected CSS behavior, not a bug Files Added: - server/src/Services/AclService.php - ACL_SOLUTION.md (documentation) Files Modified: - server/src/Http/Controllers/DataController.php References: - solid_acl_explanation.pdf - Solid Web Access Control spec - CSS documentation This should finally resolve the 401 errors!
THE SCOPE ISSUE
The access token has empty scope ("") even though we're requesting
'openid webid offline_access' during registration and authentication.
Root Cause:
- CSS caches client registrations
- Client was registered BEFORE we added scope parameter
- Even though we now send scope, CSS uses old registration
- Old registration didn't specify allowed scopes
- CSS rejects scope requests from clients without pre-approved scopes
Evidence from logs:
- [ACCESS TOKEN PAYLOAD] shows "scope": ""
- WWW-Authenticate header requires: scope="openid webid"
- ACL UPDATE FAILED with 401 due to missing webid scope
Solution:
Changed CLIENT_NAME from 'Fleetbase' to 'Fleetbase-v2' to force
CSS to create a NEW client registration with proper scopes.
This will:
1. Register new client with scope: 'openid webid offline_access'
2. CSS will allow this client to request these scopes
3. Access token will include webid scope
4. ACL operations will succeed
User must logout and login again to trigger new registration.
CSS SCOPE LIMITATION After extensive investigation, CSS is NOT granting the webid scope even when properly requested during client registration and authentication. Evidence: - Client re-registered with new name (Fleetbase-v2) - Scope 'openid webid offline_access' requested in registration - Scope ['openid', 'webid', 'offline_access'] added during auth - Access token still has "scope": "" (empty) - New client_id confirms registration succeeded - CSS simply not granting the requested scopes Root Cause: This appears to be a CSS server configuration issue. The CSS instance is configured to NOT grant scopes, regardless of client requests. Impact: - Cannot programmatically update ACLs (requires webid scope) - Cannot use AclService to grant write permissions - Must manually set up root ACL for each pod Solution: Created MANUAL_ACL_SETUP.md with three methods: 1. Using curl to PUT ACL file 2. Direct file system access 3. CSS admin API (if available) The manual ACL setup grants: - acl:Read, acl:Write, acl:Control to pod owner - acl:default for inheritance to all descendants Once root ACL is set up: - Folder creation will work - Resource import will work - All write operations will succeed This is the standard approach for CSS pod initialization when programmatic ACL management is not available. Files Added: - MANUAL_ACL_SETUP.md Next Steps: User must manually create root ACL using one of the provided methods.
- Override requestTokens() to add scope parameter to token request body - Jumbojett library does not include scope in authorization_code flow - CSS requires scope in token request to issue tokens with proper scopes - This fixes 401 Unauthorized errors for write operations - Also fixed composer.json repository path to solid-oidc-client Root cause: Access tokens were issued with empty scope because token exchange request did not include scope parameter. Now sends 'scope=openid webid offline_access' in token request.
…okens - Replace $this->clientID with $this->getClientID() - Replace $this->clientSecret with $this->getClientSecret() - Parent class properties are protected, not public - Fixes 'Undefined property' error during token exchange
- Scopes added in authenticate() are not persisted to callback handler - New OIDC client instance is created for token exchange - Must add scopes again before calling requestTokens() - This ensures scope parameter is included in token request body - Should fix empty scope in access tokens
- Scope should only be sent in authorization request, not token request - This follows the Inrupt Solid Client implementation - CSS should persist the granted scope from authorization - Added logging to verify authorization URL includes scope - Removed non-standard scope parameter from token exchange Per OIDC/OAuth2 spec and Inrupt's implementation, the scope is only sent in the initial authorization request. The authorization server should remember the granted scope and include it in issued tokens.
- Jumbojett library doesn't expose getAuthorizationURL() - authenticate() builds URL internally and redirects - Simplified logging to just show scopes being set
- Log what CSS actually returns in token response - Check if scope is included in the response - This will help debug why tokens have empty scope
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
…source syncing started