Source code for piccolo_api.shared.auth.hooks
from __future__ import annotations
import dataclasses
import inspect
import typing as t
PreLoginHook = t.Union[
t.Callable[[str], t.Optional[str]],
t.Callable[[str], t.Awaitable[t.Optional[str]]],
]
LoginSuccessHook = t.Union[
t.Callable[[str, int], t.Optional[str]],
t.Callable[[str, int], t.Awaitable[t.Optional[str]]],
]
LoginFailureHook = t.Union[
t.Callable[[str], t.Optional[str]],
t.Callable[[str], t.Awaitable[t.Optional[str]]],
]
[docs]@dataclasses.dataclass
class LoginHooks:
"""
Allows you to run custom logic during login. A hook can be a function or
coroutine.
Here's an example using :class:`session_login <piccolo_api.session_auth.endpoints.session_login>`:
.. code-block:: python
def check_ban_list(username: str, **kwargs):
'''
An example `pre_login` hook.
'''
if username in ('nuisance', 'pest'):
return 'This account has been temporarily suspended'.
async def log_success(username: str, user_id: int, **kwargs):
'''
An example `login_success` hook.
'''
await my_logging_service.record(
f'{username} just logged in'
)
async def log_failure(username: str, **kwargs):
'''
An example `login_failure` hook.
'''
await my_logging_service.record(f'{username} could not login')
return (
'To reset your password go <a href="/password-reset/">here</a>.'
)
login_endpoint = session_login(
hooks=LoginHooks(
pre_login=[check_ban_list],
login_success=[log_success],
login_failure=[log_failure],
)
)
If any of the hooks return a string, the login process is aborted, and the
login template is shown again, containing the string as a warning message.
The string can contain HTML such as links, and it will be rendered
correctly.
All of the example hooks above accept ``**kwargs`` - this is recommended
just in case more data is passed to the hooks in future Piccolo API
versions.
:param pre_login:
A list of function and / or coroutines, which accept the username as a
string.
:param login_success:
A list of function and / or coroutines, which accept the username as a
string, and the user ID as an integer. If a string is returned, the
login process stops before a session is created.
:param login_failure:
A list of function and / or coroutines, which accept the username as a
string.
""" # noqa: E501
pre_login: t.Optional[t.List[PreLoginHook]] = None
login_success: t.Optional[t.List[LoginSuccessHook]] = None
login_failure: t.Optional[t.List[LoginFailureHook]] = None
async def run_pre_login(self, username: str) -> t.Optional[str]:
if self.pre_login:
for hook in self.pre_login:
response = hook(username)
if inspect.isawaitable(response):
response = t.cast(t.Awaitable, response)
response = await response
if isinstance(response, str):
return response
return None
async def run_login_success(
self, username: str, user_id: int
) -> t.Optional[str]:
if self.login_success:
for hook in self.login_success:
response = hook(username, user_id)
if inspect.isawaitable(response):
response = t.cast(t.Awaitable, response)
response = await response
if isinstance(response, str):
return response
return None
async def run_login_failure(self, username: str) -> t.Optional[str]:
if self.login_failure:
for hook in self.login_failure:
response = hook(username)
if inspect.isawaitable(response):
response = t.cast(t.Awaitable, response)
response = await response
if isinstance(response, str):
return response
return None