Source code for backend.auth

"""
auth.py
============
Authentication API endpoints. All routes start with /api/auth
All incoming request parameters are wrapped in a JSON body.
All outgoing response returns are wrapped in a JSON entry with key 'payload',
like this:

.. code-block::

    {
      "error": "false",
      "error-msg": None,
        "payload": {
        "return-1": "true"
      }
    }


Note that method documentation assumes you are using jsonResponse/errorResponse
to generate the response, and only shows the actual returns within payload.
Ditto for request parameters.
"""

import functools
import flask
from flask import (
    Blueprint, flash, g, redirect, render_template, request, session, url_for, current_app
)
from werkzeug.security import check_password_hash, generate_password_hash
from form_response import jsonResponse, errorResponse

bp = Blueprint('auth', __name__, url_prefix='/api/auth')

[docs]@bp.route('/register', methods=['POST']) def register(): """ POST Register a new user. Parameters ------ firstName: str lastName: str email: str password: str isTutor: bool Returns ------ uid: int unique ID that idenfies this user. Raises ------ BadRequest Some part of the required parameters is missing. UsersExists User with the same email has already registered. """ redis_client = current_app.config['RDSCXN'] if request.method == 'POST': data = request.get_json(force=True) fname = data['firstName'] lname = data['lastName'] email = data['email'] password = data['password'] isTutor = int(data['isTutor']) error = None if not fname: error = 'First Name is required.' elif not lname: error = 'Last Name is required.' elif not email: error = 'Email is required.' elif not password: error = 'Password is required.' else: for uid in redis_client.keys("user*"): if email == redis_client.hget(uid, 'email'): error = 'Email {} is already registered.'.format(email) if error is None: next_uid = redis_client.get('next_uid') redis_client.incr('next_uid') redis_client.hmset("user{}".format(next_uid), {'fname': fname, 'lname': lname, 'email': email, 'password': generate_password_hash(password), 'isTutor': isTutor, 'uid': next_uid}) redis_client.bgsave() return jsonResponse({'uid': next_uid}) return errorResponse(error) return errorResponse('POST to this endpoint')
[docs]@bp.route('/login', methods=['POST']) def login(): """ POST login as a user. Parameters ------ email: str password: str Returns ------ uid: int unique ID that idenfies this user. Raises ------ InvalidEmail Email does not exist in system. InvalidPassword Password does not exist in system. Notes ----- This function also sets a encrypted session cookie which can only be decrypted server-side. See Also -------- backend.auth.getuid : getuid from session cookie. """ redis_client = current_app.config['RDSCXN'] if request.method == 'POST': data = request.get_json(force=True) email = data['email'] password = data['password'] error = None user = None for uid in redis_client.keys("user*"): u = redis_client.hgetall(uid) if u is not None and 'email' in u.keys() and u['email'] == email: user = u break if user is None or 'uid' not in user.keys(): error = 'Invalid email.' elif not check_password_hash(user['password'], password): error = 'Incorrect password.' if error is None: session.clear() session['user_id'] = user['uid'] return jsonResponse({'uid': user['uid']}) return errorResponse(error) return errorResponse('POST to this endpoint')
[docs]@bp.route('/getuid', methods=['GET']) def getuid(): """ GET get currently logged in user from session cookie Notes ----- Implicitly sends session cookie with request Returns ------- uid: int Unique identification for user. """ return jsonResponse({'uid': session.get('user_id')})
@bp.before_app_request def _load_logged_in_user(): redis_client = current_app.config['RDSCXN'] user_id = session.get('user_id') if user_id is None: g.user = None else: g.user = redis_client.hgetall(user_id)
[docs]@bp.route('/logout', methods=['GET']) def logout(): """ POST clear the current session cookie for a logged in user Notes ----- Returns an empty session cookie, in addition to an empty payload. """ session.clear() return jsonResponse()
def _login_required(view): @functools.wraps(view) def wrapped_view(**kwargs): if g.user is None: return redirect(url_for('auth.login')) return view(**kwargs) return wrapped_view