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

User login #196

Merged
merged 85 commits into from Mar 12, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
85 commits
Select commit Hold shift + click to select a range
3d3afff
wip
Jan 25, 2024
2a8ab79
user login changes
Jan 29, 2024
8e799b8
missing file
Jan 29, 2024
7ba0906
missing file
Jan 29, 2024
e4c5577
missing file
Jan 29, 2024
e237ede
Merge branch 'dev' of https://github.com/Police-Data-Accessibility-Pr…
Feb 1, 2024
18cec79
add user put test
Feb 1, 2024
e6293ef
Dev -> Main (#202)
mbodeantor Feb 7, 2024
3044b35
Merge remote-tracking branch 'origin/main' into user_login
joshuagraber Feb 7, 2024
ffea309
chore(deps): bump design-system 2.3.0 -> 2.4.0
joshuagraber Feb 7, 2024
5a8cda4
Merge branch 'user_login' of https://github.com/Police-Data-Accessibi…
Feb 9, 2024
8ab0390
chore(deps): bump design-system -> 2.4.1
joshuagraber Feb 12, 2024
c087b6f
feat(pages): Add login page
joshuagraber Feb 12, 2024
fce00c0
feat(pages): add shell of password page
joshuagraber Feb 12, 2024
a813bbb
feat(router): add new pages to router
joshuagraber Feb 12, 2024
dda07db
reset password, role check for edit permissions
Feb 15, 2024
8d9cb79
Merge branch 'user_login' of https://github.com/Police-Data-Accessibi…
Feb 15, 2024
75f5531
missing file
Feb 15, 2024
c11d49f
fixed tests
Feb 16, 2024
895841b
fixed tests
Feb 16, 2024
20d8cda
don't insert search log on test
Feb 16, 2024
5f16d4b
move login to own endpoint
Feb 19, 2024
abbeea9
session token
Feb 20, 2024
cbb7459
refresh session tokens
Feb 22, 2024
3db5e9a
style(pages): miscellaneous code cleanup
joshuagraber Feb 23, 2024
5dbace4
chore(deps): bump vue, add pinia
joshuagraber Feb 23, 2024
03b9797
chore(deps): add jwt-decode
joshuagraber Feb 23, 2024
3c492d9
chore(deps): add lodash
joshuagraber Feb 23, 2024
3a12b2a
refresh test and fix
Feb 23, 2024
ab097e8
Merge branch 'user_login' of github.com:Police-Data-Accessibility-Pro…
joshuagraber Feb 23, 2024
65ea58e
chore(deps): add persist state plugin for pinia
joshuagraber Feb 23, 2024
5cb220b
feat(state): add pinia store for auth
joshuagraber Feb 23, 2024
5cab8bd
feat(components): add AuthWrapper component
joshuagraber Feb 23, 2024
6cd2039
refactor(pages): update login page
joshuagraber Feb 23, 2024
e636d5f
refactor(router): push to login rather than update route
joshuagraber Feb 23, 2024
a12e5b9
docs(README): update client docs
joshuagraber Feb 23, 2024
e55e7af
unit tests
Feb 23, 2024
c34c435
chore(deps): pinia testing
joshuagraber Feb 23, 2024
f589994
test(pages): update tests and snapshots
joshuagraber Feb 23, 2024
0ddd257
Merge branch 'user_login' of github.com:Police-Data-Accessibility-Pro…
joshuagraber Feb 23, 2024
788618d
test(util): add test for parseJwt
joshuagraber Feb 23, 2024
6f57ce1
test(components): add test for AuthWrapper
joshuagraber Feb 23, 2024
a84a6cf
fix(router): public route logic conflicting
joshuagraber Feb 23, 2024
94ce64b
chore(router): cleanup: remove unnecessary check
joshuagraber Feb 23, 2024
09c0b65
fix(store): update logout patch
joshuagraber Feb 23, 2024
5f13ba5
fix(store): one more update to logout
joshuagraber Feb 23, 2024
f6cf8f7
feat(stores): create user store
joshuagraber Feb 26, 2024
a277f12
feat(pages): build change password route
joshuagraber Feb 26, 2024
aea7fb0
feat(pages): add password reset route
joshuagraber Feb 26, 2024
240ed05
refactor(store): log user back in on pw change
joshuagraber Feb 26, 2024
9eb023b
fix(stores): status code logic
joshuagraber Feb 26, 2024
821bbee
PR feedback changes
Feb 26, 2024
9b4f03e
Merge branch 'user_login' of https://github.com/Police-Data-Accessibi…
Feb 26, 2024
0e5c517
refactor: miscellaneous updates
joshuagraber Feb 26, 2024
c3d9e31
PR feedback changes
Feb 26, 2024
d0282fd
Merge branch 'user_login' of https://github.com/Police-Data-Accessibi…
Feb 26, 2024
76c6037
chore(local): standardize port for dev server
joshuagraber Feb 26, 2024
04e6948
refactor: update password reset
joshuagraber Feb 26, 2024
9148ca0
docs(README): update client notes
joshuagraber Feb 26, 2024
b08b714
refactor(pages): update log in per feedback
joshuagraber Feb 26, 2024
09409a3
test fix
Feb 27, 2024
15f4358
test(components): update AuthWrapper test
joshuagraber Feb 27, 2024
e55ac63
test(pages): add LogIn tests
joshuagraber Feb 27, 2024
0813d92
Merge branch 'user_login' of github.com:Police-Data-Accessibility-Pro…
joshuagraber Feb 27, 2024
e5a9fd9
chore(cleanup): remove stray log
joshuagraber Feb 27, 2024
750b5b5
test(pages): update login snapshots
joshuagraber Feb 28, 2024
1937b8b
fix(ci): indentation in test.yml
joshuagraber Feb 28, 2024
2238c94
Merge remote-tracking branch 'origin/dev' into user_login
joshuagraber Feb 28, 2024
d206a9e
test fix
Feb 28, 2024
d510eab
cleanup
Feb 28, 2024
2055435
resolve conflict
Feb 28, 2024
5eaf59c
Update pull.yaml
mbodeantor Feb 28, 2024
8f6de15
Update login_queries.py
mbodeantor Feb 28, 2024
1ac0d69
Update pull.yaml
mbodeantor Feb 28, 2024
970af9d
Update pull.yaml
mbodeantor Feb 28, 2024
293f4ce
test(pages): update login test -> coverage 100%
joshuagraber Mar 1, 2024
522370f
test(pages): add ChangePassword test
joshuagraber Mar 1, 2024
ad5f991
test(pages): misc updates to login and change password tests
joshuagraber Mar 1, 2024
5ae00de
test(pages): add tests for ResetPassword
joshuagraber Mar 1, 2024
b43d372
update login queries
joshuagraber Mar 4, 2024
98e2b67
test(components): search results card -> 100% coverage
joshuagraber Mar 4, 2024
15a266a
test(pages): miscellaneous updates to static view
joshuagraber Mar 4, 2024
fa5e17a
ci(pull): update env setting in API test
joshuagraber Mar 6, 2024
3fab966
Merge remote-tracking branch 'origin/dev' into user_login
joshuagraber Mar 7, 2024
bc15f61
docs(README): add local client base url
joshuagraber Mar 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 7 additions & 0 deletions app.py
Expand Up @@ -2,6 +2,7 @@
from flask_restful import Api
from flask_cors import CORS
from resources.User import User
from resources.ApiKey import ApiKey
from resources.QuickSearch import QuickSearch
from resources.DataSources import DataSources
from resources.DataSources import DataSourceById
Expand All @@ -19,6 +20,11 @@
api.add_resource(
User, "/user", resource_class_kwargs={"psycopg2_connection": psycopg2_connection}
)
api.add_resource(
ApiKey,
"/api_key",
resource_class_kwargs={"psycopg2_connection": psycopg2_connection},
)
api.add_resource(
QuickSearch,
"/quick-search/<search>/<location>",
Expand Down Expand Up @@ -50,5 +56,6 @@
resource_class_kwargs={"psycopg2_connection": psycopg2_connection},
)


if __name__ == "__main__":
app.run(debug=True, host="0.0.0.0")
20 changes: 19 additions & 1 deletion app_test.py
Expand Up @@ -14,7 +14,7 @@
data_source_by_id_results,
DATA_SOURCES_APPROVED_COLUMNS,
)

from middleware.user_queries import user_get_results, user_post_results
from middleware.archives_queries import (
archives_get_results,
archives_get_query,
Expand Down Expand Up @@ -125,6 +125,24 @@ def test_data_source_by_id_approved(session):
assert not response


def test_user_get_query(session):
curs = session.cursor()
user_data = user_get_results(curs, "test")

assert user_data["password_digest"]


def test_user_post_query(session):
curs = session.cursor()
user_post_results(curs, "unit_test", "unit_test")

email_check = curs.execute(
f"SELECT email FROM users WHERE email = 'unit_test'"
).fetchone()[0]

assert email_check == "unit_test"


def test_archives_get_results(session):
response = archives_get_results(conn=session)

Expand Down
5 changes: 3 additions & 2 deletions do_db_ddl_clean.sql
Expand Up @@ -135,7 +135,7 @@ CREATE TABLE if not exists state_names (
);

CREATE TABLE if not exists users (
id bigint NOT NULL,
id serial primary key,
created_at timestamp with time zone,
updated_at timestamp with time zone,
email text NOT NULL,
Expand Down Expand Up @@ -163,4 +163,5 @@ INSERT INTO agency_source_link (link_id, airtable_uid, agency_described_linked_u
INSERT INTO agency_source_link (link_id, airtable_uid, agency_described_linked_uid) VALUES (3, 'recUGIoPQbJ6laBmr', 'recv9fMNEQTbVarj2');
INSERT INTO agency_source_link (link_id, airtable_uid, agency_described_linked_uid) VALUES (4, 'rec8gO2K86yk9mQIU', 'recRvBpZqXM8mjddz');
INSERT INTO state_names VALUES (1, 'IL', 'Illinois');
INSERT INTO state_names VALUES (2, 'PA', 'Pennsylvania');
INSERT INTO state_names VALUES (2, 'PA', 'Pennsylvania');
INSERT INTO users (email, password_digest) VALUES ("test", "test");
20 changes: 20 additions & 0 deletions middleware/user_queries.py
@@ -0,0 +1,20 @@
from werkzeug.security import generate_password_hash


def user_get_results(cursor, email):
cursor.execute(f"select id, password_digest from users where email = '{email}'")
results = cursor.fetchall()
if len(results) > 0:
user_data = {"id": results[0][0], "password_digest": results[0][1]}
return user_data
else:
return {"error": "no match"}


def user_post_results(cursor, email, password):
password_digest = generate_password_hash(password)
cursor.execute(
f"insert into users (email, password_digest) values ('{email}', '{password_digest}')"
)

return
27 changes: 25 additions & 2 deletions regular_api_checks.py
Expand Up @@ -150,7 +150,28 @@ def test_get_user():
json={"email": "test2", "password": "test"},
)

return response
return response.json()["data"] == "Successfully logged in"


def test_put_user():
response = requests.get(
"https://data-sources.pdap.io/api/user",
headers=HEADERS,
json={"email": "test2", "password": "test"},
)

return response.json()["data"] == "Successfully updated password"


# api-key
def test_get_api_key():
response = requests.get(
"https://data-sources.pdap.io/api/api_key",
headers=HEADERS,
json={"email": "test2", "password": "test"},
)

return len(response.json()["api_key"]) > 0


# archives
Expand Down Expand Up @@ -228,12 +249,14 @@ def main():
"test_quicksearch_media_bulletin_pennsylvania_results",
"test_data_source_by_id",
"test_data_sources",
"test_update_data_source",
"test_data_sources_approved",
"test_data_source_by_id_approved",
"test_search_tokens_data_sources",
"test_search_tokens_data_source_by_id",
"test_search_tokens_quick_search_complaints_allegheny_results",
# "test_get_user",
"test_get_user",
"test_get_api_key",
"test_get_archives",
"test_put_archives",
"test_put_archives_brokenasof",
Expand Down
36 changes: 36 additions & 0 deletions resources/ApiKey.py
@@ -0,0 +1,36 @@
from werkzeug.security import check_password_hash
from flask_restful import Resource
from flask import request
from middleware.user_queries import user_get_results
import uuid


class ApiKey(Resource):
def __init__(self, **kwargs):
self.psycopg2_connection = kwargs["psycopg2_connection"]

def get(self):
"""
Generate an API key for a user that successfully logs in
"""
try:
data = request.get_json()
email = data.get("email")
password = data.get("password")
cursor = self.psycopg2_connection.cursor()
user_data = user_get_results(cursor, email)

if check_password_hash(user_data["password_digest"], password):
api_key = uuid.uuid4().hex
user_id = str(user_data["id"])
cursor.execute(
"UPDATE users SET api_key = %s WHERE id = %s", (api_key, user_id)
)
payload = {"api_key": api_key}
self.psycopg2_connection.commit()
return payload

except Exception as e:
self.psycopg2_connection.rollback()
print(str(e))
return {"error": str(e)}
62 changes: 33 additions & 29 deletions resources/User.py
@@ -1,63 +1,67 @@
from werkzeug.security import generate_password_hash, check_password_hash
from flask_restful import Resource
from flask import request, jsonify
import uuid
import os
import jwt
from flask import request
from middleware.user_queries import user_get_results, user_post_results


class User(Resource):
def __init__(self, **kwargs):
self.psycopg2_connection = kwargs["psycopg2_connection"]

# Login function: allows a user to login using their email and password as credentials
# The password is compared to the hashed password stored in the users table
# Once the password is verified, an API key is generated, which is stored in the users table and sent to the verified user
def get(self):
"""
Login function: allows a user to login using their email and password as credentials
The password is compared to the hashed password stored in the users table
Once the password is verified, an API key is generated, which is stored in the users table and sent to the verified user
"""
try:
data = request.get_json()
email = data.get("email")
password = data.get("password")
cursor = self.psycopg2_connection.cursor()
cursor.execute(
f"select id, password_digest from users where email = '{email}'"
)
results = cursor.fetchall()
user_data = {}
if len(results) > 0:
user_data = {"id": results[0][0], "password_digest": results[0][1]}
else:
return {"error": "no match"}

user_data = user_get_results(cursor, email)
if check_password_hash(user_data["password_digest"], password):
api_key = uuid.uuid4().hex
user_id = str(user_data["id"])
cursor.execute(
"UPDATE users SET api_key = %s WHERE id = %s", (api_key, user_id)
)
payload = {"api_key": api_key}
self.psycopg2_connection.commit()
return payload
return {"data": "Successfully logged in"}

except Exception as e:
self.psycopg2_connection.rollback()
print(str(e))
return {"error": str(e)}

# Sign up function: allows a user to sign up by submitting an email and password. The email and a hashed password are stored in the users table and this data is returned to the user upon completion
def post(self):
"""
Sign up function: allows a user to sign up by submitting an email and password.
The email and a hashed password are stored in the users table and this data is returned to the user upon completion
"""
try:
data = request.get_json()
email = data.get("email")
password = data.get("password")
cursor = self.psycopg2_connection.cursor()
user_post_results(cursor, email, password)
self.psycopg2_connection.commit()

return {"data": "Successfully added user"}

except Exception as e:
self.psycopg2_connection.rollback()
print(str(e))
return {"error": e}

# Endpoint for updating a user's password
def put(self):
joshuagraber marked this conversation as resolved.
Show resolved Hide resolved
try:
data = request.get_json()
email = data.get("email")
mbodeantor marked this conversation as resolved.
Show resolved Hide resolved
password = data.get("password")
password_digest = generate_password_hash(password)
cursor = self.psycopg2_connection.cursor()
cursor.execute(
f"insert into users (email, password_digest) values (%s, %s)",
(email, password_digest),
f"update users set password_digest = '{password_digest}' where email = '{email}'"
)
self.psycopg2_connection.commit()

return {"data": "Successfully added user"}
return {"data": "Successfully updated password"}

except Exception as e:
self.psycopg2_connection.rollback()
Expand Down