update deps

This commit is contained in:
2026-05-05 07:23:59 -06:00
parent c0c5be9420
commit 0fd7dedea6
6 changed files with 786 additions and 711 deletions

View File

@@ -12,7 +12,7 @@ from django.utils import timezone
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.utils.timezone import now from django.utils.timezone import now
from django.views.decorators.csrf import csrf_exempt, ensure_csrf_cookie from django.views.decorators.csrf import csrf_exempt, ensure_csrf_cookie
from otpauth import OtpAuth from otpauth import TOTP
from options.options import SysOptions from options.options import SysOptions
from problem.models import Problem from problem.models import Problem
@@ -42,6 +42,22 @@ from ..serializers import (
from ..tasks import send_email_async from ..tasks import send_email_async
def _totp(token):
return TOTP(token.encode("utf-8"))
def _totp_uri(token, label, issuer):
return _totp(token).to_uri(label, issuer)
def _valid_totp(token, code):
try:
code = int(code)
except (TypeError, ValueError):
return False
return _totp(token).verify(code)
class UserProfileAPI(APIView): class UserProfileAPI(APIView):
@method_decorator(ensure_csrf_cookie) @method_decorator(ensure_csrf_cookie)
def get(self, request, **kwargs): def get(self, request, **kwargs):
@@ -142,9 +158,7 @@ class TwoFactorAuthAPI(APIView):
label = f"{SysOptions.website_name_shortcut}:{user.username}" label = f"{SysOptions.website_name_shortcut}:{user.username}"
image = qrcode.make( image = qrcode.make(
OtpAuth(token).to_uri( _totp_uri(token, label, SysOptions.website_name.replace(" ", ""))
"totp", label, SysOptions.website_name.replace(" ", "")
)
) )
return self.success(img2base64(image)) return self.success(img2base64(image))
@@ -156,7 +170,7 @@ class TwoFactorAuthAPI(APIView):
""" """
code = request.data["code"] code = request.data["code"]
user = request.user user = request.user
if OtpAuth(user.tfa_token).valid_totp(code): if _valid_totp(user.tfa_token, code):
user.two_factor_auth = True user.two_factor_auth = True
user.save() user.save()
return self.success("Succeeded") return self.success("Succeeded")
@@ -170,7 +184,7 @@ class TwoFactorAuthAPI(APIView):
user = request.user user = request.user
if not user.two_factor_auth: if not user.two_factor_auth:
return self.error("2FA is already turned off") return self.error("2FA is already turned off")
if OtpAuth(user.tfa_token).valid_totp(code): if _valid_totp(user.tfa_token, code):
user.two_factor_auth = False user.two_factor_auth = False
user.save() user.save()
return self.success("Succeeded") return self.success("Succeeded")
@@ -219,7 +233,7 @@ class UserLoginAPI(APIView):
if user.two_factor_auth and "tfa_code" not in data: if user.two_factor_auth and "tfa_code" not in data:
return self.error("tfa_required") return self.error("tfa_required")
if OtpAuth(user.tfa_token).valid_totp(data["tfa_code"]): if _valid_totp(user.tfa_token, data["tfa_code"]):
prev_login = user.last_login prev_login = user.last_login
auth.login(request, user) auth.login(request, user)
request.session["prev_login"] = ( request.session["prev_login"] = (
@@ -294,7 +308,7 @@ class UserChangeEmailAPI(APIView):
if user.two_factor_auth: if user.two_factor_auth:
if "tfa_code" not in data: if "tfa_code" not in data:
return self.error("tfa_required") return self.error("tfa_required")
if not OtpAuth(user.tfa_token).valid_totp(data["tfa_code"]): if not _valid_totp(user.tfa_token, data["tfa_code"]):
return self.error("Invalid two factor verification code") return self.error("Invalid two factor verification code")
data["new_email"] = data["new_email"].lower() data["new_email"] = data["new_email"].lower()
if User.objects.filter(email=data["new_email"]).exists(): if User.objects.filter(email=data["new_email"]).exists():
@@ -320,7 +334,7 @@ class UserChangePasswordAPI(APIView):
if user.two_factor_auth: if user.two_factor_auth:
if "tfa_code" not in data: if "tfa_code" not in data:
return self.error("tfa_required") return self.error("tfa_required")
if not OtpAuth(user.tfa_token).valid_totp(data["tfa_code"]): if not _valid_totp(user.tfa_token, data["tfa_code"]):
return self.error("Invalid two factor verification code") return self.error("Invalid two factor verification code")
user.set_password(data["new_password"]) user.set_password(data["new_password"])
user.save() user.save()

View File

@@ -1,76 +1,68 @@
annotated-types==0.7.0 annotated-types==0.7.0
anyio==4.12.0 anyio==4.13.0
asgiref==3.11.0 asgiref==3.11.1
attrs==25.4.0 attrs==26.1.0
autobahn==25.12.2 autobahn==25.12.2
automat==25.4.16 automat==25.4.16
cbor2==5.7.1 cbor2==6.0.1
certifi==2025.11.12 certifi==2026.4.22
cffi==2.0.0 cffi==2.0.0
channels==4.3.2 channels==4.3.2
channels-redis==4.3.0 channels-redis==4.3.0
charset-normalizer==3.4.4 charset-normalizer==3.4.7
colorama==0.4.6 ; sys_platform == 'win32'
constantly==23.10.4 constantly==23.10.4
coverage==6.5.0 cryptography==48.0.0
cryptography==46.0.3
daphne==4.2.1 daphne==4.2.1
distro==1.9.0 distro==1.9.0
django==6.0 django==6.0.4
django-cas-ng==5.0.1 django-cas-ng==5.1.1
django-dbconn-retry==0.1.8 django-dbconn-retry==0.3.1
django-dramatiq==0.13.0 django-dramatiq==0.15.0
django-redis==5.4.0 django-redis==6.0.0
djangorestframework==3.16.0 djangorestframework==3.17.1
dramatiq==1.17.0 dramatiq==2.1.0
entrypoints==0.4 gunicorn==26.0.0
envelopes==0.4
flake8==7.0.0
flake8-coding==1.3.2
flake8-quotes==3.3.2
gunicorn==22.0.0
h11==0.16.0 h11==0.16.0
httpcore==1.0.9 httpcore==1.0.9
httpx==0.28.1 httpx==0.28.1
hyperlink==21.0.0 hyperlink==21.0.0
idna==3.11 idna==3.13
incremental==24.11.0 incremental==24.11.0
jiter==0.12.0 jiter==0.14.0
jsonfield==3.1.0 lxml==6.1.0
lxml==6.0.2
mccabe==0.7.0
msgpack==1.1.2 msgpack==1.1.2
openai==2.14.0 openai==2.34.0
otpauth==1.0.1 otpauth==2.2.1
packaging==25.0 packaging==26.2
pillow==10.2.0 pillow==12.2.0
prometheus-client==0.23.1 psycopg==3.3.4
psycopg==3.2.9 psycopg-binary==3.3.4
psycopg-binary==3.2.9
py-ubjson==0.16.1 py-ubjson==0.16.1
pyasn1==0.6.1 pyasn1==0.6.3
pyasn1-modules==0.4.2 pyasn1-modules==0.4.2
pycodestyle==2.11.1 pycparser==3.0 ; implementation_name != 'PyPy'
pycparser==2.23 pydantic==2.13.3
pydantic==2.12.5 pydantic-core==2.46.3
pydantic-core==2.41.5 pyopenssl==26.2.0
pyflakes==3.2.0 python-cas==1.7.2
pyopenssl==25.3.0 python-dateutil==2.9.0.post0
python-cas==1.7.1
python-dateutil==2.8.2
qrcode==8.2 qrcode==8.2
raven==6.10.0 redis==7.4.0
redis==7.1.0 requests==2.33.1
requests==2.32.5 sentry-sdk==2.59.0
service-identity==24.2.0 service-identity==24.2.0
six==1.17.0 six==1.17.0
sniffio==1.3.1 sniffio==1.3.1
sqlparse==0.5.5 sqlparse==0.5.5
tqdm==4.67.1 tqdm==4.67.3
twisted==25.5.0 twisted==25.5.0
txaio==25.12.2 txaio==25.12.2
typing-extensions==4.15.0 typing-extensions==4.15.0
typing-inspection==0.4.2 typing-inspection==0.4.2
ujson==5.11.0 tzdata==2026.2 ; sys_platform == 'win32'
urllib3==2.6.2 u-msgpack-python==2.8.0 ; platform_python_implementation != 'CPython'
xlsxwriter==3.2.0 ujson==5.12.0
zope-interface==8.1.1 urllib3==2.6.3
xlsxwriter==3.2.9
zope-interface==8.4

View File

@@ -10,9 +10,14 @@ For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.8/ref/settings/ https://docs.djangoproject.com/en/1.8/ref/settings/
""" """
import logging
import os import os
import raven
from copy import deepcopy from copy import deepcopy
import sentry_sdk
from sentry_sdk.integrations.django import DjangoIntegration
from sentry_sdk.integrations.logging import LoggingIntegration
from utils.shortcuts import get_env from utils.shortcuts import get_env
production_env = get_env("OJ_ENV", "dev") == "production" production_env = get_env("OJ_ENV", "dev") == "production"
@@ -40,9 +45,6 @@ VENDOR_APPS = [
"django_dbconn_retry", "django_dbconn_retry",
] ]
if production_env:
VENDOR_APPS.append("raven.contrib.django.raven_compat")
LOCAL_APPS = [ LOCAL_APPS = [
"account", "account",
@@ -148,7 +150,19 @@ HITOKOTO_DIR = os.path.join(DATA_DIR, "hitokoto")
STATICFILES_DIRS = [os.path.join(DATA_DIR, "public")] STATICFILES_DIRS = [os.path.join(DATA_DIR, "public")]
LOGGING_HANDLERS = ["console", "sentry"] if production_env else ["console"] SENTRY_DSN = get_env("SENTRY_DSN")
if production_env and SENTRY_DSN:
sentry_sdk.init(
dsn=SENTRY_DSN,
integrations=[
DjangoIntegration(),
LoggingIntegration(level=logging.INFO, event_level=logging.ERROR),
],
send_default_pii=False,
)
LOGGING_HANDLERS = ["console"]
LOGGING = { LOGGING = {
"version": 1, "version": 1,
"disable_existing_loggers": False, "disable_existing_loggers": False,
@@ -164,11 +178,6 @@ LOGGING = {
"class": "logging.StreamHandler", "class": "logging.StreamHandler",
"formatter": "standard", "formatter": "standard",
}, },
"sentry": {
"level": "ERROR",
"class": "raven.contrib.django.raven_compat.handlers.SentryHandler",
"formatter": "standard",
},
}, },
"loggers": { "loggers": {
"django.request": { "django.request": {
@@ -256,10 +265,6 @@ DRAMATIQ_RESULT_BACKEND = {
"MIDDLEWARE_OPTIONS": {"result_ttl": None}, "MIDDLEWARE_OPTIONS": {"result_ttl": None},
} }
RAVEN_CONFIG = {
"dsn": "https://b200023b8aed4d708fb593c5e0a6ad3d:1fddaba168f84fcf97e0d549faaeaff0@sentry.io/263057"
}
IP_HEADER = "HTTP_X_REAL_IP" IP_HEADER = "HTTP_X_REAL_IP"
DEFAULT_AUTO_FIELD = "django.db.models.AutoField" DEFAULT_AUTO_FIELD = "django.db.models.AutoField"

View File

@@ -5,29 +5,26 @@ description = "Add your description here"
readme = "README.md" readme = "README.md"
requires-python = ">=3.12" requires-python = ">=3.12"
dependencies = [ dependencies = [
"channels>=4.2.0", "channels>=4.3.2,<5",
"channels-redis>=4.2.0", "channels-redis>=4.3.0,<5",
"daphne>=4.1.2", "daphne>=4.2.1,<5",
"django>=5.2.3", "django>=6.0.4,<6.1",
"django-cas-ng==5.0.1", "django-cas-ng>=5.1.1,<6",
"django-dbconn-retry==0.1.8", "django-dbconn-retry>=0.3.1,<0.4",
"django-dramatiq==0.13.0", "django-dramatiq>=0.15.0,<0.16",
"django-redis==5.4.0", "django-redis>=6.0.0,<7",
"djangorestframework==3.16.0", "djangorestframework>=3.17.1,<4",
"dramatiq==1.17.0", "dramatiq>=2.1.0,<3",
"entrypoints==0.4", "gunicorn>=26.0.0,<27",
"envelopes==0.4", "openai>=2.34.0,<3",
"gunicorn==22.0.0", "otpauth>=2.2.1,<3",
"jsonfield==3.1.0", "pillow>=12.2.0,<13",
"openai>=1.108.1", "psycopg>=3.3.4,<4",
"otpauth==1.0.1", "psycopg-binary>=3.3.4,<4",
"pillow==10.2.0", "python-dateutil>=2.9.0.post0,<3",
"psycopg==3.2.9", "qrcode>=8.2,<9",
"psycopg-binary==3.2.9", "sentry-sdk[django]>=2.0.0,<3",
"python-dateutil==2.8.2", "xlsxwriter>=3.2.9,<4",
"qrcode==8.2",
"raven==6.10.0",
"xlsxwriter==3.2.0",
] ]
[dependency-groups] [dependency-groups]

View File

@@ -2,10 +2,12 @@ import os
import random import random
import re import re
from base64 import b64encode from base64 import b64encode
from email.utils import formataddr
from io import BytesIO from io import BytesIO
from django.core.mail import EmailMultiAlternatives, get_connection
from django.utils.crypto import get_random_string from django.utils.crypto import get_random_string
from envelopes import Envelope from django.utils.html import strip_tags
def rand_str(length=32, type="lower_hex"): def rand_str(length=32, type="lower_hex"):
@@ -56,21 +58,29 @@ def datetime2str(value, format="iso-8601"):
return value return value
return value.strftime(format) return value.strftime(format)
def natural_sort_key(s, _nsre=re.compile(r"(\d+)")): def natural_sort_key(s, _nsre=re.compile(r"(\d+)")):
return [int(text) if text.isdigit() else text.lower() return [int(text) if text.isdigit() else text.lower()
for text in re.split(_nsre, s)] for text in re.split(_nsre, s)]
def send_email(smtp_config, from_name, to_email, to_name, subject, content): def send_email(smtp_config, from_name, to_email, to_name, subject, content):
envelope = Envelope(from_addr=(smtp_config["email"], from_name), connection = get_connection(
to_addr=(to_email, to_name), host=smtp_config["server"],
subject=subject, port=smtp_config["port"],
html_body=content) username=smtp_config["email"],
return envelope.send(smtp_config["server"], password=smtp_config["password"],
login=smtp_config["email"], use_tls=smtp_config["tls"],
password=smtp_config["password"], )
port=smtp_config["port"], message = EmailMultiAlternatives(
tls=smtp_config["tls"]) subject=subject,
body=strip_tags(content),
from_email=formataddr((from_name, smtp_config["email"])),
to=[formataddr((to_name, to_email))],
connection=connection,
)
message.attach_alternative(content, "text/html")
return message.send()
def get_env(name, default=""): def get_env(name, default=""):

1267
uv.lock generated

File diff suppressed because it is too large Load Diff