-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathclient.py
323 lines (293 loc) · 10.4 KB
/
client.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
import argparse
import requests
from util import generate_code
from sys import exit
from getpass import getpass
import json
import os
import re
from hashlib import sha512
from constants import API_BASE
HOME = os.getenv("HOME")
def does_phone_exist(phone) -> bool:
phone_json = {"phone": phone}
response = requests.post(
f"{API_BASE}/get_phone", json=phone_json, verify="generated/auth-api.com.pem"
)
if response.status_code != 400:
return True
return False
def register(args):
print(args)
username = ""
while username.strip() == "":
username = input("Enter the username you wish to register with: ")
password = ""
while password.strip() == "":
password = getpass("Enter your password: ")
retype_password = ""
while retype_password.strip() == "":
retype_password = getpass("Retype your password: ")
if not password == retype_password:
print("Password mismatch, exiting")
exit(1)
number = ""
while number.strip() == "":
number = input(
"Enter the the phone number to which codes should be delivered: "
)
print(f"Username: {username}\nPhone number: {number}")
answer = input("Is the data correct? [y/n]").lower()
if answer not in ["yes", "ye", "y"]:
print("Did not receive confirmation, exiting")
exit(1)
print("correct! lets go!")
code = generate_code()
sms_post_data = {"code": code, "phone": number}
if does_phone_exist(number):
print("Given phone number is already being used")
exit(1)
# check if status is 200 before checking this
requests.post(
f"{API_BASE}/sendsms", json=sms_post_data, verify="generated/auth-api.com.pem"
)
input_code = input(f"Code was sent to {number} for confirmation, please enter it: ")
for i in range(3):
if not input_code == code:
print(type(input_code))
print(f"DEBUG: input_code: {input_code}")
print(type(code))
print(f"DEBUG: code: {code}")
input_code = input("Code was incorrect, please try again: ")
else:
break
if not input_code == code:
print("Try limit exceeded, exiting")
exit(1)
register_post_data = {"username": username, "password": password, "phone": number}
response = requests.post(
f"{API_BASE}/register",
json=register_post_data,
verify="generated/auth-api.com.pem",
)
if response.status_code != 200:
print(
f"Code expected: 200, received: {response.status_code}, message from server: {response.content}"
)
else:
print(
f"Creation successful for username: {username}, number : {number}\nHere are the backup codes that you can use to change your settings such as username, password or phone number:"
)
codes: dict = json.loads(response.content.decode("utf-8"))
print("================")
for code in codes.values():
print(code)
print("================")
print(
"!!!MAKE SURE TO WRITE THESE CODES DOWN AS THEY WILL ONLY BE SHOWN NOW AND NEVER AGAIN!!!"
)
with open(f"{HOME}/.2fa", "w") as f:
f.write(username)
def login():
username = ""
while username.strip() == "":
username = input("Enter your username: ")
password = ""
while password.strip() == "":
password = getpass("Enter your password: ")
login_post_data = {"username": username, "password": password}
response = requests.post(
f"{API_BASE}/login", json=login_post_data, verify="generated/auth-api.com.pem"
)
if response.status_code != 200:
print(f"Login failed, message from server: {response.content}")
exit(1)
return response.json()
def change_username(user_json):
new_username = ""
while new_username.strip() == "":
new_username = input("Enter your new username: ")
print(f"This will now be your new username: {new_username}")
conf = ""
possible_answers = ["y", "n"]
while conf not in possible_answers:
conf = input("Are you sure about this change? [y/n]").lower()
if conf == "y":
user_json["username"] = new_username
response = requests.put(
f"{API_BASE}/update/username",
json=user_json,
verify="generated/auth-api.com.pem",
)
print(f"Response from server = {response.content}")
else:
print("Canceling change")
with open(f"{HOME}/.2fa", "r") as f:
lines = f.readlines()
lines[0] = new_username
with open(f"{HOME}/.2fa", "w") as f:
f.writelines(lines)
def change_password(user_json):
new_password = ""
while new_password.strip() == "":
new_password = getpass("Enter your new password: ")
retype = ""
while retype.strip() == "":
retype = getpass("Retype your new password: ")
if not new_password == retype:
print("Passwords do not match")
return
conf = ""
possible_answers = ["y", "n"]
while conf not in possible_answers:
conf = input("Are you sure about this change? [y/n]").lower()
if conf == "y":
user_json["password"] = new_password
response = requests.put(
f"{API_BASE}/update/password",
json=user_json,
verify="generated/auth-api.com.pem",
)
print(f"Response from server = {response.content}")
else:
print("Canceling change")
def change_phone(user_json):
new_phone = ""
print("Warning!")
print(
"In order to change your phone number, you have will have to input a backup code, that you were given during your registration"
)
while new_phone.strip() == "":
new_phone = input("Enter your new phone number: ")
code = generate_code()
sms_post_data = {"code": code, "phone": new_phone}
if does_phone_exist(new_phone):
print("Given phone number is already being used")
return
# check if status is 200 before checking this
requests.post(
f"{API_BASE}/sendsms", json=sms_post_data, verify="generated/auth-api.com.pem"
)
input_code = input(
f"Code was sent to {new_phone} for confirmation, please enter it: "
)
for i in range(3):
if not input_code == code:
print(type(input_code))
print(f"DEBUG: input_code: {input_code}")
print(type(code))
print(f"DEBUG: code: {code}")
input_code = input("Code was incorrect, please try again: ")
else:
break
backup_code: str = ""
while backup_code.strip() == "":
backup_code = input("Enter one of your backup codes: ")
user_json["backup_code"] = backup_code
user_json["new_phone"] = new_phone
response = requests.put(
f"{API_BASE}/update/phone", json=user_json, verify="generated/auth-api.com.pem"
)
print(f"Response from server = {response.content}")
def delete_account(user_json):
prog = re.compile(r'.*pam_2fa.so.*')
print("WARNING!")
print(
"MAKE SURE THAT YOU DO NOT HAVE AN ACTIVE PAM CONFIGURATION USING pam_2fa.so"
)
print("DELETING YOUR ACCOUNT WHILE KEEPING THE CONFIGURATION WILL LOCK YOU OUT OF YOUR DEVICE")
files = ["/etc/pam.d/gdm-password", "/etc/pam.d/common-auth"]
for file in files:
with open(file, "r") as f:
lines = f.readlines()
if any((match := prog.match(item)) for item in lines):
if(match.group(0).startswith("#")):
print(f"Configuration found in {file}, but configuration was commented out, highly suggested to delete it if you are deleting your account")
print(match.group(0))
else:
print(f"UNCOMMENTED CONFIGURATION FOUND IN {file}, ABORTING ACCOUNT DELETION")
print(match.group(0))
exit(1)
possible_answers = ["y", "n"]
conf = ''
while conf not in possible_answers:
conf = input("Are you sure about this change? [y/n]").lower()
if conf == "y":
response = requests.delete(
f"{API_BASE}/delete", json=user_json, verify="generated/auth-api.com.pem"
)
print(f"Response from server = {response.content}")
exit(1)
else:
print("ABORTING DELETION")
exit(1)
def print_menu():
menu_options = {
1: "Change username",
2: "Change password",
3: "Change phone number",
4: "Delete account",
5: "Exit",
}
for key in menu_options.keys():
print(key, "--", menu_options[key])
def account(args):
user_json = login()
print(user_json)
print(type(user_json))
while True:
print_menu()
option = ""
try:
option = int(input("[1-5]: "))
except:
print("Not a number...\n")
if option == 1:
change_username(user_json)
elif option == 2:
change_password(user_json)
elif option == 3:
change_phone(user_json)
elif option == 4:
delete_account(user_json)
elif option == 5:
exit(1)
else:
print("Pick a number between 1 and 5")
def generate():
try:
f = open(f"{HOME}/.2fa", "r")
except:
print(f"Unable to open file in {HOME}/.2fa")
lines = f.readlines()
f.close()
print(
"Here are the emergency codes that you can use to log in with no internet connection"
)
print("=====")
with open(f"{HOME}/.2fa", "w") as f:
f.writelines(lines[0])
for i in range(10):
code = generate_code()
print(code)
f.write(sha512(code.encode("utf-8")).hexdigest() + "\n")
print("=====")
print("WRITE THEM DOWN AS THE VALUES HELD IN ~/.2fa WILL BE HASHED")
if __name__ == "__main__":
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest="command")
subparsers.required = True
register_parser = subparsers.add_parser("register", help="register to 2fa service")
reset_parser = subparsers.add_parser(
"account", help="update details for existing account"
)
generator_parser = subparsers.add_parser(
"generate", help="generate local backup codes for emergency use"
)
args = parser.parse_args()
if args.command == "register":
register(args)
elif args.command == "account":
account(args)
elif args.command == "generate":
generate()