1919from mod_api .middleware .error_handler import make_error_response
2020from mod_api .models .api_token import ApiToken
2121
22+ # Reused across every 401 response to keep the message consistent.
23+ _AUTH_FAILED_MSG = 'Bearer token is missing, expired, or invalid.'
24+
2225# These endpoints bypass auth entirely.
2326_PUBLIC_ENDPOINTS = frozenset ([
2427 'api.create_token' , # POST /auth/tokens (uses email/password body)
2528 'api.system_health' , # GET /system/health (uptime monitoring)
2629])
2730
2831
32+ def _unauthorized ():
33+ """Shorthand for a 401 response with the standard auth failure message."""
34+ return make_error_response ('unauthorized' , _AUTH_FAILED_MSG , http_status = 401 )
35+
36+
2937@mod_api .before_request
3038def authenticate_request ():
3139 """Validate Bearer token and attach user context to the request."""
@@ -36,38 +44,22 @@ def authenticate_request():
3644
3745 auth_header = request .headers .get ('Authorization' , '' )
3846 if not auth_header :
39- return make_error_response (
40- 'unauthorized' ,
41- 'Bearer token is missing, expired, or invalid.' ,
42- http_status = 401 ,
43- )
47+ return _unauthorized ()
4448
4549 parts = auth_header .split (' ' , 1 )
4650 if len (parts ) != 2 or parts [0 ] != 'Bearer' :
47- return make_error_response (
48- 'unauthorized' ,
49- 'Bearer token is missing, expired, or invalid.' ,
50- http_status = 401 ,
51- )
51+ return _unauthorized ()
5252
5353 token_value = parts [1 ].strip ()
5454 if not token_value or not token_value .startswith ('spci_' ):
55- return make_error_response (
56- 'unauthorized' ,
57- 'Bearer token is missing, expired, or invalid.' ,
58- http_status = 401 ,
59- )
55+ return _unauthorized ()
6056
6157 # Look up by prefix, then verify the full hash against each candidate.
6258 prefix = ApiToken .extract_prefix (token_value )
6359 candidates = ApiToken .query .filter_by (token_prefix = prefix ).all ()
6460
6561 if not candidates :
66- return make_error_response (
67- 'unauthorized' ,
68- 'Bearer token is missing, expired, or invalid.' ,
69- http_status = 401 ,
70- )
62+ return _unauthorized ()
7163
7264 matched_token = None
7365 for candidate in candidates :
@@ -76,18 +68,10 @@ def authenticate_request():
7668 break
7769
7870 if matched_token is None :
79- return make_error_response (
80- 'unauthorized' ,
81- 'Bearer token is missing, expired, or invalid.' ,
82- http_status = 401 ,
83- )
71+ return _unauthorized ()
8472
8573 if not matched_token .is_valid :
86- return make_error_response (
87- 'unauthorized' ,
88- 'Bearer token is missing, expired, or invalid.' ,
89- http_status = 401 ,
90- )
74+ return _unauthorized ()
9175
9276 g .api_token = matched_token
9377 g .api_user = matched_token .user
@@ -100,11 +84,7 @@ def decorator(f):
10084 def decorated_function (* args , ** kwargs ):
10185 token = getattr (g , 'api_token' , None )
10286 if token is None :
103- return make_error_response (
104- 'unauthorized' ,
105- 'Bearer token is missing, expired, or invalid.' ,
106- http_status = 401 ,
107- )
87+ return _unauthorized ()
10888 if not token .has_scope (scope ):
10989 return make_error_response (
11090 'forbidden' ,
@@ -127,11 +107,7 @@ def decorator(f):
127107 def decorated_function (* args , ** kwargs ):
128108 user = getattr (g , 'api_user' , None )
129109 if user is None :
130- return make_error_response (
131- 'unauthorized' ,
132- 'Bearer token is missing, expired, or invalid.' ,
133- http_status = 401 ,
134- )
110+ return _unauthorized ()
135111 if user .role .value not in roles :
136112 return make_error_response (
137113 'forbidden' ,
0 commit comments