Skip to content

Commit df8fb8d

Browse files
committed
fix passkey issues hopefully
1 parent d5a13a0 commit df8fb8d

6 files changed

Lines changed: 63 additions & 32 deletions

File tree

backend/routes/auth.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,8 @@ def verify_totp():
108108
try:
109109
data = request.get_json()
110110
user_id = data.get('user_id')
111-
totp_code = data.get('totp_code', '')
111+
# Ensure totp_code is string and stripped
112+
totp_code = str(data.get('totp_code', '')).strip()
112113
is_setup = data.get('is_setup', False) # True for initial setup, False for general verification
113114

114115
if not user_id or not validate_totp_code(totp_code):
@@ -125,6 +126,7 @@ def verify_totp():
125126
user_id = current_user_result['user']['id']
126127
else:
127128
# For passkey setup, allow user_id from request if provided
129+
# but ensure it's valid format if necessary (service handles that)
128130
pass
129131

130132
# Use appropriate verification method
@@ -136,6 +138,8 @@ def verify_totp():
136138
if result['success']:
137139
return jsonify(result), 200
138140
else:
141+
# Log the specific error for debugging
142+
logger.warning(f"TOTP verification failed for user {user_id}: {result.get('errors')}")
139143
return jsonify(result), 400
140144

141145
except Exception as e:

backend/services/auth_service.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ def __init__(self, db):
7474
self.db = db
7575
self.user_model = User(db)
7676
self.email_service = EmailService()
77-
self.passkey_service = PasskeyService()
77+
self.passkey_service = PasskeyService(self.db)
7878

7979
def register_user(self, username: str, email: str, password: str,
8080
phone: str = None, security_questions: list = None, lang: str = 'en') -> dict:

backend/services/passkey_service.py

Lines changed: 34 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -37,33 +37,48 @@ def _b64url_decode(data: str) -> bytes:
3737
class PasskeyService:
3838
"""Service for managing WebAuthn/Passkey authentication."""
3939

40-
def __init__(self, rp_id: str = None, rp_name: str = "TopicsFlow"):
40+
def __init__(self, db, rp_id=None, rp_name=None):
4141
"""
4242
Initialize passkey service.
4343
4444
Args:
4545
rp_id: Relying Party ID (your domain, e.g., 'localhost' or 'topicsflow.me')
4646
rp_name: Relying Party name (your app name)
4747
"""
48-
# Use environment variable or default
49-
# Origin for WebAuthn (e.g., 'http://localhost:3000' or 'https://topicsflow.me')
50-
self.origin = os.getenv('FRONTEND_URL', 'http://localhost:3000')
51-
52-
# Determine RP ID
53-
if rp_id:
54-
self.rp_id = rp_id
55-
elif os.getenv('PASSKEY_RP_ID'):
56-
self.rp_id = os.getenv('PASSKEY_RP_ID')
48+
self.db = db
49+
50+
# Determine environment
51+
# IS_AZURE is usually set in config.py or env, check here if env var
52+
self.is_azure = os.getenv('IS_AZURE', 'False').lower() == 'true'
53+
54+
if not self.is_azure:
55+
# Local Development Override
56+
# Force localhost settings to prevent accidental production configuration usage
57+
# This is critical for local testing if env vars like FRONTEND_URL are set to prod
58+
self.rp_id = 'localhost'
59+
self.origin = 'http://localhost:3000'
60+
logger.info("Running in local mode: Forced Passkey RP_ID to localhost and Origin to http://localhost:3000")
5761
else:
58-
# Derive from FRONTEND_URL if not explicitly set
59-
# This handles 'https://topicsflow.me' -> 'topicsflow.me'
60-
from urllib.parse import urlparse
61-
try:
62-
parsed_url = urlparse(self.origin)
63-
self.rp_id = parsed_url.hostname or 'localhost'
64-
except Exception as e:
65-
logger.warning(f"Failed to parse FRONTEND_URL for RP ID: {e}")
66-
self.rp_id = 'localhost'
62+
# Production / Azure Configuration
63+
# Use environment variable or default
64+
# Origin for WebAuthn (e.g., 'http://localhost:3000' or 'https://topicsflow.me')
65+
self.origin = os.getenv('FRONTEND_URL', 'http://localhost:3000')
66+
67+
# Determine RP ID
68+
if rp_id:
69+
self.rp_id = rp_id
70+
elif os.getenv('PASSKEY_RP_ID'):
71+
self.rp_id = os.getenv('PASSKEY_RP_ID')
72+
else:
73+
# Derive from FRONTEND_URL if not explicitly set
74+
# This handles 'https://topicsflow.me' -> 'topicsflow.me'
75+
from urllib.parse import urlparse
76+
try:
77+
parsed_url = urlparse(self.origin)
78+
self.rp_id = parsed_url.hostname or 'localhost'
79+
except Exception as e:
80+
logger.warning(f"Failed to parse FRONTEND_URL for RP ID: {e}")
81+
self.rp_id = 'localhost'
6782

6883
self.rp_name = rp_name or os.getenv('APP_NAME', 'TopicsFlow')
6984

backend/utils/validators.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@ def validate_totp_code(code: str) -> bool:
7979
if not code:
8080
return False
8181

82+
# Ensure code is a string
83+
code = str(code).strip()
84+
8285
# TOTP codes are 6 digits
8386
return bool(re.match(r'^\d{6}$', code))
8487

@@ -88,6 +91,9 @@ def validate_backup_code(code: str) -> bool:
8891
if not code:
8992
return False
9093

94+
# Ensure code is a string
95+
code = str(code).strip()
96+
9197
# Backup codes are typically 8 digits
9298
return bool(re.match(r'^\d{8}$', code))
9399

frontend/next.config.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ const nextConfig = {
1010
formats: ['image/webp', 'image/avif'],
1111
},
1212
env: {
13-
NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL || 'https://topicsflow.me',
13+
// defaults to undefined if not set, letting api.ts handle the fallback to localhost
14+
NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL,
1415
NEXT_PUBLIC_APP_NAME: process.env.NEXT_PUBLIC_APP_NAME || 'TopicsFlow',
1516
NEXT_PUBLIC_TENOR_API_KEY: process.env.NEXT_PUBLIC_TENOR_API_KEY || '',
1617
},

frontend/utils/api.ts

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,22 @@ class ApiClient {
2121
window.location.hostname === 'www.topicsflow.me' ||
2222
window.location.hostname.includes('azurestaticapps.net');
2323

24-
if (backendUrl) {
25-
// Use provided backend URL
26-
baseURL = backendUrl;
27-
} else if (isProduction) {
28-
// Production but no backend URL set: use topicsflow.me
29-
// This allows calls to go to /api/... on the same domain (topicsflow.me)
24+
const isLocalhost = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1';
25+
26+
if (isProduction) {
27+
// Production: Always use topicsflow.me
3028
baseURL = 'https://topicsflow.me';
29+
} else if (isLocalhost) {
30+
// Local Development: Prioritize localhost:5000
31+
// If provided backendUrl matches topicsflow.me, ignore it to prevent accidental prod usage
32+
if (backendUrl === 'https://topicsflow.me') {
33+
baseURL = 'http://localhost:5000';
34+
console.warn('[ApiClient] NEXT_PUBLIC_API_URL points to production but running on localhost. Forcing connection to http://localhost:5000');
35+
} else {
36+
baseURL = backendUrl || 'http://localhost:5000';
37+
}
3138
} else {
32-
// Local Development: Use provided URL or default to topicsflow.me if not set
33-
// (to avoid accidental localhost usage when targeting prod)
34-
// However, if needed for local dev without env var, users should set NEXT_PUBLIC_API_URL
39+
// Other environments (staging, etc): Use provided URL or fallback
3540
baseURL = backendUrl || 'https://topicsflow.me';
3641
}
3742
} else {
@@ -546,6 +551,6 @@ export const getApiBaseUrl = (): string => {
546551
return 'https://topicsflow.me';
547552
} else {
548553
// Local Development:
549-
return backendUrl || 'https://topicsflow.me';
554+
return backendUrl || 'http://localhost:5000';
550555
}
551556
};

0 commit comments

Comments
 (0)