{"id":14972806,"url":"https://github.com/lightechllc/django-nginx-secure-links","last_synced_at":"2025-10-26T20:32:09.840Z","repository":{"id":63289224,"uuid":"566724647","full_name":"LighTechLLC/django-nginx-secure-links","owner":"LighTechLLC","description":"This repository serves as a module for creating private URLs in the Django Framework using the Nginx module ngx_http_secure_link_module.","archived":false,"fork":false,"pushed_at":"2024-08-12T21:38:49.000Z","size":49,"stargazers_count":16,"open_issues_count":0,"forks_count":3,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-02-01T00:14:44.303Z","etag":null,"topics":["django","nginx-secure-links","python"],"latest_commit_sha":null,"homepage":"https://django-nginx-secure-links.readthedocs.io/en/latest/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/LighTechLLC.png","metadata":{"files":{"readme":"README.rst","changelog":"CHANGELOG.rst","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-11-16T09:34:42.000Z","updated_at":"2025-01-12T18:26:42.000Z","dependencies_parsed_at":"2024-09-23T12:01:52.464Z","dependency_job_id":"009729d0-06e8-4782-a091-189b755c6d96","html_url":"https://github.com/LighTechLLC/django-nginx-secure-links","commit_stats":{"total_commits":30,"total_committers":2,"mean_commits":15.0,"dds":"0.033333333333333326","last_synced_commit":"388775050b6550271e5111b1fe3fb46d119e9643"},"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LighTechLLC%2Fdjango-nginx-secure-links","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LighTechLLC%2Fdjango-nginx-secure-links/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LighTechLLC%2Fdjango-nginx-secure-links/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LighTechLLC%2Fdjango-nginx-secure-links/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/LighTechLLC","download_url":"https://codeload.github.com/LighTechLLC/django-nginx-secure-links/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":238397207,"owners_count":19465134,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["django","nginx-secure-links","python"],"created_at":"2024-09-24T13:47:34.147Z","updated_at":"2025-10-26T20:32:04.525Z","avatar_url":"https://github.com/LighTechLLC.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":".. image:: https://github.com/LighTechLLC/django-nginx-secure-links/actions/workflows/ci.yml/badge.svg\n    :target: https://github.com/LighTechLLC/django-nginx-secure-links/actions/workflows/ci.yml\n    :alt: Build Status\n\n.. image:: https://coveralls.io/repos/LighTechLLC/django-nginx-secure-links/badge.svg?branch=master\n   :target: https://coveralls.io/r/LighTechLLC/django-nginx-secure-links?branch=master\n   :alt: Coverage\n\nDjango Nginx Secure Links\n=========================\n\nThis module is a Django extension for using `ngx_http_secure_link_module \u003chttp://nginx.org/en/docs/http/ngx_http_secure_link_module.html\u003e`_.\nIt provides private urls with expiration lifetime by implementing described logic of ngx_http_secure_link_module.\nThe major advantage of the extension is that Django delegates file serving on Nginx layer and does only pre-signed urls generation.\n\nRequirements\n============\n\nDjango Nginx Secure Links requires Django 3.2 or later.\n\n\nInstallation\n============\n\nInstalling from PyPI is as easy as doing:\n\n.. code-block:: bash\n\n    pip install django-nginx-secure-links\n\nIf you want to install it from source, grab the git repository from GitHub and run setup.py:\n\n.. code-block:: bash\n\n    git clone git://github.com/lighTechLLC/django-nginx-secure-links.git\n    cd django-nginx-secure-links\n    python setup.py install\n\nNginx module set up\n===================\n\n**Option 1**\n\nInstall using apt (Ubuntu example):\n\n.. code-block:: bash\n\n    sudo apt install nginx-extras\n\n**Option 2**\n\nBuild from sources:\n\n.. code-block:: bash\n\n    ./configure .... --with-http_secure_link_module\n\n\nQuick example\n=============\n\n\n1. Django settings set up **settings.py**:\n\n.. code-block:: python\n\n    INSTALLED_APPS = (\n        ...\n        'nginx_secure_links',\n        ...\n    )\n    MEDIA_ROOT = '/var/www/media/'\n    MEDIA_URL = '/media/'\n    SECURE_LINK_SECRET_KEY = 'KfM6aA6M7H'\n\n- Django \u003c 4.2\n\n.. code-block:: python\n\n    DEFAULT_FILE_STORAGE = 'nginx_secure_links.storages.FileStorage'\n\n\n- Django \u003e= 4.2\n\n.. code-block:: python\n\n    STORAGES = {\n        \"default\": {\n            \"BACKEND\": \"nginx_secure_links.storages.FileStorage\",\n        },\n        \"staticfiles\": {\n            \"BACKEND\": \"django.contrib.staticfiles.storage.StaticFilesStorage\",\n        },\n    }\n\n\n\n2. Create a private file inside your ``settings.MEDIA_ROOT``:\n\n.. code-block:: bash\n\n    echo \"I'm private text file\" \u003e /var/www/media/sample.txt\n\n3. Let's start ``runserver`` and access the file outside of Django file storage. It works and the file is available. There is no access denied, because of ``runserver`` mode:\n\n.. code-block:: bash\n\n    curl http://127.0.0.1:8000/media/sample.txt\n\n4. Set up Nginx virtual host file **site.conf**:\n\n.. code-block:: nginx\n\n    server 127.0.0.1;\n    listen 80;\n\n    ...\n\n    location /media/ {\n        secure_link $arg_token,$arg_expires;\n        secure_link_md5 \"$secure_link_expires$uri KfM6aA6M7H\";\n\n        if ($secure_link = \"\") {\n            return 403;\n        }\n\n        if ($secure_link = \"0\") {\n            return 410;\n        }\n\n        alias /var/www/media/;\n    }\n\n    ...\n\n5. Let's access the file through Nginx host/port.\n\n.. code-block:: bash\n\n    curl http://127.0.0.1/media/sample.txt\n\nBecause of Nginx secure link module protection, the file won't be served\nwithout ``?token=...\u0026expires=...`` parameters. Only django users will be able\nto access files which urls generated by django storage.\n\nUsage\n=====\n\n**models.py**\n\n.. code-block:: python\n\n    class Report(models.Model):\n        pdf_file = models.FileField(upload_to='reports')\n\n**views.py**\n\n.. code-block:: python\n\n    def report_details(request, report_id)\n        instance = Report.objects.get(id=report_id)\n        return JsonResponse({'url': instance.pdf_file.url})\n\n**json response**\n\n.. code-block:: json\n\n    {\n      \"url\": \"/media/reports/29974.pdf?expires=1599214310\u0026token=ErLcMm96-4h2qsuj2Avo-w\"\n    }\n\n\nThat's it, all uploaded media files through Django will be pre-signed.\nIf you work locally and do not want to install Nginx, let's skip it for\nlocal development- django will generate pre-signed urls, but all files will be\navailable because of ``runserver`` command serves files and does not provide\n3rd-party ``nginx-secure-link`` module functionality.\n\nSettings\n========\n\n- ``SECURE_LINK_SECRET_KEY``\n\nYour specific secret string which Nginx is going to use in ``secure_link_md5`` directive.\n\n- ``SECURE_LINK_TOKEN_FIELD`` (optional, default: ``token``)\n\nYour custom name of the hash GET-parameter (?token=xyz)\n\n- ``SECURE_LINK_EXPIRES_FIELD`` (optional, default: ``expires``)\n\nYour custom name of expiration timestamp GET-parameter  (?expires=1599215210)\n\n- ``SECURE_LINK_EXPIRATION_SECONDS`` (optional, default: ``86400``- 1 day)\n\nYour custom value of expiration seconds. Any pre-signed link will be expired after ``SECURE_LINK_EXPIRATION_SECONDS``.\n\n- ``SECURE_LINK_PRIVATE_PREFIXES`` (optional, default: ``[]``)\n\nList of private paths without ``MEDIA_URL`` prefix. Just leave it empty for making all media urls private. Example:\n\n.. code-block:: python\n\n    MEDIA_URL = '/media/'\n    SECURE_LINK_PRIVATE_PREFIXES = [\n        'documents/',\n        'reports/',\n    ]\n\nIn such case all ``/media/documents/`` and ``/media/reports/`` urls will be private and pre-signed by using token and expiration time. If any of existing prefixes on the project are not listed in ``SECURE_LINK_PRIVATE_PREFIXES``, so the url will be public.\n\n- ``SECURE_LINK_PUBLIC_PREFIXES`` (optional, default: ``[]``)\n\nList of private paths without ``MEDIA_URL`` prefix. Example:\n\n.. code-block:: python\n\n    MEDIA_URL = '/media/'\n    SECURE_LINK_PUBLIC_PREFIXES = [\n        'avatars/',\n        'shared/',\n    ]\n\nIn such case only ``/media/avatars/`` and ``/media/shared/`` urls will be public and generated without pre-signed urls. All other urls, will be private and pre-signed by using token and expiration time.\n\n**Important** If you want to keep all media files privately, ``SECURE_LINK_PRIVATE_PREFIXES`` and ``SECURE_LINK_PUBLIC_PREFIXES`` should be ``[]``.\n\nCustom storage for non-media files\n==================================\n\n**Example 1:** We are going to use our own server directory and url prefix instead\nof ``settings.MEDIA_ROOT`` / ``settings.MEDIA_URL``.\nThe example is going to use all default ``settings.SECURE_LINK_*``\n\n.. code-block:: python\n\n    from nginx_secure_links.storages import FileStorage\n\n    storage = FileStorage(location='/var/www/personal_data/', base_url='/personal/')\n    storage.url('profile.pdf')\n    storage.url('profile.pdf', lifetime=60)\n    storage.url('profile.pdf', lifetime=0)\n\n**Example 2**: We are going to use custom storage with all overridden settings.\n\n.. code-block:: python\n\n  from nginx_secure_links.storages import FileStorage\n\n  storage = FileStorage(\n        location='/var/www/personal_data/',\n        base_url='/personal/'\n        nginx_secret_key='91rdywY7d4494X',\n        expires_field_name='expires_timestamp',\n        token_field_name='hash',\n        private_prefixes=[],\n        public_prefixes=[],\n        expires_seconds=60 * 60,  # 60min\n    ) # all private\n    storage.url('profile.pdf')  # /personal/profile.pdf?hash=mlkiuhbhu83d\u0026expires_timestamp=2147483647\n\nUsing It\n========\n\nGenerate pre-signed url by passing public url::\n\n    python manage.py secure_links_gen_signed /media/reports/sample.pdf\n\n\nGenerates a sample of Nginx location basing on the settings::\n\n    python manage.py secure_links_nginx_location\n\n\nFound a Bug?\n============\nIssues are tracked via GitHub issues at the `project issue page\n\u003chttps://github.com/LighTechLLC/django-nginx-secure-links/issues\u003e`_.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flightechllc%2Fdjango-nginx-secure-links","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flightechllc%2Fdjango-nginx-secure-links","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flightechllc%2Fdjango-nginx-secure-links/lists"}