An ebook/comic library service and web client
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

84 lines
2.5 KiB

  1. import base64
  2. from functools import wraps
  3. from typing import Optional, Callable
  4. from flask import request, Response, g
  5. from werkzeug.datastructures import Authorization
  6. from werkzeug.http import bytes_to_wsgi, wsgi_to_bytes
  7. from atheneum.service import (
  8. authentication_service,
  9. user_service,
  10. user_token_service
  11. )
  12. def authenticate_with_password(name: str, password: str) -> bool:
  13. user = user_service.find_by_name(name)
  14. if user is not None \
  15. and authentication_service.is_valid_password(user, password):
  16. g.user = user
  17. return True
  18. return False
  19. def authenticate_with_token(name: str, token: str) -> bool:
  20. user = user_service.find_by_name(name)
  21. user_token = user_token_service.find_by_user_and_token(user, token)
  22. if user is not None \
  23. and authentication_service.is_valid_token(user_token):
  24. g.user = user
  25. g.user_token = user_token
  26. return True
  27. return False
  28. def authentication_failed(auth_type: str) -> Response:
  29. return Response(
  30. status=401,
  31. headers={
  32. 'WWW-Authenticate': '%s realm="Login Required"' % auth_type
  33. })
  34. def parse_token_authorization_header(header_value) -> Optional[Authorization]:
  35. if not header_value:
  36. return
  37. value = wsgi_to_bytes(header_value)
  38. try:
  39. auth_type, auth_info = value.split(None, 1)
  40. auth_type = auth_type.lower()
  41. except ValueError:
  42. return
  43. if auth_type == b'token':
  44. try:
  45. username, token = base64.b64decode(auth_info).split(b':', 1)
  46. except Exception:
  47. return
  48. return Authorization('token', {'username': bytes_to_wsgi(username),
  49. 'password': bytes_to_wsgi(token)})
  50. def require_basic_auth(func: Callable) -> Callable:
  51. @wraps(func)
  52. def decorate(*args, **kwargs):
  53. auth = request.authorization
  54. if auth and authenticate_with_password(auth.username, auth.password):
  55. return func(*args, **kwargs)
  56. else:
  57. return authentication_failed('Basic')
  58. return decorate
  59. def require_token_auth(func: Callable) -> Callable:
  60. @wraps(func)
  61. def decorate(*args, **kwargs):
  62. token = parse_token_authorization_header(
  63. request.headers.get('Authorization', None))
  64. if token and authenticate_with_token(token.username, token.password):
  65. return func(*args, **kwargs)
  66. else:
  67. return authentication_failed('Bearer')
  68. return decorate