<commit_before>from flask import Blueprint, request, jsonify, render_template, current_app
from flask_mail import Message
from flask_jwt import jwt_required, verify_jwt
from functools import wraps

from gfsad import limiter
from gfsad.tasks import send_email
from gfsad.models import User
from gfsad.exceptions import FieldError
from gfsad.views.json_response import JSONResponse
from gfsad.auth import decode_token, generate_token, make_jwt


# create blueprint to separate scope
auth = Blueprint('auth', __name__, url_prefix='/auth')


def required_fields(*fields):
    """Decorator that raises FieldError if not all required fields are included in the json.
    """

    def wrapper(fn):
        @wraps(fn)
        def decorated_view(*args, **kwargs):
            errors = {}
            for field in fields:
                if field not in request.json:
                    if field not in errors:
                        errors[field] = []
                    errors[field].append(field + ' is required')

            if len(errors) > 0:
                raise FieldError(error=errors, description="Missing or invalid information")

            return fn(*args, **kwargs)
        return decorated_view
    return wrapper


@auth.route('/protected')
@jwt_required()
def protected():
    return JSONResponse(status_code=200, description='Success')


@auth.route('/register', methods=['POST'])
@required_fields('email', 'first', 'last', 'password')
@limiter.limit("5 per minute")
def register():
    data = request.json
    # create user with the data, 
    # all stormpath exceptions will be caught and passed on in standardized format
    user = User.create(**data)

    # if requires confirmation
    if current_app.config['AUTH_REQUIRE_CONFIRMATION']:
        token = generate_token((user.email, user.custom_data['email_verification_token']),
                               current_app.config['SECRET_KEY'])
        # Send Email #
        link = 'http://www.croplands.org/account/confirm?t=' + token
        send_confirmation_email(link, user.email)
        return JSONResponse(status_code=201, description='User created')

    # else just return token
    response_data = {
        'token': make_jwt(user)
    }
    return JSONResponse(status_code=201, description='User created',
                        data=response_data)


@auth.route('/login', methods=['POST'])
@required_fields('email', 'password')
@limiter.limit("20 per hour", key_func=lambda: request.json['email'] if (
        hasattr(request, 'json') and hasattr(request.json, 'email')) else request.remote_addr)
def login():
    user = User.from_login(request.json['email'], request.json['password'])
    return JSONResponse(status_code=200, description='User logged in',
                        data={
                            'token': make_jwt(user)
                        })


@auth.route('/forgot', methods=['POST'])
@required_fields('email')
@limiter.limit("10 per hour")
def forgot_password():
    user = User.from_email(request.json['email'])
    if user is not None:
        # send email
        token = generate_token(user.email, current_app.config['SECRET_KEY'])
        # Send Email #
        link = 'http://www.croplands.org/account/reset?token=' + token
        send_reset_email(link, user.email)

    return jsonify({'status_code': 200, 'description': 'Email sent'}), 200


@auth.route('/reset', methods=['POST'])
@required_fields('password', 'token')
@limiter.limit("5 per day")
def reset_password():
    token = request.json['token']
    email = decode_token(token, current_app.config['SECRET_KEY'],
                         current_app.config['AUTH_RESET_TOKEN_EXPIRATION'])
    user = User.from_email(email)
    user.change_password(request.json['password'])
    return JSONResponse(status_code=200, description='Password was changed',
                        data={'token': make_jwt(user)})


@auth.route('/send_confirm', methods=['POST'])
@required_fields('email')
@limiter.limit("5 per day")
def send_confirm():
    user = User.from_email(request.json['email'])
    if user is not None:
        # send email
        token = generate_token((user.email, user.custom_data['email_verification_token']),
                               current_app.config['SECRET_KEY'])
        # Send Email #
        link = 'http://www.croplands.org/account/confirm?t=' + token
        send_confirmation_email(link, user.email)
    return JSONResponse(status_code=200, description='Confirmation email sent')


@auth.route('/confirm', methods=['POST'])
@required_fields('token')
def confirm():
    token = request.json['token']
    email, token = decode_token(token, current_app.config['SECRET_KEY'])
    user = User.from_email(email)
    user.verify(token)
    return JSONResponse(status_code=200, description='Email confirmed')


def send_reset_email(link, email):
    html = render_template('email/reset_instructions.html', reset_link=link)
    body = render_template('email/reset_instructions.txt', reset_link=link)

    msg = Message(recipients=email, body=body, html=html,
                  subject='Global Croplands - Password Reset')
    return send_email.delay(msg)

<commit_msg>Rate limit only on post for auth endpoints<commit_after>from flask import Blueprint, request, jsonify, render_template, current_app
from flask_mail import Message
from flask_jwt import jwt_required, verify_jwt
from functools import wraps

from gfsad import limiter
from gfsad.tasks import send_email
from gfsad.models import User
from gfsad.exceptions import FieldError
from gfsad.views.json_response import JSONResponse
from gfsad.auth import decode_token, generate_token, make_jwt


# create blueprint to separate scope
auth = Blueprint('auth', __name__, url_prefix='/auth')


def required_fields(*fields):
    """Decorator that raises FieldError if not all required fields are included in the json.
    """

    def wrapper(fn):
        @wraps(fn)
        def decorated_view(*args, **kwargs):
            errors = {}
            for field in fields:
                if field not in request.json:
                    if field not in errors:
                        errors[field] = []
                    errors[field].append(field + ' is required')

            if len(errors) > 0:
                raise FieldError(error=errors, description="Missing or invalid information")

            return fn(*args, **kwargs)
        return decorated_view
    return wrapper


@auth.route('/protected')
@jwt_required()
def protected():
    return JSONResponse(status_code=200, description='Success')


@auth.route('/register', methods=['POST'])
@required_fields('email', 'first', 'last', 'password')
@limiter.limit("5 per minute", methods=['POST'], error_message='Try again later.')
def register():
    data = request.json
    # create user with the data, 
    # all stormpath exceptions will be caught and passed on in standardized format
    user = User.create(**data)

    # if requires confirmation
    if current_app.config['AUTH_REQUIRE_CONFIRMATION']:
        token = generate_token((user.email, user.custom_data['email_verification_token']),
                               current_app.config['SECRET_KEY'])
        # Send Email #
        link = 'http://www.croplands.org/account/confirm?t=' + token
        send_confirmation_email(link, user.email)
        return JSONResponse(status_code=201, description='User created')

    # else just return token
    response_data = {
        'token': make_jwt(user)
    }
    return JSONResponse(status_code=201, description='User created',
                        data=response_data)


@auth.route('/login', methods=['POST'])
@required_fields('email', 'password')
@limiter.limit("20 per hour", methods=['POST'], key_func=lambda: request.json['email'] if (
        hasattr(request, 'json') and hasattr(request.json, 'email')) else request.remote_addr)
def login():
    user = User.from_login(request.json['email'], request.json['password'])
    return JSONResponse(status_code=200, description='User logged in',
                        data={
                            'token': make_jwt(user)
                        })


@auth.route('/forgot', methods=['POST'])
@required_fields('email')
@limiter.limit("10 per hour", methods=['POST'])
def forgot_password():
    user = User.from_email(request.json['email'])
    if user is not None:
        # send email
        token = generate_token(user.email, current_app.config['SECRET_KEY'])
        # Send Email #
        link = 'http://www.croplands.org/account/reset?token=' + token
        send_reset_email(link, user.email)

    return jsonify({'status_code': 200, 'description': 'Email sent'}), 200


@auth.route('/reset', methods=['POST'])
@required_fields('password', 'token')
@limiter.limit("5 per day", methods=['POST'])
def reset_password():
    token = request.json['token']
    email = decode_token(token, current_app.config['SECRET_KEY'],
                         current_app.config['AUTH_RESET_TOKEN_EXPIRATION'])
    user = User.from_email(email)
    user.change_password(request.json['password'])
    return JSONResponse(status_code=200, description='Password was changed',
                        data={'token': make_jwt(user)})


@auth.route('/send_confirm', methods=['POST'])
@required_fields('email')
@limiter.limit("5 per day", methods=['POST'])
def send_confirm():
    user = User.from_email(request.json['email'])
    if user is not None:
        # send email
        token = generate_token((user.email, user.custom_data['email_verification_token']),
                               current_app.config['SECRET_KEY'])
        # Send Email #
        link = 'http://www.croplands.org/account/confirm?t=' + token
        send_confirmation_email(link, user.email)
    return JSONResponse(status_code=200, description='Confirmation email sent')


@auth.route('/confirm', methods=['POST'])
@required_fields('token')
def confirm():
    token = request.json['token']
    email, token = decode_token(token, current_app.config['SECRET_KEY'])
    user = User.from_email(email)
    user.verify(token)
    return JSONResponse(status_code=200, description='Email confirmed')


def send_reset_email(link, email):
    html = render_template('email/reset_instructions.html', reset_link=link)
    body = render_template('email/reset_instructions.txt', reset_link=link)

    msg = Message(recipients=email, body=body, html=html,
                  subject='Global Croplands - Password Reset')
    return send_email.delay(msg)


{'fraction_non_alphanumeric': 0.12541790046804852, 'fraction_numerical': 0.0066864074887763875, 'mean_word_length': 3.360683048729696, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 6, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1026669', 'n_tokens_mistral': 3057, 'n_tokens_neox': 2938, 'n_words': 701}