From 5f3ae90f46402217f580c8f1e702fade2c38f214 Mon Sep 17 00:00:00 2001 From: Nikita Melkozerov Date: Tue, 3 Sep 2024 14:54:14 +0200 Subject: [PATCH] Add the minimum password length requirements (#610) --- .devcontainer/devcontainer.json | 2 +- fixbackend/auth/user_manager.py | 18 ++++++++++++++++-- package-lock.json | 10 ++++++---- package.json | 2 +- tests/fixbackend/auth/router_test.py | 4 ++-- 5 files changed, 26 insertions(+), 10 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index b074faaa..4ca19588 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -8,7 +8,7 @@ // Features to add to the dev container. More info: https://containers.dev/features. "features": { "ghcr.io/devcontainers-contrib/features/poetry:2": {}, - "ghcr.io/devcontainers-contrib/features/node-asdf:0": {}, + "ghcr.io/devcontainers/features/node:1": {}, }, // Use 'forwardPorts' to make a list of ports inside the container available locally. // This can be used to network with other containers or the host. diff --git a/fixbackend/auth/user_manager.py b/fixbackend/auth/user_manager.py index ae3c0cca..434c0222 100644 --- a/fixbackend/auth/user_manager.py +++ b/fixbackend/auth/user_manager.py @@ -17,9 +17,10 @@ import re import secrets from concurrent.futures import ProcessPoolExecutor -from typing import Annotated, Any, Optional, Tuple +from typing import Annotated, Any, Optional, Tuple, Union from uuid import UUID +import fastapi_users import pyotp from fastapi import Depends, Request from fastapi_users import BaseUserManager, exceptions @@ -28,7 +29,7 @@ from starlette.responses import Response from fixbackend.auth.models import User -from fixbackend.auth.schemas import OTPConfig +from fixbackend.auth.schemas import OTPConfig, UserCreate from fixbackend.auth.user_repository import UserRepository from fixbackend.auth.user_verifier import AuthEmailSender from fixbackend.config import Config @@ -285,6 +286,19 @@ async def check_otp(self, user: User, otp: Optional[str], recovery_code: Optiona return await self.user_repository.delete_recovery_code(user.id, recovery_code, self.password_helper) return False + async def validate_password(self, password: str, user: Union[UserCreate, User]) -> None: # type: ignore + if len(password) < 16: + raise fastapi_users.InvalidPasswordException(reason="Password is too short. Minimum length: 16 characters.") + + if not re.search(r"[A-Z]", password): + raise fastapi_users.InvalidPasswordException(reason="Password must contain at least one uppercase letter.") + + if not re.search(r"[a-z]", password): + raise fastapi_users.InvalidPasswordException(reason="Password must contain at least one lowercase letter.") + + if not re.search(r"[0-9]", password): + raise fastapi_users.InvalidPasswordException(reason="Password must contain at least one digit.") + def get_password_helper(deps: FixDependency) -> PasswordHelperProtocol | None: return deps.service(ServiceNames.password_helper, PasswordHelper) diff --git a/package-lock.json b/package-lock.json index 2502fbd8..c76c2338 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "": { "devDependencies": { "daisyui": "^4.10.2", - "tailwindcss": "^3.4.3" + "tailwindcss": "^3.4.10" } }, "node_modules/@alloc/quick-lru": { @@ -341,6 +341,7 @@ "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-4.10.2.tgz", "integrity": "sha512-eCWS1W/JPyxW9IvlgW5m0R6rp9ZhRsBTW37rvEUthckkjsV04u8XipV519OoccSA46ixhSJa3q7XLI1WUFtRCA==", "dev": true, + "license": "MIT", "dependencies": { "css-selector-tokenizer": "^0.8", "culori": "^3", @@ -1221,10 +1222,11 @@ } }, "node_modules/tailwindcss": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.3.tgz", - "integrity": "sha512-U7sxQk/n397Bmx4JHbJx/iSOOv5G+II3f1kpLpY2QeUv5DcPdcTsYLlusZfq1NthHS1c1cZoyFmmkex1rzke0A==", + "version": "3.4.10", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.10.tgz", + "integrity": "sha512-KWZkVPm7yJRhdu4SRSl9d4AK2wM3a50UsvgHZO7xY77NQr2V+fIrEuoDGQcbvswWvFGbS2f6e+jC/6WJm1Dl0w==", "dev": true, + "license": "MIT", "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", diff --git a/package.json b/package.json index 1006977a..97fba359 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "devDependencies": { "daisyui": "^4.10.2", - "tailwindcss": "^3.4.3" + "tailwindcss": "^3.4.10" } } diff --git a/tests/fixbackend/auth/router_test.py b/tests/fixbackend/auth/router_test.py index 4f7e3b35..0f0ae67e 100644 --- a/tests/fixbackend/auth/router_test.py +++ b/tests/fixbackend/auth/router_test.py @@ -165,7 +165,7 @@ async def test_registration_flow( role_repo = fix_deps.add(ServiceNames.role_repository, InMemoryRoleRepository()) registration_json = { "email": "user@example.com", - "password": "changeme", + "password": "changeMe123456789", } # register user @@ -249,7 +249,7 @@ async def test_mfa_flow( verifier = fix_deps.service(ServiceNames.auth_email_sender, InMemoryVerifier) # register user - registration_json = {"email": "user2@example.com", "password": "changeme"} + registration_json = {"email": "user2@example.com", "password": "changeMe123456789"} response = await api_client.post("/api/auth/register", json=registration_json) assert response.status_code == 201