logic.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. # Site Back-End Logic
  2. from hmac import new
  3. from flask import Blueprint, render_template, request, flash, redirect, url_for, make_response
  4. from flask_login import login_user, login_required, logout_user, current_user
  5. from argon2 import PasswordHasher
  6. from .models import User, Message
  7. from . import db
  8. import string, secrets
  9. from flask_wtf import FlaskForm
  10. from wtforms import StringField, SubmitField, PasswordField, FileField, IntegerField, RadioField, TextAreaField
  11. from wtforms.validators import DataRequired
  12. logic = Blueprint('logic', __name__)
  13. # Route Rate Limiter
  14. # Application level DoS Protection
  15. from flask import Flask
  16. from flask_limiter import Limiter # HTTP Rate Limit
  17. from flask_limiter.util import get_remote_address
  18. app = Flask(__name__)
  19. limiter = Limiter(app, key_func=get_remote_address)
  20. # WTForm Classes
  21. class DescForm(FlaskForm):
  22. # declare form field, required input, placeholder and validate data
  23. description = TextAreaField(validators=[DataRequired()])
  24. submit = SubmitField('Update your Description')
  25. class PasswdForm(FlaskForm):
  26. passwd_1 = PasswordField('Edit Password', validators=[DataRequired()])
  27. passwd_2 = PasswordField('Confirm Password', validators=[DataRequired()])
  28. submit = SubmitField('Update Password')
  29. class MsgForm(FlaskForm):
  30. msg = TextAreaField(validators=[DataRequired()])
  31. submit = SubmitField('Send')
  32. class LoginForm(FlaskForm):
  33. email = StringField(validators=[DataRequired()])
  34. passwd = PasswordField(validators=[DataRequired()])
  35. submit = SubmitField('Login')
  36. class RegForm(FlaskForm):
  37. email = StringField(validators=[DataRequired()])
  38. passwd_1 = PasswordField(validators=[DataRequired()])
  39. passwd_2 = PasswordField(validators=[DataRequired()])
  40. firstname = StringField(validators=[DataRequired()])
  41. lastname = StringField(validators=[DataRequired()])
  42. age = IntegerField(validators=[DataRequired()])
  43. gender = RadioField('Gender', choices=[('M', 'Male'),('F', 'Female')], validators=[DataRequired()])
  44. submit = SubmitField('Register')
  45. @logic.route('/', methods=['GET', 'POST'])
  46. @login_required
  47. @limiter.limit('50 per hour')
  48. def profile():
  49. form_1 = DescForm()
  50. form_2 = PasswdForm()
  51. ph = PasswordHasher()
  52. # WTForm Validation Checks
  53. if form_1.validate_on_submit():
  54. new_desc = form_1.description.data
  55. form_1.description.data = ''
  56. new_desc_dbcall = User.query.filter_by(id=current_user.id).first()
  57. new_desc_dbcall.description = new_desc
  58. db.session.commit()
  59. flash('Hooray! A new description!', category='success')
  60. elif form_2.validate_on_submit():
  61. passwd_1 = form_2.passwd_1.data
  62. passwd_2 = form_2.passwd_2.data
  63. form_2.passwd_1.data = ''
  64. form_2.passwd_2.data = ''
  65. if passwd_1 == passwd_2:
  66. if len(passwd_2) >= 12:
  67. npasswd = ph.hash(passwd_con)
  68. new_passwd_dbcall = User.query.filter_by(id=current_user.id).first()
  69. new_passwd_dbcall.password = npasswd
  70. db.session.commit()
  71. flash('Nice! Updated your password!', category='success')
  72. else:
  73. flash('Password must be equal or longer than 12 characters!', category='error')
  74. else:
  75. flash('Oh no! Your passwords must match!', category='error')
  76. # DB Insertions
  77. if request.method == "POST":
  78. new_pic = request.files.get('profilepic_upload')
  79. if new_pic:
  80. # generate random filename for uploaded file
  81. alphanumeric = string.ascii_letters + string.digits
  82. ralphanum = ''.join(secrets.choice(alphanumeric) for i in range(16))
  83. new_pic_dbcall = User.query.filter_by(id=current_user.id).first()
  84. if '.png' in new_pic.filename:
  85. new_pic.save(f'app/static/uploads/{ralphanum}.png')
  86. new_pic_dbcall.profile_image = f'{ralphanum}.png'
  87. elif '.jpg' in new_pic.filename or 'jpeg' in new_pic.filename:
  88. new_pic.save(f'app/static/uploads/{ralphanum}.jpeg')
  89. new_pic_dbcall.profile_image = f'{ralphanum}.jpeg'
  90. db.session.commit()
  91. response = make_response(render_template("profile.html", user = current_user, form1 = form_1, form2= form_2))
  92. response.headers['Content-Security-Policy'] = "default-src 'self'"
  93. response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
  94. response.headers['X-Content-Type-Options'] = 'nosniff'
  95. response.headers['X-Frame-Options'] = 'SAMEORIGIN'
  96. return response
  97. @logic.route('/matchbook', methods=['GET', 'POST'])
  98. @limiter.limit('50 per hour')
  99. @login_required
  100. def matchbook():
  101. all_users = User.query.all()
  102. if request.method == 'POST':
  103. recipient_id = request.form.get('message_recipient')
  104. fdbcall = User.query.filter_by(id=current_user.id).first()
  105. fdbcall.focus = recipient_id
  106. db.session.commit()
  107. response = make_response(redirect(url_for('logic.messaging')))
  108. response.headers['Content-Security-Policy'] = "default-src 'self'"
  109. return response
  110. response = make_response(render_template("matchbook.html", user=current_user, userlist=all_users))
  111. response.headers['Content-Security-Policy'] = "default-src 'self'"
  112. response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
  113. response.headers['X-Content-Type-Options'] = 'nosniff'
  114. response.headers['X-Frame-Options'] = 'SAMEORIGIN'
  115. return response
  116. @logic.route('/messaging', methods=['GET', 'POST'])
  117. @login_required
  118. @limiter.limit('500 per hour')
  119. def messaging():
  120. recipient_id = current_user.focus
  121. recipient = User.query.filter_by(id=recipient_id).first()
  122. sent_history = Message.query.filter_by(sender=current_user.id, recipient=recipient.id).all()
  123. recv_history = Message.query.filter_by(sender=recipient.id, recipient=current_user.id).all()
  124. joint_history = sent_history + recv_history
  125. joint_history.sort(key=lambda x: x.id) # add reverse=True for descending
  126. form = MsgForm()
  127. if form.validate_on_submit():
  128. message = form.msg.data
  129. form.msg.data = ''
  130. new_msg_dbcall = Message(sender=current_user.id, recipient=recipient.id, message=message)
  131. db.session.add(new_msg_dbcall)
  132. db.session.commit()
  133. response = make_response(redirect(url_for('logic.messaging')))
  134. response.headers['Content-Security-Policy'] = "default-src 'self'"
  135. response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
  136. response.headers['X-Content-Type-Options'] = 'nosniff'
  137. response.headers['X-Frame-Options'] = 'SAMEORIGIN'
  138. return response
  139. response = make_response(render_template(
  140. "messaging.html",
  141. user = current_user,
  142. recipient = recipient,
  143. msg_hist = joint_history,
  144. form = form
  145. ))
  146. response.headers['Content-Security-Policy'] = "default-src 'self'"
  147. response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
  148. response.headers['X-Content-Type-Options'] = 'nosniff'
  149. response.headers['X-Frame-Options'] = 'SAMEORIGIN'
  150. return response
  151. @logic.route('/login', methods=['GET', 'POST'])
  152. @limiter.limit('50 per hour')
  153. def login():
  154. form = LoginForm()
  155. ph = PasswordHasher()
  156. if form.validate_on_submit():
  157. email = form.email.data
  158. form.email.data = ''
  159. challenge_passwd = form.passwd.data
  160. form.passwd.data = ''
  161. user = User.query.filter_by(email=email).first()
  162. if user:
  163. if ph.verify(user.password, challenge_passwd):
  164. flash('Successful Login!', category='success')
  165. login_user(user, remember=True)
  166. return redirect(url_for('logic.profile'))
  167. else:
  168. flash('Unsucessful Login!', category='error')
  169. else:
  170. flash('Unsucessful Login!', category='error')
  171. response = make_response (render_template(
  172. "login.html",
  173. user = current_user,
  174. form = form
  175. ))
  176. response.headers['Content-Security-Policy'] = "default-src 'self'"
  177. response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
  178. response.headers['X-Content-Type-Options'] = 'nosniff'
  179. response.headers['X-Frame-Options'] = 'SAMEORIGIN'
  180. return response
  181. @logic.route('/logout')
  182. @login_required
  183. @limiter.limit('50 per hour')
  184. def logout():
  185. logout_user()
  186. response = make_response(redirect(url_for('logic.login')))
  187. response.headers['Content-Security-Policy'] = "default-src 'self'"
  188. response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
  189. response.headers['X-Content-Type-Options'] = 'nosniff'
  190. response.headers['X-Frame-Options'] = 'SAMEORIGIN'
  191. return response
  192. # Signup Route
  193. @logic.route('/register', methods=['GET', 'POST'])
  194. @limiter.limit('50 per hour')
  195. def register():
  196. pass_list = list()
  197. ph = PasswordHasher()
  198. form = RegForm()
  199. if form.validate_on_submit():
  200. email = form.email.data
  201. firstname = form.firstname.data
  202. lastname = form.lastname.data
  203. age = form.age.data
  204. gender = form.gender.data
  205. passwd_1 = form.passwd_1.data
  206. passwd_2 = form.passwd_2.data
  207. # Basic User Input Checks
  208. email_check = User.query.filter_by(email=email).first()
  209. if len(email) < 1:
  210. flash('Your Email must be longer than 0 characters.', category='error')
  211. elif email_check:
  212. flash('This Email is already taken', category='error')
  213. else:
  214. pass_list.append('p')
  215. if len(firstname) < 1:
  216. flash('First name must be something', category='error')
  217. else:
  218. pass_list.append('p')
  219. if len(lastname) < 1:
  220. flash('Last name must be something', category='error')
  221. else:
  222. pass_list.append('p')
  223. if gender == 'M' or gender == 'F':
  224. pass_list.append('p')
  225. else:
  226. flash('Gender must be either M or F!', category='error')
  227. if len(passwd_1) < 12 or len(passwd_2) < 12:
  228. flash('Your Password must be longer than or equal to 12 characters.', category='error')
  229. else:
  230. if passwd_1 != passwd_2:
  231. flash('Your Passwords must match!', category='error')
  232. else:
  233. if len(pass_list) == 4:
  234. npasswd = ph.hash(passwd_2)
  235. new_user = User(email=email, firstname=firstname, lastname=lastname, age=age, gender=gender, password=npasswd)
  236. db.session.add(new_user)
  237. db.session.commit()
  238. flash('Account Registration Successful!', category='success')
  239. response = make_response(redirect(url_for('logic.profile')))
  240. response.headers['Content-Security-Policy'] = "default-src 'self'"
  241. response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
  242. response.headers['X-Content-Type-Options'] = 'nosniff'
  243. response.headers['X-Frame-Options'] = 'SAMEORIGIN'
  244. return response
  245. else:
  246. flash('Registration Failed', category='error')
  247. response = make_response(render_template("register.html", user = current_user, form = form))
  248. response.headers['Content-Security-Policy'] = "default-src 'self'"
  249. response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
  250. response.headers['X-Content-Type-Options'] = 'nosniff'
  251. response.headers['X-Frame-Options'] = 'SAMEORIGIN'
  252. return response