Add verification endpoint for client.

Also fix http code for missing auth.
This commit is contained in:
sfigato 2019-08-17 22:23:32 -03:00 committed by blallo
parent 7d8a6c4075
commit 0fd0cde11c
Signed by: blallo
GPG Key ID: 0CBE577C9B72DC3F
2 changed files with 41 additions and 6 deletions

View File

@ -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)

View File

@ -53,6 +53,8 @@ requirements = [
"aiohttp-session==2.7.0",
"cryptography==2.7",
"PyYAML==5.1.2",
"passlib>=1.7.1, <2.0.0",
"bcrypt==0.1.1",
]
setup_requirements = [] # type: T.List[str]