more api docs and tweaks for auth and updates API
This commit is contained in:
parent
64088759f5
commit
87ef037421
4 changed files with 89 additions and 26 deletions
|
@ -15,12 +15,16 @@ class AuthStatusSchema(Schema):
|
||||||
"""
|
"""
|
||||||
key_type: APIKeyType = APIField(
|
key_type: APIKeyType = APIField(
|
||||||
title="api key type",
|
title="api key type",
|
||||||
|
description="the type of api KEY THAT IS BEING USED"
|
||||||
)
|
)
|
||||||
readonly: bool = APIField(
|
readonly: bool = APIField(
|
||||||
title="read only",
|
title="read only",
|
||||||
description="if true, no API operations that modify data can be called"
|
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",
|
@auth_api_router.get('/status/', summary="get status",
|
||||||
|
@ -40,14 +44,19 @@ def get_status(request):
|
||||||
|
|
||||||
|
|
||||||
class APITokenSchema(Schema):
|
class APITokenSchema(Schema):
|
||||||
"""
|
token: NonEmptyStr = APIField(
|
||||||
An API token to be used with Bearer authentication
|
title="API token",
|
||||||
"""
|
description="API token to be directly used with `Authorization: Bearer <token>` HTTP header."
|
||||||
token: NonEmptyStr
|
)
|
||||||
|
|
||||||
|
|
||||||
@auth_api_router.get('/session/', response=APITokenSchema, auth=None,
|
@auth_api_router.get('/session/', response=APITokenSchema, auth=None,
|
||||||
summary="get session-bound token")
|
summary="get session-bound token")
|
||||||
def session_token(request):
|
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)
|
session_id = request.COOKIES.get(settings.SESSION_COOKIE_NAME, None)
|
||||||
return {"token": "anonymous" if session_id is None else f"session:{session_id}"}
|
return {"token": "anonymous" if session_id is None else f"session:{session_id}"}
|
||||||
|
|
|
@ -50,10 +50,16 @@ EditorGeometriesCacheReferenceElem = Annotated[
|
||||||
class EditorGeometriesPropertiesSchema(Schema):
|
class EditorGeometriesPropertiesSchema(Schema):
|
||||||
id: EditorID
|
id: EditorID
|
||||||
type: NonEmptyStr
|
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
|
level: Optional[EditorID] = None
|
||||||
bounds: bool = False
|
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
|
opacity: Optional[float] = None # todo: range
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,14 @@
|
||||||
from typing import Optional
|
from typing import Optional, Union, Annotated
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
from django.http import HttpResponse
|
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 ninja import Schema
|
||||||
from pydantic import PositiveInt
|
from pydantic import PositiveInt
|
||||||
|
|
||||||
from c3nav.api.auth import auth_responses
|
from c3nav.api.auth import auth_responses
|
||||||
from c3nav.api.utils import NonEmptyStr
|
from c3nav.api.utils import NonEmptyStr
|
||||||
from c3nav.mapdata.api.base import api_etag
|
|
||||||
from c3nav.mapdata.models import MapUpdate
|
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.cache.stats import increment_cache_key
|
||||||
from c3nav.mapdata.utils.user import get_user_data
|
from c3nav.mapdata.utils.user import get_user_data
|
||||||
from c3nav.mapdata.views import set_tile_access_cookie
|
from c3nav.mapdata.views import set_tile_access_cookie
|
||||||
|
@ -19,28 +17,78 @@ updates_api_router = APIRouter(tags=["updates"])
|
||||||
|
|
||||||
|
|
||||||
class UserDataSchema(Schema):
|
class UserDataSchema(Schema):
|
||||||
logged_in: bool
|
logged_in: bool = APIField(
|
||||||
allow_editor: bool
|
title="logged in",
|
||||||
allow_control_panel: bool
|
description="whether a user is logged in",
|
||||||
has_positions: bool
|
)
|
||||||
title: NonEmptyStr
|
allow_editor: bool = APIField(
|
||||||
subtitle: NonEmptyStr
|
title="editor access",
|
||||||
permissions: list[PositiveInt]
|
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):
|
class FetchUpdatesResponseSchema(Schema):
|
||||||
last_site_update: PositiveInt
|
last_site_update: PositiveInt = APIField(
|
||||||
last_map_update: NonEmptyStr
|
title="ID of the last site update",
|
||||||
user: Optional[UserDataSchema] = None
|
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",
|
@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})
|
response={200: FetchUpdatesResponseSchema, **auth_responses})
|
||||||
def fetch_updates(request, response: HttpResponse):
|
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')
|
cross_origin = request.META.get('HTTP_ORIGIN')
|
||||||
if cross_origin is not None:
|
if cross_origin is not None:
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -1689,7 +1689,7 @@ c3nav = {
|
||||||
c3nav.last_site_update = data.last_site_update;
|
c3nav.last_site_update = data.last_site_update;
|
||||||
c3nav._maybe_load_site_update(c3nav.state);
|
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) {
|
_maybe_load_site_update: function(state) {
|
||||||
if (c3nav.new_site_update && !state.modal && (!state.routing || !state.origin || !state.destination)) {
|
if (c3nav.new_site_update && !state.modal && (!state.routing || !state.origin || !state.destination)) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue