Add verification endpoint for client.
Also fix http code for missing auth.
This commit is contained in:
parent
7d8a6c4075
commit
0fd0cde11c
45
api/rest.py
45
api/rest.py
|
@ -5,6 +5,8 @@ The REST endpoints.
|
|||
"""
|
||||
|
||||
from aiohttp import web
|
||||
import asyncio
|
||||
from concurrent.futures import ProcessPoolExecutor
|
||||
import datetime
|
||||
import logging
|
||||
import os
|
||||
|
@ -12,8 +14,9 @@ import pkg_resources
|
|||
import typing as T
|
||||
|
||||
from aiohttp_session import get_session, Session
|
||||
from passlib.hash import bcrypt
|
||||
|
||||
from bot_z.async_operator import AsyncOperator
|
||||
from bot_z.async_operator import AsyncOperator, push_to_loop
|
||||
from api.async_bot import login, logout, checkin, checkout, status
|
||||
from api import BASE_URI, DEBUG
|
||||
|
||||
|
@ -22,6 +25,28 @@ alog = logging.getLogger("api")
|
|||
routes = web.RouteTableDef()
|
||||
OPERATORS = {} # type: T.Dict[T.Text, AsyncOperator]
|
||||
BASE_PATH = pkg_resources.resource_filename(__name__, "assets")
|
||||
EXECUTOR = ProcessPoolExecutor()
|
||||
# WARN: the default il 12 rounds; both the server and the client shall compute
|
||||
# this hash. The target client is a smartphone with poor performance on
|
||||
# crypto computations, so we should keep this low, as the verification step
|
||||
# is optionally enforced by the client.
|
||||
ROUNDS = 6
|
||||
|
||||
|
||||
def _reckon_token_response(base_uri: T.Text) -> T.Text:
|
||||
return bcrypt.using(rounds=ROUNDS, truncate_error=True).hash(base_uri)
|
||||
|
||||
|
||||
async def reckon_token_response(
|
||||
base_uri: T.Text, loop: asyncio.AbstractEventLoop
|
||||
) -> T.Text:
|
||||
"""
|
||||
A client and the server should agree if the pairing is adequate.
|
||||
This could be accomplished calculating on both sides an cryptographic
|
||||
secret. The current implementation uses bcrypt to compute the hash on
|
||||
server side and the client should verify the secret on its side.
|
||||
"""
|
||||
return await push_to_loop(loop, EXECUTOR, _reckon_token_response, base_uri)
|
||||
|
||||
|
||||
async def get_set_operator(
|
||||
|
@ -75,6 +100,14 @@ async def routing_handler(request: web.Request) -> web.Response:
|
|||
return web.json_response({"logged_in": _logged_in})
|
||||
|
||||
|
||||
@routes.get("/api/ping")
|
||||
async def ping_handler(request: web.Request) -> web.Response:
|
||||
alog.debug("pinged on %s", request.path)
|
||||
resp_data = await reckon_token_response(request.app["base_uri"], request.app.loop)
|
||||
alog.debug("ping response: %s", resp_data)
|
||||
return web.json_response({"hash": resp_data})
|
||||
|
||||
|
||||
@routes.post("/api/login")
|
||||
async def login_handler(request: web.Request) -> web.Response:
|
||||
data = await request.json()
|
||||
|
@ -82,7 +115,7 @@ async def login_handler(request: web.Request) -> web.Response:
|
|||
password = data.get("password")
|
||||
if not user or not password:
|
||||
alog.debug("login - missing username or password: %s", data)
|
||||
return web.json_response({"error": "Missing username or password"}, status=403)
|
||||
return web.json_response({"error": "Missing username or password"}, status=401)
|
||||
op, session = await get_set_operator(request, user, password)
|
||||
alog.debug("login - user: %s, password: %s", user, password)
|
||||
res = await login(op, user, password)
|
||||
|
@ -101,7 +134,7 @@ async def logout_handler(request: web.Request) -> web.Response:
|
|||
op = OPERATORS.get(op_key)
|
||||
if not op:
|
||||
return web.json_response(
|
||||
{"error": "No session", "logged_in": False}, status=403
|
||||
{"error": "No session", "logged_in": False}, status=401
|
||||
)
|
||||
res = await logout(op)
|
||||
alog.debug("logout result: %s", res)
|
||||
|
@ -117,7 +150,7 @@ async def checkin_handler(request: web.Request) -> web.Response:
|
|||
op = OPERATORS.get(session.get("async_operator"))
|
||||
if not op:
|
||||
return web.json_response(
|
||||
{"error": "No session", "logged_in": False}, status=403
|
||||
{"error": "No session", "logged_in": False}, status=401
|
||||
)
|
||||
res = await checkin(op)
|
||||
alog.debug("checkin result: %s", res)
|
||||
|
@ -131,7 +164,7 @@ async def checkout_handler(request: web.Request) -> web.Response:
|
|||
op = OPERATORS.get(session.get("async_operator"))
|
||||
if not op:
|
||||
return web.json_response(
|
||||
{"error": "No session", "logged_in": False}, status=403
|
||||
{"error": "No session", "logged_in": False}, status=401
|
||||
)
|
||||
res = await checkout(op)
|
||||
alog.debug("checkout result: %s", res)
|
||||
|
@ -145,7 +178,7 @@ async def movements_handle(request: web.Request) -> web.Response:
|
|||
op = OPERATORS.get(session.get("async_operator"))
|
||||
if not op:
|
||||
return web.json_response(
|
||||
{"error": "No session", "logged_in": False}, status=403
|
||||
{"error": "No session", "logged_in": False}, status=401
|
||||
)
|
||||
res = await status(op)
|
||||
alog.debug("movements result: %s", res)
|
||||
|
|
Loading…
Reference in New Issue
Block a user