mirror of
https://github.com/ArchiveBox/ArchiveBox
synced 2024-11-26 14:10:20 +00:00
107 lines
3.7 KiB
Python
107 lines
3.7 KiB
Python
__package__ = 'archivebox.api'
|
|
|
|
from typing import Optional
|
|
|
|
from django.http import HttpRequest
|
|
from django.contrib.auth import login
|
|
from django.contrib.auth import authenticate
|
|
from django.contrib.auth.models import AbstractBaseUser
|
|
|
|
from ninja.security import HttpBearer, APIKeyQuery, APIKeyHeader, HttpBasicAuth, django_auth_superuser
|
|
|
|
|
|
def auth_using_token(token, request: Optional[HttpRequest]=None) -> Optional[AbstractBaseUser]:
|
|
"""Given an API token string, check if a corresponding non-expired APIToken exists, and return its user"""
|
|
from api.models import APIToken # lazy import model to avoid loading it at urls.py import time
|
|
|
|
user = None
|
|
|
|
submitted_empty_form = token in ('string', '', None)
|
|
if submitted_empty_form:
|
|
user = request.user # see if user is authed via django session and use that as the default
|
|
else:
|
|
try:
|
|
token = APIToken.objects.get(token=token)
|
|
if token.is_valid():
|
|
user = token.user
|
|
except APIToken.DoesNotExist:
|
|
pass
|
|
|
|
if not user:
|
|
print('[❌] Failed to authenticate API user using API Key:', request)
|
|
|
|
return None
|
|
|
|
def auth_using_password(username, password, request: Optional[HttpRequest]=None) -> Optional[AbstractBaseUser]:
|
|
"""Given a username and password, check if they are valid and return the corresponding user"""
|
|
user = None
|
|
|
|
submitted_empty_form = (username, password) in (('string', 'string'), ('', ''), (None, None))
|
|
if submitted_empty_form:
|
|
user = request.user # see if user is authed via django session and use that as the default
|
|
else:
|
|
user = authenticate(
|
|
username=username,
|
|
password=password,
|
|
)
|
|
|
|
if not user:
|
|
print('[❌] Failed to authenticate API user using API Key:', request)
|
|
|
|
return user
|
|
|
|
|
|
### Base Auth Types
|
|
|
|
class APITokenAuthCheck:
|
|
"""The base class for authentication methods that use an api.models.APIToken"""
|
|
def authenticate(self, request: HttpRequest, key: Optional[str]=None) -> Optional[AbstractBaseUser]:
|
|
user = auth_using_token(
|
|
token=key,
|
|
request=request,
|
|
)
|
|
if user is not None:
|
|
login(request, user, backend='django.contrib.auth.backends.ModelBackend')
|
|
return user
|
|
|
|
class UserPassAuthCheck:
|
|
"""The base class for authentication methods that use a username & password"""
|
|
def authenticate(self, request: HttpRequest, username: Optional[str]=None, password: Optional[str]=None) -> Optional[AbstractBaseUser]:
|
|
user = auth_using_password(
|
|
username=username,
|
|
password=password,
|
|
request=request,
|
|
)
|
|
if user is not None:
|
|
login(request, user, backend='django.contrib.auth.backends.ModelBackend')
|
|
return user
|
|
|
|
|
|
### Django-Ninja-Provided Auth Methods
|
|
|
|
class HeaderTokenAuth(APITokenAuthCheck, APIKeyHeader):
|
|
"""Allow authenticating by passing X-API-Key=xyz as a request header"""
|
|
param_name = "X-ArchiveBox-API-Key"
|
|
|
|
class BearerTokenAuth(APITokenAuthCheck, HttpBearer):
|
|
"""Allow authenticating by passing Bearer=xyz as a request header"""
|
|
pass
|
|
|
|
class QueryParamTokenAuth(APITokenAuthCheck, APIKeyQuery):
|
|
"""Allow authenticating by passing api_key=xyz as a GET/POST query parameter"""
|
|
param_name = "api_key"
|
|
|
|
class UsernameAndPasswordAuth(UserPassAuthCheck, HttpBasicAuth):
|
|
"""Allow authenticating by passing username & password via HTTP Basic Authentication (not recommended)"""
|
|
pass
|
|
|
|
|
|
### Enabled Auth Methods
|
|
|
|
API_AUTH_METHODS = [
|
|
HeaderTokenAuth(),
|
|
BearerTokenAuth(),
|
|
QueryParamTokenAuth(),
|
|
django_auth_superuser,
|
|
UsernameAndPasswordAuth(),
|
|
]
|