jebin2 commited on
Commit
7dfb3ba
·
1 Parent(s): 34f76dc
Files changed (2) hide show
  1. dependencies.py +51 -1
  2. routers/blink.py +45 -13
dependencies.py CHANGED
@@ -1,6 +1,6 @@
1
  import logging
2
  from datetime import datetime, timedelta
3
- from typing import Optional, Tuple
4
  import ipaddress
5
  import httpx
6
  from fastapi import Request, Depends, HTTPException, status
@@ -149,6 +149,56 @@ async def get_current_user(
149
  return user
150
 
151
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
152
  async def verify_credits(
153
  user: User = Depends(get_current_user),
154
  db: AsyncSession = Depends(get_db)
 
1
  import logging
2
  from datetime import datetime, timedelta
3
+ from typing import Optional, Tuple, Union
4
  import ipaddress
5
  import httpx
6
  from fastapi import Request, Depends, HTTPException, status
 
149
  return user
150
 
151
 
152
+ async def get_optional_user(
153
+ req: Request,
154
+ db: AsyncSession = Depends(get_db)
155
+ ) -> Optional[User]:
156
+ """
157
+ Attempt to extract and verify JWT from Authorization header.
158
+ Returns the authenticated user if valid, or None if not authenticated.
159
+
160
+ Unlike get_current_user, this does NOT raise errors for missing/invalid tokens.
161
+ Useful for endpoints that work for both authenticated and anonymous users.
162
+
163
+ Usage:
164
+ @router.get("/optional-auth")
165
+ async def optional_auth_route(user: Optional[User] = Depends(get_optional_user)):
166
+ if user:
167
+ return {"user_id": user.user_id}
168
+ return {"message": "anonymous"}
169
+ """
170
+ auth_header = req.headers.get("Authorization")
171
+
172
+ if not auth_header or not auth_header.startswith("Bearer "):
173
+ return None
174
+
175
+ token = auth_header.split(" ", 1)[1]
176
+
177
+ try:
178
+ payload = verify_access_token(token)
179
+ except (TokenExpiredError, InvalidTokenError, JWTError) as e:
180
+ logger.debug(f"Optional auth failed: {e}")
181
+ return None
182
+
183
+ # Get user from DB
184
+ query = select(User).where(
185
+ User.user_id == payload.user_id,
186
+ User.is_active == True
187
+ )
188
+ result = await db.execute(query)
189
+ user = result.scalar_one_or_none()
190
+
191
+ if not user:
192
+ return None
193
+
194
+ # Validate token version
195
+ if payload.token_version < user.token_version:
196
+ logger.debug(f"Token invalidated for user {user.user_id}")
197
+ return None
198
+
199
+ return user
200
+
201
+
202
  async def verify_credits(
203
  user: User = Depends(get_current_user),
204
  db: AsyncSession = Depends(get_db)
routers/blink.py CHANGED
@@ -12,7 +12,7 @@ import logging
12
  from core.database import get_db
13
  from core.models import User, AuditLog, GeminiJob, Contact, ClientUser
14
  from services.encryption_service import decrypt_multiple_blocks
15
- from dependencies import get_geolocation
16
 
17
  logger = logging.getLogger(__name__)
18
 
@@ -430,11 +430,19 @@ async def get_contacts(
430
  async def blink(
431
  request: Request,
432
  userid: str = Query(..., description="User ID (20 chars) + encrypted data"),
433
- db: AsyncSession = Depends(get_db)
 
434
  ):
435
  """
436
  Process blink request with encrypted user data.
437
  Logs to AuditLog with log_type='client'.
 
 
 
 
 
 
 
438
  """
439
  try:
440
  # Validate minimum length
@@ -471,9 +479,37 @@ async def blink(
471
  else:
472
  ip_address = request.client.host if request.client else None
473
 
 
 
 
 
 
 
 
 
 
 
 
 
 
474
  # Get geolocation from IP address
475
  country, region = await get_geolocation(ip_address)
476
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
477
  # Store each decrypted result as separate audit log entries
478
  records_created = 0
479
  for json_data in decrypted_results:
@@ -486,7 +522,7 @@ async def blink(
486
 
487
  audit_log = AuditLog(
488
  log_type="client",
489
- user_id=None, # Anonymous until linked
490
  client_user_id=client_user_id,
491
  action="blink",
492
  details=details,
@@ -502,7 +538,7 @@ async def blink(
502
  if not decrypted_results and encrypted_data:
503
  audit_log = AuditLog(
504
  log_type="client",
505
- user_id=None,
506
  client_user_id=client_user_id,
507
  action="blink",
508
  details={"encrypted_length": len(encrypted_data), "country": country, "region": region},
@@ -516,16 +552,11 @@ async def blink(
516
 
517
  await db.commit()
518
 
519
- logger.info(f"Successfully processed blink for client: {client_user_id}, records: {records_created}")
 
520
 
521
- return JSONResponse(
522
- status_code=status.HTTP_200_OK,
523
- content={
524
- "status": "success",
525
- "client_user_id": client_user_id,
526
- "records_created": records_created
527
- }
528
- )
529
 
530
  except HTTPException:
531
  raise
@@ -536,3 +567,4 @@ async def blink(
536
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
537
  detail="Internal server error processing request"
538
  )
 
 
12
  from core.database import get_db
13
  from core.models import User, AuditLog, GeminiJob, Contact, ClientUser
14
  from services.encryption_service import decrypt_multiple_blocks
15
+ from dependencies import get_geolocation, get_optional_user
16
 
17
  logger = logging.getLogger(__name__)
18
 
 
430
  async def blink(
431
  request: Request,
432
  userid: str = Query(..., description="User ID (20 chars) + encrypted data"),
433
+ db: AsyncSession = Depends(get_db),
434
+ current_user: User = Depends(get_optional_user)
435
  ):
436
  """
437
  Process blink request with encrypted user data.
438
  Logs to AuditLog with log_type='client'.
439
+
440
+ If authenticated via JWT:
441
+ - Creates a new ClientUser entry linking client_user_id to server user_id
442
+ - Sets user_id in AuditLog entries
443
+
444
+ If not authenticated:
445
+ - Creates AuditLog entries with user_id=None (anonymous)
446
  """
447
  try:
448
  # Validate minimum length
 
479
  else:
480
  ip_address = request.client.host if request.client else None
481
 
482
+ # Determine IPv4/IPv6
483
+ ipv4_address = None
484
+ ipv6_address = None
485
+ if ip_address:
486
+ try:
487
+ ip_obj = ipaddress.ip_address(ip_address)
488
+ if ip_obj.version == 4:
489
+ ipv4_address = ip_address
490
+ else:
491
+ ipv6_address = ip_address
492
+ except ValueError:
493
+ pass # Invalid IP, leave both as None
494
+
495
  # Get geolocation from IP address
496
  country, region = await get_geolocation(ip_address)
497
 
498
+ # Determine server user_id (if authenticated)
499
+ server_user_id = current_user.user_id if current_user else None
500
+
501
+ # If authenticated, always create a new ClientUser entry
502
+ if current_user:
503
+ new_client_user = ClientUser(
504
+ user_id=current_user.user_id,
505
+ client_user_id=client_user_id,
506
+ ipv4_address=ipv4_address,
507
+ ipv6_address=ipv6_address,
508
+ device_info={"user_agent": user_agent} if user_agent else None
509
+ )
510
+ db.add(new_client_user)
511
+ logger.info(f"Created ClientUser entry: user_id={current_user.user_id}, client_user_id={client_user_id}")
512
+
513
  # Store each decrypted result as separate audit log entries
514
  records_created = 0
515
  for json_data in decrypted_results:
 
522
 
523
  audit_log = AuditLog(
524
  log_type="client",
525
+ user_id=server_user_id, # Set if authenticated, None if anonymous
526
  client_user_id=client_user_id,
527
  action="blink",
528
  details=details,
 
538
  if not decrypted_results and encrypted_data:
539
  audit_log = AuditLog(
540
  log_type="client",
541
+ user_id=server_user_id, # Set if authenticated, None if anonymous
542
  client_user_id=client_user_id,
543
  action="blink",
544
  details={"encrypted_length": len(encrypted_data), "country": country, "region": region},
 
552
 
553
  await db.commit()
554
 
555
+ auth_status = "authenticated" if current_user else "anonymous"
556
+ logger.info(f"Successfully processed blink for client: {client_user_id}, records: {records_created}, auth: {auth_status}")
557
 
558
+ # Return 204 No Content for silent tracking
559
+ return JSONResponse(status_code=status.HTTP_204_NO_CONTENT, content=None)
 
 
 
 
 
 
560
 
561
  except HTTPException:
562
  raise
 
567
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
568
  detail="Internal server error processing request"
569
  )
570
+