mirror of
https://github.com/bunkerity/bunkerweb
synced 2026-05-24 09:28:37 +00:00
Merge branch 'dev' of github.com:bunkerity/bunkerweb into dev
This commit is contained in:
commit
8d900437f6
19 changed files with 322 additions and 126 deletions
|
|
@ -13,6 +13,7 @@
|
|||
- [MISC] Fallback to default HTTPS certificate to prevent errors
|
||||
- [MISC] Various internal improvements in LUA code
|
||||
- [MISC] Updated Python Docker image to 3.12.1-alpine3.18 in Dockerfiles
|
||||
- [MISC] Switch gunicorn worker_class back to gevent in web UI
|
||||
- [DEPS] Updated ModSecurity to v3.0.11
|
||||
|
||||
## v1.5.4 - 2023/12/04
|
||||
|
|
|
|||
|
|
@ -425,9 +425,9 @@ pygments==2.17.2 \
|
|||
--hash=sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c \
|
||||
--hash=sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367
|
||||
# via mkdocs-material
|
||||
pymdown-extensions==10.6 \
|
||||
--hash=sha256:561eb3a5f3c3c2512952a4d6f5b311aa124b7147bd54a3ea0f36ce030c7e3dd9 \
|
||||
--hash=sha256:e4531379e0d74b329ff264217ef5b8b1a37bed3afe36f98001b74ecff52215c0
|
||||
pymdown-extensions==10.7 \
|
||||
--hash=sha256:6ca215bc57bc12bf32b414887a68b810637d039124ed9b2e5bd3325cbb2c050c \
|
||||
--hash=sha256:c0d64d5cf62566f59e6b2b690a4095c931107c250a8c8e1351c1de5f6b036deb
|
||||
# via mkdocs-material
|
||||
pyparsing==3.1.1 \
|
||||
--hash=sha256:32c7c0b711493c72ff18a981d24f28aaf9c1fb7ed5e9667c9e84e3db623bdbfb \
|
||||
|
|
|
|||
|
|
@ -9,11 +9,13 @@ from sys import path as sys_path
|
|||
from typing import Tuple
|
||||
|
||||
|
||||
if join(sep, "usr", "share", "bunkerweb", "utils") not in sys_path:
|
||||
sys_path.append(join(sep, "usr", "share", "bunkerweb", "utils"))
|
||||
for deps_path in [join(sep, "usr", "share", "bunkerweb", *paths) for paths in (("utils",), ("db",))]:
|
||||
if deps_path not in sys_path:
|
||||
sys_path.append(deps_path)
|
||||
|
||||
from API import API # type: ignore
|
||||
from ApiCaller import ApiCaller # type: ignore
|
||||
from Database import Database # type: ignore
|
||||
from logger import setup_logger # type: ignore
|
||||
|
||||
|
||||
|
|
@ -40,21 +42,13 @@ def format_remaining_time(seconds):
|
|||
class CLI(ApiCaller):
|
||||
def __init__(self):
|
||||
self.__logger = setup_logger("CLI", getenv("LOG_LEVEL", "INFO"))
|
||||
db_path = Path(sep, "usr", "share", "bunkerweb", "db")
|
||||
variables_path = Path(sep, "etc", "nginx", "variables.env")
|
||||
self.__variables = {}
|
||||
if variables_path.is_file():
|
||||
self.__variables = dotenv_values(variables_path)
|
||||
|
||||
if not db_path.is_dir():
|
||||
self.__variables = dotenv_values(join(sep, "etc", "nginx", "variables.env"))
|
||||
else:
|
||||
if str(db_path) not in sys_path:
|
||||
sys_path.append(str(db_path))
|
||||
|
||||
from Database import Database # type: ignore
|
||||
|
||||
db = Database(
|
||||
self.__logger,
|
||||
sqlalchemy_string=getenv("DATABASE_URI", None),
|
||||
)
|
||||
self.__variables = db.get_config()
|
||||
db = Database(self.__logger, sqlalchemy_string=self.__variables.get("DATABASE_URI", None))
|
||||
self.__variables = db.get_config()
|
||||
|
||||
self.__integration = self.__detect_integration()
|
||||
self.__use_redis = self.__variables.get("USE_REDIS", "no") == "yes"
|
||||
|
|
@ -102,12 +96,7 @@ class CLI(ApiCaller):
|
|||
self.__logger.error("USE_REDIS is set to yes but REDIS_HOST is not set, disabling redis")
|
||||
self.__use_redis = False
|
||||
|
||||
if not db_path.is_dir() or self.__integration not in (
|
||||
"kubernetes",
|
||||
"swarm",
|
||||
"autoconf",
|
||||
):
|
||||
# Docker & Linux case
|
||||
if self.__integration == "linux":
|
||||
super().__init__(
|
||||
[
|
||||
API(
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ reuse_port = True
|
|||
pidfile = join(sep, "var", "run", "bunkerweb", "ui.pid")
|
||||
worker_tmp_dir = join(sep, "dev", "shm")
|
||||
tmp_upload_dir = join(sep, "var", "tmp", "bunkerweb", "ui")
|
||||
worker_class = "gthread"
|
||||
worker_class = "gevent"
|
||||
threads = 1
|
||||
workers = 1
|
||||
graceful_timeout = 0
|
||||
|
|
|
|||
125
src/ui/main.py
125
src/ui/main.py
|
|
@ -18,28 +18,17 @@ for deps_path in [join(sep, "usr", "share", "bunkerweb", *paths) for paths in ((
|
|||
if deps_path not in sys_path:
|
||||
sys_path.append(deps_path)
|
||||
|
||||
from gevent import monkey
|
||||
|
||||
monkey.patch_all()
|
||||
|
||||
from bs4 import BeautifulSoup
|
||||
from copy import deepcopy
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from dateutil.parser import parse as dateutil_parse
|
||||
from docker import DockerClient
|
||||
from docker.errors import (
|
||||
NotFound as docker_NotFound,
|
||||
APIError as docker_APIError,
|
||||
DockerException,
|
||||
)
|
||||
from flask import (
|
||||
Flask,
|
||||
Response,
|
||||
flash,
|
||||
jsonify,
|
||||
redirect,
|
||||
render_template,
|
||||
request,
|
||||
send_file,
|
||||
session,
|
||||
url_for,
|
||||
)
|
||||
from docker.errors import NotFound as docker_NotFound, APIError as docker_APIError, DockerException
|
||||
from flask import Flask, Response, flash, jsonify, redirect, render_template, request, send_file, session, url_for
|
||||
from flask_login import current_user, LoginManager, login_required, login_user, logout_user
|
||||
from flask_wtf.csrf import CSRFProtect, CSRFError, generate_csrf
|
||||
from glob import glob
|
||||
|
|
@ -67,7 +56,7 @@ from src.Instances import Instances
|
|||
from src.ConfigFiles import ConfigFiles
|
||||
from src.Config import Config
|
||||
from src.ReverseProxied import ReverseProxied
|
||||
from src.User import User
|
||||
from src.User import AnonymousUser, User
|
||||
|
||||
from utils import check_settings, get_b64encoded_qr_image, path_to_dict
|
||||
from Database import Database # type: ignore
|
||||
|
|
@ -119,6 +108,7 @@ app.logger.setLevel(gunicorn_logger.level)
|
|||
login_manager = LoginManager()
|
||||
login_manager.init_app(app)
|
||||
login_manager.login_view = "login"
|
||||
login_manager.anonymous_user = AnonymousUser
|
||||
PLUGIN_KEYS = ["id", "name", "description", "version", "stream", "settings"]
|
||||
|
||||
INTEGRATION = "Linux"
|
||||
|
|
@ -177,7 +167,7 @@ if USER:
|
|||
updated = True
|
||||
|
||||
if updated:
|
||||
ret = db.update_ui_user(USER.get_id(), USER.password_hash, USER.is_two_factor_enabled, app.config["USER"].secret_token)
|
||||
ret = db.update_ui_user(USER.get_id(), USER.password_hash, USER.is_two_factor_enabled, USER.secret_token)
|
||||
if ret:
|
||||
app.logger.error(f"Couldn't update the admin user in the database: {ret}")
|
||||
stop(1)
|
||||
|
|
@ -220,6 +210,7 @@ try:
|
|||
LAST_RELOAD=0,
|
||||
TO_FLASH=[],
|
||||
DARK_MODE=False,
|
||||
CURRENT_TOTP_TOKEN=None,
|
||||
)
|
||||
except FileNotFoundError as e:
|
||||
app.logger.error(repr(e), e.filename)
|
||||
|
|
@ -318,7 +309,12 @@ def set_csp_header(response):
|
|||
|
||||
@login_manager.user_loader
|
||||
def load_user(user_id):
|
||||
return app.config["USER"] if app.config["USER"] and user_id == app.config["USER"].get_id() else None
|
||||
db_user = db.get_ui_user()
|
||||
if not db_user:
|
||||
app.logger.warning("Couldn't get the admin user from the database.")
|
||||
return None
|
||||
user = User(**db_user)
|
||||
return user if user_id == user.get_id() else None
|
||||
|
||||
|
||||
@app.errorhandler(CSRFError)
|
||||
|
|
@ -334,14 +330,14 @@ def handle_csrf_error(_):
|
|||
flash("Wrong CSRF token !", "error")
|
||||
if not app.config["USER"]:
|
||||
return render_template("setup.html"), 403
|
||||
return render_template("login.html", is_totp=app.config["USER"].is_two_factor_enabled), 403
|
||||
return render_template("login.html", is_totp=current_user.is_two_factor_enabled), 403
|
||||
|
||||
|
||||
@app.before_request
|
||||
def before_request():
|
||||
if app.config["USER"] and current_user.is_authenticated:
|
||||
if current_user.is_authenticated:
|
||||
passed = True
|
||||
if not session.get("totp_validated", False) and app.config["USER"].is_two_factor_enabled and "/totp" not in request.path and not request.path.startswith(("/css", "/images", "/js", "/json", "/webfonts")):
|
||||
if not session.get("totp_validated", False) and current_user.is_two_factor_enabled and "/totp" not in request.path and not request.path.startswith(("/css", "/images", "/js", "/json", "/webfonts")):
|
||||
return redirect(url_for("totp", next=request.form.get("next")))
|
||||
elif session.get("ip") != request.remote_addr:
|
||||
passed = False
|
||||
|
|
@ -481,14 +477,14 @@ def totp():
|
|||
flash("Missing token parameter.", "error")
|
||||
return redirect(url_for("totp"))
|
||||
|
||||
if not app.config["USER"].check_otp(request.form["totp_token"]):
|
||||
if not current_user.check_otp(request.form["totp_token"]):
|
||||
flash("The token is invalid.", "error")
|
||||
return redirect(url_for("totp"))
|
||||
|
||||
session["totp_validated"] = True
|
||||
redirect(url_for("loading", next=request.form.get("next") or url_for("home")))
|
||||
|
||||
if app.config["USER"] and (not app.config["USER"].is_two_factor_enabled or session.get("totp_validated", False)):
|
||||
if not current_user.is_two_factor_enabled or session.get("totp_validated", False):
|
||||
return redirect(url_for("home"))
|
||||
|
||||
return render_template("totp.html", dark_mode=app.config["DARK_MODE"])
|
||||
|
|
@ -565,7 +561,7 @@ def profile():
|
|||
error = False
|
||||
|
||||
if "curr_password" in request.form:
|
||||
if not app.config["USER"].check_password(request.form["curr_password"]):
|
||||
if not current_user.check_password(request.form["curr_password"]):
|
||||
flash("The current password is incorrect.", "error")
|
||||
error = True
|
||||
|
||||
|
|
@ -594,12 +590,12 @@ def profile():
|
|||
if error:
|
||||
return redirect(url_for("profile"))
|
||||
|
||||
app.config["USER"] = User(
|
||||
request.form.get("admin_username") or app.config["USER"].get_id(),
|
||||
user = User(
|
||||
request.form.get("admin_username") or current_user.get_id(),
|
||||
request.form.get("admin_password") or request.form["curr_password"],
|
||||
is_two_factor_enabled=app.config["USER"].is_two_factor_enabled,
|
||||
secret_token=app.config["USER"].secret_token,
|
||||
method=app.config["USER"].method,
|
||||
is_two_factor_enabled=current_user.is_two_factor_enabled,
|
||||
secret_token=current_user.secret_token,
|
||||
method=current_user.method,
|
||||
)
|
||||
|
||||
session.clear()
|
||||
|
|
@ -609,57 +605,54 @@ def profile():
|
|||
flash("Missing totp_token parameter.", "error")
|
||||
return redirect(url_for("profile"))
|
||||
|
||||
if not app.config["USER"].check_password(request.form.get("totp_password", "")):
|
||||
if not current_user.check_password(request.form.get("totp_password", "")):
|
||||
flash("The current password is incorrect.", "error")
|
||||
error = True
|
||||
|
||||
if not app.config["USER"].check_otp(request.form["totp_token"]):
|
||||
if not current_user.check_otp(request.form["totp_token"], secret=app.config["CURRENT_TOTP_TOKEN"]):
|
||||
flash("The token is invalid.", "error")
|
||||
error = True
|
||||
|
||||
app.logger.warning(request.form["totp_password"])
|
||||
|
||||
if error:
|
||||
return redirect(url_for("profile"))
|
||||
|
||||
app.logger.warning("TOTP validated")
|
||||
session["totp_validated"] = not current_user.is_two_factor_enabled
|
||||
|
||||
session["totp_validated"] = not app.config["USER"].is_two_factor_enabled
|
||||
|
||||
if app.config["USER"].is_two_factor_enabled:
|
||||
app.config["USER"].secret_token = None
|
||||
|
||||
app.config["USER"].is_two_factor_enabled = session["totp_validated"]
|
||||
|
||||
app.logger.warning(app.config["USER"])
|
||||
user = User(
|
||||
current_user.get_id(),
|
||||
request.form["totp_password"],
|
||||
is_two_factor_enabled=session["totp_validated"],
|
||||
secret_token=None if current_user.is_two_factor_enabled else app.config["CURRENT_TOTP_TOKEN"],
|
||||
method=current_user.method,
|
||||
)
|
||||
app.config["CURRENT_TOTP_TOKEN"] = None
|
||||
else:
|
||||
flash("Missing form data.", "error")
|
||||
return redirect(url_for("profile"))
|
||||
|
||||
ret = db.update_ui_user(
|
||||
app.config["USER"].get_id(),
|
||||
app.config["USER"].password_hash,
|
||||
app.config["USER"].is_two_factor_enabled,
|
||||
app.config["USER"].secret_token if app.config["USER"].is_two_factor_enabled else None,
|
||||
user.get_id(),
|
||||
user.password_hash,
|
||||
user.is_two_factor_enabled,
|
||||
user.secret_token if user.is_two_factor_enabled else None,
|
||||
)
|
||||
if ret:
|
||||
app.logger.error(f"Couldn't update the admin user in the database: {ret}")
|
||||
flash(f"Couldn't update the admin user in the database: {ret}", "error")
|
||||
return redirect(url_for("profile"))
|
||||
|
||||
app.logger.warning("User updated")
|
||||
|
||||
return redirect(url_for("profile"))
|
||||
|
||||
secret_token = ""
|
||||
totp_qr_image = ""
|
||||
|
||||
if not app.config["USER"].is_two_factor_enabled:
|
||||
app.config["USER"].refresh_totp()
|
||||
secret_token = app.config["USER"].secret_token
|
||||
totp_qr_image = get_b64encoded_qr_image(app.config["USER"].get_authentication_setup_uri())
|
||||
if not current_user.is_two_factor_enabled:
|
||||
current_user.refresh_totp()
|
||||
secret_token = current_user.secret_token
|
||||
totp_qr_image = get_b64encoded_qr_image(current_user.get_authentication_setup_uri())
|
||||
app.config["CURRENT_TOTP_TOKEN"] = secret_token
|
||||
|
||||
return render_template("profile.html", username=app.config["USER"].get_id(), is_totp=app.config["USER"].is_two_factor_enabled, secret_token=secret_token, totp_qr_image=totp_qr_image, dark_mode=app.config["DARK_MODE"])
|
||||
return render_template("profile.html", username=current_user.get_id(), is_totp=current_user.is_two_factor_enabled, secret_token=secret_token, totp_qr_image=totp_qr_image, dark_mode=app.config["DARK_MODE"])
|
||||
|
||||
|
||||
@app.route("/instances", methods=["GET", "POST"])
|
||||
|
|
@ -1658,15 +1651,26 @@ def jobs_download():
|
|||
|
||||
@app.route("/login", methods=["GET", "POST"])
|
||||
def login():
|
||||
if not app.config["USER"]:
|
||||
return redirect(url_for("setup"))
|
||||
elif current_user.is_authenticated: # type: ignore
|
||||
return redirect(url_for("home"))
|
||||
|
||||
fail = False
|
||||
if request.method == "POST" and "username" in request.form and "password" in request.form:
|
||||
app.logger.warning(f"Login attempt from {request.remote_addr} with username \"{request.form['username']}\"")
|
||||
if app.config["USER"].get_id() == request.form["username"] and app.config["USER"].check_password(request.form["password"]):
|
||||
db_user = db.get_ui_user()
|
||||
if not db_user:
|
||||
app.logger.error("Couldn't get user from database")
|
||||
stop(1)
|
||||
user = User(**db_user)
|
||||
|
||||
if user.get_id() == request.form["username"] and user.check_password(request.form["password"]):
|
||||
# log the user in
|
||||
session["ip"] = request.remote_addr
|
||||
session["user_agent"] = request.headers.get("User-Agent")
|
||||
session["totp_validated"] = False
|
||||
login_user(app.config["USER"], duration=timedelta(hours=1))
|
||||
login_user(user, duration=timedelta(hours=1))
|
||||
|
||||
# redirect him to the page he originally wanted or to the home page
|
||||
return redirect(url_for("loading", next=request.form.get("next") or url_for("home")))
|
||||
|
|
@ -1674,13 +1678,8 @@ def login():
|
|||
flash("Invalid username or password", "error")
|
||||
fail = True
|
||||
|
||||
if not app.config["USER"]:
|
||||
return redirect(url_for("setup"))
|
||||
elif current_user.is_authenticated: # type: ignore
|
||||
return redirect(url_for("home"))
|
||||
|
||||
kwargs = {
|
||||
"is_totp": app.config["USER"].is_two_factor_enabled,
|
||||
"is_totp": current_user.is_two_factor_enabled,
|
||||
} | ({"error": "Invalid username or password"} if fail else {})
|
||||
|
||||
return render_template("login.html", **kwargs), 401 if fail else 200
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ beautifulsoup4==4.12.2
|
|||
Flask==3.0.0
|
||||
Flask-Login==0.6.3
|
||||
Flask_WTF==1.2.1
|
||||
gunicorn[gthread]==21.2.0
|
||||
gunicorn[gevent]==21.2.0
|
||||
importlib-metadata==7.0.1
|
||||
pyotp==2.9.0
|
||||
python_dateutil==2.8.2
|
||||
|
|
|
|||
|
|
@ -60,6 +60,108 @@ flask-wtf==1.2.1 \
|
|||
--hash=sha256:8bb269eb9bb46b87e7c8233d7e7debdf1f8b74bf90cc1789988c29b37a97b695 \
|
||||
--hash=sha256:fa6793f2fb7e812e0fe9743b282118e581fb1b6c45d414b8af05e659bd653287
|
||||
# via -r requirements.in
|
||||
gevent==23.9.1 \
|
||||
--hash=sha256:272cffdf535978d59c38ed837916dfd2b5d193be1e9e5dcc60a5f4d5025dd98a \
|
||||
--hash=sha256:2c7b5c9912378e5f5ccf180d1fdb1e83f42b71823483066eddbe10ef1a2fcaa2 \
|
||||
--hash=sha256:36a549d632c14684bcbbd3014a6ce2666c5f2a500f34d58d32df6c9ea38b6535 \
|
||||
--hash=sha256:4368f341a5f51611411ec3fc62426f52ac3d6d42eaee9ed0f9eebe715c80184e \
|
||||
--hash=sha256:43daf68496c03a35287b8b617f9f91e0e7c0d042aebcc060cadc3f049aadd653 \
|
||||
--hash=sha256:455e5ee8103f722b503fa45dedb04f3ffdec978c1524647f8ba72b4f08490af1 \
|
||||
--hash=sha256:45792c45d60f6ce3d19651d7fde0bc13e01b56bb4db60d3f32ab7d9ec467374c \
|
||||
--hash=sha256:4e24c2af9638d6c989caffc691a039d7c7022a31c0363da367c0d32ceb4a0648 \
|
||||
--hash=sha256:52b4abf28e837f1865a9bdeef58ff6afd07d1d888b70b6804557e7908032e599 \
|
||||
--hash=sha256:52e9f12cd1cda96603ce6b113d934f1aafb873e2c13182cf8e86d2c5c41982ea \
|
||||
--hash=sha256:5f3c781c84794926d853d6fb58554dc0dcc800ba25c41d42f6959c344b4db5a6 \
|
||||
--hash=sha256:62d121344f7465e3739989ad6b91f53a6ca9110518231553fe5846dbe1b4518f \
|
||||
--hash=sha256:65883ac026731ac112184680d1f0f1e39fa6f4389fd1fc0bf46cc1388e2599f9 \
|
||||
--hash=sha256:707904027d7130ff3e59ea387dddceedb133cc742b00b3ffe696d567147a9c9e \
|
||||
--hash=sha256:72c002235390d46f94938a96920d8856d4ffd9ddf62a303a0d7c118894097e34 \
|
||||
--hash=sha256:7532c17bc6c1cbac265e751b95000961715adef35a25d2b0b1813aa7263fb397 \
|
||||
--hash=sha256:78eebaf5e73ff91d34df48f4e35581ab4c84e22dd5338ef32714264063c57507 \
|
||||
--hash=sha256:7c1abc6f25f475adc33e5fc2dbcc26a732608ac5375d0d306228738a9ae14d3b \
|
||||
--hash=sha256:7c28e38dcde327c217fdafb9d5d17d3e772f636f35df15ffae2d933a5587addd \
|
||||
--hash=sha256:7ccf0fd378257cb77d91c116e15c99e533374a8153632c48a3ecae7f7f4f09fe \
|
||||
--hash=sha256:921dda1c0b84e3d3b1778efa362d61ed29e2b215b90f81d498eb4d8eafcd0b7a \
|
||||
--hash=sha256:a2898b7048771917d85a1d548fd378e8a7b2ca963db8e17c6d90c76b495e0e2b \
|
||||
--hash=sha256:a3c5e9b1f766a7a64833334a18539a362fb563f6c4682f9634dea72cbe24f771 \
|
||||
--hash=sha256:ada07076b380918829250201df1d016bdafb3acf352f35e5693b59dceee8dd2e \
|
||||
--hash=sha256:b101086f109168b23fa3586fccd1133494bdb97f86920a24dc0b23984dc30b69 \
|
||||
--hash=sha256:bf456bd6b992eb0e1e869e2fd0caf817f0253e55ca7977fd0e72d0336a8c1c6a \
|
||||
--hash=sha256:bf7af500da05363e66f122896012acb6e101a552682f2352b618e541c941a011 \
|
||||
--hash=sha256:c3e5d2fa532e4d3450595244de8ccf51f5721a05088813c1abd93ad274fe15e7 \
|
||||
--hash=sha256:c84d34256c243b0a53d4335ef0bc76c735873986d478c53073861a92566a8d71 \
|
||||
--hash=sha256:d163d59f1be5a4c4efcdd13c2177baaf24aadf721fdf2e1af9ee54a998d160f5 \
|
||||
--hash=sha256:d57737860bfc332b9b5aa438963986afe90f49645f6e053140cfa0fa1bdae1ae \
|
||||
--hash=sha256:dbb22a9bbd6a13e925815ce70b940d1578dbe5d4013f20d23e8a11eddf8d14a7 \
|
||||
--hash=sha256:dcb8612787a7f4626aa881ff15ff25439561a429f5b303048f0fca8a1c781c39 \
|
||||
--hash=sha256:dd6c32ab977ecf7c7b8c2611ed95fa4aaebd69b74bf08f4b4960ad516861517d \
|
||||
--hash=sha256:de350fde10efa87ea60d742901e1053eb2127ebd8b59a7d3b90597eb4e586599 \
|
||||
--hash=sha256:e1ead6863e596a8cc2a03e26a7a0981f84b6b3e956101135ff6d02df4d9a6b07 \
|
||||
--hash=sha256:ed7a048d3e526a5c1d55c44cb3bc06cfdc1947d06d45006cc4cf60dedc628904 \
|
||||
--hash=sha256:f632487c87866094546a74eefbca2c74c1d03638b715b6feb12e80120960185a \
|
||||
--hash=sha256:fae8d5b5b8fa2a8f63b39f5447168b02db10c888a3e387ed7af2bd1b8612e543 \
|
||||
--hash=sha256:fde6402c5432b835fbb7698f1c7f2809c8d6b2bd9d047ac1f5a7c1d5aa569303
|
||||
# via gunicorn
|
||||
greenlet==3.0.3 \
|
||||
--hash=sha256:01bc7ea167cf943b4c802068e178bbf70ae2e8c080467070d01bfa02f337ee67 \
|
||||
--hash=sha256:0448abc479fab28b00cb472d278828b3ccca164531daab4e970a0458786055d6 \
|
||||
--hash=sha256:086152f8fbc5955df88382e8a75984e2bb1c892ad2e3c80a2508954e52295257 \
|
||||
--hash=sha256:098d86f528c855ead3479afe84b49242e174ed262456c342d70fc7f972bc13c4 \
|
||||
--hash=sha256:149e94a2dd82d19838fe4b2259f1b6b9957d5ba1b25640d2380bea9c5df37676 \
|
||||
--hash=sha256:1551a8195c0d4a68fac7a4325efac0d541b48def35feb49d803674ac32582f61 \
|
||||
--hash=sha256:15d79dd26056573940fcb8c7413d84118086f2ec1a8acdfa854631084393efcc \
|
||||
--hash=sha256:1996cb9306c8595335bb157d133daf5cf9f693ef413e7673cb07e3e5871379ca \
|
||||
--hash=sha256:1a7191e42732df52cb5f39d3527217e7ab73cae2cb3694d241e18f53d84ea9a7 \
|
||||
--hash=sha256:1ea188d4f49089fc6fb283845ab18a2518d279c7cd9da1065d7a84e991748728 \
|
||||
--hash=sha256:1f672519db1796ca0d8753f9e78ec02355e862d0998193038c7073045899f305 \
|
||||
--hash=sha256:2516a9957eed41dd8f1ec0c604f1cdc86758b587d964668b5b196a9db5bfcde6 \
|
||||
--hash=sha256:2797aa5aedac23af156bbb5a6aa2cd3427ada2972c828244eb7d1b9255846379 \
|
||||
--hash=sha256:2dd6e660effd852586b6a8478a1d244b8dc90ab5b1321751d2ea15deb49ed414 \
|
||||
--hash=sha256:3ddc0f794e6ad661e321caa8d2f0a55ce01213c74722587256fb6566049a8b04 \
|
||||
--hash=sha256:3ed7fb269f15dc662787f4119ec300ad0702fa1b19d2135a37c2c4de6fadfd4a \
|
||||
--hash=sha256:419b386f84949bf0e7c73e6032e3457b82a787c1ab4a0e43732898a761cc9dbf \
|
||||
--hash=sha256:43374442353259554ce33599da8b692d5aa96f8976d567d4badf263371fbe491 \
|
||||
--hash=sha256:52f59dd9c96ad2fc0d5724107444f76eb20aaccb675bf825df6435acb7703559 \
|
||||
--hash=sha256:57e8974f23e47dac22b83436bdcf23080ade568ce77df33159e019d161ce1d1e \
|
||||
--hash=sha256:5b51e85cb5ceda94e79d019ed36b35386e8c37d22f07d6a751cb659b180d5274 \
|
||||
--hash=sha256:649dde7de1a5eceb258f9cb00bdf50e978c9db1b996964cd80703614c86495eb \
|
||||
--hash=sha256:64d7675ad83578e3fc149b617a444fab8efdafc9385471f868eb5ff83e446b8b \
|
||||
--hash=sha256:68834da854554926fbedd38c76e60c4a2e3198c6fbed520b106a8986445caaf9 \
|
||||
--hash=sha256:6b66c9c1e7ccabad3a7d037b2bcb740122a7b17a53734b7d72a344ce39882a1b \
|
||||
--hash=sha256:70fb482fdf2c707765ab5f0b6655e9cfcf3780d8d87355a063547b41177599be \
|
||||
--hash=sha256:7170375bcc99f1a2fbd9c306f5be8764eaf3ac6b5cb968862cad4c7057756506 \
|
||||
--hash=sha256:73a411ef564e0e097dbe7e866bb2dda0f027e072b04da387282b02c308807405 \
|
||||
--hash=sha256:77457465d89b8263bca14759d7c1684df840b6811b2499838cc5b040a8b5b113 \
|
||||
--hash=sha256:7f362975f2d179f9e26928c5b517524e89dd48530a0202570d55ad6ca5d8a56f \
|
||||
--hash=sha256:81bb9c6d52e8321f09c3d165b2a78c680506d9af285bfccbad9fb7ad5a5da3e5 \
|
||||
--hash=sha256:881b7db1ebff4ba09aaaeae6aa491daeb226c8150fc20e836ad00041bcb11230 \
|
||||
--hash=sha256:894393ce10ceac937e56ec00bb71c4c2f8209ad516e96033e4b3b1de270e200d \
|
||||
--hash=sha256:99bf650dc5d69546e076f413a87481ee1d2d09aaaaaca058c9251b6d8c14783f \
|
||||
--hash=sha256:9da2bd29ed9e4f15955dd1595ad7bc9320308a3b766ef7f837e23ad4b4aac31a \
|
||||
--hash=sha256:afaff6cf5200befd5cec055b07d1c0a5a06c040fe5ad148abcd11ba6ab9b114e \
|
||||
--hash=sha256:b1b5667cced97081bf57b8fa1d6bfca67814b0afd38208d52538316e9422fc61 \
|
||||
--hash=sha256:b37eef18ea55f2ffd8f00ff8fe7c8d3818abd3e25fb73fae2ca3b672e333a7a6 \
|
||||
--hash=sha256:b542be2440edc2d48547b5923c408cbe0fc94afb9f18741faa6ae970dbcb9b6d \
|
||||
--hash=sha256:b7dcbe92cc99f08c8dd11f930de4d99ef756c3591a5377d1d9cd7dd5e896da71 \
|
||||
--hash=sha256:b7f009caad047246ed379e1c4dbcb8b020f0a390667ea74d2387be2998f58a22 \
|
||||
--hash=sha256:bba5387a6975598857d86de9eac14210a49d554a77eb8261cc68b7d082f78ce2 \
|
||||
--hash=sha256:c5e1536de2aad7bf62e27baf79225d0d64360d4168cf2e6becb91baf1ed074f3 \
|
||||
--hash=sha256:c5ee858cfe08f34712f548c3c363e807e7186f03ad7a5039ebadb29e8c6be067 \
|
||||
--hash=sha256:c9db1c18f0eaad2f804728c67d6c610778456e3e1cc4ab4bbd5eeb8e6053c6fc \
|
||||
--hash=sha256:d353cadd6083fdb056bb46ed07e4340b0869c305c8ca54ef9da3421acbdf6881 \
|
||||
--hash=sha256:d46677c85c5ba00a9cb6f7a00b2bfa6f812192d2c9f7d9c4f6a55b60216712f3 \
|
||||
--hash=sha256:d4d1ac74f5c0c0524e4a24335350edad7e5f03b9532da7ea4d3c54d527784f2e \
|
||||
--hash=sha256:d73a9fe764d77f87f8ec26a0c85144d6a951a6c438dfe50487df5595c6373eac \
|
||||
--hash=sha256:da70d4d51c8b306bb7a031d5cff6cc25ad253affe89b70352af5f1cb68e74b53 \
|
||||
--hash=sha256:daf3cb43b7cf2ba96d614252ce1684c1bccee6b2183a01328c98d36fcd7d5cb0 \
|
||||
--hash=sha256:dca1e2f3ca00b84a396bc1bce13dd21f680f035314d2379c4160c98153b2059b \
|
||||
--hash=sha256:dd4f49ae60e10adbc94b45c0b5e6a179acc1736cf7a90160b404076ee283cf83 \
|
||||
--hash=sha256:e1f145462f1fa6e4a4ae3c0f782e580ce44d57c8f2c7aae1b6fa88c0b2efdb41 \
|
||||
--hash=sha256:e3391d1e16e2a5a1507d83e4a8b100f4ee626e8eca43cf2cadb543de69827c4c \
|
||||
--hash=sha256:fcd2469d6a2cf298f198f0487e0a5b1a47a42ca0fa4dfd1b6862c999f018ebbf \
|
||||
--hash=sha256:fd096eb7ffef17c456cfa587523c5f92321ae02427ff955bebe9e3c63bc9f0da \
|
||||
--hash=sha256:fe754d231288e1e64323cfad462fcee8f0288654c10bdf4f603a39ed923bef33
|
||||
# via gevent
|
||||
gunicorn==21.2.0 \
|
||||
--hash=sha256:3213aa5e8c24949e792bcacfc176fef362e7aac80b76c56f6b5122bf350722f0 \
|
||||
--hash=sha256:88ec8bff1d634f98e61b9f65bc4bf3cd918a90806c6f5c48bc5603849ec81033
|
||||
|
|
@ -261,6 +363,12 @@ regex==2023.12.25 \
|
|||
--hash=sha256:f72cbae7f6b01591f90814250e636065850c5926751af02bb48da94dfced7baa \
|
||||
--hash=sha256:f7bc09bc9c29ebead055bcba136a67378f03d66bf359e87d0f7c759d6d4ffa31 \
|
||||
--hash=sha256:ff100b203092af77d1a5a7abe085b3506b7eaaf9abf65b73b7d6905b6cb76988
|
||||
# via gevent
|
||||
|
||||
# The following packages are considered to be unsafe in a requirements file:
|
||||
setuptools==69.0.3 \
|
||||
--hash=sha256:385eb4edd9c9d5c17540511303e39a147ce2fc04bc55289c322b9e5904fe2c05 \
|
||||
--hash=sha256:be1af57fc409f93647f2e8e4573a142ed38724b8cdd389706a867bb4efcf1e78
|
||||
# via -r requirements.in
|
||||
six==1.16.0 \
|
||||
--hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
|
||||
|
|
@ -289,3 +397,47 @@ zipp==3.17.0 \
|
|||
--hash=sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31 \
|
||||
--hash=sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0
|
||||
# via importlib-metadata
|
||||
zope-event==5.0 \
|
||||
--hash=sha256:2832e95014f4db26c47a13fdaef84cef2f4df37e66b59d8f1f4a8f319a632c26 \
|
||||
--hash=sha256:bac440d8d9891b4068e2b5a2c5e2c9765a9df762944bda6955f96bb9b91e67cd
|
||||
# via gevent
|
||||
zope-interface==6.1 \
|
||||
--hash=sha256:0c8cf55261e15590065039696607f6c9c1aeda700ceee40c70478552d323b3ff \
|
||||
--hash=sha256:13b7d0f2a67eb83c385880489dbb80145e9d344427b4262c49fbf2581677c11c \
|
||||
--hash=sha256:1f294a15f7723fc0d3b40701ca9b446133ec713eafc1cc6afa7b3d98666ee1ac \
|
||||
--hash=sha256:239a4a08525c080ff833560171d23b249f7f4d17fcbf9316ef4159f44997616f \
|
||||
--hash=sha256:2f8d89721834524a813f37fa174bac074ec3d179858e4ad1b7efd4401f8ac45d \
|
||||
--hash=sha256:2fdc7ccbd6eb6b7df5353012fbed6c3c5d04ceaca0038f75e601060e95345309 \
|
||||
--hash=sha256:34c15ca9248f2e095ef2e93af2d633358c5f048c49fbfddf5fdfc47d5e263736 \
|
||||
--hash=sha256:387545206c56b0315fbadb0431d5129c797f92dc59e276b3ce82db07ac1c6179 \
|
||||
--hash=sha256:43b576c34ef0c1f5a4981163b551a8781896f2a37f71b8655fd20b5af0386abb \
|
||||
--hash=sha256:57d0a8ce40ce440f96a2c77824ee94bf0d0925e6089df7366c2272ccefcb7941 \
|
||||
--hash=sha256:5a804abc126b33824a44a7aa94f06cd211a18bbf31898ba04bd0924fbe9d282d \
|
||||
--hash=sha256:67be3ca75012c6e9b109860820a8b6c9a84bfb036fbd1076246b98e56951ca92 \
|
||||
--hash=sha256:6af47f10cfc54c2ba2d825220f180cc1e2d4914d783d6fc0cd93d43d7bc1c78b \
|
||||
--hash=sha256:6dc998f6de015723196a904045e5a2217f3590b62ea31990672e31fbc5370b41 \
|
||||
--hash=sha256:70d2cef1bf529bff41559be2de9d44d47b002f65e17f43c73ddefc92f32bf00f \
|
||||
--hash=sha256:7ebc4d34e7620c4f0da7bf162c81978fce0ea820e4fa1e8fc40ee763839805f3 \
|
||||
--hash=sha256:964a7af27379ff4357dad1256d9f215047e70e93009e532d36dcb8909036033d \
|
||||
--hash=sha256:97806e9ca3651588c1baaebb8d0c5ee3db95430b612db354c199b57378312ee8 \
|
||||
--hash=sha256:9b9bc671626281f6045ad61d93a60f52fd5e8209b1610972cf0ef1bbe6d808e3 \
|
||||
--hash=sha256:9ffdaa5290422ac0f1688cb8adb1b94ca56cee3ad11f29f2ae301df8aecba7d1 \
|
||||
--hash=sha256:a0da79117952a9a41253696ed3e8b560a425197d4e41634a23b1507efe3273f1 \
|
||||
--hash=sha256:a41f87bb93b8048fe866fa9e3d0c51e27fe55149035dcf5f43da4b56732c0a40 \
|
||||
--hash=sha256:aa6fd016e9644406d0a61313e50348c706e911dca29736a3266fc9e28ec4ca6d \
|
||||
--hash=sha256:ad54ed57bdfa3254d23ae04a4b1ce405954969c1b0550cc2d1d2990e8b439de1 \
|
||||
--hash=sha256:b012d023b4fb59183909b45d7f97fb493ef7a46d2838a5e716e3155081894605 \
|
||||
--hash=sha256:b51b64432eed4c0744241e9ce5c70dcfecac866dff720e746d0a9c82f371dfa7 \
|
||||
--hash=sha256:bbe81def9cf3e46f16ce01d9bfd8bea595e06505e51b7baf45115c77352675fd \
|
||||
--hash=sha256:c9559138690e1bd4ea6cd0954d22d1e9251e8025ce9ede5d0af0ceae4a401e43 \
|
||||
--hash=sha256:e30506bcb03de8983f78884807e4fd95d8db6e65b69257eea05d13d519b83ac0 \
|
||||
--hash=sha256:e33e86fd65f369f10608b08729c8f1c92ec7e0e485964670b4d2633a4812d36b \
|
||||
--hash=sha256:e441e8b7d587af0414d25e8d05e27040d78581388eed4c54c30c0c91aad3a379 \
|
||||
--hash=sha256:e8bb9c990ca9027b4214fa543fd4025818dc95f8b7abce79d61dc8a2112b561a \
|
||||
--hash=sha256:ef43ee91c193f827e49599e824385ec7c7f3cd152d74cb1dfe02cb135f264d83 \
|
||||
--hash=sha256:ef467d86d3cfde8b39ea1b35090208b0447caaabd38405420830f7fd85fbdd56 \
|
||||
--hash=sha256:f89b28772fc2562ed9ad871c865f5320ef761a7fcc188a935e21fe8b31a38ca9 \
|
||||
--hash=sha256:fddbab55a2473f1d3b8833ec6b7ac31e8211b0aa608df5ab09ce07f3727326de
|
||||
# via
|
||||
# zope-event
|
||||
# zope-interface
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
from typing import Optional
|
||||
|
||||
from bcrypt import checkpw, hashpw, gensalt
|
||||
from flask_login import UserMixin
|
||||
from flask_login import AnonymousUserMixin, UserMixin
|
||||
from pyotp import random_base32
|
||||
from pyotp.totp import TOTP
|
||||
|
||||
|
|
@ -28,7 +28,7 @@ class User(UserMixin):
|
|||
self.is_two_factor_enabled = is_two_factor_enabled
|
||||
self.secret_token = secret_token
|
||||
self.method = method
|
||||
self.__totp = None
|
||||
self.__totp = TOTP(secret_token) if secret_token else None
|
||||
|
||||
@property
|
||||
def password_hash(self) -> bytes:
|
||||
|
|
@ -66,7 +66,7 @@ class User(UserMixin):
|
|||
self.secret_token = random_base32()
|
||||
self.__totp = TOTP(self.secret_token)
|
||||
|
||||
def check_otp(self, otp: str) -> bool:
|
||||
def check_otp(self, otp: str, *, secret: Optional[str] = None) -> bool:
|
||||
"""
|
||||
Check if the otp is correct by comparing it to the stored secret token
|
||||
|
||||
|
|
@ -74,9 +74,64 @@ class User(UserMixin):
|
|||
:return: The otp is being checked against the secret token. If the otp is correct,
|
||||
the user is returned.
|
||||
"""
|
||||
if secret:
|
||||
return TOTP(secret).verify(otp, valid_window=3)
|
||||
if not self.__totp:
|
||||
return False
|
||||
return self.__totp.verify(otp, valid_window=3)
|
||||
|
||||
def __repr__(self):
|
||||
return f"User({self.id!r}, {self.__password!r}, {self.is_two_factor_enabled!r}, {self.secret_token!r}, {self.method!r})"
|
||||
|
||||
|
||||
class AnonymousUser(AnonymousUserMixin):
|
||||
def __init__(self):
|
||||
self.id = None
|
||||
self.is_two_factor_enabled = False
|
||||
self.secret_token = None
|
||||
self.method = "manual"
|
||||
|
||||
@property
|
||||
def password_hash(self) -> None:
|
||||
"""
|
||||
Get the password hash
|
||||
|
||||
:return: The password hash
|
||||
"""
|
||||
return None
|
||||
|
||||
def update_password(self, password: str):
|
||||
"""
|
||||
Set the password by hashing it
|
||||
|
||||
:param password: The password to be hashed
|
||||
"""
|
||||
self.__password = hashpw(password.encode("utf-8"), gensalt(rounds=13))
|
||||
|
||||
def check_password(self, password: str):
|
||||
"""
|
||||
Check if the password is correct by hashing it and comparing it to the stored hash
|
||||
|
||||
:param password: The password to be checked
|
||||
:return: The password is being checked against the password hash. If the password is correct,
|
||||
the user is returned.
|
||||
"""
|
||||
return False
|
||||
|
||||
def get_authentication_setup_uri(self) -> str:
|
||||
return ""
|
||||
|
||||
def refresh_totp(self):
|
||||
return
|
||||
|
||||
def check_otp(self, otp: str, *, secret: Optional[str] = None) -> bool:
|
||||
"""
|
||||
Check if the otp is correct by comparing it to the stored secret token
|
||||
|
||||
:param otp: The otp to be checked
|
||||
:return: The otp is being checked against the secret token. If the otp is correct,
|
||||
the user is returned.
|
||||
"""
|
||||
if secret:
|
||||
return TOTP(secret).verify(otp, valid_window=3)
|
||||
return False
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@
|
|||
#
|
||||
# pip-compile --allow-unsafe --generate-hashes --strip-extras requirements.in
|
||||
#
|
||||
attrs==23.1.0 \
|
||||
--hash=sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04 \
|
||||
--hash=sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015
|
||||
attrs==23.2.0 \
|
||||
--hash=sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30 \
|
||||
--hash=sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1
|
||||
# via
|
||||
# outcome
|
||||
# trio
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@
|
|||
#
|
||||
# pip-compile --allow-unsafe --generate-hashes --strip-extras requirements.in
|
||||
#
|
||||
attrs==23.1.0 \
|
||||
--hash=sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04 \
|
||||
--hash=sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015
|
||||
attrs==23.2.0 \
|
||||
--hash=sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30 \
|
||||
--hash=sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1
|
||||
# via
|
||||
# outcome
|
||||
# trio
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@
|
|||
#
|
||||
# pip-compile --allow-unsafe --generate-hashes --strip-extras requirements.in
|
||||
#
|
||||
attrs==23.1.0 \
|
||||
--hash=sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04 \
|
||||
--hash=sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015
|
||||
attrs==23.2.0 \
|
||||
--hash=sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30 \
|
||||
--hash=sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1
|
||||
# via
|
||||
# outcome
|
||||
# trio
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@
|
|||
#
|
||||
# pip-compile --allow-unsafe --generate-hashes --strip-extras requirements.in
|
||||
#
|
||||
attrs==23.1.0 \
|
||||
--hash=sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04 \
|
||||
--hash=sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015
|
||||
attrs==23.2.0 \
|
||||
--hash=sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30 \
|
||||
--hash=sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1
|
||||
# via
|
||||
# outcome
|
||||
# trio
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@
|
|||
#
|
||||
# pip-compile --allow-unsafe --generate-hashes --strip-extras requirements.in
|
||||
#
|
||||
attrs==23.1.0 \
|
||||
--hash=sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04 \
|
||||
--hash=sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015
|
||||
attrs==23.2.0 \
|
||||
--hash=sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30 \
|
||||
--hash=sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1
|
||||
# via
|
||||
# outcome
|
||||
# trio
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@
|
|||
#
|
||||
# pip-compile --allow-unsafe --generate-hashes --strip-extras requirements.in
|
||||
#
|
||||
attrs==23.1.0 \
|
||||
--hash=sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04 \
|
||||
--hash=sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015
|
||||
attrs==23.2.0 \
|
||||
--hash=sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30 \
|
||||
--hash=sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1
|
||||
# via
|
||||
# outcome
|
||||
# trio
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@
|
|||
#
|
||||
# pip-compile --allow-unsafe --generate-hashes --strip-extras requirements.in
|
||||
#
|
||||
attrs==23.1.0 \
|
||||
--hash=sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04 \
|
||||
--hash=sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015
|
||||
attrs==23.2.0 \
|
||||
--hash=sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30 \
|
||||
--hash=sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1
|
||||
# via
|
||||
# outcome
|
||||
# trio
|
||||
|
|
|
|||
|
|
@ -18,9 +18,9 @@ async-timeout==4.0.3 \
|
|||
--hash=sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f \
|
||||
--hash=sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028
|
||||
# via redis
|
||||
attrs==23.1.0 \
|
||||
--hash=sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04 \
|
||||
--hash=sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015
|
||||
attrs==23.2.0 \
|
||||
--hash=sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30 \
|
||||
--hash=sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1
|
||||
# via
|
||||
# outcome
|
||||
# trio
|
||||
|
|
|
|||
|
|
@ -18,9 +18,9 @@ async-timeout==4.0.3 \
|
|||
--hash=sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f \
|
||||
--hash=sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028
|
||||
# via redis
|
||||
attrs==23.1.0 \
|
||||
--hash=sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04 \
|
||||
--hash=sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015
|
||||
attrs==23.2.0 \
|
||||
--hash=sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30 \
|
||||
--hash=sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1
|
||||
# via
|
||||
# outcome
|
||||
# trio
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@
|
|||
#
|
||||
# pip-compile --allow-unsafe --generate-hashes --strip-extras requirements.in
|
||||
#
|
||||
attrs==23.1.0 \
|
||||
--hash=sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04 \
|
||||
--hash=sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015
|
||||
attrs==23.2.0 \
|
||||
--hash=sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30 \
|
||||
--hash=sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1
|
||||
# via
|
||||
# outcome
|
||||
# trio
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@
|
|||
#
|
||||
# pip-compile --allow-unsafe --generate-hashes --strip-extras requirements.in
|
||||
#
|
||||
attrs==23.1.0 \
|
||||
--hash=sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04 \
|
||||
--hash=sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015
|
||||
attrs==23.2.0 \
|
||||
--hash=sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30 \
|
||||
--hash=sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1
|
||||
# via
|
||||
# outcome
|
||||
# trio
|
||||
|
|
|
|||
Loading…
Reference in a new issue