steveagi commited on
Commit
e63b9e1
·
1 Parent(s): 9a91588

storage app

Browse files
Files changed (6) hide show
  1. src/__init__.py +0 -0
  2. src/app/__init__.py +0 -0
  3. src/app/storage.py +285 -0
  4. src/app/utils.py +280 -0
  5. src/main.py +3 -3
  6. src/status.py +10 -0
src/__init__.py ADDED
File without changes
src/app/__init__.py ADDED
File without changes
src/app/storage.py ADDED
@@ -0,0 +1,285 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import Response
2
+ from .. import status
3
+ from . import utils
4
+ from fastapi.routing import APIRouter
5
+ from pgsoft.pghash.md5 import md5
6
+
7
+
8
+ router = APIRouter(
9
+ prefix="/storage",
10
+ tags=["storage"],
11
+ )
12
+ admin_token_md5 = "01112732d503cd3f0898d185a11c8b92"
13
+
14
+
15
+ @router.post("/create_user")
16
+ async def create_user(admin_token: str, email: str, response: Response) -> dict:
17
+ """ Create a normal user with user's email
18
+
19
+ Args:
20
+ admin_token (str): a token string of administrator
21
+ email (str): user's email
22
+
23
+ Returns:
24
+ dict: keys includes "status", "msg" and "data"
25
+ """
26
+ if md5(admin_token) != admin_token_md5:
27
+ response.status_code = status.HTTP_401_UNAUTHORIZED
28
+ return {
29
+ "status": status.FAILURE,
30
+ "data": {},
31
+ "msg": "Unauthorized"
32
+ }
33
+ email = email.lower().strip()
34
+ if utils.is_user_existed(email):
35
+ response.status_code = status.HTTP_304_NOT_MODIFIED
36
+ return {
37
+ "status": status.FAILURE,
38
+ "data": {},
39
+ "msg": "User already exists"
40
+ }
41
+ secret = utils.create_account(email, status.USER)
42
+ if secret is None:
43
+ response.status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
44
+ return {
45
+ "status": status.FAILURE,
46
+ "data": {},
47
+ "msg": "creating normal user failed"
48
+ }
49
+ response.status_code = status.HTTP_201_CREATED
50
+ return {
51
+ "status": status.SUCCESS,
52
+ "data": {
53
+ "secret": secret,
54
+ "email": email
55
+ },
56
+ "msg": "created normal user successfully."
57
+ }
58
+
59
+
60
+ @router.post("/register")
61
+ async def register(secret: str, email: str, password: str, response: Response) -> dict:
62
+ """ Register an account with user's secret and password
63
+
64
+ Args:
65
+ secret (str): a random md5code provided by administrator
66
+ email (str): user's email
67
+ password (str): user's password
68
+
69
+ Returns:
70
+ dict: keys includes "status", "msg" and "data"
71
+ """
72
+ email = email.lower().strip()
73
+ if not utils.is_user_existed(email):
74
+ response.status_code = status.HTTP_404_NOT_FOUND
75
+ return {
76
+ "status": status.FAILURE,
77
+ "data": {},
78
+ "msg": "user not found"
79
+ }
80
+ if utils.is_user_registered(email):
81
+ response.status_code = status.HTTP_304_NOT_MODIFIED
82
+ return {
83
+ "status": status.FAILURE,
84
+ "data": {},
85
+ "msg": "you can't register repeatedly"
86
+ }
87
+ saved_secret = utils.get_secret(email)
88
+ if saved_secret is None:
89
+ response.status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
90
+ return {
91
+ "status": status.FAILURE,
92
+ "data": {},
93
+ "msg": "secret not existed"
94
+ }
95
+ if saved_secret != secret:
96
+ response.status_code = status.HTTP_304_NOT_MODIFIED
97
+ return {
98
+ "status": status.FAILURE,
99
+ "data": {},
100
+ "msg": "incorrect secret or maybe your email is wrong",
101
+ }
102
+ if not utils.set_password(email, md5(password)):
103
+ response.status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
104
+ return {
105
+ "status": status.FAILURE,
106
+ "data": {},
107
+ "msg": "failed to register"
108
+ }
109
+ if not utils.remove_user_secret(email):
110
+ response.status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
111
+ return {
112
+ "status": status.FAILURE,
113
+ "data": {},
114
+ "msg": "failed to remove secret"
115
+ }
116
+ response.status_code = status.HTTP_200_OK
117
+ return {
118
+ "status": status.SUCCESS,
119
+ "data": {},
120
+ "msg": "Register Successfully, please login"
121
+ }
122
+
123
+
124
+ @router.post("/login")
125
+ async def login(email: str, password: str, response: Response) -> dict:
126
+ if email is None or email == "":
127
+ response.status_code = status.HTTP_400_BAD_REQUEST
128
+ return {
129
+ "status": status.FAILURE,
130
+ "data": {},
131
+ "msg": "email is empty"
132
+ }
133
+ if password is None or password == "":
134
+ response.status_code = status.HTTP_400_BAD_REQUEST
135
+ return {
136
+ "status": status.FAILURE,
137
+ "data": {},
138
+ "msg": "password is empty"
139
+ }
140
+ email = email.lower().strip()
141
+ if not utils.is_user_existed(email):
142
+ response.status_code = status.HTTP_404_NOT_FOUND
143
+ return {
144
+ "status": status.FAILURE,
145
+ "data": {},
146
+ "msg": "email does not exist"
147
+ }
148
+ password_digest = utils.get_password(email)
149
+ if password_digest is None:
150
+ response.status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
151
+ return {
152
+ "status": status.FAILURE,
153
+ "data": {},
154
+ "msg": "password not existed"
155
+ }
156
+ password = md5(password)
157
+ if password_digest != password:
158
+ response.status_code = status.HTTP_400_BAD_REQUEST
159
+ return {
160
+ "status": status.FAILURE,
161
+ "data": {},
162
+ "msg": "incorrect password"
163
+ }
164
+ response.status_code = status.HTTP_200_OK
165
+ return {
166
+ "status": status.SUCCESS,
167
+ "data": {},
168
+ "msg": "login succeeded"
169
+ }
170
+
171
+
172
+ @router.post("/delete_password")
173
+ async def delete_password(admin_token: str, email: str, response: Response) -> dict:
174
+ """ delete password with user's email
175
+
176
+ Args:
177
+ admin_token (str): a token string of administrator
178
+ email (str): user's email
179
+
180
+ Returns:
181
+ dict: keys includes "status", "msg" and "data"
182
+ """
183
+ if md5(admin_token) != admin_token_md5:
184
+ response.status_code = status.HTTP_401_UNAUTHORIZED
185
+ return {
186
+ "status": status.FAILURE,
187
+ "data": {},
188
+ "msg": "Unauthorized"
189
+ }
190
+ email = email.lower().strip()
191
+ if not utils.is_user_existed(email):
192
+ response.status_code = status.HTTP_304_NOT_MODIFIED
193
+ return {
194
+ "status": status.FAILURE,
195
+ "data": {},
196
+ "msg": "User does not exists"
197
+ }
198
+ if not utils.get_password(email):
199
+ response.status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
200
+ return {
201
+ "status": status.FAILURE,
202
+ "data": {},
203
+ "msg": "password not existed"
204
+ }
205
+ if not utils.delete_password(email):
206
+ response.status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
207
+ return {
208
+ "status": status.FAILURE,
209
+ "data": {},
210
+ "msg": "failed to delete password"
211
+ }
212
+ secret = utils.generate_secret(email)
213
+ if secret is None:
214
+ response.status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
215
+ return {
216
+ "status": status.FAILURE,
217
+ "data": {},
218
+ "msg": "creating secret failed"
219
+ }
220
+ response.status_code = status.HTTP_200_OK
221
+ return {
222
+ "status": status.SUCCESS,
223
+ "data": {
224
+ "secret": secret,
225
+ "email": email
226
+ },
227
+ "msg": "successfully deleted password."
228
+ }
229
+
230
+
231
+ @router.post("/reset_password")
232
+ async def reset_password(secret: str, email: str, password: str, response: Response) -> dict:
233
+ """ Reset password with user's email
234
+
235
+ Args:
236
+ secret (str): a random md5code provided by administrator
237
+ email (str): user's email
238
+ password (str): user's password
239
+
240
+ Returns:
241
+ dict: keys includes "status", "msg" and "data"
242
+ """
243
+ email = email.lower().strip()
244
+ if not utils.is_user_existed(email):
245
+ response.status_code = status.HTTP_404_NOT_FOUND
246
+ return {
247
+ "status": status.FAILURE,
248
+ "data": {},
249
+ "msg": "user not found"
250
+ }
251
+ saved_secret = utils.get_secret(email)
252
+ if saved_secret is None:
253
+ response.status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
254
+ return {
255
+ "status": status.FAILURE,
256
+ "data": {},
257
+ "msg": "secret not existed"
258
+ }
259
+ if saved_secret != secret:
260
+ response.status_code = status.HTTP_304_NOT_MODIFIED
261
+ return {
262
+ "status": status.FAILURE,
263
+ "data": {},
264
+ "msg": "incorrect secret or maybe your email is wrong",
265
+ }
266
+ if not utils.set_password(email, md5(password)):
267
+ response.status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
268
+ return {
269
+ "status": status.FAILURE,
270
+ "data": {},
271
+ "msg": "failed to change password"
272
+ }
273
+ if not utils.remove_user_secret(email):
274
+ response.status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
275
+ return {
276
+ "status": status.FAILURE,
277
+ "data": {},
278
+ "msg": "failed to remove secret"
279
+ }
280
+ response.status_code = status.HTTP_200_OK
281
+ return {
282
+ "status": status.SUCCESS,
283
+ "data": {},
284
+ "msg": "Reset password successfully, please login"
285
+ }
src/app/utils.py ADDED
@@ -0,0 +1,280 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import huggingface_hub as hf
2
+ import os
3
+ from pgsoft import pgfile
4
+ from pgsoft.pghash.md5 import md5
5
+ from pgsoft.pgdate.date_utils import beijing
6
+
7
+
8
+ hfapi = hf.HfApi()
9
+ repo_id = "pgsoft/pguser"
10
+ user_file_dir = "users"
11
+ db_token = os.getenv("db_token")
12
+
13
+
14
+ def file_exists(filename: str) -> bool:
15
+ """ Check if a file exist in dataset pguser
16
+
17
+ Args:
18
+ filename (str): relative path of the file
19
+
20
+ Returns:
21
+ bool: True if exist, False otherwise
22
+ """
23
+ return hfapi.file_exists(repo_id=repo_id,
24
+ filename=filename,
25
+ repo_type="dataset",
26
+ token=db_token)
27
+
28
+
29
+ def upload_file(localpath: str, remotepath: str, commit_message: str = None) -> bool:
30
+ """ upload a file to dataset pguser
31
+
32
+ Args:
33
+ localpath (str): the local path of the file
34
+ remotepath (str): the remote path of the file
35
+
36
+ Returns:
37
+ bool: True if success, False if failed
38
+ """
39
+ return pgfile.upload(localpath=localpath,
40
+ remotepath=remotepath,
41
+ repo_id=repo_id,
42
+ repo_type="dataset",
43
+ token=db_token,
44
+ commit_message=commit_message)
45
+
46
+
47
+ def download_file(remotepath: str) -> str | None:
48
+ """ download a file from dataset pguser
49
+
50
+ Args:
51
+ remotepath (str): the remote path of the file
52
+
53
+ Returns:
54
+ str | None: the local path of the file,
55
+ return None if failed or file not exists
56
+ """
57
+ return pgfile.download(repo_id=repo_id,
58
+ repo_type="dataset",
59
+ remotepath=remotepath,
60
+ token=db_token,
61
+ localdir="./")
62
+
63
+
64
+ def userdir(email: str) -> str:
65
+ """ the path to store userdata
66
+
67
+ Args:
68
+ email (str): user's email
69
+
70
+ Returns:
71
+ str: the path to store userdata
72
+ """
73
+ subdir = md5(email)[:5]
74
+ return f"{user_file_dir}/{subdir}/{email}"
75
+
76
+
77
+ def is_user_existed(email: str) -> bool:
78
+ """ Check if user exists in dataset according to if user's role file exists
79
+
80
+ Args:
81
+ email (str): user's email
82
+
83
+ Returns:
84
+ bool: True if exists, False if not
85
+ """
86
+ filepath = f"{userdir(email)}/role"
87
+ return file_exists(filepath)
88
+
89
+
90
+ def is_user_registered(email: str) -> bool:
91
+ """ Check if a existed user registered according to if user's password file exists
92
+
93
+ Args:
94
+ email (str): user's email
95
+
96
+ Returns:
97
+ bool: True if exists, False if not
98
+ """
99
+ filepath = f"{userdir(email)}/password"
100
+ return file_exists(filepath)
101
+
102
+
103
+ def get_secret(email: str) -> str | None:
104
+ """ Get secret of user
105
+
106
+ Args:
107
+ email (str): user's email
108
+
109
+ Returns:
110
+ str: a random md5code of user saved in dataset
111
+ """
112
+ filepath = f"{userdir(email)}/secret"
113
+ localpath = download_file(filepath)
114
+ if localpath is None:
115
+ return None
116
+ try:
117
+ with open(localpath, "r") as f:
118
+ secret = f.read()
119
+ return secret
120
+ except Exception as e:
121
+ print(f"[get_secret]Failed to read secret, "
122
+ + f"{type(e)}: {e}")
123
+ return None
124
+
125
+
126
+ def generate_user_secret() -> str:
127
+ """ Generate a unique random md5code as user's secret
128
+
129
+ Returns:
130
+ str: a random md5code
131
+ """
132
+ secret = md5(beijing())
133
+ return secret
134
+
135
+
136
+ def remove_user_secret(email: str) -> bool:
137
+ """ remove user's secret
138
+
139
+ Args:
140
+ email (str): user's email
141
+
142
+ Returns:
143
+ bool: True if succeed, False if failed
144
+ """
145
+ filepath = f"{userdir(email)}/secret"
146
+ try:
147
+ hfapi.delete_file(repo_id=repo_id,
148
+ repo_type="dataset",
149
+ path_in_repo=filepath,
150
+ token=db_token,
151
+ commit_message="remove secret")
152
+ return True
153
+ except Exception as e:
154
+ print(f"[remove_user_secret]Failed to remove secret, "
155
+ + f"{type(e)}: {e}")
156
+ return False
157
+
158
+
159
+ def create_account(email: str, role: str) -> str | None:
160
+ """ Create a account of designated role, which could be ADMIN or USER
161
+
162
+ Args:
163
+ email (str): user's email
164
+ role (str): the role of the account to be created
165
+
166
+ Returns:
167
+ str: the secret to be sent to user
168
+ """
169
+ prefix = userdir(email)
170
+ if not os.path.exists(prefix):
171
+ os.makedirs(prefix)
172
+ role_path = f"{prefix}/role"
173
+ secret_path = f"{prefix}/secret"
174
+ secret = generate_user_secret()
175
+ try:
176
+ with open(role_path, "w") as f:
177
+ f.write(role)
178
+ with open(secret_path, "w") as f:
179
+ f.write(secret)
180
+ if not upload_file(role_path, role_path, "create account"):
181
+ return None
182
+ if not upload_file(secret_path, secret_path, "create account"):
183
+ return None
184
+ return secret
185
+ except Exception as e:
186
+ print(f"[create_account]{type(e)}: {e}")
187
+ return None
188
+
189
+
190
+ def set_password(email: str, password: str) -> bool:
191
+ """ save user's password to user's directory
192
+
193
+ Args:
194
+ email (str): user's email
195
+ password (str): password to save
196
+
197
+ Returns:
198
+ bool: True if succeed, False if failed
199
+ """
200
+ prefix = userdir(email)
201
+ if not os.path.exists(prefix):
202
+ os.makedirs(prefix)
203
+ filepath = f"{prefix}/password"
204
+ try:
205
+ with open(filepath, "w") as f:
206
+ f.write(password)
207
+ return upload_file(filepath, filepath, "set password")
208
+ except Exception as e:
209
+ print(f"[set_password] {type(e)}: {e}")
210
+ return False
211
+
212
+
213
+ def get_password(email: str) -> str | None:
214
+ """ Get password of user
215
+
216
+ Args:
217
+ email (str): user's email
218
+
219
+ Returns:
220
+ str: password hashed with MD5
221
+ """
222
+
223
+ filepath = f"{userdir(email)}/password"
224
+ localpath = download_file(filepath)
225
+ if localpath is None:
226
+ return None
227
+ try:
228
+ with open(localpath, "r") as f:
229
+ password = f.read()
230
+ return password
231
+ except Exception as e:
232
+ print(f"[get_password]Failed to read password, "
233
+ + f"{type(e)}: {e}")
234
+ return None
235
+
236
+
237
+ def delete_password(email: str) -> bool:
238
+ """ delete user's password
239
+
240
+ Args:
241
+ email (str): user's email
242
+
243
+ Returns:
244
+ bool: True if succeed, False if failed
245
+ """
246
+ filepath = f"{userdir(email)}/password"
247
+ try:
248
+ hfapi.delete_file(repo_id=repo_id,
249
+ repo_type="dataset",
250
+ path_in_repo=filepath,
251
+ token=db_token,
252
+ commit_message="delete password")
253
+ return True
254
+ except Exception as e:
255
+ print(f"[delete_password]Failed to delete password, "
256
+ + f"{type(e)}: {e}")
257
+ return False
258
+
259
+
260
+ def generate_secret(email: str) -> str | None:
261
+ """ Create a user secret
262
+ Args:
263
+ email (str): user's email
264
+ Returns:
265
+ str: the secret to be sent to user
266
+ """
267
+ prefix = userdir(email)
268
+ if not os.path.exists(prefix):
269
+ os.makedirs(prefix)
270
+ secret_path = f"{prefix}/secret"
271
+ secret = generate_user_secret()
272
+ try:
273
+ with open(secret_path, "w") as f:
274
+ f.write(secret)
275
+ if not upload_file(secret_path, secret_path, "create account"):
276
+ return None
277
+ return secret
278
+ except Exception as e:
279
+ print(f"[create_account]{type(e)}: {e}")
280
+ return None
src/main.py CHANGED
@@ -1,15 +1,15 @@
1
  from fastapi import FastAPI
2
  import uvicorn
3
- from src.app import user
4
 
5
 
6
  app = FastAPI()
7
- app.include_router(user.router)
8
 
9
 
10
  @app.get("/")
11
  def root():
12
- return {"detail": "PgSoft User API"}
13
 
14
 
15
  if __name__ == "__main__":
 
1
  from fastapi import FastAPI
2
  import uvicorn
3
+ from src.app import storage
4
 
5
 
6
  app = FastAPI()
7
+ app.include_router(storage.router)
8
 
9
 
10
  @app.get("/")
11
  def root():
12
+ return {"detail": "Storage server"}
13
 
14
 
15
  if __name__ == "__main__":
src/status.py ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ from starlette.status import *
2
+
3
+
4
+ SUCCESS = "OK"
5
+ FAILURE = "Failure"
6
+
7
+ # Roles
8
+ ADMIN = "admin"
9
+ USER = "user"
10
+ GUEST = "guest"