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

Use bcrypt instead of passlib #270

Merged
merged 3 commits into from
Oct 19, 2023
Merged
Show file tree
Hide file tree
Changes from 2 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
38 changes: 22 additions & 16 deletions app/core/security.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import secrets
from datetime import datetime, timedelta

import bcrypt
from fastapi.security import OAuth2AuthorizationCodeBearer
from jose import jwt
from passlib.context import CryptContext
from sqlalchemy.ext.asyncio import AsyncSession

from app.core.config import Settings
from app.cruds import cruds_users
from app.models import models_core
from app.schemas import schemas_auth

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto", bcrypt__rounds=13)
"""
In order to salt and hash password, we use a *passlib* [CryptContext](https://passlib.readthedocs.io/en/stable/narr/quickstart.html) object.
Petitoto marked this conversation as resolved.
Show resolved Hide resolved

Expand All @@ -36,31 +35,38 @@
"""


def get_password_hash(password: str) -> str:
def generate_token(nbytes=32) -> str:
"""
Return a salted hash computed from password. The function use a bcrypt based *passlib* CryptContext.
Both the salt and the algorithm identifier are included in the hash.
Generate a `nbytes` bytes cryptographically strong random urlsafe token using the *secrets* library.

By default, a 32 bytes token is generated.
"""
return pwd_context.hash(password)
# We use https://docs.python.org/3/library/secrets.html#secrets.token_urlsafe to generate the activation secret token
return secrets.token_urlsafe(nbytes)


def verify_password(plain_password: str, hashed_password: str | None) -> bool:
def get_password_hash(password: str) -> str:
"""
Compare `plain_password` against its salted hash representation `hashed_password`. The function use a bcrypt based *passlib* CryptContext.

Pass hashed_password=None to simulate the delay a real verification would have taken. This is useful to limit timing attacks
Return a salted hash computed from password.
Both the salt and the algorithm identifier are included in the hash.
"""
return pwd_context.verify(plain_password, hashed_password)
hashed = bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt(rounds=13))
return hashed.decode("utf-8")


def generate_token(nbytes=32) -> str:
def verify_password(plain_password: str, hashed_password: str | None) -> bool:
"""
Generate a `nbytes` bytes cryptographically strong random urlsafe token using the *secrets* library.
Compare `plain_password` against its salted hash representation `hashed_password`.

By default, a 32 bytes token is generated.
We genrerate a fake_hash for the case where hashed_password=None (ie the email isn't valid) to simulate the delay a real verification would have taken.
This is useful to limit timing attacks that could be used to guess valid emails.
"""
# We use https://docs.python.org/3/library/secrets.html#secrets.token_urlsafe to generate the activation secret token
return secrets.token_urlsafe(nbytes)
fake_hash = bcrypt.hashpw(generate_token(12).encode("utf-8"), bcrypt.gensalt(13))
if hashed_password is None:
return bcrypt.checkpw(plain_password.encode("utf-8"), fake_hash)
return bcrypt.checkpw(
plain_password.encode("utf-8"), hashed_password.encode("utf-8")
)


async def authenticate_user(
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
alembic==1.11.3 # database migrations
fastapi==0.99.0
Jinja2==3.1.2 # template engine for html files
passlib[bcrypt]==1.7.4 # password hashing using bcrypt
bcrypt==4.0.1 # password hashing
python-dotenv==1.0.0 # load environment variables from .env file
python-jose[cryptography]==3.3.0 # generate and verify the JWT tokens
python-multipart==0.0.6 # a form data parser, as oauth flow requires form-data parameters
Expand Down