Skip to content
This repository has been archived by the owner on Dec 31, 2024. It is now read-only.

Commit

Permalink
Add API key support and authentication (#85)
Browse files Browse the repository at this point in the history
  • Loading branch information
ReinderVosDeWael authored Feb 19, 2024
1 parent bcee2e3 commit 6f25562
Show file tree
Hide file tree
Showing 6 changed files with 79 additions and 5 deletions.
1 change: 1 addition & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ env:
LWAPI_OPENAI_API_KEY: fake_key
LWAPI_S3_ACCESS_KEY: fake_key
LWAPI_S3_SECRET_KEY: fake_key
LWAPI_API_KEY: test

jobs:
tests:
Expand Down
5 changes: 5 additions & 0 deletions src/linguaweb_api/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@ class Settings(pydantic_settings.BaseSettings): # type: ignore[valid-type, misc
json_schema_extra={"env": "LOGGER_VERBOSITY"},
)

API_KEY: pydantic.SecretStr = pydantic.Field(
...,
json_schema_extra={"env": "API_KEY"},
)

ENVIRONMENT: str = pydantic.Field(
"development",
json_schema_extra={"env": "ENVIRONMENT"},
Expand Down
30 changes: 30 additions & 0 deletions src/linguaweb_api/core/security.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"""Security module for the CTK API."""
import fastapi
from fastapi import security, status

from linguaweb_api.core import config

settings = config.get_settings()
API_KEY = settings.API_KEY


def check_api_key(
api_key_header: str = fastapi.Security(security.APIKeyHeader(name="x-api-key")),
) -> None:
"""Checks the validity of the API key provided in the API key header.
At present, only a single API key is supported and is stored in the environment.
Args:
api_key_header: The API key provided in the API key header.
Raises:
fastapi.HTTPException: 401 If the API key is invalid or missing.
"""
if api_key_header == API_KEY.get_secret_value():
return
raise fastapi.HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid or missing API Key.",
)
8 changes: 6 additions & 2 deletions src/linguaweb_api/routers/admin/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from fastapi import status
from sqlalchemy import orm

from linguaweb_api.core import config, models
from linguaweb_api.core import config, models, security
from linguaweb_api.microservices import s3, sql
from linguaweb_api.routers.admin import controller, schemas

Expand All @@ -14,7 +14,11 @@

logger = logging.getLogger(LOGGER_NAME)

router = fastapi.APIRouter(prefix="/admin", tags=["admin"])
router = fastapi.APIRouter(
prefix="/admin",
tags=["admin"],
dependencies=[fastapi.Depends(security.check_api_key)],
)


@router.post(
Expand Down
1 change: 1 addition & 0 deletions tests/.test.env
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ LWAPI_OPENAI_API_KEY=fake_key
LWAPI_S3_ACCESS_KEY=fake_key
LWAPI_S3_SECRET_KEY=fake_key
LWAPI_SQLITE_FILE=tests/test.sqlite
LWAPI_API_KEY=test
39 changes: 36 additions & 3 deletions tests/endpoint/test_admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,26 @@ def _mock_services(mocker: pytest_mock.MockerFixture) -> Generator[None, None, N
yield


def test_add_word_no_auth(
client: testclient.TestClient,
endpoints: conftest.Endpoints,
) -> None:
"""Tests the add word endpoint without authentication."""
response = client.post(endpoints.POST_ADD_WORD, data={"word": "test_word"})

assert response.status_code == status.HTTP_403_FORBIDDEN


def test_add_word(
client: testclient.TestClient,
endpoints: conftest.Endpoints,
) -> None:
"""Tests the add word endpoint."""
response = client.post(endpoints.POST_ADD_WORD, data={"word": "test_word"})
response = client.post(
endpoints.POST_ADD_WORD,
data={"word": "test_word"},
headers={"x-api-key": "test"},
)
expected_keys = {
"id",
"synonyms",
Expand All @@ -62,7 +76,11 @@ def test_add_word_already_exists(
endpoints: conftest.Endpoints,
) -> None:
"""Tests the add word endpoint when the word already exists."""
client.post(endpoints.POST_ADD_WORD, data={"word": "test_word"})
client.post(
endpoints.POST_ADD_WORD,
data={"word": "test_word"},
headers={"x-api-key": "test"},
)
expected_keys = {
"id",
"synonyms",
Expand All @@ -73,12 +91,26 @@ def test_add_word_already_exists(
"language",
}

response = client.post(endpoints.POST_ADD_WORD, data={"word": "test_word"})
response = client.post(
endpoints.POST_ADD_WORD,
data={"word": "test_word"},
headers={"x-api-key": "test"},
)

assert response.status_code == status.HTTP_201_CREATED
assert set(response.json().keys()) == expected_keys


def test_add_preset_words_no_auth(
client: testclient.TestClient,
endpoints: conftest.Endpoints,
) -> None:
"""Tests the add preset words endpoint without authentication."""
response = client.post(endpoints.POST_ADD_PRESET_WORDS)

assert response.status_code == status.HTTP_403_FORBIDDEN


def test_add_preset_words(
client: testclient.TestClient,
endpoints: conftest.Endpoints,
Expand All @@ -99,6 +131,7 @@ def test_add_preset_words(
response = client.post(
endpoints.POST_ADD_PRESET_WORDS,
data={"max_words": str(max_words)},
headers={"x-api-key": "test"},
)

assert response.status_code == status.HTTP_201_CREATED
Expand Down

0 comments on commit 6f25562

Please sign in to comment.