Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
19 changes: 12 additions & 7 deletions server/mergin/auth/forms.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Copyright (C) Lutra Consulting Limited
#
# SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-MerginMaps-Commercial

import re
import safe
from flask_wtf import FlaskForm
Expand Down Expand Up @@ -48,18 +49,22 @@ class ExtendedEmail(Email):
1. spaces,
2. special characters ,:;()<>[]\"
3, multiple @ symbols,
4, leading, trailing, or consecutive dots in the local part
5, invalid domain part - missing top level domain (user@example), consecutive dots
Custom check for additional invalid characters disallows |'— because they make our email sending service to fail
4, leading, trailing, or consecutive dots in the local part,
5, invalid domain part - missing top level domain (user@example), consecutive dots,
The extended validation checks email addresses using the regex provided by Brevo,
so that we stay consistent with their validation rules and avoid API failures.
"""

def __call__(self, form, field):
super().__call__(form, field)

if re.search(r"[|'—]", field.data):
raise ValidationError(
f"Email address '{field.data}' contains an invalid character."
)
email = field.data.strip()

pattern = r"^[\x60#&*\/=?^{!}~'+\w-]+(\.[\x60#&*\/=?^{!}~'+\w-]+)*\.?@([_a-zA-Z0-9-]+(\.[_a-zA-Z0-9-]+)*\.)[a-zA-Z0-9-]*[a-zA-Z0-9]{2,}$"
email_regexp = re.compile(pattern, re.IGNORECASE)

if not email_regexp.match(email):
raise ValidationError(f"Email address '{email}' is invalid.")


class PasswordValidator:
Expand Down
12 changes: 9 additions & 3 deletions server/mergin/tests/test_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,14 @@ def test_logout(client):
400,
), # tests with upper case, but email already exists
(" mergin@mergin.com ", "#pwd123", 400), # invalid password
("verylonglonglonglonglonglonglongemail@example.com", "#pwd1234", 201),
(
"verylonglonglonglonglonglonglongemail@lutra-consulting.co.uk",
"#pwd1234",
201,
), # long local part, second-level domain, dash in domain
("us.er@mergin.com", "#pwd1234", 201), # dot is allowed
("us er@mergin.com", "#pwd1234", 400), # space is disallowed
("test@gmaiñ.com", "#pwd1234", 400), # non-ASCII character in the domain
]


Expand Down Expand Up @@ -936,15 +941,16 @@ def test_server_usage(client):
("日人日本人", True), # non-ascii character
("usér", True), # non-ascii character
("user\\", False), # disallowed character
("user\260", True), # non-ascii character (°)
("user\260", False), # not letter character (°)
("user|", False), # vertical bar
("us er", False), # space in the middle
("us,er", False), # comma
("us—er", False), # dash
("us'er", False), # apostrophe
("us´er", False), # acute accent
(" user", True), # starting with space (will be stripped)
("us.er", True), # dot in the middle
(".user", False), # starting with dot
("us-er", True), # hyphen
]


Expand Down
Loading