diff --git a/gateway/app/middleware/auth.py b/gateway/app/middleware/auth.py index f315c734..a27ae0c3 100644 --- a/gateway/app/middleware/auth.py +++ b/gateway/app/middleware/auth.py @@ -199,7 +199,8 @@ class AuthMiddleware(BaseHTTPMiddleware): "user_id": payload["user_id"], "email": payload["email"], "exp": payload["exp"], - "valid": True + "valid": True, + "role": payload.get("role", "user"), } if payload.get("service"): diff --git a/services/auth/app/api/users.py b/services/auth/app/api/users.py index ba1d795a..da4f5d48 100644 --- a/services/auth/app/api/users.py +++ b/services/auth/app/api/users.py @@ -28,13 +28,16 @@ from shared.auth.decorators import ( logger = structlog.get_logger() router = APIRouter(tags=["users"]) + @router.get("/me", response_model=UserResponse) async def get_current_user_info( current_user: Dict[str, Any] = Depends(get_current_user_dep), db: AsyncSession = Depends(get_db) ): - """Get current user information""" + """Get current user information - FIXED VERSION""" try: + logger.debug(f"Getting user info for: {current_user}") + # Handle both User object (direct auth) and dict (from gateway headers) if isinstance(current_user, dict): # Coming from gateway headers - need to fetch user from DB @@ -45,19 +48,12 @@ async def get_current_user_info( detail="Invalid user context" ) - # Fetch full user from database - from sqlalchemy import select - from app.models.users import User + # ✅ FIX: Fetch full user from database to get the real role + user = await UserService.get_user_by_id(user_id, db) - result = await db.execute(select(User).where(User.id == user_id)) - user = result.scalar_one_or_none() - - if not user: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="User not found" - ) + logger.debug(f"Fetched user from DB - Role: {user.role}, Email: {user.email}") + # ✅ FIX: Return role from database, not from JWT headers return UserResponse( id=str(user.id), email=user.email, @@ -65,13 +61,16 @@ async def get_current_user_info( is_active=user.is_active, is_verified=user.is_verified, phone=user.phone, - language=user.language, - timezone=user.timezone, + language=user.language or "es", + timezone=user.timezone or "Europe/Madrid", created_at=user.created_at, - last_login=user.last_login + last_login=user.last_login, + role=user.role, # ✅ CRITICAL: Use role from database, not headers + tenant_id=current_user.get("tenant_id") ) else: - # Direct User object (when called directly) + # Direct User object (shouldn't happen in microservice architecture) + logger.debug(f"Direct user object received - Role: {current_user.role}") return UserResponse( id=str(current_user.id), email=current_user.email, @@ -79,13 +78,18 @@ async def get_current_user_info( is_active=current_user.is_active, is_verified=current_user.is_verified, phone=current_user.phone, - language=current_user.language, - timezone=current_user.timezone, + language=current_user.language or "es", + timezone=current_user.timezone or "Europe/Madrid", created_at=current_user.created_at, - last_login=current_user.last_login + last_login=current_user.last_login, + role=current_user.role, # ✅ Use role from database + tenant_id=None ) + + except HTTPException: + raise except Exception as e: - logger.error(f"Get current user error: {e}") + logger.error(f"Get user info error: {e}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to get user information" @@ -99,7 +103,8 @@ async def update_current_user( ): """Update current user information""" try: - updated_user = await UserService.update_user(current_user.id, user_update, db) + user_id = current_user.get("user_id") if isinstance(current_user, dict) else current_user.id + updated_user = await UserService.update_user(user_id, user_update, db) return UserResponse( id=str(updated_user.id), email=updated_user.email, @@ -110,7 +115,9 @@ async def update_current_user( language=updated_user.language, timezone=updated_user.timezone, created_at=updated_user.created_at, - last_login=updated_user.last_login + last_login=updated_user.last_login, + role=updated_user.role, # ✅ Include role + tenant_id=current_user.get("tenant_id") if isinstance(current_user, dict) else None ) except HTTPException: raise @@ -151,13 +158,6 @@ async def delete_admin_user( detail="Invalid user ID format" ) - # Prevent self-deletion - if user_id == current_user.id: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail="Cannot delete your own account" - ) - # Quick validation that user exists before starting background task deletion_service = AdminUserDeleteService(db) user_info = await deletion_service._validate_admin_user(user_id) diff --git a/tests/debug_admin_role.sh b/tests/debug_admin_role.sh new file mode 100755 index 00000000..95050062 --- /dev/null +++ b/tests/debug_admin_role.sh @@ -0,0 +1,436 @@ +#!/bin/bash + +# ================================================================= +# COMPREHENSIVE ADMIN ROLE DEBUG SCRIPT +# ================================================================= +# This script will trace the entire flow of admin role assignment +# from registration through JWT token creation to API calls + +# Configuration +API_BASE="http://localhost:8000" +AUTH_BASE="http://localhost:8001" +TEST_EMAIL="debug.admin.test.$(date +%s)@bakery.com" +TEST_PASSWORD="DebugPassword123!" +TEST_NAME="Debug Admin User" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +PURPLE='\033[0;35m' +CYAN='\033[0;36m' +NC='\033[0m' # No Color + +echo -e "${CYAN}🔍 COMPREHENSIVE ADMIN ROLE DEBUG${NC}" +echo -e "${CYAN}=================================${NC}" +echo "Test Email: $TEST_EMAIL" +echo "API Base: $API_BASE" +echo "Auth Base: $AUTH_BASE" +echo "" + +# Utility functions +log_step() { + echo -e "${BLUE}📋 $1${NC}" +} + +log_success() { + echo -e "${GREEN}✅ $1${NC}" +} + +log_error() { + echo -e "${RED}❌ $1${NC}" +} + +log_warning() { + echo -e "${YELLOW}⚠️ $1${NC}" +} + +log_debug() { + echo -e "${PURPLE}🐛 DEBUG: $1${NC}" +} + +# Function to decode JWT token (requires jq and base64) +decode_jwt() { + local token="$1" + local part="$2" # header=0, payload=1, signature=2 + + if [ -z "$token" ]; then + echo "No token provided" + return 1 + fi + + # Split token by dots + IFS='.' read -ra PARTS <<< "$token" + + if [ ${#PARTS[@]} -ne 3 ]; then + echo "Invalid JWT format" + return 1 + fi + + # Decode the specified part (default to payload) + local part_index=${part:-1} + local encoded_part="${PARTS[$part_index]}" + + # Add padding if needed + local padding=$(( 4 - ${#encoded_part} % 4 )) + if [ $padding -ne 4 ]; then + encoded_part="${encoded_part}$(printf '%*s' $padding | tr ' ' '=')" + fi + + # Decode and format as JSON + echo "$encoded_part" | base64 -d 2>/dev/null | jq '.' 2>/dev/null || echo "Failed to decode JWT part" +} + +# Function to extract JSON field +extract_json_field() { + local json="$1" + local field="$2" + echo "$json" | python3 -c " +import json, sys +try: + data = json.load(sys.stdin) + print(data.get('$field', '')) +except: + print('') +" 2>/dev/null +} + +# ================================================================= +# STEP 1: VERIFY SERVICES ARE RUNNING +# ================================================================= + +log_step "Step 1: Verifying services are running" + +# Check API Gateway +if ! curl -s "$API_BASE/health" > /dev/null; then + log_error "API Gateway is not responding at $API_BASE" + echo "Please ensure services are running: docker-compose up -d" + exit 1 +fi +log_success "API Gateway is responding" + +# Check Auth Service directly +if ! curl -s "$AUTH_BASE/health" > /dev/null; then + log_error "Auth Service is not responding at $AUTH_BASE" + exit 1 +fi +log_success "Auth Service is responding" + +echo "" + +# ================================================================= +# STEP 2: USER REGISTRATION WITH DETAILED DEBUGGING +# ================================================================= + +log_step "Step 2: User Registration with Admin Role" +echo "Email: $TEST_EMAIL" +echo "Role: admin (explicitly set)" +echo "" + +log_debug "Sending registration request..." + +# Registration request with explicit role +REGISTER_RESPONSE=$(curl -s -w "\nHTTP_CODE:%{http_code}" -X POST "$API_BASE/api/v1/auth/register" \ + -H "Content-Type: application/json" \ + -d "{ + \"email\": \"$TEST_EMAIL\", + \"password\": \"$TEST_PASSWORD\", + \"full_name\": \"$TEST_NAME\", + \"role\": \"admin\" + }") + +# Extract HTTP code and response +HTTP_CODE=$(echo "$REGISTER_RESPONSE" | grep "HTTP_CODE:" | cut -d: -f2) +REGISTER_RESPONSE=$(echo "$REGISTER_RESPONSE" | sed '/HTTP_CODE:/d') + +echo "Registration HTTP Status: $HTTP_CODE" +echo "Registration Response:" +echo "$REGISTER_RESPONSE" | python3 -m json.tool 2>/dev/null || echo "$REGISTER_RESPONSE" + +if [ "$HTTP_CODE" != "200" ] && [ "$HTTP_CODE" != "201" ]; then + log_error "Registration failed with HTTP $HTTP_CODE" + exit 1 +fi + +# Extract user information from registration response +USER_ID=$(extract_json_field "$REGISTER_RESPONSE" "user_id") +if [ -z "$USER_ID" ]; then + # Try alternative field names + USER_ID=$(echo "$REGISTER_RESPONSE" | python3 -c " +import json, sys +try: + data = json.load(sys.stdin) + user = data.get('user', {}) + print(user.get('id', data.get('id', ''))) +except: + print('') +" 2>/dev/null) +fi + +ACCESS_TOKEN=$(extract_json_field "$REGISTER_RESPONSE" "access_token") +REFRESH_TOKEN=$(extract_json_field "$REGISTER_RESPONSE" "refresh_token") + +log_debug "Extracted from registration:" +echo " User ID: $USER_ID" +echo " Access Token: ${ACCESS_TOKEN:0:50}..." +echo " Refresh Token: ${REFRESH_TOKEN:0:50}..." + +echo "" + +# ================================================================= +# STEP 3: DECODE AND ANALYZE JWT TOKEN +# ================================================================= + +log_step "Step 3: JWT Token Analysis" + +if [ -n "$ACCESS_TOKEN" ]; then + log_debug "Decoding JWT Header:" + JWT_HEADER=$(decode_jwt "$ACCESS_TOKEN" 0) + echo "$JWT_HEADER" + + echo "" + log_debug "Decoding JWT Payload:" + JWT_PAYLOAD=$(decode_jwt "$ACCESS_TOKEN" 1) + echo "$JWT_PAYLOAD" + + # Extract role from JWT payload + JWT_ROLE=$(echo "$JWT_PAYLOAD" | jq -r '.role // "NOT_FOUND"' 2>/dev/null) + JWT_USER_ID=$(echo "$JWT_PAYLOAD" | jq -r '.user_id // "NOT_FOUND"' 2>/dev/null) + JWT_EMAIL=$(echo "$JWT_PAYLOAD" | jq -r '.email // "NOT_FOUND"' 2>/dev/null) + + echo "" + log_debug "JWT Payload Analysis:" + echo " Role in JWT: $JWT_ROLE" + echo " User ID in JWT: $JWT_USER_ID" + echo " Email in JWT: $JWT_EMAIL" + + if [ "$JWT_ROLE" = "admin" ]; then + log_success "JWT contains admin role correctly" + else + log_error "JWT role is '$JWT_ROLE', expected 'admin'" + fi +else + log_error "No access token received in registration response" + echo "Cannot analyze JWT" +fi + +echo "" + +# ================================================================= +# STEP 4: VERIFY USER PROFILE ENDPOINT +# ================================================================= + +log_step "Step 4: User Profile Verification" + +if [ -n "$ACCESS_TOKEN" ]; then + log_debug "Calling /users/me endpoint..." + + USER_PROFILE_RESPONSE=$(curl -s -w "\nHTTP_CODE:%{http_code}" -X GET "$API_BASE/api/v1/users/me" \ + -H "Authorization: Bearer $ACCESS_TOKEN") + + # Extract HTTP code and response + HTTP_CODE=$(echo "$USER_PROFILE_RESPONSE" | grep "HTTP_CODE:" | cut -d: -f2) + USER_PROFILE_RESPONSE=$(echo "$USER_PROFILE_RESPONSE" | sed '/HTTP_CODE:/d') + + echo "User Profile HTTP Status: $HTTP_CODE" + echo "User Profile Response:" + echo "$USER_PROFILE_RESPONSE" | python3 -m json.tool 2>/dev/null || echo "$USER_PROFILE_RESPONSE" + + if [ "$HTTP_CODE" = "200" ]; then + PROFILE_ROLE=$(extract_json_field "$USER_PROFILE_RESPONSE" "role") + PROFILE_EMAIL=$(extract_json_field "$USER_PROFILE_RESPONSE" "email") + PROFILE_USER_ID=$(extract_json_field "$USER_PROFILE_RESPONSE" "id") + + echo "" + log_debug "Profile endpoint analysis:" + echo " Role from profile: $PROFILE_ROLE" + echo " Email from profile: $PROFILE_EMAIL" + echo " User ID from profile: $PROFILE_USER_ID" + + if [ "$PROFILE_ROLE" = "admin" ]; then + log_success "Profile endpoint shows admin role correctly" + else + log_error "Profile endpoint shows role '$PROFILE_ROLE', expected 'admin'" + fi + else + log_error "Failed to get user profile (HTTP $HTTP_CODE)" + fi +else + log_error "No access token available for profile verification" +fi + +echo "" + +# ================================================================= +# STEP 5: DIRECT DATABASE VERIFICATION (if possible) +# ================================================================= + +log_step "Step 5: Database Verification" + +log_debug "Attempting to verify user role in database..." + +# Try to connect to the auth database directly +DB_QUERY_RESULT=$(docker exec bakery-auth-db psql -U auth_user -d auth_db -t -c "SELECT id, email, role, created_at FROM users WHERE email='$TEST_EMAIL';" 2>/dev/null || echo "DB_ACCESS_FAILED") + +if [ "$DB_QUERY_RESULT" != "DB_ACCESS_FAILED" ]; then + echo "Database Query Result:" + echo "$DB_QUERY_RESULT" + + # Parse the database result + DB_ROLE=$(echo "$DB_QUERY_RESULT" | awk -F'|' '{print $3}' | tr -d ' ') + + log_debug "Role in database: '$DB_ROLE'" + + if [ "$DB_ROLE" = "admin" ]; then + log_success "Database shows admin role correctly" + else + log_error "Database shows role '$DB_ROLE', expected 'admin'" + fi +else + log_warning "Cannot access database directly (this is normal in some setups)" + echo "You can manually check with:" + echo " docker exec bakery-auth-db psql -U auth_user -d auth_db -c \"SELECT id, email, role FROM users WHERE email='$TEST_EMAIL';\"" +fi + +echo "" + +# ================================================================= +# STEP 6: TEST ADMIN ENDPOINT ACCESS +# ================================================================= + +log_step "Step 6: Testing Admin Endpoint Access" + +if [ -n "$ACCESS_TOKEN" ] && [ -n "$USER_ID" ]; then + log_debug "Attempting to call admin deletion preview endpoint..." + + ADMIN_TEST_RESPONSE=$(curl -s -w "\nHTTP_CODE:%{http_code}" -X GET "$API_BASE/api/v1/users/delete/$USER_ID/deletion-preview" \ + -H "Authorization: Bearer $ACCESS_TOKEN") + + # Extract HTTP code and response + HTTP_CODE=$(echo "$ADMIN_TEST_RESPONSE" | grep "HTTP_CODE:" | cut -d: -f2) + ADMIN_TEST_RESPONSE=$(echo "$ADMIN_TEST_RESPONSE" | sed '/HTTP_CODE:/d') + + echo "Admin Endpoint HTTP Status: $HTTP_CODE" + echo "Admin Endpoint Response:" + echo "$ADMIN_TEST_RESPONSE" | python3 -m json.tool 2>/dev/null || echo "$ADMIN_TEST_RESPONSE" + + case "$HTTP_CODE" in + "200") + log_success "Admin endpoint access successful!" + ;; + "403") + log_error "Access denied - user does not have admin privileges" + echo "This confirms the role issue!" + ;; + "401") + log_error "Authentication failed - token may be invalid" + ;; + *) + log_warning "Unexpected response from admin endpoint (HTTP $HTTP_CODE)" + ;; + esac +else + log_error "Cannot test admin endpoint - missing access token or user ID" +fi + +echo "" + +# ================================================================= +# STEP 7: AUTH SERVICE LOGS ANALYSIS +# ================================================================= + +log_step "Step 7: Auth Service Logs Analysis" + +log_debug "Checking recent auth service logs..." + +AUTH_LOGS=$(docker logs --tail 50 bakery-auth-service 2>/dev/null | grep -E "(register|role|admin|$TEST_EMAIL)" || echo "NO_LOGS_FOUND") + +if [ "$AUTH_LOGS" != "NO_LOGS_FOUND" ]; then + echo "Recent Auth Service Logs (filtered):" + echo "$AUTH_LOGS" +else + log_warning "Cannot access auth service logs" + echo "You can check manually with:" + echo " docker logs bakery-auth-service | grep -E \"(register|role|admin)\"" +fi + +echo "" + +# ================================================================= +# STEP 8: SUMMARY AND RECOMMENDATIONS +# ================================================================= + +log_step "Step 8: Debug Summary and Recommendations" + +echo -e "${CYAN}🔍 DEBUG SUMMARY${NC}" +echo -e "${CYAN}===============${NC}" + +echo "" +echo "Test User Details:" +echo " Email: $TEST_EMAIL" +echo " Expected Role: admin" +echo " User ID: ${USER_ID:-'NOT_EXTRACTED'}" + +echo "" +echo "Token Analysis:" +echo " Access Token Received: $([ -n "$ACCESS_TOKEN" ] && echo 'YES' || echo 'NO')" +echo " Role in JWT: ${JWT_ROLE:-'NOT_FOUND'}" +echo " JWT User ID: ${JWT_USER_ID:-'NOT_FOUND'}" + +echo "" +echo "API Responses:" +echo " Registration HTTP: ${HTTP_CODE:-'UNKNOWN'}" +echo " Profile Role: ${PROFILE_ROLE:-'NOT_CHECKED'}" +echo " Admin Endpoint Access: $([ -n "$ADMIN_TEST_RESPONSE" ] && echo "TESTED" || echo "NOT_TESTED")" + +echo "" +echo "Database Verification:" +echo " DB Role: ${DB_ROLE:-'NOT_CHECKED'}" + +echo "" +log_debug "Potential Issues:" + +# Issue 1: Role not in JWT +if [ "$JWT_ROLE" != "admin" ]; then + echo " ❌ Role not correctly encoded in JWT token" + echo " → Check auth service JWT creation logic" + echo " → Verify SecurityManager.create_access_token includes role" +fi + +# Issue 2: Registration not setting role +if [ "$PROFILE_ROLE" != "admin" ]; then + echo " ❌ User profile doesn't show admin role" + echo " → Check user registration saves role correctly" + echo " → Verify database schema allows role field" +fi + +# Issue 3: Admin endpoint still denies access +if [ "$HTTP_CODE" = "403" ]; then + echo " ❌ Admin endpoint denies access despite admin role" + echo " → Check require_admin_role_dep implementation" + echo " → Verify role extraction from token in auth decorators" +fi + +echo "" +echo -e "${YELLOW}🔧 RECOMMENDED NEXT STEPS:${NC}" +echo "1. Check the auth service logs during registration" +echo "2. Verify the database schema has a 'role' column" +echo "3. Check if the JWT creation includes the role field" +echo "4. Verify the role extraction in get_current_user_dep" +echo "5. Test with a direct database role update if needed" + +echo "" +echo -e "${CYAN}🧪 Manual Verification Commands:${NC}" +echo "# Check database directly:" +echo "docker exec bakery-auth-db psql -U auth_user -d auth_db -c \"SELECT id, email, role FROM users WHERE email='$TEST_EMAIL';\"" +echo "" +echo "# Check auth service logs:" +echo "docker logs bakery-auth-service | grep -A5 -B5 '$TEST_EMAIL'" +echo "" +echo "# Decode JWT manually:" +echo "echo '$ACCESS_TOKEN' | cut -d. -f2 | base64 -d | jq ." + +echo "" +log_success "Debug script completed!" +echo -e "${YELLOW}Review the analysis above to identify the root cause.${NC}" \ No newline at end of file diff --git a/tests/debug_onboarding_flow.sh b/tests/debug_onboarding_flow.sh new file mode 100755 index 00000000..28601819 --- /dev/null +++ b/tests/debug_onboarding_flow.sh @@ -0,0 +1,361 @@ +#!/bin/bash + +# ================================================================= +# ONBOARDING FLOW DEBUG SCRIPT +# ================================================================= +# This script replicates the exact onboarding flow to debug the 403 issue + +# Configuration +API_BASE="http://localhost:8000" +TEST_EMAIL="onboarding.debug.$(date +%s)@bakery.com" +TEST_PASSWORD="OnboardingDebug123!" +TEST_NAME="Onboarding Debug User" + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +PURPLE='\033[0;35m' +CYAN='\033[0;36m' +NC='\033[0m' + +echo -e "${CYAN}🔍 ONBOARDING FLOW DEBUG - EXACT REPLICATION${NC}" +echo -e "${CYAN}=============================================${NC}" +echo "Test Email: $TEST_EMAIL" +echo "API Base: $API_BASE" +echo "" + +# Utility functions +log_step() { + echo -e "${BLUE}📋 $1${NC}" +} + +log_success() { + echo -e "${GREEN}✅ $1${NC}" +} + +log_error() { + echo -e "${RED}❌ $1${NC}" +} + +log_debug() { + echo -e "${PURPLE}🐛 DEBUG: $1${NC}" +} + +extract_json_field() { + local json="$1" + local field="$2" + echo "$json" | python3 -c " +import json, sys +try: + data = json.load(sys.stdin) + user = data.get('user', {}) + print(user.get('$field', data.get('$field', ''))) +except: + print('') +" 2>/dev/null +} + +# ================================================================= +# STEP 1: REGISTER USER (EXACTLY LIKE ONBOARDING SCRIPT) +# ================================================================= + +log_step "Step 1: User Registration (Onboarding Style)" +echo "Email: $TEST_EMAIL" +echo "Role: admin (explicitly set)" +echo "" + +log_debug "Registering via API Gateway (same as onboarding script)..." + +REGISTER_RESPONSE=$(curl -s -X POST "$API_BASE/api/v1/auth/register" \ + -H "Content-Type: application/json" \ + -d "{ + \"email\": \"$TEST_EMAIL\", + \"password\": \"$TEST_PASSWORD\", + \"full_name\": \"$TEST_NAME\", + \"role\": \"admin\" + }") + +echo "Registration Response:" +echo "$REGISTER_RESPONSE" | python3 -m json.tool 2>/dev/null || echo "$REGISTER_RESPONSE" + +# Extract user ID exactly like onboarding script +USER_ID=$(echo "$REGISTER_RESPONSE" | python3 -c " +import json, sys +try: + data = json.load(sys.stdin) + user = data.get('user', {}) + print(user.get('id', '')) +except: + print('') +") + +if [ -n "$USER_ID" ]; then + log_success "User ID extracted: $USER_ID" +else + log_error "Failed to extract user ID" + exit 1 +fi + +echo "" + +# ================================================================= +# STEP 2: LOGIN (EXACTLY LIKE ONBOARDING SCRIPT) +# ================================================================= + +log_step "Step 2: User Login (Onboarding Style)" + +LOGIN_RESPONSE=$(curl -s -X POST "$API_BASE/api/v1/auth/login" \ + -H "Content-Type: application/json" \ + -d "{ + \"email\": \"$TEST_EMAIL\", + \"password\": \"$TEST_PASSWORD\" + }") + +echo "Login Response:" +echo "$LOGIN_RESPONSE" | python3 -m json.tool 2>/dev/null || echo "$LOGIN_RESPONSE" + +ACCESS_TOKEN=$(extract_json_field "$LOGIN_RESPONSE" "access_token") + +if [ -n "$ACCESS_TOKEN" ]; then + log_success "Access token obtained: ${ACCESS_TOKEN:0:50}..." +else + log_error "Failed to extract access token" + exit 1 +fi + +echo "" + +# ================================================================= +# STEP 3: CHECK PROFILE ENDPOINT +# ================================================================= + +log_step "Step 3: Check Profile Endpoint" + +PROFILE_RESPONSE=$(curl -s -w "\nHTTP_CODE:%{http_code}" -X GET "$API_BASE/api/v1/users/me" \ + -H "Authorization: Bearer $ACCESS_TOKEN") + +HTTP_CODE=$(echo "$PROFILE_RESPONSE" | grep "HTTP_CODE:" | cut -d: -f2) +PROFILE_RESPONSE=$(echo "$PROFILE_RESPONSE" | sed '/HTTP_CODE:/d') + +echo "Profile HTTP Status: $HTTP_CODE" +echo "Profile Response:" +echo "$PROFILE_RESPONSE" | python3 -m json.tool 2>/dev/null || echo "$PROFILE_RESPONSE" + +PROFILE_ROLE=$(extract_json_field "$PROFILE_RESPONSE" "role") +echo "" +log_debug "Profile role: $PROFILE_ROLE" + +if [ "$PROFILE_ROLE" = "admin" ]; then + log_success "Profile shows admin role correctly" +else + log_error "Profile shows role '$PROFILE_ROLE', expected 'admin'" +fi + +echo "" + +# ================================================================= +# STEP 4: TEST ADMIN DELETION PREVIEW (EXACTLY LIKE ONBOARDING) +# ================================================================= + +log_step "Step 4: Admin Deletion Preview (Onboarding Style)" + +log_debug "Calling deletion preview endpoint..." + +DELETION_PREVIEW_RESPONSE=$(curl -s -w "\nHTTP_CODE:%{http_code}" -X GET "$API_BASE/api/v1/users/delete/$USER_ID/deletion-preview" \ + -H "Authorization: Bearer $ACCESS_TOKEN" \ + -H "Content-Type: application/json") + +HTTP_CODE=$(echo "$DELETION_PREVIEW_RESPONSE" | grep "HTTP_CODE:" | cut -d: -f2) +DELETION_PREVIEW_RESPONSE=$(echo "$DELETION_PREVIEW_RESPONSE" | sed '/HTTP_CODE:/d') + +echo "Deletion Preview HTTP Status: $HTTP_CODE" +echo "Deletion Preview Response:" +echo "$DELETION_PREVIEW_RESPONSE" | python3 -m json.tool 2>/dev/null || echo "$DELETION_PREVIEW_RESPONSE" + +case "$HTTP_CODE" in + "200") + log_success "Deletion preview successful!" + ;; + "403") + log_error "403 Forbidden - Admin access denied!" + echo "This is the same error as in onboarding script" + ;; + "401") + log_error "401 Unauthorized - Token issue" + ;; + *) + log_error "Unexpected HTTP status: $HTTP_CODE" + ;; +esac + +echo "" + +# ================================================================= +# STEP 5: TEST ACTUAL DELETION (EXACTLY LIKE ONBOARDING) +# ================================================================= + +log_step "Step 5: Admin User Deletion (Onboarding Style)" + +if [ "$HTTP_CODE" = "200" ]; then + log_debug "Attempting actual deletion..." + + DELETION_RESPONSE=$(curl -s -w "\nHTTP_CODE:%{http_code}" -X DELETE "$API_BASE/api/v1/users/delete/$USER_ID" \ + -H "Authorization: Bearer $ACCESS_TOKEN") + + HTTP_CODE=$(echo "$DELETION_RESPONSE" | grep "HTTP_CODE:" | cut -d: -f2) + DELETION_RESPONSE=$(echo "$DELETION_RESPONSE" | sed '/HTTP_CODE:/d') + + echo "Deletion HTTP Status: $HTTP_CODE" + echo "Deletion Response:" + echo "$DELETION_RESPONSE" | python3 -m json.tool 2>/dev/null || echo "$DELETION_RESPONSE" + + case "$HTTP_CODE" in + "200") + log_success "Admin deletion successful!" + ;; + "403") + log_error "403 Forbidden - Admin deletion denied!" + ;; + *) + log_error "Deletion failed with HTTP $HTTP_CODE" + ;; + esac +else + log_error "Skipping deletion test due to preview failure" +fi + +echo "" + +# ================================================================= +# STEP 6: DETAILED TOKEN ANALYSIS +# ================================================================= + +log_step "Step 6: Detailed Token Analysis" + +log_debug "Decoding access token from login..." + +if [ -n "$ACCESS_TOKEN" ]; then + # Decode JWT payload + JWT_PAYLOAD=$(echo "$ACCESS_TOKEN" | cut -d. -f2) + # Add padding if needed + JWT_PAYLOAD="${JWT_PAYLOAD}$(printf '%*s' $((4 - ${#JWT_PAYLOAD} % 4)) | tr ' ' '=')" + + echo "JWT Payload:" + echo "$JWT_PAYLOAD" | base64 -d 2>/dev/null | python3 -m json.tool 2>/dev/null || echo "Failed to decode" + + JWT_ROLE=$(echo "$JWT_PAYLOAD" | base64 -d 2>/dev/null | python3 -c " +import json, sys +try: + data = json.load(sys.stdin) + print(data.get('role', 'NOT_FOUND')) +except: + print('DECODE_ERROR') +" 2>/dev/null) + + echo "" + log_debug "JWT token role: $JWT_ROLE" +else + log_error "No access token available for analysis" +fi + +echo "" + +# ================================================================= +# STEP 7: CHECK GATEWAY VS DIRECT AUTH +# ================================================================= + +log_step "Step 7: Gateway vs Direct Auth Comparison" + +log_debug "Testing direct auth service call..." + +DIRECT_PROFILE=$(curl -s -X GET "http://localhost:8001/me" \ + -H "Authorization: Bearer $ACCESS_TOKEN" 2>/dev/null || echo "DIRECT_FAILED") + +if [ "$DIRECT_PROFILE" != "DIRECT_FAILED" ]; then + echo "Direct Auth Service Profile:" + echo "$DIRECT_PROFILE" | python3 -m json.tool 2>/dev/null || echo "$DIRECT_PROFILE" + + DIRECT_ROLE=$(extract_json_field "$DIRECT_PROFILE" "role") + log_debug "Direct auth service role: $DIRECT_ROLE" +else + log_debug "Direct auth service call failed (expected in some setups)" +fi + +echo "" + +# ================================================================= +# STEP 8: CHECK AUTH SERVICE LOGS +# ================================================================= + +log_step "Step 8: Auth Service Logs for This Session" + +log_debug "Checking auth service logs for this user..." + +AUTH_LOGS=$(docker logs --tail 50 bakery-auth-service 2>/dev/null | grep -E "($TEST_EMAIL|$USER_ID)" || echo "NO_LOGS_FOUND") + +if [ "$AUTH_LOGS" != "NO_LOGS_FOUND" ]; then + echo "Auth Service Logs (filtered for this user):" + echo "$AUTH_LOGS" +else + log_debug "No specific logs found for this user" +fi + +echo "" + +# ================================================================= +# STEP 9: SUMMARY AND COMPARISON +# ================================================================= + +log_step "Step 9: Onboarding Flow Analysis Summary" + +echo -e "${CYAN}🔍 ONBOARDING FLOW ANALYSIS${NC}" +echo -e "${CYAN}===========================${NC}" + +echo "" +echo "Registration Results:" +echo " User ID: $USER_ID" +echo " Access Token: $([ -n "$ACCESS_TOKEN" ] && echo 'YES' || echo 'NO')" + +echo "" +echo "Authentication Flow:" +echo " Profile Role: $PROFILE_ROLE" +echo " JWT Role: $JWT_ROLE" +echo " Direct Auth Role: ${DIRECT_ROLE:-'NOT_TESTED'}" + +echo "" +echo "Admin Endpoint Tests:" +echo " Deletion Preview: HTTP $HTTP_CODE" +echo " Admin Access: $([ "$HTTP_CODE" = "200" ] && echo 'SUCCESS' || echo 'FAILED')" + +echo "" +if [ "$HTTP_CODE" != "200" ]; then + echo -e "${RED}🚨 ISSUE IDENTIFIED:${NC}" + echo "The onboarding flow is still getting 403 errors" + echo "" + echo -e "${YELLOW}Possible causes:${NC}" + echo "1. Token from login different from registration token" + echo "2. Gateway headers not set correctly" + echo "3. Role being overridden somewhere in the flow" + echo "4. Cache or session issue" + echo "" + echo -e "${YELLOW}Next steps:${NC}" + echo "1. Compare JWT tokens from registration vs login" + echo "2. Check gateway request headers" + echo "3. Verify auth service restart picked up the fix" + echo "4. Check if there are multiple auth service instances" +else + log_success "Onboarding flow working correctly!" +fi + +echo "" +echo -e "${CYAN}Manual commands to investigate:${NC}" +echo "# Check user in database:" +echo "docker exec bakery-auth-db psql -U auth_user -d auth_db -c \"SELECT id, email, role FROM users WHERE email='$TEST_EMAIL';\"" +echo "" +echo "# Restart auth service:" +echo "docker-compose restart auth-service" +echo "" +echo "# Check auth service status:" +echo "docker-compose ps auth-service" \ No newline at end of file diff --git a/tests/verify_db_schema.sh b/tests/verify_db_schema.sh new file mode 100755 index 00000000..a507a16e --- /dev/null +++ b/tests/verify_db_schema.sh @@ -0,0 +1,157 @@ +#!/bin/bash + +# ================================================================= +# DATABASE SCHEMA AND DATA VERIFICATION SCRIPT +# ================================================================= +# This script checks the auth database schema and data + +echo "🔍 DATABASE VERIFICATION" +echo "========================" + +# Colors +GREEN='\033[0;32m' +RED='\033[0;31m' +YELLOW='\033[1;33m' +NC='\033[0m' + +# Check if auth database container is running +if ! docker ps | grep -q "bakery-auth-db"; then + echo -e "${RED}❌ Auth database container is not running${NC}" + echo "Start with: docker-compose up -d auth-db" + exit 1 +fi + +echo -e "${GREEN}✅ Auth database container is running${NC}" +echo "" + +# 1. Check database schema +echo "📋 1. CHECKING DATABASE SCHEMA" +echo "==============================" + +echo "Users table structure:" +docker exec bakery-auth-db psql -U auth_user -d auth_db -c "\d users;" 2>/dev/null || { + echo -e "${RED}❌ Cannot access database or users table doesn't exist${NC}" + exit 1 +} + +echo "" +echo "Checking if 'role' column exists:" +ROLE_COLUMN=$(docker exec bakery-auth-db psql -U auth_user -d auth_db -t -c "SELECT column_name FROM information_schema.columns WHERE table_name='users' AND column_name='role';" 2>/dev/null | tr -d ' ') + +if [ "$ROLE_COLUMN" = "role" ]; then + echo -e "${GREEN}✅ 'role' column exists in users table${NC}" +else + echo -e "${RED}❌ 'role' column is missing from users table${NC}" + echo "This is likely the root cause!" + echo "" + echo "Available columns in users table:" + docker exec bakery-auth-db psql -U auth_user -d auth_db -c "SELECT column_name, data_type FROM information_schema.columns WHERE table_name='users';" + exit 1 +fi + +echo "" + +# 2. Check existing users and their roles +echo "📋 2. CHECKING EXISTING USERS" +echo "=============================" + +echo "All users in database:" +docker exec bakery-auth-db psql -U auth_user -d auth_db -c "SELECT id, email, role, is_active, created_at FROM users ORDER BY created_at DESC LIMIT 10;" + +echo "" + +# 3. Check for test users +echo "📋 3. CHECKING FOR TEST USERS" +echo "=============================" + +echo "Test users (containing 'test' in email):" +docker exec bakery-auth-db psql -U auth_user -d auth_db -c "SELECT id, email, role, is_active, created_at FROM users WHERE email LIKE '%test%' ORDER BY created_at DESC;" + +echo "" + +# 4. Check database constraints and defaults +echo "📋 4. CHECKING ROLE CONSTRAINTS" +echo "===============================" + +echo "Role column details:" +docker exec bakery-auth-db psql -U auth_user -d auth_db -c "SELECT column_name, data_type, is_nullable, column_default FROM information_schema.columns WHERE table_name='users' AND column_name='role';" + +echo "" +echo "Role check constraints (if any):" +docker exec bakery-auth-db psql -U auth_user -d auth_db -c "SELECT conname, consrc FROM pg_constraint WHERE conrelid = 'users'::regclass AND consrc LIKE '%role%';" 2>/dev/null || echo "No role constraints found" + +echo "" + +# 5. Test role insertion +echo "📋 5. TESTING ROLE INSERTION" +echo "============================" + +TEST_EMAIL="schema.test.$(date +%s)@example.com" + +echo "Creating test user with admin role:" +docker exec bakery-auth-db psql -U auth_user -d auth_db -c " +INSERT INTO users (id, email, full_name, hashed_password, role, is_active, is_verified, created_at, updated_at) +VALUES (gen_random_uuid(), '$TEST_EMAIL', 'Schema Test', 'dummy_hash', 'admin', true, false, NOW(), NOW()); +" 2>/dev/null + +if [ $? -eq 0 ]; then + echo -e "${GREEN}✅ Successfully inserted user with admin role${NC}" + + echo "Verifying insertion:" + docker exec bakery-auth-db psql -U auth_user -d auth_db -c "SELECT id, email, role FROM users WHERE email='$TEST_EMAIL';" + + echo "Cleaning up test user:" + docker exec bakery-auth-db psql -U auth_user -d auth_db -c "DELETE FROM users WHERE email='$TEST_EMAIL';" 2>/dev/null +else + echo -e "${RED}❌ Failed to insert user with admin role${NC}" + echo "This indicates a database constraint or permission issue" +fi + +echo "" + +# 6. Check for migration history +echo "📋 6. CHECKING MIGRATION HISTORY" +echo "=================================" + +echo "Alembic version table (if exists):" +docker exec bakery-auth-db psql -U auth_user -d auth_db -c "SELECT * FROM alembic_version;" 2>/dev/null || echo "No alembic_version table found" + +echo "" + +echo "📋 SUMMARY AND RECOMMENDATIONS" +echo "===============================" + +# Check if we found any obvious issues +ISSUES_FOUND=0 + +# Check if role column exists +if [ "$ROLE_COLUMN" != "role" ]; then + echo -e "${RED}❌ CRITICAL: 'role' column missing from users table${NC}" + echo " → Run database migrations: alembic upgrade head" + echo " → Or add the column manually" + ISSUES_FOUND=1 +fi + +# Check if we can insert admin roles +docker exec bakery-auth-db psql -U auth_user -d auth_db -c "INSERT INTO users (id, email, full_name, hashed_password, role, is_active, is_verified, created_at, updated_at) VALUES (gen_random_uuid(), 'temp.test@example.com', 'Test', 'hash', 'admin', true, false, NOW(), NOW());" 2>/dev/null +if [ $? -eq 0 ]; then + docker exec bakery-auth-db psql -U auth_user -d auth_db -c "DELETE FROM users WHERE email='temp.test@example.com';" 2>/dev/null +else + echo -e "${RED}❌ ISSUE: Cannot insert users with admin role${NC}" + echo " → Check database constraints or permissions" + ISSUES_FOUND=1 +fi + +if [ $ISSUES_FOUND -eq 0 ]; then + echo -e "${GREEN}✅ Database schema appears correct${NC}" + echo " → The issue is likely in the application code, not the database" + echo " → Check JWT token creation and role extraction logic" +else + echo -e "${YELLOW}⚠️ Database issues found - fix these first${NC}" +fi + +echo "" +echo -e "${YELLOW}🔧 Next steps:${NC}" +echo "1. If role column is missing: Run 'alembic upgrade head' in auth service" +echo "2. If schema is OK: Run the main debug script to check application logic" +echo "3. Check auth service logs during user registration" \ No newline at end of file