I'm building a webapp with React and Flask and I have an issue with POST request.
This is my app.py file:
import sys
import os
from flask import Flask, jsonify, request, render_template
from flask_sqlalchemy import SQLAlchemy
from flask_cors import CORS, cross_origin
from flask_mail import Mail, Message
from models import User, Project, Image, db
from api import blueprints
from tools import format_email
app = Flask(__name__,
static_folder='../build/static',
template_folder="../build"
)
app.config.from_object(os.environ['APP_SETTINGS'])
db.init_app(app)
cors = CORS(app)
mail = Mail(app)
# Register the blueprints
for b in blueprints:
app.register_blueprint(b)
#cross_origin
#app.route('/', defaults={'u_path': ''})
#app.route('/<path:u_path>')
def index(u_path=None):
return render_template("index.html")
#app.route('/api/process_email', methods=['POST'])
def process_email():
print('plop')
data = request.get_json()
formated_email = format_email(
data['firstname'],
data['lastname'],
data['email'],
data['message']
)
msg = Message(formated_email['title'], sender='plop#gmail.com', recipients=['plop#gmail.com'])
msg.body = formated_email['textbody']
msg.html = formated_email['htmlbody']
mail.send(msg)
return 'done'
if __name__ == "__main__":
app.run()
In the config.py I have this set CORS_HEADERS = 'Content-Type'
When I test this with Postman, my email is sent without any issue. But from the app, I get a 405 METHOD NOT ALLOWED response.
This is how I send the request:
axios.post(API_URL + 'process_email/', {
"firstname": values.firstname,
"lastname": values.lastname,
"email": values.email,
"message": values.message
}, {
headers: {
'Content-Type': 'text/plain;charset=utf-8',
},
withCredentials: 'same-origin'
})
.then(({ data }) => {
console.log(data)
}, (error) => {
toast.error("gneuuuu");
})
.catch(() => {toast.error("Oups! Something went wrong!")});
This is what I have:
Since I put a proxy in place, I don't see preflights request anymore.
I tried with a simple fetch or use superagent but issue is still here, and I clearly don't understand how CORS works. Any help would be appreciated. Thanks!
If anyone needs the answer: issue was in the way I was sending the request with axios. Instead of:
axios.post(API_URL + 'process_email/', {...
I have to remove the trailing / ! This works fine:
axios.post(API_URL + 'process_email', {...
Related
I've already created a working CRUD app with the backend done in Python-Flask and Python-PyMongo, but now I need to migrate the backend from Flask to Tornado. There isn't very much up to date documentation online for Tornado, and bear in mind also that I just started learning web dev two weeks ago. My Flask backend looks like this:
from flask import Flask, request, jsonify
from flask_pymongo import PyMongo, ObjectId
from flask_cors import CORS
app = Flask(__name__)
app.config["MONGO_URI"]="mongodb+srv://<user>:<pass>#cluster0.f0zvq.mongodb.net/myFirstDatabase?retryWrites=true&w=majority"
mongo = PyMongo(app)
CORS(app)
db = mongo.db.users
#GET and POST responses
#app.route('/users', methods=['GET', 'POST'])
def createUser():
if request.method == "GET":
users = []
for doc in db.find():
users.append({
'_id': str(ObjectId(doc['_id'])),
'username': doc['username'],
'firstName': doc['firstName'],
'lastName': doc['lastName'],
'dob': doc['dob']
})
return jsonify(users)
elif request.method == "POST":
id = db.insert_one({
'username': request.json['username'],
'firstName': request.json['firstName'],
'lastName': request.json['lastName'],
'dob': request.json['dob']
})
return jsonify({'id': str(id.inserted_id), 'msg': 'User Added Successfully!'})
{...}
if __name__ == "__main__":
app.run(debug=True)
So my attempt at migrating it to Tornado (based on what I could find online) is something like this:
import tornado.ioloop
import tornado.web
import urllib.parse
from bson.json_util import dumps
from bson import ObjectId
from pymongo import MongoClient
cluster = MongoClient("mongodb+srv://<user>:<pass>#cluster0.vsuex.mongodb.net/myFirstDatabase?retryWrites=true&w=majority")
db = cluster["test"]
collection = db["test"]
class UserHandler(tornado.web.RequestHandler):
#This one is working
def get(self):
users = []
for doc in collection.find():
users.append({
'_id': str(doc['_id']),
'username': doc['username'],
'firstName': doc['firstName'],
'lastName': doc['lastName'],
'dob': doc['dob']
})
self.write(json.dumps(users))
def post(self):
body = urllib.parse.urlparse(self.request.body)
for key in body:
#having trouble at this line
body[key] = body[key][0]
id = collection.insert_one({
"username": body["username"],
"firstName": body["firstName"],
"lastName": body["lastName"],
"dob": body["dob"]
})
self.write(dumps({'id': str(id.inserted_id), 'msg': 'User Added Successfully!'}))
{...}
def make_app():
return tornado.web.Application([
(r"/users", UserHandler)
],
debug = True,
autoreload = True)
if __name__ == "__main__":
app = make_app()
port = 8888
app.listen(port)
print(f"🌐 Server is listening on port {8888}")
#start server on current thread
tornado.ioloop.IOLoop.current().start()
The error I'm getting so far from Postman when I attempt to post some data is:
line 56, in post
body[key] = body[key][0]
TypeError: tuple indices must be integers or slices, not bytes
Any help is appreciated, thanks!
Solved the function for POST requests:
async def post(self):
user = tornado.escape.json_decode(self.request.body)
id = await collection.insert_one({
"username": user["username"],
"firstName": user["firstName"],
"lastName": user["lastName"],
"dob": user["dob"]
})
self.set_header('Content-Type', 'application/json')
return self.write({'id': str(id.inserted_id), 'msg': 'User Added
Successfully!'})
I am attempting to log a user in and retrieve an auth token for the user; however, I am getting a 401 UNAUTHORIZED. Researching this issue I keep coming across the "Authorization Header" is incorrect or invalid; however, I am logging the user in and I do not have a valid auth code yet. I cannot understand why CORS will not allow this request through:
Using AXIOS:
export const sendServer = (url, token, method, data, callback) => {
let newUrl = `http://localhost:5000/${url}`
axios({
method: method.toUpperCase(),
url: newUrl,
data: data,
headers: { 'Authorization': token }
})
.then(response => callback({ success: true, data: response.data }))
.catch(error => callback({ success: false, error: error.toJSON() }))
}
On The Flask side:
from flask import Flask
from flask_restful import Api
from flask_cors import CORS
from config import ProdConfig
from auth import Authenticate, check_token
app = Flask(__name__)
app.config.from_object(ProdConfig)
CORS(app)
api = Api(app)
api.add_resource(Authenticate, '/login', endpoint="login")
The '/login' endpoint does not require authentication.
class Authenticate(Resource):
def __init__(self):
self.pwd_context = CryptContext(schemes=["pbkdf2_sha256"],default="pbkdf2_sha256",pbkdf2_sha256__default_rounds=30000)
def post(self):
if request.endpoint == 'login':
.......
Any help would greatly be appreciated.
With flask_jwt_extended, whenever I'm trying to send a POST request with the following decorators:
#jwt_refresh_token_required
#jwt_required
I am having this 401 error:
{
"msg": "Missing CSRF token"
}
When I use a GET instead, it's working fine.
I have read the documentation that talk about double submit protection, but that does not solve my problem. Any ideas how I could fix my issue?
The code to reproduce the problem is below.
Below is the structure of my code:
- src/__init.py__ # where I put all configs
- src/auth.py # where are the endpoints
init.py
login_serializer = URLSafeTimedSerializer(SERIALIZER_SECRET_KEY)
jwt = JWTManager()
def create_app():
app = Flask(__name__)
app.config["SECRET_KEY"] = SERIALIZER_SECRET_KEY
app.config['JWT_SECRET_KEY'] = JWT_SECRET_KEY
app.config['JWT_TOKEN_LOCATION'] = ['cookies']
app.config['JWT_COOKIE_CSRF_PROTECT'] = True
db.init_app(app)
jwt.init_app(app)
# blueprint for auth routes in our app
from .auth import auth as auth_blueprint
app.register_blueprint(auth_blueprint)
# blueprint for non-auth parts of app
from .routes import main as main_blueprint
app.register_blueprint(main_blueprint)
return app
auth.py
import logging
from flask import Blueprint, request, current_app as app, jsonify
from werkzeug.security import generate_password_hash, check_password_hash
from . import login_serializer, jwt
from flask_jwt_extended import (jwt_required, jwt_refresh_token_required,
get_jwt_identity, get_raw_jwt, unset_jwt_cookies,
current_user, create_access_token, create_refresh_token, set_access_cookies, set_refresh_cookies)
auth = Blueprint('auth', __name__)
def set_response_cookies(token_identity, resp=None, token_types=["access", "refresh"]):
"""
Helper function to set cookies in response
"""
logging.warning("Setting cookies")
resp = jsonify(resp)
token_types.sort()
if token_types == ["access", "refresh"]:
access_token = create_access_token(identity = token_identity)
refresh_token = create_refresh_token(identity = token_identity)
if not resp:
resp = jsonify({"access_token": access_token, "refresh_token": refresh_token})
set_access_cookies(resp, access_token)
set_refresh_cookies(resp, refresh_token)
return resp
elif token_types == ["access"]:
access_token = create_access_token(identity = token_identity)
if not resp:
resp = jsonify({"access_token": access_token})
set_access_cookies(resp, access_token)
return resp
elif token_types == ["refresh"]:
refresh_token = create_refresh_token(identity = token_identity)
if not resp:
resp = jsonify({"refresh_token": refresh_token})
set_refresh_cookies(resp, refresh_token)
return resp
else:
raise ValueError("Wrong Call to this function")
#jwt.user_claims_loader
def add_claims_to_access_token(identity):
"""
"""
return {
'email': identity
}
#jwt.user_loader_callback_loader
def user_loader_callback(identity):
"""
Ignore Here, but I use it to get a User object (not mentionned here) from a Token.
"""
return User.objects(
email=identity,
).first()
#auth.route('/token', methods=['POST'])
def token_post():
""" obj =
{"email": "email", "password": "password"} => Tokens
"""
obj = request.get_json()
resp = set_response_cookies(obj["email"], {"token": True}, ["access", "refresh"])
return resp, 200
#auth.route('/token/access', methods=['POST'])
#jwt_refresh_token_required
def refresh_access_cookies():
if current_user:
resp = set_response_cookies(current_user.email, {"token_refreshed": True}, ["access"])
return resp, 200
So, here, all I have to do to reproduce the error is:
Make a POST request to /token => In postman, my response will get all cookies and headers.
Make a POST request to /token/access => Give the error mentioned above.
On your configuration, you enabled JWT_COOKIE_CSRF_PROTECT to true.
For devep purpose, the error will be gone if you can set it to False which may not safe.
On production, You need to pass csrf_token on your request header.
I think this links can help you.
https://flask-jwt-extended.readthedocs.io/en/stable/tokens_in_cookies/ (see the last section)
https://flask-wtf.readthedocs.io/en/stable/csrf.html
I have a flask application, where I want to add a recaptcha field. I use it to verify, that an email can be sent. here is my code so far:
from flask import render_template, request, flash, session, url_for, redirect
from flask import Flask
from flask_mail import Mail, Message
from flask_recaptcha import ReCaptcha
app.config.update({'RECAPTCHA_ENABLED': True,
'RECAPTCHA_SITE_KEY':
'6LdJ4GcUAAAAAN0hnsIFLyzzJ6MWaWb7WaEZ1wKi',
'RECAPTCHA_SECRET_KEY':
'secret-key'})
app=Flask(__name__)
recaptcha = ReCaptcha(app=app)
mail_settings = {
"MAIL_SERVER": 'smtp.gmail.com',
"MAIL_PORT": 465,
"MAIL_USE_SSL": True,
"MAIL_USERNAME": 'USERNAME',
"MAIL_PASSWORD": 'PASSWORD'
}
app.config.update(mail_settings)
mail = Mail(app)
#app.route('/')
def index():
return render_template('index.html')
#app.route('/mail', methods=['GET', 'POST'])
def send_mail():
r = requests.post('https://www.google.com/recaptcha/api/siteverify',
data = {'secret' :
'secret_key',
'response' :
request.form['g-recaptcha-response']})
google_response = json.loads(r.text)
print('JSON: ', google_response)
if google_response['success']:
msg = Message('Thank you for contacting me', sender='kristofferlocktolboll#gmail.com', recipients = [request.form['email']])
msg.body ='sut den'
mail.send(msg)
return render_template('index.html')
else:
return render_template('index.html')
app.run(debug=True)
The problem is that whenever I have the flask_recaptcha import ReCaptcha I get the following error:
it looks like the import statement is incorrect, but since I'm not using WTForms, I don't know what do else. Whenever I remove the import statement it gives a syntax error instead (which makes sense)
Usage: flask run [OPTIONS]
Error: The file/path provided (routes) does not appear to exist.
Please verify the path is correct. If app is not on PYTHONPATH,
ensure the extension is .py
I'm currently working on creating a Cookie from an endpoint. As my backend and frontend only interacts via RESTful endpoints, is there anyway I can create a cookie when the frontend calls my backend's endpoint?
flask.make_response.set_cookie() doesn't seem to work for me. Also, I can't use app.route('/') to set my cookie either.
You can do this with Set-Cookie header returning with a response.
from flask import Flask
from flask_restful import Resource, Api
app = Flask(__name__)
api = Api(app)
class HelloWorld(Resource):
def get(self):
return {'task': 'Hello world'}, 200, {'Set-Cookie': 'name=Nicholas'}
api.add_resource(HelloWorld, '/')
if __name__ == '__main__':
app.run(debug=True)
Setting the header in the response tuple is one of the standard approaches. However, keep in mind that the Set-Cookie header can be specified multiple times, which means that a python Dictionary won't be the most effective way to set the cookies in the response.
According to the flask docs the header object can also be initialized with a list of tuples, which might be more convenient in some cases.
Example:
from flask import Flask
from flask_restful import Api, Resource
app = Flask(__name__, static_url_path='')
api = Api(app)
class CookieHeaders(Resource):
def get(self):
# Will only set one cookie "age = 23"
return { 'message' : 'Made with dict'}, 200, { 'Set-Cookie':'name=john', 'Set-Cookie':'age=23' }
def post(self):
# Will set both cookies "name = john" and "age = 23"
headers = [ ('Set-Cookie', 'name=john'), ('Set-Cookie', 'age=23') ]
return { 'message' : ' Made with a list of tuples'}, 200, headers
api.add_resource(CookieHeaders, '/')
if __name__ == '__main__':
app.run(debug=True)
The GET call will only set 1 cookie (due to the lack of multi-key support in python dictionaries), but the POST call will set both.
Flask has a #after_this_request callback decorator. (see: http://flask.pocoo.org/docs/1.0/api/#flask.after_this_request)
so you can set your cookies in it
from flask import after_this_request
from flask_restful import Resource
class FooResource(Resource):
def get(self):
#after_this_request
def set_is_bar_cookie(response):
response.set_cookie('is_bar', 'no', max_age=64800, httponly=True)
return response
return {'data': 'foooo'}
or even
from flask import after_this_request, request
from flask_restful import Resource, abort
class FooResource(Resource):
def get(self):
self._check_is_bar()
return {'data': 'foooo'}
def _check_is_bar(self)
if request.cookies.get('is_bar') == 'yes':
abort(403)
#after_this_request
def set_is_bar_cookie(response):
response.set_cookie('is_bar', 'no', max_age=64800, httponly=True)
return response