auth.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. __package__ = 'archivebox.api'
  2. from typing import Optional, cast
  3. from django.http import HttpRequest
  4. from django.contrib.auth import login
  5. from django.contrib.auth import authenticate
  6. from django.contrib.auth.models import AbstractBaseUser
  7. from ninja.security import HttpBearer, APIKeyQuery, APIKeyHeader, HttpBasicAuth, django_auth_superuser
  8. def auth_using_token(token, request: Optional[HttpRequest]=None) -> Optional[AbstractBaseUser]:
  9. """Given an API token string, check if a corresponding non-expired APIToken exists, and return its user"""
  10. from api.models import APIToken # lazy import model to avoid loading it at urls.py import time
  11. user = None
  12. submitted_empty_form = token in ('string', '', None)
  13. if submitted_empty_form:
  14. assert request is not None, 'No request provided for API key authentication'
  15. user = request.user # see if user is authed via django session and use that as the default
  16. else:
  17. try:
  18. token = APIToken.objects.get(token=token)
  19. if token.is_valid():
  20. user = token.created_by
  21. except APIToken.DoesNotExist:
  22. pass
  23. if not user:
  24. print('[❌] Failed to authenticate API user using API Key:', request)
  25. return None
  26. return cast(AbstractBaseUser, user)
  27. def auth_using_password(username, password, request: Optional[HttpRequest]=None) -> Optional[AbstractBaseUser]:
  28. """Given a username and password, check if they are valid and return the corresponding user"""
  29. user = None
  30. submitted_empty_form = (username, password) in (('string', 'string'), ('', ''), (None, None))
  31. if submitted_empty_form:
  32. assert request is not None, 'No request provided for API key authentication'
  33. user = request.user # see if user is authed via django session and use that as the default
  34. else:
  35. user = authenticate(
  36. username=username,
  37. password=password,
  38. )
  39. if not user:
  40. print('[❌] Failed to authenticate API user using API Key:', request)
  41. user = None
  42. return cast(AbstractBaseUser | None, user)
  43. ### Base Auth Types
  44. class APITokenAuthCheck:
  45. """The base class for authentication methods that use an api.models.APIToken"""
  46. def authenticate(self, request: HttpRequest, key: Optional[str]=None) -> Optional[AbstractBaseUser]:
  47. user = auth_using_token(
  48. token=key,
  49. request=request,
  50. )
  51. if user is not None:
  52. login(request, user, backend='django.contrib.auth.backends.ModelBackend')
  53. return user
  54. class UserPassAuthCheck:
  55. """The base class for authentication methods that use a username & password"""
  56. def authenticate(self, request: HttpRequest, username: Optional[str]=None, password: Optional[str]=None) -> Optional[AbstractBaseUser]:
  57. user = auth_using_password(
  58. username=username,
  59. password=password,
  60. request=request,
  61. )
  62. if user is not None:
  63. login(request, user, backend='django.contrib.auth.backends.ModelBackend')
  64. return user
  65. ### Django-Ninja-Provided Auth Methods
  66. class HeaderTokenAuth(APITokenAuthCheck, APIKeyHeader):
  67. """Allow authenticating by passing X-API-Key=xyz as a request header"""
  68. param_name = "X-ArchiveBox-API-Key"
  69. class BearerTokenAuth(APITokenAuthCheck, HttpBearer):
  70. """Allow authenticating by passing Bearer=xyz as a request header"""
  71. pass
  72. class QueryParamTokenAuth(APITokenAuthCheck, APIKeyQuery):
  73. """Allow authenticating by passing api_key=xyz as a GET/POST query parameter"""
  74. param_name = "api_key"
  75. class UsernameAndPasswordAuth(UserPassAuthCheck, HttpBasicAuth):
  76. """Allow authenticating by passing username & password via HTTP Basic Authentication (not recommended)"""
  77. pass
  78. ### Enabled Auth Methods
  79. API_AUTH_METHODS = [
  80. HeaderTokenAuth(),
  81. BearerTokenAuth(),
  82. QueryParamTokenAuth(),
  83. # django_auth_superuser, # django admin cookie auth, not secure to use with csrf=False
  84. UsernameAndPasswordAuth(),
  85. ]