Carry out realtime updates over Flask cache or POST request changes - javascript

The concepts of webhooks are quite simple, yet I am here for some guidance on how a realtime change in POST request is handled.
Consider a webhook program in FLASK:
#app.route('/webhook', methods = ['POST'])
def webhook():
data = json.loads(request.data)
return data
Such a program will easy return a json POST request. And result will look like this:
{
"passphrase": "SkZSV1YwZz0=",
"timenow": "2021-09-15T21:18:00Z",
"item": "apple",
"quantity": "50",
"account": "9999"
}
Now I have successfully read in the webhook data. Next step is to do something with the data.
Say I would like to search my database 'df' for item: apple and return the price.
def get_item_price(df, item_name):
# Returns price by filtering df.
df = df[df['name'] == item_name]
df.reset_index(inplace = True)
price = df['price']
return price
So, price_of_item = get_item_price(df, item_name) can now be used. But since webhook is a POST request I am using cache to access values from other routes.
cache.set("price_data", price_of_item )
I can get the price of the apple by calling cache from another route. Say:
#app.route("/index")
def index():
price_data_recieved= cache.get("price_data")
# do some stuff based on price_data_recieved, and call a api
return index_template.format(message = price_of_item)
index_template = """
<div> the price of apple is: {price_data_recieved}{some_other_stuff} </div>
"""
However this is not real time, I need to reload the browser every time. Can I make changes to this such that as a new POST request changes my index route output as well. I assume its like a ping to /index and initiate a task.
Resources explored unsuccessfully:
Make a POST request while redirecting in flask
Display data streamed from a Flask view as it updates
Update and render a value from Flask periodically
Execute a function after Flask returns response

I am going with While Loop to check if cache values have changed from the previous stored once for now, until a better method is available.

Related

Fetch data from django backend having parts of dynamic URL private

Let's say I am developing a django REST api that is secured via token and basic authentication. The idea is to list juices. So I am having an URL that lists all juices from specific brands.
So I got a dynamic URL like: path("juices/<str:name_brand>/" ...)
What I want to do now is to show all juices of the brand coca-cola in my frontend where no authentication is required. So all visitors should see the list of juices from coca-cola.
I need to use JS fetch since it has to be asynch. Thus my code is something like:
async function get_juices() {
let juices = await fetch(
`${hostname}/api/v1/juices/coca-cola/
)
result = await juices.json();
return juices;
}
My backend is a simple ListView that dynamically filters by brand.
But my problem now is that the API is private and I can't use my credentials in my frontend for obvious security reasons. Because any user could just read the frontend code and use my credentials to access the juices of other brands too.
I was reading now in some posts that it is possible to have a proxy server which only accepts requests from a specific client and reroutes the request with credentials that are being saved on the proxy server. But I have no idea how to implement this with my django app.
I was wondering if there might be a simpler, quicker solution to access some dynamic URLs in public mode and others that are private?
Or any hints how I could implement a reverse proxy to achieve what I need?
Thanks very much in advance
EDIT:
My view:
class JuicesList(APIView):
def get(self, request, **url_params):
name_brand = url_params.get("name_brand", None)
result = Juices.objects.filter(name_brand=name_brand)
return Response(result)
The default permission classes are "SessionAuthentication" and "TokenAuthentication"
You can use a simple if-else in your view and return an appropriate response with respect to the user and the brand name:
from rest_framework import status
class JuicesList(APIView):
def get(self, request, **url_params):
name_brand = url_params.get("name_brand", None)
if not request.user.is_staff and name_brand != 'coca-cola':
return Response({'Error': 'Permission denied'}, status=status.HTTP_403_FORBIDDEN)
result = Juices.objects.filter(name_brand=name_brand)
return Response(result) # This not look correct? You haven't serialized the queryset?
Moving further, this type of permissions are a bit manual and not very DRY (do not repeat yourself), hence you can use permissions DRF docs:
from rest_framework import permissions
class JuicesPermission(permissions.BasePermission):
def has_permission(self, request, view):
return request.user.is_staff or view.kwargs.get("name_brand", None) == 'coca-cola'
class JuicesList(APIView):
permission_classes = [JuicesPermission]
def get(self, request, **url_params):
name_brand = url_params.get("name_brand", None)
result = Juices.objects.filter(name_brand=name_brand)
return Response(result)

How to prevent user from accessing JSON data from query parameters?

I am developing a really simple webapp that searches for a company's stocks.
Here is the JS code (uses AJAX to fetch the company's stock from the server):
document.getElementById("requestQuoteBtn").addEventListener("click", function createQuoteRequest(){
var quoteSymbol = document.getElementById("requestedSymbol").value;
var quoteRequest = createAJAX();
quoteRequest.open('GET', '/quote?sym='+quoteSymbol);
quoteRequest.send();
quoteRequest.onload = function getQuoteRequest(){
if(quoteRequest.status == 200){ // SUCCESSFUL
displayQuoteData(false, JSON.parse(quoteRequest.response)); // basically shows a hidden div with the data
}
else // NO COMPANY W/ THIS SYMBOL FOUND
displayQuoteData(true, null);
};
});
Here is the Flask code:
#app.route("/quote", methods=["GET"])
#login_required
def quote():
requestedSymbol = request.args.get("sym")
if not requestedSymbol:
return "no symbol"
quoteData = lookup(requestedSymbol) # USES AN API TO FETCH COMPANY'S STOCK
if quoteData is None:
return "NONE", 404
else:
return quoteData
The issue is that if the user accesses, for example, this URL:
www.mywebsite.com/quote?sym=AAPL
It will literally show a raw HTML with JSON's data, instead of my website with the data:
{"name":"Apple, Inc.","price":"$245.18","symbol":"AAPL"}
How can I prevent this?
If you simply want to make sure that users do not accidentally access your api endpoint when trying to access your website (aka this is about user experience and your not concerned with adding auth to your API endpoint)
The easiest way is to create separate routes for your api and your client routing
Update:
#app.route("/api/quote", methods=["GET"])
Likewise update:
quoteRequest.open('GET', '/api/quote?sym='+quoteSymbol);
Your client routing will still be:
#app.route("/quote", methods=["GET"])
If you want to make sure that nobody can access your api endpoint then you need to add some sort of authorization to your endpoint.
If you do not secure your API with some authorization then anyone can access the data you return from your server API simply by visiting the route.
Either way setting up separate routes for your API endpoints and client side routes should solve the problem of showing API data instead of your client template when visiting:
mywebsite.com/quote?sym=AAPL

Push live updates to client through Django Channels & Websockets

I'm trying to make a page which shows live-updating data to the client. The rest of the site is built with Django, so I'm trying to use Channels for this.
The data I am displaying is saved in both a JSON file and a MySQL database for further calculations in other parts of the site. Ideally, I would like to display the latest data received (that is, when the file updates) to the client as it is received.
And even though as I understand Channels are built exactly for this purpose, I am having trouble doing it.
I have tried sending multiple requests from the client-side with delay and loops in the consumer, but it either (ironically) only updates on refresh or updates instantly. However, neither of these approaches are triggered by a change in the file or database.
This is the code that "works", but doesn't really do what's required. (also, admittedly, there's basically nothing there...)
# consumers.py
def ws_connect(message):
message.reply_channel.send({"accept": True})
def ws_receive(message):
with open("data.json") as jsonfile:
jsondata = json.load(jsonfile)
res = json.dumps(jsondata)
message.reply_channel.send({ "text": res, })
#routing.py
from channels.routing import route
from .consumers import ws_receive, ws_connect
channel_routing = [
route("websocket.receive", ws_receive, path=r"^/websockets/$"),
route("websocket.connect", ws_connect, path=r"^/websockets/$"),
]
JS used:
<script>
var wsurl = "ws://" + "mywebsite.com" + "/websockets/";
socket = new WebSocket(wsurl);
socket.onopen = function() {
socket.send("this is a request");
console.log('sent');
}
socket.onmessage = function(message) {
console.log(message.data);
document.getElementById("livedata").innerHTML = message.data;
}
</script>
I'd be perfectly happy with a link to the docs that would help me achieve something like this, as I've managed to not find the solution for a whole week.
Add user to django channels Group on ws_connect
from channels.auth import channel_session_user_from_http
from channels import Group
#channel_session_user_from_http
def ws_connect(message, **kwargs):
http_user = message.user
if not http_user.is_anonymous:
message.reply_channel.send({'accept': True})
Group('user-'+str(http_user.id)).add(message.reply_channel)`
send any new updates data to the user by
Group('user-1').send({'text': 'hello user 1'})
Group('user-2').send({'text': 'hello user 2'})

AngularJs bookmarkable url and query parameters

I'm trying to fix one mistake which one of the previous developer has did. Currently in project we have half-bookmarkable pages. Why half? Because if token has expired user will be redirect to resourse where he has to provide his credentials and on success he will be redirect to previous resource which he has used before. Sounds good for now. The problem here is next, some of resources have server side filtering and highly complicated logic which comes in the end to the one object like this:
param = {
limit: 10,
offset: 0,
orderBy: 'value',
query: 'string query can include 10 more filters'
};
then thru the service it sends a request to the end point
var custom = {};
function data(param) {
custom.params = param;
return $http.get('/data_endpoint', custom)
.then(function (response) {
return response.data;
});
From this part request works fine and response is correct but url isn't. User cannot store current url with search params because url doesn't have it and if user will try to get back to previous page with applied filters he won't be able to do that.
Also there is interceptor in this application, which add one more query param to every api request, because every api request requires some specific validation. This param is always the same.
I have tried to add $location.search(param) exactly to this data service but it breaks the app with infinity loop of login / logout looping, because interceptor stops working and all other request are sending without special query param
Then I thought to add this params inside interceptor like this:
config.params.hasOwnProperty('query') ? $location.search(config.params) : $location.search();
But it still breaks app to infinity loop.
This method adds query to url but doesn't allow to modify it, so if user applied additional filtering, it doesn't send new modified request but sends old one.
if (config.params.hasOwnProperty('query')){
for (var key in config.params){
$location.search(key, config.params[key])
}
}
That's why I decided to ask question here, probably somebody gives an advice how to solve this problem. Thanks in advance.

Struggling to build a JS/PHP validation function for my app

I have a web service that returns a JSON object when the web service is queried and a match is found, an example of a successful return is below:
{"terms":[{"term":{"termName":"Focus Puller","definition":"A focus puller or 1st assistant camera..."}}]}
If the query does not produce a match it returns:
Errant query: SELECT termName, definition FROM terms WHERE termID = xxx
Now, when I access this through my Win 8 Metro app I parson the JSON notation object using the following code to get a JS object:
var searchTerm = JSON.parse(Result.responseText)
I then have code that processes searchTerm and binds the returned values to the app page control. If I enter in a successful query that finds match in the DB everything works great.
What I can't work out is a way of validating a bad query. I want to test the value that is returned by var searchTerm = JSON.parse(Result.responseText) and continue doing what I'm doing now if it is a successful result, but then handle the result differently on failure. What check should I make to test this? I am happy to implement additional validation either in my app or in the web service, any advice is appreciated.
Thanks!
There are a couple of different ways to approach this.
One approach would be to utilize the HTTP response headers to relay information about the query (i.e. HTTP 200 status for a found record, 404 for a record that is not found, 400 for a bad request, etc.). You could then inspect the response code to determine what you need to do. The pro of this approach is that this would not require any change to the response message format. The con might be that you then have to modify the headers being returned. This is more typical of the approach used with true RESTful services.
Another approach might be to return success/error messaging as part of the structured JSON response. Such that your JSON might look like:
{
"result":"found",
"message":
{
"terms":[{"term":{"termName":"Focus Puller","definition":"A focus puller or 1st assistant camera..."}}]}
}
}
You could obviously change the value of result in the data to return an error and place the error message in message.
The pros here is that you don't have to worry about header modification, and that your returned data would always be parse-able via JSON.parse(). The con is that now you have extra verbosity in your response messaging.

Categories