"""
This example demonstrates usage of JWT token authentication with
Flasgger-enabled application.

Swagger version used is 3, although 2 will also run just fine. For more details
on how to use Flask-JWT in general, take a look at the:
https://pythonhosted.org/Flask-JWT.

In this example the login credentials to call the protected API are as follows:

    Username: guest
    Password: secret

In order to access protected API methods, user must first login with valid
credentials. Successful login appends JWT token in login response header
(name of the field is: jwt-token) - this is the field that Swagger-UI will
search for when gets the response and if found - it will be automatically use
its value when issuing next requests. These requests have Authorization HTTP
header field with content set to: JWT token_value_goes_here (note the space
between JWT and token value).

Benefit of this solution is that once token is obtained, there is no need to
enter it manually while working. Consider a good practice to implement method
to refresh token, in order to prevent token expiration.

Note however that if you want to use JWT_AUTH_HEADER_NAME - you must provide
your own JWT request handler. This is because flask-jwt is not actively
maintained anymore, making it impossible to add JWT_AUTH_HEADER_NAME to it. In
normal circumstances, you will not need to use this field, but if you want to
protect your Flasgger page in order to prevent unauthorized access to it by
using basic HTTP auth on some web-server you will have to.
"""


from flask import Flask, jsonify, request
from flask_jwt import JWT, jwt_required, current_identity, JWTError
from werkzeug.security import safe_str_cmp
from flasgger import Swagger


class User(object):
    def __init__(self, user_id, username, password):
        self.id = user_id
        self.username = username
        self.password = password

    def __str__(self):
        return "User(id='%s')" % self.id


users = [
    User(1, 'guest', 'secret'),
]

username_table = {u.username: u for u in users}
userid_table = {u.id: u for u in users}


def authenticate(username, password):
    user = username_table.get(username, None)
    if user and safe_str_cmp(user.password.encode('utf-8'), password.encode('utf-8')):
        return user


def identity(payload):
    user_id = payload['identity']
    return userid_table.get(user_id, None)


app = Flask(__name__)
app.debug = True
app.config["SECRET_KEY"] = "super-secret"
app.config["SWAGGER"] = {
    "title": "Swagger JWT Authentiation App",
    "uiversion": 3,
}
app.config['JWT_AUTH_URL_RULE'] = '/api/auth'
app.config['JWT_AUTH_HEADER_NAME'] = 'JWTAuthorization'

swag = Swagger(app,
    template={
        "openapi": "3.0.0",
        "info": {
            "title": "Swagger Basic Auth App",
            "version": "1.0",
        },
        "consumes": [
            "application/x-www-form-urlencoded",
        ],
        "produces": [
            "application/json",
        ],
    },
)


def jwt_request_handler():
    auth_header_name = app.config['JWT_AUTH_HEADER_NAME']
    auth_header_value = request.headers.get(auth_header_name, None)
    auth_header_prefix = app.config['JWT_AUTH_HEADER_PREFIX']

    if not auth_header_value:
        return

    parts = auth_header_value.split()

    if parts[0].lower() != auth_header_prefix.lower():
        raise JWTError('Invalid JWT header', 'Unsupported authorization type')
    elif len(parts) == 1:
        raise JWTError('Invalid JWT header', 'Token missing')
    elif len(parts) > 2:
        raise JWTError('Invalid JWT header', 'Token contains spaces')

    return parts[1]


jwt = JWT(app, authenticate, identity)
jwt.request_handler(jwt_request_handler)


@app.route("/login", methods=["POST"])
def login():
    """
    User authenticate method.
    ---
    description: Authenticate user with supplied credentials.
    parameters:
      - name: username
        in: formData
        type: string
        required: true
      - name: password
        in: formData
        type: string
        required: true
    responses:
      200:
        description: User successfully logged in.
      400:
        description: User login failed.
    """
    try:
        username = request.form.get("username")
        password = request.form.get("password")

        user = authenticate(username, password)
        if not user:
            raise Exception("User not found!")

        resp = jsonify({"message": "User authenticated"})
        resp.status_code = 200

        access_token = jwt.jwt_encode_callback(user)

        # add token to response headers - so SwaggerUI can use it
        resp.headers.extend({'jwt-token': access_token})

    except Exception as e:
        resp = jsonify({"message": "Bad username and/or password"})
        resp.status_code = 401

    return resp


@app.route("/protected", methods=["GET"])
@jwt_required()
def protected():
    """
    Protected content method.
    ---
    description: Protected content method. Can not be seen without valid token.
    responses:
      200:
        description: User successfully accessed the content.
    """
    resp = jsonify({"protected": "{} - you saw me!".format(current_identity)})
    resp.status_code = 200

    return resp


if __name__ == '__main__':
    app.run()