1
Fork 0
mirror of https://github.com/RYGhub/royalnet.git synced 2024-11-27 13:34:28 +00:00

API login routes completed

This commit is contained in:
Steffo 2020-02-07 15:36:19 +01:00
parent 741b9f50db
commit bb3aa1fd85
13 changed files with 89 additions and 30 deletions

View file

@ -1,12 +1,14 @@
# Imports go here! # Imports go here!
from .api_royalnet_version import ApiRoyalnetVersionStar from .api_royalnet_version import ApiRoyalnetVersionStar
from .api_royalnet_login import ApiRoyalnetLoginStar from .api_login_royalnet import ApiLoginRoyalnetStar
from .api_token_info import ApiTokenInfoStar
# Enter the PageStars of your Pack here! # Enter the PageStars of your Pack here!
available_page_stars = [ available_page_stars = [
ApiRoyalnetVersionStar, ApiRoyalnetVersionStar,
ApiRoyalnetLoginStar, ApiLoginRoyalnetStar,
ApiTokenInfoStar,
] ]
# Don't change this, it should automatically generate __all__ # Don't change this, it should automatically generate __all__

View file

@ -6,10 +6,10 @@ from ..tables.aliases import Alias
from ..tables.tokens import Token from ..tables.tokens import Token
class ApiRoyalnetLoginStar(ApiStar): class ApiLoginRoyalnetStar(ApiStar):
path = "/api/royalnet/login/v1" path = "/api/login/royalnet/v1"
async def api(self, data: ApiDataDict) -> dict: async def api(self, data: ApiData) -> dict:
TokenT = self.alchemy.get(Token) TokenT = self.alchemy.get(Token)
UserT = self.alchemy.get(User) UserT = self.alchemy.get(User)
AliasT = self.alchemy.get(Alias) AliasT = self.alchemy.get(Alias)
@ -20,7 +20,7 @@ class ApiRoyalnetLoginStar(ApiStar):
async with self.session_acm() as session: async with self.session_acm() as session:
user: User = await ru.asyncify(session.query(UserT).filter_by(username=username).one_or_none) user: User = await ru.asyncify(session.query(UserT).filter_by(username=username).one_or_none)
if user is None: if user is None:
raise NotFoundException("User not found") raise NotFoundError("User not found")
pswd_check = user.test_password(password) pswd_check = user.test_password(password)
if not pswd_check: if not pswd_check:
raise ApiError("Invalid password") raise ApiError("Invalid password")

View file

@ -5,7 +5,7 @@ from royalnet.constellation.api import *
class ApiRoyalnetVersionStar(ApiStar): class ApiRoyalnetVersionStar(ApiStar):
path = "/api/royalnet/version/v1" path = "/api/royalnet/version/v1"
async def api(self, data: ApiDataDict) -> dict: async def api(self, data: ApiData) -> dict:
return { return {
"semantic": rv.semantic "semantic": rv.semantic
} }

View file

@ -0,0 +1,13 @@
import datetime
import royalnet.utils as ru
from royalnet.constellation.api import *
from ..tables.users import User
from ..tables.aliases import Alias
class ApiTokenInfoStar(ApiStar):
path = "/api/token/info/v1"
async def api(self, data: ApiData) -> dict:
token = await data.token()
return token.json()

View file

@ -4,6 +4,7 @@ from sqlalchemy import Column, \
ForeignKey ForeignKey
from sqlalchemy.orm import relationship from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declared_attr from sqlalchemy.ext.declarative import declared_attr
import royalnet.utils as ru
class Alias: class Alias:
@ -22,10 +23,10 @@ class Alias:
return relationship("User", backref="aliases") return relationship("User", backref="aliases")
@classmethod @classmethod
def find_user(cls, alchemy, session, alias: str): async def find_user(cls, alchemy, session, alias: str):
result = session.query(alchemy.get(cls)).filter_by(alias=alias.lower()).one_or_none() result = await ru.asyncify(session.query(alchemy.get(cls)).filter_by(alias=alias.lower()).one_or_none)
if result is not None: if result is not None:
result = result.royal result = result.user
return result return result
def __init__(self, user: str, alias: str): def __init__(self, user: str, alias: str):

View file

@ -3,6 +3,8 @@ import secrets
from sqlalchemy import * from sqlalchemy import *
from sqlalchemy.orm import * from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declared_attr from sqlalchemy.ext.declarative import declared_attr
import royalnet.utils as ru
from .users import User
class Token: class Token:
@ -40,3 +42,7 @@ class Token:
"token": self.token, "token": self.token,
"expiration": self.expiration.isoformat() "expiration": self.expiration.isoformat()
} }
@classmethod
async def authenticate(cls, alchemy, session, token: str) -> "Token":
return await ru.asyncify(session.query(alchemy.get(cls)).filter_by(token=token).one_or_none)

View file

@ -75,7 +75,6 @@ class CommandData:
Alias.find_user(self._interface.alchemy, self.session, alias) Alias.find_user(self._interface.alchemy, self.session, alias)
) )
@contextlib.asynccontextmanager @contextlib.asynccontextmanager
async def keyboard(self, text, keys: List["KeyboardKey"]): async def keyboard(self, text, keys: List["KeyboardKey"]):
yield yield

View file

@ -1,7 +1,7 @@
from .apistar import ApiStar from .apistar import ApiStar
from .jsonapi import api_response, api_success, api_error from .jsonapi import api_response, api_success, api_error
from .apidatadict import ApiDataDict from .apidata import ApiData
from .apierrors import ApiError, MissingParameterException, NotFoundException, UnauthorizedException from .apierrors import ApiError, MissingParameterError, NotFoundError, ForbiddenError
__all__ = [ __all__ = [
@ -9,8 +9,8 @@ __all__ = [
"api_response", "api_response",
"api_success", "api_success",
"api_error", "api_error",
"ApiDataDict", "ApiData",
"ApiError", "ApiError",
"MissingParameterException", "MissingParameterError",
"NotFoundException", "NotFoundError",
] ]

View file

@ -0,0 +1,31 @@
from .apierrors import MissingParameterError
from royalnet.backpack.tables.tokens import Token
from royalnet.backpack.tables.users import User
from .apierrors import *
class ApiData(dict):
def __init__(self, data, star):
super().__init__(data)
self.star = star
self._session = None
def __missing__(self, key):
raise MissingParameterError(f"Missing '{key}'")
async def token(self) -> Token:
token = await Token.authenticate(self.star.alchemy, self.session, self["token"])
if token is None:
raise ForbiddenError("'token' is invalid")
return token
async def user(self) -> Token:
return (await self.token()).user
@property
def session(self):
if self._session is None:
if self.star.alchemy is None:
raise UnsupportedError("'alchemy' is not enabled on this Royalnet instance")
self._session = self.star.alchemy.Session()
return self._session

View file

@ -1,6 +0,0 @@
from .apierrors import MissingParameterException
class ApiDataDict(dict):
def __missing__(self, key):
raise MissingParameterException(f"Missing '{key}'")

View file

@ -2,13 +2,21 @@ class ApiError(Exception):
pass pass
class NotFoundException(ApiError): class NotFoundError(ApiError):
pass pass
class UnauthorizedException(ApiError): class ForbiddenError(ApiError):
pass pass
class MissingParameterException(ApiError): class MissingParameterError(ApiError):
pass
class NotImplementedError(ApiError):
pass
class UnsupportedError(NotImplementedError):
pass pass

View file

@ -5,8 +5,8 @@ from starlette.requests import Request
from starlette.responses import JSONResponse from starlette.responses import JSONResponse
from ..pagestar import PageStar from ..pagestar import PageStar
from .jsonapi import api_error, api_success from .jsonapi import api_error, api_success
from .apidatadict import ApiDataDict from .apidata import ApiData
from .apierrors import ApiError, NotFoundException from .apierrors import *
class ApiStar(PageStar, ABC): class ApiStar(PageStar, ABC):
@ -19,9 +19,13 @@ class ApiStar(PageStar, ABC):
except JSONDecodeError: except JSONDecodeError:
data = {} data = {}
try: try:
response = await self.api(ApiDataDict(data)) response = await self.api(ApiData(data, self))
except NotFoundException as e: except NotFoundError as e:
return api_error(e, code=404) return api_error(e, code=404)
except ForbiddenError as e:
return api_error(e, code=403)
except NotImplementedError as e:
return api_error(e, code=501)
except ApiError as e: except ApiError as e:
return api_error(e, code=400) return api_error(e, code=400)
except Exception as e: except Exception as e:

View file

@ -27,6 +27,7 @@ def api_error(error: Exception, code: int = 500) -> JSONResponse:
result = { result = {
"success": False, "success": False,
"error_type": error.__class__.__qualname__, "error_type": error.__class__.__qualname__,
"error_args": list(error.args) "error_args": list(error.args),
"error_code": code,
} }
return api_response(result, code=code) return api_response(result, code=code)