more api docs and tweaks for auth and updates API

This commit is contained in:
Laura Klünder 2023-12-04 13:46:32 +01:00
parent 64088759f5
commit 87ef037421
4 changed files with 89 additions and 26 deletions

View file

@ -15,12 +15,16 @@ class AuthStatusSchema(Schema):
"""
key_type: APIKeyType = APIField(
title="api key type",
description="the type of api KEY THAT IS BEING USED"
)
readonly: bool = APIField(
title="read only",
description="if true, no API operations that modify data can be called"
)
scopes: list[str]
scopes: list[str] = APIField(
title="authorized scopes",
description="scopes available with the current authorization",
)
@auth_api_router.get('/status/', summary="get status",
@ -40,14 +44,19 @@ def get_status(request):
class APITokenSchema(Schema):
"""
An API token to be used with Bearer authentication
"""
token: NonEmptyStr
token: NonEmptyStr = APIField(
title="API token",
description="API token to be directly used with `Authorization: Bearer <token>` HTTP header."
)
@auth_api_router.get('/session/', response=APITokenSchema, auth=None,
summary="get session-bound token")
def session_token(request):
"""
Get an API token that is bound to the transmitted session cookie.
Keep in mind that this API token will be invalid if the session gets signed out or similar.
"""
session_id = request.COOKIES.get(settings.SESSION_COOKIE_NAME, None)
return {"token": "anonymous" if session_id is None else f"session:{session_id}"}

View file

@ -50,10 +50,16 @@ EditorGeometriesCacheReferenceElem = Annotated[
class EditorGeometriesPropertiesSchema(Schema):
id: EditorID
type: NonEmptyStr
space: Optional[EditorID] = None
space: Union[
Annotated[EditorID, APIField(title="level")],
Annotated[None, APIField(title="null")]
] = APIField(None, title="lolala")
level: Optional[EditorID] = None
bounds: bool = False
color: Optional[str] = None
color: Union[
Annotated[str, APIField(title="color")],
Annotated[None, APIField(title="no color")]
] = None
opacity: Optional[float] = None # todo: range

View file

@ -1,16 +1,14 @@
from typing import Optional
from typing import Optional, Union, Annotated
from urllib.parse import urlparse
from django.http import HttpResponse
from ninja import Router as APIRouter
from ninja import Router as APIRouter, Field as APIField
from ninja import Schema
from pydantic import PositiveInt
from c3nav.api.auth import auth_responses
from c3nav.api.utils import NonEmptyStr
from c3nav.mapdata.api.base import api_etag
from c3nav.mapdata.models import MapUpdate
from c3nav.mapdata.schemas.responses import BoundsSchema
from c3nav.mapdata.utils.cache.stats import increment_cache_key
from c3nav.mapdata.utils.user import get_user_data
from c3nav.mapdata.views import set_tile_access_cookie
@ -19,28 +17,78 @@ updates_api_router = APIRouter(tags=["updates"])
class UserDataSchema(Schema):
logged_in: bool
allow_editor: bool
allow_control_panel: bool
has_positions: bool
title: NonEmptyStr
subtitle: NonEmptyStr
permissions: list[PositiveInt]
logged_in: bool = APIField(
title="logged in",
description="whether a user is logged in",
)
allow_editor: bool = APIField(
title="editor access",
description="whether the user signed in can access the editor (or accessing the editor is possible as guest)."
"this does not mean that the current API authorization allows accessing the editor API.",
)
allow_control_panel: bool = APIField(
title="control panel access",
description="whether the user signed in can access the control panel.",
)
has_positions: bool = APIField(
title="user has positions",
description="whether the user signed in has created any positions",
)
title: NonEmptyStr = APIField(
title="user data title",
description="data to show in the top right corner. can be the user name or `Login` or similar",
example="ada_lovelace",
)
subtitle: NonEmptyStr = APIField(
title="user data subtitle",
description="a description of the current user data state to display below the user data title",
example="3 areas unlocked",
)
permissions: list[PositiveInt] = APIField(
title="access permissions",
description="IDs of access restrictions that this user (even if maybe not signed in) has access to",
example=[2, 5],
)
class FetchUpdatesResponseSchema(Schema):
last_site_update: PositiveInt
last_map_update: NonEmptyStr
user: Optional[UserDataSchema] = None
last_site_update: PositiveInt = APIField(
title="ID of the last site update",
description="If this ID changes, it means a major code change may have occured. "
"A reload of all data is recommended.",
example=1,
)
last_map_update: NonEmptyStr = APIField(
title="string identifier of the last map update",
description="Map updates are incremental, not every map update will change all data. API endpoitns will be "
"aware of this. Use `E-Tag` and `If-None-Match` on API endpoints to query if the data has changed.",
)
user_data: Union[
Annotated[UserDataSchema, APIField(
title="user data",
description="always supplied, unless it is a cross-origin request",
)],
Annotated[None, APIField(
title="null",
description="only for cross-origin requests",
)],
] = APIField(None,
title="user data",
description="user data of this request. ommited for cross-origin requests.",
)
@updates_api_router.get('/fetch/', summary="fetch updates",
description="get regular updates.\n\n"
"this endpoint also sets/updates the tile access cookie."
"if not called regularly, the tileserver will ignore your access permissions.\n\n"
"this endpoint can be called cross-origin, but it will have no user data then.",
response={200: FetchUpdatesResponseSchema, **auth_responses})
def fetch_updates(request, response: HttpResponse):
"""
Get regular updates.
This endpoint also sets/updates the tile access cookie.
If not called regularly, the tileserver will ignore your access permissions.
This endpoint can be called cross-origin, but it will have no user data then.
"""
cross_origin = request.META.get('HTTP_ORIGIN')
if cross_origin is not None:
try:

View file

@ -1689,7 +1689,7 @@ c3nav = {
c3nav.last_site_update = data.last_site_update;
c3nav._maybe_load_site_update(c3nav.state);
}
c3nav._set_user_data(data.user);
c3nav._set_user_data(data.user_data);
},
_maybe_load_site_update: function(state) {
if (c3nav.new_site_update && !state.modal && (!state.routing || !state.origin || !state.destination)) {