Skip to content

Feat: Implement API Key Authentication and Standardize Logging#72

Open
alebmorais wants to merge 2 commits intomainfrom
feat-secure-and-debug
Open

Feat: Implement API Key Authentication and Standardize Logging#72
alebmorais wants to merge 2 commits intomainfrom
feat-secure-and-debug

Conversation

@alebmorais
Copy link
Owner

@alebmorais alebmorais commented Oct 28, 2025

This commit introduces two major improvements to the application:

  1. API Key Authentication: All API endpoints are now protected by an API key. This is a critical security enhancement to prevent unauthorized access. The API key is configured via the API_KEY environment variable and must be sent in the X-API-Key header.

  2. Standardized Logging: All print statements have been replaced with the logging module. A centralized logger is configured to write to logs/app.log, providing a structured and persistent record of application events and errors. This will significantly improve debugging and observability.

In addition to these main changes, a duplicate set_sense_mode method was removed to improve code clarity.

Summary by CodeRabbit

  • New Features
    • Endpoints now require an API key for access to improve API security.
  • Bug Fixes
    • Replaced ad-hoc console output with structured logging and standardized runtime messages.
    • Improved error handling so failures return clearer, user-friendly responses.
  • Chores
    • Environment example updated to include an API_KEY entry and explanatory comment.

✏️ Tip: You can customize this high-level summary in your review settings.

This commit introduces two major improvements to the application:

1.  **API Key Authentication:** All API endpoints are now protected by an API key. This is a critical security enhancement to prevent unauthorized access. The API key is configured via the `API_KEY` environment variable and must be sent in the `X-API-Key` header.

2.  **Standardized Logging:** All `print` statements have been replaced with the `logging` module. A centralized logger is configured to write to `logs/app.log`, providing a structured and persistent record of application events and errors. This will significantly improve debugging and observability.

In addition to these main changes, a duplicate `set_sense_mode` method was removed to improve code clarity.
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @alebmorais, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the application's security posture by implementing API key authentication across all endpoints. Concurrently, it improves the application's maintainability and diagnostic capabilities by migrating from ad-hoc print statements to a standardized logging system. These changes aim to create a more robust and observable application.

Highlights

  • API Key Authentication: All API endpoints are now protected by an API key, configured via the API_KEY environment variable and required in the X-API-Key header for enhanced security.
  • Standardized Logging: All print statements have been replaced with the logging module, configured to write structured logs to logs/app.log and the console, improving debugging and observability.
  • Code Cleanup: A duplicate set_sense_mode method was removed from main.py to improve code clarity and reduce redundancy.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@coderabbitai
Copy link

coderabbitai bot commented Oct 28, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds API key authentication for FastAPI endpoints via an APIKeyHeader dependency and get_api_key validator; introduces API_KEY in .env.example. Replaces print/traceback calls with structured Python logging and standardizes error handling across endpoints.

Changes

Cohort / File(s) Summary
Env config
\.env.example
Added comment and API_KEY="your_secret_api_key" entry to document and provide the API key environment variable.
Auth + Logging + Endpoints
main.py
Added APIKeyHeader and get_api_key() dependency that enforces API key validation; added API_KEY config read from environment. Updated endpoint signatures to depend on get_api_key. Replaced ad-hoc prints/tracebacks with structured logging (file + stdout handlers) and standardized error logging/responses.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant FastAPI
    participant get_api_key
    participant Endpoint
    participant Logger

    Client->>FastAPI: HTTP request (Authorization / x-api-key)
    FastAPI->>get_api_key: Depends(api_key_header) -> validate against API_KEY
    alt valid
        get_api_key->>FastAPI: returns key
        FastAPI->>Endpoint: call handler
        Endpoint->>Logger: info/debug logs (operation)
        Endpoint->>Client: 200 OK / payload
    else invalid or missing
        get_api_key->>Logger: warning/error (unauthorized)
        get_api_key->>Client: 401 Unauthorized (HTTPException)
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Review get_api_key() correctness and header handling (name, casing).
  • Confirm all sensitive endpoints now include the api_key dependency.
  • Verify logging configuration (handlers, rotation if needed) and that error logging uses exc_info where intended.

Possibly related PRs

Poem

🐇 I hid a tiny secret key,
Guards the routes so safe and free,
Logs now hum a tidy tune,
No more prints at half past noon,
Hop, secure—API bliss for me!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the two main changes: implementing API Key Authentication and standardizing logging with the logging module.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat-secure-and-debug

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request significantly improves the application's security and observability by introducing API key authentication and standardizing on the logging module. The changes are well-implemented and consistent across the codebase. I have identified a few important issues: a security vulnerability (timing attack) in the API key comparison, a missing check for the API key at startup, and an unprotected WebSocket endpoint that contradicts the goal of securing all endpoints. Details and suggestions are provided in the specific comments.

Comment on lines +47 to +54
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s",
handlers=[
logging.FileHandler(LOG_DIR / "app.log"),
logging.StreamHandler(),
],
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using logging.basicConfig() configures the root logger. This can sometimes cause issues if third-party libraries also try to configure logging, as it's a global configuration. A more robust approach is to create and configure a specific logger for your application, like logger = logging.getLogger("pi_productivity"). This isolates your application's logging configuration and prevents it from affecting or being affected by other libraries. While basicConfig might be acceptable for a simple, standalone application, using a named logger is a better practice for maintainability.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 434 to +477
@app.post("/sense/mode", response_class=JSONResponse)
async def set_sense_mode_endpoint(mode_name: str = Form(...)):
async def set_sense_mode_endpoint(
mode_name: str = Form(...), api_key: str = Depends(get_api_key)
):
new_mode = await run_in_threadpool(sense.set_sense_mode, mode_name)
return JSONResponse({"status": "success", "mode": new_mode})

@app.post("/ocr", response_class=JSONResponse)
async def run_ocr_endpoint():
async def run_ocr_endpoint(api_key: str = Depends(get_api_key)):
try:
img_path, txt_path, text = await run_in_threadpool(sense.run_ocr_once)
return JSONResponse({"status": "success", "image_path": img_path, "text_path": txt_path, "text": text})
except Exception as e:
# 1. (SEGURO) Loga o erro completo no seu terminal/console
# Assim VOCÊ pode ver o que deu errado.
print("--- ERRO NO ENDPOINT DE OCR ---")
traceback.print_exc()
print("-------------------------------")
logging.error("Error in OCR endpoint", exc_info=True)

# 2. (SEGURO) Envia uma mensagem genérica para o usuário
# O usuário/invasor não vê nenhuma informação sensível.
return JSONResponse(
{"status": "error", "message": "Ocorreu um erro interno ao processar a imagem."},
status_code=500
{"status": "error", "message": "Ocorreu um erro interno ao processar a imagem."},
status_code=500,
)
@app.get("/camera.jpg")
async def camera_jpeg():
async def camera_jpeg(api_key: str = Depends(get_api_key)):
frame = await run_in_threadpool(sense.read_jpeg)
data = frame or b'' # Return empty bytes if no frame
data = frame or b'' # Return empty bytes if no frame
return StreamingResponse(io.BytesIO(data), media_type="image/jpeg")

@app.get("/api/week-calendar", response_class=JSONResponse)
async def get_week_calendar():
async def get_week_calendar(api_key: str = Depends(get_api_key)):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Authenticate endpoints without updating browser clients

The new Depends(get_api_key) requirement on /sense/mode, /ocr, /camera.jpg, and /api/week-calendar forces every request to supply an X-API-Key header. None of the existing browser calls add this header (static/app.js posts to /sense/mode and /ocr and fetches /api/week-calendar, and the <img> tag for /camera.jpg cannot send custom headers). After this change the web UI cannot set modes, trigger OCR, load the week calendar, or display the camera feed because every call now returns 401/403. Either keep these endpoints anonymous or update the front-end to attach the key (and provide a workable approach for the camera image which currently cannot be authenticated).

Useful? React with 👍 / 👎.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
main.py (1)

484-492: WebSocket is unauthenticated; add API key check before accept()

/ws currently accepts all clients, bypassing the new API key layer. Enforce auth via header or query param and close with policy violation when invalid. Security-critical.

-@app.websocket("/ws")
-async def websocket_endpoint(ws: WebSocket):
-    await bus.add(ws)
+@app.websocket("/ws")
+async def websocket_endpoint(ws: WebSocket):
+    # Authenticate via header or query param before accepting
+    key = ws.headers.get("x-api-key") or ws.query_params.get("api_key")
+    if not API_KEY or key != API_KEY:
+        # 1008 = Policy Violation
+        await ws.close(code=1008)
+        return
+    await bus.add(ws)
🧹 Nitpick comments (6)
.env.example (1)

7-8: API_KEY formatting + guidance

To satisfy dotenv-linter and reduce friction, drop quotes and add a quick generation tip.

-# A secret key to protect your API endpoints
-API_KEY="your_secret_api_key"
+# A secret key to protect your API endpoints
+# Generate with: python -c "import secrets; print(secrets.token_urlsafe(32))"
+API_KEY=your_secret_api_key
main.py (5)

461-463: Drop unused exception variable

Silences Ruff F841 without changing behavior.

-    except Exception as e:
-        logging.error("Error in OCR endpoint", exc_info=True)
+    except Exception:
+        logging.error("Error in OCR endpoint", exc_info=True)

47-54: Harden logging: rotation, reload-safety, and encoding

Use RotatingFileHandler to prevent unbounded growth and force=True to avoid duplicate handlers under uvicorn reload.

+from logging.handlers import RotatingFileHandler
@@
-logging.basicConfig(
+logging.basicConfig(
     level=logging.INFO,
     format="%(asctime)s - %(levelname)s - %(message)s",
-    handlers=[
-        logging.FileHandler(LOG_DIR / "app.log"),
-        logging.StreamHandler(),
-    ],
-)
+    handlers=[
+        RotatingFileHandler(LOG_DIR / "app.log", maxBytes=10_000_000, backupCount=5, encoding="utf-8"),
+        logging.StreamHandler(),
+    ],
+    force=True,
+)

Also applies to: 9-9


126-137: Optional: TRY300 — move success log into else block

Minor clarity per Ruff TRY300.

-        try:
-            cam = Picamera2()
-            # Reduzir a resolução pode acelerar a captura e o processamento
-            config = cam.create_still_configuration(main={"size": (1024, 576)})
-            cam.configure(config)
-            cam.start()
-            time.sleep(1)  # Allow camera to warm up
-            logging.info("Camera initialized.")
-            return cam
-        except Exception as e:
-            logging.error(f"Error initializing camera: {e}", exc_info=True)
-            return None
+        try:
+            cam = Picamera2()
+            config = cam.create_still_configuration(main={"size": (1024, 576)})
+            cam.configure(config)
+            cam.start()
+            time.sleep(1)  # Allow camera to warm up
+        except Exception as e:
+            logging.error(f"Error initializing camera: {e}", exc_info=True)
+            return None
+        else:
+            logging.info("Camera initialized.")
+            return cam

533-533: Keep a handle to background task and clean up on shutdown

Prevents orphaned tasks (RUF006) and ensures graceful shutdown.

-    asyncio.create_task(broadcast_loop())
+    app.state.broadcast_task = asyncio.create_task(broadcast_loop())

Add a shutdown hook (new code outside the shown hunk):

@app.on_event("shutdown")
async def on_shutdown():
    task = getattr(app.state, "broadcast_task", None)
    if task:
        task.cancel()
        with suppress(asyncio.CancelledError):
            await task

251-255: Convert remaining print statements to logging for consistency across modules

The logging improvements in main.py are good. Verification confirms print statements still exist in: motion_client.py, sense_mode.py, epaper.py, and analyze_productivity.py (16 total). These should be converted to logging to maintain the structured logging pattern established in main.py.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 21cc09b and 1e263ec.

📒 Files selected for processing (2)
  • .env.example (1 hunks)
  • main.py (14 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
main.py (4)
sense_mode.py (4)
  • is_running (133-135)
  • stop (128-131)
  • clear (36-38)
  • start (120-126)
motion_client.py (3)
  • list_all_tasks_simple (106-142)
  • post (45-50)
  • get (32-43)
task_database.py (2)
  • upsert_motion_tasks (71-98)
  • fetch_items_for_display (189-248)
epaper.py (1)
  • render_list (66-91)
🪛 dotenv-linter (4.0.0)
.env.example

[warning] 8-8: [QuoteCharacter] The value has quote characters (', ")

(QuoteCharacter)

🪛 Ruff (0.14.1)
main.py

134-134: Consider moving this statement to an else block

(TRY300)


451-451: Unused function argument: api_key

(ARG001)


457-457: Unused function argument: api_key

(ARG001)


461-461: Local variable e is assigned to but never used

Remove assignment to unused variable e

(F841)


471-471: Unused function argument: api_key

(ARG001)


477-477: Unused function argument: api_key

(ARG001)


533-533: Store a reference to the return value of asyncio.create_task

(RUF006)

⏰ 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). (2)
  • GitHub Check: Codacy Static Code Analysis
  • GitHub Check: Codacy Static Code Analysis
🔇 Additional comments (1)
main.py (1)

441-448: Confirm intent: should the HTML UI (/) also require API key?

If yes, add dependencies=[Depends(get_api_key)] to the route or set a global dependency on the app. Note: browsers can’t send custom headers on initial page load without a proxy or query parameter flow.

OCR_INTERVAL = int(os.getenv("OCR_INTERVAL", "600"))
API_KEY = os.getenv("API_KEY")

api_key_header = APIKeyHeader(name="X-API-Key")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Consistent 401 handling, constant-time compare, and auth header hint

Make missing/wrong key paths consistent and safer: disable auto_error, use constant-time compare, and return WWW-Authenticate for clients.

+import hmac
@@
-api_key_header = APIKeyHeader(name="X-API-Key")
+api_key_header = APIKeyHeader(name="X-API-Key", auto_error=False)
@@
-async def get_api_key(api_key: str = Depends(api_key_header)):
-    if not API_KEY or api_key != API_KEY:
+async def get_api_key(api_key: str = Depends(api_key_header)):
+    if not API_KEY or not api_key or not hmac.compare_digest(api_key, API_KEY):
         raise HTTPException(
             status_code=status.HTTP_401_UNAUTHORIZED,
             detail="Invalid or missing API Key",
+            headers={"WWW-Authenticate": "APIKey"},
         )
     return api_key

Also applies to: 72-79

🤖 Prompt for AI Agents
In main.py around line 69 (and also lines 72-79), the APIKeyHeader is created
and key validation currently allows differing behavior for missing vs invalid
keys and uses a non-constant comparison; change the APIKeyHeader to disable
automatic errors (APIKeyHeader(..., auto_error=False)), then in the dependency
explicitly check if the header is absent or does not match the expected key
using secrets.compare_digest for a constant-time comparison, and for both
missing and wrong keys raise the same HTTPException with status_code=401 and
headers={"WWW-Authenticate":"API-Key"} to provide a consistent 401 response and
authentication hint to clients.

Comment on lines 449 to 453
@app.post("/sense/mode", response_class=JSONResponse)
async def set_sense_mode_endpoint(mode_name: str = Form(...)):
async def set_sense_mode_endpoint(
mode_name: str = Form(...), api_key: str = Depends(get_api_key)
):
new_mode = await run_in_threadpool(sense.set_sense_mode, mode_name)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Remove unused api_key params; use decorator-level dependency

Eliminates ARG001 warnings and centralizes auth per-route.

-@app.post("/sense/mode", response_class=JSONResponse)
-async def set_sense_mode_endpoint(
-    mode_name: str = Form(...), api_key: str = Depends(get_api_key)
-):
+@app.post("/sense/mode", response_class=JSONResponse, dependencies=[Depends(get_api_key)])
+async def set_sense_mode_endpoint(mode_name: str = Form(...)):
@@
-@app.post("/ocr", response_class=JSONResponse)
-async def run_ocr_endpoint(api_key: str = Depends(get_api_key)):
+@app.post("/ocr", response_class=JSONResponse, dependencies=[Depends(get_api_key)])
+async def run_ocr_endpoint():
@@
-@app.get("/camera.jpg")
-async def camera_jpeg(api_key: str = Depends(get_api_key)):
+@app.get("/camera.jpg", dependencies=[Depends(get_api_key)])
+async def camera_jpeg():
@@
-@app.get("/api/week-calendar", response_class=JSONResponse)
-async def get_week_calendar(api_key: str = Depends(get_api_key)):
+@app.get("/api/week-calendar", response_class=JSONResponse, dependencies=[Depends(get_api_key)])
+async def get_week_calendar():

Also applies to: 456-469, 471-475, 477-483

🧰 Tools
🪛 Ruff (0.14.1)

451-451: Unused function argument: api_key

(ARG001)

🤖 Prompt for AI Agents
In main.py around lines 449-453 (and similarly at 456-469, 471-475, 477-483),
the endpoint functions declare an unused api_key parameter via
Depends(get_api_key); remove that unused parameter from the function signatures
and move the dependency into the route decorator (e.g., app.post(...,
dependencies=[Depends(get_api_key)]) or app.get(...,
dependencies=[Depends(get_api_key)]) as appropriate) so authentication is
applied at the decorator level and ARG001 warnings are eliminated.

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
main.py (1)

484-498: Based on my verification of the codebase, the review comment is accurate and confirms a real authentication gap. Here are the key findings:

Verification Results:

  1. WebSocket endpoint is unauthenticated: The /ws endpoint (lines 484-492) has no API key validation or any other authentication mechanism. It accepts all connections.

  2. Build payload includes task data: build_status_payload() (lines 432-438) returns:

    • tasks: from sense.db.fetch_items_for_display(limit=20) — task titles, subtitles, due dates, and statuses
    • Plus mode, sensor readings, and timestamp
  3. API endpoint is protected: The /api/week-calendar endpoint (lines 476-482) requires valid API key authentication via api_key: str = Depends(get_api_key) which validates the X-API-Key header.

  4. Broadcast exposes task data: The broadcast_loop() (lines 494-498) publishes this payload to all connected WebSocket clients every 2 seconds without any authentication checks.

  5. Data is the same source: Both fetch_items_for_display() and fetch_week_calendar() query the same task database with the same filters (non-completed tasks for the current week).


WebSocket /ws remains unauthenticated while streaming protected task data

The /ws endpoint broadcasts build_status_payload() every 2 seconds, which includes task data (titles, subtitles, due dates, statuses) from sense.db.fetch_items_for_display(). This same task data is protected by API key on the /api/week-calendar endpoint. An unauthenticated client can connect to /ws and receive unrestricted access to the same sensitive information.

To align security across endpoints:

  • Add lightweight auth check on the WebSocket handshake (e.g., query param ?api_key=... validated via get_api_key logic), or
  • Explicitly document that /ws is intentionally public and task data is non-sensitive.

Currently this represents a security inconsistency where HTTP endpoints require authentication but WebSocket streaming does not.

♻️ Duplicate comments (3)
main.py (3)

1-9: Fix undefined secrets import and harden get_api_key handling

get_api_key uses secrets.compare_digest but secrets isn’t imported, which will raise a NameError on the first authenticated request. Also, missing headers still go through FastAPI’s default 403 path rather than your custom 401 logic.

I suggest:

  • Add the missing import.
  • Disable APIKeyHeader’s auto_error and allow None in the dependency.
  • Normalize both missing and invalid keys to the same 401 response, and optionally add a WWW-Authenticate hint.
+import secrets
@@
-API_KEY = os.getenv("API_KEY")
-
-api_key_header = APIKeyHeader(name="X-API-Key")
+API_KEY = os.getenv("API_KEY")
+
+api_key_header = APIKeyHeader(name="X-API-Key", auto_error=False)
@@
-async def get_api_key(api_key: str = Depends(api_key_header)):
-    if not API_KEY or not secrets.compare_digest(api_key, API_KEY):
+async def get_api_key(api_key: str | None = Depends(api_key_header)):
+    if not API_KEY or not api_key or not secrets.compare_digest(api_key, API_KEY):
         raise HTTPException(
             status_code=status.HTTP_401_UNAUTHORIZED,
-            detail="Invalid or missing API Key",
+            detail="Invalid or missing API Key",
+            headers={"WWW-Authenticate": "API-Key"},
         )
     return api_key

This resolves F821 and gives you consistent, constant‑time API key validation.

Also applies to: 15-24, 28-28, 67-79


61-68: Consider failing fast when API_KEY is missing

Right now the app happily starts with API_KEY unset, but all protected endpoints will then return 401, which can be confusing in deployment.

Consider explicitly checking for API_KEY during startup and aborting if it’s missing, so misconfiguration is caught immediately:

 @app.on_event("startup")
 async def on_startup():
+    if not API_KEY:
+        raise RuntimeError(
+            "API_KEY environment variable not set. The application cannot start without it."
+        )
     # Instantiate the main application object here so hardware initialization

This aligns the runtime behavior with the “API key required” contract and surfaces config errors early.

Also applies to: 500-520


449-483: Clean up unused api_key params and OCR exception variable; verify client compatibility

Two separate points in this block:

  1. Unused api_key parameters (ARG001) and cleaner auth wiring

All four endpoints declare api_key: str = Depends(get_api_key) but never use the value, which Ruff flags as ARG001. A cleaner pattern is to move the dependency to the decorator and drop the unused parameter entirely:

-@app.post("/sense/mode", response_class=JSONResponse)
-async def set_sense_mode_endpoint(
-    mode_name: str = Form(...), api_key: str = Depends(get_api_key)
-):
+@app.post(
+    "/sense/mode",
+    response_class=JSONResponse,
+    dependencies=[Depends(get_api_key)],
+)
+async def set_sense_mode_endpoint(mode_name: str = Form(...)):
@@
-@app.post("/ocr", response_class=JSONResponse)
-async def run_ocr_endpoint(api_key: str = Depends(get_api_key)):
+@app.post(
+    "/ocr",
+    response_class=JSONResponse,
+    dependencies=[Depends(get_api_key)],
+)
+async def run_ocr_endpoint():
@@
-    except Exception as e:
-        logging.error("Error in OCR endpoint", exc_info=True)
+    except Exception:
+        logging.error("Error in OCR endpoint", exc_info=True)
@@
-@app.get("/camera.jpg")
-async def camera_jpeg(api_key: str = Depends(get_api_key)):
+@app.get("/camera.jpg", dependencies=[Depends(get_api_key)])
+async def camera_jpeg():
@@
-@app.get("/api/week-calendar", response_class=JSONResponse)
-async def get_week_calendar(api_key: str = Depends(get_api_key)):
+@app.get(
+    "/api/week-calendar",
+    response_class=JSONResponse,
+    dependencies=[Depends(get_api_key)],
+)
+async def get_week_calendar():

This removes the lint errors (including the unused e) and centralizes auth per route.

  1. Confirm browser/front‑end clients still work with the new auth

Requiring X-API-Key on /sense/mode, /ocr, /camera.jpg, and /api/week-calendar means:

  • Existing JS calls (e.g., from static/app.js) must now attach the header.
  • The <img src="/camera.jpg"> in the template cannot send custom headers, so the camera stream will 401/403 unless you’ve reworked that flow (e.g., via a proxied endpoint, signed URL, or JS blob URL).

Please double‑check that the front‑end has been updated accordingly and that all UI flows (mode switching, OCR trigger, week calendar, camera image) still function under the new auth model.

🧹 Nitpick comments (2)
main.py (2)

122-137: Optional: use try/except/else for clearer camera init flow (TRY300)

The camera init try block mixes the happy path and error handling, which Ruff flags (TRY300). You can make success vs failure clearer by using else:

     try:
         cam = Picamera2()
         # Reduzir a resolução pode acelerar a captura e o processamento
         config = cam.create_still_configuration(main={"size": (1024, 576)})
         cam.configure(config)
         cam.start()
         time.sleep(1)  # Allow camera to warm up
-        logging.info("Camera initialized.")
-        return cam
-    except Exception as e:
-        logging.error(f"Error initializing camera: {e}", exc_info=True)
-        return None
+    except Exception as e:
+        logging.error("Error initializing camera", exc_info=True)
+        return None
+    else:
+        logging.info("Camera initialized.")
+        return cam

Behavior stays the same, but the control flow is cleaner and linter‑friendly.


533-534: Store the broadcast_loop task reference (RUF006) for observability and lifecycle control

asyncio.create_task(broadcast_loop()) is fire‑and‑forget; Ruff flags this (RUF006), and any exceptions in the task may become “never retrieved” warnings.

You can keep a handle on the task (and optionally cancel it on shutdown) by storing it on the app:

-    asyncio.create_task(broadcast_loop())
+    app.state.broadcast_task = asyncio.create_task(broadcast_loop())

If you later add a shutdown event, you can cancel app.state.broadcast_task cleanly.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1e263ec and 2c3008a.

📒 Files selected for processing (1)
  • main.py (14 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
main.py (3)
sense_mode.py (4)
  • is_running (133-135)
  • stop (128-131)
  • clear (36-38)
  • start (120-126)
motion_client.py (4)
  • list_all_tasks_simple (106-142)
  • post (45-50)
  • get (32-43)
  • create_task (153-159)
task_database.py (2)
  • upsert_motion_tasks (71-98)
  • fetch_items_for_display (189-248)
🪛 GitHub Check: Codacy Static Code Analysis
main.py

[warning] 73-73: main.py#L73
undefined name 'secrets' (F821)

🪛 Ruff (0.14.5)
main.py

73-73: Undefined name secrets

(F821)


134-134: Consider moving this statement to an else block

(TRY300)


451-451: Unused function argument: api_key

(ARG001)


457-457: Unused function argument: api_key

(ARG001)


461-461: Local variable e is assigned to but never used

Remove assignment to unused variable e

(F841)


471-471: Unused function argument: api_key

(ARG001)


477-477: Unused function argument: api_key

(ARG001)


533-533: Store a reference to the return value of asyncio.create_task

(RUF006)

⏰ 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). (1)
  • GitHub Check: Codacy Static Code Analysis
🔇 Additional comments (1)
main.py (1)

165-167: Logging refactor looks solid

The move from print/traceback to structured logging with exc_info=True, centralized handlers, and contextual messages across motion sync, joystick actions, OCR errors, startup, and hardware loops is consistent and appropriate for debugging on-device behavior. No functional issues spotted here.

Also applies to: 229-230, 251-263, 268-268, 317-320, 337-355, 358-361, 374-379, 462-463, 467-468, 510-512, 523-528, 535-537

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant