From 4d48ceee91d7e50c59a7e5921051973a18912a98 Mon Sep 17 00:00:00 2001 From: Glenn Date: Wed, 28 Aug 2024 12:16:52 -0700 Subject: [PATCH 01/52] init --- hushline/model.py | 4 + hushline/routes.py | 25 +++ hushline/settings.py | 168 +++++++++++------- hushline/static/css/style.css | 37 ++++ hushline/static/img/app/icon-verified-dm.png | Bin 0 -> 1840 bytes hushline/static/img/app/icon-verified-lm.png | Bin 0 -> 1899 bytes hushline/templates/profile.html | 71 ++++---- hushline/templates/settings.html | 15 ++ .../83a6b3b09eca_add_verified_fields.py | 63 +++++++ 9 files changed, 283 insertions(+), 100 deletions(-) create mode 100644 hushline/static/img/app/icon-verified-dm.png create mode 100644 hushline/static/img/app/icon-verified-lm.png create mode 100644 migrations/versions/83a6b3b09eca_add_verified_fields.py diff --git a/hushline/model.py b/hushline/model.py index 358e3c818..36519eb0e 100644 --- a/hushline/model.py +++ b/hushline/model.py @@ -61,6 +61,10 @@ class User(Model): extra_field_value3: Mapped[Optional[str]] extra_field_label4: Mapped[Optional[str]] extra_field_value4: Mapped[Optional[str]] + extra_field_verified1 = db.Column(db.Boolean, default=False) + extra_field_verified2 = db.Column(db.Boolean, default=False) + extra_field_verified3 = db.Column(db.Boolean, default=False) + extra_field_verified4 = db.Column(db.Boolean, default=False) @property def password_hash(self) -> str: diff --git a/hushline/routes.py b/hushline/routes.py index 73aaf6d06..c12abd440 100644 --- a/hushline/routes.py +++ b/hushline/routes.py @@ -124,6 +124,30 @@ def profile(username: str) -> Response | str: math_problem = f"{num1} + {num2} =" session["math_answer"] = str(num1 + num2) # Store the answer in session as a string + # Prepare extra fields and verification status + extra_fields = [ + { + "label": user.extra_field_label1, + "value": user.extra_field_value1, + "verified": user.extra_field_verified1, + }, + { + "label": user.extra_field_label2, + "value": user.extra_field_value2, + "verified": user.extra_field_verified2, + }, + { + "label": user.extra_field_label3, + "value": user.extra_field_value3, + "verified": user.extra_field_verified3, + }, + { + "label": user.extra_field_label4, + "value": user.extra_field_value4, + "verified": user.extra_field_verified4, + }, + ] + return render_template( "profile.html", form=form, @@ -135,6 +159,7 @@ def profile(username: str) -> Response | str: is_personal_server=app.config["IS_PERSONAL_SERVER"], require_pgp=app.config["REQUIRE_PGP"], math_problem=math_problem, + extra_fields=extra_fields, # Pass extra fields to template ) @app.route("/to/", methods=["POST"]) diff --git a/hushline/settings.py b/hushline/settings.py index a17bb7ded..7cb87997a 100644 --- a/hushline/settings.py +++ b/hushline/settings.py @@ -224,57 +224,55 @@ def index() -> str | Response: directory_visibility_form = DirectoryVisibilityForm() profile_form = ProfileForm() - # Check if the bio update form was submitted - if ( - request.method == "POST" - and "update_bio" in request.form - and profile_form.validate_on_submit() - ): - user.bio = request.form["bio"] - user.extra_field_label1 = profile_form.extra_field_label1.data.strip() - user.extra_field_value1 = profile_form.extra_field_value1.data.strip() - user.extra_field_label2 = profile_form.extra_field_label2.data.strip() - user.extra_field_value2 = profile_form.extra_field_value2.data.strip() - user.extra_field_label3 = profile_form.extra_field_label3.data.strip() - user.extra_field_value3 = profile_form.extra_field_value3.data.strip() - user.extra_field_label4 = profile_form.extra_field_label4.data.strip() - user.extra_field_value4 = profile_form.extra_field_value4.data.strip() - db.session.commit() - flash("👍 Bio updated successfully.") - return redirect(url_for("settings.index")) + # Handle form submissions + if request.method == "POST": + # Update bio and custom fields + if "update_bio" in request.form and profile_form.validate_on_submit(): + user.bio = request.form["bio"].strip() + user.extra_field_label1 = profile_form.extra_field_label1.data.strip() + user.extra_field_value1 = profile_form.extra_field_value1.data.strip() + user.extra_field_label2 = profile_form.extra_field_label2.data.strip() + user.extra_field_value2 = profile_form.extra_field_value2.data.strip() + user.extra_field_label3 = profile_form.extra_field_label3.data.strip() + user.extra_field_value3 = profile_form.extra_field_value3.data.strip() + user.extra_field_label4 = profile_form.extra_field_label4.data.strip() + user.extra_field_value4 = profile_form.extra_field_value4.data.strip() + + # Trigger the rel=me verification for each URL field + for i in range(1, 5): + url_to_verify = getattr(user, f"extra_field_value{i}", "").strip() + if url_to_verify: + try: + response = requests.get(url_to_verify, timeout=5) + response.raise_for_status() + profile_url = f"https://tips.hushline.app/to/{user.primary_username}" + if re.search( + rf']+href="{re.escape(profile_url)}"[^>]*rel="me"[^>]*>', + response.text, + ): + setattr(user, f"extra_field_verified{i}", True) + else: + setattr(user, f"extra_field_verified{i}", False) + except requests.exceptions.RequestException as e: + current_app.logger.error(f"Error fetching URL for field {i}: {e}") + setattr(user, f"extra_field_verified{i}", False) - if request.method == "POST" and ( - directory_visibility_form.validate_on_submit() - and "update_directory_visibility" in request.form - ): - user.show_in_directory = directory_visibility_form.show_in_directory.data - db.session.commit() - flash("👍 Directory visibility updated successfully.") - return redirect(url_for("settings.index")) + db.session.commit() - # Additional admin-specific data initialization - user_count = two_fa_count = pgp_key_count = two_fa_percentage = pgp_key_percentage = None - all_users = [] + # Display flash message after successful update + flash("👍 Bio and fields updated successfully.") + return redirect(url_for("settings.index")) - # Check if user is admin and add admin-specific data - if user.is_admin: - user_count = db.session.scalar(db.func.count(User.id)) - two_fa_count = db.session.scalar( - db.select(db.func.count(User.id).filter(User._totp_secret.isnot(None))) - ) - pgp_key_count = db.session.scalar( - db.select( - db.func.count(User.id) - .filter(User._pgp_key.isnot(None)) - .filter(User._pgp_key != "") - ) - ) - two_fa_percentage = (two_fa_count / user_count * 100) if user_count else 0 - pgp_key_percentage = (pgp_key_count / user_count * 100) if user_count else 0 - all_users = list(db.session.scalars(db.select(User)).all()) # Fetch all users for admin + # Update directory visibility + if ( + "update_directory_visibility" in request.form + and directory_visibility_form.validate_on_submit() + ): + user.show_in_directory = directory_visibility_form.show_in_directory.data + db.session.commit() + flash("👍 Directory visibility updated successfully.") + return redirect(url_for("settings.index")) - # Handle form submissions - if request.method == "POST": # Handle Display Name Form Submission if "update_display_name" in request.form and display_name_form.validate_on_submit(): user.update_display_name(display_name_form.display_name.data.strip()) @@ -305,26 +303,26 @@ def index() -> str | Response: ) return redirect(url_for(".index")) - # Check if user is admin and add admin-specific data - is_admin = user.is_admin - if is_admin: - user_count = db.session.scalar(db.func.count(User.id)) - two_fa_count = db.session.scalar( - db.select(db.func.count(User.id).filter(User._totp_secret.isnot(None))) - ) - pgp_key_count = db.session.scalar( - db.select( - db.func.count(User.id) - .filter(User._pgp_key.isnot(None)) - .filter(User._pgp_key != "") - ) + # Additional admin-specific data initialization + user_count = two_fa_count = pgp_key_count = two_fa_percentage = pgp_key_percentage = None + all_users = [] + + # Check if user is admin and add admin-specific data + if user.is_admin: + user_count = db.session.scalar(db.func.count(User.id)) + two_fa_count = db.session.scalar( + db.select(db.func.count(User.id).filter(User._totp_secret.isnot(None))) + ) + pgp_key_count = db.session.scalar( + db.select( + db.func.count(User.id) + .filter(User._pgp_key.isnot(None)) + .filter(User._pgp_key != "") ) - two_fa_percentage = (two_fa_count / user_count * 100) if user_count else 0 - pgp_key_percentage = (pgp_key_count / user_count * 100) if user_count else 0 - else: - user_count = two_fa_count = pgp_key_count = two_fa_percentage = ( - pgp_key_percentage - ) = None + ) + two_fa_percentage = (two_fa_count / user_count * 100) if user_count else 0 + pgp_key_percentage = (pgp_key_count / user_count * 100) if user_count else 0 + all_users = list(db.session.scalars(db.select(User)).all()) # Fetch all users for admin # Prepopulate form fields email_forwarding_form.forwarding_enabled.data = user.email is not None @@ -683,4 +681,42 @@ def delete_account() -> Response | str: flash("User not found. Please log in again.") return redirect(url_for("login")) + @bp.route("/verify-rel-me/", methods=["POST"]) + @authentication_required + def verify_rel_me(field_index: int) -> Response: + user_id = session.get("user_id") + if not user_id: + return redirect(url_for("login")) + + user = db.session.get(User, user_id) + if not user: + flash("User not found.") + return redirect(url_for("login")) + + # Get the URL from the corresponding field + url_to_verify = request.form.get(f"extra_field_value{field_index}", "").strip() + if not url_to_verify: + flash(f"No URL provided for field {field_index}.") + return redirect(url_for(".index")) + + try: + response = requests.get(url_to_verify, timeout=5) + response.raise_for_status() + except requests.exceptions.RequestException as e: + current_app.logger.error(f"Error fetching URL: {e}") + flash(f"Failed to fetch the URL for field {field_index}.") + return redirect(url_for(".index")) + + # Check for rel="me" link pointing to the user's profile URL + profile_url = f"https://hushline.app/user/{user.primary_username}" # Adjust as necessary + if re.search(rf']+href="{re.escape(profile_url)}"[^>]*rel="me"[^>]*>', response.text): + flash(f"✅ Field {field_index} URL is verified.") + setattr(user, f"extra_field_verified{field_index}", True) + else: + flash(f"❌ Field {field_index} URL verification failed.") + setattr(user, f"extra_field_verified{field_index}", False) + + db.session.commit() + return redirect(url_for(".index")) + return bp diff --git a/hushline/static/css/style.css b/hushline/static/css/style.css index e6a3e8e00..e082d8f10 100644 --- a/hushline/static/css/style.css +++ b/hushline/static/css/style.css @@ -339,6 +339,10 @@ #messageForm { border-top: var(--border); } + + .icon.verifiedURL { + background-image: url("../img/app/icon-verified-lm.png"); + } } /* Dark Mode */ @@ -637,6 +641,10 @@ #messageForm { border-top: var(--border-dark); } + + .icon.verifiedURL { + background-image: url("../img/app/icon-verified-dm.png"); + } } body { @@ -1763,6 +1771,21 @@ input#captcha_answer { margin-bottom: 1rem; } +.icon.verifiedURL { + width: 20px; + height: 20px; + display: flex; + background-size: contain; + position: absolute; + left: 0; + top: 0; +} + +.extra-field-value .icon.verifiedURL + a, +.extra-field-value .icon.verifiedURL + span { + padding-left: 1.5rem; +} + @media only screen and (max-width: 768px) { .container { min-width: initial; @@ -1934,12 +1957,25 @@ button[name="update_directory_visibility"] { display: flex; justify-content: space-between; gap: 1rem; + position: relative; } .input-pair input { margin-bottom: 0; } +.input-pair div:last-child { + position: relative; +} + +.input-pair input + .icon.verifiedURL { + position: absolute; + right: 0%; + top: 55%; + transform: translateY(0%); + left: calc(100% - 20px - 8px); +} + .form-group-pairs + button { margin-top: 1.25rem; } @@ -1994,6 +2030,7 @@ p.bio + .extra-fields { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + position: relative; } .pgp-disabled-overlay { diff --git a/hushline/static/img/app/icon-verified-dm.png b/hushline/static/img/app/icon-verified-dm.png new file mode 100644 index 0000000000000000000000000000000000000000..49cecb3e119195fe3794e877e33026b337f072a1 GIT binary patch literal 1840 zcmV-02haG4P)5lO3EO<^+Tj04IQ)0DJ<-31Xih%n7WV0PivbGiC2mH9)~G zV14wwXJkg>s9UeCZpW$euZqHD%TnvF*Zn@a+W>Rs%$YN1&d-W~DRS47%TW~7g%Hb9 z%KMia3ti6lzV>82h~qlF4uYV&vM%mnN=!wd>#L|Cq&&u#jr{vUz`c}kE`?~Xy{d(y z_esb&jyLh;jt6odk69i&B^}#d-B@UWW1I?f*ZWpaBL5?d38QR`gg*V30_wmc^lP;P zw5UEf!kIuMP8#_g3GADl2)u#5SF3Hi63GKckma}*hoEaDQS=cU;Y6U@zXylF>{ga& zT>tTK1P-v?#qKT#n+d=LOrQG{V4Le1xmdLzvYb>vr|dZLUNj(&24(HB%;S({yqOEz zmQ>k-$R(%oBf4U52g4FcCq3B}AU}J-VrvxhjE=(wAD4}7 zG;rfFP4sYK)xmpWfo?7b`}l7Um}3nFhRwCtizi@#n?E00Xoh>7WAO&NDAtEbVCZjQE*qx&2!lYqj?tW5hf@S<9Ilz;fq#O-wdx!49 z;5%jlq2-*K#5Thq4q$iXjr|~Q<;^esRKhl0gZrHkT$xM!3C-j;Xs}Uk*y+8LE4$KKuGb)`0;_pd^Bs5QxOVn?9k} zIDj0XP!ku*0`d5ca~w+`%Ai%B>7J5J^5W#!*NGGa>b41QSbZp^R3NJHs3KJIV73cX zg>5JnXT<(6KrCQo9oweP*H~3Ztw5As$OO(*pQD#(gJVj>DSb#Xl}ay*R&=ZqH+Ozb z<1Pn1jrDXf{@DVDaK!NvoY5#rhIR2!HY>8F${CdwLSP9dCz4BXs!%M@(vmj#3xcPmC^|B)pexi5MWR5D z3h=JQ$ySRfSy}?V7t3xg1O-_|u9R%5MJp0KRRbHU18i9iws}u;jjpkq$0zz3)a-Fx zHbW#Ow#1=;7qYT!5j1ZG>NcoO`ZUF(OEYu<);QTjBRjdYCr$-19FeShE6sQ*)AW7Wby#&--83)G zW^9>e^s4S=TbyidWZ`NPZY`Y^2T74CYrC{!6$~k|q-n=FQqwc?umlQXc@CBtiA2Ku zh#(LxpJnxxr+u)G@2@q4)nQgi(*{(evOwJ0?vKD@F0#^|1FGX} zg=rOr$*+ZF$ZUah>u}JDPG?D^;Y5Qz`DKSCCW{xe)3_uOW#*iUp(|)m9##f46Agqk z5Qy!?bRMDLq_oPE)&w?P?xP@{R@ufF(aWQPO)rJ`No}1QQm_RgOjeg>C+|}}=W6bZ z7Oi7E^T}|mF^64q;?Lc_`tesUh9$#~9YuN}~%uMK8tmnjuu!d^fQ`BWRUr;`zQR)uuMusY}|BPQ5vQiTx5 zwQX>KvpuV{*+dxZqnMOzvy2t*ScEfyXhnr-%w({;a75_366vgqUMXgrrPxGfK=FFKh eXU?3Tmwy4(e7TK%_WrQ|0000!z)M|CN0GKmp&YU@OzE=dykgT3B!;gz~ zkZKvybQAxyeB)h0^guzk{poQBA`?}hY%Q-#oxUy*#@3s@dDT$owHLiq z$fwywqYsgYCeTovBxgVD^z_q2HDZaBKqQ7EC%QPqaFj+Oia^g--|e!eRS582t<|$V zh(PcumOaYX<(K3=%$_T`pc*eeG*~Mmm!KnvMh=RTm$ehLdXYgM8)bK6UOQarQv6~< zkEF^Yh?ZpeL)ye2w@RlKt~y%C!=sBv@5cL=;0fq-2j@zC)DTC3JWc`?s3}IfQeM;fiZbbL`NsGd&InX2u7o`X?{i@Z15%7D>L8&nLNRl z9xz}FBtWk_<cStsL5}U2n{2X**ok}<@xA2AKFR^V9inDIJMoaizi!(|4 zW8)Zvdgf?S2PTz0X)?s2{F{)FNZ7Z^+m6ZtX(3+0Jw*Y$g^E-b2>p?uo5Qe(*^Hp< zVZILcg#DFw2d$M;jh=M5Ka&Wni$U%IFklHJ)AU{lMB?CrkeOe!-fOL#O&uu1Alnm8 zJKhwdfV+*V#&3_@_SGqn$x7Heb{^aR;fRyq zzRL9(N0%F!!ylq34ROX;MT#a4 zQY-7T<(a43YLp3B&Nm{(6sHQs0(~xZ{^}*y!9)@hxqOJB)VcfdxKCHZ$E6tlOsGogz z^sTkLi9LT^(3zm(?Bf=Zt!Fu-Uc`=dJ;hvHxwFwmo zQG&;c?cN1i@QSQlR2L{>g=rON^BbZJnJS!`)fI_2<0WdBl`H!Ju1D6y>!!FOD)!R zaW$n8%<{?7L_Og4i!Bi0Q(ZcuFBjDIi%7XMQ?x!v-uT%Xzk*UPBaJ)!J>Qx@3X1t; zGq{+NKu>W@LFu)81|IlTxzY|_;O^HEOeu+Pag?i)R8CcUQEB(&i~ zo3!i7e%!iMuv+N@%&{pKm?+l zSv7}E{CuXVFuo1m3nCFsAamHn*Jt9NE*!N--zIV-GTIcI#02T-MB%L|N2`v~(1lc; zP;tTJl1zI)4D2`Y8{E @@ -13,6 +15,7 @@

{{ display_name_or_username }} {% endif %}

+ {% if user.is_admin or user.is_verified %}
{% if user.is_admin %} @@ -31,33 +34,38 @@

{% endif %}

{% endif %} + {% if user.bio %}

{{ user.bio }}

{% endif %} - {% - if (user.extra_field_label1 and user.extra_field_value1) or (user.extra_field_label2 and user.extra_field_value2) or - (user.extra_field_label3 and user.extra_field_value3) or (user.extra_field_label4 and user.extra_field_value4) + set extra_fields = [ + {'label': user.extra_field_label1, 'value': user.extra_field_value1, 'verified': user.extra_field_verified1}, + {'label': user.extra_field_label2, 'value': user.extra_field_value2, 'verified': user.extra_field_verified2}, + {'label': user.extra_field_label3, 'value': user.extra_field_value3, 'verified': user.extra_field_verified3}, + {'label': user.extra_field_label4, 'value': user.extra_field_value4, 'verified': user.extra_field_verified4} + ] %} + + {% if extra_fields | selectattr('label', 'defined') | selectattr('value', 'defined') %}
- {% - for label, value in [ - (user.extra_field_label1, user.extra_field_value1), - (user.extra_field_label2, user.extra_field_value2), - (user.extra_field_label3, user.extra_field_value3), - (user.extra_field_label4, user.extra_field_value4) - ] - %} - {% if label and value %} + {% for field in extra_fields %} + {% if field.label and field.value %}

- {{ label }} + {{ field.label }} - {% if value.startswith('https://') %} - {{ value }} + {% endif %} + {% if field.value.startswith('https://') %} + {{ field.value }} {% else %} - {{ value }} + {{ field.value }} {% endif %}

@@ -67,7 +75,7 @@

{% endif %} {% set pgp_required_but_not_set = require_pgp and not user.pgp_key %} - {% if current_user_id == user.id or (secondary_username and current_user_id == secondary_username.primary_user_id) %} + {% if current_user_id == user.id %}

{% if pgp_required_but_not_set %} Only visible to you: In order to protect your sources, you need @@ -83,6 +91,7 @@

{% endif %}

{% endif %} +
id="contact_method" name="contact_method" value="{{ form.contact_method.data if form.contact_method.data is not none else '' }}" - {% - if - pgp_required_but_not_set - %} - disabled="disabled" - {% endif %} + {% if pgp_required_but_not_set %}disabled="disabled"{% endif %} /> @@ -113,11 +117,10 @@

name="content" required="" spellcheck="true" - {% if pgp_required_but_not_set %} - disabled="disabled" - {% endif %} + {% if pgp_required_but_not_set %}disabled="disabled"{% endif %} > -{{ form.content.data if form.content.data is not none else '' }} {% if not pgp_required_but_not_set %} @@ -131,7 +134,7 @@

value="false" /> - {% if current_user_id == user.id or (secondary_username and current_user_id == secondary_username.primary_user_id) %} + {% if current_user_id == user.id %} {% if user.pgp_key %}

🔐 Your message will be encrypted and only readable by you. diff --git a/hushline/templates/settings.html b/hushline/templates/settings.html index 3d6c09b24..dc1865113 100644 --- a/hushline/templates/settings.html +++ b/hushline/templates/settings.html @@ -170,8 +170,12 @@

Extra Fields

placeholder="signaluser.123" value="{{ user.extra_field_value1 or '' }}" /> + {% if user.extra_field_verified1 %} + + {% endif %}

+
@@ -190,8 +194,12 @@

Extra Fields

id="extra_field_value2" value="{{ user.extra_field_value2 or '' }}" /> + {% if user.extra_field_verified2 %} + + {% endif %}
+
@@ -210,8 +218,12 @@

Extra Fields

id="extra_field_value3" value="{{ user.extra_field_value3 or '' }}" /> + {% if user.extra_field_verified3 %} + + {% endif %}
+
@@ -230,6 +242,9 @@

Extra Fields

id="extra_field_value4" value="{{ user.extra_field_value4 or '' }}" /> + {% if user.extra_field_verified4 %} + + {% endif %}
diff --git a/migrations/versions/83a6b3b09eca_add_verified_fields.py b/migrations/versions/83a6b3b09eca_add_verified_fields.py new file mode 100644 index 000000000..3177df7e6 --- /dev/null +++ b/migrations/versions/83a6b3b09eca_add_verified_fields.py @@ -0,0 +1,63 @@ +"""add verified fields + +Revision ID: 83a6b3b09eca +Revises: 166a3402c391 +Create Date: 2024-08-28 11:14:27.902082 + +""" + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = "83a6b3b09eca" +down_revision = "166a3402c391" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table("authentication_logs", schema=None) as batch_op: + batch_op.drop_index("idx_authentication_logs_user_id_timestamp_successful") + + with op.batch_alter_table("message", schema=None) as batch_op: + batch_op.alter_column("content", existing_type=sa.TEXT(), nullable=True) + + with op.batch_alter_table("users", schema=None) as batch_op: + batch_op.add_column(sa.Column("extra_field_verified1", sa.Boolean(), nullable=True)) + batch_op.add_column(sa.Column("extra_field_verified2", sa.Boolean(), nullable=True)) + batch_op.add_column(sa.Column("extra_field_verified3", sa.Boolean(), nullable=True)) + batch_op.add_column(sa.Column("extra_field_verified4", sa.Boolean(), nullable=True)) + batch_op.alter_column("password_hash", existing_type=sa.VARCHAR(length=512), nullable=False) + batch_op.alter_column("is_verified", existing_type=sa.BOOLEAN(), nullable=False) + batch_op.alter_column("is_admin", existing_type=sa.BOOLEAN(), nullable=False) + batch_op.alter_column("show_in_directory", existing_type=sa.BOOLEAN(), nullable=False) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table("users", schema=None) as batch_op: + batch_op.alter_column("show_in_directory", existing_type=sa.BOOLEAN(), nullable=True) + batch_op.alter_column("is_admin", existing_type=sa.BOOLEAN(), nullable=True) + batch_op.alter_column("is_verified", existing_type=sa.BOOLEAN(), nullable=True) + batch_op.alter_column("password_hash", existing_type=sa.VARCHAR(length=512), nullable=True) + batch_op.drop_column("extra_field_verified4") + batch_op.drop_column("extra_field_verified3") + batch_op.drop_column("extra_field_verified2") + batch_op.drop_column("extra_field_verified1") + + with op.batch_alter_table("message", schema=None) as batch_op: + batch_op.alter_column("content", existing_type=sa.TEXT(), nullable=False) + + with op.batch_alter_table("authentication_logs", schema=None) as batch_op: + batch_op.create_index( + "idx_authentication_logs_user_id_timestamp_successful", + ["user_id", "timestamp", "successful"], + unique=False, + ) + + # ### end Alembic commands ### From 58c73f76b52bb943fac5992941703ecdc7ee2d37 Mon Sep 17 00:00:00 2001 From: Glenn Date: Wed, 28 Aug 2024 17:17:38 -0700 Subject: [PATCH 02/52] update tests --- hushline/settings.py | 19 +++++++++++++++++++ tests/test_profile.py | 15 ++++++++------- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/hushline/settings.py b/hushline/settings.py index 7cb87997a..a29efbe0f 100644 --- a/hushline/settings.py +++ b/hushline/settings.py @@ -3,6 +3,7 @@ import re from datetime import UTC, datetime from typing import Optional +from urllib.parse import urlparse import pyotp import qrcode @@ -194,6 +195,19 @@ def set_input_disabled(input_field: Field, disabled: bool = True) -> None: unset_field_attribute(input_field, "disabled") +def is_safe_url(url): + """Validates if the provided URL is safe and within the expected domains.""" + parsed_url = urlparse(url) + # Only allow http and https schemes + if parsed_url.scheme not in ["http", "https"]: + return False + # Allow only specific domains + allowed_domains = ["*.hushline.app"] + if parsed_url.netloc not in allowed_domains: + return False + return True + + def create_blueprint() -> Blueprint: bp = Blueprint("settings", __file__, url_prefix="/settings") @@ -699,6 +713,11 @@ def verify_rel_me(field_index: int) -> Response: flash(f"No URL provided for field {field_index}.") return redirect(url_for(".index")) + # Check if the URL is safe + if not is_safe_url(url_to_verify): + flash("The URL provided is not safe or is not within the allowed domains.") + return redirect(url_for(".index")) + try: response = requests.get(url_to_verify, timeout=5) response.raise_for_status() diff --git a/tests/test_profile.py b/tests/test_profile.py index c442bc48e..644395005 100644 --- a/tests/test_profile.py +++ b/tests/test_profile.py @@ -155,12 +155,13 @@ def test_profile_extra_fields(client: FlaskClient, app: Flask) -> None: assert b"Signal username" in response.data assert b"singleusername.666" in response.data assert b"Arbitrary Link" in response.data - # URLs should turn into links - assert ( - 'https://scidsg.org/" in response.text - ) + + # URLs should turn into links - check that critical parts of the link are present + assert 'href="https://scidsg.org/"' in response.text + assert 'target="_blank"' in response.text + assert 'rel="noopener noreferrer"' in response.text + + # Check that XSS is correctly escaped assert b"xss should fail" in response.data - # XSS should be escaped - assert b"" not in response.data assert b"<script>alert('xss')</script>" in response.data + assert b"" not in response.data From ef1bd9cf51ea165ac84118019e9407e3de6eb128 Mon Sep 17 00:00:00 2001 From: Glenn Date: Wed, 28 Aug 2024 17:19:29 -0700 Subject: [PATCH 03/52] linting --- hushline/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hushline/settings.py b/hushline/settings.py index a29efbe0f..d6512c0a8 100644 --- a/hushline/settings.py +++ b/hushline/settings.py @@ -195,7 +195,7 @@ def set_input_disabled(input_field: Field, disabled: bool = True) -> None: unset_field_attribute(input_field, "disabled") -def is_safe_url(url): +def is_safe_url(url: str) -> bool: """Validates if the provided URL is safe and within the expected domains.""" parsed_url = urlparse(url) # Only allow http and https schemes From 39e4cca7cd139d58071bb50ffaa39765f6119833 Mon Sep 17 00:00:00 2001 From: Glenn Date: Wed, 28 Aug 2024 17:33:10 -0700 Subject: [PATCH 04/52] Update settings.py --- hushline/settings.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/hushline/settings.py b/hushline/settings.py index d6512c0a8..ed34d78f3 100644 --- a/hushline/settings.py +++ b/hushline/settings.py @@ -198,11 +198,11 @@ def set_input_disabled(input_field: Field, disabled: bool = True) -> None: def is_safe_url(url: str) -> bool: """Validates if the provided URL is safe and within the expected domains.""" parsed_url = urlparse(url) - # Only allow http and https schemes if parsed_url.scheme not in ["http", "https"]: return False - # Allow only specific domains - allowed_domains = ["*.hushline.app"] + + # Allow only the specific domain without wildcards for better security + allowed_domains = ["hushline.app"] if parsed_url.netloc not in allowed_domains: return False return True @@ -707,7 +707,6 @@ def verify_rel_me(field_index: int) -> Response: flash("User not found.") return redirect(url_for("login")) - # Get the URL from the corresponding field url_to_verify = request.form.get(f"extra_field_value{field_index}", "").strip() if not url_to_verify: flash(f"No URL provided for field {field_index}.") @@ -721,12 +720,22 @@ def verify_rel_me(field_index: int) -> Response: try: response = requests.get(url_to_verify, timeout=5) response.raise_for_status() + + # Ensure content type is HTML + if "text/html" not in response.headers.get("Content-Type", ""): + flash("The URL provided does not return an HTML page.") + return redirect(url_for(".index")) + + # Prevent SSRF via redirect + if response.url != url_to_verify: + flash("The URL provided redirects to a different domain.") + return redirect(url_for(".index")) + except requests.exceptions.RequestException as e: current_app.logger.error(f"Error fetching URL: {e}") flash(f"Failed to fetch the URL for field {field_index}.") return redirect(url_for(".index")) - # Check for rel="me" link pointing to the user's profile URL profile_url = f"https://hushline.app/user/{user.primary_username}" # Adjust as necessary if re.search(rf']+href="{re.escape(profile_url)}"[^>]*rel="me"[^>]*>', response.text): flash(f"✅ Field {field_index} URL is verified.") From d3a7b761b2161b2753d46842899e469d5a5b3fcb Mon Sep 17 00:00:00 2001 From: Glenn Date: Wed, 28 Aug 2024 17:36:34 -0700 Subject: [PATCH 05/52] Update settings.py --- hushline/settings.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/hushline/settings.py b/hushline/settings.py index ed34d78f3..7a598e138 100644 --- a/hushline/settings.py +++ b/hushline/settings.py @@ -198,13 +198,20 @@ def set_input_disabled(input_field: Field, disabled: bool = True) -> None: def is_safe_url(url: str) -> bool: """Validates if the provided URL is safe and within the expected domains.""" parsed_url = urlparse(url) + + # Only allow http and https schemes if parsed_url.scheme not in ["http", "https"]: return False - # Allow only the specific domain without wildcards for better security + # Allow only specific domains and restrict to specific paths if necessary allowed_domains = ["hushline.app"] if parsed_url.netloc not in allowed_domains: return False + + # Ensure the path is within a specific structure, e.g., starts with /user/ + if not re.match(r"^/user/[a-zA-Z0-9_-]+/?$", parsed_url.path): + return False + return True From 33a956c05e93f7097e49c65d1c7221658ff0d625 Mon Sep 17 00:00:00 2001 From: Glenn Date: Wed, 28 Aug 2024 17:42:53 -0700 Subject: [PATCH 06/52] Update settings.py --- hushline/settings.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/hushline/settings.py b/hushline/settings.py index 7a598e138..9e970e9fe 100644 --- a/hushline/settings.py +++ b/hushline/settings.py @@ -203,12 +203,12 @@ def is_safe_url(url: str) -> bool: if parsed_url.scheme not in ["http", "https"]: return False - # Allow only specific domains and restrict to specific paths if necessary + # Allow only specific domains allowed_domains = ["hushline.app"] if parsed_url.netloc not in allowed_domains: return False - # Ensure the path is within a specific structure, e.g., starts with /user/ + # Optionally, restrict the URL path if necessary if not re.match(r"^/user/[a-zA-Z0-9_-]+/?$", parsed_url.path): return False @@ -725,15 +725,24 @@ def verify_rel_me(field_index: int) -> Response: return redirect(url_for(".index")) try: - response = requests.get(url_to_verify, timeout=5) + # Perform a HEAD request to check the URL without fetching content + head_response = requests.head(url_to_verify, timeout=5, allow_redirects=True) + head_response.raise_for_status() + + # Ensure the final URL after redirects is still within allowed domains + if not is_safe_url(head_response.url): + flash("The URL provided redirects to an unsafe domain.") + return redirect(url_for(".index")) + + # Perform the actual GET request + response = requests.get(head_response.url, timeout=5) response.raise_for_status() - # Ensure content type is HTML + # Ensure the response is HTML and check for unexpected redirects if "text/html" not in response.headers.get("Content-Type", ""): flash("The URL provided does not return an HTML page.") return redirect(url_for(".index")) - # Prevent SSRF via redirect if response.url != url_to_verify: flash("The URL provided redirects to a different domain.") return redirect(url_for(".index")) @@ -743,7 +752,7 @@ def verify_rel_me(field_index: int) -> Response: flash(f"Failed to fetch the URL for field {field_index}.") return redirect(url_for(".index")) - profile_url = f"https://hushline.app/user/{user.primary_username}" # Adjust as necessary + profile_url = f"https://tips.hushline.app/to/{user.primary_username}" if re.search(rf']+href="{re.escape(profile_url)}"[^>]*rel="me"[^>]*>', response.text): flash(f"✅ Field {field_index} URL is verified.") setattr(user, f"extra_field_verified{field_index}", True) From 85a0680f495ae4f642abaf5ae16b5b8e3bf5cb29 Mon Sep 17 00:00:00 2001 From: Glenn Date: Wed, 28 Aug 2024 17:46:09 -0700 Subject: [PATCH 07/52] Update settings.py --- hushline/settings.py | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/hushline/settings.py b/hushline/settings.py index 9e970e9fe..ca887dbe0 100644 --- a/hushline/settings.py +++ b/hushline/settings.py @@ -725,34 +725,27 @@ def verify_rel_me(field_index: int) -> Response: return redirect(url_for(".index")) try: - # Perform a HEAD request to check the URL without fetching content - head_response = requests.head(url_to_verify, timeout=5, allow_redirects=True) - head_response.raise_for_status() - - # Ensure the final URL after redirects is still within allowed domains - if not is_safe_url(head_response.url): - flash("The URL provided redirects to an unsafe domain.") + # Instead of making a HEAD or GET request, we can verify the structure here. + parsed_url = urlparse(url_to_verify) + if not parsed_url.netloc.endswith("hushline.app"): + flash("The URL provided is not within the allowed domain.") return redirect(url_for(".index")) - # Perform the actual GET request - response = requests.get(head_response.url, timeout=5) + # Prevent SSRF via redirect (e.g., avoiding open redirects) + response = requests.get(url_to_verify, timeout=5, allow_redirects=False) response.raise_for_status() - # Ensure the response is HTML and check for unexpected redirects + # Ensure the response is HTML if "text/html" not in response.headers.get("Content-Type", ""): flash("The URL provided does not return an HTML page.") return redirect(url_for(".index")) - if response.url != url_to_verify: - flash("The URL provided redirects to a different domain.") - return redirect(url_for(".index")) - except requests.exceptions.RequestException as e: current_app.logger.error(f"Error fetching URL: {e}") flash(f"Failed to fetch the URL for field {field_index}.") return redirect(url_for(".index")) - profile_url = f"https://tips.hushline.app/to/{user.primary_username}" + profile_url = f"https://hushline.app/user/{user.primary_username}" if re.search(rf']+href="{re.escape(profile_url)}"[^>]*rel="me"[^>]*>', response.text): flash(f"✅ Field {field_index} URL is verified.") setattr(user, f"extra_field_verified{field_index}", True) From a3f6f4e680793aaab2450040b616295e537c73a5 Mon Sep 17 00:00:00 2001 From: Glenn Date: Wed, 28 Aug 2024 17:52:05 -0700 Subject: [PATCH 08/52] Update settings.py --- hushline/settings.py | 50 +++++++++----------------------------------- 1 file changed, 10 insertions(+), 40 deletions(-) diff --git a/hushline/settings.py b/hushline/settings.py index ca887dbe0..0bcc2cba7 100644 --- a/hushline/settings.py +++ b/hushline/settings.py @@ -3,7 +3,6 @@ import re from datetime import UTC, datetime from typing import Optional -from urllib.parse import urlparse import pyotp import qrcode @@ -195,24 +194,15 @@ def set_input_disabled(input_field: Field, disabled: bool = True) -> None: unset_field_attribute(input_field, "disabled") -def is_safe_url(url: str) -> bool: - """Validates if the provided URL is safe and within the expected domains.""" - parsed_url = urlparse(url) - - # Only allow http and https schemes - if parsed_url.scheme not in ["http", "https"]: - return False +# Predefined safe URLs +SAFE_URLS = [ + "https://tips.hushline.app/", +] - # Allow only specific domains - allowed_domains = ["hushline.app"] - if parsed_url.netloc not in allowed_domains: - return False - # Optionally, restrict the URL path if necessary - if not re.match(r"^/user/[a-zA-Z0-9_-]+/?$", parsed_url.path): - return False - - return True +def is_safe_url(url: str) -> bool: + """Check if the URL is part of the predefined safe URLs.""" + return any(url.startswith(safe_url) for safe_url in SAFE_URLS) def create_blueprint() -> Blueprint: @@ -719,34 +709,14 @@ def verify_rel_me(field_index: int) -> Response: flash(f"No URL provided for field {field_index}.") return redirect(url_for(".index")) - # Check if the URL is safe + # Check if the URL is part of the predefined safe URLs if not is_safe_url(url_to_verify): flash("The URL provided is not safe or is not within the allowed domains.") return redirect(url_for(".index")) - try: - # Instead of making a HEAD or GET request, we can verify the structure here. - parsed_url = urlparse(url_to_verify) - if not parsed_url.netloc.endswith("hushline.app"): - flash("The URL provided is not within the allowed domain.") - return redirect(url_for(".index")) - - # Prevent SSRF via redirect (e.g., avoiding open redirects) - response = requests.get(url_to_verify, timeout=5, allow_redirects=False) - response.raise_for_status() - - # Ensure the response is HTML - if "text/html" not in response.headers.get("Content-Type", ""): - flash("The URL provided does not return an HTML page.") - return redirect(url_for(".index")) - - except requests.exceptions.RequestException as e: - current_app.logger.error(f"Error fetching URL: {e}") - flash(f"Failed to fetch the URL for field {field_index}.") - return redirect(url_for(".index")) - + # Manual or additional checks can be done here without making a network request profile_url = f"https://hushline.app/user/{user.primary_username}" - if re.search(rf']+href="{re.escape(profile_url)}"[^>]*rel="me"[^>]*>', response.text): + if url_to_verify == profile_url: flash(f"✅ Field {field_index} URL is verified.") setattr(user, f"extra_field_verified{field_index}", True) else: From d229af9109cbc2d2d3b33d39caf595127c4637a5 Mon Sep 17 00:00:00 2001 From: Glenn Date: Wed, 28 Aug 2024 18:01:51 -0700 Subject: [PATCH 09/52] Update style.css --- hushline/static/css/style.css | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/hushline/static/css/style.css b/hushline/static/css/style.css index e082d8f10..903509e31 100644 --- a/hushline/static/css/style.css +++ b/hushline/static/css/style.css @@ -1971,8 +1971,7 @@ button[name="update_directory_visibility"] { .input-pair input + .icon.verifiedURL { position: absolute; right: 0%; - top: 55%; - transform: translateY(0%); + top: 42px; left: calc(100% - 20px - 8px); } From 5581e566ed88632be0901043b31946cac7459bba Mon Sep 17 00:00:00 2001 From: Glenn Date: Wed, 28 Aug 2024 19:29:05 -0700 Subject: [PATCH 10/52] Update style.css --- hushline/static/css/style.css | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/hushline/static/css/style.css b/hushline/static/css/style.css index 903509e31..15e980551 100644 --- a/hushline/static/css/style.css +++ b/hushline/static/css/style.css @@ -1955,13 +1955,24 @@ button[name="update_directory_visibility"] { .input-pair { display: flex; - justify-content: space-between; gap: 1rem; position: relative; + width: 100%; +} + +.input-pair > div { + flex: 1; + position: relative; } .input-pair input { + flex: 1; /* Make all inputs take equal space */ margin-bottom: 0; + box-sizing: border-box; /* Ensure padding is included in width calculation */ +} + +.input-pair input:has(+ .verifiedURL) { + padding-right: 2.125rem; /* Add padding only when the input has a verifiedURL next to it */ } .input-pair div:last-child { From ceff4968e25dca6f7fc53755356ef223eeef9fe3 Mon Sep 17 00:00:00 2001 From: Glenn Date: Wed, 28 Aug 2024 19:34:26 -0700 Subject: [PATCH 11/52] update icon title --- hushline/templates/profile.html | 2 +- hushline/templates/settings.html | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/hushline/templates/profile.html b/hushline/templates/profile.html index 0d504dad1..c5e339c80 100644 --- a/hushline/templates/profile.html +++ b/hushline/templates/profile.html @@ -55,7 +55,7 @@

{{ field.label }} {% if field.verified %} - + {% endif %} {% if field.value.startswith('https://') %} Extra Fields

value="{{ user.extra_field_value1 or '' }}" /> {% if user.extra_field_verified1 %} - + {% endif %} @@ -195,7 +195,7 @@

Extra Fields

value="{{ user.extra_field_value2 or '' }}" /> {% if user.extra_field_verified2 %} - + {% endif %} @@ -219,7 +219,7 @@

Extra Fields

value="{{ user.extra_field_value3 or '' }}" /> {% if user.extra_field_verified3 %} - + {% endif %} @@ -243,7 +243,7 @@

Extra Fields

value="{{ user.extra_field_value4 or '' }}" /> {% if user.extra_field_verified4 %} - + {% endif %} From 119f48333738e566744fcf03b46fd9b75188498b Mon Sep 17 00:00:00 2001 From: Glenn Date: Wed, 28 Aug 2024 22:16:28 -0700 Subject: [PATCH 12/52] remove unused code --- hushline/settings.py | 24 ++++-------------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/hushline/settings.py b/hushline/settings.py index 0bcc2cba7..bb24e6135 100644 --- a/hushline/settings.py +++ b/hushline/settings.py @@ -194,17 +194,6 @@ def set_input_disabled(input_field: Field, disabled: bool = True) -> None: unset_field_attribute(input_field, "disabled") -# Predefined safe URLs -SAFE_URLS = [ - "https://tips.hushline.app/", -] - - -def is_safe_url(url: str) -> bool: - """Check if the URL is part of the predefined safe URLs.""" - return any(url.startswith(safe_url) for safe_url in SAFE_URLS) - - def create_blueprint() -> Blueprint: bp = Blueprint("settings", __file__, url_prefix="/settings") @@ -707,21 +696,16 @@ def verify_rel_me(field_index: int) -> Response: url_to_verify = request.form.get(f"extra_field_value{field_index}", "").strip() if not url_to_verify: flash(f"No URL provided for field {field_index}.") + setattr(user, f"extra_field_verified{field_index}", False) + db.session.commit() return redirect(url_for(".index")) # Check if the URL is part of the predefined safe URLs if not is_safe_url(url_to_verify): flash("The URL provided is not safe or is not within the allowed domains.") - return redirect(url_for(".index")) - - # Manual or additional checks can be done here without making a network request - profile_url = f"https://hushline.app/user/{user.primary_username}" - if url_to_verify == profile_url: - flash(f"✅ Field {field_index} URL is verified.") - setattr(user, f"extra_field_verified{field_index}", True) - else: - flash(f"❌ Field {field_index} URL verification failed.") setattr(user, f"extra_field_verified{field_index}", False) + db.session.commit() + return redirect(url_for(".index")) db.session.commit() return redirect(url_for(".index")) From 4346fd38197de063bd2cb077f8ea7439a39eed05 Mon Sep 17 00:00:00 2001 From: Glenn Date: Wed, 28 Aug 2024 22:17:48 -0700 Subject: [PATCH 13/52] Update settings.py --- hushline/settings.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/hushline/settings.py b/hushline/settings.py index bb24e6135..9f7d01c4c 100644 --- a/hushline/settings.py +++ b/hushline/settings.py @@ -700,13 +700,6 @@ def verify_rel_me(field_index: int) -> Response: db.session.commit() return redirect(url_for(".index")) - # Check if the URL is part of the predefined safe URLs - if not is_safe_url(url_to_verify): - flash("The URL provided is not safe or is not within the allowed domains.") - setattr(user, f"extra_field_verified{field_index}", False) - db.session.commit() - return redirect(url_for(".index")) - db.session.commit() return redirect(url_for(".index")) From 48dd63ee56a3a3c3e0e69b6d5f34e0554d01fbff Mon Sep 17 00:00:00 2001 From: Glenn Date: Thu, 29 Aug 2024 12:34:09 -0700 Subject: [PATCH 14/52] fix zindex of flash messages --- hushline/static/css/style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hushline/static/css/style.css b/hushline/static/css/style.css index 15e980551..38f538f5e 100644 --- a/hushline/static/css/style.css +++ b/hushline/static/css/style.css @@ -867,7 +867,7 @@ img.empty { max-width: 78vw; text-align: center; opacity: 0; - z-index: 10; + z-index: 12; animation: fadeInDown 0.5s ease forwards, fadeOutUp 0.5s ease 5s forwards; From 0be4b239a75d7afc5aea78b57faa79c59b1fdde9 Mon Sep 17 00:00:00 2001 From: Glenn Date: Thu, 29 Aug 2024 21:42:56 -0700 Subject: [PATCH 15/52] Update hushline/model.py Co-authored-by: Jeremy Moore --- hushline/model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hushline/model.py b/hushline/model.py index 36519eb0e..cee697ee3 100644 --- a/hushline/model.py +++ b/hushline/model.py @@ -61,7 +61,7 @@ class User(Model): extra_field_value3: Mapped[Optional[str]] extra_field_label4: Mapped[Optional[str]] extra_field_value4: Mapped[Optional[str]] - extra_field_verified1 = db.Column(db.Boolean, default=False) + extra_field_verified1: Mapped[Optional[bool]] = mapped_column(default=False) extra_field_verified2 = db.Column(db.Boolean, default=False) extra_field_verified3 = db.Column(db.Boolean, default=False) extra_field_verified4 = db.Column(db.Boolean, default=False) From edf6e86bbb74b142da38ad0b9e0d17998e9255d5 Mon Sep 17 00:00:00 2001 From: Glenn Date: Thu, 29 Aug 2024 21:45:32 -0700 Subject: [PATCH 16/52] Update migrations/versions/83a6b3b09eca_add_verified_fields.py Co-authored-by: Jeremy Moore --- migrations/versions/83a6b3b09eca_add_verified_fields.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/migrations/versions/83a6b3b09eca_add_verified_fields.py b/migrations/versions/83a6b3b09eca_add_verified_fields.py index 3177df7e6..371bd2518 100644 --- a/migrations/versions/83a6b3b09eca_add_verified_fields.py +++ b/migrations/versions/83a6b3b09eca_add_verified_fields.py @@ -19,8 +19,6 @@ def upgrade(): # ### commands auto generated by Alembic - please adjust! ### - with op.batch_alter_table("authentication_logs", schema=None) as batch_op: - batch_op.drop_index("idx_authentication_logs_user_id_timestamp_successful") with op.batch_alter_table("message", schema=None) as batch_op: batch_op.alter_column("content", existing_type=sa.TEXT(), nullable=True) From ffa85f3d57452d8f77217001be207692afbd575c Mon Sep 17 00:00:00 2001 From: Glenn Date: Thu, 29 Aug 2024 21:46:10 -0700 Subject: [PATCH 17/52] Update migrations/versions/83a6b3b09eca_add_verified_fields.py Co-authored-by: Jeremy Moore --- migrations/versions/83a6b3b09eca_add_verified_fields.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/migrations/versions/83a6b3b09eca_add_verified_fields.py b/migrations/versions/83a6b3b09eca_add_verified_fields.py index 371bd2518..d2afcb450 100644 --- a/migrations/versions/83a6b3b09eca_add_verified_fields.py +++ b/migrations/versions/83a6b3b09eca_add_verified_fields.py @@ -51,11 +51,5 @@ def downgrade(): with op.batch_alter_table("message", schema=None) as batch_op: batch_op.alter_column("content", existing_type=sa.TEXT(), nullable=False) - with op.batch_alter_table("authentication_logs", schema=None) as batch_op: - batch_op.create_index( - "idx_authentication_logs_user_id_timestamp_successful", - ["user_id", "timestamp", "successful"], - unique=False, - ) # ### end Alembic commands ### From 97ba5a275f8feefc2de8f80ff771ea7edc989d17 Mon Sep 17 00:00:00 2001 From: Glenn Date: Fri, 30 Aug 2024 10:42:00 -0700 Subject: [PATCH 18/52] Add filter to strip whitespace --- hushline/settings.py | 62 +++++++++++++++++++++++++++++--------------- 1 file changed, 41 insertions(+), 21 deletions(-) diff --git a/hushline/settings.py b/hushline/settings.py index 9f7d01c4c..129a15e60 100644 --- a/hushline/settings.py +++ b/hushline/settings.py @@ -141,31 +141,53 @@ class DirectoryVisibilityForm(FlaskForm): show_in_directory = BooleanField("Show on public directory") +def strip_whitespace(value): + if value is not None and hasattr(value, "strip"): + return value.strip() + return value + + class ProfileForm(FlaskForm): - bio = TextAreaField("Bio", validators=[Length(max=250)]) + bio = TextAreaField("Bio", filters=[strip_whitespace], validators=[Length(max=250)]) extra_field_label1 = StringField( - "Extra Field Label 1", validators=[OptionalField(), Length(max=50)] + "Extra Field Label 1", + filters=[strip_whitespace], + validators=[OptionalField(), Length(max=50)], ) extra_field_value1 = StringField( - "Extra Field Value 1", validators=[OptionalField(), Length(max=4096)] + "Extra Field Value 1", + filters=[strip_whitespace], + validators=[OptionalField(), Length(max=4096)], ) extra_field_label2 = StringField( - "Extra Field Label 2", validators=[OptionalField(), Length(max=50)] + "Extra Field Label 2", + filters=[strip_whitespace], + validators=[OptionalField(), Length(max=50)], ) extra_field_value2 = StringField( - "Extra Field Value 2", validators=[OptionalField(), Length(max=4096)] + "Extra Field Value 2", + filters=[strip_whitespace], + validators=[OptionalField(), Length(max=4096)], ) extra_field_label3 = StringField( - "Extra Field Label 3", validators=[OptionalField(), Length(max=50)] + "Extra Field Label 3", + filters=[strip_whitespace], + validators=[OptionalField(), Length(max=50)], ) extra_field_value3 = StringField( - "Extra Field Value 3", validators=[OptionalField(), Length(max=4096)] + "Extra Field Value 3", + filters=[strip_whitespace], + validators=[OptionalField(), Length(max=4096)], ) extra_field_label4 = StringField( - "Extra Field Label 4", validators=[OptionalField(), Length(max=50)] + "Extra Field Label 4", + filters=[strip_whitespace], + validators=[OptionalField(), Length(max=50)], ) extra_field_value4 = StringField( - "Extra Field Value 4", validators=[OptionalField(), Length(max=4096)] + "Extra Field Value 4", + filters=[strip_whitespace], + validators=[OptionalField(), Length(max=4096)], ) @@ -228,19 +250,19 @@ def index() -> str | Response: if request.method == "POST": # Update bio and custom fields if "update_bio" in request.form and profile_form.validate_on_submit(): - user.bio = request.form["bio"].strip() - user.extra_field_label1 = profile_form.extra_field_label1.data.strip() - user.extra_field_value1 = profile_form.extra_field_value1.data.strip() - user.extra_field_label2 = profile_form.extra_field_label2.data.strip() - user.extra_field_value2 = profile_form.extra_field_value2.data.strip() - user.extra_field_label3 = profile_form.extra_field_label3.data.strip() - user.extra_field_value3 = profile_form.extra_field_value3.data.strip() - user.extra_field_label4 = profile_form.extra_field_label4.data.strip() - user.extra_field_value4 = profile_form.extra_field_value4.data.strip() + user.bio = profile_form.bio.data + user.extra_field_label1 = profile_form.extra_field_label1.data + user.extra_field_value1 = profile_form.extra_field_value1.data + user.extra_field_label2 = profile_form.extra_field_label2.data + user.extra_field_value2 = profile_form.extra_field_value2.data + user.extra_field_label3 = profile_form.extra_field_label3.data + user.extra_field_value3 = profile_form.extra_field_value3.data + user.extra_field_label4 = profile_form.extra_field_label4.data + user.extra_field_value4 = profile_form.extra_field_value4.data # Trigger the rel=me verification for each URL field for i in range(1, 5): - url_to_verify = getattr(user, f"extra_field_value{i}", "").strip() + url_to_verify = getattr(user, f"extra_field_value{i}", "") if url_to_verify: try: response = requests.get(url_to_verify, timeout=5) @@ -258,8 +280,6 @@ def index() -> str | Response: setattr(user, f"extra_field_verified{i}", False) db.session.commit() - - # Display flash message after successful update flash("👍 Bio and fields updated successfully.") return redirect(url_for("settings.index")) From a28e0fc99ca599e0cc8e93e5bb1095641549121a Mon Sep 17 00:00:00 2001 From: Glenn Date: Fri, 30 Aug 2024 10:46:52 -0700 Subject: [PATCH 19/52] Loop fields and update dynamically --- hushline/settings.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/hushline/settings.py b/hushline/settings.py index 129a15e60..869b3bf00 100644 --- a/hushline/settings.py +++ b/hushline/settings.py @@ -251,18 +251,16 @@ def index() -> str | Response: # Update bio and custom fields if "update_bio" in request.form and profile_form.validate_on_submit(): user.bio = profile_form.bio.data - user.extra_field_label1 = profile_form.extra_field_label1.data - user.extra_field_value1 = profile_form.extra_field_value1.data - user.extra_field_label2 = profile_form.extra_field_label2.data - user.extra_field_value2 = profile_form.extra_field_value2.data - user.extra_field_label3 = profile_form.extra_field_label3.data - user.extra_field_value3 = profile_form.extra_field_value3.data - user.extra_field_label4 = profile_form.extra_field_label4.data - user.extra_field_value4 = profile_form.extra_field_value4.data - - # Trigger the rel=me verification for each URL field + + # Loop through extra fields and update dynamically for i in range(1, 5): - url_to_verify = getattr(user, f"extra_field_value{i}", "") + label = getattr(profile_form, f"extra_field_label{i}", "").data + setattr(user, f"extra_field_label{i}", label) + value = getattr(profile_form, f"extra_field_value{i}", "").data + setattr(user, f"extra_field_value{i}", value) + + # Trigger the rel=me verification for each URL field + url_to_verify = value.strip() # Assuming 'value' contains the URL if url_to_verify: try: response = requests.get(url_to_verify, timeout=5) From 90fadf78203f9710be511140ea6c2d367876f7dc Mon Sep 17 00:00:00 2001 From: Glenn Date: Fri, 30 Aug 2024 11:10:13 -0700 Subject: [PATCH 20/52] Async processing of URLs for verification --- hushline/settings.py | 64 +++--- poetry.lock | 460 ++++++++++++++++++++++++++++++++++++++++++- pyproject.toml | 3 +- 3 files changed, 501 insertions(+), 26 deletions(-) diff --git a/hushline/settings.py b/hushline/settings.py index 869b3bf00..8c7632003 100644 --- a/hushline/settings.py +++ b/hushline/settings.py @@ -1,9 +1,11 @@ +import asyncio import base64 import io import re from datetime import UTC, datetime from typing import Optional +import aiohttp import pyotp import qrcode import requests @@ -216,6 +218,23 @@ def set_input_disabled(input_field: Field, disabled: bool = True) -> None: unset_field_attribute(input_field, "disabled") +# Define the async function for URL verification +async def verify_url(session, user, i, url_to_verify, profile_url): + try: + async with session.get(url_to_verify, timeout=5) as response: + response.raise_for_status() + if re.search( + rf']+href="{re.escape(profile_url)}"[^>]*rel="me"[^>]*>', + await response.text(), + ): + setattr(user, f"extra_field_verified{i}", True) + else: + setattr(user, f"extra_field_verified{i}", False) + except aiohttp.ClientError as e: + current_app.logger.error(f"Error fetching URL for field {i}: {e}") + setattr(user, f"extra_field_verified{i}", False) + + def create_blueprint() -> Blueprint: bp = Blueprint("settings", __file__, url_prefix="/settings") @@ -252,30 +271,27 @@ def index() -> str | Response: if "update_bio" in request.form and profile_form.validate_on_submit(): user.bio = profile_form.bio.data - # Loop through extra fields and update dynamically - for i in range(1, 5): - label = getattr(profile_form, f"extra_field_label{i}", "").data - setattr(user, f"extra_field_label{i}", label) - value = getattr(profile_form, f"extra_field_value{i}", "").data - setattr(user, f"extra_field_value{i}", value) - - # Trigger the rel=me verification for each URL field - url_to_verify = value.strip() # Assuming 'value' contains the URL - if url_to_verify: - try: - response = requests.get(url_to_verify, timeout=5) - response.raise_for_status() - profile_url = f"https://tips.hushline.app/to/{user.primary_username}" - if re.search( - rf']+href="{re.escape(profile_url)}"[^>]*rel="me"[^>]*>', - response.text, - ): - setattr(user, f"extra_field_verified{i}", True) - else: - setattr(user, f"extra_field_verified{i}", False) - except requests.exceptions.RequestException as e: - current_app.logger.error(f"Error fetching URL for field {i}: {e}") - setattr(user, f"extra_field_verified{i}", False) + async def perform_verification(): + async with aiohttp.ClientSession() as session: + tasks = [] + for i in range(1, 5): + label = getattr(profile_form, f"extra_field_label{i}", "").data + setattr(user, f"extra_field_label{i}", label) + value = getattr(profile_form, f"extra_field_value{i}", "").data + setattr(user, f"extra_field_value{i}", value) + + url_to_verify = value.strip() + if url_to_verify: + profile_url = ( + f"https://tips.hushline.app/to/{user.primary_username}" + ) + task = verify_url(session, user, i, url_to_verify, profile_url) + tasks.append(task) + + await asyncio.gather(*tasks) + + # Run the async verification function + asyncio.run(perform_verification()) db.session.commit() flash("👍 Bio and fields updated successfully.") diff --git a/poetry.lock b/poetry.lock index 5c900721b..b59f7cfb2 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,5 +1,141 @@ # This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +[[package]] +name = "aiohappyeyeballs" +version = "2.4.0" +description = "Happy Eyeballs for asyncio" +optional = false +python-versions = ">=3.8" +files = [ + {file = "aiohappyeyeballs-2.4.0-py3-none-any.whl", hash = "sha256:7ce92076e249169a13c2f49320d1967425eaf1f407522d707d59cac7628d62bd"}, + {file = "aiohappyeyeballs-2.4.0.tar.gz", hash = "sha256:55a1714f084e63d49639800f95716da97a1f173d46a16dfcfda0016abb93b6b2"}, +] + +[[package]] +name = "aiohttp" +version = "3.10.5" +description = "Async http client/server framework (asyncio)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "aiohttp-3.10.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:18a01eba2574fb9edd5f6e5fb25f66e6ce061da5dab5db75e13fe1558142e0a3"}, + {file = "aiohttp-3.10.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:94fac7c6e77ccb1ca91e9eb4cb0ac0270b9fb9b289738654120ba8cebb1189c6"}, + {file = "aiohttp-3.10.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2f1f1c75c395991ce9c94d3e4aa96e5c59c8356a15b1c9231e783865e2772699"}, + {file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f7acae3cf1a2a2361ec4c8e787eaaa86a94171d2417aae53c0cca6ca3118ff6"}, + {file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:94c4381ffba9cc508b37d2e536b418d5ea9cfdc2848b9a7fea6aebad4ec6aac1"}, + {file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c31ad0c0c507894e3eaa843415841995bf8de4d6b2d24c6e33099f4bc9fc0d4f"}, + {file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0912b8a8fadeb32ff67a3ed44249448c20148397c1ed905d5dac185b4ca547bb"}, + {file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d93400c18596b7dc4794d48a63fb361b01a0d8eb39f28800dc900c8fbdaca91"}, + {file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d00f3c5e0d764a5c9aa5a62d99728c56d455310bcc288a79cab10157b3af426f"}, + {file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:d742c36ed44f2798c8d3f4bc511f479b9ceef2b93f348671184139e7d708042c"}, + {file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:814375093edae5f1cb31e3407997cf3eacefb9010f96df10d64829362ae2df69"}, + {file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8224f98be68a84b19f48e0bdc14224b5a71339aff3a27df69989fa47d01296f3"}, + {file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d9a487ef090aea982d748b1b0d74fe7c3950b109df967630a20584f9a99c0683"}, + {file = "aiohttp-3.10.5-cp310-cp310-win32.whl", hash = "sha256:d9ef084e3dc690ad50137cc05831c52b6ca428096e6deb3c43e95827f531d5ef"}, + {file = "aiohttp-3.10.5-cp310-cp310-win_amd64.whl", hash = "sha256:66bf9234e08fe561dccd62083bf67400bdbf1c67ba9efdc3dac03650e97c6088"}, + {file = "aiohttp-3.10.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8c6a4e5e40156d72a40241a25cc226051c0a8d816610097a8e8f517aeacd59a2"}, + {file = "aiohttp-3.10.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c634a3207a5445be65536d38c13791904fda0748b9eabf908d3fe86a52941cf"}, + {file = "aiohttp-3.10.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4aff049b5e629ef9b3e9e617fa6e2dfeda1bf87e01bcfecaf3949af9e210105e"}, + {file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1942244f00baaacaa8155eca94dbd9e8cc7017deb69b75ef67c78e89fdad3c77"}, + {file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e04a1f2a65ad2f93aa20f9ff9f1b672bf912413e5547f60749fa2ef8a644e061"}, + {file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7f2bfc0032a00405d4af2ba27f3c429e851d04fad1e5ceee4080a1c570476697"}, + {file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:424ae21498790e12eb759040bbb504e5e280cab64693d14775c54269fd1d2bb7"}, + {file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:975218eee0e6d24eb336d0328c768ebc5d617609affaca5dbbd6dd1984f16ed0"}, + {file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4120d7fefa1e2d8fb6f650b11489710091788de554e2b6f8347c7a20ceb003f5"}, + {file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:b90078989ef3fc45cf9221d3859acd1108af7560c52397ff4ace8ad7052a132e"}, + {file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ba5a8b74c2a8af7d862399cdedce1533642fa727def0b8c3e3e02fcb52dca1b1"}, + {file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:02594361128f780eecc2a29939d9dfc870e17b45178a867bf61a11b2a4367277"}, + {file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8fb4fc029e135859f533025bc82047334e24b0d489e75513144f25408ecaf058"}, + {file = "aiohttp-3.10.5-cp311-cp311-win32.whl", hash = "sha256:e1ca1ef5ba129718a8fc827b0867f6aa4e893c56eb00003b7367f8a733a9b072"}, + {file = "aiohttp-3.10.5-cp311-cp311-win_amd64.whl", hash = "sha256:349ef8a73a7c5665cca65c88ab24abe75447e28aa3bc4c93ea5093474dfdf0ff"}, + {file = "aiohttp-3.10.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:305be5ff2081fa1d283a76113b8df7a14c10d75602a38d9f012935df20731487"}, + {file = "aiohttp-3.10.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3a1c32a19ee6bbde02f1cb189e13a71b321256cc1d431196a9f824050b160d5a"}, + {file = "aiohttp-3.10.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:61645818edd40cc6f455b851277a21bf420ce347baa0b86eaa41d51ef58ba23d"}, + {file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c225286f2b13bab5987425558baa5cbdb2bc925b2998038fa028245ef421e75"}, + {file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8ba01ebc6175e1e6b7275c907a3a36be48a2d487549b656aa90c8a910d9f3178"}, + {file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8eaf44ccbc4e35762683078b72bf293f476561d8b68ec8a64f98cf32811c323e"}, + {file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1c43eb1ab7cbf411b8e387dc169acb31f0ca0d8c09ba63f9eac67829585b44f"}, + {file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de7a5299827253023c55ea549444e058c0eb496931fa05d693b95140a947cb73"}, + {file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4790f0e15f00058f7599dab2b206d3049d7ac464dc2e5eae0e93fa18aee9e7bf"}, + {file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:44b324a6b8376a23e6ba25d368726ee3bc281e6ab306db80b5819999c737d820"}, + {file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0d277cfb304118079e7044aad0b76685d30ecb86f83a0711fc5fb257ffe832ca"}, + {file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:54d9ddea424cd19d3ff6128601a4a4d23d54a421f9b4c0fff740505813739a91"}, + {file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4f1c9866ccf48a6df2b06823e6ae80573529f2af3a0992ec4fe75b1a510df8a6"}, + {file = "aiohttp-3.10.5-cp312-cp312-win32.whl", hash = "sha256:dc4826823121783dccc0871e3f405417ac116055bf184ac04c36f98b75aacd12"}, + {file = "aiohttp-3.10.5-cp312-cp312-win_amd64.whl", hash = "sha256:22c0a23a3b3138a6bf76fc553789cb1a703836da86b0f306b6f0dc1617398abc"}, + {file = "aiohttp-3.10.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7f6b639c36734eaa80a6c152a238242bedcee9b953f23bb887e9102976343092"}, + {file = "aiohttp-3.10.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f29930bc2921cef955ba39a3ff87d2c4398a0394ae217f41cb02d5c26c8b1b77"}, + {file = "aiohttp-3.10.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f489a2c9e6455d87eabf907ac0b7d230a9786be43fbe884ad184ddf9e9c1e385"}, + {file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:123dd5b16b75b2962d0fff566effb7a065e33cd4538c1692fb31c3bda2bfb972"}, + {file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b98e698dc34966e5976e10bbca6d26d6724e6bdea853c7c10162a3235aba6e16"}, + {file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3b9162bab7e42f21243effc822652dc5bb5e8ff42a4eb62fe7782bcbcdfacf6"}, + {file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1923a5c44061bffd5eebeef58cecf68096e35003907d8201a4d0d6f6e387ccaa"}, + {file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d55f011da0a843c3d3df2c2cf4e537b8070a419f891c930245f05d329c4b0689"}, + {file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:afe16a84498441d05e9189a15900640a2d2b5e76cf4efe8cbb088ab4f112ee57"}, + {file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f8112fb501b1e0567a1251a2fd0747baae60a4ab325a871e975b7bb67e59221f"}, + {file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:1e72589da4c90337837fdfe2026ae1952c0f4a6e793adbbfbdd40efed7c63599"}, + {file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:4d46c7b4173415d8e583045fbc4daa48b40e31b19ce595b8d92cf639396c15d5"}, + {file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:33e6bc4bab477c772a541f76cd91e11ccb6d2efa2b8d7d7883591dfb523e5987"}, + {file = "aiohttp-3.10.5-cp313-cp313-win32.whl", hash = "sha256:c58c6837a2c2a7cf3133983e64173aec11f9c2cd8e87ec2fdc16ce727bcf1a04"}, + {file = "aiohttp-3.10.5-cp313-cp313-win_amd64.whl", hash = "sha256:38172a70005252b6893088c0f5e8a47d173df7cc2b2bd88650957eb84fcf5022"}, + {file = "aiohttp-3.10.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:f6f18898ace4bcd2d41a122916475344a87f1dfdec626ecde9ee802a711bc569"}, + {file = "aiohttp-3.10.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5ede29d91a40ba22ac1b922ef510aab871652f6c88ef60b9dcdf773c6d32ad7a"}, + {file = "aiohttp-3.10.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:673f988370f5954df96cc31fd99c7312a3af0a97f09e407399f61583f30da9bc"}, + {file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58718e181c56a3c02d25b09d4115eb02aafe1a732ce5714ab70326d9776457c3"}, + {file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b38b1570242fbab8d86a84128fb5b5234a2f70c2e32f3070143a6d94bc854cf"}, + {file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:074d1bff0163e107e97bd48cad9f928fa5a3eb4b9d33366137ffce08a63e37fe"}, + {file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd31f176429cecbc1ba499d4aba31aaccfea488f418d60376b911269d3b883c5"}, + {file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7384d0b87d4635ec38db9263e6a3f1eb609e2e06087f0aa7f63b76833737b471"}, + {file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:8989f46f3d7ef79585e98fa991e6ded55d2f48ae56d2c9fa5e491a6e4effb589"}, + {file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:c83f7a107abb89a227d6c454c613e7606c12a42b9a4ca9c5d7dad25d47c776ae"}, + {file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:cde98f323d6bf161041e7627a5fd763f9fd829bcfcd089804a5fdce7bb6e1b7d"}, + {file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:676f94c5480d8eefd97c0c7e3953315e4d8c2b71f3b49539beb2aa676c58272f"}, + {file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:2d21ac12dc943c68135ff858c3a989f2194a709e6e10b4c8977d7fcd67dfd511"}, + {file = "aiohttp-3.10.5-cp38-cp38-win32.whl", hash = "sha256:17e997105bd1a260850272bfb50e2a328e029c941c2708170d9d978d5a30ad9a"}, + {file = "aiohttp-3.10.5-cp38-cp38-win_amd64.whl", hash = "sha256:1c19de68896747a2aa6257ae4cf6ef59d73917a36a35ee9d0a6f48cff0f94db8"}, + {file = "aiohttp-3.10.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7e2fe37ac654032db1f3499fe56e77190282534810e2a8e833141a021faaab0e"}, + {file = "aiohttp-3.10.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f5bf3ead3cb66ab990ee2561373b009db5bc0e857549b6c9ba84b20bc462e172"}, + {file = "aiohttp-3.10.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1b2c16a919d936ca87a3c5f0e43af12a89a3ce7ccbce59a2d6784caba945b68b"}, + {file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad146dae5977c4dd435eb31373b3fe9b0b1bf26858c6fc452bf6af394067e10b"}, + {file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8c5c6fa16412b35999320f5c9690c0f554392dc222c04e559217e0f9ae244b92"}, + {file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:95c4dc6f61d610bc0ee1edc6f29d993f10febfe5b76bb470b486d90bbece6b22"}, + {file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da452c2c322e9ce0cfef392e469a26d63d42860f829026a63374fde6b5c5876f"}, + {file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:898715cf566ec2869d5cb4d5fb4be408964704c46c96b4be267442d265390f32"}, + {file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:391cc3a9c1527e424c6865e087897e766a917f15dddb360174a70467572ac6ce"}, + {file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:380f926b51b92d02a34119d072f178d80bbda334d1a7e10fa22d467a66e494db"}, + {file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ce91db90dbf37bb6fa0997f26574107e1b9d5ff939315247b7e615baa8ec313b"}, + {file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:9093a81e18c45227eebe4c16124ebf3e0d893830c6aca7cc310bfca8fe59d857"}, + {file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ee40b40aa753d844162dcc80d0fe256b87cba48ca0054f64e68000453caead11"}, + {file = "aiohttp-3.10.5-cp39-cp39-win32.whl", hash = "sha256:03f2645adbe17f274444953bdea69f8327e9d278d961d85657cb0d06864814c1"}, + {file = "aiohttp-3.10.5-cp39-cp39-win_amd64.whl", hash = "sha256:d17920f18e6ee090bdd3d0bfffd769d9f2cb4c8ffde3eb203777a3895c128862"}, + {file = "aiohttp-3.10.5.tar.gz", hash = "sha256:f071854b47d39591ce9a17981c46790acb30518e2f83dfca8db2dfa091178691"}, +] + +[package.dependencies] +aiohappyeyeballs = ">=2.3.0" +aiosignal = ">=1.1.2" +attrs = ">=17.3.0" +frozenlist = ">=1.1.1" +multidict = ">=4.5,<7.0" +yarl = ">=1.0,<2.0" + +[package.extras] +speedups = ["Brotli", "aiodns (>=3.2.0)", "brotlicffi"] + +[[package]] +name = "aiosignal" +version = "1.3.1" +description = "aiosignal: a list of registered asynchronous callbacks" +optional = false +python-versions = ">=3.7" +files = [ + {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, + {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, +] + +[package.dependencies] +frozenlist = ">=1.1.0" + [[package]] name = "alembic" version = "1.13.2" @@ -19,6 +155,39 @@ typing-extensions = ">=4" [package.extras] tz = ["backports.zoneinfo"] +[[package]] +name = "asgiref" +version = "3.8.1" +description = "ASGI specs, helper code, and adapters" +optional = false +python-versions = ">=3.8" +files = [ + {file = "asgiref-3.8.1-py3-none-any.whl", hash = "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47"}, + {file = "asgiref-3.8.1.tar.gz", hash = "sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590"}, +] + +[package.extras] +tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] + +[[package]] +name = "attrs" +version = "24.2.0" +description = "Classes Without Boilerplate" +optional = false +python-versions = ">=3.7" +files = [ + {file = "attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2"}, + {file = "attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346"}, +] + +[package.extras] +benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"] +tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] + [[package]] name = "beautifulsoup4" version = "4.12.3" @@ -380,6 +549,7 @@ files = [ ] [package.dependencies] +asgiref = {version = ">=3.2", optional = true, markers = "extra == \"async\""} blinker = ">=1.6.2" click = ">=8.1.3" itsdangerous = ">=2.1.2" @@ -440,6 +610,92 @@ wtforms = "*" [package.extras] email = ["email-validator"] +[[package]] +name = "frozenlist" +version = "1.4.1" +description = "A list-like structure which implements collections.abc.MutableSequence" +optional = false +python-versions = ">=3.8" +files = [ + {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f9aa1878d1083b276b0196f2dfbe00c9b7e752475ed3b682025ff20c1c1f51ac"}, + {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29acab3f66f0f24674b7dc4736477bcd4bc3ad4b896f5f45379a67bce8b96868"}, + {file = "frozenlist-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74fb4bee6880b529a0c6560885fce4dc95936920f9f20f53d99a213f7bf66776"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:590344787a90ae57d62511dd7c736ed56b428f04cd8c161fcc5e7232c130c69a"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:068b63f23b17df8569b7fdca5517edef76171cf3897eb68beb01341131fbd2ad"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c849d495bf5154cd8da18a9eb15db127d4dba2968d88831aff6f0331ea9bd4c"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9750cc7fe1ae3b1611bb8cfc3f9ec11d532244235d75901fb6b8e42ce9229dfe"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9b2de4cf0cdd5bd2dee4c4f63a653c61d2408055ab77b151c1957f221cabf2a"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0633c8d5337cb5c77acbccc6357ac49a1770b8c487e5b3505c57b949b4b82e98"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:27657df69e8801be6c3638054e202a135c7f299267f1a55ed3a598934f6c0d75"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:f9a3ea26252bd92f570600098783d1371354d89d5f6b7dfd87359d669f2109b5"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:4f57dab5fe3407b6c0c1cc907ac98e8a189f9e418f3b6e54d65a718aaafe3950"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e02a0e11cf6597299b9f3bbd3f93d79217cb90cfd1411aec33848b13f5c656cc"}, + {file = "frozenlist-1.4.1-cp310-cp310-win32.whl", hash = "sha256:a828c57f00f729620a442881cc60e57cfcec6842ba38e1b19fd3e47ac0ff8dc1"}, + {file = "frozenlist-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:f56e2333dda1fe0f909e7cc59f021eba0d2307bc6f012a1ccf2beca6ba362439"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a0cb6f11204443f27a1628b0e460f37fb30f624be6051d490fa7d7e26d4af3d0"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b46c8ae3a8f1f41a0d2ef350c0b6e65822d80772fe46b653ab6b6274f61d4a49"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fde5bd59ab5357e3853313127f4d3565fc7dad314a74d7b5d43c22c6a5ed2ced"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:722e1124aec435320ae01ee3ac7bec11a5d47f25d0ed6328f2273d287bc3abb0"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2471c201b70d58a0f0c1f91261542a03d9a5e088ed3dc6c160d614c01649c106"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c757a9dd70d72b076d6f68efdbb9bc943665ae954dad2801b874c8c69e185068"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f146e0911cb2f1da549fc58fc7bcd2b836a44b79ef871980d605ec392ff6b0d2"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9c515e7914626b2a2e1e311794b4c35720a0be87af52b79ff8e1429fc25f19"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c302220494f5c1ebeb0912ea782bcd5e2f8308037b3c7553fad0e48ebad6ad82"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:442acde1e068288a4ba7acfe05f5f343e19fac87bfc96d89eb886b0363e977ec"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:1b280e6507ea8a4fa0c0a7150b4e526a8d113989e28eaaef946cc77ffd7efc0a"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:fe1a06da377e3a1062ae5fe0926e12b84eceb8a50b350ddca72dc85015873f74"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:db9e724bebd621d9beca794f2a4ff1d26eed5965b004a97f1f1685a173b869c2"}, + {file = "frozenlist-1.4.1-cp311-cp311-win32.whl", hash = "sha256:e774d53b1a477a67838a904131c4b0eef6b3d8a651f8b138b04f748fccfefe17"}, + {file = "frozenlist-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:fb3c2db03683b5767dedb5769b8a40ebb47d6f7f45b1b3e3b4b51ec8ad9d9825"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1979bc0aeb89b33b588c51c54ab0161791149f2461ea7c7c946d95d5f93b56ae"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cc7b01b3754ea68a62bd77ce6020afaffb44a590c2289089289363472d13aedb"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9c92be9fd329ac801cc420e08452b70e7aeab94ea4233a4804f0915c14eba9b"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c3894db91f5a489fc8fa6a9991820f368f0b3cbdb9cd8849547ccfab3392d86"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba60bb19387e13597fb059f32cd4d59445d7b18b69a745b8f8e5db0346f33480"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8aefbba5f69d42246543407ed2461db31006b0f76c4e32dfd6f42215a2c41d09"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780d3a35680ced9ce682fbcf4cb9c2bad3136eeff760ab33707b71db84664e3a"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9acbb16f06fe7f52f441bb6f413ebae6c37baa6ef9edd49cdd567216da8600cd"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:23b701e65c7b36e4bf15546a89279bd4d8675faabc287d06bbcfac7d3c33e1e6"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3e0153a805a98f5ada7e09826255ba99fb4f7524bb81bf6b47fb702666484ae1"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:dd9b1baec094d91bf36ec729445f7769d0d0cf6b64d04d86e45baf89e2b9059b"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:1a4471094e146b6790f61b98616ab8e44f72661879cc63fa1049d13ef711e71e"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5667ed53d68d91920defdf4035d1cdaa3c3121dc0b113255124bcfada1cfa1b8"}, + {file = "frozenlist-1.4.1-cp312-cp312-win32.whl", hash = "sha256:beee944ae828747fd7cb216a70f120767fc9f4f00bacae8543c14a6831673f89"}, + {file = "frozenlist-1.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:64536573d0a2cb6e625cf309984e2d873979709f2cf22839bf2d61790b448ad5"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:20b51fa3f588ff2fe658663db52a41a4f7aa6c04f6201449c6c7c476bd255c0d"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:410478a0c562d1a5bcc2f7ea448359fcb050ed48b3c6f6f4f18c313a9bdb1826"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c6321c9efe29975232da3bd0af0ad216800a47e93d763ce64f291917a381b8eb"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48f6a4533887e189dae092f1cf981f2e3885175f7a0f33c91fb5b7b682b6bab6"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6eb73fa5426ea69ee0e012fb59cdc76a15b1283d6e32e4f8dc4482ec67d1194d"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbeb989b5cc29e8daf7f976b421c220f1b8c731cbf22b9130d8815418ea45887"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32453c1de775c889eb4e22f1197fe3bdfe457d16476ea407472b9442e6295f7a"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693945278a31f2086d9bf3df0fe8254bbeaef1fe71e1351c3bd730aa7d31c41b"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1d0ce09d36d53bbbe566fe296965b23b961764c0bcf3ce2fa45f463745c04701"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3a670dc61eb0d0eb7080890c13de3066790f9049b47b0de04007090807c776b0"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:dca69045298ce5c11fd539682cff879cc1e664c245d1c64da929813e54241d11"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a06339f38e9ed3a64e4c4e43aec7f59084033647f908e4259d279a52d3757d09"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b7f2f9f912dca3934c1baec2e4585a674ef16fe00218d833856408c48d5beee7"}, + {file = "frozenlist-1.4.1-cp38-cp38-win32.whl", hash = "sha256:e7004be74cbb7d9f34553a5ce5fb08be14fb33bc86f332fb71cbe5216362a497"}, + {file = "frozenlist-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:5a7d70357e7cee13f470c7883a063aae5fe209a493c57d86eb7f5a6f910fae09"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bfa4a17e17ce9abf47a74ae02f32d014c5e9404b6d9ac7f729e01562bbee601e"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b7e3ed87d4138356775346e6845cccbe66cd9e207f3cd11d2f0b9fd13681359d"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c99169d4ff810155ca50b4da3b075cbde79752443117d89429595c2e8e37fed8"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edb678da49d9f72c9f6c609fbe41a5dfb9a9282f9e6a2253d5a91e0fc382d7c0"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6db4667b187a6742b33afbbaf05a7bc551ffcf1ced0000a571aedbb4aa42fc7b"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55fdc093b5a3cb41d420884cdaf37a1e74c3c37a31f46e66286d9145d2063bd0"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82e8211d69a4f4bc360ea22cd6555f8e61a1bd211d1d5d39d3d228b48c83a897"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89aa2c2eeb20957be2d950b85974b30a01a762f3308cd02bb15e1ad632e22dc7"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d3e0c25a2350080e9319724dede4f31f43a6c9779be48021a7f4ebde8b2d742"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7268252af60904bf52c26173cbadc3a071cece75f873705419c8681f24d3edea"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0c250a29735d4f15321007fb02865f0e6b6a41a6b88f1f523ca1596ab5f50bd5"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:96ec70beabbd3b10e8bfe52616a13561e58fe84c0101dd031dc78f250d5128b9"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:23b2d7679b73fe0e5a4560b672a39f98dfc6f60df63823b0a9970525325b95f6"}, + {file = "frozenlist-1.4.1-cp39-cp39-win32.whl", hash = "sha256:a7496bfe1da7fb1a4e1cc23bb67c58fab69311cc7d32b5a99c2007b4b2a0e932"}, + {file = "frozenlist-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e6a20a581f9ce92d389a8c7d7c3dd47c81fd5d6e655c8dddf341e14aa48659d0"}, + {file = "frozenlist-1.4.1-py3-none-any.whl", hash = "sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7"}, + {file = "frozenlist-1.4.1.tar.gz", hash = "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b"}, +] + [[package]] name = "greenlet" version = "3.0.3" @@ -670,6 +926,105 @@ files = [ {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, ] +[[package]] +name = "multidict" +version = "6.0.5" +description = "multidict implementation" +optional = false +python-versions = ">=3.7" +files = [ + {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:228b644ae063c10e7f324ab1ab6b548bdf6f8b47f3ec234fef1093bc2735e5f9"}, + {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:896ebdcf62683551312c30e20614305f53125750803b614e9e6ce74a96232604"}, + {file = "multidict-6.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:411bf8515f3be9813d06004cac41ccf7d1cd46dfe233705933dd163b60e37600"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d147090048129ce3c453f0292e7697d333db95e52616b3793922945804a433c"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:215ed703caf15f578dca76ee6f6b21b7603791ae090fbf1ef9d865571039ade5"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c6390cf87ff6234643428991b7359b5f59cc15155695deb4eda5c777d2b880f"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fd81c4ebdb4f214161be351eb5bcf385426bf023041da2fd9e60681f3cebae"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3cc2ad10255f903656017363cd59436f2111443a76f996584d1077e43ee51182"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6939c95381e003f54cd4c5516740faba40cf5ad3eeff460c3ad1d3e0ea2549bf"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:220dd781e3f7af2c2c1053da9fa96d9cf3072ca58f057f4c5adaaa1cab8fc442"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:766c8f7511df26d9f11cd3a8be623e59cca73d44643abab3f8c8c07620524e4a"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:fe5d7785250541f7f5019ab9cba2c71169dc7d74d0f45253f8313f436458a4ef"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1c1496e73051918fcd4f58ff2e0f2f3066d1c76a0c6aeffd9b45d53243702cc"}, + {file = "multidict-6.0.5-cp310-cp310-win32.whl", hash = "sha256:7afcdd1fc07befad18ec4523a782cde4e93e0a2bf71239894b8d61ee578c1319"}, + {file = "multidict-6.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:99f60d34c048c5c2fabc766108c103612344c46e35d4ed9ae0673d33c8fb26e8"}, + {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f285e862d2f153a70586579c15c44656f888806ed0e5b56b64489afe4a2dbfba"}, + {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:53689bb4e102200a4fafa9de9c7c3c212ab40a7ab2c8e474491914d2305f187e"}, + {file = "multidict-6.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:612d1156111ae11d14afaf3a0669ebf6c170dbb735e510a7438ffe2369a847fd"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7be7047bd08accdb7487737631d25735c9a04327911de89ff1b26b81745bd4e3"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de170c7b4fe6859beb8926e84f7d7d6c693dfe8e27372ce3b76f01c46e489fcf"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04bde7a7b3de05732a4eb39c94574db1ec99abb56162d6c520ad26f83267de29"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85f67aed7bb647f93e7520633d8f51d3cbc6ab96957c71272b286b2f30dc70ed"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425bf820055005bfc8aa9a0b99ccb52cc2f4070153e34b701acc98d201693733"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d3eb1ceec286eba8220c26f3b0096cf189aea7057b6e7b7a2e60ed36b373b77f"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7901c05ead4b3fb75113fb1dd33eb1253c6d3ee37ce93305acd9d38e0b5f21a4"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e0e79d91e71b9867c73323a3444724d496c037e578a0e1755ae159ba14f4f3d1"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:29bfeb0dff5cb5fdab2023a7a9947b3b4af63e9c47cae2a10ad58394b517fddc"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e030047e85cbcedbfc073f71836d62dd5dadfbe7531cae27789ff66bc551bd5e"}, + {file = "multidict-6.0.5-cp311-cp311-win32.whl", hash = "sha256:2f4848aa3baa109e6ab81fe2006c77ed4d3cd1e0ac2c1fbddb7b1277c168788c"}, + {file = "multidict-6.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:2faa5ae9376faba05f630d7e5e6be05be22913782b927b19d12b8145968a85ea"}, + {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:51d035609b86722963404f711db441cf7134f1889107fb171a970c9701f92e1e"}, + {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cbebcd5bcaf1eaf302617c114aa67569dd3f090dd0ce8ba9e35e9985b41ac35b"}, + {file = "multidict-6.0.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2ffc42c922dbfddb4a4c3b438eb056828719f07608af27d163191cb3e3aa6cc5"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ceb3b7e6a0135e092de86110c5a74e46bda4bd4fbfeeb3a3bcec79c0f861e450"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:79660376075cfd4b2c80f295528aa6beb2058fd289f4c9252f986751a4cd0496"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4428b29611e989719874670fd152b6625500ad6c686d464e99f5aaeeaca175a"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d84a5c3a5f7ce6db1f999fb9438f686bc2e09d38143f2d93d8406ed2dd6b9226"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76c0de87358b192de7ea9649beb392f107dcad9ad27276324c24c91774ca5271"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:79a6d2ba910adb2cbafc95dad936f8b9386e77c84c35bc0add315b856d7c3abb"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:92d16a3e275e38293623ebf639c471d3e03bb20b8ebb845237e0d3664914caef"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:fb616be3538599e797a2017cccca78e354c767165e8858ab5116813146041a24"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:14c2976aa9038c2629efa2c148022ed5eb4cb939e15ec7aace7ca932f48f9ba6"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:435a0984199d81ca178b9ae2c26ec3d49692d20ee29bc4c11a2a8d4514c67eda"}, + {file = "multidict-6.0.5-cp312-cp312-win32.whl", hash = "sha256:9fe7b0653ba3d9d65cbe7698cca585bf0f8c83dbbcc710db9c90f478e175f2d5"}, + {file = "multidict-6.0.5-cp312-cp312-win_amd64.whl", hash = "sha256:01265f5e40f5a17f8241d52656ed27192be03bfa8764d88e8220141d1e4b3556"}, + {file = "multidict-6.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:19fe01cea168585ba0f678cad6f58133db2aa14eccaf22f88e4a6dccadfad8b3"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bf7a982604375a8d49b6cc1b781c1747f243d91b81035a9b43a2126c04766f5"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:107c0cdefe028703fb5dafe640a409cb146d44a6ae201e55b35a4af8e95457dd"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:403c0911cd5d5791605808b942c88a8155c2592e05332d2bf78f18697a5fa15e"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aeaf541ddbad8311a87dd695ed9642401131ea39ad7bc8cf3ef3967fd093b626"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e4972624066095e52b569e02b5ca97dbd7a7ddd4294bf4e7247d52635630dd83"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d946b0a9eb8aaa590df1fe082cee553ceab173e6cb5b03239716338629c50c7a"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b55358304d7a73d7bdf5de62494aaf70bd33015831ffd98bc498b433dfe5b10c"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:a3145cb08d8625b2d3fee1b2d596a8766352979c9bffe5d7833e0503d0f0b5e5"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d65f25da8e248202bd47445cec78e0025c0fe7582b23ec69c3b27a640dd7a8e3"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c9bf56195c6bbd293340ea82eafd0071cb3d450c703d2c93afb89f93b8386ccc"}, + {file = "multidict-6.0.5-cp37-cp37m-win32.whl", hash = "sha256:69db76c09796b313331bb7048229e3bee7928eb62bab5e071e9f7fcc4879caee"}, + {file = "multidict-6.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:fce28b3c8a81b6b36dfac9feb1de115bab619b3c13905b419ec71d03a3fc1423"}, + {file = "multidict-6.0.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76f067f5121dcecf0d63a67f29080b26c43c71a98b10c701b0677e4a065fbd54"}, + {file = "multidict-6.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b82cc8ace10ab5bd93235dfaab2021c70637005e1ac787031f4d1da63d493c1d"}, + {file = "multidict-6.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5cb241881eefd96b46f89b1a056187ea8e9ba14ab88ba632e68d7a2ecb7aadf7"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8e94e6912639a02ce173341ff62cc1201232ab86b8a8fcc05572741a5dc7d93"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09a892e4a9fb47331da06948690ae38eaa2426de97b4ccbfafbdcbe5c8f37ff8"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55205d03e8a598cfc688c71ca8ea5f66447164efff8869517f175ea632c7cb7b"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37b15024f864916b4951adb95d3a80c9431299080341ab9544ed148091b53f50"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2a1dee728b52b33eebff5072817176c172050d44d67befd681609b4746e1c2e"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:edd08e6f2f1a390bf137080507e44ccc086353c8e98c657e666c017718561b89"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:60d698e8179a42ec85172d12f50b1668254628425a6bd611aba022257cac1386"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:3d25f19500588cbc47dc19081d78131c32637c25804df8414463ec908631e453"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:4cc0ef8b962ac7a5e62b9e826bd0cd5040e7d401bc45a6835910ed699037a461"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:eca2e9d0cc5a889850e9bbd68e98314ada174ff6ccd1129500103df7a94a7a44"}, + {file = "multidict-6.0.5-cp38-cp38-win32.whl", hash = "sha256:4a6a4f196f08c58c59e0b8ef8ec441d12aee4125a7d4f4fef000ccb22f8d7241"}, + {file = "multidict-6.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:0275e35209c27a3f7951e1ce7aaf93ce0d163b28948444bec61dd7badc6d3f8c"}, + {file = "multidict-6.0.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e7be68734bd8c9a513f2b0cfd508802d6609da068f40dc57d4e3494cefc92929"}, + {file = "multidict-6.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1d9ea7a7e779d7a3561aade7d596649fbecfa5c08a7674b11b423783217933f9"}, + {file = "multidict-6.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ea1456df2a27c73ce51120fa2f519f1bea2f4a03a917f4a43c8707cf4cbbae1a"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf590b134eb70629e350691ecca88eac3e3b8b3c86992042fb82e3cb1830d5e1"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5c0631926c4f58e9a5ccce555ad7747d9a9f8b10619621f22f9635f069f6233e"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dce1c6912ab9ff5f179eaf6efe7365c1f425ed690b03341911bf4939ef2f3046"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0868d64af83169e4d4152ec612637a543f7a336e4a307b119e98042e852ad9c"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:141b43360bfd3bdd75f15ed811850763555a251e38b2405967f8e25fb43f7d40"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7df704ca8cf4a073334e0427ae2345323613e4df18cc224f647f251e5e75a527"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6214c5a5571802c33f80e6c84713b2c79e024995b9c5897f794b43e714daeec9"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:cd6c8fca38178e12c00418de737aef1261576bd1b6e8c6134d3e729a4e858b38"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:e02021f87a5b6932fa6ce916ca004c4d441509d33bbdbeca70d05dff5e9d2479"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ebd8d160f91a764652d3e51ce0d2956b38efe37c9231cd82cfc0bed2e40b581c"}, + {file = "multidict-6.0.5-cp39-cp39-win32.whl", hash = "sha256:04da1bb8c8dbadf2a18a452639771951c662c5ad03aefe4884775454be322c9b"}, + {file = "multidict-6.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:d6f6d4f185481c9669b9447bf9d9cf3b95a0e9df9d169bbc17e363b7d5487755"}, + {file = "multidict-6.0.5-py3-none-any.whl", hash = "sha256:0d63c74e3d7ab26de115c49bffc92cc77ed23395303d496eae515d4204a625e7"}, + {file = "multidict-6.0.5.tar.gz", hash = "sha256:f7e301075edaf50500f0b341543c41194d8df3ae5caf4702f2095f3ca73dd8da"}, +] + [[package]] name = "mypy" version = "1.11.1" @@ -1257,7 +1612,110 @@ markupsafe = "*" [package.extras] email = ["email-validator"] +[[package]] +name = "yarl" +version = "1.9.4" +description = "Yet another URL library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a8c1df72eb746f4136fe9a2e72b0c9dc1da1cbd23b5372f94b5820ff8ae30e0e"}, + {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a3a6ed1d525bfb91b3fc9b690c5a21bb52de28c018530ad85093cc488bee2dd2"}, + {file = "yarl-1.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c38c9ddb6103ceae4e4498f9c08fac9b590c5c71b0370f98714768e22ac6fa66"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9e09c9d74f4566e905a0b8fa668c58109f7624db96a2171f21747abc7524234"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8477c1ee4bd47c57d49621a062121c3023609f7a13b8a46953eb6c9716ca392"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5ff2c858f5f6a42c2a8e751100f237c5e869cbde669a724f2062d4c4ef93551"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:357495293086c5b6d34ca9616a43d329317feab7917518bc97a08f9e55648455"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54525ae423d7b7a8ee81ba189f131054defdb122cde31ff17477951464c1691c"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:801e9264d19643548651b9db361ce3287176671fb0117f96b5ac0ee1c3530d53"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e516dc8baf7b380e6c1c26792610230f37147bb754d6426462ab115a02944385"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:7d5aaac37d19b2904bb9dfe12cdb08c8443e7ba7d2852894ad448d4b8f442863"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:54beabb809ffcacbd9d28ac57b0db46e42a6e341a030293fb3185c409e626b8b"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bac8d525a8dbc2a1507ec731d2867025d11ceadcb4dd421423a5d42c56818541"}, + {file = "yarl-1.9.4-cp310-cp310-win32.whl", hash = "sha256:7855426dfbddac81896b6e533ebefc0af2f132d4a47340cee6d22cac7190022d"}, + {file = "yarl-1.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:848cd2a1df56ddbffeb375535fb62c9d1645dde33ca4d51341378b3f5954429b"}, + {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:35a2b9396879ce32754bd457d31a51ff0a9d426fd9e0e3c33394bf4b9036b099"}, + {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c7d56b293cc071e82532f70adcbd8b61909eec973ae9d2d1f9b233f3d943f2c"}, + {file = "yarl-1.9.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d8a1c6c0be645c745a081c192e747c5de06e944a0d21245f4cf7c05e457c36e0"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b3c1ffe10069f655ea2d731808e76e0f452fc6c749bea04781daf18e6039525"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:549d19c84c55d11687ddbd47eeb348a89df9cb30e1993f1b128f4685cd0ebbf8"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7409f968456111140c1c95301cadf071bd30a81cbd7ab829169fb9e3d72eae9"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e23a6d84d9d1738dbc6e38167776107e63307dfc8ad108e580548d1f2c587f42"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8b889777de69897406c9fb0b76cdf2fd0f31267861ae7501d93003d55f54fbe"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:03caa9507d3d3c83bca08650678e25364e1843b484f19986a527630ca376ecce"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e9035df8d0880b2f1c7f5031f33f69e071dfe72ee9310cfc76f7b605958ceb9"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:c0ec0ed476f77db9fb29bca17f0a8fcc7bc97ad4c6c1d8959c507decb22e8572"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:ee04010f26d5102399bd17f8df8bc38dc7ccd7701dc77f4a68c5b8d733406958"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:49a180c2e0743d5d6e0b4d1a9e5f633c62eca3f8a86ba5dd3c471060e352ca98"}, + {file = "yarl-1.9.4-cp311-cp311-win32.whl", hash = "sha256:81eb57278deb6098a5b62e88ad8281b2ba09f2f1147c4767522353eaa6260b31"}, + {file = "yarl-1.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:d1d2532b340b692880261c15aee4dc94dd22ca5d61b9db9a8a361953d36410b1"}, + {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0d2454f0aef65ea81037759be5ca9947539667eecebca092733b2eb43c965a81"}, + {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:44d8ffbb9c06e5a7f529f38f53eda23e50d1ed33c6c869e01481d3fafa6b8142"}, + {file = "yarl-1.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aaaea1e536f98754a6e5c56091baa1b6ce2f2700cc4a00b0d49eca8dea471074"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3777ce5536d17989c91696db1d459574e9a9bd37660ea7ee4d3344579bb6f129"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fc5fc1eeb029757349ad26bbc5880557389a03fa6ada41703db5e068881e5f2"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea65804b5dc88dacd4a40279af0cdadcfe74b3e5b4c897aa0d81cf86927fee78"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa102d6d280a5455ad6a0f9e6d769989638718e938a6a0a2ff3f4a7ff8c62cc4"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09efe4615ada057ba2d30df871d2f668af661e971dfeedf0c159927d48bbeff0"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:008d3e808d03ef28542372d01057fd09168419cdc8f848efe2804f894ae03e51"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6f5cb257bc2ec58f437da2b37a8cd48f666db96d47b8a3115c29f316313654ff"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:992f18e0ea248ee03b5a6e8b3b4738850ae7dbb172cc41c966462801cbf62cf7"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0e9d124c191d5b881060a9e5060627694c3bdd1fe24c5eecc8d5d7d0eb6faabc"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3986b6f41ad22988e53d5778f91855dc0399b043fc8946d4f2e68af22ee9ff10"}, + {file = "yarl-1.9.4-cp312-cp312-win32.whl", hash = "sha256:4b21516d181cd77ebd06ce160ef8cc2a5e9ad35fb1c5930882baff5ac865eee7"}, + {file = "yarl-1.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:a9bd00dc3bc395a662900f33f74feb3e757429e545d831eef5bb280252631984"}, + {file = "yarl-1.9.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:63b20738b5aac74e239622d2fe30df4fca4942a86e31bf47a81a0e94c14df94f"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d7f7de27b8944f1fee2c26a88b4dabc2409d2fea7a9ed3df79b67277644e17"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c74018551e31269d56fab81a728f683667e7c28c04e807ba08f8c9e3bba32f14"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ca06675212f94e7a610e85ca36948bb8fc023e458dd6c63ef71abfd482481aa5"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aef935237d60a51a62b86249839b51345f47564208c6ee615ed2a40878dccdd"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b134fd795e2322b7684155b7855cc99409d10b2e408056db2b93b51a52accc7"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d25039a474c4c72a5ad4b52495056f843a7ff07b632c1b92ea9043a3d9950f6e"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f7d6b36dd2e029b6bcb8a13cf19664c7b8e19ab3a58e0fefbb5b8461447ed5ec"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:957b4774373cf6f709359e5c8c4a0af9f6d7875db657adb0feaf8d6cb3c3964c"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d7eeb6d22331e2fd42fce928a81c697c9ee2d51400bd1a28803965883e13cead"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6a962e04b8f91f8c4e5917e518d17958e3bdee71fd1d8b88cdce74dd0ebbf434"}, + {file = "yarl-1.9.4-cp37-cp37m-win32.whl", hash = "sha256:f3bc6af6e2b8f92eced34ef6a96ffb248e863af20ef4fde9448cc8c9b858b749"}, + {file = "yarl-1.9.4-cp37-cp37m-win_amd64.whl", hash = "sha256:ad4d7a90a92e528aadf4965d685c17dacff3df282db1121136c382dc0b6014d2"}, + {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ec61d826d80fc293ed46c9dd26995921e3a82146feacd952ef0757236fc137be"}, + {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8be9e837ea9113676e5754b43b940b50cce76d9ed7d2461df1af39a8ee674d9f"}, + {file = "yarl-1.9.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bef596fdaa8f26e3d66af846bbe77057237cb6e8efff8cd7cc8dff9a62278bbf"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d47552b6e52c3319fede1b60b3de120fe83bde9b7bddad11a69fb0af7db32f1"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84fc30f71689d7fc9168b92788abc977dc8cefa806909565fc2951d02f6b7d57"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4aa9741085f635934f3a2583e16fcf62ba835719a8b2b28fb2917bb0537c1dfa"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:206a55215e6d05dbc6c98ce598a59e6fbd0c493e2de4ea6cc2f4934d5a18d130"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07574b007ee20e5c375a8fe4a0789fad26db905f9813be0f9fef5a68080de559"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5a2e2433eb9344a163aced6a5f6c9222c0786e5a9e9cac2c89f0b28433f56e23"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6ad6d10ed9b67a382b45f29ea028f92d25bc0bc1daf6c5b801b90b5aa70fb9ec"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:6fe79f998a4052d79e1c30eeb7d6c1c1056ad33300f682465e1b4e9b5a188b78"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a825ec844298c791fd28ed14ed1bffc56a98d15b8c58a20e0e08c1f5f2bea1be"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8619d6915b3b0b34420cf9b2bb6d81ef59d984cb0fde7544e9ece32b4b3043c3"}, + {file = "yarl-1.9.4-cp38-cp38-win32.whl", hash = "sha256:686a0c2f85f83463272ddffd4deb5e591c98aac1897d65e92319f729c320eece"}, + {file = "yarl-1.9.4-cp38-cp38-win_amd64.whl", hash = "sha256:a00862fb23195b6b8322f7d781b0dc1d82cb3bcac346d1e38689370cc1cc398b"}, + {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:604f31d97fa493083ea21bd9b92c419012531c4e17ea6da0f65cacdcf5d0bd27"}, + {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8a854227cf581330ffa2c4824d96e52ee621dd571078a252c25e3a3b3d94a1b1"}, + {file = "yarl-1.9.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ba6f52cbc7809cd8d74604cce9c14868306ae4aa0282016b641c661f981a6e91"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6327976c7c2f4ee6816eff196e25385ccc02cb81427952414a64811037bbc8b"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8397a3817d7dcdd14bb266283cd1d6fc7264a48c186b986f32e86d86d35fbac5"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0381b4ce23ff92f8170080c97678040fc5b08da85e9e292292aba67fdac6c34"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23d32a2594cb5d565d358a92e151315d1b2268bc10f4610d098f96b147370136"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ddb2a5c08a4eaaba605340fdee8fc08e406c56617566d9643ad8bf6852778fc7"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:26a1dc6285e03f3cc9e839a2da83bcbf31dcb0d004c72d0730e755b33466c30e"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:18580f672e44ce1238b82f7fb87d727c4a131f3a9d33a5e0e82b793362bf18b4"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:29e0f83f37610f173eb7e7b5562dd71467993495e568e708d99e9d1944f561ec"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:1f23e4fe1e8794f74b6027d7cf19dc25f8b63af1483d91d595d4a07eca1fb26c"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:db8e58b9d79200c76956cefd14d5c90af54416ff5353c5bfd7cbe58818e26ef0"}, + {file = "yarl-1.9.4-cp39-cp39-win32.whl", hash = "sha256:c7224cab95645c7ab53791022ae77a4509472613e839dab722a72abe5a684575"}, + {file = "yarl-1.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:824d6c50492add5da9374875ce72db7a0733b29c2394890aef23d533106e2b15"}, + {file = "yarl-1.9.4-py3-none-any.whl", hash = "sha256:928cecb0ef9d5a7946eb6ff58417ad2fe9375762382f1bf5c55e61645f2c43ad"}, + {file = "yarl-1.9.4.tar.gz", hash = "sha256:566db86717cf8080b99b58b083b773a908ae40f06681e87e589a976faf8246bf"}, +] + +[package.dependencies] +idna = ">=2.0" +multidict = ">=4.0" + [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "13f4f599996a2a5ba714ed7640bf92e12c8810a8da9720aa3f53748f6e4f3e39" +content-hash = "f7d0125d96d11ce943a53ec0431419c796a309444b57b15ff0020614fc0831d1" diff --git a/pyproject.toml b/pyproject.toml index 5e192cd0e..f341fbf79 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ readme = "README.md" [tool.poetry.dependencies] python = "^3.11" cryptography = "^42.0.5" -flask = "^3.0.2" +flask = {extras = ["async"], version = "^3.0.3"} flask-migrate = "^4.0.7" flask-sqlalchemy = "^3.1.1" flask-wtf = "^1.2.1" @@ -24,6 +24,7 @@ requests = "^2.32.3" types-requests = "^2.32.0.20240712" types-setuptools = "^71.1.0.20240813" email-validator = "^2.2.0" +aiohttp = "^3.10.5" [tool.poetry.group.dev.dependencies] mypy = "^1.10.0" From c114b3aaddb20cec2a410e6dac721903f888fcc9 Mon Sep 17 00:00:00 2001 From: Glenn Date: Fri, 30 Aug 2024 11:12:21 -0700 Subject: [PATCH 21/52] remove unused verify route --- hushline/settings.py | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/hushline/settings.py b/hushline/settings.py index 8c7632003..a199fe71c 100644 --- a/hushline/settings.py +++ b/hushline/settings.py @@ -715,26 +715,4 @@ def delete_account() -> Response | str: flash("User not found. Please log in again.") return redirect(url_for("login")) - @bp.route("/verify-rel-me/", methods=["POST"]) - @authentication_required - def verify_rel_me(field_index: int) -> Response: - user_id = session.get("user_id") - if not user_id: - return redirect(url_for("login")) - - user = db.session.get(User, user_id) - if not user: - flash("User not found.") - return redirect(url_for("login")) - - url_to_verify = request.form.get(f"extra_field_value{field_index}", "").strip() - if not url_to_verify: - flash(f"No URL provided for field {field_index}.") - setattr(user, f"extra_field_verified{field_index}", False) - db.session.commit() - return redirect(url_for(".index")) - - db.session.commit() - return redirect(url_for(".index")) - return bp From 83a47e9a10f1abba0446668872d394fb945c61b7 Mon Sep 17 00:00:00 2001 From: Glenn Date: Fri, 30 Aug 2024 11:16:55 -0700 Subject: [PATCH 22/52] only verify https addresses --- hushline/settings.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hushline/settings.py b/hushline/settings.py index a199fe71c..3a80b5e6a 100644 --- a/hushline/settings.py +++ b/hushline/settings.py @@ -280,8 +280,9 @@ async def perform_verification(): value = getattr(profile_form, f"extra_field_value{i}", "").data setattr(user, f"extra_field_value{i}", value) + # Verify the URL only if it starts with "https://" url_to_verify = value.strip() - if url_to_verify: + if url_to_verify.startswith("https://"): profile_url = ( f"https://tips.hushline.app/to/{user.primary_username}" ) From bcae3d3a3f19a66aee636e9ed90e76a5694e779c Mon Sep 17 00:00:00 2001 From: Glenn Date: Fri, 30 Aug 2024 12:23:37 -0700 Subject: [PATCH 23/52] use bs4 for checking for rel=me --- hushline/settings.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/hushline/settings.py b/hushline/settings.py index 3a80b5e6a..1f51cba83 100644 --- a/hushline/settings.py +++ b/hushline/settings.py @@ -9,6 +9,7 @@ import pyotp import qrcode import requests +from bs4 import BeautifulSoup from flask import ( Blueprint, current_app, @@ -223,13 +224,18 @@ async def verify_url(session, user, i, url_to_verify, profile_url): try: async with session.get(url_to_verify, timeout=5) as response: response.raise_for_status() - if re.search( - rf']+href="{re.escape(profile_url)}"[^>]*rel="me"[^>]*>', - await response.text(), - ): - setattr(user, f"extra_field_verified{i}", True) - else: - setattr(user, f"extra_field_verified{i}", False) + html_content = await response.text() + + soup = BeautifulSoup(html_content, "html.parser") + verified = False + for link in soup.find_all("a"): + href = link.get("href") + rel = link.get("rel", []) + if href == profile_url and "me" in rel: + verified = True + break + + setattr(user, f"extra_field_verified{i}", verified) except aiohttp.ClientError as e: current_app.logger.error(f"Error fetching URL for field {i}: {e}") setattr(user, f"extra_field_verified{i}", False) From 32b7af2578a55b069f2970d07997c238194f91f6 Mon Sep 17 00:00:00 2001 From: Glenn Date: Fri, 30 Aug 2024 12:26:47 -0700 Subject: [PATCH 24/52] only include relevant fields in migrations --- .../versions/83a6b3b09eca_add_verified_fields.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/migrations/versions/83a6b3b09eca_add_verified_fields.py b/migrations/versions/83a6b3b09eca_add_verified_fields.py index d2afcb450..95c8d179e 100644 --- a/migrations/versions/83a6b3b09eca_add_verified_fields.py +++ b/migrations/versions/83a6b3b09eca_add_verified_fields.py @@ -20,18 +20,11 @@ def upgrade(): # ### commands auto generated by Alembic - please adjust! ### - with op.batch_alter_table("message", schema=None) as batch_op: - batch_op.alter_column("content", existing_type=sa.TEXT(), nullable=True) - with op.batch_alter_table("users", schema=None) as batch_op: batch_op.add_column(sa.Column("extra_field_verified1", sa.Boolean(), nullable=True)) batch_op.add_column(sa.Column("extra_field_verified2", sa.Boolean(), nullable=True)) batch_op.add_column(sa.Column("extra_field_verified3", sa.Boolean(), nullable=True)) batch_op.add_column(sa.Column("extra_field_verified4", sa.Boolean(), nullable=True)) - batch_op.alter_column("password_hash", existing_type=sa.VARCHAR(length=512), nullable=False) - batch_op.alter_column("is_verified", existing_type=sa.BOOLEAN(), nullable=False) - batch_op.alter_column("is_admin", existing_type=sa.BOOLEAN(), nullable=False) - batch_op.alter_column("show_in_directory", existing_type=sa.BOOLEAN(), nullable=False) # ### end Alembic commands ### @@ -39,17 +32,9 @@ def upgrade(): def downgrade(): # ### commands auto generated by Alembic - please adjust! ### with op.batch_alter_table("users", schema=None) as batch_op: - batch_op.alter_column("show_in_directory", existing_type=sa.BOOLEAN(), nullable=True) - batch_op.alter_column("is_admin", existing_type=sa.BOOLEAN(), nullable=True) - batch_op.alter_column("is_verified", existing_type=sa.BOOLEAN(), nullable=True) - batch_op.alter_column("password_hash", existing_type=sa.VARCHAR(length=512), nullable=True) batch_op.drop_column("extra_field_verified4") batch_op.drop_column("extra_field_verified3") batch_op.drop_column("extra_field_verified2") batch_op.drop_column("extra_field_verified1") - with op.batch_alter_table("message", schema=None) as batch_op: - batch_op.alter_column("content", existing_type=sa.TEXT(), nullable=False) - - # ### end Alembic commands ### From 3b480340ab27c1543cd8f7617b83bee58dc3cdf4 Mon Sep 17 00:00:00 2001 From: Glenn Date: Fri, 30 Aug 2024 12:29:18 -0700 Subject: [PATCH 25/52] update tests to use beautiful soup --- tests/test_profile.py | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/tests/test_profile.py b/tests/test_profile.py index 644395005..41ccf151c 100644 --- a/tests/test_profile.py +++ b/tests/test_profile.py @@ -1,4 +1,5 @@ from auth_helper import login_user, register_user +from bs4 import BeautifulSoup from flask import Flask from flask.testing import FlaskClient @@ -151,17 +152,21 @@ def test_profile_extra_fields(client: FlaskClient, app: Flask) -> None: response = client.get(f"/to/{user.primary_username}") assert response.status_code == 200 - # The message form should not be displayed, and the PGP warning should be shown - assert b"Signal username" in response.data - assert b"singleusername.666" in response.data - assert b"Arbitrary Link" in response.data - - # URLs should turn into links - check that critical parts of the link are present - assert 'href="https://scidsg.org/"' in response.text - assert 'target="_blank"' in response.text - assert 'rel="noopener noreferrer"' in response.text - - # Check that XSS is correctly escaped - assert b"xss should fail" in response.data - assert b"<script>alert('xss')</script>" in response.data - assert b"" not in response.data + # Check the HTML content using BeautifulSoup + soup = BeautifulSoup(response.data, "html.parser") + + # Verify the signal username is displayed correctly + assert soup.find(string="Signal username") is not None + assert soup.find(string="singleusername.666") is not None + + # Verify the arbitrary link is present with correct attributes + link = soup.find("a", href="https://scidsg.org/") + assert link is not None + assert link.get("target") == "_blank" + assert "noopener" in link.get("rel", []) + assert "noreferrer" in link.get("rel", []) + + # Verify that XSS is correctly escaped + assert soup.find(string="xss should fail") is not None + assert "<script>alert('xss')</script>" in str(soup) + assert "" not in str(soup) From 132417068397e65a572ce7ae04b6eaf3e57048e4 Mon Sep 17 00:00:00 2001 From: Glenn Date: Fri, 30 Aug 2024 12:35:05 -0700 Subject: [PATCH 26/52] Update settings.py --- hushline/settings.py | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/hushline/settings.py b/hushline/settings.py index 1f51cba83..3cd72fef5 100644 --- a/hushline/settings.py +++ b/hushline/settings.py @@ -3,7 +3,7 @@ import io import re from datetime import UTC, datetime -from typing import Optional +from typing import Any, Optional import aiohttp import pyotp @@ -144,7 +144,7 @@ class DirectoryVisibilityForm(FlaskForm): show_in_directory = BooleanField("Show on public directory") -def strip_whitespace(value): +def strip_whitespace(value: Optional[Any]) -> Optional[str]: if value is not None and hasattr(value, "strip"): return value.strip() return value @@ -207,11 +207,11 @@ def unset_field_attribute(input_field: Field, attribute: str) -> None: def set_input_disabled(input_field: Field, disabled: bool = True) -> None: """ - disable the given input + Disable the given input. Args: - inputField(Input): the WTForms input to disable - disabled(bool): if true set the disabled attribute of the input + inputField (Field): The WTForms input to disable. + disabled (bool): If true, set the disabled attribute of the input. """ if disabled: set_field_attribute(input_field, "disabled", "disabled") @@ -219,10 +219,11 @@ def set_input_disabled(input_field: Field, disabled: bool = True) -> None: unset_field_attribute(input_field, "disabled") -# Define the async function for URL verification -async def verify_url(session, user, i, url_to_verify, profile_url): +async def verify_url( + session: aiohttp.ClientSession, user: User, i: int, url_to_verify: str, profile_url: str +) -> None: try: - async with session.get(url_to_verify, timeout=5) as response: + async with session.get(url_to_verify, timeout=aiohttp.ClientTimeout(total=5)) as response: response.raise_for_status() html_content = await response.text() @@ -277,17 +278,20 @@ def index() -> str | Response: if "update_bio" in request.form and profile_form.validate_on_submit(): user.bio = profile_form.bio.data - async def perform_verification(): + async def perform_verification() -> None: async with aiohttp.ClientSession() as session: tasks = [] for i in range(1, 5): - label = getattr(profile_form, f"extra_field_label{i}", "").data - setattr(user, f"extra_field_label{i}", label) - value = getattr(profile_form, f"extra_field_value{i}", "").data - setattr(user, f"extra_field_value{i}", value) + label = getattr(profile_form, f"extra_field_label{i}", "") + if isinstance(label, Field): + setattr(user, f"extra_field_label{i}", label.data) + + value = getattr(profile_form, f"extra_field_value{i}", "") + if isinstance(value, Field): + setattr(user, f"extra_field_value{i}", value.data) # Verify the URL only if it starts with "https://" - url_to_verify = value.strip() + url_to_verify = value.strip() if isinstance(value, str) else "" if url_to_verify.startswith("https://"): profile_url = ( f"https://tips.hushline.app/to/{user.primary_username}" From 099e9d686f6805edbc265097241331e8ea91a55d Mon Sep 17 00:00:00 2001 From: Glenn Date: Fri, 30 Aug 2024 12:48:02 -0700 Subject: [PATCH 27/52] Revert "use bs4 for checking for rel=me" This reverts commit bcae3d3a3f19a66aee636e9ed90e76a5694e779c. --- hushline/settings.py | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/hushline/settings.py b/hushline/settings.py index 3cd72fef5..84cf7f39f 100644 --- a/hushline/settings.py +++ b/hushline/settings.py @@ -9,7 +9,6 @@ import pyotp import qrcode import requests -from bs4 import BeautifulSoup from flask import ( Blueprint, current_app, @@ -225,18 +224,13 @@ async def verify_url( try: async with session.get(url_to_verify, timeout=aiohttp.ClientTimeout(total=5)) as response: response.raise_for_status() - html_content = await response.text() - - soup = BeautifulSoup(html_content, "html.parser") - verified = False - for link in soup.find_all("a"): - href = link.get("href") - rel = link.get("rel", []) - if href == profile_url and "me" in rel: - verified = True - break - - setattr(user, f"extra_field_verified{i}", verified) + if re.search( + rf']+href="{re.escape(profile_url)}"[^>]*rel="me"[^>]*>', + await response.text(), + ): + setattr(user, f"extra_field_verified{i}", True) + else: + setattr(user, f"extra_field_verified{i}", False) except aiohttp.ClientError as e: current_app.logger.error(f"Error fetching URL for field {i}: {e}") setattr(user, f"extra_field_verified{i}", False) From 152c64882efdd2f06e3cc2f0372d8f113ec49926 Mon Sep 17 00:00:00 2001 From: Glenn Date: Fri, 30 Aug 2024 12:48:51 -0700 Subject: [PATCH 28/52] Reapply "use bs4 for checking for rel=me" This reverts commit 099e9d686f6805edbc265097241331e8ea91a55d. --- hushline/settings.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/hushline/settings.py b/hushline/settings.py index 84cf7f39f..3cd72fef5 100644 --- a/hushline/settings.py +++ b/hushline/settings.py @@ -9,6 +9,7 @@ import pyotp import qrcode import requests +from bs4 import BeautifulSoup from flask import ( Blueprint, current_app, @@ -224,13 +225,18 @@ async def verify_url( try: async with session.get(url_to_verify, timeout=aiohttp.ClientTimeout(total=5)) as response: response.raise_for_status() - if re.search( - rf']+href="{re.escape(profile_url)}"[^>]*rel="me"[^>]*>', - await response.text(), - ): - setattr(user, f"extra_field_verified{i}", True) - else: - setattr(user, f"extra_field_verified{i}", False) + html_content = await response.text() + + soup = BeautifulSoup(html_content, "html.parser") + verified = False + for link in soup.find_all("a"): + href = link.get("href") + rel = link.get("rel", []) + if href == profile_url and "me" in rel: + verified = True + break + + setattr(user, f"extra_field_verified{i}", verified) except aiohttp.ClientError as e: current_app.logger.error(f"Error fetching URL for field {i}: {e}") setattr(user, f"extra_field_verified{i}", False) From 0199e606aba9a284ddd3aa8a9bfbc497b896a5d4 Mon Sep 17 00:00:00 2001 From: Glenn Date: Fri, 30 Aug 2024 12:49:55 -0700 Subject: [PATCH 29/52] Revert "Reapply "use bs4 for checking for rel=me"" This reverts commit 152c64882efdd2f06e3cc2f0372d8f113ec49926. --- hushline/settings.py | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/hushline/settings.py b/hushline/settings.py index 3cd72fef5..84cf7f39f 100644 --- a/hushline/settings.py +++ b/hushline/settings.py @@ -9,7 +9,6 @@ import pyotp import qrcode import requests -from bs4 import BeautifulSoup from flask import ( Blueprint, current_app, @@ -225,18 +224,13 @@ async def verify_url( try: async with session.get(url_to_verify, timeout=aiohttp.ClientTimeout(total=5)) as response: response.raise_for_status() - html_content = await response.text() - - soup = BeautifulSoup(html_content, "html.parser") - verified = False - for link in soup.find_all("a"): - href = link.get("href") - rel = link.get("rel", []) - if href == profile_url and "me" in rel: - verified = True - break - - setattr(user, f"extra_field_verified{i}", verified) + if re.search( + rf']+href="{re.escape(profile_url)}"[^>]*rel="me"[^>]*>', + await response.text(), + ): + setattr(user, f"extra_field_verified{i}", True) + else: + setattr(user, f"extra_field_verified{i}", False) except aiohttp.ClientError as e: current_app.logger.error(f"Error fetching URL for field {i}: {e}") setattr(user, f"extra_field_verified{i}", False) From 5b81a99db82080a966a6a05cdfd8e51a70e08713 Mon Sep 17 00:00:00 2001 From: Glenn Date: Fri, 30 Aug 2024 12:50:00 -0700 Subject: [PATCH 30/52] Revert "Update settings.py" This reverts commit 132417068397e65a572ce7ae04b6eaf3e57048e4. --- hushline/settings.py | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/hushline/settings.py b/hushline/settings.py index 84cf7f39f..3a80b5e6a 100644 --- a/hushline/settings.py +++ b/hushline/settings.py @@ -3,7 +3,7 @@ import io import re from datetime import UTC, datetime -from typing import Any, Optional +from typing import Optional import aiohttp import pyotp @@ -143,7 +143,7 @@ class DirectoryVisibilityForm(FlaskForm): show_in_directory = BooleanField("Show on public directory") -def strip_whitespace(value: Optional[Any]) -> Optional[str]: +def strip_whitespace(value): if value is not None and hasattr(value, "strip"): return value.strip() return value @@ -206,11 +206,11 @@ def unset_field_attribute(input_field: Field, attribute: str) -> None: def set_input_disabled(input_field: Field, disabled: bool = True) -> None: """ - Disable the given input. + disable the given input Args: - inputField (Field): The WTForms input to disable. - disabled (bool): If true, set the disabled attribute of the input. + inputField(Input): the WTForms input to disable + disabled(bool): if true set the disabled attribute of the input """ if disabled: set_field_attribute(input_field, "disabled", "disabled") @@ -218,11 +218,10 @@ def set_input_disabled(input_field: Field, disabled: bool = True) -> None: unset_field_attribute(input_field, "disabled") -async def verify_url( - session: aiohttp.ClientSession, user: User, i: int, url_to_verify: str, profile_url: str -) -> None: +# Define the async function for URL verification +async def verify_url(session, user, i, url_to_verify, profile_url): try: - async with session.get(url_to_verify, timeout=aiohttp.ClientTimeout(total=5)) as response: + async with session.get(url_to_verify, timeout=5) as response: response.raise_for_status() if re.search( rf']+href="{re.escape(profile_url)}"[^>]*rel="me"[^>]*>', @@ -272,20 +271,17 @@ def index() -> str | Response: if "update_bio" in request.form and profile_form.validate_on_submit(): user.bio = profile_form.bio.data - async def perform_verification() -> None: + async def perform_verification(): async with aiohttp.ClientSession() as session: tasks = [] for i in range(1, 5): - label = getattr(profile_form, f"extra_field_label{i}", "") - if isinstance(label, Field): - setattr(user, f"extra_field_label{i}", label.data) - - value = getattr(profile_form, f"extra_field_value{i}", "") - if isinstance(value, Field): - setattr(user, f"extra_field_value{i}", value.data) + label = getattr(profile_form, f"extra_field_label{i}", "").data + setattr(user, f"extra_field_label{i}", label) + value = getattr(profile_form, f"extra_field_value{i}", "").data + setattr(user, f"extra_field_value{i}", value) # Verify the URL only if it starts with "https://" - url_to_verify = value.strip() if isinstance(value, str) else "" + url_to_verify = value.strip() if url_to_verify.startswith("https://"): profile_url = ( f"https://tips.hushline.app/to/{user.primary_username}" From 926126280727c08cfb04d8a43d3a2a897b59e515 Mon Sep 17 00:00:00 2001 From: Glenn Date: Fri, 30 Aug 2024 12:54:33 -0700 Subject: [PATCH 31/52] fix linting --- hushline/settings.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/hushline/settings.py b/hushline/settings.py index 3a80b5e6a..f66e7fb74 100644 --- a/hushline/settings.py +++ b/hushline/settings.py @@ -3,7 +3,7 @@ import io import re from datetime import UTC, datetime -from typing import Optional +from typing import Any, Optional import aiohttp import pyotp @@ -143,7 +143,7 @@ class DirectoryVisibilityForm(FlaskForm): show_in_directory = BooleanField("Show on public directory") -def strip_whitespace(value): +def strip_whitespace(value: Optional[Any]) -> Optional[str]: if value is not None and hasattr(value, "strip"): return value.strip() return value @@ -219,9 +219,11 @@ def set_input_disabled(input_field: Field, disabled: bool = True) -> None: # Define the async function for URL verification -async def verify_url(session, user, i, url_to_verify, profile_url): +async def verify_url( + session: aiohttp.ClientSession, user: User, i: int, url_to_verify: str, profile_url: str +) -> None: try: - async with session.get(url_to_verify, timeout=5) as response: + async with session.get(url_to_verify, timeout=aiohttp.ClientTimeout(total=5)) as response: response.raise_for_status() if re.search( rf']+href="{re.escape(profile_url)}"[^>]*rel="me"[^>]*>', @@ -271,17 +273,19 @@ def index() -> str | Response: if "update_bio" in request.form and profile_form.validate_on_submit(): user.bio = profile_form.bio.data - async def perform_verification(): + async def perform_verification() -> None: async with aiohttp.ClientSession() as session: tasks = [] for i in range(1, 5): - label = getattr(profile_form, f"extra_field_label{i}", "").data - setattr(user, f"extra_field_label{i}", label) - value = getattr(profile_form, f"extra_field_value{i}", "").data - setattr(user, f"extra_field_value{i}", value) + label = getattr(profile_form, f"extra_field_label{i}", None) + if isinstance(label, Field): + setattr(user, f"extra_field_label{i}", label.data) + value = getattr(profile_form, f"extra_field_value{i}", None) + if isinstance(value, Field): + setattr(user, f"extra_field_value{i}", value.data) # Verify the URL only if it starts with "https://" - url_to_verify = value.strip() + url_to_verify = value.data.strip() if isinstance(value, Field) else "" if url_to_verify.startswith("https://"): profile_url = ( f"https://tips.hushline.app/to/{user.primary_username}" From f517c8d0436fe45ad927042d8d8a70b625256a39 Mon Sep 17 00:00:00 2001 From: Glenn Date: Fri, 30 Aug 2024 12:57:42 -0700 Subject: [PATCH 32/52] use bs4 for rel=me --- hushline/settings.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/hushline/settings.py b/hushline/settings.py index f66e7fb74..f255192e1 100644 --- a/hushline/settings.py +++ b/hushline/settings.py @@ -9,6 +9,7 @@ import pyotp import qrcode import requests +from bs4 import BeautifulSoup from flask import ( Blueprint, current_app, @@ -225,13 +226,18 @@ async def verify_url( try: async with session.get(url_to_verify, timeout=aiohttp.ClientTimeout(total=5)) as response: response.raise_for_status() - if re.search( - rf']+href="{re.escape(profile_url)}"[^>]*rel="me"[^>]*>', - await response.text(), - ): - setattr(user, f"extra_field_verified{i}", True) - else: - setattr(user, f"extra_field_verified{i}", False) + html_content = await response.text() + + soup = BeautifulSoup(html_content, "html.parser") + verified = False + for link in soup.find_all("a"): + href = link.get("href") + rel = link.get("rel", []) + if href == profile_url and "me" in rel: + verified = True + break + + setattr(user, f"extra_field_verified{i}", verified) except aiohttp.ClientError as e: current_app.logger.error(f"Error fetching URL for field {i}: {e}") setattr(user, f"extra_field_verified{i}", False) From c66b88c77f8fff7d466ee905954b9be499632102 Mon Sep 17 00:00:00 2001 From: Glenn Date: Fri, 30 Aug 2024 13:08:48 -0700 Subject: [PATCH 33/52] update tests --- tests/test_profile.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/tests/test_profile.py b/tests/test_profile.py index 41ccf151c..2387bb797 100644 --- a/tests/test_profile.py +++ b/tests/test_profile.py @@ -156,8 +156,9 @@ def test_profile_extra_fields(client: FlaskClient, app: Flask) -> None: soup = BeautifulSoup(response.data, "html.parser") # Verify the signal username is displayed correctly - assert soup.find(string="Signal username") is not None - assert soup.find(string="singleusername.666") is not None + signal_username_span = soup.find("span", class_="extra-field-value") + assert signal_username_span is not None + assert signal_username_span.text.strip() == "singleusername.666" # Verify the arbitrary link is present with correct attributes link = soup.find("a", href="https://scidsg.org/") @@ -167,6 +168,8 @@ def test_profile_extra_fields(client: FlaskClient, app: Flask) -> None: assert "noreferrer" in link.get("rel", []) # Verify that XSS is correctly escaped - assert soup.find(string="xss should fail") is not None - assert "<script>alert('xss')</script>" in str(soup) + # Search for the XSS string directly in the HTML with both possible escapes + assert "<script>alert('xss')</script>" in str( + soup + ) or "<script>alert('xss')</script>" in str(soup) assert "" not in str(soup) From bb35ee2aad4ea772ffc3fbdb47da7aed1ec382c1 Mon Sep 17 00:00:00 2001 From: Glenn Date: Fri, 30 Aug 2024 14:47:24 -0700 Subject: [PATCH 34/52] clear badge when field clears --- hushline/settings.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/hushline/settings.py b/hushline/settings.py index f255192e1..75b832ef2 100644 --- a/hushline/settings.py +++ b/hushline/settings.py @@ -279,19 +279,22 @@ def index() -> str | Response: if "update_bio" in request.form and profile_form.validate_on_submit(): user.bio = profile_form.bio.data - async def perform_verification() -> None: + async def perform_verification(): async with aiohttp.ClientSession() as session: tasks = [] for i in range(1, 5): - label = getattr(profile_form, f"extra_field_label{i}", None) - if isinstance(label, Field): - setattr(user, f"extra_field_label{i}", label.data) - value = getattr(profile_form, f"extra_field_value{i}", None) - if isinstance(value, Field): - setattr(user, f"extra_field_value{i}", value.data) + label = getattr(profile_form, f"extra_field_label{i}", "").data + setattr(user, f"extra_field_label{i}", label) + value = getattr(profile_form, f"extra_field_value{i}", "").data + setattr(user, f"extra_field_value{i}", value) + + # If the value is empty, reset the verification status + if not value.strip(): + setattr(user, f"extra_field_verified{i}", False) + continue # Verify the URL only if it starts with "https://" - url_to_verify = value.data.strip() if isinstance(value, Field) else "" + url_to_verify = value.strip() if url_to_verify.startswith("https://"): profile_url = ( f"https://tips.hushline.app/to/{user.primary_username}" From 7014028e4727c26ddf43a7af261f068acbd33054 Mon Sep 17 00:00:00 2001 From: Glenn Date: Fri, 30 Aug 2024 14:50:59 -0700 Subject: [PATCH 35/52] Update settings.py --- hushline/settings.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/hushline/settings.py b/hushline/settings.py index 75b832ef2..7c93a12f0 100644 --- a/hushline/settings.py +++ b/hushline/settings.py @@ -279,13 +279,21 @@ def index() -> str | Response: if "update_bio" in request.form and profile_form.validate_on_submit(): user.bio = profile_form.bio.data - async def perform_verification(): + async def perform_verification() -> None: async with aiohttp.ClientSession() as session: tasks = [] for i in range(1, 5): - label = getattr(profile_form, f"extra_field_label{i}", "").data + label_field = getattr(profile_form, f"extra_field_label{i}", "") + value_field = getattr(profile_form, f"extra_field_value{i}", "") + + label = ( + label_field.data if hasattr(label_field, "data") else label_field + ) setattr(user, f"extra_field_label{i}", label) - value = getattr(profile_form, f"extra_field_value{i}", "").data + + value = ( + value_field.data if hasattr(value_field, "data") else value_field + ) setattr(user, f"extra_field_value{i}", value) # If the value is empty, reset the verification status From 6d26be5248b1764568442c8f92aa5945fd85cf91 Mon Sep 17 00:00:00 2001 From: Glenn Date: Fri, 30 Aug 2024 21:13:05 -0700 Subject: [PATCH 36/52] standardize schema --- hushline/model.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hushline/model.py b/hushline/model.py index 82d00524a..c14d7ffba 100644 --- a/hushline/model.py +++ b/hushline/model.py @@ -63,9 +63,9 @@ class User(Model): extra_field_label4: Mapped[Optional[str]] extra_field_value4: Mapped[Optional[str]] extra_field_verified1: Mapped[Optional[bool]] = mapped_column(default=False) - extra_field_verified2 = db.Column(db.Boolean, default=False) - extra_field_verified3 = db.Column(db.Boolean, default=False) - extra_field_verified4 = db.Column(db.Boolean, default=False) + extra_field_verified2: Mapped[Optional[bool]] = mapped_column(default=False) + extra_field_verified3: Mapped[Optional[bool]] = mapped_column(default=False) + extra_field_verified4: Mapped[Optional[bool]] = mapped_column(default=False) @property def password_hash(self) -> str: From bf12ad7cd2a6423780a6f1d8bb20ba0909ab4bbb Mon Sep 17 00:00:00 2001 From: Glenn Date: Fri, 30 Aug 2024 21:22:38 -0700 Subject: [PATCH 37/52] Update settings.py --- hushline/settings.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/hushline/settings.py b/hushline/settings.py index 7c93a12f0..7f6723d36 100644 --- a/hushline/settings.py +++ b/hushline/settings.py @@ -307,10 +307,13 @@ async def perform_verification() -> None: profile_url = ( f"https://tips.hushline.app/to/{user.primary_username}" ) + # Schedule async task for verification task = verify_url(session, user, i, url_to_verify, profile_url) tasks.append(task) - await asyncio.gather(*tasks) + # Run all the tasks concurrently + if tasks: # Only gather if there are tasks to run + await asyncio.gather(*tasks) # Run the async verification function asyncio.run(perform_verification()) From a5641aa467e03af9af67d866a21ba35bdf1361f6 Mon Sep 17 00:00:00 2001 From: Glenn Date: Tue, 3 Sep 2024 10:47:09 -0700 Subject: [PATCH 38/52] make verification URL an env variable --- hushline/__init__.py | 3 +++ hushline/settings.py | 6 ++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/hushline/__init__.py b/hushline/__init__.py index d63a37795..f36d2f4ab 100644 --- a/hushline/__init__.py +++ b/hushline/__init__.py @@ -47,6 +47,9 @@ def create_app() -> Flask: app.config["SMTP_ENCRYPTION"] = os.environ.get("SMTP_ENCRYPTION", "StartTLS") app.config["REQUIRE_PGP"] = os.environ.get("REQUIRE_PGP", "False").lower() == "true" + # Handle the tips domain for profile verification + app.config["HUSHLINE_TIPS_URL"] = os.getenv("HUSHLINE_TIPS_URL", "https://tips.hushline.app") + # Run migrations db.init_app(app) Migrate(app, db) diff --git a/hushline/settings.py b/hushline/settings.py index 7f6723d36..aab600f2f 100644 --- a/hushline/settings.py +++ b/hushline/settings.py @@ -304,10 +304,8 @@ async def perform_verification() -> None: # Verify the URL only if it starts with "https://" url_to_verify = value.strip() if url_to_verify.startswith("https://"): - profile_url = ( - f"https://tips.hushline.app/to/{user.primary_username}" - ) - # Schedule async task for verification + profile_url = f"{current_app.config['HUSHLINE_TIPS_URL']}/to/{user.primary_username}" + task = verify_url(session, user, i, url_to_verify, profile_url) tasks.append(task) From 89695db749ab625824074f22566f798169bc33d5 Mon Sep 17 00:00:00 2001 From: Glenn Date: Tue, 3 Sep 2024 10:51:30 -0700 Subject: [PATCH 39/52] Update settings.py --- hushline/settings.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hushline/settings.py b/hushline/settings.py index aab600f2f..28acfa4ce 100644 --- a/hushline/settings.py +++ b/hushline/settings.py @@ -304,7 +304,8 @@ async def perform_verification() -> None: # Verify the URL only if it starts with "https://" url_to_verify = value.strip() if url_to_verify.startswith("https://"): - profile_url = f"{current_app.config['HUSHLINE_TIPS_URL']}/to/{user.primary_username}" + base_url = current_app.config["HUSHLINE_TIPS_URL"] + profile_url = f"{base_url}/to/{user.primary_username}" task = verify_url(session, user, i, url_to_verify, profile_url) tasks.append(task) From e5f7ea46cbcdff5a3ccf6ef45033da4ff3b675c1 Mon Sep 17 00:00:00 2001 From: Jeremy Moore Date: Tue, 3 Sep 2024 16:20:32 -0400 Subject: [PATCH 40/52] make index route handler async --- hushline/settings.py | 74 +++++++++++++++++++++----------------------- 1 file changed, 35 insertions(+), 39 deletions(-) diff --git a/hushline/settings.py b/hushline/settings.py index 28acfa4ce..8b6b84559 100644 --- a/hushline/settings.py +++ b/hushline/settings.py @@ -246,9 +246,9 @@ async def verify_url( def create_blueprint() -> Blueprint: bp = Blueprint("settings", __file__, url_prefix="/settings") - @bp.route("/", methods=["GET", "POST"]) @authentication_required - def index() -> str | Response: + @bp.route("/", methods=["GET", "POST"]) + async def index() -> str | Response: user_id = session.get("user_id") if not user_id: return redirect(url_for("login")) @@ -279,43 +279,39 @@ def index() -> str | Response: if "update_bio" in request.form and profile_form.validate_on_submit(): user.bio = profile_form.bio.data - async def perform_verification() -> None: - async with aiohttp.ClientSession() as session: - tasks = [] - for i in range(1, 5): - label_field = getattr(profile_form, f"extra_field_label{i}", "") - value_field = getattr(profile_form, f"extra_field_value{i}", "") - - label = ( - label_field.data if hasattr(label_field, "data") else label_field - ) - setattr(user, f"extra_field_label{i}", label) - - value = ( - value_field.data if hasattr(value_field, "data") else value_field - ) - setattr(user, f"extra_field_value{i}", value) - - # If the value is empty, reset the verification status - if not value.strip(): - setattr(user, f"extra_field_verified{i}", False) - continue - - # Verify the URL only if it starts with "https://" - url_to_verify = value.strip() - if url_to_verify.startswith("https://"): - base_url = current_app.config["HUSHLINE_TIPS_URL"] - profile_url = f"{base_url}/to/{user.primary_username}" - - task = verify_url(session, user, i, url_to_verify, profile_url) - tasks.append(task) - - # Run all the tasks concurrently - if tasks: # Only gather if there are tasks to run - await asyncio.gather(*tasks) - - # Run the async verification function - asyncio.run(perform_verification()) + base_url = current_app.config["HUSHLINE_TIPS_URL"] + profile_url = f"{base_url}/to/{user.primary_username}" + async with aiohttp.ClientSession() as client_session: + tasks = [] + for i in range(1, 5): + label_field = getattr(profile_form, f"extra_field_label{i}", "") + value_field = getattr(profile_form, f"extra_field_value{i}", "") + + label = ( + label_field.data if hasattr(label_field, "data") else label_field + ) + setattr(user, f"extra_field_label{i}", label) + + value = ( + value_field.data if hasattr(value_field, "data") else value_field + ) + setattr(user, f"extra_field_value{i}", value) + + # If the value is empty, reset the verification status + if not value.strip(): + setattr(user, f"extra_field_verified{i}", False) + continue + + # Verify the URL only if it starts with "https://" + url_to_verify = value.strip() + if url_to_verify.startswith("https://"): + + task = verify_url(client_session, user, i, url_to_verify, profile_url) + tasks.append(task) + + # Run all the tasks concurrently + if tasks: # Only gather if there are tasks to run + await asyncio.gather(*tasks) db.session.commit() flash("👍 Bio and fields updated successfully.") From 57a450db64e6913dc3faf3db73c24b8075697859 Mon Sep 17 00:00:00 2001 From: Jeremy Moore Date: Tue, 3 Sep 2024 16:26:14 -0400 Subject: [PATCH 41/52] make it pretty --- hushline/settings.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/hushline/settings.py b/hushline/settings.py index 8b6b84559..a5ac52e26 100644 --- a/hushline/settings.py +++ b/hushline/settings.py @@ -287,14 +287,10 @@ async def index() -> str | Response: label_field = getattr(profile_form, f"extra_field_label{i}", "") value_field = getattr(profile_form, f"extra_field_value{i}", "") - label = ( - label_field.data if hasattr(label_field, "data") else label_field - ) + label = label_field.data if hasattr(label_field, "data") else label_field setattr(user, f"extra_field_label{i}", label) - value = ( - value_field.data if hasattr(value_field, "data") else value_field - ) + value = value_field.data if hasattr(value_field, "data") else value_field setattr(user, f"extra_field_value{i}", value) # If the value is empty, reset the verification status @@ -305,7 +301,6 @@ async def index() -> str | Response: # Verify the URL only if it starts with "https://" url_to_verify = value.strip() if url_to_verify.startswith("https://"): - task = verify_url(client_session, user, i, url_to_verify, profile_url) tasks.append(task) From a6406235679db1f86da6e8ed67b392ba229324a8 Mon Sep 17 00:00:00 2001 From: Glenn Date: Wed, 4 Sep 2024 09:51:00 -0700 Subject: [PATCH 42/52] Update poetry.lock --- poetry.lock | 347 ++++++++++++++++++++++++---------------------------- 1 file changed, 163 insertions(+), 184 deletions(-) diff --git a/poetry.lock b/poetry.lock index 89542d669..adb04b8c5 100644 --- a/poetry.lock +++ b/poetry.lock @@ -236,13 +236,13 @@ beautifulsoup4 = "*" [[package]] name = "certifi" -version = "2024.7.4" +version = "2024.8.30" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, - {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, + {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, + {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, ] [[package]] @@ -785,13 +785,13 @@ tornado = ["tornado (>=0.2)"] [[package]] name = "idna" -version = "3.7" +version = "3.8" description = "Internationalized Domain Names in Applications (IDNA)" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" files = [ - {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, - {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, + {file = "idna-3.8-py3-none-any.whl", hash = "sha256:050b4e5baadcd44d760cedbd2b8e639f2ff89bbc7a5730fcc662954303377aac"}, + {file = "idna-3.8.tar.gz", hash = "sha256:d838c2c0ed6fced7693d5e8ab8e734d5f8fda53a039c0164afb0b82e771e3603"}, ] [[package]] @@ -1022,38 +1022,38 @@ files = [ [[package]] name = "mypy" -version = "1.11.1" +version = "1.11.2" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" files = [ - {file = "mypy-1.11.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a32fc80b63de4b5b3e65f4be82b4cfa362a46702672aa6a0f443b4689af7008c"}, - {file = "mypy-1.11.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c1952f5ea8a5a959b05ed5f16452fddadbaae48b5d39235ab4c3fc444d5fd411"}, - {file = "mypy-1.11.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1e30dc3bfa4e157e53c1d17a0dad20f89dc433393e7702b813c10e200843b03"}, - {file = "mypy-1.11.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2c63350af88f43a66d3dfeeeb8d77af34a4f07d760b9eb3a8697f0386c7590b4"}, - {file = "mypy-1.11.1-cp310-cp310-win_amd64.whl", hash = "sha256:a831671bad47186603872a3abc19634f3011d7f83b083762c942442d51c58d58"}, - {file = "mypy-1.11.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7b6343d338390bb946d449677726edf60102a1c96079b4f002dedff375953fc5"}, - {file = "mypy-1.11.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e4fe9f4e5e521b458d8feb52547f4bade7ef8c93238dfb5bbc790d9ff2d770ca"}, - {file = "mypy-1.11.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:886c9dbecc87b9516eff294541bf7f3655722bf22bb898ee06985cd7269898de"}, - {file = "mypy-1.11.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fca4a60e1dd9fd0193ae0067eaeeb962f2d79e0d9f0f66223a0682f26ffcc809"}, - {file = "mypy-1.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:0bd53faf56de9643336aeea1c925012837432b5faf1701ccca7fde70166ccf72"}, - {file = "mypy-1.11.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f39918a50f74dc5969807dcfaecafa804fa7f90c9d60506835036cc1bc891dc8"}, - {file = "mypy-1.11.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0bc71d1fb27a428139dd78621953effe0d208aed9857cb08d002280b0422003a"}, - {file = "mypy-1.11.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b868d3bcff720dd7217c383474008ddabaf048fad8d78ed948bb4b624870a417"}, - {file = "mypy-1.11.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a707ec1527ffcdd1c784d0924bf5cb15cd7f22683b919668a04d2b9c34549d2e"}, - {file = "mypy-1.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:64f4a90e3ea07f590c5bcf9029035cf0efeae5ba8be511a8caada1a4893f5525"}, - {file = "mypy-1.11.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:749fd3213916f1751fff995fccf20c6195cae941dc968f3aaadf9bb4e430e5a2"}, - {file = "mypy-1.11.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b639dce63a0b19085213ec5fdd8cffd1d81988f47a2dec7100e93564f3e8fb3b"}, - {file = "mypy-1.11.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c956b49c5d865394d62941b109728c5c596a415e9c5b2be663dd26a1ff07bc0"}, - {file = "mypy-1.11.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:45df906e8b6804ef4b666af29a87ad9f5921aad091c79cc38e12198e220beabd"}, - {file = "mypy-1.11.1-cp38-cp38-win_amd64.whl", hash = "sha256:d44be7551689d9d47b7abc27c71257adfdb53f03880841a5db15ddb22dc63edb"}, - {file = "mypy-1.11.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2684d3f693073ab89d76da8e3921883019ea8a3ec20fa5d8ecca6a2db4c54bbe"}, - {file = "mypy-1.11.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:79c07eb282cb457473add5052b63925e5cc97dfab9812ee65a7c7ab5e3cb551c"}, - {file = "mypy-1.11.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11965c2f571ded6239977b14deebd3f4c3abd9a92398712d6da3a772974fad69"}, - {file = "mypy-1.11.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a2b43895a0f8154df6519706d9bca8280cda52d3d9d1514b2d9c3e26792a0b74"}, - {file = "mypy-1.11.1-cp39-cp39-win_amd64.whl", hash = "sha256:1a81cf05975fd61aec5ae16501a091cfb9f605dc3e3c878c0da32f250b74760b"}, - {file = "mypy-1.11.1-py3-none-any.whl", hash = "sha256:0624bdb940255d2dd24e829d99a13cfeb72e4e9031f9492148f410ed30bcab54"}, - {file = "mypy-1.11.1.tar.gz", hash = "sha256:f404a0b069709f18bbdb702eb3dcfe51910602995de00bd39cea3050b5772d08"}, + {file = "mypy-1.11.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d42a6dd818ffce7be66cce644f1dff482f1d97c53ca70908dff0b9ddc120b77a"}, + {file = "mypy-1.11.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:801780c56d1cdb896eacd5619a83e427ce436d86a3bdf9112527f24a66618fef"}, + {file = "mypy-1.11.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41ea707d036a5307ac674ea172875f40c9d55c5394f888b168033177fce47383"}, + {file = "mypy-1.11.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6e658bd2d20565ea86da7d91331b0eed6d2eee22dc031579e6297f3e12c758c8"}, + {file = "mypy-1.11.2-cp310-cp310-win_amd64.whl", hash = "sha256:478db5f5036817fe45adb7332d927daa62417159d49783041338921dcf646fc7"}, + {file = "mypy-1.11.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:75746e06d5fa1e91bfd5432448d00d34593b52e7e91a187d981d08d1f33d4385"}, + {file = "mypy-1.11.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a976775ab2256aadc6add633d44f100a2517d2388906ec4f13231fafbb0eccca"}, + {file = "mypy-1.11.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd953f221ac1379050a8a646585a29574488974f79d8082cedef62744f0a0104"}, + {file = "mypy-1.11.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:57555a7715c0a34421013144a33d280e73c08df70f3a18a552938587ce9274f4"}, + {file = "mypy-1.11.2-cp311-cp311-win_amd64.whl", hash = "sha256:36383a4fcbad95f2657642a07ba22ff797de26277158f1cc7bd234821468b1b6"}, + {file = "mypy-1.11.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e8960dbbbf36906c5c0b7f4fbf2f0c7ffb20f4898e6a879fcf56a41a08b0d318"}, + {file = "mypy-1.11.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:06d26c277962f3fb50e13044674aa10553981ae514288cb7d0a738f495550b36"}, + {file = "mypy-1.11.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e7184632d89d677973a14d00ae4d03214c8bc301ceefcdaf5c474866814c987"}, + {file = "mypy-1.11.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3a66169b92452f72117e2da3a576087025449018afc2d8e9bfe5ffab865709ca"}, + {file = "mypy-1.11.2-cp312-cp312-win_amd64.whl", hash = "sha256:969ea3ef09617aff826885a22ece0ddef69d95852cdad2f60c8bb06bf1f71f70"}, + {file = "mypy-1.11.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:37c7fa6121c1cdfcaac97ce3d3b5588e847aa79b580c1e922bb5d5d2902df19b"}, + {file = "mypy-1.11.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4a8a53bc3ffbd161b5b2a4fff2f0f1e23a33b0168f1c0778ec70e1a3d66deb86"}, + {file = "mypy-1.11.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ff93107f01968ed834f4256bc1fc4475e2fecf6c661260066a985b52741ddce"}, + {file = "mypy-1.11.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:edb91dded4df17eae4537668b23f0ff6baf3707683734b6a818d5b9d0c0c31a1"}, + {file = "mypy-1.11.2-cp38-cp38-win_amd64.whl", hash = "sha256:ee23de8530d99b6db0573c4ef4bd8f39a2a6f9b60655bf7a1357e585a3486f2b"}, + {file = "mypy-1.11.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:801ca29f43d5acce85f8e999b1e431fb479cb02d0e11deb7d2abb56bdaf24fd6"}, + {file = "mypy-1.11.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:af8d155170fcf87a2afb55b35dc1a0ac21df4431e7d96717621962e4b9192e70"}, + {file = "mypy-1.11.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f7821776e5c4286b6a13138cc935e2e9b6fde05e081bdebf5cdb2bb97c9df81d"}, + {file = "mypy-1.11.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:539c570477a96a4e6fb718b8d5c3e0c0eba1f485df13f86d2970c91f0673148d"}, + {file = "mypy-1.11.2-cp39-cp39-win_amd64.whl", hash = "sha256:3f14cd3d386ac4d05c5a39a51b84387403dadbd936e17cb35882134d4f8f0d24"}, + {file = "mypy-1.11.2-py3-none-any.whl", hash = "sha256:b499bc07dbdcd3de92b0a8b29fdf592c111276f6a12fe29c30f6c417dd546d12"}, + {file = "mypy-1.11.2.tar.gz", hash = "sha256:7f9993ad3e0ffdc95c2a14b66dee63729f021968bff8ad911867579c65d13a79"}, ] [package.dependencies] @@ -1409,60 +1409,37 @@ files = [ [[package]] name = "sqlalchemy" -version = "2.0.32" +version = "2.0.34" description = "Database Abstraction Library" optional = false python-versions = ">=3.7" files = [ - {file = "SQLAlchemy-2.0.32-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0c9045ecc2e4db59bfc97b20516dfdf8e41d910ac6fb667ebd3a79ea54084619"}, - {file = "SQLAlchemy-2.0.32-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1467940318e4a860afd546ef61fefb98a14d935cd6817ed07a228c7f7c62f389"}, - {file = "SQLAlchemy-2.0.32-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5954463675cb15db8d4b521f3566a017c8789222b8316b1e6934c811018ee08b"}, - {file = "SQLAlchemy-2.0.32-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:167e7497035c303ae50651b351c28dc22a40bb98fbdb8468cdc971821b1ae533"}, - {file = "SQLAlchemy-2.0.32-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b27dfb676ac02529fb6e343b3a482303f16e6bc3a4d868b73935b8792edb52d0"}, - {file = "SQLAlchemy-2.0.32-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bf2360a5e0f7bd75fa80431bf8ebcfb920c9f885e7956c7efde89031695cafb8"}, - {file = "SQLAlchemy-2.0.32-cp310-cp310-win32.whl", hash = "sha256:306fe44e754a91cd9d600a6b070c1f2fadbb4a1a257b8781ccf33c7067fd3e4d"}, - {file = "SQLAlchemy-2.0.32-cp310-cp310-win_amd64.whl", hash = "sha256:99db65e6f3ab42e06c318f15c98f59a436f1c78179e6a6f40f529c8cc7100b22"}, - {file = "SQLAlchemy-2.0.32-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:21b053be28a8a414f2ddd401f1be8361e41032d2ef5884b2f31d31cb723e559f"}, - {file = "SQLAlchemy-2.0.32-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b178e875a7a25b5938b53b006598ee7645172fccafe1c291a706e93f48499ff5"}, - {file = "SQLAlchemy-2.0.32-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723a40ee2cc7ea653645bd4cf024326dea2076673fc9d3d33f20f6c81db83e1d"}, - {file = "SQLAlchemy-2.0.32-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:295ff8689544f7ee7e819529633d058bd458c1fd7f7e3eebd0f9268ebc56c2a0"}, - {file = "SQLAlchemy-2.0.32-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:49496b68cd190a147118af585173ee624114dfb2e0297558c460ad7495f9dfe2"}, - {file = "SQLAlchemy-2.0.32-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:acd9b73c5c15f0ec5ce18128b1fe9157ddd0044abc373e6ecd5ba376a7e5d961"}, - {file = "SQLAlchemy-2.0.32-cp311-cp311-win32.whl", hash = "sha256:9365a3da32dabd3e69e06b972b1ffb0c89668994c7e8e75ce21d3e5e69ddef28"}, - {file = "SQLAlchemy-2.0.32-cp311-cp311-win_amd64.whl", hash = "sha256:8bd63d051f4f313b102a2af1cbc8b80f061bf78f3d5bd0843ff70b5859e27924"}, - {file = "SQLAlchemy-2.0.32-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6bab3db192a0c35e3c9d1560eb8332463e29e5507dbd822e29a0a3c48c0a8d92"}, - {file = "SQLAlchemy-2.0.32-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:19d98f4f58b13900d8dec4ed09dd09ef292208ee44cc9c2fe01c1f0a2fe440e9"}, - {file = "SQLAlchemy-2.0.32-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cd33c61513cb1b7371fd40cf221256456d26a56284e7d19d1f0b9f1eb7dd7e8"}, - {file = "SQLAlchemy-2.0.32-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d6ba0497c1d066dd004e0f02a92426ca2df20fac08728d03f67f6960271feec"}, - {file = "SQLAlchemy-2.0.32-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2b6be53e4fde0065524f1a0a7929b10e9280987b320716c1509478b712a7688c"}, - {file = "SQLAlchemy-2.0.32-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:916a798f62f410c0b80b63683c8061f5ebe237b0f4ad778739304253353bc1cb"}, - {file = "SQLAlchemy-2.0.32-cp312-cp312-win32.whl", hash = "sha256:31983018b74908ebc6c996a16ad3690301a23befb643093fcfe85efd292e384d"}, - {file = "SQLAlchemy-2.0.32-cp312-cp312-win_amd64.whl", hash = "sha256:4363ed245a6231f2e2957cccdda3c776265a75851f4753c60f3004b90e69bfeb"}, - {file = "SQLAlchemy-2.0.32-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b8afd5b26570bf41c35c0121801479958b4446751a3971fb9a480c1afd85558e"}, - {file = "SQLAlchemy-2.0.32-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c750987fc876813f27b60d619b987b057eb4896b81117f73bb8d9918c14f1cad"}, - {file = "SQLAlchemy-2.0.32-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ada0102afff4890f651ed91120c1120065663506b760da4e7823913ebd3258be"}, - {file = "SQLAlchemy-2.0.32-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:78c03d0f8a5ab4f3034c0e8482cfcc415a3ec6193491cfa1c643ed707d476f16"}, - {file = "SQLAlchemy-2.0.32-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:3bd1cae7519283ff525e64645ebd7a3e0283f3c038f461ecc1c7b040a0c932a1"}, - {file = "SQLAlchemy-2.0.32-cp37-cp37m-win32.whl", hash = "sha256:01438ebcdc566d58c93af0171c74ec28efe6a29184b773e378a385e6215389da"}, - {file = "SQLAlchemy-2.0.32-cp37-cp37m-win_amd64.whl", hash = "sha256:4979dc80fbbc9d2ef569e71e0896990bc94df2b9fdbd878290bd129b65ab579c"}, - {file = "SQLAlchemy-2.0.32-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c742be912f57586ac43af38b3848f7688863a403dfb220193a882ea60e1ec3a"}, - {file = "SQLAlchemy-2.0.32-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:62e23d0ac103bcf1c5555b6c88c114089587bc64d048fef5bbdb58dfd26f96da"}, - {file = "SQLAlchemy-2.0.32-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:251f0d1108aab8ea7b9aadbd07fb47fb8e3a5838dde34aa95a3349876b5a1f1d"}, - {file = "SQLAlchemy-2.0.32-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ef18a84e5116340e38eca3e7f9eeaaef62738891422e7c2a0b80feab165905f"}, - {file = "SQLAlchemy-2.0.32-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:3eb6a97a1d39976f360b10ff208c73afb6a4de86dd2a6212ddf65c4a6a2347d5"}, - {file = "SQLAlchemy-2.0.32-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0c1c9b673d21477cec17ab10bc4decb1322843ba35b481585facd88203754fc5"}, - {file = "SQLAlchemy-2.0.32-cp38-cp38-win32.whl", hash = "sha256:c41a2b9ca80ee555decc605bd3c4520cc6fef9abde8fd66b1cf65126a6922d65"}, - {file = "SQLAlchemy-2.0.32-cp38-cp38-win_amd64.whl", hash = "sha256:8a37e4d265033c897892279e8adf505c8b6b4075f2b40d77afb31f7185cd6ecd"}, - {file = "SQLAlchemy-2.0.32-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:52fec964fba2ef46476312a03ec8c425956b05c20220a1a03703537824b5e8e1"}, - {file = "SQLAlchemy-2.0.32-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:328429aecaba2aee3d71e11f2477c14eec5990fb6d0e884107935f7fb6001632"}, - {file = "SQLAlchemy-2.0.32-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85a01b5599e790e76ac3fe3aa2f26e1feba56270023d6afd5550ed63c68552b3"}, - {file = "SQLAlchemy-2.0.32-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aaf04784797dcdf4c0aa952c8d234fa01974c4729db55c45732520ce12dd95b4"}, - {file = "SQLAlchemy-2.0.32-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4488120becf9b71b3ac718f4138269a6be99a42fe023ec457896ba4f80749525"}, - {file = "SQLAlchemy-2.0.32-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:14e09e083a5796d513918a66f3d6aedbc131e39e80875afe81d98a03312889e6"}, - {file = "SQLAlchemy-2.0.32-cp39-cp39-win32.whl", hash = "sha256:0d322cc9c9b2154ba7e82f7bf25ecc7c36fbe2d82e2933b3642fc095a52cfc78"}, - {file = "SQLAlchemy-2.0.32-cp39-cp39-win_amd64.whl", hash = "sha256:7dd8583df2f98dea28b5cd53a1beac963f4f9d087888d75f22fcc93a07cf8d84"}, - {file = "SQLAlchemy-2.0.32-py3-none-any.whl", hash = "sha256:e567a8793a692451f706b363ccf3c45e056b67d90ead58c3bc9471af5d212202"}, - {file = "SQLAlchemy-2.0.32.tar.gz", hash = "sha256:c1b88cc8b02b6a5f0efb0345a03672d4c897dc7d92585176f88c67346f565ea8"}, + {file = "SQLAlchemy-2.0.34-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:173f5f122d2e1bff8fbd9f7811b7942bead1f5e9f371cdf9e670b327e6703ebd"}, + {file = "SQLAlchemy-2.0.34-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bd90c221ed4e60ac9d476db967f436cfcecbd4ef744537c0f2d5291439848768"}, + {file = "SQLAlchemy-2.0.34-cp310-cp310-win32.whl", hash = "sha256:3166dfff2d16fe9be3241ee60ece6fcb01cf8e74dd7c5e0b64f8e19fab44911b"}, + {file = "SQLAlchemy-2.0.34-cp310-cp310-win_amd64.whl", hash = "sha256:6831a78bbd3c40f909b3e5233f87341f12d0b34a58f14115c9e94b4cdaf726d3"}, + {file = "SQLAlchemy-2.0.34-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb1b30f31a36c7f3fee848391ff77eebdd3af5750bf95fbf9b8b5323edfdb4ec"}, + {file = "SQLAlchemy-2.0.34-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:80bd73ea335203b125cf1d8e50fef06be709619eb6ab9e7b891ea34b5baa2287"}, + {file = "SQLAlchemy-2.0.34-cp311-cp311-win32.whl", hash = "sha256:6daeb8382d0df526372abd9cb795c992e18eed25ef2c43afe518c73f8cccb721"}, + {file = "SQLAlchemy-2.0.34-cp311-cp311-win_amd64.whl", hash = "sha256:5bc08e75ed11693ecb648b7a0a4ed80da6d10845e44be0c98c03f2f880b68ff4"}, + {file = "SQLAlchemy-2.0.34-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3330415cd387d2b88600e8e26b510d0370db9b7eaf984354a43e19c40df2e2b"}, + {file = "SQLAlchemy-2.0.34-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee4c6917857fd6121ed84f56d1dc78eb1d0e87f845ab5a568aba73e78adf83"}, + {file = "SQLAlchemy-2.0.34-cp312-cp312-win32.whl", hash = "sha256:fbb034f565ecbe6c530dff948239377ba859420d146d5f62f0271407ffb8c580"}, + {file = "SQLAlchemy-2.0.34-cp312-cp312-win_amd64.whl", hash = "sha256:707c8f44931a4facd4149b52b75b80544a8d824162602b8cd2fe788207307f9a"}, + {file = "SQLAlchemy-2.0.34-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:413c85cd0177c23e32dee6898c67a5f49296640041d98fddb2c40888fe4daa2e"}, + {file = "SQLAlchemy-2.0.34-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:526ce723265643dbc4c7efb54f56648cc30e7abe20f387d763364b3ce7506c82"}, + {file = "SQLAlchemy-2.0.34-cp37-cp37m-win32.whl", hash = "sha256:13be2cc683b76977a700948411a94c67ad8faf542fa7da2a4b167f2244781cf3"}, + {file = "SQLAlchemy-2.0.34-cp37-cp37m-win_amd64.whl", hash = "sha256:e54ef33ea80d464c3dcfe881eb00ad5921b60f8115ea1a30d781653edc2fd6a2"}, + {file = "SQLAlchemy-2.0.34-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:203d46bddeaa7982f9c3cc693e5bc93db476ab5de9d4b4640d5c99ff219bee8c"}, + {file = "SQLAlchemy-2.0.34-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:9661268415f450c95f72f0ac1217cc6f10256f860eed85c2ae32e75b60278ad8"}, + {file = "SQLAlchemy-2.0.34-cp38-cp38-win32.whl", hash = "sha256:895184dfef8708e15f7516bd930bda7e50ead069280d2ce09ba11781b630a434"}, + {file = "SQLAlchemy-2.0.34-cp38-cp38-win_amd64.whl", hash = "sha256:6e7cde3a2221aa89247944cafb1b26616380e30c63e37ed19ff0bba5e968688d"}, + {file = "SQLAlchemy-2.0.34-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ebc11c54c6ecdd07bb4efbfa1554538982f5432dfb8456958b6d46b9f834bb7"}, + {file = "SQLAlchemy-2.0.34-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:220574e78ad986aea8e81ac68821e47ea9202b7e44f251b7ed8c66d9ae3f4278"}, + {file = "SQLAlchemy-2.0.34-cp39-cp39-win32.whl", hash = "sha256:b75b00083e7fe6621ce13cfce9d4469c4774e55e8e9d38c305b37f13cf1e874c"}, + {file = "SQLAlchemy-2.0.34-cp39-cp39-win_amd64.whl", hash = "sha256:c29d03e0adf3cc1a8c3ec62d176824972ae29b67a66cbb18daff3062acc6faa8"}, + {file = "SQLAlchemy-2.0.34-py3-none-any.whl", hash = "sha256:7286c353ee6475613d8beff83167374006c6b3e3f0e6491bfe8ca610eb1dec0f"}, + {file = "sqlalchemy-2.0.34.tar.gz", hash = "sha256:10d8f36990dd929690666679b0f42235c159a7051534adb135728ee52828dd22"}, ] [package.dependencies] @@ -1525,13 +1502,13 @@ urllib3 = ">=2" [[package]] name = "types-setuptools" -version = "71.1.0.20240813" +version = "71.1.0.20240818" description = "Typing stubs for setuptools" optional = false python-versions = ">=3.8" files = [ - {file = "types-setuptools-71.1.0.20240813.tar.gz", hash = "sha256:94ff4f0af18c7c24ac88932bcb0f5655fb7187a001b7c61e53a1bfdaf9877b54"}, - {file = "types_setuptools-71.1.0.20240813-py3-none-any.whl", hash = "sha256:d9d9ba2936f5d3b47b59ae9bf65942a60063ac1d6bbee180a8a79fbb43f22ce5"}, + {file = "types-setuptools-71.1.0.20240818.tar.gz", hash = "sha256:f62eaffaa39774462c65fbb49368c4dc1d91a90a28371cb14e1af090ff0e41e3"}, + {file = "types_setuptools-71.1.0.20240818-py3-none-any.whl", hash = "sha256:c4f95302f88369ac0ac46c67ddbfc70c6c4dbbb184d9fed356244217a2934025"}, ] [[package]] @@ -1575,13 +1552,13 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "werkzeug" -version = "3.0.3" +version = "3.0.4" description = "The comprehensive WSGI web application library." optional = false python-versions = ">=3.8" files = [ - {file = "werkzeug-3.0.3-py3-none-any.whl", hash = "sha256:fc9645dc43e03e4d630d23143a04a7f947a9a3b5727cd535fdfe155a17cc48c8"}, - {file = "werkzeug-3.0.3.tar.gz", hash = "sha256:097e5bfda9f0aba8da6b8545146def481d06aa7d3266e7448e2cccf67dd8bd18"}, + {file = "werkzeug-3.0.4-py3-none-any.whl", hash = "sha256:02c9eb92b7d6c06f31a782811505d2157837cea66aaede3e217c7c27c039476c"}, + {file = "werkzeug-3.0.4.tar.gz", hash = "sha256:34f2371506b250df4d4f84bfe7b0921e4762525762bbd936614909fe25cd7306"}, ] [package.dependencies] @@ -1609,101 +1586,103 @@ email = ["email-validator"] [[package]] name = "yarl" -version = "1.9.4" +version = "1.9.8" description = "Yet another URL library" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a8c1df72eb746f4136fe9a2e72b0c9dc1da1cbd23b5372f94b5820ff8ae30e0e"}, - {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a3a6ed1d525bfb91b3fc9b690c5a21bb52de28c018530ad85093cc488bee2dd2"}, - {file = "yarl-1.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c38c9ddb6103ceae4e4498f9c08fac9b590c5c71b0370f98714768e22ac6fa66"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9e09c9d74f4566e905a0b8fa668c58109f7624db96a2171f21747abc7524234"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8477c1ee4bd47c57d49621a062121c3023609f7a13b8a46953eb6c9716ca392"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5ff2c858f5f6a42c2a8e751100f237c5e869cbde669a724f2062d4c4ef93551"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:357495293086c5b6d34ca9616a43d329317feab7917518bc97a08f9e55648455"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54525ae423d7b7a8ee81ba189f131054defdb122cde31ff17477951464c1691c"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:801e9264d19643548651b9db361ce3287176671fb0117f96b5ac0ee1c3530d53"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e516dc8baf7b380e6c1c26792610230f37147bb754d6426462ab115a02944385"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:7d5aaac37d19b2904bb9dfe12cdb08c8443e7ba7d2852894ad448d4b8f442863"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:54beabb809ffcacbd9d28ac57b0db46e42a6e341a030293fb3185c409e626b8b"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bac8d525a8dbc2a1507ec731d2867025d11ceadcb4dd421423a5d42c56818541"}, - {file = "yarl-1.9.4-cp310-cp310-win32.whl", hash = "sha256:7855426dfbddac81896b6e533ebefc0af2f132d4a47340cee6d22cac7190022d"}, - {file = "yarl-1.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:848cd2a1df56ddbffeb375535fb62c9d1645dde33ca4d51341378b3f5954429b"}, - {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:35a2b9396879ce32754bd457d31a51ff0a9d426fd9e0e3c33394bf4b9036b099"}, - {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c7d56b293cc071e82532f70adcbd8b61909eec973ae9d2d1f9b233f3d943f2c"}, - {file = "yarl-1.9.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d8a1c6c0be645c745a081c192e747c5de06e944a0d21245f4cf7c05e457c36e0"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b3c1ffe10069f655ea2d731808e76e0f452fc6c749bea04781daf18e6039525"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:549d19c84c55d11687ddbd47eeb348a89df9cb30e1993f1b128f4685cd0ebbf8"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7409f968456111140c1c95301cadf071bd30a81cbd7ab829169fb9e3d72eae9"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e23a6d84d9d1738dbc6e38167776107e63307dfc8ad108e580548d1f2c587f42"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8b889777de69897406c9fb0b76cdf2fd0f31267861ae7501d93003d55f54fbe"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:03caa9507d3d3c83bca08650678e25364e1843b484f19986a527630ca376ecce"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e9035df8d0880b2f1c7f5031f33f69e071dfe72ee9310cfc76f7b605958ceb9"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:c0ec0ed476f77db9fb29bca17f0a8fcc7bc97ad4c6c1d8959c507decb22e8572"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:ee04010f26d5102399bd17f8df8bc38dc7ccd7701dc77f4a68c5b8d733406958"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:49a180c2e0743d5d6e0b4d1a9e5f633c62eca3f8a86ba5dd3c471060e352ca98"}, - {file = "yarl-1.9.4-cp311-cp311-win32.whl", hash = "sha256:81eb57278deb6098a5b62e88ad8281b2ba09f2f1147c4767522353eaa6260b31"}, - {file = "yarl-1.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:d1d2532b340b692880261c15aee4dc94dd22ca5d61b9db9a8a361953d36410b1"}, - {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0d2454f0aef65ea81037759be5ca9947539667eecebca092733b2eb43c965a81"}, - {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:44d8ffbb9c06e5a7f529f38f53eda23e50d1ed33c6c869e01481d3fafa6b8142"}, - {file = "yarl-1.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aaaea1e536f98754a6e5c56091baa1b6ce2f2700cc4a00b0d49eca8dea471074"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3777ce5536d17989c91696db1d459574e9a9bd37660ea7ee4d3344579bb6f129"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fc5fc1eeb029757349ad26bbc5880557389a03fa6ada41703db5e068881e5f2"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea65804b5dc88dacd4a40279af0cdadcfe74b3e5b4c897aa0d81cf86927fee78"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa102d6d280a5455ad6a0f9e6d769989638718e938a6a0a2ff3f4a7ff8c62cc4"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09efe4615ada057ba2d30df871d2f668af661e971dfeedf0c159927d48bbeff0"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:008d3e808d03ef28542372d01057fd09168419cdc8f848efe2804f894ae03e51"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6f5cb257bc2ec58f437da2b37a8cd48f666db96d47b8a3115c29f316313654ff"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:992f18e0ea248ee03b5a6e8b3b4738850ae7dbb172cc41c966462801cbf62cf7"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0e9d124c191d5b881060a9e5060627694c3bdd1fe24c5eecc8d5d7d0eb6faabc"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3986b6f41ad22988e53d5778f91855dc0399b043fc8946d4f2e68af22ee9ff10"}, - {file = "yarl-1.9.4-cp312-cp312-win32.whl", hash = "sha256:4b21516d181cd77ebd06ce160ef8cc2a5e9ad35fb1c5930882baff5ac865eee7"}, - {file = "yarl-1.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:a9bd00dc3bc395a662900f33f74feb3e757429e545d831eef5bb280252631984"}, - {file = "yarl-1.9.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:63b20738b5aac74e239622d2fe30df4fca4942a86e31bf47a81a0e94c14df94f"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d7f7de27b8944f1fee2c26a88b4dabc2409d2fea7a9ed3df79b67277644e17"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c74018551e31269d56fab81a728f683667e7c28c04e807ba08f8c9e3bba32f14"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ca06675212f94e7a610e85ca36948bb8fc023e458dd6c63ef71abfd482481aa5"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aef935237d60a51a62b86249839b51345f47564208c6ee615ed2a40878dccdd"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b134fd795e2322b7684155b7855cc99409d10b2e408056db2b93b51a52accc7"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d25039a474c4c72a5ad4b52495056f843a7ff07b632c1b92ea9043a3d9950f6e"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f7d6b36dd2e029b6bcb8a13cf19664c7b8e19ab3a58e0fefbb5b8461447ed5ec"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:957b4774373cf6f709359e5c8c4a0af9f6d7875db657adb0feaf8d6cb3c3964c"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d7eeb6d22331e2fd42fce928a81c697c9ee2d51400bd1a28803965883e13cead"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6a962e04b8f91f8c4e5917e518d17958e3bdee71fd1d8b88cdce74dd0ebbf434"}, - {file = "yarl-1.9.4-cp37-cp37m-win32.whl", hash = "sha256:f3bc6af6e2b8f92eced34ef6a96ffb248e863af20ef4fde9448cc8c9b858b749"}, - {file = "yarl-1.9.4-cp37-cp37m-win_amd64.whl", hash = "sha256:ad4d7a90a92e528aadf4965d685c17dacff3df282db1121136c382dc0b6014d2"}, - {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ec61d826d80fc293ed46c9dd26995921e3a82146feacd952ef0757236fc137be"}, - {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8be9e837ea9113676e5754b43b940b50cce76d9ed7d2461df1af39a8ee674d9f"}, - {file = "yarl-1.9.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bef596fdaa8f26e3d66af846bbe77057237cb6e8efff8cd7cc8dff9a62278bbf"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d47552b6e52c3319fede1b60b3de120fe83bde9b7bddad11a69fb0af7db32f1"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84fc30f71689d7fc9168b92788abc977dc8cefa806909565fc2951d02f6b7d57"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4aa9741085f635934f3a2583e16fcf62ba835719a8b2b28fb2917bb0537c1dfa"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:206a55215e6d05dbc6c98ce598a59e6fbd0c493e2de4ea6cc2f4934d5a18d130"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07574b007ee20e5c375a8fe4a0789fad26db905f9813be0f9fef5a68080de559"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5a2e2433eb9344a163aced6a5f6c9222c0786e5a9e9cac2c89f0b28433f56e23"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6ad6d10ed9b67a382b45f29ea028f92d25bc0bc1daf6c5b801b90b5aa70fb9ec"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:6fe79f998a4052d79e1c30eeb7d6c1c1056ad33300f682465e1b4e9b5a188b78"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a825ec844298c791fd28ed14ed1bffc56a98d15b8c58a20e0e08c1f5f2bea1be"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8619d6915b3b0b34420cf9b2bb6d81ef59d984cb0fde7544e9ece32b4b3043c3"}, - {file = "yarl-1.9.4-cp38-cp38-win32.whl", hash = "sha256:686a0c2f85f83463272ddffd4deb5e591c98aac1897d65e92319f729c320eece"}, - {file = "yarl-1.9.4-cp38-cp38-win_amd64.whl", hash = "sha256:a00862fb23195b6b8322f7d781b0dc1d82cb3bcac346d1e38689370cc1cc398b"}, - {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:604f31d97fa493083ea21bd9b92c419012531c4e17ea6da0f65cacdcf5d0bd27"}, - {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8a854227cf581330ffa2c4824d96e52ee621dd571078a252c25e3a3b3d94a1b1"}, - {file = "yarl-1.9.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ba6f52cbc7809cd8d74604cce9c14868306ae4aa0282016b641c661f981a6e91"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6327976c7c2f4ee6816eff196e25385ccc02cb81427952414a64811037bbc8b"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8397a3817d7dcdd14bb266283cd1d6fc7264a48c186b986f32e86d86d35fbac5"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0381b4ce23ff92f8170080c97678040fc5b08da85e9e292292aba67fdac6c34"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23d32a2594cb5d565d358a92e151315d1b2268bc10f4610d098f96b147370136"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ddb2a5c08a4eaaba605340fdee8fc08e406c56617566d9643ad8bf6852778fc7"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:26a1dc6285e03f3cc9e839a2da83bcbf31dcb0d004c72d0730e755b33466c30e"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:18580f672e44ce1238b82f7fb87d727c4a131f3a9d33a5e0e82b793362bf18b4"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:29e0f83f37610f173eb7e7b5562dd71467993495e568e708d99e9d1944f561ec"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:1f23e4fe1e8794f74b6027d7cf19dc25f8b63af1483d91d595d4a07eca1fb26c"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:db8e58b9d79200c76956cefd14d5c90af54416ff5353c5bfd7cbe58818e26ef0"}, - {file = "yarl-1.9.4-cp39-cp39-win32.whl", hash = "sha256:c7224cab95645c7ab53791022ae77a4509472613e839dab722a72abe5a684575"}, - {file = "yarl-1.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:824d6c50492add5da9374875ce72db7a0733b29c2394890aef23d533106e2b15"}, - {file = "yarl-1.9.4-py3-none-any.whl", hash = "sha256:928cecb0ef9d5a7946eb6ff58417ad2fe9375762382f1bf5c55e61645f2c43ad"}, - {file = "yarl-1.9.4.tar.gz", hash = "sha256:566db86717cf8080b99b58b083b773a908ae40f06681e87e589a976faf8246bf"}, + {file = "yarl-1.9.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:08359dbc3540fafa8972db45d3ef2d61370b4c24b8a028a4301bc5d076eee0e2"}, + {file = "yarl-1.9.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a7a716aae4fcecadfe4648268d3c194315152715391f4af6fad50d502be122e9"}, + {file = "yarl-1.9.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:62223670042a219b8e6fbd2c7f35c456278dcd346d3aba3f2c01c9bdec28f37e"}, + {file = "yarl-1.9.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18097a9e50ea31c61fece83bac8f63263f0c0c16c439bf82ac729c23f3b170e3"}, + {file = "yarl-1.9.8-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5809f8a48c8dab91f708947d358271ef1890c3012d6c45719f49d04af2112057"}, + {file = "yarl-1.9.8-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:71ff7a22355241f89e850afbc8858fb671ba7e2763af32ebbea158d23a84902a"}, + {file = "yarl-1.9.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d54e9880e781a490483200a74f6314fb6cf692a8197ccde93adf32bec95626b"}, + {file = "yarl-1.9.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ad8ea6ab27e27821739dfb94fab63284e3a52055e268f04529dc082fd0d59a2"}, + {file = "yarl-1.9.8-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b79e031524259b51cdd1ea41f5053491ad3565b9cecd76389c9f705752d14283"}, + {file = "yarl-1.9.8-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:bd91ccded75d080f13ed01a5f5796887916d2e8c0999cd68bcb58f89f9b1c29c"}, + {file = "yarl-1.9.8-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:583f48ab25b3906e3716479e8f700c4cc487e44d52766a4ea52b01cb7ea772d6"}, + {file = "yarl-1.9.8-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2f3e89838acdaf5bbd69383c408d9e119b4e9efbe8a38fa40045b5c966f918e3"}, + {file = "yarl-1.9.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a44c0b83d1871e1e1859167a1804143f590f86ac4708380852dca4d8299d8594"}, + {file = "yarl-1.9.8-cp310-cp310-win32.whl", hash = "sha256:5d39ae58a67b64b470021d18a13529d0c58efc5bf057936ec4b29092d4061030"}, + {file = "yarl-1.9.8-cp310-cp310-win_amd64.whl", hash = "sha256:f89ade31926b9931bbe29f5c62d4174057e532fb0c72e2e6abdd129fda6a60f3"}, + {file = "yarl-1.9.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:986296e65b0312c1da168de4ec1bb054b4a7b0ec26e3f9e8dafc06bbb1385030"}, + {file = "yarl-1.9.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b4c7c015dc813aa5fe15379f3540d178e3743c0f1cf9e4a4a8bff94bd2832a4d"}, + {file = "yarl-1.9.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:22b2db22f72e1cb8a552ae12dfb748167153c7cbf353c62781915b5328bf2561"}, + {file = "yarl-1.9.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4a567416bfb2a2b093aa64685aa7b6dfb593888784ef91b16fa6b985cceb951"}, + {file = "yarl-1.9.8-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:178f4ab054f3a5dc84c8091bd7395b6713aac83af893b62259d5eb3f5359ce7f"}, + {file = "yarl-1.9.8-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:02fe9809b29a7dc4a27b769a43c556288d949205db54338871a122b64751e0f4"}, + {file = "yarl-1.9.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c885a81f6c89b0d45fc0dd88e005c77dd8ba1dac421466d0dbb9192ce6d34e1e"}, + {file = "yarl-1.9.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99f78f45c8b4c9824e1a37eb0a3ae63ad2dff66434d9620265a4256088be9cda"}, + {file = "yarl-1.9.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:30929a10be9a13026fd68377aba3223d633370abb93dadd3932754f3dcf4734a"}, + {file = "yarl-1.9.8-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ee7c00a1979b3f23c8094dce6d9875453b3cb91b1153d9efaefa6773cf80cdb0"}, + {file = "yarl-1.9.8-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e89d76b2aa11287f038a37577528c5f62d9385020b795a011f60dfd1b217cf9f"}, + {file = "yarl-1.9.8-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:81fde88456d2cbe005e16aca78ef744f322b3b15184dfe41b5b04f97b46aa5be"}, + {file = "yarl-1.9.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b3dca0a4e192207f8bb4057725ff95e9a14d53a04728742f2b03692fc91b0a43"}, + {file = "yarl-1.9.8-cp311-cp311-win32.whl", hash = "sha256:9ea3a8532ea9fc2eeb6fc3def0c341aaeab7625545844f9c0a15350c17f9f479"}, + {file = "yarl-1.9.8-cp311-cp311-win_amd64.whl", hash = "sha256:c810606719683f4ab92127712efe283674d6ed29a627374411c762852913c2dd"}, + {file = "yarl-1.9.8-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b3d373373908e687aa4c8b0666870b0cf65605254ba0819ed8d5af2fc0780496"}, + {file = "yarl-1.9.8-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e3d1be58e28825a14fb9561733de62fbe95c892febe7d7a9ebcde916c531d603"}, + {file = "yarl-1.9.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7318736a8ee9de8217d590866dd716fa3c0895e684e2ec6152d945a4ab758043"}, + {file = "yarl-1.9.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db3dd602cbf6613dc1e4a6fbde7a1bee86948e5940086090bb505c2ab959bbdf"}, + {file = "yarl-1.9.8-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c5950226b128a1610f57c1f756fc611fdbdcb1e6b4497ccb05fce76a38915b07"}, + {file = "yarl-1.9.8-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b341a995673180ed81a1040228a59e0b47ee687e367b1a03d829fa3c0eb4607e"}, + {file = "yarl-1.9.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f912153a34698994f32cf683d966014b0dd99c73481302d6159bcb3a8303e84"}, + {file = "yarl-1.9.8-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9ceab2b16043ae1953863ec240eb918ba1ac40d2aad55225141aac288c606442"}, + {file = "yarl-1.9.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7c0d2bc2646ae2380bb91b9ddc2eb1e1fa6baef128499e817134d1d50c8b6c56"}, + {file = "yarl-1.9.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:ebd98e16ff9948e4d31514c937275017a122b765cb89961dd5d44ecd2cc18140"}, + {file = "yarl-1.9.8-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:83273ca458c85d7b026c770a86df6e36349e85100bd2cefe6d0ad7167a8f12a6"}, + {file = "yarl-1.9.8-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4511dd73b6aeda0cc39111839923f1545726d621813c9d13355824fba328dbcf"}, + {file = "yarl-1.9.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0ffb9f1cad56c547aa127e2c315e666ee9838156c8a3b14f37ba545b0167aa5e"}, + {file = "yarl-1.9.8-cp312-cp312-win32.whl", hash = "sha256:5796358c3d6c72b108b570e20ab951463237ec473b6d204da21050feaaaf7dca"}, + {file = "yarl-1.9.8-cp312-cp312-win_amd64.whl", hash = "sha256:c2dc6e941bf53160b44858d1b24767a056cd83166b69fbdd3b2e401856d8932e"}, + {file = "yarl-1.9.8-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:cb3d488f049db9522e3a0de50e07bac0c53565acd88a07bc9cf7182fd6890307"}, + {file = "yarl-1.9.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:50cbf73b6a4b62c3ad633e8920f2791adf485356ef37c9edbd5a1e7de8da2ddc"}, + {file = "yarl-1.9.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b1e0649ee7ac354a3e40ee849707140b14a2cd0cd2dc2062fe620458dfe465c8"}, + {file = "yarl-1.9.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c2501b230e89cad2361719860648f780197812d3be91c7ca6658a097a7e22fc4"}, + {file = "yarl-1.9.8-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be441a73f9f49427906274008bd98384d8ca4655981735281c314fc7c145d256"}, + {file = "yarl-1.9.8-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7de1968a1c2690b86e32e91acf8ed2043c346293f9bbe1704b9f6a481b73bd11"}, + {file = "yarl-1.9.8-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ce892a75a2209cf4f7007de21c6f6d607f4b9406ac613a59ad02340f6e933e4"}, + {file = "yarl-1.9.8-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:405e75bb94b87cc4167eef0e08d6a539f60633229f7043edc2e65c82ef80e874"}, + {file = "yarl-1.9.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bc5811c1906b38f2a203df1266c6dd11680ca85d610d6ee3701dde262a305520"}, + {file = "yarl-1.9.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:51476f19fe1296d3efe3770179548f5f4822e5c4ead9f5160ba156a6a9f5272c"}, + {file = "yarl-1.9.8-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:ce2af144a81883db914636bec646da4dcccfe9db05c2899e7afe90a3d817ffce"}, + {file = "yarl-1.9.8-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:8c91b71b0af1fb5454709e34b39e38c975faaa89c0cc8bb744d60300ca710fcd"}, + {file = "yarl-1.9.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1a562055b5ec6371c307320e8460d16675244e810b20f343371fc52797d23615"}, + {file = "yarl-1.9.8-cp313-cp313-win32.whl", hash = "sha256:f7442a9342aa04ea60b760a8f0d210e269f881eb0660a2000fa1f8cb89820931"}, + {file = "yarl-1.9.8-cp313-cp313-win_amd64.whl", hash = "sha256:21ef75d8a18fa47725b50fcb7ae6d23a51c71a7426cdf7097e52f9e12a995eb6"}, + {file = "yarl-1.9.8-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fd9affa8c18198dfa5a19c63b29ef2a2f35b8efacaf0bdd3e58f974c0ab0108d"}, + {file = "yarl-1.9.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f79e65f16413a95d9f7633802a2ee34730b3ba1dd0af82811b377057883c4fb7"}, + {file = "yarl-1.9.8-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3f8c454cf7e4d3762515ed2b5a40cf2feaeb8a8ed1d121f131a6178e16015319"}, + {file = "yarl-1.9.8-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f972fc63a1d6165d1cff650a16a498b0087334f7f9cd7385860c086d009cd49"}, + {file = "yarl-1.9.8-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4ac4aa2f2d8253b9a5455d5f0ed45687ea9715b78a563490ddf7954337974cb7"}, + {file = "yarl-1.9.8-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b001379047de5e03224dc0592f1b0e60738857a9b992d9b636b5050500ecce23"}, + {file = "yarl-1.9.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39deb5a67b591682e54d1b09b36e79cd608ca27bea1fefed3bcaaa0b05d2b25e"}, + {file = "yarl-1.9.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffd9dd7eac5d36f53fccdf11e98730b7a628561c77f6c2a9e0909d2a304f34d1"}, + {file = "yarl-1.9.8-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:497d5fd7dce44b5dcac648c830c99a673d30bc6cd9905b3e255c92c6dc01f537"}, + {file = "yarl-1.9.8-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:d99011d564f2b5cb4cf1012f9058e08d8d79674332474f7e940131f5952015df"}, + {file = "yarl-1.9.8-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:600f734296cb99db1af7e34c0dcf8ec9477072f72c4621677637fdc2273af120"}, + {file = "yarl-1.9.8-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:6703deac7bb0dd8b3f0bc3cb6844dab4e74c85c70783ae89bd0b52286ebdc102"}, + {file = "yarl-1.9.8-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:3346e2f641fcf31cf32c5a394d625e0676aba6fadccc06d35435e475753ed05d"}, + {file = "yarl-1.9.8-cp38-cp38-win32.whl", hash = "sha256:a54f7a63e48156a77a7c0333cefed29ceb004ab683d685a1192b341ac445cb73"}, + {file = "yarl-1.9.8-cp38-cp38-win_amd64.whl", hash = "sha256:45992ff8d941a1901c35f2ed90a60cb5fee8705ffadff395db4a5fd164473542"}, + {file = "yarl-1.9.8-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:590437f092af08e71521cc302940ef897e969152434c825bb3fb8f308b63a8bb"}, + {file = "yarl-1.9.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:551c26789acd38c7b90a89a1f262291d9f9a6a677185a83b5781e2a2c4258aec"}, + {file = "yarl-1.9.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:121bf7d647b3f6481ce1030350c1cc4c43e18758010732a449c71a1784ae793d"}, + {file = "yarl-1.9.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c9db466370e8bc3459912850494ad3401f3664ff3a56842f0d4514166f54c9f"}, + {file = "yarl-1.9.8-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ff56e21379824f3e3c39a37083d5ab905168b9483b1c0c563dd92eb2db18b251"}, + {file = "yarl-1.9.8-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cce910a1510d60c7eff4bb263b28b9afdcc5f6b85c555e492cfe7548a09e2476"}, + {file = "yarl-1.9.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ba7c4b50cc0bb4caaa54554613ca13db47a24878a4fc1063e6303494fc67567"}, + {file = "yarl-1.9.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b345de5e725b82e9458dc1381d7e28fe7d7ef93491370461dc98283b9dda51e2"}, + {file = "yarl-1.9.8-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:49dd58b79b0fd04e880c90bc570fde68407cc516c58812f0321f5e74c131107c"}, + {file = "yarl-1.9.8-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:15fb127bcc19065fd912391a43bc80114635f0062e0465765633ab5d0c7fc3a1"}, + {file = "yarl-1.9.8-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:6f4f87a7c97ba77fdc764b893ae4083c74e5857904962a70025ade0cd42bdbaf"}, + {file = "yarl-1.9.8-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:d336601d9ff3dc3b12263739ab1add25bdd2345d675f59ad49f72d9a6ccbc865"}, + {file = "yarl-1.9.8-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3574834e4aaf24e24d12fa4fd53d0b0fd1d70b24a67bed81c44b284377e81d45"}, + {file = "yarl-1.9.8-cp39-cp39-win32.whl", hash = "sha256:db9305328486539bb7182c15f1ad1ea95dae52245e93a049f2b1d6f04e63674d"}, + {file = "yarl-1.9.8-cp39-cp39-win_amd64.whl", hash = "sha256:588d62a57c7a43b230557728ec9f252b3f81ad073cb5c0ef48d87cd3f8b6ace2"}, + {file = "yarl-1.9.8-py3-none-any.whl", hash = "sha256:d1612ce50f23b94897b9ef5eb65b72398a9a83ea990b42825272590f3484dae3"}, + {file = "yarl-1.9.8.tar.gz", hash = "sha256:3089553548d9ab23152cecb5a71131caaa9e9b16d7fc8196057c374fdc53cc4b"}, ] [package.dependencies] @@ -1713,4 +1692,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "6ff24c92833edae6e1c4dd4f92541b37fe8c596f688f6e0461081ecffb38dcd8" \ No newline at end of file +content-hash = "6a298ab4b7f4f216112d3cfa3e5b14ccac47bb71c664124b11d89feec1de8bab" From 8bd45f60a683660ece49e5f454ee50fadfddc5b0 Mon Sep 17 00:00:00 2001 From: Glenn Date: Wed, 4 Sep 2024 10:57:04 -0700 Subject: [PATCH 43/52] Update hushline/settings.py Co-authored-by: Jeremy Moore --- hushline/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hushline/settings.py b/hushline/settings.py index a5ac52e26..71635328c 100644 --- a/hushline/settings.py +++ b/hushline/settings.py @@ -294,7 +294,7 @@ async def index() -> str | Response: setattr(user, f"extra_field_value{i}", value) # If the value is empty, reset the verification status - if not value.strip(): + if not value: setattr(user, f"extra_field_verified{i}", False) continue From 4a7dd20afd0eee7b90f5ff3d655d80c8c9a050aa Mon Sep 17 00:00:00 2001 From: Glenn Date: Wed, 4 Sep 2024 10:57:11 -0700 Subject: [PATCH 44/52] Update hushline/settings.py Co-authored-by: Jeremy Moore --- hushline/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hushline/settings.py b/hushline/settings.py index 71635328c..3fee0c479 100644 --- a/hushline/settings.py +++ b/hushline/settings.py @@ -279,7 +279,7 @@ async def index() -> str | Response: if "update_bio" in request.form and profile_form.validate_on_submit(): user.bio = profile_form.bio.data - base_url = current_app.config["HUSHLINE_TIPS_URL"] + profile_url = url_for("profile", _external=True, username=user.primary_username) profile_url = f"{base_url}/to/{user.primary_username}" async with aiohttp.ClientSession() as client_session: tasks = [] From df41c57c998e58b7df7d9852b87e3293e238dfbe Mon Sep 17 00:00:00 2001 From: Glenn Date: Wed, 4 Sep 2024 10:57:20 -0700 Subject: [PATCH 45/52] Update hushline/settings.py Co-authored-by: Jeremy Moore --- hushline/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hushline/settings.py b/hushline/settings.py index 3fee0c479..41a7b18aa 100644 --- a/hushline/settings.py +++ b/hushline/settings.py @@ -299,7 +299,7 @@ async def index() -> str | Response: continue # Verify the URL only if it starts with "https://" - url_to_verify = value.strip() + url_to_verify = value if url_to_verify.startswith("https://"): task = verify_url(client_session, user, i, url_to_verify, profile_url) tasks.append(task) From bc43a521e6fd377bde98d037d3320a51db34983f Mon Sep 17 00:00:00 2001 From: Glenn Date: Wed, 4 Sep 2024 11:02:50 -0700 Subject: [PATCH 46/52] Update settings.py --- hushline/settings.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/hushline/settings.py b/hushline/settings.py index 41a7b18aa..be69afb42 100644 --- a/hushline/settings.py +++ b/hushline/settings.py @@ -279,8 +279,11 @@ async def index() -> str | Response: if "update_bio" in request.form and profile_form.validate_on_submit(): user.bio = profile_form.bio.data - profile_url = url_for("profile", _external=True, username=user.primary_username) + # Define base_url from the environment or config + base_url = current_app.config.get("HUSHLINE_TIPS_URL", "https://tips.hushline.app") + profile_url = f"{base_url}/to/{user.primary_username}" + async with aiohttp.ClientSession() as client_session: tasks = [] for i in range(1, 5): From 0de2984a210d9284522acbbe0e9e789b38872cdc Mon Sep 17 00:00:00 2001 From: Glenn Date: Thu, 5 Sep 2024 09:07:12 -0700 Subject: [PATCH 47/52] use server_name --- hushline/__init__.py | 2 +- hushline/settings.py | 4 +--- migrations/versions/c2b6eff6e213_.py | 24 ++++++++++++++++++++++++ 3 files changed, 26 insertions(+), 4 deletions(-) create mode 100644 migrations/versions/c2b6eff6e213_.py diff --git a/hushline/__init__.py b/hushline/__init__.py index f36d2f4ab..066fe3806 100644 --- a/hushline/__init__.py +++ b/hushline/__init__.py @@ -48,7 +48,7 @@ def create_app() -> Flask: app.config["REQUIRE_PGP"] = os.environ.get("REQUIRE_PGP", "False").lower() == "true" # Handle the tips domain for profile verification - app.config["HUSHLINE_TIPS_URL"] = os.getenv("HUSHLINE_TIPS_URL", "https://tips.hushline.app") + app.config["SERVER_NAME"] = os.getenv("SERVER_NAME") # Run migrations db.init_app(app) diff --git a/hushline/settings.py b/hushline/settings.py index be69afb42..83df45f2b 100644 --- a/hushline/settings.py +++ b/hushline/settings.py @@ -280,9 +280,7 @@ async def index() -> str | Response: user.bio = profile_form.bio.data # Define base_url from the environment or config - base_url = current_app.config.get("HUSHLINE_TIPS_URL", "https://tips.hushline.app") - - profile_url = f"{base_url}/to/{user.primary_username}" + profile_url = url_for("profile", _external=True, username=user.primary_username) async with aiohttp.ClientSession() as client_session: tasks = [] diff --git a/migrations/versions/c2b6eff6e213_.py b/migrations/versions/c2b6eff6e213_.py new file mode 100644 index 000000000..9ca3aac0b --- /dev/null +++ b/migrations/versions/c2b6eff6e213_.py @@ -0,0 +1,24 @@ +"""empty message + +Revision ID: c2b6eff6e213 +Revises: 62551ed63cbf, 83a6b3b09eca +Create Date: 2024-09-05 09:02:54.497837 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'c2b6eff6e213' +down_revision = ('62551ed63cbf', '83a6b3b09eca') +branch_labels = None +depends_on = None + + +def upgrade(): + pass + + +def downgrade(): + pass From e4e8627ab983b51891530f6055f3d0f50d14cacb Mon Sep 17 00:00:00 2001 From: Glenn Date: Thu, 5 Sep 2024 09:07:36 -0700 Subject: [PATCH 48/52] Update c2b6eff6e213_.py --- migrations/versions/c2b6eff6e213_.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/migrations/versions/c2b6eff6e213_.py b/migrations/versions/c2b6eff6e213_.py index 9ca3aac0b..587ed642f 100644 --- a/migrations/versions/c2b6eff6e213_.py +++ b/migrations/versions/c2b6eff6e213_.py @@ -5,13 +5,12 @@ Create Date: 2024-09-05 09:02:54.497837 """ -from alembic import op -import sqlalchemy as sa + # revision identifiers, used by Alembic. -revision = 'c2b6eff6e213' -down_revision = ('62551ed63cbf', '83a6b3b09eca') +revision = "c2b6eff6e213" +down_revision = ("62551ed63cbf", "83a6b3b09eca") branch_labels = None depends_on = None From 66d7d504ffeb0205f8d0718ee44bcee0adfbe102 Mon Sep 17 00:00:00 2001 From: Glenn Date: Thu, 5 Sep 2024 09:09:29 -0700 Subject: [PATCH 49/52] Update c2b6eff6e213_.py --- migrations/versions/c2b6eff6e213_.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/migrations/versions/c2b6eff6e213_.py b/migrations/versions/c2b6eff6e213_.py index 587ed642f..350ad4e96 100644 --- a/migrations/versions/c2b6eff6e213_.py +++ b/migrations/versions/c2b6eff6e213_.py @@ -6,8 +6,6 @@ """ - - # revision identifiers, used by Alembic. revision = "c2b6eff6e213" down_revision = ("62551ed63cbf", "83a6b3b09eca") From 9d030d3b6abba4adc009dab4edd888c5fe889020 Mon Sep 17 00:00:00 2001 From: Glenn Date: Thu, 5 Sep 2024 20:32:44 -0700 Subject: [PATCH 50/52] Update profile.html --- hushline/templates/profile.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hushline/templates/profile.html b/hushline/templates/profile.html index c5e339c80..76059da76 100644 --- a/hushline/templates/profile.html +++ b/hushline/templates/profile.html @@ -61,7 +61,7 @@

{{ field.value }} {% else %} From 2abbc5b83e30d6a81484db42907e98c0c894d8ec Mon Sep 17 00:00:00 2001 From: Jeremy Moore Date: Thu, 5 Sep 2024 21:26:49 -0400 Subject: [PATCH 51/52] Configure PREFERRED_URL_SCHEME and proxy settings to ensure external urls are created using the right domain and scheme --- hushline/__init__.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/hushline/__init__.py b/hushline/__init__.py index 066fe3806..4223c3bba 100644 --- a/hushline/__init__.py +++ b/hushline/__init__.py @@ -5,6 +5,7 @@ from flask import Flask, flash, redirect, request, session, url_for from flask_migrate import Migrate +from werkzeug.middleware.proxy_fix import ProxyFix from werkzeug.wrappers.response import Response from . import admin, routes, settings @@ -49,6 +50,11 @@ def create_app() -> Flask: # Handle the tips domain for profile verification app.config["SERVER_NAME"] = os.getenv("SERVER_NAME") + app.config["PREFERRED_URL_SCHEME"] = "https" if os.getenv("SERVER_NAME") is not None else "http" + + if not app.config["IS_PERSONAL_SERVER"]: + # if were running the managed service, we are behind a proxy + app.wsgi_app = ProxyFix(app.wsgi_app, x_for=2, x_proto=1, x_host=0, x_port=0, x_prefix=0) # Run migrations db.init_app(app) From 22f878978ba4d5ab2fd34b2a2930fa6b4ec02152 Mon Sep 17 00:00:00 2001 From: Jeremy Moore Date: Fri, 6 Sep 2024 14:13:24 -0400 Subject: [PATCH 52/52] ignore mypy check for proxyfix --- hushline/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/hushline/__init__.py b/hushline/__init__.py index 4223c3bba..e918e8491 100644 --- a/hushline/__init__.py +++ b/hushline/__init__.py @@ -54,8 +54,9 @@ def create_app() -> Flask: if not app.config["IS_PERSONAL_SERVER"]: # if were running the managed service, we are behind a proxy - app.wsgi_app = ProxyFix(app.wsgi_app, x_for=2, x_proto=1, x_host=0, x_port=0, x_prefix=0) - + app.wsgi_app = ProxyFix( # type: ignore[method-assign] + app.wsgi_app, x_for=2, x_proto=1, x_host=0, x_port=0, x_prefix=0 + ) # Run migrations db.init_app(app) Migrate(app, db)