Skip to content

Commit f4fa9b8

Browse files
Partial refactor of password pattern handling
1 parent ceae20b commit f4fa9b8

File tree

4 files changed

+35
-28
lines changed

4 files changed

+35
-28
lines changed

main.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
from fastapi.exceptions import RequestValidationError, HTTPException, StarletteHTTPException
99
from sqlmodel import Session
1010
from routers import authentication, organization, role, user
11-
from utils.auth import get_user_with_relations, get_optional_user, NeedsNewTokens, get_user_from_reset_token, PasswordValidationError, AuthenticationError
12-
from utils.models import User, Organization
11+
from utils.auth import PASSWORD_PATTERN, get_user_with_relations, get_optional_user, NeedsNewTokens, get_user_from_reset_token, PasswordValidationError, AuthenticationError
12+
from utils.models import User
1313
from utils.db import get_session, set_up_db
1414
from utils.images import MAX_FILE_SIZE, MIN_DIMENSION, MAX_DIMENSION, ALLOWED_CONTENT_TYPES
1515

@@ -174,6 +174,8 @@ async def read_register(
174174
):
175175
if params["user"]:
176176
return RedirectResponse(url="/dashboard", status_code=302)
177+
178+
params["password_pattern"] = PASSWORD_PATTERN
177179
return templates.TemplateResponse(params["request"], "authentication/register.html", params)
178180

179181

templates/authentication/register.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
<div class="mb-3">
2626
<label for="password" class="form-label">Password</label>
2727
<input type="password" class="form-control" id="password" name="password"
28-
pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[@$!%*?&\{\}\<\>\.\,\\\\'#\-_=\+\(\)\[\]:;\|~\/])[A-Za-z\d@$!%*?&\{\}\<\>\.\,\\\\'#\-_=\+\(\)\[\]:;\|~\/]{8,}"
28+
pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[@$!%*?&\{\}\<\>\.\,\\\\'#\-_=\+\(\)\[\]:;\|~\/]).{8,}"
2929
title="Must contain at least one number, one uppercase and lowercase letter, one special character, and at least 8 or more characters"
3030
placeholder="Enter your password" required
3131
autocomplete="new-password">

tests/test_auth.py

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
get_password_hash,
1313
validate_token,
1414
generate_password_reset_url,
15-
PASSWORD_PATTERN
15+
COMPILED_PASSWORD_PATTERN
1616
)
1717

1818

@@ -99,43 +99,40 @@ def test_password_pattern():
9999
"lowercase": lowercase_letters,
100100
"digit": digits
101101
}
102-
required_length = 8
103-
104-
# Randomized valid password tests
105-
for _ in range(50):
106-
password = ""
107-
for element in required_elements:
108-
n = random.randint(required_length // len(required_elements), required_length)
109-
password += ''.join(
110-
random.choice(required_elements[element])
111-
for _ in range(n)
112-
)
102+
103+
# Valid password tests
104+
for element in required_elements:
105+
for c in required_elements[element]:
106+
password = c + "test"
107+
for element in required_elements:
108+
if element != element:
109+
password += random.choice(required_elements[element])
113110
# Randomize the order of the characters in the string
114111
password = ''.join(random.sample(password, len(password)))
115-
assert re.match(PASSWORD_PATTERN, password) is not None
112+
assert re.match(COMPILED_PASSWORD_PATTERN, password) is not None, f"Password {password} does not match the pattern"
116113

117114
# Invalid password tests
118115

119116
# Empty password
120117
password = ""
121-
assert re.match(PASSWORD_PATTERN, password) is None
118+
assert re.match(COMPILED_PASSWORD_PATTERN, password) is None
122119

123120
# Too short
124121
password = "aA1!aA1"
125-
assert re.match(PASSWORD_PATTERN, password) is None
122+
assert re.match(COMPILED_PASSWORD_PATTERN, password) is None
126123

127124
# No uppercase letter
128125
password = "a1!" * 3
129-
assert re.match(PASSWORD_PATTERN, password) is None
126+
assert re.match(COMPILED_PASSWORD_PATTERN, password) is None
130127

131128
# No lowercase letter
132129
password = "A1!" * 3
133-
assert re.match(PASSWORD_PATTERN, password) is None
130+
assert re.match(COMPILED_PASSWORD_PATTERN, password) is None
134131

135132
# No digit
136133
password = "aA!" * 3
137-
assert re.match(PASSWORD_PATTERN, password) is None
134+
assert re.match(COMPILED_PASSWORD_PATTERN, password) is None
138135

139136
# No special character
140137
password = "aA1" * 3
141-
assert re.match(PASSWORD_PATTERN, password) is None
138+
assert re.match(COMPILED_PASSWORD_PATTERN, password) is None

utils/auth.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,21 @@
3434
ALGORITHM = "HS256"
3535
ACCESS_TOKEN_EXPIRE_MINUTES = 30
3636
REFRESH_TOKEN_EXPIRE_DAYS = 30
37-
PASSWORD_PATTERN = re.compile(
38-
r"(?=.*?\d)" # At least one digit
39-
r"(?=.*?[a-z])" # At least one lowercase letter
40-
r"(?=.*?[A-Z])" # At least one uppercase letter
41-
r"(?=.*?[@$!%*?&{}<>.,\\'#\-_=+\(\)\[\]:;|~/\^])" # At least one special character
37+
PASSWORD_PATTERN = (
38+
r"(?=.*\d)" # At least one digit
39+
r"(?=.*[a-z])" # At least one lowercase letter
40+
r"(?=.*[A-Z])" # At least one uppercase letter
41+
r"(?=.*[\\@$!%*?&{}<>.,'#\-_=+\(\)\[\]:;|~/\^])" # At least one special character
4242
r".{8,}" # At least 8 characters long
4343
)
44+
HTML_PASSWORD_PATTERN = (
45+
r"(?=.*\d)" # At least one digit
46+
r"(?=.*[a-z])" # At least one lowercase letter
47+
r"(?=.*[A-Z])" # At least one uppercase letter
48+
r"(?=.*[\\@$!%*?&\{\}\<\>\.\,\\'#\-_=\+\(\)\[\]:;\|~\/])" # At least one special character
49+
r".{8,}" # At least 8 characters long
50+
)
51+
COMPILED_PASSWORD_PATTERN = re.compile(HTML_PASSWORD_PATTERN)
4452

4553

4654
# --- Custom Exceptions ---
@@ -112,7 +120,7 @@ def validate_password_strength(v: str) -> str:
112120
- At least 8 characters long
113121
"""
114122
logger.debug(f"Validating password for {field_name}")
115-
if not PASSWORD_PATTERN.match(v):
123+
if not COMPILED_PASSWORD_PATTERN.match(v):
116124
logger.debug(f"Password for {field_name} does not satisfy the security policy")
117125
raise PasswordValidationError(
118126
field=field_name,

0 commit comments

Comments
 (0)