Repository navigation
Staging #230
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Staging #230
Changes from 7 commits
fdc5810
53b60db
461060b
5ab0d4e
24c5ff3
3549a12
5182962
a7ccd41
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,7 +6,6 @@ | |
| import logging | ||
| import os | ||
| import re | ||
| import time | ||
| import secrets | ||
|
|
||
| from pathlib import Path | ||
|
|
@@ -161,6 +160,17 @@ async def _set_mail_hash(email: str, password_hash: str) -> bool: | |
| status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | ||
| detail="Internal server error" | ||
| ) | ||
|
|
||
| def _is_request_secure(request: Request) -> bool: | ||
| """Determine if the request is secure (HTTPS).""" | ||
|
|
||
| # Check X-Forwarded-Proto first (proxy-aware) | ||
| forwarded_proto = request.headers.get("x-forwarded-proto") | ||
| if forwarded_proto: | ||
| return forwarded_proto == "https" | ||
|
|
||
| # Fallback to request URL scheme | ||
| return request.url.scheme == "https" | ||
|
|
||
| async def _authenticate_email_user(email: str, password: str): | ||
| """Authenticate an email user.""" | ||
|
|
@@ -206,18 +216,18 @@ async def email_signup(request: Request, signup_data: EmailSignupRequest) -> JSO | |
| """Handle email/password user registration.""" | ||
| try: | ||
| # Check if email authentication is enabled | ||
| if os.getenv("EMAIL_AUTH_ENABLED", "").lower() not in ["true", "1", "yes", "on"]: | ||
| raise HTTPException( | ||
| status_code=status.HTTP_403_FORBIDDEN, | ||
| detail="GEmail authentication is not enabled" | ||
| ) | ||
| if not _is_email_auth_enabled(): | ||
| return JSONResponse( | ||
| {"success": False, "error": "Email authentication is not enabled"}, | ||
| status_code=status.HTTP_403_FORBIDDEN | ||
| ) | ||
|
|
||
| # Validate required fields | ||
| if not all([signup_data.firstName, signup_data.lastName, | ||
| signup_data.email, signup_data.password]): | ||
| raise HTTPException( | ||
| status_code=status.HTTP_400_BAD_REQUEST, | ||
| detail="All fields are required" | ||
| return JSONResponse( | ||
| {"success": False, "error": "All fields are required"}, | ||
| status_code=status.HTTP_400_BAD_REQUEST | ||
| ) | ||
|
|
||
| first_name = signup_data.firstName.strip() | ||
|
|
@@ -227,16 +237,16 @@ async def email_signup(request: Request, signup_data: EmailSignupRequest) -> JSO | |
|
|
||
| # Validate email format | ||
| if not _validate_email(email): | ||
| raise HTTPException( | ||
| status_code=status.HTTP_400_BAD_REQUEST, | ||
| detail="Invalid email format" | ||
| return JSONResponse( | ||
| {"success": False, "error": "Invalid email format"}, | ||
| status_code=status.HTTP_400_BAD_REQUEST | ||
| ) | ||
|
|
||
| # Validate password strength | ||
| if len(password) < 8: | ||
| raise HTTPException( | ||
| status_code=status.HTTP_400_BAD_REQUEST, | ||
| detail="Password must be at least 8 characters long" | ||
| return JSONResponse( | ||
| {"success": False, "error": "Password must be at least 8 characters long"}, | ||
| status_code=status.HTTP_400_BAD_REQUEST | ||
| ) | ||
|
|
||
| api_token = secrets.token_urlsafe(32) | ||
|
|
@@ -265,23 +275,23 @@ async def email_signup(request: Request, signup_data: EmailSignupRequest) -> JSO | |
| key="api_token", | ||
| value=api_token, | ||
| httponly=True, | ||
| secure=True | ||
| secure=_is_request_secure(request) | ||
| ) | ||
| return response | ||
|
|
||
| except Exception as e: | ||
| logging.error("Signup error: %s", e) | ||
| raise HTTPException( | ||
| status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | ||
| detail="Registration failed" | ||
| return JSONResponse( | ||
| {"success": False, "error": "Registration failed"}, | ||
| status_code=status.HTTP_500_INTERNAL_SERVER_ERROR | ||
| ) | ||
|
Comment on lines
282
to
287
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Tighten exception handling and improve logging. Avoid blind - except Exception as e:
- logging.error("Signup error: %s", e)
+ except HTTPException:
+ raise
+ except Exception as e:
+ logging.exception("Signup error: %s", e)
return JSONResponse(
{"success": False, "error": "Registration failed"},
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR
)- except Exception as e:
- logging.error("Login error: %s", e)
+ except HTTPException:
+ raise
+ except Exception as e:
+ logging.exception("Login error: %s", e)
return JSONResponse(
{"success": False, "error": "Login failed"},
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR
)Also applies to: 364-369 🧰 Tools🪛 Ruff (0.12.2)282-282: Do not catch blind exception: (BLE001) 283-283: Use Replace with (TRY400) 🤖 Prompt for AI Agents |
||
|
|
||
| @auth_router.post("/login/email") | ||
| async def email_login(request: Request, login_data: EmailLoginRequest) -> JSONResponse: | ||
| """Handle email/password user login.""" | ||
| try: | ||
| # Check if email authentication is enabled | ||
| if os.getenv("EMAIL_AUTH_ENABLED", "").lower() not in ["true", "1", "yes", "on"]: | ||
| if not _is_email_auth_enabled(): | ||
| return JSONResponse( | ||
| {"success": False, "error": "Email authentication is not enabled"}, | ||
| status_code=status.HTTP_403_FORBIDDEN | ||
|
|
@@ -309,48 +319,52 @@ async def email_login(request: Request, login_data: EmailLoginRequest) -> JSONRe | |
|
|
||
| if not success: | ||
| return JSONResponse( | ||
| {"success": False, "error": result}, | ||
| {"success": False, "error": result}, | ||
| status_code=status.HTTP_401_UNAUTHORIZED | ||
| ) | ||
|
|
||
| # Set session data - result is a dict when success is True | ||
| if isinstance(result, dict): | ||
| user_node = result.get("user") | ||
| identity_node = result.get("identity") | ||
|
|
||
| # Access node properties correctly | ||
| user_props = ( | ||
| user_node.properties | ||
| if user_node and hasattr(user_node, "properties") | ||
| else {} | ||
| ) | ||
| identity_props = ( | ||
| identity_node.properties | ||
| if identity_node and hasattr(identity_node, "properties") | ||
| else {} | ||
| ) | ||
|
|
||
| request.session["user_info"] = { | ||
| "id": identity_props.get("provider_user_id", email), | ||
| "name": user_props.get("name", ""), | ||
| "email": user_props.get("email", email), | ||
| "picture": user_props.get("picture", ""), | ||
| "provider": "email", | ||
|
|
||
| user_data = { | ||
| 'id': identity_props.get("provider_user_id", email), | ||
| 'email': identity_props.get('email', email), | ||
| 'name': identity_props.get('name', ''), | ||
| 'picture': identity_props.get('picture', ''), | ||
| } | ||
| request.session["email_authenticated"] = True | ||
| request.session["token_validated_at"] = time.time() | ||
|
|
||
| return JSONResponse({"success": True, "message": "Login successful"}) | ||
| else: | ||
| return JSONResponse( | ||
| {"success": False, "error": "Authentication failed"}, | ||
| status_code=status.HTTP_401_UNAUTHORIZED | ||
| ) | ||
| # Call the registered Google callback handler if it exists to store user data. | ||
| handler = getattr(request.app.state, "callback_handler", None) | ||
| if handler: | ||
| api_token = secrets.token_urlsafe(32) # ~43 chars, hard to guess | ||
|
|
||
| # Call the registered handler (await if async) | ||
| await handler('email', user_data, api_token) | ||
| response = JSONResponse({"success": True}, status_code=200) | ||
|
|
||
| response.set_cookie( | ||
| key="api_token", | ||
| value=api_token, | ||
Check failureCode scanning / CodeQL Clear-text storage of sensitive information
This expression stores [sensitive data (password)](1) as clear text.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. but we're using secure cookie, why do we need to encrypt it twice? |
||
| httponly=True, | ||
| secure=_is_request_secure(request) | ||
| ) | ||
| return response | ||
|
|
||
| return JSONResponse( | ||
| {"success": False, "error": "Authentication failed"}, | ||
| status_code=status.HTTP_401_UNAUTHORIZED | ||
| ) | ||
| except Exception as e: | ||
| logging.error("Login error: %s", e) | ||
| return JSONResponse( | ||
| {"success": False, "error": "Login failed"}, | ||
| {"success": False, "error": "Login failed"}, | ||
| status_code=status.HTTP_500_INTERNAL_SERVER_ERROR | ||
| ) | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,7 +1,11 @@ | ||||||||||||||||||||||||||
| {# User profile button and dropdown #} | ||||||||||||||||||||||||||
| <button id="user-profile-btn" class="user-profile-btn" title="{{ user_info.name }}" | ||||||||||||||||||||||||||
| aria-haspopup="true" aria-expanded="false" aria-controls="user-profile-dropdown"> | ||||||||||||||||||||||||||
| <img src="{{ user_info.picture }}" alt="{{ user_info.name[0] | upper }}" class="user-profile-img"> | ||||||||||||||||||||||||||
| {% if user_info.picture %} | ||||||||||||||||||||||||||
| <img src="{{ user_info.picture }}" alt="{{ user_info.name[0] | upper }}" class="user-profile-img"> | ||||||||||||||||||||||||||
| {% else %} | ||||||||||||||||||||||||||
| <div class="user-profile-img user-profile-initial">{{ user_info.name[0] | upper }}</div> | ||||||||||||||||||||||||||
| {% endif %} | ||||||||||||||||||||||||||
|
Comment on lines
+4
to
+8
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Guard against empty/missing name; improve alt text. Indexing - {% if user_info.picture %}
- <img src="{{ user_info.picture }}" alt="{{ user_info.name[0] | upper }}" class="user-profile-img">
- {% else %}
- <div class="user-profile-img user-profile-initial">{{ user_info.name[0] | upper }}</div>
- {% endif %}
+ {% set display_char = (user_info.name or user_info.email or '?')[:1] | upper %}
+ {% set alt_text = (user_info.name or user_info.email or 'User') %}
+ {% if user_info.picture %}
+ <img src="{{ user_info.picture }}" alt="{{ alt_text }}" class="user-profile-img">
+ {% else %}
+ <div class="user-profile-img user-profile-initial">{{ display_char }}</div>
+ {% endif %}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||
| </button> | ||||||||||||||||||||||||||
| <div id="user-profile-dropdown" class="user-profile-dropdown" role="menu" aria-labelledby="user-profile-btn"> | ||||||||||||||||||||||||||
| <div class="user-profile-info"> | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -7,7 +7,7 @@ FALKORDB_HOST="${FALKORDB_HOST:-localhost}" | |||||||||||
| FALKORDB_PORT="${FALKORDB_PORT:-6379}" | ||||||||||||
|
|
||||||||||||
| # Start FalkorDB Redis server in background | ||||||||||||
| redis-server --loadmodule /var/lib/falkordb/bin/falkordb.so & | ||||||||||||
| redis-server --loadmodule /var/lib/falkordb/bin/falkordb.so | cat & | ||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Pipeline hides Redis startup failures; remove pipe or enable pipefail.
-set -e
+set -e
+set -o pipefail
@@
-redis-server --loadmodule /var/lib/falkordb/bin/falkordb.so | cat &
+redis-server --loadmodule /var/lib/falkordb/bin/falkordb.so &If you need to tee logs, prefer 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||
|
|
||||||||||||
| # Wait until FalkorDB is ready | ||||||||||||
| echo "Waiting for FalkorDB to start on $FALKORDB_HOST:$FALKORDB_PORT..." | ||||||||||||
|
|
||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Make proxy HTTPS detection robust (multi-proxy, casing).
Handle comma-separated
X-Forwarded-Protoand case-insensitivity to avoid misclassifying HTTPS and failing to setSecureon cookies.📝 Committable suggestion
🤖 Prompt for AI Agents