Skip to content

Commit

Permalink
Feat: use zxcvbn to verfiy password strength
Browse files Browse the repository at this point in the history
  • Loading branch information
Rotheem committed Jul 18, 2024
1 parent f684f0a commit 87ad009
Show file tree
Hide file tree
Showing 8 changed files with 26 additions and 4 deletions.
10 changes: 10 additions & 0 deletions app/app.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
"""File defining the Metadata. And the basic functions creating the database tables and calling the router"""

import json
import logging
import os
import uuid
from collections.abc import AsyncGenerator, Awaitable, Callable
from contextlib import asynccontextmanager
Expand All @@ -20,6 +22,7 @@
from sqlalchemy.engine import Connection, Engine
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import Session
from zxcvbn.matching import add_frequency_lists

from app import api
from app.core import models_core
Expand Down Expand Up @@ -304,6 +307,13 @@ async def lifespan(app: FastAPI) -> AsyncGenerator:
):
hyperion_error_logger.info("Redis client not configured")

password_dict_files = os.listdir("assets/password_dict")
password_dicts: dict[str, list[str]] = {}
for password_dict_file in password_dict_files:
with Path.open(Path(f"assets/password_dict/{password_dict_file}")) as file:
password_dicts[password_dict_file] = json.load(file)
add_frequency_lists(password_dicts)

@app.middleware("http")
async def logging_middleware(
request: Request,
Expand Down
7 changes: 7 additions & 0 deletions app/utils/validators.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import re

import zxcvbn

"""
A collection of Pydantic validators
See https://pydantic-docs.helpmanual.io/usage/validators/#reuse-validators
Expand All @@ -13,6 +15,11 @@ def password_validator(password: str) -> str:
https://pydantic-docs.helpmanual.io/usage/validators/#reuse-validators
"""
password = password.strip()
result = zxcvbn.zxcvbn(password)
if not result["score"] == 4:
raise ValueError(

Check warning on line 20 in app/utils/validators.py

View check run for this annotation

Codecov / codecov/patch

app/utils/validators.py#L20

Added line #L20 was not covered by tests
result["feedback"],
)
if not re.fullmatch(
r"(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$€%^&*\(\)_+\-.,.?\":\{\}|<>'/;\[\]]).{6,}",
password,
Expand Down
1 change: 1 addition & 0 deletions assets/password_dict/commonWords.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions assets/password_dict/firstnames.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions assets/password_dict/lastnames.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions assets/password_dict/wikipedia.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion requirements-common.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ redis==5.0.7
requests==2.32.3
SQLAlchemy[asyncio]==2.0.31 # [asyncio] allows greenlet to be installed on Apple M1 devices.
unidecode==1.3.8
uvicorn[standard]==0.29.0
uvicorn[standard]==0.29.0
zxcvbn==4.4.28
6 changes: 3 additions & 3 deletions tests/test_users.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ def test_create_and_activate_user(mocker: MockerFixture, client: TestClient) ->
"/users/activate",
json={
"activation_token": UNIQUE_TOKEN,
"password": "Password1!",
"password": "eclair!AEECL69",
"firstname": "firstname",
"name": "name",
"nickname": "nickname",
Expand Down Expand Up @@ -212,7 +212,7 @@ def test_recover_and_reset_password(mocker: MockerFixture, client: TestClient) -

response = client.post(
"/users/reset-password",
json={"reset_token": UNIQUE_TOKEN, "new_password": "New_password1"},
json={"reset_token": UNIQUE_TOKEN, "new_password": "eclar!AEECL69"},
)

assert response.status_code == 201
Expand Down Expand Up @@ -302,7 +302,7 @@ def test_change_password(client: TestClient) -> None:
json={
"email": student_user_email,
"old_password": student_user_password,
"new_password": "The_new_password1",
"new_password": "pluseclair!AEECL69",
},
headers={"Authorization": f"Bearer {token_student_user}"},
)
Expand Down

0 comments on commit 87ad009

Please sign in to comment.