diff --git a/vue-apps-py/README.MD b/vue-apps-py/README.MD index a5cc92b..19ffda0 100644 --- a/vue-apps-py/README.MD +++ b/vue-apps-py/README.MD @@ -8,18 +8,26 @@ You will probably need to install ***uvicorn*** to launch a server built with Fa python3 -m pip install "uvicorn[standard]" ``` -After importing an app such as - -main.py: +After importing an app such as `main.py`: ```python -from vue_apps_py.vue_app_server import VueApp +from src.vue_apps_py.security import SecurityHelper +from src.vue_apps_py.server import VueApp +from src.vue_apps_py.client import ClientAPI +from src.vue_apps_py.server_utils.models import Intent -app = VueApp() +# Initialize API +api = ClientAPI(client_id='vue-app-0', client_secret='abccf12389efab222') + +# Prepare Security +key_holder = SecurityHelper(public_key='abbcbbbcaksjdhf/skdjhfnnsn/sjdkfjj21234=') + +# Get Vue Application Server +vue_app = VueApp(client_api=api, security_helper=key_holder, intents=[Intent.INLINE_QUERY], debug=True) ``` You can launch web server: ```shell -uvicorn main:app --reload +uvicorn test:vue_app --reload ``` diff --git a/vue-apps-py/pyproject.toml b/vue-apps-py/pyproject.toml index 43470da..0b38fea 100644 --- a/vue-apps-py/pyproject.toml +++ b/vue-apps-py/pyproject.toml @@ -5,10 +5,10 @@ build-backend = "setuptools.build_meta" [project] name = "vue_apps_py" version = "0.0.1" -authors = [{ name = "ArbinaHQ", email = "hey@arbina.com" }] +authors = [{ name = "Arbina", email = "havlong@arbina.com" }] description = "Python library for building Vue Apps" readme = "README.MD" -requires-python = ">=3.9" +requires-python = ">=3.7" classifiers = ["Programming Language :: Python :: 3", "Operating System :: OS Independent"] [project.urls] diff --git a/vue-apps-py/src/vue_apps_py/client.py b/vue-apps-py/src/vue_apps_py/client.py new file mode 100644 index 0000000..ff16f01 --- /dev/null +++ b/vue-apps-py/src/vue_apps_py/client.py @@ -0,0 +1,10 @@ +class ClientAPI: + client_id: str + client_secret: str + + def __init__(self, client_id: str, client_secret: str): + self.client_id = client_id + self.client_secret = client_secret + + def get_token(self) -> str: + return self.client_secret diff --git a/vue-apps-py/src/vue_apps_py/security.py b/vue-apps-py/src/vue_apps_py/security.py new file mode 100644 index 0000000..652f394 --- /dev/null +++ b/vue-apps-py/src/vue_apps_py/security.py @@ -0,0 +1,15 @@ +from ed25519.keys import VerifyingKey + + +class SecurityHelper: + public_key: VerifyingKey + + def __init__(self, public_key: str): + self.public_key = VerifyingKey(public_key.encode(), encoding='base64') + + def check(self, http: bytes, signature: bytes) -> bool: + try: + self.public_key.verify(signature, http, encoding='base64') + return True + except AssertionError: + return False diff --git a/vue-apps-py/src/vue_apps_py/server.py b/vue-apps-py/src/vue_apps_py/server.py new file mode 100644 index 0000000..ed9aedb --- /dev/null +++ b/vue-apps-py/src/vue_apps_py/server.py @@ -0,0 +1,38 @@ +from typing import List + +from fastapi import FastAPI, Request, HTTPException + +from client import ClientAPI +from security import SecurityHelper +from server_utils.models import PongModel, Intent + + +def VueApp(client_api: ClientAPI, security_helper: SecurityHelper, intents: List[Intent], debug: bool = False): + app = FastAPI(debug=debug, title='VueApp', description='Vue Application Server', version='0.0.1') + + async def check_request(request: Request) -> bool: + if 'X-Signature-Ed25519' not in request.headers or 'X-Signature-Timestamp' not in request.headers: + return False + body_to_verify = await request.body() + timestamp = request.headers['X-Signature-Timestamp'].encode('utf-8') + signature = request.headers['X-Signature-Ed25519'].encode('utf-8') + return security_helper.check(timestamp + body_to_verify, signature) + + @app.post(path='/ping', response_model=PongModel) + async def handle_ping(request: Request) -> PongModel: + ok = await check_request(request) + if not ok: + raise HTTPException(status_code=401, detail='Verification of signature failed') + + return PongModel(message='pong', token=client_api.get_token()) + + if Intent.INLINE_QUERY in intents: + @app.post(path='/auto', response_model=PongModel) + async def handle_autocomplete(request: Request) -> PongModel: + ok = await check_request(request) + if not ok: + raise HTTPException(status_code=401, detail='Verification of signature failed') + + return PongModel(message='pong', token=client_api.get_token()) + + return app diff --git a/vue-apps-py/src/vue_apps_py/vue_app_client.py b/vue-apps-py/src/vue_apps_py/server_utils/__init__.py similarity index 100% rename from vue-apps-py/src/vue_apps_py/vue_app_client.py rename to vue-apps-py/src/vue_apps_py/server_utils/__init__.py diff --git a/vue-apps-py/src/vue_apps_py/server_utils/models.py b/vue-apps-py/src/vue_apps_py/server_utils/models.py new file mode 100644 index 0000000..efb4efc --- /dev/null +++ b/vue-apps-py/src/vue_apps_py/server_utils/models.py @@ -0,0 +1,20 @@ +from enum import Enum, auto +from typing import Optional + +from pydantic import BaseModel, Field + + +class PingModel(BaseModel): + nickname: str + + +class PongModel(BaseModel): + message: str = Field(default='ping', title='Message', description='Message answered on pings') + token: Optional[str] = Field(default=None, title='Token', description='Token for API to check') + + +class Intent(Enum): + PAGE_APP = auto() + PAGE_MENTIONS = auto() + INLINE_QUERY = auto() + INTERACTIONS = auto() diff --git a/vue-apps-py/src/vue_apps_py/vue_app_security.py b/vue-apps-py/src/vue_apps_py/vue_app_security.py deleted file mode 100644 index e69de29..0000000 diff --git a/vue-apps-py/src/vue_apps_py/vue_app_server.py b/vue-apps-py/src/vue_apps_py/vue_app_server.py deleted file mode 100644 index e69de29..0000000