User and token endpoints with updated middleware

pull/9/head
kompotkot 2021-07-21 14:30:29 +00:00
rodzic e547b2fb2f
commit 4dd8b4c79b
9 zmienionych plików z 208 dodań i 33 usunięć

Wyświetl plik

@ -0,0 +1,2 @@

Wyświetl plik

@ -2,43 +2,21 @@
The Moonstream HTTP API
"""
import logging
from typing import Any, Dict, List, Optional
import uuid
from fastapi import (
BackgroundTasks,
Depends,
FastAPI,
Form,
HTTPException,
Path,
Query,
Request,
Response,
)
from bugout.data import BugoutUser
from fastapi import FastAPI, Form
from fastapi.middleware.cors import CORSMiddleware
from fastapi.security import OAuth2PasswordRequestForm
from . import data
from .settings import DOCS_TARGET_PATH, ORIGINS
from .routes.users import app as users_api
from .settings import ORIGINS, bugout_client as bc, MOONSTREAM_APPLICATION_ID
from .version import MOONSTREAM_VERSION
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
tags_metadata = [{"name": "users", "description": "Operations with users."}]
app = FastAPI(openapi_url=None)
app = FastAPI(
title=f"Moonstream API.",
description="The Bugout blockchain inspector API.",
version=MOONSTREAM_VERSION,
openapi_tags=tags_metadata,
openapi_url="/openapi.json",
docs_url=None,
redoc_url=f"/{DOCS_TARGET_PATH}",
)
# CORS settings
app.add_middleware(
CORSMiddleware,
allow_origins=ORIGINS,
@ -49,10 +27,13 @@ app.add_middleware(
@app.get("/ping", response_model=data.PingResponse)
async def ping() -> data.PingResponse:
async def ping_handler() -> data.PingResponse:
return data.PingResponse(status="ok")
@app.get("/version", response_model=data.VersionResponse)
async def version() -> data.VersionResponse:
async def version_handler() -> data.VersionResponse:
return data.VersionResponse(version=MOONSTREAM_VERSION)
app.mount("/users", users_api)

Wyświetl plik

@ -0,0 +1,63 @@
import logging
from typing import Awaitable, Callable, Dict, List, Optional
from bugout.data import BugoutUser
from bugout.exceptions import BugoutResponseException
from starlette.middleware.base import BaseHTTPMiddleware
from fastapi import Request, Response
from .settings import bugout_client as bc
logger = logging.getLogger(__name__)
class BroodAuthMiddleware(BaseHTTPMiddleware):
"""
Checks the authorization header on the request. If it represents a verified Brood user,
create another request and get groups user belongs to, after this
adds a brood_user attribute to the request.state. Otherwise raises a 403 error.
"""
def __init__(self, app, whitelist: Optional[Dict[str, str]] = None):
self.whitelist: Dict[str, str] = {}
if whitelist is not None:
self.whitelist = whitelist
super().__init__(app)
async def dispatch(
self, request: Request, call_next: Callable[[Request], Awaitable[Response]]
):
path = request.url.path.rstrip("/")
method = request.method
if path in self.whitelist.keys() and self.whitelist[path] == method:
return await call_next(request)
authorization_header = request.headers.get("authorization")
if authorization_header is None:
return Response(
status_code=403, content="No authorization header passed with request"
)
user_token_list = authorization_header.split()
if len(user_token_list) != 2:
return Response(status_code=403, content="Wrong authorization header")
user_token: str = user_token_list[-1]
try:
user: BugoutUser = bc.get_user(user_token)
if not user.verified:
logger.info(
f"Attempted journal access by unverified Brood account: {user.id}"
)
return Response(
status_code=403,
content="Only verified accounts can access journals",
)
except BugoutResponseException as e:
return Response(status_code=e.status_code, content=e.detail)
except Exception as e:
logger.error(f"Error processing Brood response: {str(e)}")
return Response(status_code=500, content="Internal server error")
request.state.user = user
request.state.token = user_token
return await call_next(request)

Wyświetl plik

@ -0,0 +1,108 @@
"""
The Moonstream users HTTP API
"""
import logging
from typing import Any, Dict
import uuid
from bugout.data import BugoutToken, BugoutUser
from bugout.exceptions import BugoutResponseException
from fastapi import (
FastAPI,
Form,
HTTPException,
Request,
)
from fastapi.middleware.cors import CORSMiddleware
from ..middleware import BroodAuthMiddleware
from ..settings import (
MOONSTREAM_APPLICATION_ID,
DOCS_TARGET_PATH,
ORIGINS,
DOCS_PATHS,
bugout_client as bc,
)
from ..version import MOONSTREAM_VERSION
logger = logging.getLogger(__name__)
tags_metadata = [
{"name": "users", "description": "Operations with users."},
{"name": "tokens", "description": "Operations with user tokens."},
]
app = FastAPI(
title=f"Moonstream API.",
description="The Bugout blockchain inspector API.",
version=MOONSTREAM_VERSION,
openapi_tags=tags_metadata,
openapi_url="/openapi.json",
docs_url=None,
redoc_url=f"/{DOCS_TARGET_PATH}",
)
app.add_middleware(
CORSMiddleware,
allow_origins=ORIGINS,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
whitelist_paths: Dict[str, str] = {}
whitelist_paths.update(DOCS_PATHS)
whitelist_paths.update({"/users": "POST", "/users/tokens": "POST"})
app.add_middleware(BroodAuthMiddleware, whitelist=whitelist_paths)
@app.post("/", tags=["users"], response_model=BugoutUser)
async def create_user_handler(
username: str = Form(...), email: str = Form(...), password: str = Form(...)
) -> BugoutUser:
try:
user: BugoutUser = bc.create_user(
username, email, password, MOONSTREAM_APPLICATION_ID
)
except BugoutResponseException as e:
return HTTPException(status_code=e.status_code, detail=e.detail)
except Exception as e:
return HTTPException(status_code=500)
return user
@app.get("/", tags=["users"], response_model=BugoutUser)
async def get_user_handler(request: Request) -> BugoutUser:
user: BugoutUser = request.state.user
if str(user.application_id) != str(MOONSTREAM_APPLICATION_ID):
raise HTTPException(
status_code=403, detail="User does not belong to this application"
)
return user
@app.post("/tokens", tags=["tokens"], response_model=BugoutToken)
async def login_handler(
username: str = Form(...), password: str = Form(...)
) -> BugoutToken:
try:
token: BugoutToken = bc.create_token(
username, password, MOONSTREAM_APPLICATION_ID
)
except BugoutResponseException as e:
return HTTPException(status_code=e.status_code)
except Exception as e:
return HTTPException(status_code=500)
return token
@app.delete("/tokens", tags=["tokens"], response_model=uuid.UUID)
async def logout_handler(request: Request) -> uuid.UUID:
token = request.state.token
try:
token_id: uuid.UUID = bc.revoke_token(token)
except BugoutResponseException as e:
return HTTPException(status_code=e.status_code, detail=e.detail)
except Exception as e:
return HTTPException(status_code=500)
return token_id

Wyświetl plik

@ -1,10 +1,24 @@
import os
from bugout.app import Bugout
# Bugout
# TODO(kompotkot): CHANGE TO PROD!!!!!!!
bugout_client = Bugout("http://127.0.0.1:7474", "http://127.0.0.1:7475")
MOONSTREAM_APPLICATION_ID = os.environ.get("MOONSTREAM_APPLICATION_ID")
if MOONSTREAM_APPLICATION_ID is None:
raise ValueError("MOONSTREAM_APPLICATION_ID environment variable must be set")
MOONSTREAM_DATA_JOURNAL_ID = os.environ.get("MOONSTREAM_DATA_JOURNAL_ID")
if MOONSTREAM_DATA_JOURNAL_ID is None:
raise ValueError("MOONSTREAM_DATA_JOURNAL_ID environment variable must be set")
# Origin
RAW_ORIGIN = os.environ.get("MOONSTREAM_CORS_ALLOWED_ORIGINS")
if RAW_ORIGIN is None:
raise ValueError(
"MOONSTREAM_CORS_ALLOWED_ORIGINS environment variable must be set (comma-separated list of CORS allowed origins"
"MOONSTREAM_CORS_ALLOWED_ORIGINS environment variable must be set (comma-separated list of CORS allowed origins)"
)
ORIGINS = RAW_ORIGIN.split(",")
@ -14,3 +28,8 @@ MOONSTREAM_OPENAPI_LIST = []
MOONSTREAM_OPENAPI_LIST_RAW = os.environ.get("MOONSTREAM_OPENAPI_LIST")
if MOONSTREAM_OPENAPI_LIST_RAW is not None:
MOONSTREAM_OPENAPI_LIST = MOONSTREAM_OPENAPI_LIST_RAW.split(",")
DOCS_PATHS = {}
for path in MOONSTREAM_OPENAPI_LIST:
DOCS_PATHS[f"/{path}/{DOCS_TARGET_PATH}"] = "GET"
DOCS_PATHS[f"/{path}/{DOCS_TARGET_PATH}/openapi.json"] = "GET"

Wyświetl plik

@ -3,7 +3,7 @@ asgiref==3.4.1
black==21.7b0
boto3==1.18.1
botocore==1.21.1
bugout==0.1.12
bugout==0.1.13
certifi==2021.5.30
charset-normalizer==2.0.3
click==8.0.1
@ -14,9 +14,9 @@ jmespath==0.10.0
mypy==0.910
mypy-extensions==0.4.3
pathspec==0.9.0
pkg-resources==0.0.0
pydantic==1.8.2
python-dateutil==2.8.2
python-multipart-0.0.5
regex==2021.7.6
requests==2.26.0
s3transfer==0.5.0

Wyświetl plik

@ -1,2 +1,4 @@
export MOONSTREAM_CORS_ALLOWED_ORIGINS="http://localhost:3000,https://moonstream.to"
export MOONSTREAM_OPENAPI_LIST=""
export MOONSTREAM_OPENAPI_LIST="subscriptions"
export MOONSTREAM_APPLICATION_ID="<issued_bugout_application_id>"
export MOONSTREAM_DATA_JOURNAL_ID="<bugout_journal_id_to_store_blockchain_data>"