From 441a112fb2c32cb6e35a790e239e1d4eb3fdb5b8 Mon Sep 17 00:00:00 2001 From: Ivan <iyuvlasov_1@edu.hse.ru> Date: Mon, 12 Dec 2022 20:33:30 +0300 Subject: [PATCH] Added availability to create BotTokens for automated access to API --- auth/middleware.py | 12 +++++++++--- endpoints/__init__.py | 2 +- endpoints/routes.py | 3 +++ endpoints/user.py | 29 ++++++++++++++++++++++++++++- models/__init__.py | 2 +- models/users.py | 40 +++++++++++++++++++++++++++++++++++----- responses/errors.py | 7 +++++++ 7 files changed, 84 insertions(+), 11 deletions(-) diff --git a/auth/middleware.py b/auth/middleware.py index aa0b5a4..b4463f0 100644 --- a/auth/middleware.py +++ b/auth/middleware.py @@ -1,10 +1,12 @@ +from datetime import datetime + from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint from starlette.requests import Request from starlette.responses import Response from auth.key import decrypt_token -from models import User -from responses.errors import TokenUndefinedError, UserDontExist +from models import User, BotToken +from responses.errors import TokenUndefinedError, UserDontExist, BotTokenDontExist def parse_token(request: Request) -> str: @@ -24,13 +26,17 @@ def parse_token(request: Request) -> str: class JWTAuthenticationMiddleware(BaseHTTPMiddleware): """ JWT Authentication middleware """ + async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response: token = parse_token(request) data = decrypt_token(token) user = await User.get_or_none(id=data["user_id"]) if user is None: raise UserDontExist + if "bot_token_id" in data: + bot_token = await BotToken.get_or_none(id=data["bot_token_id"]) + if bot_token is None or bot_token.expires_at.timestamp() < datetime.now().timestamp(): + raise BotTokenDontExist request.state.token_data = data request.state.user = user return await call_next(request) - diff --git a/endpoints/__init__.py b/endpoints/__init__.py index 71ddfbe..26bd82d 100644 --- a/endpoints/__init__.py +++ b/endpoints/__init__.py @@ -1,5 +1,5 @@ from .oauth import github_oauth_callback, github_oauth_redirect from .service import ping -from .user import get_me +from .user import get_me, create_bot_token, get_bot_tokens, delete_bot_token from .parser import load_report from .indicators import get_indicators_from_group diff --git a/endpoints/routes.py b/endpoints/routes.py index 0c31a9f..abb0dcc 100644 --- a/endpoints/routes.py +++ b/endpoints/routes.py @@ -14,6 +14,9 @@ api_routes = [ Route("/getMe", get_me, methods=["GET"]), Route("/loadReport", load_report, methods=["POST"]), Route("/getIndicatorsFromGroup", get_indicators_from_group, methods=["GET"]), + Route("/createBotToken", create_bot_token, methods=["GET"]), + Route("/getBotTokens", get_bot_tokens, methods=["GET"]), + Route("/deleteBotToken", delete_bot_token, methods=["GET"]) ] admin_routes = [] diff --git a/endpoints/user.py b/endpoints/user.py index 1dd5b26..eb22d7e 100644 --- a/endpoints/user.py +++ b/endpoints/user.py @@ -1,7 +1,8 @@ from starlette.requests import Request from models import User -from models.users import UserPD +from models.users import UserPD, BotToken +from responses.errors import BotTokenDontExist from responses.responses import OkResponse @@ -10,3 +11,29 @@ async def get_me(request: Request): user: User = request.state.user user_model = UserPD.from_orm(user) return OkResponse(user_model) + + +async def create_bot_token(request: Request): + """ Creates and returns bot token """ + user: User = request.state.user + expires = int(request.query_params.get("expires", 1)) + token = user.create_token(for_bot=True) + await BotToken.create_from_token(token, expires) + return OkResponse({"token": token}) + + +async def get_bot_tokens(request: Request): + """ Returns all bot tokens of user """ + user: User = request.state.user + tokens = await BotToken.filter(user=user).all() + return OkResponse({"tokens": [{"id": str(token.id)[:4], "expires_in": token.expires_at} for token in tokens]}) + + +async def delete_bot_token(request: Request): + """ Deletes bot token """ + user: User = request.state.user + token_id = request.query_params["token_id"] + token = await BotToken.get_or_none(id=token_id) + if token is None: + raise BotTokenDontExist + return OkResponse({"deleted": await token.delete()}) diff --git a/models/__init__.py b/models/__init__.py index 58086f9..852d9ef 100644 --- a/models/__init__.py +++ b/models/__init__.py @@ -1,3 +1,3 @@ -from .users import User +from .users import User, BotToken from .indicator import Indicator, IndicatorGroup from .report import Report diff --git a/models/users.py b/models/users.py index 3afa643..4ff7544 100644 --- a/models/users.py +++ b/models/users.py @@ -1,9 +1,11 @@ from datetime import datetime, timedelta +from uuid import uuid4 from jwcrypto.jwt import JWT from pydantic import BaseModel from tortoise import Model, fields -from auth.key import key +from auth.key import key, decrypt_token +from responses.errors import UserDontExist class User(Model): @@ -14,14 +16,16 @@ class User(Model): is_admin = fields.BooleanField(default=False) is_private = fields.BooleanField(default=False) - def create_token(self) -> str: + def create_token(self, for_bot=False) -> str: """ Creates JWT token for user """ - jwt = JWT(header={"alg": "RS256"}, claims={ + claims = { "user_id": self.id, - "is_api_token": False, "exp": int((datetime.now() + timedelta(days=30)).timestamp()), "iss": "ioc" - }) + } + if for_bot: + claims["bot_token_id"] = str(uuid4()) + jwt = JWT(header={"alg": "RS256"}, claims=claims) jwt.make_signed_token(key.jwk) return jwt.serialize() @@ -57,3 +61,29 @@ class UserPD(BaseModel): 'is_admin': {'exclude': True}, 'is_private': {'exclude': True} } + + +class BotToken(Model): + """ ORM model of Bot Token """ + id = fields.UUIDField(pk=True) + user = fields.ForeignKeyField('models.User', on_delete=fields.CASCADE) + expires_at = fields.DatetimeField() + created_at = fields.DatetimeField(auto_now=True) + + class Meta: + table = "bot_tokens" + + @staticmethod + async def create_from_token(token: str, expires_in: int = 30) -> 'BotToken': + """ Creates new bot token from token + + Args: + token: Token, which will be used to create bot token + expires_in: Time in days, when token will be expired + """ + data = decrypt_token(token) + user = await User.get_or_none(id=data["user_id"]) + if user is None: + raise UserDontExist + return await BotToken.create(user=user, id=data["bot_token_id"], + expires_at=datetime.now() + timedelta(days=expires_in)) diff --git a/responses/errors.py b/responses/errors.py index 8f26a64..a999a58 100644 --- a/responses/errors.py +++ b/responses/errors.py @@ -73,3 +73,10 @@ class IndicatorGroupDoesNotExist(ApiError): code = 402 description = "Indicator group does not exist" http_code = 404 + + +class BotTokenDontExist(ApiError): + """ Bot token not found in database """ + code = 503 + description = "Bot token does not exist" + http_code = 404 -- GitLab