mirror of
https://github.com/RYGhub/royalnet.git
synced 2024-11-27 13:34:28 +00:00
Do some token things
This commit is contained in:
parent
bb3aa1fd85
commit
56cbd5b5c6
11 changed files with 138 additions and 18 deletions
|
@ -1,4 +1,4 @@
|
|||
from typing import Set, Dict, Union
|
||||
from typing import *
|
||||
from contextlib import contextmanager, asynccontextmanager
|
||||
from royalnet.utils import asyncify
|
||||
from royalnet.alchemy.errors import TableNotFoundError
|
||||
|
@ -6,7 +6,7 @@ from sqlalchemy import create_engine
|
|||
from sqlalchemy.engine import Engine
|
||||
from sqlalchemy.schema import Table
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.ext.declarative.api import DeclarativeMeta
|
||||
from sqlalchemy.ext.declarative.api import DeclarativeMeta, AbstractConcreteBase
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
|
||||
|
@ -26,7 +26,7 @@ class Alchemy:
|
|||
raise NotImplementedError("sqlite databases aren't supported, as they can't be used in multithreaded"
|
||||
" applications")
|
||||
self._engine: Engine = create_engine(database_uri)
|
||||
self._Base: DeclarativeMeta = declarative_base(bind=self._engine)
|
||||
self._Base = declarative_base(bind=self._engine)
|
||||
self.Session: sessionmaker = sessionmaker(bind=self._engine)
|
||||
self._tables: Dict[str, Table] = {}
|
||||
for table in tables:
|
||||
|
@ -38,7 +38,7 @@ class Alchemy:
|
|||
self._tables[name] = bound_table
|
||||
self._Base.metadata.create_all()
|
||||
|
||||
def get(self, table: Union[str, type]) -> Table:
|
||||
def get(self, table: Union[str, type]) -> DeclarativeMeta:
|
||||
"""Get the table with a specified name or class.
|
||||
|
||||
Args:
|
||||
|
|
|
@ -2,13 +2,16 @@
|
|||
from .api_royalnet_version import ApiRoyalnetVersionStar
|
||||
from .api_login_royalnet import ApiLoginRoyalnetStar
|
||||
from .api_token_info import ApiTokenInfoStar
|
||||
|
||||
from .api_token_passwd import ApiTokenPasswdStar
|
||||
from .api_token_create import ApiTokenCreateStar
|
||||
|
||||
# Enter the PageStars of your Pack here!
|
||||
available_page_stars = [
|
||||
ApiRoyalnetVersionStar,
|
||||
ApiLoginRoyalnetStar,
|
||||
ApiTokenInfoStar,
|
||||
ApiTokenPasswdStar,
|
||||
ApiTokenCreateStar,
|
||||
]
|
||||
|
||||
# Don't change this, it should automatically generate __all__
|
||||
|
|
21
royalnet/backpack/stars/api_token_create.py
Normal file
21
royalnet/backpack/stars/api_token_create.py
Normal file
|
@ -0,0 +1,21 @@
|
|||
from typing import *
|
||||
import datetime
|
||||
import royalnet.utils as ru
|
||||
from royalnet.constellation.api import *
|
||||
from ..tables.tokens import Token
|
||||
from sqlalchemy import and_
|
||||
|
||||
|
||||
class ApiTokenCreateStar(ApiStar):
|
||||
path = "/api/token/create/v1"
|
||||
|
||||
async def api(self, data: ApiData) -> dict:
|
||||
user = await data.user()
|
||||
try:
|
||||
duration = int(data["duration"])
|
||||
except ValueError:
|
||||
raise InvalidParameterError("Duration is not a valid integer")
|
||||
new_token = Token.generate(self.alchemy, user, datetime.timedelta(seconds=duration))
|
||||
data.session.add(new_token)
|
||||
await data.session_commit()
|
||||
return new_token.json()
|
33
royalnet/backpack/stars/api_token_passwd.py
Normal file
33
royalnet/backpack/stars/api_token_passwd.py
Normal file
|
@ -0,0 +1,33 @@
|
|||
from typing import *
|
||||
import datetime
|
||||
import royalnet.utils as ru
|
||||
from royalnet.constellation.api import *
|
||||
from sqlalchemy import and_
|
||||
from ..tables.tokens import Token
|
||||
|
||||
|
||||
class ApiTokenPasswdStar(ApiStar):
|
||||
path = "/api/token/passwd/v1"
|
||||
|
||||
async def api(self, data: ApiData) -> dict:
|
||||
TokenT = self.alchemy.get(Token)
|
||||
token = await data.token()
|
||||
user = token.user
|
||||
user.set_password(data["new_password"])
|
||||
tokens: List[Token] = await ru.asyncify(
|
||||
data.session
|
||||
.query(self.alchemy.get(Token))
|
||||
.filter(
|
||||
and_(
|
||||
TokenT.user == user,
|
||||
TokenT.expiration >= datetime.datetime.now()
|
||||
))
|
||||
.all
|
||||
)
|
||||
for t in tokens:
|
||||
if t.token != token.token:
|
||||
t.expired = True
|
||||
await data.session_commit()
|
||||
return {
|
||||
"revoked_tokens": len(tokens) - 1
|
||||
}
|
|
@ -4,7 +4,6 @@ from sqlalchemy import *
|
|||
from sqlalchemy.orm import *
|
||||
from sqlalchemy.ext.declarative import declared_attr
|
||||
import royalnet.utils as ru
|
||||
from .users import User
|
||||
|
||||
|
||||
class Token:
|
||||
|
@ -30,10 +29,18 @@ class Token:
|
|||
def expired(self):
|
||||
return datetime.datetime.now() > self.expiration
|
||||
|
||||
@expired.setter
|
||||
def expired(self, value):
|
||||
if value is True:
|
||||
self.expiration = datetime.datetime.fromtimestamp(0)
|
||||
else:
|
||||
raise ValueError("'expired' can only be set to True.")
|
||||
|
||||
@classmethod
|
||||
def generate(cls, user, expiration_delta: datetime.timedelta):
|
||||
def generate(cls, alchemy, user, expiration_delta: datetime.timedelta):
|
||||
# noinspection PyArgumentList
|
||||
token = cls(user=user, expiration=datetime.datetime.now() + expiration_delta, token=secrets.token_urlsafe())
|
||||
TokenT = alchemy.get(cls)
|
||||
token = TokenT(user=user, expiration=datetime.datetime.now() + expiration_delta, token=secrets.token_urlsafe())
|
||||
return token
|
||||
|
||||
def json(self) -> dict:
|
||||
|
|
|
@ -26,6 +26,7 @@ class CommandData:
|
|||
if self._session is None:
|
||||
if self._interface.alchemy is None:
|
||||
raise UnsupportedError("'alchemy' is not enabled on this Royalnet instance")
|
||||
log.debug("Creating Session...")
|
||||
self._session = self._interface.alchemy.Session()
|
||||
return self._session
|
||||
|
||||
|
@ -34,11 +35,13 @@ class CommandData:
|
|||
if self._session:
|
||||
log.warning("Session had to be created to be committed")
|
||||
# noinspection PyUnresolvedReferences
|
||||
log.debug("Committing Session...")
|
||||
await ru.asyncify(self.session.commit)
|
||||
|
||||
async def session_close(self):
|
||||
"""Asyncronously close the :attr:`.session` of this object."""
|
||||
if self._session is not None:
|
||||
log.debug("Closing Session...")
|
||||
await ru.asyncify(self._session.close)
|
||||
|
||||
async def reply(self, text: str) -> None:
|
||||
|
@ -71,9 +74,7 @@ class CommandData:
|
|||
|
||||
Parameters:
|
||||
alias: the Alias to search for."""
|
||||
return await ru.asyncify(
|
||||
Alias.find_user(self._interface.alchemy, self.session, alias)
|
||||
)
|
||||
return await Alias.find_user(self._interface.alchemy, self.session, alias)
|
||||
|
||||
@contextlib.asynccontextmanager
|
||||
async def keyboard(self, text, keys: List["KeyboardKey"]):
|
||||
|
|
|
@ -1,7 +1,16 @@
|
|||
from .apistar import ApiStar
|
||||
from .jsonapi import api_response, api_success, api_error
|
||||
from .apidata import ApiData
|
||||
from .apierrors import ApiError, MissingParameterError, NotFoundError, ForbiddenError
|
||||
from .apierrors import \
|
||||
ApiError, \
|
||||
NotFoundError, \
|
||||
ForbiddenError, \
|
||||
BadRequestError, \
|
||||
ParameterError, \
|
||||
MissingParameterError, \
|
||||
InvalidParameterError, \
|
||||
NotImplementedError, \
|
||||
UnsupportedError
|
||||
|
||||
|
||||
__all__ = [
|
||||
|
@ -13,4 +22,13 @@ __all__ = [
|
|||
"ApiError",
|
||||
"MissingParameterError",
|
||||
"NotFoundError",
|
||||
"ApiError",
|
||||
"NotFoundError",
|
||||
"ForbiddenError",
|
||||
"BadRequestError",
|
||||
"ParameterError",
|
||||
"MissingParameterError",
|
||||
"InvalidParameterError",
|
||||
"NotImplementedError",
|
||||
"UnsupportedError",
|
||||
]
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
import logging
|
||||
from .apierrors import MissingParameterError
|
||||
from royalnet.backpack.tables.tokens import Token
|
||||
from royalnet.backpack.tables.users import User
|
||||
from .apierrors import *
|
||||
import royalnet.utils as ru
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ApiData(dict):
|
||||
|
@ -19,7 +23,7 @@ class ApiData(dict):
|
|||
raise ForbiddenError("'token' is invalid")
|
||||
return token
|
||||
|
||||
async def user(self) -> Token:
|
||||
async def user(self) -> User:
|
||||
return (await self.token()).user
|
||||
|
||||
@property
|
||||
|
@ -27,5 +31,20 @@ class ApiData(dict):
|
|||
if self._session is None:
|
||||
if self.star.alchemy is None:
|
||||
raise UnsupportedError("'alchemy' is not enabled on this Royalnet instance")
|
||||
log.debug("Creating Session...")
|
||||
self._session = self.star.alchemy.Session()
|
||||
return self._session
|
||||
|
||||
async def session_commit(self):
|
||||
"""Asyncronously commit the :attr:`.session` of this object."""
|
||||
if self._session:
|
||||
log.warning("Session had to be created to be committed")
|
||||
# noinspection PyUnresolvedReferences
|
||||
log.debug("Committing Session...")
|
||||
await ru.asyncify(self.session.commit)
|
||||
|
||||
async def session_close(self):
|
||||
"""Asyncronously close the :attr:`.session` of this object."""
|
||||
if self._session is not None:
|
||||
log.debug("Closing Session...")
|
||||
await ru.asyncify(self._session.close)
|
||||
|
|
|
@ -10,7 +10,19 @@ class ForbiddenError(ApiError):
|
|||
pass
|
||||
|
||||
|
||||
class MissingParameterError(ApiError):
|
||||
class BadRequestError(ApiError):
|
||||
pass
|
||||
|
||||
|
||||
class ParameterError(BadRequestError):
|
||||
pass
|
||||
|
||||
|
||||
class MissingParameterError(ParameterError):
|
||||
pass
|
||||
|
||||
|
||||
class InvalidParameterError(ParameterError):
|
||||
pass
|
||||
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ from ..pagestar import PageStar
|
|||
from .jsonapi import api_error, api_success
|
||||
from .apidata import ApiData
|
||||
from .apierrors import *
|
||||
from royalnet.utils import sentry_exc
|
||||
|
||||
|
||||
class ApiStar(PageStar, ABC):
|
||||
|
@ -18,19 +19,24 @@ class ApiStar(PageStar, ABC):
|
|||
data = await request.json()
|
||||
except JSONDecodeError:
|
||||
data = {}
|
||||
apidata = ApiData(data, self)
|
||||
try:
|
||||
response = await self.api(ApiData(data, self))
|
||||
response = await self.api(apidata)
|
||||
except NotFoundError as e:
|
||||
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 BadRequestError as e:
|
||||
return api_error(e, code=400)
|
||||
except Exception as e:
|
||||
sentry_exc(e)
|
||||
return api_error(e, code=500)
|
||||
else:
|
||||
return api_success(response)
|
||||
finally:
|
||||
await apidata.session_close()
|
||||
|
||||
async def api(self, data: dict) -> dict:
|
||||
raise NotImplementedError()
|
||||
|
|
|
@ -47,7 +47,7 @@ class Serf:
|
|||
}
|
||||
except ImportError as e:
|
||||
log.error(f"{e.__class__.__name__} during the import of {pack_name}:\n"
|
||||
f"{traceback.format_exception(*sys.exc_info())}")
|
||||
f"{''.join(traceback.format_exception(*sys.exc_info()))}")
|
||||
log.info(f"Packs: {len(packs)} imported")
|
||||
|
||||
self.alchemy: Optional[ra.Alchemy] = None
|
||||
|
|
Loading…
Reference in a new issue