Allow only authenicated users access to HTML video URL using Flask - python

I'm working on a web app that hosts videos/photos so I can easily share with friends and family, running on an Ubuntu Server (20.04) with Python 3.8 Flask and Apache2. I've got everything working however there's one issue, to access the site and the media you must be logged in (authentication is done using JSON Web Tokens) which works fine however due to the media files being stored in the 'static' flask directory (actually a symlink that is in this directory), anyone that finds the URL (while long and difficult to brute force) can directory access these files.
To combat this I tried using a GET request as follows in the HTML:
<img src="/webapp/goes/here/image?title={{file_name}}" alt="{{ file_name }}" onerror="if (this.src != '/static/loading.gif') this.src = '/static/loading.gif';" >
linking back to the flask py file:
#app.route('/webapp/goes/here/image')
def sendimage():
if jwtVerify(request.cookies):
file = request.args.get('title')
filename = 'dir/to/image/not/in/static/dir' + str(file)
return send_file(filename, mimetype='image/jpg')
else:
return redirect(url_for("login"))
Which checks for the JWT authentication and this works perfectly, stopping direct unauthenticated URL access to the files, however, when using this method for videos (changing the MIME of course) it is very very slow and results in lots of buffering.
I've resorted to just hard coding the static directory into the HTML file and with Jinja changing the file name, authentication is still required to access the HTML page but anyone with the URL can get to the video files which I don't want. The video is much much faster to load now though with this method.
<source src="/static/dir/to/video/{{filename}}" type="video/mp4">
Is there a better way to do this that ensures authentication to access the videos or reduce the buffering in the first (successful authenication) method?
Maybe using something along the lines of?
<source src="/webapp/not/real/dir/to/video/{{filename}}" type="video/mp4">
#app.route('/not/real/dir/to/video/*')
def send_video():
if jwtVerify(request.cookies):
return (real_URl_to_video)
else:
return redirect(url_for("login"))
Thanks for the help!

Related

Flask redirect url_for generates Cloud Run service URL instead of domain

Background:
I've built and deployed an app with Google Cloud Firebase. At a high level, I have a Python Flask server running on Cloud Run, and I serve static JS files with Firebase hosting.
Issue:
Sometimes, I want to redirect the user, but I'm sending them to the Cloud Run service URL rather than my app domain.
EDIT: I'm NOT experiencing this in the JS on the browser, but ONLY in the Python on the server.
Python
If a user navigates to a page without being signed in, e.g. following a link, they are redirected to my login page. For example, if someone who is not signed in tries to look at someone else's profile, the following code redirects them to the authentication blueprint's login endpoint:
if not session.get('user'):
return redirect(url_for('authentication.login'))
I would expect them to be redirected to my-app-name.web.app/auth/login but instead they're routed to my-cloudrun-service-name-XXXXX-region.run.app/auth/login. While the pages loaded look the same, they're really not. The second one redirects to the Cloud Run service URL, which doesn't have my static files served by Firebase Hosting.
I'm not really sure, but I believe this happens because Flask is generating the URL using the request context. So the request from the browser hits Cloud Run Load Balancer, which directs the request to my Cloud Run instance, but that means the Flask app in my Cloud Run instance only sees the internal Google Cloud redirect, and it doesn't know my domain.
I've tried solving this by setting app.config['SEVER_NAME'] = my-app-name.web.app, but I just get the "Not Found" page on each request. Is SEVER_NAME the solution but I'm not implementing it correctly, or is there another way to fix the Flask url_for generation?
I've found what I deem to be an effective solution / workaround.
I set the app config to store my BASE_URL when the app is created:
app.config['BASE_URL'] = 'https://my-url.domain'
Then I can access this as application context during requests, even from blueprints:
#blueprint.route('my_route/')
def my_route():
if not session.get('user'):
return redirect(current_app.config['BASE_URL'] + url_for('authentication.login', 302)
This has two key benefits for me:
There's only one place, the app config, to update if I change the domain
url_for is still used, so there's no hardcoding in case blueprints or routes change

How do I connect browser's microphone in my flask app?

I am using speech_recognition module to identify a search query through voice and then open a google chrome page showing the result for the query. Basically, it's a replacement of the google voice search but it's initiated through the terminal. But I want to make this into a web-app. I created the flask app:
-Search(directory)
-search.py (opens a tab using terminal directly/works independently)
-app.py (main flask app)
-static(directory)
-templates (directory)
But since the app is hosted on the server, my search.py takes input from the server mic(in this case it's my PC's mic/ but on AWS, it won't work). How do I take input from the client browser and use it in speech.py? Should I delete this file and use it directly in my main app? What is the most effective way to execute this functionality?
Here is my search.py script if anyone wants to know:
It works through the terminal.
import subprocess
import speech_recognition as sr
browser_exe_path = "..."
r=sr.Recognizer()
with sr.Microphone() as source:
print("Listening!")
audio=r.listen(source)
try:
s_name=r.recognize_google(audio)
"""
Code to open browser and search the query
"""
except:
print("Error!")
These two would probably be the best ways:
make a module/package of your own speech recognition tool and import it into your flask app
integrate the functionality itself into the app.
If you plan on using it again, it might be a good idea to keep the speech recognition separate from the web app, because then you can use it again. But you can customise it much more if you integrate it with, for example, the view functions for your application. Also, you should probably put all your search.py logic in one function or class, so that you can call it. Otherwise, if you import it as it is now, it will immediately run.
Either way, you need a speech structure that looks something like this:
The user submits some speech, either live, recorded, or as a file. We'll call this speech file speech.wav (or any other file type, your choice)
speech.wav is read and parsed by your speech recognition tool. It might return a list of words, or maybe just a string. We'll call this output.
output is returned to the webpage and rendered as something for the user to read.
I suggest starting with a form submission and if you can get that to work, you can try a live speech recognition with AJAX. Start basic and just ask the user to add an audio file or record one. The following script will open up the file browser if on desktop, or get the user to record if on iOS or Android.
<input name="audio-recording" type="file" accept="audio/*" id="audio-recording" capture>
<label for="audio-recording">Add Audio</label>
<p id="output"></p>
So once they've got a file there you need to access it. You may want to customise it, but here is a basic script which will take control of the above audio. Credit for this script goes to google developers.
<script>
const recorder = document.getElementById('audio-recording');
recorder.addEventListener('change', function(e) {
const file = e.target.files[0];
const url = URL.createObjectURL(file);
// Do something with the audio file.
});
</script>
Where it says // Do something with the audio file, it might be a cool idea to make an AJAX GET request, which will return the sentence. But this is where it gets really tricky, because you need to give the information to flask in arguments, not an audio file. But because we've stored the place where the file exists at the constant url in our script, we can use that as the argument, for example:
from flask import request, jsonify
import search # this is your own search.py that you mentioned in your question.
#app.route("/process_audio")
def process_audio():
url = request.args.get("url")
text = search.a_function(url) #returns the text from the audio, which you've done, so I've omitted code
if text != None
return jsonify(result="success",text=text)
else:
return jsonify(result="fail")
This'll return data in something called JSON format, which is like the bridge between client side js and server side python. It might look something like this:
{
"result":"success",
"text":"This is a test voice recording"
}
Then, you need to have some jQuery (or any other js library, but jQuery is nice and easy) to manage the AJAX call:
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script type=text/javascript>
const recorder = document.getElementById('audio-recording');
recorder.addEventListener('change', function(e) {
const file = e.target.files[0];
const url = URL.createObjectURL(file);
$.getJSON('/process_audio', {
url: url
}, function(data) {
$("#output").text(data.text);
});
return false;
</script>
Apologies for any bracketing errors there. So that should send a GET request for some JSON to the URL of "/audio_process", which will return what we saw earlier, and then it will output the "text" of the JSON to the "#output" HTML selector.
There may be some debugging needed, but that seems to do the trick.

Python: Request POST method is not uploading a fie to the web server

I am stuck with this problem that I have had for a while.
I have code here that is intended to upload a file to a web server.
class uploadFile_Functions():
def __init__(self, DESTINATION_URL, TARGET_FILE):
self.DESTINATION_URL = DESTINATION_URL
self.TARGET_FILE = TARGET_FILE
def uploadFile_WebServ(self):
dest_site = self.DESTINATION_URL
t_file = self.TARGET_FILE
with open(t_file,'rb') as f:
print('Uploading the file to the server')
upload_req = requests.post(dest_site, files={t_file:f})
print('Upload successful')
The dest_site will contain the link to the webpage folder that I have, and the t_file will contain the file to be uploaded.
What happens is that when I run this code, there were no errors thrown during runtime, and the shell printed the 'Upload successful' statement. But I checked the webpage folder in chrome, and the file is not even there.
I do not know if this needs some sort of authentication since this is a web server. I was able to get a file from the same webpage folder using the GET method, but no success so far with the POST method.
I appreciate any answer that I get from you guys. Thank you!

Empty reply from Django web service

I have created a web service in django and its hosted on a shared server.The django web service respond to request from a game made in unity. But whenever game tries to request a django Web service url the server send empty resonse.Response is always:
WWW Error: server return empty string
The Unity webplayer expects a http served policy file named "crossdomain.xml" to be available on the domain you want to access with the WWW class, (although this is not needed if it is the same domain that is hosting the unity3d file).So I placed a file "crossdomain.xml" at the root of my domain ,but still i am getting same empty reply.Help plz...
EDIT:
I tried it through browser my service works fine and reply with proper response.And you know what My game can communicate to django web service when both are running on local machine.But now the django project is hosted on actual server and when game tried accessing service it never get response :(
url.py
urlpatterns = patterns('',
url(r'^crossdomain.xml$',views.CrossDomain),
url(r'^ReadFile/$',views.ReadFile),
)
views.py
def CrossDomain(request):
f = open(settings.MEDIA_ROOT+'jsondata/crossdomain.xml', 'r')
data = f.read()
f.close()
return HttpResponse(data, mimetype="application/xml")
def ReadFile(request):
f = open(settings.MEDIA_ROOT+'jsondata/some_file.json', 'r')
data = f.read()
f.close()
return HttpResponse(data, mimetype="application/javascript")
def Test(request):
return HttpResponse("Hello", mimetype="text/plain")
As I said using django for this is slight overkill because you could just serve them. Point aside though. If your serving on a different server it could be
A) Connection problems mean that your response is lost
B) Firewall issues mean that the request mean something
C) The server isn't setup correctly and therefore it justs get an error.
You need to test the response on the server. so is you access the page on the server through your browser. If so then make the game make a request and check the server error and access logs. In the apache access log you should see something like
GET "/url" 200 each time a request is made.
If you don't see any request getting through then either the request isn't made or its been lost.
If you do then the problem is in the code somewhere.

Using web.webopenid with web.py to authenticate users

I have a web.py app I'm running through mod_wsgi locally (http://localhost/...). I've gotten to the point of adding authentication to my app and wanted to use web.py's builtin module. I started with a brief example found here: http://log.liminastudio.com/programming/howto-use-openid-with-web-py
import web, web.webopenid
urls = (
r'/openid', 'web.webopenid.host',
r'/', 'Index'
)
app = web.application(urls, globals())
class Index:
def GET(self):
body = '''
<html><head><title>Web.py OpenID Test</title></head>
<body>
%s
</body>
</html>
''' % (web.webopenid.form('/openid'))
return body
if __name__ == "__main__": app.run()
This works well enough running in the terminal and going to http://localhost:8080/. Another example http://c-farrell.blogspot.com/2010/11/usrbinenv-pythonimport-webfrom-web.html does a similar technique but makes more sense to me.
#!/usr/bin/env python
import web
from web import webopenid
urls = (
'/', 'index',
'/openid', 'webopenid.host',
)
... more code ...
class index:
def GET(self):
oid = webopenid.status()
if not oid:
return 'please log in: ' + \
webopenid.form('/openid')
else:
return 'you are logged in as:' + \
webopenid.form('/openid')
Here's where I get a little lost. From what I can tell, the argument passed to form is the return URL after signing in. For example, if I put 'http://www.yahoo.com/' it will take me there after every login attempt. I feel like this should point back to my own controller and just check there, but the convention seems to be to use the web.webopenid.host controller, which I guess handles the id and returns to the base '/' url. I think I'm getting there, but the status returned is always None.
From what I gather then, this is either a code issue, or there's something in my apache configuration that is keeping the authentication from working. In web.webopenid, the library creates a .openid_secret_key file in the same directory as the web server. When I run the example code, this gets created. When I run my code through apache, it does not (at least not in the cgi-bin. Somewhere else?) Anyway, if this file isn't being generated or being regenerated every time, it will keep me from logging in. I believe it's an apache issue as I tried running my app through the web.py webserver and I did get the file created and I can authenticate. All I can conclude is this file isn't being written and every subsequent query tries a new file and I can never authentication. Can any apache/mod_wsgi gurus explain to me where this file is being written or if this is the actual problem?
Most likely obvious causes for this were given in answer to same question on mod_wsgi list. See:
https://groups.google.com/d/msg/modwsgi/iL65jNeY5jA/KgEq33E8548J
It is probably a combination of the first two, current working directory and Apache user access rights.

Categories