9
9
# Define the blueprint
10
10
auth_bp = Blueprint ('auth' , __name__ )
11
11
12
+ ### Delete email collection, no dependencies (duplicate data editing, causes consistency issues)
13
+ ### check user_id and email, shouldn't be too much more expensive since
14
+ ### subcollections don't automatically load
12
15
@auth_bp .route ('/login' , methods = ['POST' ])
13
16
def login ():
14
17
db = current_app .db
15
18
data = request .get_json ()
16
- user_id = data .get ('user_id' )
19
+ user_id = data .get ('user_id' ) # either user_id or email
17
20
password = data .get ('password' )
18
21
if not user_id :
19
22
raise MissingUserIDError ()
20
23
if not password :
21
24
raise MissingPasswordError ()
22
- user_ref = db .collection ('Users' ).document (user_id ).get () # checks for UserID
23
- if not user_ref .exists :
24
- user_ref = db .collection ('UserEmails' ).document (user_id ).get () # checks emails
25
- if not user_ref .exists :
26
- raise UserNotFound ()
27
- user_id = user_ref .get ('user_id' )
28
- true_password_hash = user_ref .get ('passwordHash' )
29
- if not sha256_crypt .verify (password , true_password_hash ):
30
- raise InvalidPassword ()
31
- user = User (user_id )
32
- login_user (user )
33
- return jsonify ({"message" : f'{ current_user .id } logged in successfully.' }), 200
25
+ users_ref = db .collection ('Users' )
26
+ docs = users_ref .stream ()
27
+ for doc in docs :
28
+ user_id_db = doc .id
29
+ email_db = doc .get ('email' )
30
+ if user_id == user_id_db or user_id == email_db :
31
+ true_password_hash = doc .get ('passwordHash' )
32
+ if not sha256_crypt .verify (password , true_password_hash ):
33
+ raise InvalidPassword ()
34
+ else :
35
+ user = User (user_id )
36
+ login_user (user )
37
+ return jsonify ({"message" : f'{ current_user .id } logged in successfully.' }), 200
38
+ raise UserNotFound ()
34
39
35
40
@auth_bp .route ('/logout' , methods = ['POST' ])
36
41
@login_required
37
42
def logout ():
38
43
logout_user ()
39
44
return jsonify ({"message" : "Logged out successfully" }), 200
40
45
41
-
46
+ ### Delete email collection, no dependencies (duplicate data editing, causes consistency issues)
47
+ ### check user_id and email, shouldn't be too much more expensive since
48
+ ### subcollections don't automatically load
42
49
# return email already exists error code 400
43
50
@auth_bp .route ('/register' , methods = ['POST' ])
44
51
def register ():
@@ -60,21 +67,22 @@ def register():
60
67
date = datetime .strptime (date_joined , '%Y-%m-%d' )
61
68
except :
62
69
raise InvalidDateFormat ()
63
- if db .collection ('Users' ).document (user_id ).get ().exists :
64
- raise UserAlreadyExistsError ()
65
- if db .collection ('UserEmails' ).document (email ).get ().exists :
66
- raise EmailAlreadyExistsError ()
70
+ users_ref = db .collection ('Users' )
71
+ docs = users_ref .stream ()
72
+ for doc in docs :
73
+ existing_user_id = doc .id
74
+ existing_email = doc .get ('email' )
75
+ if user_id == existing_user_id :
76
+ raise UserAlreadyExistsError ()
77
+ if email == existing_email :
78
+ raise EmailAlreadyExistsError ()
67
79
passwordHash = sha256_crypt .hash (password )
68
80
# use set to create new document with specified data (overwrites existing documents)
69
81
db .collection ('Users' ).document (user_id ).set ({
70
82
'passwordHash' : passwordHash ,
71
83
'email' : email ,
72
84
'dateJoined' : date_joined
73
85
})
74
- db .collection ('UserEmails' ).document (email ).set ({
75
- 'user_id' :user_id ,
76
- 'passwordHash' : passwordHash
77
- })
78
86
user = User (user_id )
79
87
login_user (user )
80
88
return jsonify ({"message" : f'{ current_user .id } logged in successfully.' }), 200
@@ -84,48 +92,52 @@ def get_user_info(user_id):
84
92
user_ref = db .collection ('Users' ).document (user_id ).get ()
85
93
email = user_ref .get ('email' )
86
94
date_joined = user_ref .get ('dateJoined' )
87
- return jsonify ({"user_id" : user_id , "email" :email , "date_joined" :date_joined }), 200
95
+ return jsonify ({"user_id" : user_id , "email" :email , "date_joined" :date_joined })
88
96
89
97
@auth_bp .route ('/user_info' , methods = ['GET' ])
90
98
@login_required
91
99
def user_info ():
92
- return get_user_info (current_user .id )
93
-
100
+ return get_user_info (current_user .id ), 200
94
101
95
- # input json includes 'old_email', 'new_email', old_user_id', 'new_user_id'
96
- @auth_bp .route ('/change_user_info' , methods = ['POST' ])
102
+ ### Resource for changing an email
103
+ # input json includes 'new_email'
104
+ @auth_bp .route ('/change_user_email' , methods = ['POST' ])
97
105
@login_required
98
- def change_user_info ():
106
+ def change_user_email ():
99
107
db = current_app .db
100
108
user_id = current_user .id
101
109
data = request .get_json ()
102
- new_user_id = data .get ('new_user_id' )
103
110
new_email = data .get ('new_email' )
104
- if new_user_id != "" :
105
- # In Users collection: create new document with {new_user_id} loaded with old user data, then delete old {user_id} document
106
- # see here: https://stackoverflow.com/questions/47885921/can-i-change-the-name-of-a-document-in-firestore
107
- # In UserEmails collection: update {user_id} field for the corresponding email
108
- user_id = new_user_id
109
- user = User (user_id ) # create a new object with the new user_id
110
- login_user (user ) # login user using new user_id
111
- pass
111
+ if not new_email :
112
+ raise MissingNewEmail ()
112
113
if new_email != "" :
113
- # Get old {email} from {user_id} document
114
- # In UserEmails collection: create new document with {new_email} loaded with old {email} data, then delete old {email} document
115
- # In Users collection: find document {user_id} amd change the email to {new_email}
116
- pass
117
- return get_user_info (new_user_id )
114
+ db .collection ('Users' ).document (user_id ).update ({
115
+ 'email' : new_email ,
116
+ })
117
+ return jsonify ({"message" : "Email changed successfully." }), 201
118
118
119
- # input json includes 'old_password ', 'new_password'
119
+ # input json includes 'current_password ', 'new_password'
120
120
@auth_bp .route ('/change_user_password' , methods = ['POST' ])
121
121
@login_required
122
122
def change_user_password ():
123
123
db = current_app .db
124
124
user_id = current_user .id
125
125
data = request .get_json ()
126
- old_password = data .get ('old_password ' )
126
+ current_password = data .get ('current_password ' )
127
127
new_password = data .get ('new_password' )
128
- # In Users collection: find current user and get the {passwordHash}. Check if the hashed {old_password} matches {passwordHash} (similar to login endpoint).
129
- # If not matches, raise InvalidPassword()
130
- # If matches, hash the {new_password} and replace old value for {passwordHash} with new value for {passwordHash} on database.
128
+ user_doc = db .collection ('Users' ).document (user_id ).get ()
129
+ if not current_password :
130
+ raise MissingPasswordError ()
131
+ if not new_password :
132
+ raise MissingNewPasswordError ()
133
+ true_password_hash = user_doc .get ('passwordHash' )
134
+ if not sha256_crypt .verify (current_password , true_password_hash ):
135
+ raise InvalidPassword ()
136
+ newPasswordHash = sha256_crypt .hash (new_password )
137
+ db .collection ('Users' ).document (user_id ).update ({
138
+ 'passwordHash' : newPasswordHash ,
139
+ })
140
+ return jsonify ({"message" : 'Password changed successfully.' }), 201
141
+
131
142
143
+ ### FUTURE TODO: Reset password functionality
0 commit comments