Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Verified URLs #529

Merged
merged 60 commits into from
Sep 6, 2024
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
4d48cee
init
glenn-sorrentino Aug 28, 2024
58c73f7
update tests
glenn-sorrentino Aug 29, 2024
ef1bd9c
linting
glenn-sorrentino Aug 29, 2024
39e4cca
Update settings.py
glenn-sorrentino Aug 29, 2024
d3a7b76
Update settings.py
glenn-sorrentino Aug 29, 2024
33a956c
Update settings.py
glenn-sorrentino Aug 29, 2024
85a0680
Update settings.py
glenn-sorrentino Aug 29, 2024
a3f6f4e
Update settings.py
glenn-sorrentino Aug 29, 2024
d229af9
Update style.css
glenn-sorrentino Aug 29, 2024
5581e56
Update style.css
glenn-sorrentino Aug 29, 2024
ceff496
update icon title
glenn-sorrentino Aug 29, 2024
119f483
remove unused code
glenn-sorrentino Aug 29, 2024
4346fd3
Update settings.py
glenn-sorrentino Aug 29, 2024
a3e20fa
Merge branch 'main' into me
glenn-sorrentino Aug 29, 2024
48dd63e
fix zindex of flash messages
glenn-sorrentino Aug 29, 2024
0be4b23
Update hushline/model.py
glenn-sorrentino Aug 30, 2024
edf6e86
Update migrations/versions/83a6b3b09eca_add_verified_fields.py
glenn-sorrentino Aug 30, 2024
ffa85f3
Update migrations/versions/83a6b3b09eca_add_verified_fields.py
glenn-sorrentino Aug 30, 2024
97ba5a2
Add filter to strip whitespace
glenn-sorrentino Aug 30, 2024
a28e0fc
Loop fields and update dynamically
glenn-sorrentino Aug 30, 2024
90fadf7
Async processing of URLs for verification
glenn-sorrentino Aug 30, 2024
c114b3a
remove unused verify route
glenn-sorrentino Aug 30, 2024
83a47e9
only verify https addresses
glenn-sorrentino Aug 30, 2024
bcae3d3
use bs4 for checking for rel=me
glenn-sorrentino Aug 30, 2024
32b7af2
only include relevant fields in migrations
glenn-sorrentino Aug 30, 2024
3b48034
update tests to use beautiful soup
glenn-sorrentino Aug 30, 2024
1324170
Update settings.py
glenn-sorrentino Aug 30, 2024
099e9d6
Revert "use bs4 for checking for rel=me"
glenn-sorrentino Aug 30, 2024
152c648
Reapply "use bs4 for checking for rel=me"
glenn-sorrentino Aug 30, 2024
0199e60
Revert "Reapply "use bs4 for checking for rel=me""
glenn-sorrentino Aug 30, 2024
5b81a99
Revert "Update settings.py"
glenn-sorrentino Aug 30, 2024
9261262
fix linting
glenn-sorrentino Aug 30, 2024
f517c8d
use bs4 for rel=me
glenn-sorrentino Aug 30, 2024
c66b88c
update tests
glenn-sorrentino Aug 30, 2024
c24c938
Merge branch 'main' into me
glenn-sorrentino Aug 30, 2024
bb35ee2
clear badge when field clears
glenn-sorrentino Aug 30, 2024
7014028
Update settings.py
glenn-sorrentino Aug 30, 2024
6d26be5
standardize schema
glenn-sorrentino Aug 31, 2024
bf12ad7
Update settings.py
glenn-sorrentino Aug 31, 2024
a5641aa
make verification URL an env variable
glenn-sorrentino Sep 3, 2024
89695db
Update settings.py
glenn-sorrentino Sep 3, 2024
e5f7ea4
make index route handler async
jeremywmoore Sep 3, 2024
57a450d
make it pretty
jeremywmoore Sep 3, 2024
c9ba6c4
Merge pull request #535 from scidsg/me-async-route
glenn-sorrentino Sep 3, 2024
59b6d4f
Merge branch 'main' into me
glenn-sorrentino Sep 4, 2024
41267fb
Merge branch 'main' into me
glenn-sorrentino Sep 4, 2024
a640623
Update poetry.lock
glenn-sorrentino Sep 4, 2024
7a1bd06
Merge branch 'main' into me
glenn-sorrentino Sep 4, 2024
8bd45f6
Update hushline/settings.py
glenn-sorrentino Sep 4, 2024
4a7dd20
Update hushline/settings.py
glenn-sorrentino Sep 4, 2024
df41c57
Update hushline/settings.py
glenn-sorrentino Sep 4, 2024
bc43a52
Update settings.py
glenn-sorrentino Sep 4, 2024
0de2984
use server_name
glenn-sorrentino Sep 5, 2024
e4e8627
Update c2b6eff6e213_.py
glenn-sorrentino Sep 5, 2024
66d7d50
Update c2b6eff6e213_.py
glenn-sorrentino Sep 5, 2024
9d030d3
Update profile.html
glenn-sorrentino Sep 6, 2024
2abbc5b
Configure PREFERRED_URL_SCHEME and proxy settings to ensure external …
jeremywmoore Sep 6, 2024
e14e53c
Merge branch 'me' into me-dev
glenn-sorrentino Sep 6, 2024
22f8789
ignore mypy check for proxyfix
jeremywmoore Sep 6, 2024
dc86e6f
Merge pull request #539 from scidsg/me-dev
glenn-sorrentino Sep 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions hushline/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
glenn-sorrentino marked this conversation as resolved.
Show resolved Hide resolved
extra_field_verified2 = db.Column(db.Boolean, default=False)
glenn-sorrentino marked this conversation as resolved.
Show resolved Hide resolved
extra_field_verified3 = db.Column(db.Boolean, default=False)
extra_field_verified4 = db.Column(db.Boolean, default=False)

@property
def password_hash(self) -> str:
Expand Down
25 changes: 25 additions & 0 deletions hushline/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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/<username>", methods=["POST"])
Expand Down
152 changes: 86 additions & 66 deletions hushline/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
glenn-sorrentino marked this conversation as resolved.
Show resolved Hide resolved
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()
glenn-sorrentino marked this conversation as resolved.
Show resolved Hide resolved

# Trigger the rel=me verification for each URL field
glenn-sorrentino marked this conversation as resolved.
Show resolved Hide resolved
for i in range(1, 5):
url_to_verify = getattr(user, f"extra_field_value{i}", "").strip()
glenn-sorrentino marked this conversation as resolved.
Show resolved Hide resolved
glenn-sorrentino marked this conversation as resolved.
Show resolved Hide resolved
if url_to_verify:
try:
response = requests.get(url_to_verify, timeout=5)
glenn-sorrentino marked this conversation as resolved.
Show resolved Hide resolved
response.raise_for_status()
profile_url = f"https://tips.hushline.app/to/{user.primary_username}"
if re.search(
glenn-sorrentino marked this conversation as resolved.
Show resolved Hide resolved
rf'<a[^>]+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())
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -683,4 +681,26 @@ def delete_account() -> Response | str:
flash("User not found. Please log in again.")
return redirect(url_for("login"))

@bp.route("/verify-rel-me/<int:field_index>", methods=["POST"])
glenn-sorrentino marked this conversation as resolved.
Show resolved Hide resolved
@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"))
glenn-sorrentino marked this conversation as resolved.
Show resolved Hide resolved

url_to_verify = request.form.get(f"extra_field_value{field_index}", "").strip()
glenn-sorrentino marked this conversation as resolved.
Show resolved Hide resolved
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
51 changes: 49 additions & 2 deletions hushline/static/css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,10 @@
#messageForm {
border-top: var(--border);
}

.icon.verifiedURL {
background-image: url("../img/app/icon-verified-lm.png");
}
}

/* Dark Mode */
Expand Down Expand Up @@ -637,6 +641,10 @@
#messageForm {
border-top: var(--border-dark);
}

.icon.verifiedURL {
background-image: url("../img/app/icon-verified-dm.png");
}
}

body {
Expand Down Expand Up @@ -859,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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -1932,12 +1955,35 @@ 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 {
position: relative;
}

.input-pair input + .icon.verifiedURL {
position: absolute;
right: 0%;
top: 42px;
left: calc(100% - 20px - 8px);
}

.form-group-pairs + button {
Expand Down Expand Up @@ -1994,6 +2040,7 @@ p.bio + .extra-fields {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
position: relative;
}

.pgp-disabled-overlay {
Expand Down
Binary file added hushline/static/img/app/icon-verified-dm.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added hushline/static/img/app/icon-verified-lm.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading