Skip to content
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

fix: Device routes #21

Merged
merged 4 commits into from
Apr 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion services/api/carlos/api/app_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@

from .config import CarlosAPISettings
from .logging_patch import setup_logging
from .routes import main_router, public_router

DOCS_URL = "/docs"
OPENAPI_URL = "/openapi.json"
Expand All @@ -19,6 +18,8 @@
def create_app(api_settings: CarlosAPISettings | None = None) -> FastAPI:
"""Creates and configures the FastAPI app."""

from .routes import main_router, public_router

api_settings = api_settings or CarlosAPISettings()

# ensures that the logging is handled via loguru
Expand Down
25 changes: 15 additions & 10 deletions services/api/carlos/api/depends/authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
https://auth0.com/blog/build-and-secure-fastapi-server-with-auth0/
"""

__all__ = ["VerifyToken", "cached_token_verify_from_env"]
__all__ = ["VerifyToken", "verify_token"]

import os
import secrets
Expand Down Expand Up @@ -104,10 +104,10 @@ def __init__(self, config: Auth0Settings):
jwks_url = f"https://{self.config.domain}/.well-known/jwks.json"
self.jwks_client = jwt.PyJWKClient(jwks_url)

async def verify(
def verify(
self,
security_scopes: SecurityScopes,
token: HTTPAuthorizationCredentials = Depends(HTTPBearer(auto_error=False)),
token: HTTPAuthorizationCredentials,
) -> dict: # pragma: no cover
"""Verifies the token and returns the payload if it is valid."""

Expand Down Expand Up @@ -146,11 +146,16 @@ async def verify(
return payload


@lru_cache()
def cached_token_verify_from_env():
"""A cached dependency to construct the VerifyToken object.
def verify_token(
security_scopes: SecurityScopes,
token: HTTPAuthorizationCredentials = Depends(HTTPBearer(auto_error=False)),
) -> dict:
"""Can be used as a FastAPI dependency to verify the token."""

return _cached_verify_token().verify(security_scopes=security_scopes, token=token)

This is useful to avoid reading the environment variables too often.
It further prevents that the environment variables are read during import.
"""
return VerifyToken(config=Auth0Settings()).verify

@lru_cache()
def _cached_verify_token() -> VerifyToken:
"""Reads the Auth0 settings from the environment."""
return VerifyToken(config=Auth0Settings())
4 changes: 2 additions & 2 deletions services/api/carlos/api/routes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

from fastapi import APIRouter, Security

from carlos.api.depends.authentication import cached_token_verify_from_env
from carlos.api.depends.authentication import verify_token

from .device_server_routes import device_server_router
from .devices_routes import devices_router
from .health_routes import health_router

main_router = APIRouter(dependencies=[Security(cached_token_verify_from_env())])
main_router = APIRouter(dependencies=[Security(verify_token)])
"""This is the main router for the API. It is for routes that require authentication."""
main_router.include_router(devices_router, prefix="/devices", tags=["devices"])

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from pydantic.alias_generators import to_camel
from starlette.responses import PlainTextResponse

from carlos.api.depends.authentication import cached_token_verify_from_env
from carlos.api.depends.authentication import verify_token as auth_verify_token

from .protocol import WebsocketProtocol
from .state import DEVICE_CONNECTION_MANAGER
Expand Down Expand Up @@ -45,7 +45,7 @@ def extract_client_hostname(connection: Request | WebSocket) -> str:
@device_server_router.get(
get_websocket_token_endpoint(device_id_param),
summary="Get a token to be used to connect to the websocket.",
dependencies=[Security(cached_token_verify_from_env)],
dependencies=[Security(auth_verify_token)],
response_model=str,
response_class=PlainTextResponse,
tags=["devices"],
Expand Down
3 changes: 2 additions & 1 deletion services/api/carlos/api/routes/devices_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from carlos.database.context import RequestContext
from carlos.database.device.device_management import (
CarlosDevice,
CarlosDeviceCreate,
CarlosDeviceUpdate,
create_device,
get_device,
Expand All @@ -28,7 +29,7 @@ async def list_devices_route(

@devices_router.post("", summary="Register a new device", response_model=CarlosDevice)
async def register_device_route(
device: CarlosDevice,
device: CarlosDeviceCreate,
context: RequestContext = Depends(request_context),
): # pragma: no cover
"""Register a new device."""
Expand Down
15 changes: 9 additions & 6 deletions services/api/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,11 @@
from devtools.context_manager import EnvironmentContext
from starlette.testclient import TestClient

from carlos.api.app_factory import create_app
from carlos.api.depends.authentication import TESTING_TOKEN, Auth0Region


@pytest.fixture(scope="session", autouse=True)
def api_test_environment(carlos_db_test_environment):

env = {
def build_environment() -> dict[str, str]:
return {
"API_CORS_ORIGINS": '["http://127.0.0.1:8000"]',
# For development, we want to enable the API docs
"API_DOCS_ENABLED": "1",
Expand All @@ -23,14 +20,20 @@ def api_test_environment(carlos_db_test_environment):
"ENVIRONMENT": "pytest",
}

with EnvironmentContext(environment_variables=env, echo=None):

@pytest.fixture(scope="session", autouse=True)
def api_test_environment(carlos_db_test_environment):

with EnvironmentContext(environment_variables=build_environment(), echo=None):
yield


@pytest.fixture(scope="session")
def client() -> TestClient:
"""Returns a test client that can be used to make requests to the API."""

from carlos.api.app_factory import create_app

return TestClient(
create_app(), headers={"Authorization": f"Bearer {TESTING_TOKEN}"}
)
Loading
Loading