I would like to serve user uploaded media files using nginx on the same host as the Django application rather than a CDN or S3 or similar.
The django-private-storage library can be used to protect media files behind a login: https://github.com/edoburu/django-private-storage
I am deploying my Django application with Dokku.
Dokku says that dokku persistant storage plugin should be used to allow for user uploads to be persisted on the host. https://dokku.com/docs~v0.9.2/advanced-usage/persistent-storage/
My confusion is that django-private-storage requires you to edit the config for nginx.
Specifically, it requires you to set the location of the private media being served to be internal. So that the URL cannot be accessed from the outside by a user who isn't logged in.
The dokku docs don't explain how to use persistant storage behind an application login.
Do I actually need django-persistant-storage to be able to write user uploaded media?
How can I combine these solutions so that my application, which is inside a container, can read and write media files, which media files are served by nginx, and served at an internal location that can only be accessed by a user who is logged into the application?
Updates (Oct 2021)
I am able to deploy my app, upload files and access them at the appropriate URL. But I haven't been able to protect them against unauthenticated access.
I haven't yet used django-private-storage or dokku persistant storage. Once the files are inaccessible I plan to follow these steps to allow authenticated access: https://b0uh.github.io/protect-django-media-files-per-user-basis-with-nginx.html
I created a file my_conf.conf saved to /home/dokku/backend/nginx.conf.d
which contains
location /protected/ {
internal;
alias /home/dokku/backend/;
}
and then rebooted Nginx
I can't actually see the images anywhere on host, but if I run dokku enter backend then my files are there in the container under '/mediafiles/testuploads/'
Here is settings.py
MEDIA_ROOT = os.path.join(BASE_DIR, 'mediafiles')
MEDIA_URL = '/media/'
and models.py
class User(AbstractUser):
profile_image = models.ImageField(upload_to='testuploads/', null=True)
Let's do it by yourself!
You can serve your profile images with your custom view that checks auth!
You may implement the needed feature without additional dependencies. As a bonus, you will understand the whole process.
So, you need to:
Add a path, something like /profiles/<int:user_id>/image/ in your urls.py
Use this link with a proper user_id in your front-end (change a needed template)
Write a class-based or function-based view for this endpoint, as usual, check the user_id parameter, check the auth in request, compare user in request with the user in the user instance and maybe something else.
Response with 401 Not Authorized when you have an unauthorized request.
Response with FileResponse in OK.
from django.http import FileResponse
response = FileResponse(open('myfile.png', 'rb'))
use your user.profile_image property
Theoretically, if the user doesn't know the old path to /media/testuploads/filename.ext this file is "not shared".
But if you want to be sure - don't serve /media/ folder with NGINX or exactly /media/testuploads/ path if you want to serve another media files (return 401 https://$host$request_uri; in NGINX config in the needed block). Such changes need NGINX to be reloaded.
Watch view caching in the next seasons 😀 to improve the performance. But the browser will cache the image if it works in default settings.
Related
I have project in django 1.0.4 - yes I know it is old.
I want to use the lack of access to media (audio) files for users who are not logged in.
After making changes to nginx, logged in users also have no access.
I tried with view and url function - no result
my nginx settings:
location /media/content/audio/ {
deny all;
}
my function and url
#login_required
def protected_serve(request, path, document_root=None, show_indexes=False):
if not request.user.is_authenticated:
raise Http404()
else:
return serve(request, path, document_root, show_indexes)
urlpatterns += patterns('',
(r'^media/content/audio/(?P<path>.*)$', protected_serve),
)
You're very close to having the whole puzzle put together. There are two things you need to do:
Configure NGINX that you do want to be able to serve data from a particular folder, but that said folder isn't public. The authorization to send files from a folder will come from the application behind NGINX, not from external requests to NGINX.
Have your django app send the kind of response to NGINX that NGINX understands to mean "serve this file from the protected area in 1"
The way you achieve the first goal is to use the config directive "internal"
Achieving the second goal is to use the HTTP response header "X-Accel-Redirect" as #ralf states in the comments above.
Here is a blog post on the subject: https://clubhouse.io/developer-how-to/how-to-use-internal-redirects-in-nginx/
A Python project to help you achieve the same goal: https://pypi.org/project/django-transfer/
NGINX Docs: https://www.nginx.com/resources/wiki/start/topics/examples/xsendfile/
in the urls i have this url that leads to the serve_protected_file on each time someone is trying to access a media file
url(r'^%s(?P<path>.*)$' % settings.MEDIA_URL[1:], views.serve_protected_file, {'document_root': settings.MEDIA_ROOT})
the serve_protected_file looks like this
def serve_protected_file(request, path, document_root=None, show_indexes=False):
if request.user.is_authenticated:
return serve(request, path, document_root, show_indexes)
raise Http404()
This only works in the development environment
When i deploy then the files are served with the nginx and it gives me the file trough the url which looks like that
https://staging.mywebsite.com/media/img/531126758844.jpg
how to restrict it everything in my media folder is private and external access to a file should be restricted unless user is authenticated
my staging.nginx.conf looks like
location ~ ^/media.*?/(.*)$ {
alias /data/www/staging/mywesite/media/$1;
access_log off;
}
okay Eurica after 2 hours of head banging...
Since the media files are all confidential, I just deleted the media configuration in staging.nginx.conf and it works like a charm
so now is executing the django view..., nginx was intercepting the django view
I'm using this package called django_hosts to re-route urls for some apps.
Everything is working fine except for the fact that django_hosts is not working with Django Authentication.
I hosted this url api.example.com, so on this page with the url api.example.com:8000/add_post, I want users to add post but before doing that you must be authenticated. So after I logged in, I still can't submit post via the form talkless of posting. But when I go back to example.com, it shows that I'm logged in but api.example.com is telling me otherwise.
How do I make django authentication work with this package?
The problem is that the authentication token is hooked to the domain. Using Django's default configuration, the api.example.com can't access the example.com auth token.
You can change this behaviour by setting the SESSION_COOKIE_DOMAIN configuration in your settings.py module:
SESSION_COOKIE_DOMAIN = 'example.com'
But not too fast! Do it carefully, otherwise you can break your application:
Be cautious when updating this setting on a production site. If you
update this setting to enable cross-domain cookies on a site that
previously used standard domain cookies, existing user cookies will be
set to the old domain. This may result in them being unable to log in
as long as these cookies persist.
More info on the official documentation.
I have a Flask-Admin project set up with Flask-Security as well. It is pretty much https://pythonhosted.org/Flask-Security/quickstart.html#id1 but just more advanced. I can access the login page at localhost/login and logout and localhost/logout. The logging in and logging out works.
The templates for Flask-Admin works and everything is displayed as I'd expect. However, there are no templates on my machine or docker container where the Flask-Admin app is run. I installed Flask by running pip install Flask-Admin. I know I can over ride the security log in by adding something like
SECURITY_LOGIN_USER_TEMPLATE = 'security/login_user.html'
to the config file and uploading to /templates/security/login_user.html. There is also using
{%extends base.html}
to have a common theme. Should I have template files already in my project?
Flask Security have a default login template, if you want to use your own template for login or register follow these steps:
Create in template folder the a subfolder named security
Add your html documents to this folder
Go to your flask configuration and add the following settings:
If your want the register functionality
SECURITY_REGISTERABLE = True
Add the name of your templates:
SECURITY_LOGIN_USER_TEMPLATE = 'security/login.html'
SECURITY_REGISTER_USER_TEMPLATE = 'security/register.html'
Remember to use the appropriate form in login.html and in register.html, usually causes doubts but is simple:
register.html: register_user_form.field
login.html: login_user_form.field
These are the configurations for this work correctly.
this repository can you to see and understand better doubt:
I am trying to use django social auth to auth via facebook.
I used this documentation to make changes in my django project and to create and setup the facebook app. I am running django project on local server. The project has url http://127.0.0.1:8000. When hit the following link on my project's web page:
Template
<p>Facebook</p>
Rendered template
<p>Facebook</p>
it redirects me to a facebook page where I can see the following message:
Given URL is not allowed by the Application configuration: One or more
of the given URLs is not allowed by the App's settings. It must match
the Website URL or Canvas URL, or the domain must be a subdomain of
one of the App's domains.
How to change facebook app's settings to allow
http://127.0.0.1:8000?
upd
Added localhost to app domains and http://localhost:8000/ to site URL in my fb app's settings.
The Facebook app doesn't allow IP addresses like that, but it does allow localhost. In the App Domains on the Settings tab you need to add localhost. What sucks is that other services (such as twitter) don't accept localhost and you can only use 127.0.0.1:8000, which means you have to switch back and forth.
It works for local development, but it's not pretty.
EDIT:
You also need to add http://localhost:8000/ to the Site URL first