This commit addresses the "Login not detected" issue after completing OAuth login via elicitation by unifying the session architecture and adding comprehensive visibility into background session status. ## Changes ### 1. Enhanced check_logged_in with comprehensive logging (oauth_tools.py) - Added detailed logging at each step of token lookup - Implemented fallback strategy: first search by provisioning_client_id, then fall back to user_id lookup - This allows detection of refresh tokens created via any flow (elicitation or browser login) - Log messages include flow_type, provisioned_at, and provisioning_client_id for debugging ### 2. Unified session architecture (browser_oauth_routes.py) - Browser login now stores provisioning_client_id=state when saving refresh token - This makes browser and elicitation flows consistent - both can be found by the same state parameter - Treats Flow 2 (elicitation) and browser login as the same "background session" ### 3. Enhanced /user/page with session status (userinfo_routes.py) - Added comprehensive background access section showing: - Background Access: Granted/Not Granted (with visual indicators) - Flow Type: browser/flow2/hybrid - Provisioned At: timestamp - Token Audience: nextcloud/mcp - Scopes: detailed scope list - Status displayed regardless of which flow created the session (browser login or elicitation) ### 4. Added revoke functionality (userinfo_routes.py, app.py) - New POST endpoint: /user/revoke - Allows users to revoke background access (delete refresh token) - Browser session cookie remains valid for UI access - Confirmation dialog before revocation - Success page with auto-redirect back to /user/page - Registered route in app.py browser_routes ## Testing All tests pass: - 6/6 login elicitation tests pass - 21/21 core OAuth tests pass - Comprehensive logging helps debug future issues ## Fixes Resolves: "Login not detected. Please ensure you completed the login at the provided URL before clicking OK." The issue occurred because elicitation and browser login created separate sessions. Now they are unified under the same architecture. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
112 lines
4.0 KiB
Bash
Executable File
112 lines
4.0 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
echo "Applying audience fix to Keycloak realm for ALL clients..."
|
|
|
|
# Get admin token
|
|
ADMIN_TOKEN=$(curl -s -X POST "http://localhost:8888/realms/master/protocol/openid-connect/token" \
|
|
-d "grant_type=password" \
|
|
-d "client_id=admin-cli" \
|
|
-d "username=admin" \
|
|
-d "password=admin" | jq -r '.access_token')
|
|
|
|
if [ -z "$ADMIN_TOKEN" ] || [ "$ADMIN_TOKEN" == "null" ]; then
|
|
echo "Failed to get admin token. Is Keycloak running?"
|
|
exit 1
|
|
fi
|
|
|
|
echo "Got admin token"
|
|
|
|
# Create a default client scope with audience mapper that will apply to ALL clients
|
|
echo "Creating default audience scope..."
|
|
|
|
# First, delete if it exists
|
|
curl -s -X DELETE "http://localhost:8888/admin/realms/nextcloud-mcp/client-scopes/default-audience" \
|
|
-H "Authorization: Bearer $ADMIN_TOKEN" 2>/dev/null
|
|
|
|
# Create new client scope
|
|
SCOPE_RESPONSE=$(curl -s -X POST "http://localhost:8888/admin/realms/nextcloud-mcp/client-scopes" \
|
|
-H "Authorization: Bearer $ADMIN_TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"name": "default-audience",
|
|
"protocol": "openid-connect",
|
|
"attributes": {
|
|
"include.in.token.scope": "false",
|
|
"display.on.consent.screen": "false"
|
|
},
|
|
"protocolMappers": [
|
|
{
|
|
"name": "mcp-server-audience",
|
|
"protocol": "openid-connect",
|
|
"protocolMapper": "oidc-audience-mapper",
|
|
"consentRequired": false,
|
|
"config": {
|
|
"included.client.audience": "nextcloud-mcp-server",
|
|
"access.token.claim": "true",
|
|
"id.token.claim": "false"
|
|
}
|
|
},
|
|
{
|
|
"name": "mcp-url-audience",
|
|
"protocol": "openid-connect",
|
|
"protocolMapper": "oidc-audience-mapper",
|
|
"consentRequired": false,
|
|
"config": {
|
|
"included.custom.audience": "http://localhost:8002",
|
|
"access.token.claim": "true",
|
|
"id.token.claim": "false"
|
|
}
|
|
}
|
|
]
|
|
}')
|
|
|
|
# Get the scope ID
|
|
SCOPE_ID=$(curl -s -X GET "http://localhost:8888/admin/realms/nextcloud-mcp/client-scopes" \
|
|
-H "Authorization: Bearer $ADMIN_TOKEN" | jq -r '.[] | select(.name == "default-audience") | .id')
|
|
|
|
if [ -z "$SCOPE_ID" ] || [ "$SCOPE_ID" == "null" ]; then
|
|
echo "Failed to create client scope"
|
|
exit 1
|
|
fi
|
|
|
|
echo "Created client scope with ID: $SCOPE_ID"
|
|
|
|
# Make this a default client scope (applies to ALL clients automatically)
|
|
curl -s -X PUT "http://localhost:8888/admin/realms/nextcloud-mcp/default-default-client-scopes/$SCOPE_ID" \
|
|
-H "Authorization: Bearer $ADMIN_TOKEN"
|
|
|
|
echo "Made it a default client scope"
|
|
|
|
# Now update ALL existing clients to use this scope
|
|
echo "Updating existing clients..."
|
|
|
|
# Get all clients
|
|
CLIENTS=$(curl -s -X GET "http://localhost:8888/admin/realms/nextcloud-mcp/clients" \
|
|
-H "Authorization: Bearer $ADMIN_TOKEN" | jq -r '.[] | select(.clientId != "admin-cli" and .clientId != "account" and .clientId != "broker" and .clientId != "realm-management" and .clientId != "security-admin-console" and .clientId != "account-console") | .id')
|
|
|
|
for CLIENT_ID in $CLIENTS; do
|
|
CLIENT_NAME=$(curl -s -X GET "http://localhost:8888/admin/realms/nextcloud-mcp/clients/$CLIENT_ID" \
|
|
-H "Authorization: Bearer $ADMIN_TOKEN" | jq -r '.clientId')
|
|
|
|
echo " Adding scope to client: $CLIENT_NAME"
|
|
|
|
# Add the default scope to this client
|
|
curl -s -X PUT "http://localhost:8888/admin/realms/nextcloud-mcp/clients/$CLIENT_ID/default-client-scopes/$SCOPE_ID" \
|
|
-H "Authorization: Bearer $ADMIN_TOKEN"
|
|
done
|
|
|
|
echo ""
|
|
echo "Testing with a new token..."
|
|
TOKEN=$(curl -s -X POST 'http://localhost:8888/realms/nextcloud-mcp/protocol/openid-connect/token' \
|
|
-d 'grant_type=password' \
|
|
-d 'client_id=nextcloud-mcp-server' \
|
|
-d 'client_secret=mcp-secret-change-in-production' \
|
|
-d 'username=admin' \
|
|
-d 'password=admin' | jq -r '.access_token')
|
|
|
|
echo "Token audience:"
|
|
echo "$TOKEN" | cut -d. -f2 | base64 -d 2>/dev/null | python3 -c "import sys,json; d=json.load(sys.stdin); print('aud:', d.get('aud', 'NO AUD'))"
|
|
|
|
echo ""
|
|
echo "✅ Audience configuration applied to ALL clients in the realm!"
|
|
echo "New clients registered by Gemini will automatically get these audiences." |