users.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639
  1. from fastapi import APIRouter, Form, Depends, HTTPException, Body
  2. from fastapi.security import OAuth2PasswordRequestForm, OAuth2PasswordBearer
  3. from app.models.models import User, UserPydantic,User_information,User_point
  4. from app.api import deps
  5. from sqlalchemy.orm import Session
  6. from typing import Any, Dict, Optional
  7. import secrets
  8. from fastapi_login.exceptions import InvalidCredentialsException
  9. from fastapi_login import LoginManager
  10. from datetime import timedelta,datetime
  11. from app.config import settings
  12. from pathlib import Path
  13. from jose import jwt
  14. import emails
  15. from emails.template import JinjaTemplate
  16. import logging
  17. import bcrypt
  18. from app.crud import crud_users
  19. import smtplib
  20. from email.mime.text import MIMEText
  21. from google.oauth2 import id_token
  22. from google.auth.transport import requests
  23. from fastapi.openapi.models import OAuthFlows as OAuthFlowsModel
  24. from fastapi.security.utils import get_authorization_scheme_param
  25. import rpyc
  26. from datetime import timedelta,datetime
  27. from app.log import my_log
  28. from app.api.registration import input_information
  29. users = APIRouter()
  30. SECRET: str = secrets.token_urlsafe(32)
  31. manager = LoginManager(SECRET, '/login',default_expiry=timedelta(hours=72))
  32. async def check_token(access_token: str):
  33. result = await User.filter(token=access_token).first()
  34. if not result:
  35. print("no access")
  36. return None
  37. user_id = result.id
  38. return user_id
  39. @manager.user_loader()
  40. async def query_user(user_id: str):
  41. """
  42. Get a user from the db
  43. :param user_id: E-Mail of the user
  44. :return: None or the user object
  45. """
  46. # result = await User.filter(email=user_id,is_active=1).first()
  47. result = await User.filter(email=user_id).first()
  48. if not result:
  49. print('無此筆資料')
  50. return None
  51. return result
  52. #@manager.user_loader()
  53. async def query_user_username(user_id: str):
  54. """
  55. Get a user from the db
  56. :param user_id: E-Mail of the user
  57. :return: None or the user object
  58. """
  59. result = await User.filter(username=user_id).first()
  60. if not result:
  61. print('無此筆資料')
  62. return None
  63. return result
  64. @users.post("/login")
  65. async def login(data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(deps.get_db), position: str=Form(default='')):
  66. email = data.username
  67. password = data.password
  68. user = await query_user(email)
  69. if not user:
  70. # you can return any response or error of your choice
  71. return {"message":"查無此人"}
  72. # elif password != user.password:
  73. # raise InvalidCredentialsException
  74. else:
  75. user_pydantic = UserPydantic.from_orm(user)
  76. user_dict = user_pydantic.dict(exclude_unset=True)
  77. access_token = manager.create_access_token(
  78. data={'sub': email}
  79. )
  80. user_dict.update({"token":access_token})
  81. token_update = user.update_from_dict(user_dict)
  82. await user.save()
  83. try:
  84. infor,created = await User_information.get_or_create(
  85. user_id=user.id,
  86. defaults={
  87. 'name': '',
  88. 'birthday' :datetime.now(),
  89. 'gender': '',
  90. 'phone': '',
  91. 'address': '',
  92. 'position': '{"學員":1,"開課工藝家":0,"其他":0}',
  93. "identity": ''
  94. }
  95. )
  96. except Exception as e:
  97. my_log("error",__name__,str(e))
  98. stored_hashed_password = user.password.encode('utf-8')
  99. if bcrypt.checkpw(password.encode('utf-8'),stored_hashed_password):
  100. return {'msg':'登入成功','code':'200','access_token': access_token,'username':user.username,'email':user.email,'points':user.points}
  101. else:
  102. return {"message": "Invalid username or password"}
  103. oauth2_scheme = OAuth2PasswordBearer(tokenUrl="https://oauth2.googleapis.com/token")
  104. CLIENT_ID = settings.CLIENT_ID
  105. @users.post("/login/google/access-token")
  106. async def login(username: str = Form(default=''), password: str = Form(default=''), email: str = Form(default=''), position: str = Form(default=''),
  107. ) -> Any:
  108. """
  109. OAuth2 compatible token login, get an access token for future requests
  110. """
  111. access_token = manager.create_access_token(
  112. data={'sub': username}
  113. )
  114. user = await User.get_or_none(email=email) # 確認信箱是否已存在
  115. if not user:
  116. user = await User.create(username=username, password=password, email=email,token=access_token, is_superuser=0, points=0,is_active =1,create_time = datetime.now())
  117. print(user.id)
  118. else:
  119. user.token = access_token
  120. print("save token")
  121. await user.save()
  122. my_log("error",__name__,user.id)
  123. try:
  124. infor,created = await User_information.get_or_create(
  125. user_id=user.id,
  126. defaults={
  127. 'name': '',
  128. 'birthday' :datetime.now(),
  129. 'gender': '',
  130. 'phone': '',
  131. 'address': '',
  132. 'position': '{"學員":1,"開課工藝家":0,"其他":0}',
  133. "identity": ''
  134. }
  135. )
  136. except Exception as e:
  137. my_log("error",__name__,str(e))
  138. # if user:
  139. # print('已用相同信箱註冊過,再開一個GMAIL帳號')
  140. # u = await User.create(username=username, password=password,email=email)
  141. # try:
  142. # my_log("error",__name__,str(user_pydantic))
  143. # infor,created = await User_information.get_or_create(
  144. # user_id=user.id,
  145. # defaults={
  146. # 'position': {"學員":1,"開課工藝家":0,"其他":0}
  147. # }
  148. # )
  149. # except Exception as e:
  150. # my_log("error",__name__,str(e))
  151. return_msg = {
  152. "access_token": access_token,
  153. "token_type": "bearer",
  154. 'code':'200',
  155. }
  156. return return_msg
  157. # if add_time_code:
  158. # available_ser_no = crud.serial_number.available(db, ser_no=add_time_code)
  159. # print(available_ser_no)
  160. # if available_ser_no:
  161. # user_in = schemas.UserUpdate(available_time=user.available_time + available_ser_no.time)
  162. # crud.user.update(db, db_obj=user, obj_in=user_in)
  163. #
  164. # ser_no_in = schemas.SerialNumberUpdate(code=available_ser_no.code, is_used=True,
  165. # used_datetime=str(datetime.now()), owner_id=user.id)
  166. # crud.serial_number.update(db, db_obj=available_ser_no, obj_in=ser_no_in)
  167. # print(available_ser_no.time, type(available_ser_no.time))
  168. # return_msg['time_added'] = available_ser_no.time
  169. # else:
  170. # return_msg['time_added'] = -1
  171. # return return_msg
  172. @users.post("/logout")
  173. async def logout():
  174. return {"msg":"logout success","code":200}
  175. @users.post("/add") # 寄認證信
  176. async def add(username: str = Form(default=''), password: str = Form(default=''), email: str = Form(default=''), re_password: str = Form(default='')):
  177. if username and password and email:
  178. user_email = await query_user(email)
  179. user_username = await query_user_username(username)
  180. if user_email or user_username:
  181. return {"msg":"該信箱或使用者名稱已存在","code":403}
  182. # elif user_username:
  183. # return {"msg":"該使用者名稱已存在","code":403}
  184. else:
  185. if password == re_password:
  186. # access_token = manager.create_access_token(
  187. # data={'sub': email}
  188. # )
  189. print('前',settings.SECRET_KEY)
  190. expiration_time = datetime.utcnow() + timedelta(hours=0.5)
  191. access_token = jwt.encode({'email':email,'exp':expiration_time}, settings.SECRET_KEY, algorithm="HS256")
  192. hashed_password = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8')
  193. u = await User.create(username=username, password=hashed_password, email=email,is_superuser=0,token=access_token,points=0,is_active=0,create_time = datetime.now())
  194. if u:
  195. try:
  196. infor,created = await User_information.get_or_create(
  197. user_id=u.id,
  198. defaults={
  199. 'name': '',
  200. 'birthday' :datetime.now(),
  201. 'gender': '',
  202. 'phone': '',
  203. 'address': '',
  204. 'position': '{"學員":1,"開課工藝家":0,"其他":0}',
  205. "identity": ''
  206. }
  207. )
  208. except Exception as e:
  209. my_log("error",__name__,str(e))
  210. # message = '註冊認證'
  211. message = f"請點擊以下連結完成註冊流程:\n\nhttps://cmm.ai:8088/api/verify?token={access_token}"
  212. subject = '註冊信'
  213. print(message)
  214. send_email(email,access_token,subject,message)
  215. return {"msg": "已寄送註冊信", "code": 200}
  216. else:
  217. return {"msg": "未寄出註冊信", "code":403}
  218. else:
  219. return {"msg":"確認密碼錯誤","code":403}
  220. return {"msg": "create user failed", "code": 403}
  221. @users.get("/verify") # 註冊認證確認
  222. async def verify_email(token:str):
  223. try:
  224. print(token)
  225. print('後',settings.SECRET_KEY)
  226. payload = jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"])
  227. print('解密結果',payload)
  228. email = payload["email"]
  229. # is_active True
  230. user = await User.filter(email=email,is_active=0).first()
  231. if user:
  232. user_pydantic = UserPydantic.from_orm(user)
  233. user_dict = user_pydantic.dict(exclude_unset=True)
  234. user_dict.update({"is_active": 1})
  235. is_active_update = user.update_from_dict(user_dict)
  236. await user.save()
  237. result = '信箱驗證成功'
  238. else:
  239. result = '信箱驗證失敗'
  240. return {"message": result}
  241. except Exception as e:
  242. return {"msg": str(e), "code": 500}
  243. # except jwt.ExpiredSignatureError:
  244. # raise HTTPException(status_code=400, detail="token已失效")
  245. # except jwt.DecodeError:
  246. # raise HTTPException(status_code=400, detail="無效token")
  247. def generate_password_reset_token(email: str) -> str:
  248. delta = timedelta(hours=settings.EMAIL_RESET_TOKEN_EXPIRE_HOURS)
  249. now = datetime.utcnow()
  250. expires = now + delta
  251. exp = expires.timestamp()
  252. encoded_jwt = jwt.encode(
  253. {"exp": exp, "nbf": now, "sub": email}, settings.SECRET_KEY, algorithm="HS256",)
  254. print(encoded_jwt)
  255. return encoded_jwt
  256. def verify_password_reset_token(token: str):
  257. try:
  258. decoded_token = jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"])
  259. print(decoded_token)
  260. return decoded_token["sub"]
  261. except jwt.JWTError:
  262. return None
  263. def send_email(
  264. email_to: str,
  265. token: str,
  266. subject_template: str = "",
  267. html_template: str = "",
  268. environment: Dict[str, Any] = {},
  269. ):
  270. # message = emails.Message(
  271. # subject=JinjaTemplate(subject_template),
  272. # html=JinjaTemplate(html_template),
  273. # mail_from=(settings.EMAILS_FROM_NAME, settings.EMAILS_FROM_EMAIL),
  274. # )
  275. subject=subject_template
  276. html=html_template
  277. mailobj={}
  278. mailobj['toaddr']=email_to
  279. mailobj['title']=subject
  280. mailobj['totext']=html
  281. conn = rpyc.connect("192.168.192.80", 12345)
  282. conn.root.mailto(mailobj)
  283. return {"message":f"send email"}
  284. #message = emails.Message(
  285. # subject=JinjaTemplate(subject_template),
  286. # html=JinjaTemplate(html_template),
  287. # mail_from=(settings.EMAILS_FROM_NAME, settings.EMAILS_FROM_EMAIL),
  288. #)
  289. #smtp_options = {"host": settings.SMTP_HOST, "port": settings.SMTP_PORT}
  290. #if settings.SMTP_TLS:
  291. # smtp_options["tls"] = True
  292. #if settings.SMTP_USER:
  293. # smtp_options["user"] = settings.SMTP_USER
  294. #if settings.SMTP_PASSWORD:
  295. # smtp_options["password"] = settings.SMTP_PASSWORD
  296. #response = message.send(to=email_to, render=environment, smtp=smtp_options)
  297. #print('RResponse',response)
  298. #return {"message":f"send email result: {response}"}
  299. # logging.info(f"send email result: {response}")
  300. # email_content = MIMEText(message)
  301. # email_content["Subject"] = subject
  302. # email_content["From"] = 'zooey@choozmo.com'
  303. # email_content["To"] = email_to
  304. # try:
  305. # print('測試成功')
  306. # # Connect to the SMTP server
  307. # smtp_server = smtplib.SMTP(settings.SMTP_HOST, settings.SMTP_PORT)
  308. # smtp_server.starttls()
  309. #
  310. # # Login to the email account
  311. # smtp_server.login(settings.SMTP_HOST, settings.SMTP_PASSWORD)
  312. #
  313. # # Send the email
  314. # smtp_server.sendmail(settings.SMTP_HOST, email_to, email_content.as_string())
  315. #
  316. # # Close the connection
  317. # smtp_server.quit()
  318. #
  319. # return {"message": "Email sent successfully."}
  320. # except Exception as e:
  321. # print('測試失敗')
  322. def create_singup_url():
  323. url=''
  324. return url
  325. def send_reset_password_email(email_to: str, email: str, token: str) -> None:
  326. subject = f"Password recovery for user {email}"
  327. with open(Path(settings.EMAIL_TEMPLATES_DIR) / "reset_password.html") as f:
  328. template_str = f.read()
  329. server_host = settings.SERVER_HOST
  330. link = f"{server_host}/reset-password?token={token}"
  331. message = '重新設定密碼'
  332. send_email(
  333. email_to=email_to,
  334. subject_template=subject,
  335. html_template=template_str,
  336. environment={
  337. "project_name": settings.PROJECT_NAME,
  338. "username": email,
  339. "email": email_to,
  340. "valid_hours": settings.EMAIL_RESET_TOKEN_EXPIRE_HOURS,
  341. "link": link,
  342. },
  343. )
  344. @users.post("/password-recovery/{email}")
  345. async def recover_password(email:str):
  346. user = await User.filter(email=email).first()
  347. if not user:
  348. raise HTTPException(
  349. status_code=404,
  350. detail="The user with this username does not exist in the system.",
  351. )
  352. password_reset_token = generate_password_reset_token(email=email)
  353. send_reset_password_email(
  354. email_to=user.email, email=email, token=password_reset_token
  355. )
  356. return {"msg": "Password recovery email sent"}
  357. from pydantic import BaseModel
  358. class Msg(BaseModel):
  359. msg: str
  360. @users.post("/reset-password/", response_model=Msg)
  361. async def reset_password(
  362. token: str = Body(...),
  363. new_password: str = Body(...),
  364. db: Session = Depends(deps.get_db),
  365. ) -> Any:
  366. """
  367. Reset password
  368. """
  369. email = verify_password_reset_token(token)
  370. # print(email)
  371. if not email:
  372. raise HTTPException(status_code=400, detail="Invalid token")
  373. # user = await query_user(email)
  374. user = await User.filter(email=email).first()
  375. if not user:
  376. raise HTTPException(
  377. status_code=404,
  378. detail="The user with this username does not exist in the system.",
  379. )
  380. # elif not crud.user.is_active(user):
  381. # raise HTTPException(status_code=400, detail="Inactive user")
  382. hashed_password = bcrypt.hashpw(new_password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8')
  383. user.password = hashed_password
  384. # db.add(user)
  385. db.commit()
  386. # print(user.password)
  387. db.close()
  388. return {"msg": "Password updated successfully"}
  389. @users.get("/delete_user/{id}")
  390. async def delete(
  391. id: int,
  392. user_id = Depends(check_token)
  393. ):
  394. if not user_id :
  395. return {"msg": "no exit", "code": 200}
  396. user = await User.get(id=user_id)
  397. if user.is_superuser != 2:
  398. return {"msg": "no access", "code": 200}
  399. if id:
  400. await User.filter(id=id).delete()
  401. return {"msg": "success", "code": 200}
  402. return {"msg": "failed", "code": 400}
  403. @users.get("/information")
  404. async def get_information(token:str):
  405. user_id = await check_token(access_token=token)
  406. if user_id:
  407. result = await User.filter(id=user_id).first()
  408. user_point,created = await User_point.get_or_create(
  409. user_id=user_id,
  410. defaults={
  411. "hours" : 0,
  412. "points": 0
  413. }
  414. )
  415. results = {
  416. "id" : result.id,
  417. "username" : result.username,
  418. "email" : result.email,
  419. "points" : user_point.points,
  420. "hours" : user_point.hours,
  421. "token" : result.token
  422. }
  423. return {"msg":results, "code":200}
  424. else:
  425. return {"msg":"no access", "code":500}
  426. @users.get("/protect")
  427. def protected_route(user=Depends(manager)):
  428. if user is None:
  429. return {'message': "no access"}
  430. return {'user': user}
  431. @users.get("/check_user")
  432. async def check_user(
  433. user_id = Depends(check_token)
  434. ):
  435. user = await User.get(id=user_id)
  436. return {"msg": "success", "code": 200,"is_super":user.is_superuser}
  437. @users.get("/check_user_by_id")
  438. async def check_user(
  439. user_id : int
  440. ):
  441. try:
  442. user = await User.get(id=user_id)
  443. user_info = await User_information.get(user_id=user_id)
  444. except:
  445. return {"msg": "no this user", "code": 500}
  446. result = {
  447. "id" : user.id,
  448. "username" : user.username,
  449. "email" : user.email,
  450. "information" : user_info
  451. }
  452. return {"msg": "success", "code": 200,"result":result}
  453. @users.get("/user_point")
  454. async def user_point(
  455. user_id = Depends(check_token)
  456. ):
  457. try:
  458. user = await User.get_or_none(id=user_id)
  459. user_point = await User_point.get_or_none(user_id=user_id)
  460. if not user :
  461. return {"msg": "沒有權限", "code": 500}
  462. elif not user_point :
  463. return {"msg": "沒有點數資料", "code": 500}
  464. return {"msg": "success", "code": 200,"result":user_point}
  465. except Exception as e:
  466. return {"msg": str(e), "code": 500}
  467. import random
  468. import string
  469. @users.get("/get_token")
  470. async def get_token(
  471. authToken :str=None,
  472. email :str=None
  473. ):
  474. try:
  475. if authToken is None :
  476. return {"msg": "no access", "code": 500}
  477. user = await User.get_or_none(email = email)
  478. if not user:
  479. length_of_string = 8
  480. tmp = "".join(
  481. random.choice(string.ascii_letters + string.digits)
  482. for _ in range(length_of_string)
  483. )
  484. await add(username='MOC登入'+tmp,email=email, password="MOC"+tmp,re_password="MOC"+tmp)
  485. return {"msg": "no this user", "code": 500}
  486. user.token = authToken
  487. await user.save()
  488. return {"msg": "success", "code": 200,"token":authToken }
  489. except Exception as e:
  490. return {"msg": str(e), "code": 500}
  491. @users.get("/user_information_count")
  492. async def user_information_count(
  493. identity :str =None
  494. ):
  495. try:
  496. identity_list =[]
  497. if identity:
  498. identity_list = eval(identity)
  499. else :
  500. distinct_class_name_ids = await User_information.all().values_list('identity')
  501. for tmp in distinct_class_name_ids:
  502. if tmp is not None :
  503. for ids in tmp:
  504. if ids not in identity_list:
  505. identity_list.append(ids)
  506. result = None
  507. # for entry in identity_list :
  508. # if entry == "學習者":
  509. # user_list = await User_information.all()
  510. # count = 0
  511. # for user in user_list:
  512. # position_list = eval(user.identity)
  513. # class_num = await Favorite_course.filter(class_name_id = entry[0]).count()
  514. # result.append({"class_name_id":entry[0],"count":class_num})
  515. return {"msg": "success", "code": 200,"result":result}
  516. except Exception as e:
  517. return {"msg": str(e), "code": 500}