update deps
This commit is contained in:
@@ -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()
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
@@ -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=""):
|
||||||
|
|||||||
Reference in New Issue
Block a user