kopia lustrzana https://github.com/bugout-dev/moonstream
User and token endpoints with updated middleware
rodzic
e547b2fb2f
commit
4dd8b4c79b
|
@ -0,0 +1,2 @@
|
||||||
|
|
||||||
|
|
|
@ -2,43 +2,21 @@
|
||||||
The Moonstream HTTP API
|
The Moonstream HTTP API
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
from typing import Any, Dict, List, Optional
|
|
||||||
import uuid
|
|
||||||
|
|
||||||
from fastapi import (
|
from bugout.data import BugoutUser
|
||||||
BackgroundTasks,
|
from fastapi import FastAPI, Form
|
||||||
Depends,
|
|
||||||
FastAPI,
|
|
||||||
Form,
|
|
||||||
HTTPException,
|
|
||||||
Path,
|
|
||||||
Query,
|
|
||||||
Request,
|
|
||||||
Response,
|
|
||||||
)
|
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
from fastapi.security import OAuth2PasswordRequestForm
|
|
||||||
|
|
||||||
from . import data
|
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
|
from .version import MOONSTREAM_VERSION
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
logger = logging.getLogger(__name__)
|
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(
|
app.add_middleware(
|
||||||
CORSMiddleware,
|
CORSMiddleware,
|
||||||
allow_origins=ORIGINS,
|
allow_origins=ORIGINS,
|
||||||
|
@ -49,10 +27,13 @@ app.add_middleware(
|
||||||
|
|
||||||
|
|
||||||
@app.get("/ping", response_model=data.PingResponse)
|
@app.get("/ping", response_model=data.PingResponse)
|
||||||
async def ping() -> data.PingResponse:
|
async def ping_handler() -> data.PingResponse:
|
||||||
return data.PingResponse(status="ok")
|
return data.PingResponse(status="ok")
|
||||||
|
|
||||||
|
|
||||||
@app.get("/version", response_model=data.VersionResponse)
|
@app.get("/version", response_model=data.VersionResponse)
|
||||||
async def version() -> data.VersionResponse:
|
async def version_handler() -> data.VersionResponse:
|
||||||
return data.VersionResponse(version=MOONSTREAM_VERSION)
|
return data.VersionResponse(version=MOONSTREAM_VERSION)
|
||||||
|
|
||||||
|
|
||||||
|
app.mount("/users", users_api)
|
||||||
|
|
|
@ -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)
|
|
@ -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
|
|
@ -1,10 +1,24 @@
|
||||||
import os
|
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
|
# Origin
|
||||||
RAW_ORIGIN = os.environ.get("MOONSTREAM_CORS_ALLOWED_ORIGINS")
|
RAW_ORIGIN = os.environ.get("MOONSTREAM_CORS_ALLOWED_ORIGINS")
|
||||||
if RAW_ORIGIN is None:
|
if RAW_ORIGIN is None:
|
||||||
raise ValueError(
|
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(",")
|
ORIGINS = RAW_ORIGIN.split(",")
|
||||||
|
|
||||||
|
@ -14,3 +28,8 @@ MOONSTREAM_OPENAPI_LIST = []
|
||||||
MOONSTREAM_OPENAPI_LIST_RAW = os.environ.get("MOONSTREAM_OPENAPI_LIST")
|
MOONSTREAM_OPENAPI_LIST_RAW = os.environ.get("MOONSTREAM_OPENAPI_LIST")
|
||||||
if MOONSTREAM_OPENAPI_LIST_RAW is not None:
|
if MOONSTREAM_OPENAPI_LIST_RAW is not None:
|
||||||
MOONSTREAM_OPENAPI_LIST = MOONSTREAM_OPENAPI_LIST_RAW.split(",")
|
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"
|
||||||
|
|
|
@ -3,7 +3,7 @@ asgiref==3.4.1
|
||||||
black==21.7b0
|
black==21.7b0
|
||||||
boto3==1.18.1
|
boto3==1.18.1
|
||||||
botocore==1.21.1
|
botocore==1.21.1
|
||||||
bugout==0.1.12
|
bugout==0.1.13
|
||||||
certifi==2021.5.30
|
certifi==2021.5.30
|
||||||
charset-normalizer==2.0.3
|
charset-normalizer==2.0.3
|
||||||
click==8.0.1
|
click==8.0.1
|
||||||
|
@ -14,9 +14,9 @@ jmespath==0.10.0
|
||||||
mypy==0.910
|
mypy==0.910
|
||||||
mypy-extensions==0.4.3
|
mypy-extensions==0.4.3
|
||||||
pathspec==0.9.0
|
pathspec==0.9.0
|
||||||
pkg-resources==0.0.0
|
|
||||||
pydantic==1.8.2
|
pydantic==1.8.2
|
||||||
python-dateutil==2.8.2
|
python-dateutil==2.8.2
|
||||||
|
python-multipart-0.0.5
|
||||||
regex==2021.7.6
|
regex==2021.7.6
|
||||||
requests==2.26.0
|
requests==2.26.0
|
||||||
s3transfer==0.5.0
|
s3transfer==0.5.0
|
|
@ -1,2 +1,4 @@
|
||||||
export MOONSTREAM_CORS_ALLOWED_ORIGINS="http://localhost:3000,https://moonstream.to"
|
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>"
|
||||||
|
|
Ładowanie…
Reference in New Issue