Problems with find_one_and_update using Pymongo/Flask with React application - reactjs

I am working on the administrator part of a beginner's project I'm working on. I'm building in React.js with Pymongo/Flask connected to MongoDB Atlas for database storage. The page I'm working on allows the administrator to query the database to return all the users for a particular course they are taking or role they have (instructor or administrator). The returned data is mapped over to child components in React with a series of input fields using the defaultValue being populated by the props for the children (i.e. first name, last name, email, etc.). I'm saving new values to the child components' states and using JSON.stringify to make an axios.patch request. I'd like to be able to alter any user's information and submit it to the Mongo DB Atlas server, but am having some issues.
Here is what I think would be the necessary code from the front end:
saveChanges(id, data) {
var token = window.sessionStorage.getItem("token")
const updata = JSON.stringify(data)
axios.patch(`http://127.0.0.1:5000/update-instructor/${id}`, JSON.stringify({updata}), { headers: {"Authorization" : `Bearer ${token}`}})
.catch(error => {
console.log("There was an error with the patch request to instructor", error)
})
}
On the backend, this is the route that axios is calling:
#app.route('/update-instructor/<id>', methods=['GET', 'PATCH'])
def update_one_instructor(id):
id = ObjectId(id)
id_call = {"_id" : id}
updateObject = request.get_json(force=True)
instructors.find_one_and_update(id_call,
{ "$set" : { updateObject } },
return_document = ReturnDocument.AFTER)
The imports and setup of my flask/Pymongo:
import datetime
from distutils.log import error
import json
import pymongo
from bson.objectid import ObjectId
from bson import json_util
from flask_jwt_extended import create_access_token
from flask_jwt_extended import decode_token
from flask_jwt_extended import JWTManager
from flask_jwt_extended import jwt_required
from flask import Flask, jsonify, make_response, Response, request
from flask_cors import CORS, cross_origin
from pymongo import ReturnDocument
from werkzeug.security import generate_password_hash, check_password_hash
CONNECTION_URL = *connection url*
app = Flask(__name__)
app.config['CORS_HEADERS'] = 'Content-Type'
cors = CORS(app)
app.config['JWT_SECRET_KEY'] = *secret key*
jwt = JWTManager(app)
try:
client = pymongo.MongoClient(CONNECTION_URL, serverSelectionTimeoutMS = 10000)
except:
print("Error - cannot connect to database")
Database = client.get_database(*database name*)
instructors = Database.instructors
I'm getting several issues. On the front end in Chrome, I am getting:
Access to XMLHttpRequest at 'http://127.0.0.1:5000/update-instructor/*string of ObjectID*' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.
as well as:
PATCH http://127.0.0.1:5000/update-instructor/*string of ObjectID* net::ERR_FAILED
On the backend I'm getting a 400 error:
127.0.0.1 - - [14/Mar/2022 17:17:43] "OPTIONS /update-instructor/*string of ObjectID* HTTP/1.1" 400 -
Might be unecessary information here; but I'm not sure what is relevant. Any ideas on how I can get this patch request to go through and update MongoDB Atlas and, subsequently, the state in my parent component?

I found the solution. It seems it was an error in my Pymongo/Flask setup.
#app.route('/update-user/<id>', methods=['GET', 'PATCH'])
def update_one_user(id):
id = ObjectId(id)
updateObject = request.get_json()
jsonify(updateObject)
result = users.find_one_and_update({"_id" : id},
{ "$set" : updateObject },
return_document = ReturnDocument.AFTER)
return "User Updated"
I also did some refactoring, so the route is slightly changed. Basically it seems that using fewer variables as well as well as removing the {} from updateObject did the trick. But I also refactored my front end code to
saveChanges(id, data) {
let config = {
headers: {
"Content-Type": "application/json",
'Access-Control-Allow-Origin': '*'
}
}
axios.patch(`http://127.0.0.1:5000/update-user/${id}`, JSON.stringify(data), config)
.catch(error => {
console.log("There was an error with the patch request to instructor", error)
})
}
It now includes some extra headers for CORS, but was was pointed out to me, it was the http 400 that was causing the CORS issue.

Related

How to fix python flask cors issue using flask-cors library

I could really use some help. I can't figure out what I'm doing wrong. I keep getting
Edit : Frontend React application runs on localhost:3000, backend is running on localhost:5000
Access to XMLHttpRequest at 'http://localhost:5000/api/auth/login' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
def create_app(test_config=None):
logger = logging.getLogger(__name__)
logger.info("Flask App Starting")
# create and configure the app
app = Flask(__name__, instance_relative_config=True)
CORS(app)
cors = CORS(app, resources={r"/api/*": {"origins": "*"}})
logging.getLogger('flask_cors').level = logging.DEBUG
app.config.from_mapping(
SECRET_KEY="dev",
JWT_SECRET_KEY="super secret key",
JWT_ACCESS_TOKEN_EXPIRES=timedelta(hours=2),
)
if test_config is None:
# load the instance config, if it exists, when not testing
app.config.from_pyfile("config.py", silent=True)
else:
# load the test config if passed in
app.config.from_mapping(test_config)
jwt = JWTManager(app)
"""
Adding blueprints
"""
from app.routes import tester
from app.routes import auth
from app.routes import api_routes
from app.routes import similar_routes
app.register_blueprint(tester.bp)
app.register_blueprint(auth.bp)
app.register_blueprint(api_routes.bp)
app.register_blueprint(similar_routes.bp)
#app.before_request
def check_login():
"""Before each request check if token exist."""
pass
logger.info("Checking if token is required")
if (not getattr(app.view_functions[flask.request.endpoint], "is_public", False)):
logger.info("Token required")
try:
result = verify_jwt_in_request(locations="headers")
logger.debug(f"Identity sent in is {get_jwt_identity()}")
except Exception as e:
logger.error("Error occured during checking token")
logger.error(e)
return jsonify(msg="Token Expired"),401
#app.errorhandler(Exception)
def all_exception_handler(error):
logger.error("Error caught" + str(error) )
return jsonify(msg="Oh no! A Server error occured. :,( "), 500
return app
if __name__ == "__main__":
loggingSetup()
app = create_app()
logger.info("App Created")
app.run(debug=True)
logger.info("App Running")
I'm making API calls from my react frontend, using axios
axios.defaults.baseURL = "http://localhost:5000/api"
function getHeaders(token){
return {
'Accept': 'application/json',
'Content-Type': 'application/json;charset=UTF-8',
"Authorization": "Bearer " + token,
'Access-Control-Allow-Origin': '*'
}
}
async function createCustomObject(token) {
let url = "/ontology/create-object";
let options = {
method: "POST",
url: url,
headers: getHeaders(token),
};
let response = await axios(options).then((response) => {
let data = response.data
}).catch((error) => {
handleError(error.response)
})
return response;
What am I missing?
You would set your origin to http://localhost:3000:
cors = CORS(app, resources={r"/api": {"origins": "http://localhost:3000"}})
'Access-Control-Allow-Origin': 'http://localhost:3000'
I resolved my issue using proxy after trying a couple of failed attempts using CORS solution.
I simply put "proxy": "http://127.0.0.1:5000" in my package.json and therefore, I can then use
fetch(`/test`)
.then((res) => res.json())
.then((data) => {
//do something
});
easily in my app without actually providing the full url to the backend (http://127.0.0.1:5000).

What is the right way to specify headers using axios?

I'm wrestling with cross origin headers while testing my app:
react side:
const url = "http://localhost:5000/blog/posts";
const headers = { headers: "Access-Control-Allow-Origin" };
axios.post(url, data, headers).then( ...
Flask backend __init__.py :
...
...
from flask_cors import CORS
def create_app(script_info=None):
app = Flask(__name__)
from project.api.blog import blog_blueprint
from project.api.auth import auth_blueprint
CORS(blog_blueprint, resources={'origin': ['http://localhost:3000']})
app.register_blueprint(blog_blueprint)
app.register_blueprint(auth_blueprint)
return app
the above gives me an exception in the catch block of the try-catch statement:
TypeError: name.toUpperCase is not a function
using Flask's defaults which means exposing the endpoint to any domain:
from project.api.blog import blog_blueprint
from project.api.auth import auth_blueprint
CORS(blog_blueprint)
gives me Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:5000/blog/posts. (Reason: CORS header 'Access-Control-Allow-Origin' missing)
I've also tried to use a decorator from Flask-CORS:
from flask_cors import cross_origin
class BlogPosts(Resource):
#cross_origin()
def post(self):
parser.add_argument('category', type=str)
parser.add_argument('title', type=str)
parser.add_argument('post', type=str)
args = parser.parse_args()
new_post = Posts(title=args.title, category=args.category, post=args.post)
db.session.add(new_post)
db.session.commit()
return {'status': 'success', 'message': 'post added'}, 201
Any help is much appreciated.
Strangely, I fixed my problem by refactoring my code into function-based view:
CORS(blog_blueprint)
#blog_blueprint.route('/posts', methods=['GET', 'POST'])
#cross_origin()
def blog_posts():
if request.method == 'POST':
post_data = request.get_json()
category = post_data.get('category')
title = post_data.get('title')
post = post_data.get('post')
new_post = Posts(title=title, category=category, post=post)
db.session.add(new_post)
db.session.commit()
return {'status': 'success', 'message': 'post added'}, 201
return {'status': 'success', 'message': [post.to_json() for post in Posts.query.filter_by(category=category)]}, 200
I'm not very happy with this because Flask's CORS library should work same regardless if I'm using class-based or function-based view for handling APIs.

Blocked by CORS policy "...does not have HTTP ok status" (Amplify and ReactJS, AWS Gateway and Lambda)

I'm almost embarassed to be asking this question due to CORS support out there on SO but I can't get by:
Access to XMLHttpRequest at 'https://a93xxxxx.execute-api.eu-west-1.amazonaws.com/dev[object%20Object]' from origin 'https://www.example.com' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.
I've even published my React project with Amplify and attempted it from the real domain name to even eliminate anything to do with the development environment (Cloud 9 running npm version 6.14.8)
I've also made a test running Chrome with the --disable-web-security flag.
My Lambda function contains the following (out of the box stub)
exports.handler = async (event) => {
// TODO implement
const response = {
statusCode: 200,
// Uncomment below to enable CORS requests
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers" : "Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With",
"Access-Control-Allow-Methods" : "OPTIONS,POST,GET,PUT"
}
,
body: JSON.stringify("Hello from Lambda!")
};
return response;
};
Note that I've uncommented the CORS request part and the response statusCode is set to 200.
The code in my application that execute when a submission form is sent from the client:
uploadcontactusdata = async data => {
try {
console.log("Contact Us pressed")
const settings = {
method: 'POST',
body: JSON.stringify(data),
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
}
}
const fetchResponse = await API.post('econtactus', settings);
Notification({
title: 'Success',
message: 'Notification has been sent',
type: 'success'
});
}
catch (err) {
console.log("unable to send");
console.error(err)
}
}
I created the API Gateway + Lambda using Amplify (version 4.41.2). Not sure where else to look now. Any clues will be appreciated. Thanks
You can completely get past the need for api gateway by using appsync.
amplify add api
Choose graphql (I have not tried using rest but you shouldn't need it) choose the basic schema, edit it if you'd like, and publish. Once it's published you can create your own method. You can view this inside the AppSync UI under Schema.
type Mutation {
yourMethod(input: Input!): TableName <-- add your method to the list
}
Now inside Appsync choose Data Sources and add datasource. Give it a name, choose lambda as the type, then find your lambda in the list. Once it's added go back to your schema and find the method you created above. On the right side bar locate your method and click the attach link. Find the data source you just added. Fill out the region and lambda ARN. MAKE SURE you choose new role and not an existing one.
You might need to configure the request and response templates.
For request:
{
"version" : "2017-02-28",
"operation": "Invoke",
"payload": $util.toJson($context.args)
}
For response:
$util.toJson($context.result)
Now you can call your lambda directly from the UI and return your result without worrying about CORS or managing API Gateway.

React/Axios API Get Request Issues (CORS & Internal Server Error 500)

I am attempting to complete an axios GET request to an API and I'm running into an Internal Server Error - 500 and I'm curious if this is simply my code and/or my attempt at making this call or something else. Even though the CORS issue seems to be behind me, I'll start from the beginning just in case its related to my current issue.
My initial attempt at the request gave me the following CORS error:
...from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight
request doesn't pass access control check: It does not have HTTP ok status.
After doing a lot of research on this, I found that I could append https://cors-anywhere.herokuapp.com to my target API URL and get around this issue. So far, so good but now I am getting the following locally in my browser: net::ERR_NAME_NOT_RESOLVED
So I decided to jump over to Postman and input the given headers to access this API to see if I could find more information and I'm getting the following on Postman:
{
"timestamp": "2020-11-13T01:04:47.288+0000",
"message": "General error occurred please contact support for more details",
"error": "Internal Server Error",
"status": 500
}
Now, within the documentation of this API, it states that a 500 is a server error on their part but I'm not confident in that as I think it may just be my own doing here. So I basically have two questions...
Should the developer of the API do/change anything to avoid the CORS issue or is that a common thing to run into?
Is the 500 error response on me or them?
Below is my axios request in my App.js file of my React application. Please let me know if any other code or info is needed. Thanks so much in advance for any help!
import React, { Component } from 'react';
import './App.css';
import axios from 'axios';
class App extends Component {
state = {
events: []
}
constructor() {
super();
const proxyURL = 'https://cors-anywhere.herokuapp.com'
const URL = 'https://api.example.com/api/'
const proxiedURL = proxyURL + URL
axios.get(proxiedURL, {
headers: {
'Accept': 'application/json',
'Authorization': 'Bearer ' + process.env.REACT_APP_AUTH_API_KEY
}
})
.then((res) => {
console.log(res.data)
})
.catch((error) => {
console.log(error)
})
}
render() {
return (
<div className="App">
<header className="App-header">
<h1>Data</h1>
</header>
</div>
);
}
}
export default App;
According to the documentation for cors-anywhere:
This API enables cross-origin requests to anywhere.
Usage:
/ Shows help /iscorsneeded This is the only resource
on this host which is served without CORS headers. /
Create a request to , and includes CORS headers in the response.
Your code is missing a trailing slash after https://cors-anywhere.herokuapp.com to work, i.e.: https://cors-anywhere.herokuapp.com/.
To answer your two other questions:
CORS issues are very common and it is up to the developer of the API to set from which domain the API can be called. More on MSDN
The 500 response means this in an internal server error, so on the server-side. Though it can be because of many reasons, like querying the wrong URL, passing unexpected data... Ideally all these should be covered and different errors would be returned every time but this is rarely the case. :)

JavaScript CORS error when uploading files with Axios

I am developing a web application with Flask on the backend and React and Redux on the frontend.
I want to add a "Change Profile Picture" option to the profile page but whenever I make a post request with axios to my /api/user/upload_image/ route, i get the following errors:
Access to XMLHttpRequest at 'http://localhost:5000/api/user/update_image' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
PATCH http://localhost:5000/api/user/update_image net::ERR_FAILED
Which is weird becuase I have set up my CORS wrapper in my Flask app like so:
self.cors = CORS(self.app, resources={r"/api/*": {"origins": "*"}})
which should allow requests to /api/ from all origins.
I also tried to do the same thing with Postman and it worked like a charm - uploaded the file and saved it to /server/public/profile_pictures/
When i try to upload regular JSON text from my react application it works as well. It bugs out on file uploads only.
Here is the JSX for the input + the event handler
<label>
Change Profile Picture
<input onChange={(e) => {
this.setState({image: e.target.files[0]})}
} type="file" name="image" />
</label>
Then i have a submit button which dispatches the following action with this.state.image as a parameter:
export const updateImage = (file) => {
return async (dispatch, getState) => {
const formData = {
user_id: getState().currentUser.user.user_id,
auth_key: getState().currentUser.auth_key,
image: file
}
Axios.patch("http://localhost:5000/api/user/update_image", formData, {
headers: {
'Content-Type' : 'multipart/form-data'
}
})
.then(response => {
dispatch({type: UPDATE_IMAGE, payload: response.data})
})
}
I tried using the built in formData method to create the JS object too but that was no good either.
Finally here is the python method which is called when the /api/user/update_image route is hit:
def update_image(self, request):
image = request.files['image']
data = request.params
image.save("./public/profile_pictures/user_p_picture_id_"+data['user_id']+".jpg")
fsql.update("""UPDATE users SET profile_picture = %s WHERE user_id = %s""", ("/public/profile_pictures/user_p_picture_id_"+data['user_id']+".jpg", data['user_id']))
return jsonify({
"error_code" : "200",
"error_message" : "Success"
})
I actually solved this about a week and a half ago but I checked the status today.
So the solution was to make a few changes to my config parameter and CORS parameters. Here is the configs i am using right now:
config = {
'ORIGINS': [
'http://localhost:3000', # React
'http://127.0.0.1:3000', # React
],
'SECRET_KEY': '...' #secret key
self.cors = CORS(self.app, resources={
r'/api/*': {
"Access-Control-Allow-Origin": config["ORIGINS"],
"Access-Control-Allow-Credentials": True,
'supports_credentials': True
},
},
supports_credentials = True,
expose_headers = "*"
)
self.app.config['UPLOAD_FOLDER'] = r'/*' # Change this to only the folder you want to save images to
self.app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # Change this according to your file size
This solved my CORS and file transport issues.
I really hope this helps someone. The CORS docs on flask-cors do not cover everything in regards to file uploading and session storage so we kind of have to solve the errors without knowing how everything works - like trying to solve a puzzle with missing pieces.
HMU in messages if you have any good tools for CORS in flask which are well documented and have a community around them.

Resources