# coding: utf-8 # pylint: disable=too-few-public-methods, no-self-use, missing-docstring, unused-argument """ Provides API logic relevant to user authentication """ from flask_restful import reqparse, inputs, Resource from flask import g import util import auth import config from model import User, UserValidator, Config import task from main import API from api.helpers import make_bad_request_exception, make_empty_ok_response from api.decorators import verify_captcha, parse_signin from google.appengine.ext import ndb # pylint: disable=import-error @API.resource('/api/v1/auth/signup') class SignupAPI(Resource): @verify_captcha('signupForm') def post(self): """Creates new user account if provided valid arguments""" parser = reqparse.RequestParser() parser.add_argument('email', type=UserValidator.create('unique_email'), required=True) parser.add_argument('username', type=UserValidator.create('unique_username')) parser.add_argument('password', type=UserValidator.create('password')) parser.add_argument('remember', type=inputs.boolean, default=False) args = parser.parse_args() user_db = auth.create_user_db( auth_id=None, name='', username=args.username, email=args.email, verified=True if not config.CONFIG_DB.verify_email else False, password=args.password ) user_db.put() if config.CONFIG_DB.verify_email: task.verify_user_email_notification(user_db) return make_empty_ok_response() # if users don't need to verify email, we automaticaly signin newly registered user auth.signin_user_db(user_db, remember=args.remember) return user_db.to_dict(include=User.get_private_properties()) @API.resource('/api/v1/auth/signin') class SigninAPI(Resource): @verify_captcha('signinForm') @parse_signin def post(self): """Signs in existing user. Note, g.user_db is set inside parse_signin decorator""" if g.user_db and g.user_db.verified and g.user_db.active: auth.signin_user_db(g.user_db, remember=g.args.remember) if g.user_db is None: make_bad_request_exception('Seems like these credentials are invalid') return g.user_db.to_dict(include=User.get_private_properties()) @API.resource('/api/v1/auth/signout') class SignoutAPI(Resource): def post(self): """Signs out user. Also it sends back config object with public properties, in case signed out user was admin, we want override his config object with new one, since admin config contains even private properties""" auth.signout_user() app_config = config.CONFIG_DB.to_dict(include=Config.get_public_properties()) app_config['development'] = config.DEVELOPMENT return app_config @API.resource('/api/v1/auth/resend-verification') class ResendActivationAPI(Resource): @parse_signin def post(self): """Resends email verification to user""" if g.user_db and not g.user_db.verified and g.user_db.active: task.verify_user_email_notification(g.user_db) return make_empty_ok_response() @API.resource('/api/v1/auth/forgot') class ForgotPasswordAPI(Resource): def post(self): """Sends email with token for resetting password to an user""" parser = reqparse.RequestParser() parser.add_argument('email', type=UserValidator.create('existing_email')) args = parser.parse_args() user_db = User.get_by('email', args.email) task.reset_password_notification(user_db) return make_empty_ok_response() @API.resource('/api/v1/auth/reset') class ResetPasswordAPI(Resource): @ndb.toplevel def post(self): """Sets new password given by user if he provided valid token Notice ndb.toplevel decorator here, so we can perform asynchronous put and signing in in parallel """ parser = reqparse.RequestParser() parser.add_argument('token', type=UserValidator.create('token')) parser.add_argument('newPassword', type=UserValidator.create('password'), dest='new_password') args = parser.parse_args() user_db = User.get_by('token', args.token) user_db.password_hash = util.password_hash(args.new_password) user_db.token = util.uuid() user_db.verified = True user_db.put_async() auth.signin_user_db(user_db) return user_db.to_dict(include=User.get_private_properties())