Skip to content

Commit 0be77d3

Browse files
DMalone87Mike Yavorsky
and
Mike Yavorsky
authored
Argon Conversion (#421)
* Argon Conversion - 1st Attempt Attempting to convert the password hashing to argon2. This is the first attempt at the conversion. It appears that the API function was completely broken by this attempt and no API calls are accepted. The API is returning a 403 error for all requests. * Fix port assignment * Remove jupyter from backend reqs * Flake8 Corrections * import password hasher and avoid fixture for app context * fix flake8 issue * use cffi 1.17.1 * Remove platform specification * Update reqs --------- Co-authored-by: Mike Yavorsky <[email protected]>
1 parent 9a85f3b commit 0be77d3

15 files changed

+206
-1028
lines changed

.env.template

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ GRAPH_DB=police_data
44
GRAPH_URI=db
55
GRAPH_NM_URI=db:7687
66
GRAPH_PORT=5432
7-
NPDI_API_PORT=5000
7+
NPDI_API_PORT=5001
88
MIXPANEL_TOKEN=your_mixpanel_token
99
MAIL_SERVER=mail.yourdomain.com
1010
MAIL_PORT=465

backend/Dockerfile

+8-8
Original file line numberDiff line numberDiff line change
@@ -7,30 +7,30 @@ RUN apt-get update -y && apt-get install -y \
77
libpq-dev \
88
gcc \
99
python3-dev \
10-
libffi-dev
10+
libffi-dev \
11+
build-essential \
12+
&& rm -rf /var/lib/apt/lists/*
1113

1214
# Install Rust
1315
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
1416
ENV PATH="/root/.cargo/bin:${PATH}"
1517

16-
18+
# Add docker-compose-wait (if still needed)
1719
ADD https://github.com/ufoscout/docker-compose-wait/releases/download/2.12.1/wait /wait
1820
RUN chmod +x /wait
1921

20-
2122
FROM base
2223
WORKDIR /app/
2324

24-
ARG NPDI_API_PORT=5000
25-
2625
RUN pip3 install --upgrade pip
2726

2827
COPY requirements/ requirements/
29-
3028
RUN pip3 install -r requirements/dev_unix.txt
29+
3130
COPY . .
3231

33-
ENV PORT=$NPDI_API_PORT
32+
ARG NPDI_API_PORT=5001
33+
ENV NPDI_API_PORT=$NPDI_API_PORT
34+
EXPOSE $NPDI_API_PORT
3435

3536
CMD /wait && ./run_dev.sh
36-

backend/Dockerfile.cloud

+3-13
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
# Stage 1: Build Stage
22
FROM python:3-slim-bookworm AS build
33

4-
# Install required packages
54
RUN apt-get update -y && apt-get install -y \
65
gcc \
76
g++ \
@@ -13,21 +12,13 @@ RUN apt-get update -y && apt-get install -y \
1312
&& rm -rf /var/lib/apt/lists/*
1413

1514
# Install Rust
16-
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y \
17-
&& . "$HOME/.cargo/env"
15+
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
1816
ENV PATH="/root/.cargo/bin:${PATH}"
1917

2018
WORKDIR /app/
2119

20+
# Instead of manually downloading a wheel, rely on pip:
2221
COPY requirements/prod.txt .
23-
24-
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
25-
RUN arch=$(arch) && \
26-
file=pandas-2.2.2-cp312-cp312-manylinux_2_17_${arch}.manylinux2014_${arch}.whl && \
27-
url="https://pypi.debian.net/pandas/${file}" && \
28-
wget ${url} && \
29-
sed -i "s/pandas==2.2.2/${file}/" prod.txt
30-
3122
RUN pip install --no-cache-dir --user -r prod.txt
3223

3324
COPY . .
@@ -42,12 +33,11 @@ ARG NPDI_API_PORT=5000
4233
# Copy installed packages from the build stage
4334
COPY --from=build /root/.local /root/.local
4435

45-
# Update PATH to include pip-installed packages
4636
ENV PATH=/root/.local/bin:${PATH}
4737

4838
COPY . .
4939

5040
EXPOSE $NPDI_API_PORT
5141
ENV NPDI_API_PORT=$NPDI_API_PORT
5242

53-
ENTRYPOINT [ "./run_cloud.sh" ]
43+
ENTRYPOINT ["./run_cloud.sh"]

backend/api.py

+7-4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from flask import Flask
55
from flask_mail import Mail
66
from flask_cors import CORS
7+
from argon2 import PasswordHasher
78
from backend.config import get_config_from_env
89
from backend.auth import jwt, refresh_token
910
from backend.schemas import spec
@@ -46,7 +47,7 @@ def register_extensions(app: Flask):
4647
# Neo4j setup
4748
# Driver setup
4849
db_driver = GraphDatabase.driver(
49-
f"bolt://{app.config["GRAPH_NM_URI"]}",
50+
f'bolt://{app.config["GRAPH_NM_URI"]}',
5051
auth=(
5152
app.config["GRAPH_USER"],
5253
app.config["GRAPH_PASSWORD"]
@@ -70,10 +71,12 @@ def register_extensions(app: Flask):
7071
neo_config.DATABASE_URL = neo_url
7172

7273
spec.register(app)
73-
# login_manager.init_app(app)
74-
# TODO: Add the correct route info
75-
# login_manager.login_view = 'auth.login'
74+
75+
# Authentication
76+
ph = PasswordHasher()
77+
app.config['PASSWORD_HASHER'] = ph
7678
jwt.init_app(app)
79+
7780
Mail(app)
7881
CORS(app, resources={r"/api/*": {"origins": "*"}})
7982

backend/database/models/user.py

+20-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Define the SQL classes for Users."""
22

3-
from werkzeug.security import generate_password_hash, check_password_hash
3+
from flask import current_app
4+
from argon2 import exceptions as argon2_exceptions
45
from backend.schemas import JsonSerializable, PropertyEnum
56
from neomodel import (
67
Relationship, StructuredNode,
@@ -68,6 +69,14 @@ class User(StructuredNode, JsonSerializable):
6869
'backend.database.models.source.StagedInvitation',
6970
"EXTENDED")
7071

72+
def _get_password_hasher(self):
73+
"""
74+
Get the password hasher.
75+
Returns:
76+
PasswordHasher: The password hasher.
77+
"""
78+
return current_app.config['PASSWORD_HASHER']
79+
7180
def verify_password(self, pw: str) -> bool:
7281
"""
7382
Verify the user's password using bcrypt.
@@ -77,16 +86,22 @@ def verify_password(self, pw: str) -> bool:
7786
Returns:
7887
bool: True if the password is correct, False otherwise.
7988
"""
80-
# return bcrypt.checkpw(pw.encode("utf8"), self.password.encode("utf8"))
81-
return check_password_hash(self.password_hash, pw)
89+
try:
90+
return self._get_password_hasher().verify(self.password_hash, pw)
91+
except argon2_exceptions.VerifyMismatchError:
92+
return False
93+
except argon2_exceptions.VerificationError:
94+
return False
95+
except argon2_exceptions.InvalidHash:
96+
return False
8297

8398
def set_password(self, pw: str):
8499
"""
85100
Set the user's password.
86101
Args:
87102
pw (str): The password to set.
88103
"""
89-
self.password_hash = User.hash_password(pw)
104+
self.password_hash = self.hash_password(pw)
90105

91106
def send_email_verification(self):
92107
"""
@@ -119,7 +134,7 @@ def hash_password(cls, pw: str) -> str:
119134
Returns:
120135
str: The hashed password.
121136
"""
122-
return generate_password_hash(pw)
137+
return current_app.config['PASSWORD_HASHER'].hash(pw)
123138

124139
@classmethod
125140
def get_by_email(cls, email: str) -> "User":

backend/routes/auth.py

+1
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ def register():
6969
"""
7070
logger = logging.getLogger("user_register")
7171
body: RegisterUserDTO = request.validated_body
72+
logger.info(f"Registering user with email {body.email}.")
7273

7374
# Check to see if user already exists
7475
user = User.nodes.first_or_none(email=body.email)

backend/tests/test_auth.py

+20-9
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
import pytest
22
from backend.database.models.user import User, UserRole
3+
from argon2 import PasswordHasher
4+
5+
ph = PasswordHasher()
36

47
mock_user = {
58
"email": "[email protected]",
6-
"password_hash": User.hash_password("my_password"),
9+
"password_hash": ph.hash("my_password"),
710
"first_name": "John",
811
"last_name": "Doe",
912
"phone_number": "1234567890",
@@ -20,8 +23,12 @@ def existing_user():
2023

2124
@pytest.mark.parametrize(
2225
(
23-
"email", "password", "firstname", "lastname",
24-
"phone_number", "expected_status_code"
26+
"email",
27+
"password",
28+
"firstname",
29+
"lastname",
30+
"phone_number",
31+
"expected_status_code",
2532
),
2633
[
2734
("[email protected]", "my_password", "John", "Doe", "1234567890", 200),
@@ -31,9 +38,14 @@ def existing_user():
3138
],
3239
)
3340
def test_register(
34-
client, existing_user, email, password,
35-
firstname, lastname, phone_number,
36-
expected_status_code
41+
client,
42+
existing_user,
43+
email,
44+
password,
45+
firstname,
46+
lastname,
47+
phone_number,
48+
expected_status_code,
3749
):
3850
res = client.post(
3951
"api/v1/auth/register",
@@ -43,7 +55,7 @@ def test_register(
4355
"firstname": firstname,
4456
"lastname": lastname,
4557
"phone_number": phone_number,
46-
}
58+
},
4759
)
4860

4961
assert res.status_code == expected_status_code
@@ -68,8 +80,7 @@ def test_register(
6880
[("my_password", 200), ("bad_password", 401), (None, 422)],
6981
)
7082
def test_login(
71-
db_session,
72-
client, example_user, password, expected_status_code
83+
db_session, client, example_user, password, expected_status_code
7384
):
7485
res = client.post(
7586
"api/v1/auth/login",

docker-compose.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ services:
4343
context: .
4444
dockerfile: ./backend/Dockerfile
4545
args:
46-
NPDI_API_PORT: ${NPDI_API_PORT:-5000}
46+
NPDI_API_PORT: ${NPDI_API_PORT:-5001}
4747
volumes:
4848
- .:/app
4949
depends_on:
@@ -57,7 +57,7 @@ services:
5757
MIXPANEL_TOKEN: ${MIXPANEL_TOKEN:-notset}
5858
WAIT_HOSTS: db:7687
5959
ports:
60-
- ${NPDI_API_PORT:-5000}:${NPDI_API_PORT:-5000}
60+
- ${NPDI_API_PORT:-5001}:${NPDI_API_PORT:-5001}
6161
volumes:
6262
neo4j: {}
6363
neo4j_logs: {}

requirements/Dockerfile

+14-17
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,21 @@
1-
# psycopg requires postgres development files in order to compile the
2-
# requirements, so this image starts with the same image as the database
3-
# containers and installs the same version of python as the api containers
4-
5-
FROM postgres:17 as base
1+
FROM python:3-slim-bookworm
62

3+
# Install packages required to build Python and extensions
74
RUN apt-get update && apt-get install -y \
8-
make build-essential libssl-dev zlib1g-dev libbz2-dev libreadline-dev \
9-
libsqlite3-dev wget curl llvm libncursesw5-dev xz-utils tk-dev \
10-
libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev git
11-
12-
FROM base
5+
curl wget git make build-essential libssl-dev zlib1g-dev libbz2-dev \
6+
libreadline-dev libsqlite3-dev llvm libncursesw5-dev xz-utils tk-dev \
7+
libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev ca-certificates \
8+
libopenblas-dev gfortran \
9+
&& rm -rf /var/lib/apt/lists/*
1310

14-
SHELL ["bash", "-lc"]
15-
RUN curl https://pyenv.run | bash && \
16-
echo 'export PATH="$HOME/.pyenv/shims:$HOME/.pyenv/bin:$PATH"' >> ~/.bashrc
11+
# Install Rust and Cargo for packages requiring Rust
12+
RUN curl https://sh.rustup.rs -sSf | sh -s -- -y
13+
ENV PATH="/root/.cargo/bin:${PATH}"
1714

18-
ENV PYTHON_VERSION=3.13.0
19-
RUN pyenv install ${PYTHON_VERSION} && pyenv global ${PYTHON_VERSION}
20-
RUN pip install -U pip-tools
15+
# Upgrade pip and install pip-tools
16+
RUN pip install --no-cache-dir -U pip pip-tools
2117

18+
# Copy requirements directory and run the update script
2219
COPY . requirements/
2320

24-
CMD python requirements/update.py
21+
CMD python requirements/update.py

requirements/_core.in

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
argon2-cffi
12
bcrypt
23
black
34
boto3
@@ -28,9 +29,9 @@ openpyxl
2829
xlsxwriter
2930
numpy
3031
spectree
31-
jupyter
3232
mixpanel
3333
ua-parser
3434
ujson
3535
neo4j
3636
neomodel==5.3.3
37+
cffi==1.17.1

0 commit comments

Comments
 (0)