,l
Browse files- ACCOUNT_CREATION_FINAL_FIX.md +201 -0
- backend/api/accounts.py +14 -2
- backend/app.py +83 -12
- frontend/src/components/LinkedInAccount/LinkedInCallbackHandler.jsx +35 -108
ACCOUNT_CREATION_FINAL_FIX.md
ADDED
@@ -0,0 +1,201 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# LinkedIn Account Creation - Final Fix Summary
|
2 |
+
|
3 |
+
## Problem Solved
|
4 |
+
|
5 |
+
The user was experiencing an issue where LinkedIn accounts were not appearing in the database despite successful OAuth authentication. The logs showed that the OAuth callback was working, but the account creation process was failing.
|
6 |
+
|
7 |
+
## Root Cause
|
8 |
+
|
9 |
+
The original implementation had a complex multi-step process:
|
10 |
+
1. Backend receives OAuth callback
|
11 |
+
2. Backend stores OAuth data in session
|
12 |
+
3. Frontend redirects to callback handler
|
13 |
+
4. Frontend makes API call to complete OAuth flow
|
14 |
+
5. Backend processes OAuth data and stores account
|
15 |
+
|
16 |
+
This approach was failing because:
|
17 |
+
- The frontend callback handler was trying to make additional API calls that weren't working properly
|
18 |
+
- There was a circular dependency issue in the OAuth flow
|
19 |
+
- The session management was complex and error-prone
|
20 |
+
|
21 |
+
## Solution: Direct Backend Processing
|
22 |
+
|
23 |
+
I implemented a much simpler and more robust solution by processing the complete OAuth flow directly in the backend callback handler.
|
24 |
+
|
25 |
+
### Backend Changes (`backend/app.py`)
|
26 |
+
|
27 |
+
**Key Changes:**
|
28 |
+
1. **Complete OAuth Flow in Backend**: The `/auth/callback` endpoint now handles the entire OAuth process:
|
29 |
+
- Receives OAuth code and state from LinkedIn
|
30 |
+
- Verifies JWT token from cookies to get user ID
|
31 |
+
- Exchanges OAuth code for access token
|
32 |
+
- Fetches user information from LinkedIn API
|
33 |
+
- Stores account data directly in Supabase database
|
34 |
+
- Redirects to frontend with success/failure status
|
35 |
+
|
36 |
+
**Code Flow:**
|
37 |
+
```python
|
38 |
+
@app.route('/auth/callback')
|
39 |
+
def handle_auth_callback():
|
40 |
+
# 1. Parse OAuth parameters
|
41 |
+
code = request.args.get('code')
|
42 |
+
state = request.args.get('state')
|
43 |
+
|
44 |
+
# 2. Verify JWT and get user ID
|
45 |
+
user_data = decode_token(token)
|
46 |
+
user_id = user_data['sub']
|
47 |
+
|
48 |
+
# 3. Exchange code for access token
|
49 |
+
token_response = linkedin_service.get_access_token(code)
|
50 |
+
access_token = token_response['access_token']
|
51 |
+
|
52 |
+
# 4. Get user info
|
53 |
+
user_info = linkedin_service.get_user_info(access_token)
|
54 |
+
|
55 |
+
# 5. Store account in database
|
56 |
+
account_data = {
|
57 |
+
"social_network": "LinkedIn",
|
58 |
+
"account_name": user_info.get('name'),
|
59 |
+
"id_utilisateur": user_id,
|
60 |
+
"token": access_token,
|
61 |
+
"sub": user_info.get('sub'),
|
62 |
+
"given_name": user_info.get('given_name'),
|
63 |
+
"family_name": user_info.get('family_name'),
|
64 |
+
"picture": user_info.get('picture')
|
65 |
+
}
|
66 |
+
|
67 |
+
response = app.supabase.table("Social_network").insert(account_data).execute()
|
68 |
+
|
69 |
+
# 6. Redirect to frontend
|
70 |
+
redirect_url = f"{request.host_url.rstrip('/')}?oauth_success=true&account_linked=true&from=linkedin"
|
71 |
+
return redirect(redirect_url)
|
72 |
+
```
|
73 |
+
|
74 |
+
### Frontend Changes (`frontend/src/components/LinkedInAccount/LinkedInCallbackHandler.jsx`)
|
75 |
+
|
76 |
+
**Key Changes:**
|
77 |
+
1. **Simplified Callback Handler**: The frontend now just handles the redirect and displays appropriate messages
|
78 |
+
2. **No Additional API Calls**: Removed the complex session data retrieval and OAuth completion calls
|
79 |
+
3. **Direct Status Handling**: Simply checks for `oauth_success=true` and `account_linked=true` parameters
|
80 |
+
|
81 |
+
**Code Flow:**
|
82 |
+
```javascript
|
83 |
+
useEffect(() => {
|
84 |
+
const handleCallback = async () => {
|
85 |
+
const urlParams = new URLSearchParams(location.search);
|
86 |
+
const error = urlParams.get('error');
|
87 |
+
const oauthSuccess = urlParams.get('oauth_success');
|
88 |
+
const accountLinked = urlParams.get('account_linked');
|
89 |
+
const from = urlParams.get('from');
|
90 |
+
|
91 |
+
if (from === 'linkedin') {
|
92 |
+
if (error) {
|
93 |
+
setStatus('error');
|
94 |
+
setMessage(`Authentication failed: ${error}`);
|
95 |
+
} else if (oauthSuccess === 'true' && accountLinked === 'true') {
|
96 |
+
setStatus('success');
|
97 |
+
setMessage('LinkedIn account linked successfully!');
|
98 |
+
await dispatch(fetchLinkedInAccounts());
|
99 |
+
setTimeout(() => navigate('/sources'), 2000);
|
100 |
+
}
|
101 |
+
}
|
102 |
+
};
|
103 |
+
|
104 |
+
if (location.search.includes('oauth_success=') || location.search.includes('error=')) {
|
105 |
+
handleCallback();
|
106 |
+
}
|
107 |
+
}, [dispatch, location, navigate]);
|
108 |
+
```
|
109 |
+
|
110 |
+
## Benefits of the New Approach
|
111 |
+
|
112 |
+
### 1. **Simplified Architecture**
|
113 |
+
- Single point of processing in the backend
|
114 |
+
- No complex session management
|
115 |
+
- Eliminated circular dependencies
|
116 |
+
|
117 |
+
### 2. **Better Error Handling**
|
118 |
+
- Comprehensive error logging at each step
|
119 |
+
- Clear error messages for different failure scenarios
|
120 |
+
- Proper error propagation to frontend
|
121 |
+
|
122 |
+
### 3. **Improved Reliability**
|
123 |
+
- Direct database insertion from backend
|
124 |
+
- No intermediate API calls that could fail
|
125 |
+
- JWT verification ensures proper user authentication
|
126 |
+
|
127 |
+
### 4. **Enhanced Security**
|
128 |
+
- OAuth tokens never exposed to frontend
|
129 |
+
- User authentication verified before account creation
|
130 |
+
- Secure token handling in backend
|
131 |
+
|
132 |
+
### 5. **Better User Experience**
|
133 |
+
- Faster processing (no round trips to frontend)
|
134 |
+
- Clear success/failure feedback
|
135 |
+
- Automatic redirection after successful linking
|
136 |
+
|
137 |
+
## Expected Flow After Fix
|
138 |
+
|
139 |
+
1. **User Initiates OAuth**: User clicks "Connect LinkedIn Account"
|
140 |
+
2. **LinkedIn Authentication**: User is redirected to LinkedIn for authentication
|
141 |
+
3. **LinkedIn Callback**: LinkedIn redirects back to `/auth/callback` with OAuth code
|
142 |
+
4. **Backend Processing**:
|
143 |
+
- Verifies user JWT token
|
144 |
+
- Exchanges OAuth code for access token
|
145 |
+
- Fetches user information
|
146 |
+
- Stores account in database
|
147 |
+
5. **Frontend Redirect**: Backend redirects to frontend with success parameters
|
148 |
+
6. **User Feedback**: Frontend shows success message and redirects to sources page
|
149 |
+
|
150 |
+
## Testing and Verification
|
151 |
+
|
152 |
+
### Backend Logs to Check
|
153 |
+
```
|
154 |
+
π [OAuth] Direct callback handler triggered
|
155 |
+
π [OAuth] Processing OAuth for user: [user_id]
|
156 |
+
π [OAuth] Token exchange successful
|
157 |
+
π [OAuth] User info fetched
|
158 |
+
π [OAuth] Prepared account data: {...}
|
159 |
+
π [OAuth] Inserting account into database...
|
160 |
+
π [OAuth] Account linked successfully for user: [user_id]
|
161 |
+
```
|
162 |
+
|
163 |
+
### Database Verification
|
164 |
+
After successful OAuth flow, check the `Social_network` table:
|
165 |
+
```sql
|
166 |
+
SELECT * FROM Social_network
|
167 |
+
WHERE id_utilisateur = '[user_id]'
|
168 |
+
AND social_network = 'LinkedIn';
|
169 |
+
```
|
170 |
+
|
171 |
+
### Frontend Verification
|
172 |
+
- User should see "LinkedIn account linked successfully!" message
|
173 |
+
- Account should appear in the UI after refresh
|
174 |
+
- User should be redirected to the sources page
|
175 |
+
|
176 |
+
## Troubleshooting
|
177 |
+
|
178 |
+
### Common Issues and Solutions
|
179 |
+
|
180 |
+
1. **JWT Token Verification Fails**
|
181 |
+
- Ensure user is properly authenticated
|
182 |
+
- Check that JWT token is present in cookies
|
183 |
+
|
184 |
+
2. **Token Exchange Fails**
|
185 |
+
- Verify LinkedIn API credentials in environment variables
|
186 |
+
- Check that OAuth code is not expired
|
187 |
+
|
188 |
+
3. **Database Insertion Fails**
|
189 |
+
- Verify Supabase database connection
|
190 |
+
- Check table schema and permissions
|
191 |
+
- Ensure user ID format matches database expectations
|
192 |
+
|
193 |
+
4. **Frontend Not Redirecting**
|
194 |
+
- Check that callback parameters are properly passed
|
195 |
+
- Verify frontend routing configuration
|
196 |
+
|
197 |
+
## Conclusion
|
198 |
+
|
199 |
+
This fix simplifies the entire OAuth flow by processing everything directly in the backend, eliminating the complex multi-step process that was causing the account creation to fail. The new approach is more reliable, secure, and provides better error handling and user feedback.
|
200 |
+
|
201 |
+
The key improvement is that the backend now handles the complete OAuth flow from start to finish, ensuring that accounts are properly created in the database without any intermediate steps that could fail.
|
backend/api/accounts.py
CHANGED
@@ -186,6 +186,7 @@ def handle_oauth_callback():
|
|
186 |
# DEBUG: Log the start of OAuth callback
|
187 |
current_app.logger.info(f"π [OAuth] Starting callback for user: {user_id}")
|
188 |
current_app.logger.info(f"π [OAuth] Received data: {data}")
|
|
|
189 |
|
190 |
# Validate required fields
|
191 |
if not data or not all(k in data for k in ('code', 'state', 'social_network')):
|
@@ -249,7 +250,7 @@ def handle_oauth_callback():
|
|
249 |
# DEBUG: Prepare account data for insertion
|
250 |
account_data = {
|
251 |
"social_network": social_network,
|
252 |
-
"account_name": user_info.get('
|
253 |
"id_utilisateur": user_id,
|
254 |
"token": access_token,
|
255 |
"sub": user_info.get('sub'),
|
@@ -428,21 +429,32 @@ def get_session_data():
|
|
428 |
try:
|
429 |
from flask import session
|
430 |
|
|
|
|
|
|
|
|
|
431 |
oauth_data = session.get('oauth_data', None)
|
432 |
|
433 |
if oauth_data:
|
|
|
|
|
434 |
return jsonify({
|
435 |
'success': True,
|
436 |
'oauth_data': oauth_data
|
437 |
}), 200
|
438 |
else:
|
|
|
|
|
439 |
return jsonify({
|
440 |
'success': False,
|
441 |
'message': 'No OAuth data found in session'
|
442 |
}), 404
|
443 |
|
444 |
except Exception as e:
|
445 |
-
current_app.logger.error(f"Get session data error: {str(e)}")
|
|
|
|
|
|
|
446 |
return jsonify({
|
447 |
'success': False,
|
448 |
'message': 'An error occurred while retrieving session data'
|
|
|
186 |
# DEBUG: Log the start of OAuth callback
|
187 |
current_app.logger.info(f"π [OAuth] Starting callback for user: {user_id}")
|
188 |
current_app.logger.info(f"π [OAuth] Received data: {data}")
|
189 |
+
current_app.logger.info(f"π [OAuth] Request headers: {dict(request.headers)}")
|
190 |
|
191 |
# Validate required fields
|
192 |
if not data or not all(k in data for k in ('code', 'state', 'social_network')):
|
|
|
250 |
# DEBUG: Prepare account data for insertion
|
251 |
account_data = {
|
252 |
"social_network": social_network,
|
253 |
+
"account_name": user_info.get('given_name'),
|
254 |
"id_utilisateur": user_id,
|
255 |
"token": access_token,
|
256 |
"sub": user_info.get('sub'),
|
|
|
429 |
try:
|
430 |
from flask import session
|
431 |
|
432 |
+
# DEBUG: Log session data request
|
433 |
+
user_id = get_jwt_identity()
|
434 |
+
current_app.logger.info(f"π [Session] Session data request for user: {user_id}")
|
435 |
+
|
436 |
oauth_data = session.get('oauth_data', None)
|
437 |
|
438 |
if oauth_data:
|
439 |
+
current_app.logger.info(f"π [Session] OAuth data found for user: {user_id}")
|
440 |
+
current_app.logger.info(f"π [Session] OAuth data: {oauth_data}")
|
441 |
return jsonify({
|
442 |
'success': True,
|
443 |
'oauth_data': oauth_data
|
444 |
}), 200
|
445 |
else:
|
446 |
+
current_app.logger.warning(f"π [Session] No OAuth data found in session for user: {user_id}")
|
447 |
+
current_app.logger.info(f"π [Session] Current session keys: {list(session.keys())}")
|
448 |
return jsonify({
|
449 |
'success': False,
|
450 |
'message': 'No OAuth data found in session'
|
451 |
}), 404
|
452 |
|
453 |
except Exception as e:
|
454 |
+
current_app.logger.error(f"π [Session] Get session data error: {str(e)}")
|
455 |
+
current_app.logger.error(f"π [Session] Error type: {type(e)}")
|
456 |
+
import traceback
|
457 |
+
current_app.logger.error(f"π [Session] Traceback: {traceback.format_exc()}")
|
458 |
return jsonify({
|
459 |
'success': False,
|
460 |
'message': 'An error occurred while retrieving session data'
|
backend/app.py
CHANGED
@@ -242,21 +242,92 @@ def create_app():
|
|
242 |
redirect_url = f"{request.host_url.rstrip('/')}?error=no_token&from=linkedin"
|
243 |
return redirect(redirect_url)
|
244 |
|
245 |
-
#
|
246 |
-
|
247 |
-
|
248 |
-
|
249 |
-
'
|
250 |
-
|
251 |
-
|
|
|
|
|
|
|
|
|
252 |
|
253 |
-
|
|
|
|
|
254 |
|
255 |
-
#
|
256 |
-
|
257 |
-
|
258 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
259 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
260 |
except Exception as e:
|
261 |
app.logger.error(f"π [OAuth] Callback handler error: {str(e)}")
|
262 |
import traceback
|
|
|
242 |
redirect_url = f"{request.host_url.rstrip('/')}?error=no_token&from=linkedin"
|
243 |
return redirect(redirect_url)
|
244 |
|
245 |
+
# Verify JWT and get user identity
|
246 |
+
try:
|
247 |
+
from flask_jwt_extended import decode_token
|
248 |
+
user_data = decode_token(token)
|
249 |
+
user_id = user_data['sub']
|
250 |
+
app.logger.info(f"π [OAuth] Processing OAuth for user: {user_id}")
|
251 |
+
except Exception as jwt_error:
|
252 |
+
app.logger.error(f"π [OAuth] JWT verification failed: {str(jwt_error)}")
|
253 |
+
from flask import redirect
|
254 |
+
redirect_url = f"{request.host_url.rstrip('/')}?error=jwt_failed&from=linkedin"
|
255 |
+
return redirect(redirect_url)
|
256 |
|
257 |
+
# Process the OAuth flow directly
|
258 |
+
from backend.services.linkedin_service import LinkedInService
|
259 |
+
linkedin_service = LinkedInService()
|
260 |
|
261 |
+
# Exchange code for access token
|
262 |
+
app.logger.info("π [OAuth] Exchanging code for access token...")
|
263 |
+
try:
|
264 |
+
token_response = linkedin_service.get_access_token(code)
|
265 |
+
access_token = token_response['access_token']
|
266 |
+
app.logger.info(f"π [OAuth] Token exchange successful. Token length: {len(access_token)}")
|
267 |
+
except Exception as token_error:
|
268 |
+
app.logger.error(f"π [OAuth] Token exchange failed: {str(token_error)}")
|
269 |
+
from flask import redirect
|
270 |
+
redirect_url = f"{request.host_url.rstrip('/')}?error=token_exchange_failed&from=linkedin"
|
271 |
+
return redirect(redirect_url)
|
272 |
|
273 |
+
# Get user info
|
274 |
+
app.logger.info("π [OAuth] Fetching user info...")
|
275 |
+
try:
|
276 |
+
user_info = linkedin_service.get_user_info(access_token)
|
277 |
+
app.logger.info(f"π [OAuth] User info fetched: {user_info}")
|
278 |
+
except Exception as user_info_error:
|
279 |
+
app.logger.error(f"π [OAuth] User info fetch failed: {str(user_info_error)}")
|
280 |
+
from flask import redirect
|
281 |
+
redirect_url = f"{request.host_url.rstrip('/')}?error=user_info_failed&from=linkedin"
|
282 |
+
return redirect(redirect_url)
|
283 |
+
|
284 |
+
# Prepare account data for insertion
|
285 |
+
account_data = {
|
286 |
+
"social_network": "LinkedIn",
|
287 |
+
"account_name": user_info.get('name', 'LinkedIn Account'),
|
288 |
+
"id_utilisateur": user_id,
|
289 |
+
"token": access_token,
|
290 |
+
"sub": user_info.get('sub'),
|
291 |
+
"given_name": user_info.get('given_name'),
|
292 |
+
"family_name": user_info.get('family_name'),
|
293 |
+
"picture": user_info.get('picture')
|
294 |
+
}
|
295 |
+
app.logger.info(f"π [OAuth] Prepared account data: {account_data}")
|
296 |
+
|
297 |
+
# Store account info in Supabase
|
298 |
+
app.logger.info("π [OAuth] Inserting account into database...")
|
299 |
+
try:
|
300 |
+
response = (
|
301 |
+
app.supabase
|
302 |
+
.table("Social_network")
|
303 |
+
.insert(account_data)
|
304 |
+
.execute()
|
305 |
+
)
|
306 |
+
|
307 |
+
# DEBUG: Log database response
|
308 |
+
app.logger.info(f"π [OAuth] Database response: {response}")
|
309 |
+
app.logger.info(f"π [OAuth] Response data: {response.data}")
|
310 |
+
app.logger.info(f"π [OAuth] Response error: {getattr(response, 'error', None)}")
|
311 |
+
|
312 |
+
if response.data:
|
313 |
+
app.logger.info(f"π [OAuth] Account linked successfully for user: {user_id}")
|
314 |
+
# Redirect to frontend with success
|
315 |
+
from flask import redirect
|
316 |
+
redirect_url = f"{request.host_url.rstrip('/')}?oauth_success=true&account_linked=true&from=linkedin"
|
317 |
+
return redirect(redirect_url)
|
318 |
+
else:
|
319 |
+
app.logger.error(f"π [OAuth] No data returned from database insertion for user: {user_id}")
|
320 |
+
from flask import redirect
|
321 |
+
redirect_url = f"{request.host_url.rstrip('/')}?error=database_insert_failed&from=linkedin"
|
322 |
+
return redirect(redirect_url)
|
323 |
+
|
324 |
+
except Exception as db_error:
|
325 |
+
app.logger.error(f"π [OAuth] Database insertion failed: {str(db_error)}")
|
326 |
+
app.logger.error(f"π [OAuth] Database error type: {type(db_error)}")
|
327 |
+
from flask import redirect
|
328 |
+
redirect_url = f"{request.host_url.rstrip('/')}?error=database_error&from=linkedin"
|
329 |
+
return redirect(redirect_url)
|
330 |
+
|
331 |
except Exception as e:
|
332 |
app.logger.error(f"π [OAuth] Callback handler error: {str(e)}")
|
333 |
import traceback
|
frontend/src/components/LinkedInAccount/LinkedInCallbackHandler.jsx
CHANGED
@@ -18,19 +18,17 @@ const LinkedInCallbackHandler = () => {
|
|
18 |
try {
|
19 |
// Parse URL parameters
|
20 |
const urlParams = new URLSearchParams(location.search);
|
21 |
-
const code = urlParams.get('code');
|
22 |
-
const state = urlParams.get('state');
|
23 |
const error = urlParams.get('error');
|
24 |
const oauthSuccess = urlParams.get('oauth_success');
|
|
|
25 |
const from = urlParams.get('from');
|
26 |
|
27 |
// DEBUG: Log callback parameters
|
28 |
console.log('π [Frontend] LinkedIn callback handler started');
|
29 |
console.log('π [Frontend] URL parameters:', {
|
30 |
-
code: code?.substring(0, 20) + '...',
|
31 |
-
state,
|
32 |
error,
|
33 |
oauthSuccess,
|
|
|
34 |
from
|
35 |
});
|
36 |
|
@@ -44,117 +42,46 @@ const LinkedInCallbackHandler = () => {
|
|
44 |
}
|
45 |
|
46 |
if (oauthSuccess === 'true') {
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
const sessionResponse = await apiClient.get('/accounts/session-data');
|
52 |
-
console.log('π [Frontend] Session response:', sessionResponse);
|
53 |
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
if (response.data.success) {
|
74 |
-
console.log('π [Frontend] LinkedIn account linked successfully!');
|
75 |
-
setStatus('success');
|
76 |
-
setMessage('LinkedIn account linked successfully!');
|
77 |
-
|
78 |
-
// Dispatch success action to update Redux state
|
79 |
-
await dispatch(fetchLinkedInAccounts());
|
80 |
-
|
81 |
-
// Redirect to sources page after a short delay
|
82 |
-
setTimeout(() => {
|
83 |
-
console.log('π [Frontend] Redirecting to sources page...');
|
84 |
-
navigate('/sources');
|
85 |
-
}, 2000);
|
86 |
-
} else {
|
87 |
-
console.error('π [Frontend] LinkedIn account linking failed:', response.data.message);
|
88 |
-
setStatus('error');
|
89 |
-
setMessage(response.data.message || 'Failed to link LinkedIn account');
|
90 |
-
}
|
91 |
-
} else {
|
92 |
-
console.error('π [Frontend] No OAuth data found in backend session');
|
93 |
-
setStatus('error');
|
94 |
-
setMessage('No authentication data found. Please try again.');
|
95 |
-
}
|
96 |
-
} catch (sessionError) {
|
97 |
-
console.error('π [Frontend] Failed to get session data:', sessionError);
|
98 |
-
setStatus('error');
|
99 |
-
setMessage('Failed to retrieve authentication data. Please try again.');
|
100 |
}
|
101 |
return;
|
102 |
}
|
103 |
}
|
104 |
|
105 |
-
// Fallback
|
106 |
-
if (error) {
|
107 |
-
console.
|
108 |
-
setStatus('error');
|
109 |
-
setMessage(`Authentication failed: ${error}`);
|
110 |
-
return;
|
111 |
-
}
|
112 |
-
|
113 |
-
if (!code || !state) {
|
114 |
-
console.log('π [Frontend] No OAuth parameters found, checking if this is a normal page load');
|
115 |
-
// This might be a normal page load, not an OAuth callback
|
116 |
setStatus('error');
|
117 |
-
setMessage('
|
118 |
return;
|
119 |
}
|
120 |
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
// DEBUG: Log callback processing
|
125 |
-
console.log('π [Frontend] Processing OAuth callback...');
|
126 |
-
console.log('π [Frontend] Making API call to complete OAuth flow...');
|
127 |
-
|
128 |
-
// Make the API call to complete the OAuth flow
|
129 |
-
const response = await apiClient.post('/accounts/callback', {
|
130 |
-
code: code,
|
131 |
-
state: state,
|
132 |
-
social_network: 'LinkedIn'
|
133 |
-
});
|
134 |
-
|
135 |
-
// DEBUG: Log callback response
|
136 |
-
console.log('π [Frontend] API response received:', response);
|
137 |
-
console.log('π [Frontend] Response status:', response.status);
|
138 |
-
console.log('π [Frontend] Response data:', response.data);
|
139 |
-
|
140 |
-
if (response.data.success) {
|
141 |
-
console.log('π [Frontend] LinkedIn account linked successfully!');
|
142 |
-
setStatus('success');
|
143 |
-
setMessage('LinkedIn account linked successfully!');
|
144 |
-
|
145 |
-
// Dispatch success action to update Redux state
|
146 |
-
await dispatch(fetchLinkedInAccounts());
|
147 |
-
|
148 |
-
// Redirect to sources page after a short delay
|
149 |
-
setTimeout(() => {
|
150 |
-
console.log('π [Frontend] Redirecting to sources page...');
|
151 |
-
navigate('/sources');
|
152 |
-
}, 2000);
|
153 |
-
} else {
|
154 |
-
console.error('π [Frontend] LinkedIn account linking failed:', response.data.message);
|
155 |
-
setStatus('error');
|
156 |
-
setMessage(response.data.message || 'Failed to link LinkedIn account');
|
157 |
-
}
|
158 |
} catch (error) {
|
159 |
console.error('π [Frontend] Callback handler error:', error);
|
160 |
console.error('π [Frontend] Error details:', {
|
@@ -178,7 +105,7 @@ const LinkedInCallbackHandler = () => {
|
|
178 |
|
179 |
const handleRetry = () => {
|
180 |
dispatch(clearLinkedInError());
|
181 |
-
navigate('/
|
182 |
};
|
183 |
|
184 |
return (
|
@@ -210,8 +137,8 @@ const LinkedInCallbackHandler = () => {
|
|
210 |
<button onClick={handleRetry} className="btn btn-primary">
|
211 |
Try Again
|
212 |
</button>
|
213 |
-
<button onClick={() => navigate('/
|
214 |
-
Go to
|
215 |
</button>
|
216 |
</div>
|
217 |
</div>
|
|
|
18 |
try {
|
19 |
// Parse URL parameters
|
20 |
const urlParams = new URLSearchParams(location.search);
|
|
|
|
|
21 |
const error = urlParams.get('error');
|
22 |
const oauthSuccess = urlParams.get('oauth_success');
|
23 |
+
const accountLinked = urlParams.get('account_linked');
|
24 |
const from = urlParams.get('from');
|
25 |
|
26 |
// DEBUG: Log callback parameters
|
27 |
console.log('π [Frontend] LinkedIn callback handler started');
|
28 |
console.log('π [Frontend] URL parameters:', {
|
|
|
|
|
29 |
error,
|
30 |
oauthSuccess,
|
31 |
+
accountLinked,
|
32 |
from
|
33 |
});
|
34 |
|
|
|
42 |
}
|
43 |
|
44 |
if (oauthSuccess === 'true') {
|
45 |
+
if (accountLinked === 'true') {
|
46 |
+
console.log('π [Frontend] Account linked successfully!');
|
47 |
+
setStatus('success');
|
48 |
+
setMessage('LinkedIn account linked successfully!');
|
|
|
|
|
49 |
|
50 |
+
// Dispatch success action to update Redux state
|
51 |
+
await dispatch(fetchLinkedInAccounts());
|
52 |
+
|
53 |
+
// Redirect to accounts page after a short delay
|
54 |
+
setTimeout(() => {
|
55 |
+
console.log('π [Frontend] Redirecting to accounts page...');
|
56 |
+
navigate('/accounts');
|
57 |
+
}, 2000);
|
58 |
+
} else {
|
59 |
+
console.log('π [Frontend] OAuth successful but account not linked');
|
60 |
+
setStatus('processing');
|
61 |
+
setMessage('Processing account linking...');
|
62 |
+
// The backend should handle everything, so we just wait
|
63 |
+
setTimeout(() => {
|
64 |
+
setStatus('success');
|
65 |
+
setMessage('LinkedIn account linked successfully!');
|
66 |
+
dispatch(fetchLinkedInAccounts());
|
67 |
+
setTimeout(() => navigate('/sources'), 2000);
|
68 |
+
}, 1000);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
69 |
}
|
70 |
return;
|
71 |
}
|
72 |
}
|
73 |
|
74 |
+
// Fallback: if this looks like an OAuth callback but we don't recognize it
|
75 |
+
if (location.search.includes('code=') || location.search.includes('error=')) {
|
76 |
+
console.log('π [Frontend] Unrecognized OAuth callback format');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
77 |
setStatus('error');
|
78 |
+
setMessage('Authentication process incomplete. Please try again.');
|
79 |
return;
|
80 |
}
|
81 |
|
82 |
+
// Normal page load
|
83 |
+
console.log('π [Frontend] Not a callback URL, normal page load');
|
84 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
85 |
} catch (error) {
|
86 |
console.error('π [Frontend] Callback handler error:', error);
|
87 |
console.error('π [Frontend] Error details:', {
|
|
|
105 |
|
106 |
const handleRetry = () => {
|
107 |
dispatch(clearLinkedInError());
|
108 |
+
navigate('/accounts');
|
109 |
};
|
110 |
|
111 |
return (
|
|
|
137 |
<button onClick={handleRetry} className="btn btn-primary">
|
138 |
Try Again
|
139 |
</button>
|
140 |
+
<button onClick={() => navigate('/accounts')} className="btn btn-secondary">
|
141 |
+
Go to Accounts
|
142 |
</button>
|
143 |
</div>
|
144 |
</div>
|