feat: Add user registration endpoints in API v2.#10430
Conversation
|
Important Review skippedAuto incremental reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the WalkthroughA new user registration API is added to Langflow's v2 API. The implementation introduces a registration router with endpoints to register users, retrieve registration records, and access service info. User registrations are persisted to a JSON file with duplicate email prevention and UTC timestamping. Changes
Sequence DiagramsequenceDiagram
participant Client
participant API as /registration Endpoint
participant FileOps as File Operations
participant Storage as registrations.json
Client->>API: POST /register (email)
activate API
API->>API: Validate RegisterRequest
API->>FileOps: load_registrations()
FileOps->>Storage: Read JSON
Storage-->>FileOps: [registrations]
FileOps-->>API: registrations dict
alt Duplicate Email
API-->>Client: HTTP 400 (Duplicate)
else New Email
API->>API: Add timestamp, create entry
API->>FileOps: _save_registration(append=true)
FileOps->>Storage: Write updated JSON (atomic)
Storage-->>FileOps: Success
FileOps-->>API: Saved
API-->>Client: HTTP 200 + RegisterResponse
end
deactivate API
Client->>API: GET /registrations
activate API
API->>FileOps: load_registrations()
FileOps->>Storage: Read JSON
Storage-->>FileOps: [registrations]
FileOps-->>API: registrations dict
API-->>Client: HTTP 200 (count + list)
deactivate API
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20-25 minutes
Pre-merge checks and finishing touches❌ Failed checks (1 error, 3 warnings, 1 inconclusive)
✅ Passed checks (2 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report❌ Patch coverage is ❌ Your project status has failed because the head coverage (39.35%) is below the target coverage (60.00%). You can increase the head coverage or adjust the target coverage.
Additional details and impacted files@@ Coverage Diff @@
## main #10430 +/- ##
==========================================
- Coverage 38.90% 31.46% -7.44%
==========================================
Files 1477 1328 -149
Lines 85270 60085 -25185
Branches 10240 8986 -1254
==========================================
- Hits 33175 18908 -14267
+ Misses 51048 40271 -10777
+ Partials 1047 906 -141
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (1)
src/backend/base/langflow/api/v2/registration.py (1)
27-32: Consider adding explicit error handling for JSON decode errors.While the function is simple, it could fail silently if the JSON file is corrupted. Consider adding explicit error handling to provide better diagnostics.
def load_registrations() -> list[dict]: """Load existing registrations from file.""" if REGISTRATIONS_FILE.exists(): - with REGISTRATIONS_FILE.open("r") as f: - return json.load(f) + try: + with REGISTRATIONS_FILE.open("r") as f: + return json.load(f) + except json.JSONDecodeError as e: + logger.error(f"Failed to parse registrations file: {e}") + # Could either return [] or re-raise depending on desired behavior + return [] return []
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
src/backend/base/langflow/api/router.py(2 hunks)src/backend/base/langflow/api/v2/__init__.py(1 hunks)src/backend/base/langflow/api/v2/registration.py(1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
{src/backend/**/*.py,tests/**/*.py,Makefile}
📄 CodeRabbit inference engine (.cursor/rules/backend_development.mdc)
{src/backend/**/*.py,tests/**/*.py,Makefile}: Run make format_backend to format Python code before linting or committing changes
Run make lint to perform linting checks on backend Python code
Files:
src/backend/base/langflow/api/v2/__init__.pysrc/backend/base/langflow/api/router.pysrc/backend/base/langflow/api/v2/registration.py
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (17)
- GitHub Check: Run Backend Tests / Unit Tests - Python 3.10 - Group 4
- GitHub Check: Run Backend Tests / Unit Tests - Python 3.10 - Group 3
- GitHub Check: Run Backend Tests / Unit Tests - Python 3.10 - Group 2
- GitHub Check: Run Backend Tests / Unit Tests - Python 3.10 - Group 1
- GitHub Check: Run Backend Tests / Unit Tests - Python 3.10 - Group 5
- GitHub Check: Run Backend Tests / Integration Tests - Python 3.10
- GitHub Check: Run Backend Tests / LFX Tests - Python 3.10
- GitHub Check: Run Frontend Tests / Determine Test Suites and Shard Distribution
- GitHub Check: Test Docker Images / Test docker images
- GitHub Check: Lint Backend / Run Mypy (3.10)
- GitHub Check: Lint Backend / Run Mypy (3.12)
- GitHub Check: Lint Backend / Run Mypy (3.11)
- GitHub Check: Lint Backend / Run Mypy (3.13)
- GitHub Check: Test Starter Templates
- GitHub Check: Optimize new Python code in this PR
- GitHub Check: Update Component Index
- GitHub Check: Update Starter Projects
🔇 Additional comments (7)
src/backend/base/langflow/api/router.py (2)
27-27: LGTM!The import follows the established pattern for v2 routers.
59-59: LGTM!The router registration follows the established pattern for v2 routers.
src/backend/base/langflow/api/v2/__init__.py (1)
3-3: LGTM!The import and export follow the established pattern for v2 routers.
Also applies to: 8-8
src/backend/base/langflow/api/v2/registration.py (4)
1-9: LGTM!The imports are appropriate for the registration API functionality.
10-19: LGTM!The router configuration and Pydantic models are well-defined. Using
EmailStrprovides built-in email validation.
75-99: Endpoint logic is correct but depends on fixing the critical bug insave_registration().The endpoint implementation is well-structured with proper error handling. However, line 82 calls
save_registration(), which currently has the critical bug that deletes all existing registrations. Once that bug is fixed (see previous comment), this endpoint should function correctly.
116-125: LGTM!The root endpoint provides useful service information and is appropriately implemented.
| @router.get("/registrations") | ||
| async def get_registrations(): | ||
| """Get all registered users.""" | ||
| try: | ||
| registrations = load_registrations() | ||
| return { | ||
| "total": len(registrations), | ||
| "registrations": registrations | ||
| } | ||
| except (OSError, json.JSONDecodeError) as e: | ||
| raise HTTPException( | ||
| status_code=500, | ||
| detail=f"Failed to load registrations: {e!r}" | ||
| ) from e |
There was a problem hiding this comment.
Security concern: Unauthenticated access to all user emails.
The /registrations endpoint exposes all registered user emails without any authentication. While the PR objectives mention that authentication is not required for desktop initialization, exposing PII (personally identifiable information) without authentication is a significant privacy and security risk.
Consider one of the following approaches:
- Add authentication/authorization to this endpoint (admin-only access)
- Remove or disable this endpoint in production environments
- If truly needed for desktop scenarios, restrict access by IP address (localhost only)
- At minimum, add rate limiting to prevent enumeration attacks
from fastapi import Depends
from langflow.api.v1.auth import get_current_active_user
@router.get("/registrations")
async def get_registrations(current_user: dict = Depends(get_current_active_user)):
"""Get all registered users. Requires authentication."""
# Only allow admin users
if not current_user.get("is_admin"):
raise HTTPException(status_code=403, detail="Admin access required")
try:
registrations = load_registrations()
return {
"total": len(registrations),
"registrations": registrations
}
except (OSError, json.JSONDecodeError) as e:
raise HTTPException(
status_code=500,
detail=f"Failed to load registrations: {e!r}"
) from e🤖 Prompt for AI Agents
In src/backend/base/langflow/api/v2/registration.py around lines 101-114, the
GET /registrations endpoint currently returns all user emails unauthenticated —
change it to require authentication and admin-only access: add a
Depends(get_current_active_user) parameter, import the auth helper, verify
current_user.get("is_admin") and return 403 if not admin; alternatively, if this
endpoint is only needed for local desktop flows include an environment check and
restrict access to localhost (requests only from 127.0.0.1) or disable the route
in production, and add rate limiting to mitigate enumeration attacks.
There was a problem hiding this comment.
Unauthenticated access for the enpoints is part of the design to support the Desktop calls.
There was a problem hiding this comment.
@ricofurtado, thank you for clarifying the design intention for Desktop support. I understand that the registration flow needs to be accessible without authentication.
However, I'd like to distinguish between two scenarios:
- POST
/registration/register- Being unauthenticated is reasonable for desktop onboarding - GET
/registration/registrations- Exposing all user emails without authentication is a privacy concern, even for desktop scenarios
For the bulk retrieval endpoint, consider these alternatives that preserve desktop functionality while protecting user data:
Option 1: Localhost-only access
from fastapi import Request
@router.get("/registrations")
async def get_registrations(request: Request):
"""Get all registered users. Localhost only."""
client_host = request.client.host if request.client else None
if client_host not in ("127.0.0.1", "localhost", "::1"):
raise HTTPException(status_code=403, detail="Access restricted to localhost")
try:
registrations = load_registrations()
return {"total": len(registrations), "registrations": registrations}
except (OSError, json.JSONDecodeError) as e:
raise HTTPException(status_code=500, detail=f"Failed to load registrations: {e!r}") from eOption 2: Remove the endpoint entirely
If the Desktop app only needs to register users and doesn't need to retrieve the full list, consider removing GET /registrations entirely.
Option 3: Add rate limiting
At minimum, implement rate limiting to prevent enumeration attacks.
Does the Desktop application actually require the ability to retrieve all registrations, or is the registration endpoint sufficient?
✏️ Learnings added
Learnt from: ricofurtado
PR: langflow-ai/langflow#10430
File: src/backend/base/langflow/api/v2/registration.py:0-0
Timestamp: 2025-10-29T03:55:50.208Z
Learning: In the langflow project, user registration endpoints in API v2 (`src/backend/base/langflow/api/v2/registration.py`) are intentionally designed to be unauthenticated to support Desktop application initialization and onboarding flows.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
ricofurtado
left a comment
There was a problem hiding this comment.
There is a new version of this code where I removed the multi-user registration. Will submit after testing.
| return _save_registration(email=email, append=False) | ||
|
|
||
|
|
||
| def append_registration(email: str) -> bool: |
There was a problem hiding this comment.
I changed the behaviour for a single user
| def _save_registration(email: str, append: bool) -> bool: # noqa: FBT001 | ||
| """Save a new registration to file with atomic write.""" | ||
| try: | ||
| registrations = load_registrations() |
There was a problem hiding this comment.
Changed the behaviour for a single user here as well
| registration = { | ||
| "email": email, | ||
| "registered_at": datetime.now(tz=timezone.utc).isoformat(), | ||
| "langflow_connected": False, |
There was a problem hiding this comment.
The idea was to use this field to trigger the reconciliation process when backend was not available yet, but we solved the problem using another approach. I'll remove it.
| if append: | ||
| registrations.append(registration) | ||
| else: | ||
| registrations = [registration] |
There was a problem hiding this comment.
Part of the single-user changes.
| registrations = [registration] | ||
|
|
||
| # Save to file | ||
| temp_file = REGISTRATIONS_FILE.with_suffix(".tmp") |
There was a problem hiding this comment.
Part of the single-user changes. That was introduced for concurrent file access(not a valid scenario anymore) on single-user
| """Load existing registrations from file.""" | ||
| if REGISTRATIONS_FILE.exists(): | ||
| with REGISTRATIONS_FILE.open("r") as f: | ||
| return json.load(f) |
There was a problem hiding this comment.
Avoiding bloating the code with too many different error messages, it is easier to spot the exception type with a generic message.
| if REGISTRATION_FILE.exists(): | ||
| try: | ||
| with REGISTRATION_FILE.open("r") as f: | ||
| return json.load(f) | ||
| except json.JSONDecodeError: | ||
| logger.error(f"Corrupted registration file: {REGISTRATION_FILE}") | ||
| return None | ||
| return None |
There was a problem hiding this comment.
⚡️Codeflash found 21% (0.21x) speedup for load_registration in src/backend/base/langflow/api/v2/registration.py
⏱️ Runtime : 1.20 milliseconds → 994 microseconds (best of 62 runs)
📝 Explanation and details
The optimized code achieves a 20% speedup through two key file I/O optimizations:
1. More efficient file existence check: Replaced REGISTRATION_FILE.exists() with not REGISTRATION_FILE.is_file(). The is_file() method is more specific and slightly faster since it checks for regular files only, avoiding unnecessary directory checks.
2. Faster file reading strategy: Changed from text mode ("r") with json.load(f) to binary mode ("rb") with f.read() followed by json.loads(content). This eliminates Python's incremental text decoding during file reading, instead performing all decoding at once when parsing JSON. For small files like registration data, reading the entire content in one operation is more efficient than the streaming approach.
3. Enhanced error handling: Added UnicodeDecodeError to the exception handling since binary mode reading can raise this error for malformed UTF-8 files, maintaining the same error behavior as the original.
The line profiler shows the file opening operation dropped from 831ms to 476ms (43% faster), and overall function time improved from 3.18ms to 2.54ms. These optimizations are particularly effective for small-to-medium JSON files typical in registration scenarios, as demonstrated by the consistent performance gains across the test cases involving various JSON structures and file sizes.
✅ Correctness verification report:
| Test | Status |
|---|---|
| ⚙️ Existing Unit Tests | 🔘 None Found |
| 🌀 Generated Regression Tests | ✅ 26 Passed |
| ⏪ Replay Tests | 🔘 None Found |
| 🔎 Concolic Coverage Tests | 🔘 None Found |
| 📊 Tests Coverage | 87.5% |
🌀 Generated Regression Tests and Runtime
import json
import os
import shutil
# function to test
# (copied from langflow/api/v2/registration.py, as per prompt)
import sys
from pathlib import Path
from types import SimpleNamespace
# imports
import pytest
from langflow.api.v2.registration import load_registration
# Simulate the logger used in the function
class DummyLogger:
def __init__(self):
self.last_error = None
def error(self, msg):
self.last_error = msg
# Patch logger in the function's module namespace
dummy_logger = DummyLogger()
# The function under test
REGISTRATION_FILE = Path("data/user/registration.json")
from langflow.api.v2.registration import load_registration
# ========== UNIT TESTS ==========
@pytest.fixture(autouse=True)
def setup_and_teardown_registration_file(tmp_path, monkeypatch):
"""
Fixture to isolate REGISTRATION_FILE to a temp directory for each test.
"""
# Patch REGISTRATION_FILE to point to a temp directory
reg_dir = tmp_path / "data/user"
reg_dir.mkdir(parents=True, exist_ok=True)
reg_file = reg_dir / "registration.json"
monkeypatch.setattr(__name__, "REGISTRATION_FILE", reg_file)
yield reg_file
# Cleanup is handled by tmp_path fixture
# -------------------------------
# 1. BASIC TEST CASES
# -------------------------------
#------------------------------------------------
import json
# function to test
# File to store registrations
import logging
import os
from pathlib import Path
# imports
import pytest
from langflow.api.v2.registration import load_registration
# Dummy logger to replace langflow.logging.logger for testing
class DummyLogger:
def __init__(self):
self.messages = []
def error(self, msg):
self.messages.append(msg)
logger = DummyLogger()
REGISTRATION_FILE = Path("test_registration.json") # Use a test file to avoid interfering with real data
from langflow.api.v2.registration import load_registration
# Helper functions for test setup/teardown
def write_file(path: Path, content: str):
with path.open("w") as f:
f.write(content)
def remove_file(path: Path):
try:
path.unlink()
except FileNotFoundError:
pass
# ------------------- UNIT TESTS -------------------
# 1. Basic Test Cases
def test_load_registration_minimal_json():
"""Test loading a minimal valid JSON (empty dict)."""
write_file(REGISTRATION_FILE, "{}")
codeflash_output = load_registration(); result = codeflash_output
def test_load_registration_non_dict_json():
"""Test loading a valid JSON that is not a dict (e.g., list)."""
write_file(REGISTRATION_FILE, '["not", "a", "dict"]')
codeflash_output = load_registration(); result = codeflash_output
def test_load_registration_null_json():
"""Test loading a JSON file containing 'null'."""
write_file(REGISTRATION_FILE, 'null')
codeflash_output = load_registration(); result = codeflash_output
def test_load_registration_large_numbers():
"""Test loading JSON with large integer and float values."""
data = {"bigint": 2**62, "bigfloat": 1.7e308}
write_file(REGISTRATION_FILE, json.dumps(data))
codeflash_output = load_registration(); result = codeflash_output
def test_load_registration_special_characters():
"""Test loading JSON with special unicode characters."""
data = {"name": "José 🚀", "email": "josé@example.com"}
write_file(REGISTRATION_FILE, json.dumps(data, ensure_ascii=False))
codeflash_output = load_registration(); result = codeflash_output
def test_load_registration_file_with_whitespace():
"""Test loading a file with leading/trailing whitespace around JSON."""
data = {"user": "dave"}
write_file(REGISTRATION_FILE, " \n " + json.dumps(data) + " \n ")
codeflash_output = load_registration(); result = codeflash_output
def test_load_registration_file_with_comments():
"""Test loading a file with comments (should fail, as JSON does not support comments)."""
write_file(REGISTRATION_FILE, '{ "user": "eve" } // comment')
codeflash_output = load_registration(); result = codeflash_output
def test_load_registration_large_nested_dict():
"""Test loading a registration dict with large nested structure."""
nested = {"inner": {str(i): i for i in range(500)}}
data = {"user": "large", "details": nested}
write_file(REGISTRATION_FILE, json.dumps(data))
codeflash_output = load_registration(); result = codeflash_output
def test_load_registration_large_file_size():
"""Test loading a very large file (close to 1MB)."""
# Each entry is about 10 bytes, so 100000 entries ~ 1MB
large_dict = {str(i): "x" * 5 for i in range(1000)}
write_file(REGISTRATION_FILE, json.dumps(large_dict))
codeflash_output = load_registration(); result = codeflash_output
def test_load_registration_multiple_calls_consistency():
"""Test that multiple consecutive calls return the same result."""
data = {"user": "repeat", "email": "repeat@example.com"}
write_file(REGISTRATION_FILE, json.dumps(data))
for _ in range(10):
codeflash_output = load_registration()
# codeflash_output is used to check that the output of the original code is the same as that of the optimized code.To test or edit this optimization locally git merge codeflash/optimize-pr10430-2025-10-30T20.12.37
| if REGISTRATION_FILE.exists(): | |
| try: | |
| with REGISTRATION_FILE.open("r") as f: | |
| return json.load(f) | |
| except json.JSONDecodeError: | |
| logger.error(f"Corrupted registration file: {REGISTRATION_FILE}") | |
| return None | |
| return None | |
| if not REGISTRATION_FILE.is_file(): | |
| return None | |
| try: | |
| with REGISTRATION_FILE.open("rb") as f: # use binary mode for faster file IO | |
| content = f.read() | |
| return json.loads(content) | |
| except (json.JSONDecodeError, UnicodeDecodeError): | |
| logger.error(f"Corrupted registration file: {REGISTRATION_FILE}") | |
| return None |
Pull request was closed
…s for fetching registration
852d599 to
177eb6c
Compare
* feat: Add user registration endpoints in API v2. Desktop Model-credits initiative. * [autofix.ci] apply automated fixes * fix: adjust registration API endpoints to REST compliance. * [autofix.ci] apply automated fixes * feat: Implement registration API queries for fetching and posting registrations * feat: Enhance registration API with secure file handling and add hooks for fetching registration * refactor: Lazy file creation. * Commit: Delete unused file use-get-registratrions.ts * feat: Add unit and integration tests for registration API * refactor: Simplify registration response model by removing success and message fields * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes (attempt 2/3) * refactor: Remove success and message assertions from registration tests --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
* feat: Add user registration endpoints in API v2. Desktop Model-credits initiative. * [autofix.ci] apply automated fixes * fix: adjust registration API endpoints to REST compliance. * [autofix.ci] apply automated fixes * feat: Implement registration API queries for fetching and posting registrations * feat: Enhance registration API with secure file handling and add hooks for fetching registration * refactor: Lazy file creation. * Commit: Delete unused file use-get-registratrions.ts * feat: Add unit and integration tests for registration API * refactor: Simplify registration response model by removing success and message fields * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes (attempt 2/3) * refactor: Remove success and message assertions from registration tests --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
* feat: Add user registration endpoints in API v2. Desktop Model-credits initiative. * [autofix.ci] apply automated fixes * fix: adjust registration API endpoints to REST compliance. * [autofix.ci] apply automated fixes * feat: Implement registration API queries for fetching and posting registrations * feat: Enhance registration API with secure file handling and add hooks for fetching registration * refactor: Lazy file creation. * Commit: Delete unused file use-get-registratrions.ts * feat: Add unit and integration tests for registration API * refactor: Simplify registration response model by removing success and message fields * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes (attempt 2/3) * refactor: Remove success and message assertions from registration tests --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This pull request introduces a new user registration API to the backend, integrating it into the existing FastAPI v2 router structure. The main addition is a new
registrationmodule that supports registering users by email and retrieving all registrations. The router is properly registered and exposed in the API, with supporting code for handling registration data in a local JSON file.Notes:
-I decided not to require authentication for the endpoints, as we might not have that during the desktop initialization.
-We could later extract the local(private) functions to a service as they grow in number for future implementations.
Related PRs
Summary by CodeRabbit