working base site setup with gatekeeping working

This commit is contained in:
JakeBreath
2026-08-03 12:43:33 -05:00
parent 2764644d8b
commit 85e6241ad4
37 changed files with 1146 additions and 58 deletions
+33 -13
View File
@@ -64,26 +64,42 @@ def _is_api_request(request):
return False
def _get_bearer_token(request):
def _get_credentials(request):
"""Extract (username, token) from 'Authorization: Bearer <username> <token>'."""
auth = request.headers.get('Authorization', '')
if auth.lower().startswith('bearer '):
return auth.split(' ', 1)[1].strip()
explicit = request.headers.get('X-Auth-Token')
return explicit or None
if not auth.lower().startswith('bearer '):
return None, None
parts = auth.split(' ', 2) # ['Bearer', '<username>', '<token>']
if len(parts) < 3:
return None, None
return parts[1].strip(), parts[2].strip()
def _authenticate_token(request, token):
def _authenticate_token(request, username, token):
import hmac
from django.contrib.auth import get_user_model
from django.utils import timezone
from profiles.models import ApiToken
if not token:
if not username or not token:
return False
User = get_user_model()
# Resolve the user case-insensitively; prefer an exact-case match if it
# exists to avoid ambiguity.
user = (
User.objects.filter(username=username).first()
or User.objects.filter(username__iexact=username).first()
)
if user is None:
return False
prefix = token[:8]
# Matches on the prefix keyed index, then constant-time compares.
for candidate in ApiToken.objects.filter(key_prefix=prefix):
# Scope the lookup to the claimed user so a token never authenticates as
# anyone but its owner (prevents cross-authentication).
for candidate in ApiToken.objects.filter(user=user, key_prefix=prefix):
if hmac.compare_digest(candidate.token or '', token):
candidate.last_used = timezone.now()
candidate.save(update_fields=['last_used'])
@@ -117,11 +133,15 @@ class GateMiddleware:
if _is_whitelisted(request):
return self.get_response(request)
# API/media requests are allowed a bearer token to bypass the gate
# (non-browser downloaders such as the Fabric mod).
# API/media requests are allowed a bearer credential to bypass the
# gate (non-browser downloaders such as the Fabric mod). Credentials
# are 'Authorization: Bearer <username> <token>'. Token auth is
# CSRF-safe (header-based, not cookie-based), so mark the request as
# CSRF-processed to let it reach the view.
if _is_token_request(request):
token = _get_bearer_token(request)
if _authenticate_token(request, token):
username, token = _get_credentials(request)
if _authenticate_token(request, username, token):
request.csrf_processing_done = True
return self.get_response(request)
# fall through: reject with 401 for API/media.
+52 -24
View File
@@ -3,42 +3,70 @@ import platform
import os
from functools import lru_cache
@lru_cache(maxsize=1)
def get_os_info():
"""
Detect Linux distribution and return (fontawesome_class, display_name).
Falls back to generic Linux.
"""
system = platform.system()
if system != 'Linux':
return ('fa-brands fa-linux', system)
# Try to read /etc/os-release
os_release_path = '/etc/os-release'
# Map distro IDs (from /etc/os-release) to (fontawesome_class, display_name)
_DISTRO_MAPPING = {
'debian': ('fa-brands fa-debian', 'Debian'),
'ubuntu': ('fa-brands fa-ubuntu', 'Ubuntu'),
'fedora': ('fa-brands fa-fedora', 'Fedora'),
'rhel': ('fa-brands fa-redhat', 'RHEL'),
'centos': ('fa-brands fa-centos', 'CentOS'),
'opensuse': ('fa-brands fa-opensuse', 'openSUSE'),
'suse': ('fa-brands fa-suse', 'SUSE'),
'arch': ('fa-brands fa-arch-linux', 'Arch Linux'),
'raspbian': ('fa-brands fa-raspberry-pi', 'Raspberry Pi OS'),
}
# Map platform.system() values to (fontawesome_class, display_name)
_SYSTEM_MAPPING = {
'Windows': ('fa-brands fa-windows', 'Windows'),
'Darwin': ('fa-brands fa-apple', 'macOS'),
'FreeBSD': ('fa-brands fa-freebsd', 'FreeBSD'),
}
def _parse_os_release():
"""
Read /etc/os-release and return (distro_id, distro_name, id_like_list).
Any missing field falls back to empty values.
"""
distro_id = ''
distro_name = ''
id_like = ''
try:
with open(os_release_path, 'r') as f:
with open('/etc/os-release', 'r') as f:
for line in f:
if line.startswith('ID='):
distro_id = line.split('=', 1)[1].strip().strip('"').lower()
elif line.startswith('NAME='):
distro_name = line.split('=', 1)[1].strip().strip('"')
elif line.startswith('ID_LIKE='):
id_like = line.split('=', 1)[1].strip().strip('"')
except Exception:
pass
return distro_id, distro_name, id_like.split()
# Map IDs to FontAwesome icons and display names
mapping = {
'debian': ('fa-brands fa-debian', 'Debian'),
'ubuntu': ('fa-brands fa-ubuntu', 'Ubuntu'),
'fedora': ('fa-brands fa-fedora', 'Fedora'),
'rhel': ('fa-brands fa-redhat', 'RHEL'),
'centos': ('fa-brands fa-centos', 'CentOS'),
'opensuse': ('fa-brands fa-suse', 'openSUSE'),
'suse': ('fa-brands fa-suse', 'SUSE'),
}
if distro_id in mapping:
return mapping[distro_id]
@lru_cache(maxsize=1)
def get_os_info():
"""
Detect the operating system / Linux distribution and return
(fontawesome_class, display_name). Falls back to generic Linux.
"""
system = platform.system()
if system != 'Linux':
return _SYSTEM_MAPPING.get(system, ('fa-brands fa-linux', system))
distro_id, distro_name, id_like = _parse_os_release()
# Exact ID match first
if distro_id in _DISTRO_MAPPING:
return _DISTRO_MAPPING[distro_id]
# ID_LIKE fallback (e.g. linuxmint/pop/neon -> ubuntu, lmde/kali -> debian,
# rocky/almalinux -> rhel/fedora). Walk in order and take the first hit.
for parent in id_like:
if parent in _DISTRO_MAPPING:
return _DISTRO_MAPPING[parent]
return ('fa-brands fa-linux', distro_name or 'Linux')
+1 -1
View File
@@ -48,13 +48,13 @@ MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'common.middleware.GateMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'common.middleware.TimingMiddleware',
'common.middleware.GateMiddleware',
]
ROOT_URLCONF = 'common.urls'