1
+ __package__ = 'archivebox.api'
2
+
3
+ from typing import Optional
4
+
5
+ from django .http import HttpRequest
6
+ from django .contrib .auth import login
1
7
from django .contrib .auth import authenticate
2
- from ninja import Form , Router , Schema
3
- from ninja .security import HttpBearer
8
+ from django .contrib .auth .models import AbstractBaseUser
4
9
5
- from api . models import Token
10
+ from ninja . security import HttpBearer , APIKeyQuery , APIKeyHeader , HttpBasicAuth , django_auth_superuser
6
11
7
- router = Router ()
8
12
13
+ def auth_using_token (token , request : Optional [HttpRequest ]= None ) -> Optional [AbstractBaseUser ]:
14
+ """Given an API token string, check if a corresponding non-expired APIToken exists, and return its user"""
15
+ from api .models import APIToken # lazy import model to avoid loading it at urls.py import time
16
+
17
+ user = None
9
18
10
- class GlobalAuth (HttpBearer ):
11
- def authenticate (self , request , token ):
19
+ submitted_empty_form = token in ('string' , '' , None )
20
+ if submitted_empty_form :
21
+ user = request .user # see if user is authed via django session and use that as the default
22
+ else :
12
23
try :
13
- return Token .objects .get (token = token ).user
14
- except Token .DoesNotExist :
24
+ token = APIToken .objects .get (token = token )
25
+ if token .is_valid ():
26
+ user = token .user
27
+ except APIToken .DoesNotExist :
15
28
pass
16
29
30
+ if not user :
31
+ print ('[❌] Failed to authenticate API user using API Key:' , request )
17
32
18
- class AuthSchema (Schema ):
19
- email : str
20
- password : str
21
-
33
+ return None
22
34
23
- @ router . post ( "/authenticate" , auth = None ) # overriding global auth
24
- def get_token ( request , auth_data : AuthSchema ):
25
- user = authenticate ( username = auth_data . email , password = auth_data . password )
26
- if user :
27
- # Assuming a user can have multiple tokens and you want to create a new one every time
28
- new_token = Token . objects . create ( user = user )
29
- return { "token" : new_token . token , "expires" : new_token . expiry_as_iso8601 }
35
+ def auth_using_password ( username , password , request : Optional [ HttpRequest ] = None ) -> Optional [ AbstractBaseUser ]:
36
+ """Given a username and password, check if they are valid and return the corresponding user"""
37
+ user = None
38
+
39
+ submitted_empty_form = ( username , password ) in (( 'string' , 'string' ), ( '' , '' ), ( None , None ))
40
+ if submitted_empty_form :
41
+ user = request . user # see if user is authed via django session and use that as the default
30
42
else :
31
- return {"error" : "Invalid credentials" }
43
+ user = authenticate (
44
+ username = username ,
45
+ password = password ,
46
+ )
47
+
48
+ if not user :
49
+ print ('[❌] Failed to authenticate API user using API Key:' , request )
50
+
51
+ return user
52
+
53
+
54
+ ### Base Auth Types
55
+
56
+ class APITokenAuthCheck :
57
+ """The base class for authentication methods that use an api.models.APIToken"""
58
+ def authenticate (self , request : HttpRequest , key : Optional [str ]= None ) -> Optional [AbstractBaseUser ]:
59
+ user = auth_using_token (
60
+ token = key ,
61
+ request = request ,
62
+ )
63
+ if user is not None :
64
+ login (request , user , backend = 'django.contrib.auth.backends.ModelBackend' )
65
+ return user
66
+
67
+ class UserPassAuthCheck :
68
+ """The base class for authentication methods that use a username & password"""
69
+ def authenticate (self , request : HttpRequest , username : Optional [str ]= None , password : Optional [str ]= None ) -> Optional [AbstractBaseUser ]:
70
+ user = auth_using_password (
71
+ username = username ,
72
+ password = password ,
73
+ request = request ,
74
+ )
75
+ if user is not None :
76
+ login (request , user , backend = 'django.contrib.auth.backends.ModelBackend' )
77
+ return user
78
+
79
+
80
+ ### Django-Ninja-Provided Auth Methods
81
+
82
+ class UsernameAndPasswordAuth (UserPassAuthCheck , HttpBasicAuth ):
83
+ """Allow authenticating by passing username & password via HTTP Basic Authentication (not recommended)"""
84
+ pass
85
+
86
+ class QueryParamTokenAuth (APITokenAuthCheck , APIKeyQuery ):
87
+ """Allow authenticating by passing api_key=xyz as a GET/POST query parameter"""
88
+ param_name = "api_key"
89
+
90
+ class HeaderTokenAuth (APITokenAuthCheck , APIKeyHeader ):
91
+ """Allow authenticating by passing X-API-Key=xyz as a request header"""
92
+ param_name = "X-API-Key"
32
93
94
+ class BearerTokenAuth (APITokenAuthCheck , HttpBearer ):
95
+ """Allow authenticating by passing Bearer=xyz as a request header"""
96
+ pass
33
97
34
- class TokenValidationSchema (Schema ):
35
- token : str
36
98
99
+ ### Enabled Auth Methods
37
100
38
- @router .post ("/validate_token" , auth = None ) # No authentication required for this endpoint
39
- def validate_token (request , token_data : TokenValidationSchema ):
40
- try :
41
- # Attempt to authenticate using the provided token
42
- user = GlobalAuth ().authenticate (request , token_data .token )
43
- if user :
44
- return {"status" : "valid" }
45
- else :
46
- return {"status" : "invalid" }
47
- except Token .DoesNotExist :
48
- return {"status" : "invalid" }
101
+ API_AUTH_METHODS = [
102
+ QueryParamTokenAuth (),
103
+ HeaderTokenAuth (),
104
+ BearerTokenAuth (),
105
+ django_auth_superuser ,
106
+ UsernameAndPasswordAuth (),
107
+ ]
0 commit comments