A simple web application that allows invitation based registration to a matrix instance
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

256 lines
7.6 KiB

5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
  1. import logging
  2. import os
  3. import uuid
  4. from logging.config import dictConfig
  5. from urllib.parse import urlparse, urljoin
  6. import flask
  7. from flask import Flask, redirect, render_template, request, g, flash, url_for
  8. from flask_login import LoginManager, login_required, login_user, logout_user, \
  9. UserMixin
  10. from flask_wtf import CSRFProtect
  11. from db import get_db, get_registration_codes, add_registration_code, \
  12. expire_registration_code, delete_registration_code, get_registered_users, \
  13. add_registered_user
  14. from forms import RegistrationForm, LoginForm, RegistrationCodeForm, \
  15. ExpireRegistrationCodeForm
  16. from register_new_matrix_user import register_new_user
  17. csrf = CSRFProtect()
  18. login_manager = LoginManager()
  19. dictConfig({
  20. 'version': 1,
  21. 'disable_existing_loggers': False,
  22. 'formatters': {'default': {
  23. 'format': '[%(asctime)s] %(levelname)s in %(module)s: %(message)s',
  24. }},
  25. 'handlers': {
  26. 'wsgi': {
  27. 'class': 'logging.StreamHandler',
  28. 'stream': 'ext://flask.logging.wsgi_errors_stream',
  29. 'formatter': 'default'
  30. }
  31. },
  32. 'root': {
  33. 'level': 'INFO',
  34. 'handlers': ['wsgi']
  35. }
  36. })
  37. log = logging.getLogger(__name__)
  38. def init_db(flask_app):
  39. with flask_app.app_context():
  40. log.info("Initializing DB")
  41. db = get_db()
  42. with flask_app.open_resource('schema.sql', mode='r') as f:
  43. db.cursor().executescript(f.read())
  44. log.info("Initialized DB")
  45. db.commit()
  46. def create_app():
  47. flask_app = Flask(__name__)
  48. flask_app.config.update(dict(
  49. APPLICATION_ROOT=os.getenv("APPLICATION_ROOT", "/"),
  50. ADMIN_TOKEN=os.getenv("ADMIN_TOKEN", uuid.uuid4().__str__()),
  51. SECRET_KEY=os.getenv("SECRET_KEY", "changeme"),
  52. WTF_CSRF_SECRET_KEY=os.getenv("CSRF_SECRET_KEY", "csrf_changeme"),
  53. MATRIX_HOMESERVER=os.getenv("MATRIX_HOMESERVER"),
  54. MATRIX_SHARED_SECRET=os.getenv("MATRIX_SHARED_SECRET"),
  55. REGISTRATION_SUCCESS_REDIRECT=os.getenv("REGISTRATION_SUCCESS_REDIRECT")
  56. ))
  57. log.info("Admin Token: %s" % flask_app.config.get("ADMIN_TOKEN"))
  58. csrf.init_app(flask_app)
  59. login_manager.init_app(flask_app)
  60. login_manager.login_view = "admin_login"
  61. init_db(flask_app)
  62. log.info("Application ready")
  63. return flask_app
  64. app = create_app()
  65. log.info("Bound reverse proxy wsgi app")
  66. def flash_form_errors(form):
  67. if hasattr(form, 'errors') and len(form.errors) > 0:
  68. for error in form.errors.items():
  69. flash("%s: %s" % (form[error[0]].label.text, error[1]), 'error')
  70. class User(UserMixin):
  71. username: str
  72. token: str
  73. authenticated: bool = False
  74. def __init__(self, username: str, token: str):
  75. self.username = username
  76. self.token = token
  77. def is_authenticated(self):
  78. return self.authenticated
  79. def get_id(self):
  80. return self.username
  81. def is_safe_url(target):
  82. ref_url = urlparse(request.host_url)
  83. test_url = urlparse(urljoin(request.host_url, target))
  84. return test_url.scheme in ('http', 'https') and ref_url.netloc == test_url.netloc
  85. def get_successful_registration_redirect():
  86. target = app.config.get('REGISTRATION_SUCCESS_REDIRECT')
  87. if target is None or not target.startswith('http'):
  88. return url_for('index', _external=True)
  89. return target
  90. @login_manager.user_loader
  91. def load_user(user_id):
  92. if user_id == "admin":
  93. user = User("admin", app.config.get("ADMIN_TOKEN"))
  94. g.user = user
  95. return user
  96. else:
  97. return None
  98. @app.route('/')
  99. def index():
  100. return redirect(url_for('registration'))
  101. @app.route('/register', methods=('GET', 'POST'))
  102. def registration():
  103. form = RegistrationForm()
  104. if form.validate_on_submit():
  105. if app.config.get("MATRIX_HOMESERVER") is None:
  106. flash("Matrix Homeserver Currently Unavailable. Please Try Again Later!")
  107. return render_template('register.html', form=form)
  108. else:
  109. if app.config.get("MATRIX_SHARED_SECRET") is None:
  110. flash("Registration Configuration Is Invalid. Contact Administrator!")
  111. return render_template('register.html', form=form)
  112. else:
  113. response = register_new_user(
  114. form.username.data,
  115. form.password.data,
  116. app.config.get("MATRIX_HOMESERVER"),
  117. app.config.get("MATRIX_SHARED_SECRET")
  118. )
  119. if response is not None:
  120. registered_user_id = response['user_id']
  121. add_registered_user(
  122. form.registration_code.data,
  123. form.username.data,
  124. registered_user_id)
  125. return redirect(get_successful_registration_redirect())
  126. else:
  127. flash("Registration Failure. Contact Administrator!")
  128. return render_template('register.html', form=form)
  129. flash_form_errors(form)
  130. if 'registrationCode' in request.values:
  131. form.registration_code.data = request.values['registrationCode']
  132. return render_template('register.html', form=form)
  133. @app.route('/admin')
  134. @login_required
  135. def admin_index():
  136. context = {
  137. 'add_registration_code_form': RegistrationCodeForm(),
  138. 'registration_codes': get_registration_codes(),
  139. 'registered_users': get_registered_users()
  140. }
  141. return render_template('admin.html', **context)
  142. @app.route('/admin/add_registration_code', methods=['POST'])
  143. @login_required
  144. def admin_add_registration_code():
  145. form = RegistrationCodeForm()
  146. if form.validate_on_submit():
  147. expiration_time = form.expiration_time.data
  148. max_usages = form.max_usages.data
  149. add_registration_code(expiration_time, max_usages)
  150. redirect(url_for('admin_index', _external=True))
  151. flash_form_errors(form)
  152. return redirect(url_for('admin_index', _external=True))
  153. @app.route('/admin/expire_registration_code', methods=['POST'])
  154. @login_required
  155. def admin_expire_registration_code():
  156. form = ExpireRegistrationCodeForm()
  157. if form.validate_on_submit():
  158. if form.expire.data:
  159. expire_registration_code(form.registration_code.data)
  160. elif form.delete.data:
  161. delete_registration_code(form.registration_code.data)
  162. redirect(url_for('admin_index', _external=True))
  163. flash_form_errors(form)
  164. return redirect(url_for('admin_index', _external=True))
  165. @app.route('/admin/login', methods=('GET', 'POST'))
  166. def admin_login():
  167. form = LoginForm()
  168. if form.validate_on_submit():
  169. user = load_user(form.username.data)
  170. if user is not None:
  171. if form.token.data == user.token:
  172. user.authenticated = True
  173. login_user(user)
  174. flask.flash('Logged in successfully.')
  175. next_loc = flask.request.args.get('next')
  176. if not is_safe_url(next_loc):
  177. return flask.abort(400)
  178. else:
  179. if next_loc is not None:
  180. return redirect(next_loc)
  181. else:
  182. return redirect(url_for('admin_index', _external=True))
  183. flash_form_errors(form)
  184. return render_template('login.html', form=form)
  185. @app.route("/admin/logout")
  186. @login_required
  187. def admin_logout():
  188. logout_user()
  189. flask.flash('Logged out successfully.')
  190. return redirect(url_for('index', _external=True))
  191. @app.teardown_appcontext
  192. def close_connection(exception):
  193. db = getattr(g, '_database', None)
  194. if db is not None:
  195. db.close()
  196. if __name__ == '__main__':
  197. app.run()