Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 0 additions & 107 deletions .circleci/config.yml

This file was deleted.

33 changes: 33 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Publish

on:
release:
types: [published]

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.13"
- run: pip install build
- run: python -m build
- uses: actions/upload-artifact@v4
with:
name: dist
path: dist/

publish:
needs: build
runs-on: ubuntu-latest
environment: pypi
permissions:
id-token: write
steps:
- uses: actions/download-artifact@v4
with:
name: dist
path: dist/
- uses: pypa/gh-action-pypi-publish@release/v1
57 changes: 57 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: Test

on:
push:
branches: [master]
pull_request:
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.13"
- run: pip install tox
- run: tox -e ruff

test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- run: pip install tox tox-gh-actions
- run: tox

test-django-main:
runs-on: ubuntu-latest
continue-on-error: true
strategy:
fail-fast: false
matrix:
include:
- python-version: "3.12"
tox-env: py312-djmain
- python-version: "3.13"
tox-env: py313-djmain
- python-version: "3.14"
tox-env: py314-djmain
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- run: pip install tox
- run: tox -e ${{ matrix.tox-env }}
42 changes: 18 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@

[![](https://img.shields.io/pypi/v/django-authlink.svg)](https://pypi.python.org/pypi/django-authlink/)
[![](https://img.shields.io/badge/license-MIT-blue.svg)](https://pypi.python.org/pypi/django-authlink/)
[![CircleCI](https://circleci.com/gh/lukeburden/django-authlink.svg?style=svg)](https://circleci.com/gh/lukeburden/django-authlink)
[![Codecov](https://codecov.io/gh/lukeburden/django-authlink/branch/master/graph/badge.svg)](https://codecov.io/gh/lukeburden/django-authlink)
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black)
[![Test](https://github.com/lukeburden/django-authlink/actions/workflows/test.yml/badge.svg)](https://github.com/lukeburden/django-authlink/actions/workflows/test.yml)
[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)


## django-authlink
Expand Down Expand Up @@ -102,38 +101,33 @@ Default: 60
Allows increasing or decreasing the period of validity for an authlink.


### Contribute
### Supported versions

`django-authlink` supports a variety of Python and Django versions. It's best if you test each one of these before committing. Our [Circle CI Integration](https://circleci.com) will test these when you push but knowing before you commit prevents from having to do a lot of extra commits to get the build to pass.
`django-authlink` supports the Python and Django versions currently supported upstream:

#### Environment Setup
- Python 3.10 through 3.14
- Django 5.2 (LTS) and 6.0

In order to easily test on all these Pythons and run the exact same thing that CI will execute you'll want to setup [pyenv](https://github.com/yyuu/pyenv) and install the Python versions outlined in [tox.ini](tox.ini).
Django's `main` branch is also tested in CI, but failures there do not fail the build.

If you are on Mac OS X, it's recommended you use [brew](http://brew.sh/). After installing `brew` run:
### Contribute

```bash
brew install pyenv pyenv-virtualenv pyenv-virtualenvwrapper
```
Tests run via [tox](https://tox.wiki) across all supported Python/Django combinations, and
[GitHub Actions](https://github.com/lukeburden/django-authlink/actions) runs the same
environments on push and pull request.

Next, install the various python versions we want to test against and create a virtualenv specifically for `django-authlink`:
To run the tests locally against the Python versions you have installed:

```bash
pyenv install 3.6.10
pyenv install 3.7.6
pyenv install 3.8.1
pyenv virtualenv 3.8.1 authlink
pyenv activate authlink
pip install detox
pyenv shell authlink 3.6.10 3.7.6
pip install tox
tox
```

Now ensure the `authlink` virtualenv is activated, make the other python versions also on our path, and run the tests!

To run a single environment, or lint/format checks with [Ruff](https://docs.astral.sh/ruff/):

```bash
pyenv shell authlink 3.6.10 3.7.6
detox
tox -e py313-dj60
tox -e ruff
```

This will execute the test environments in parallel as defined in the `tox.ini`.
Releases are published to PyPI automatically when a GitHub release is created.
22 changes: 11 additions & 11 deletions authlink/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@
from django.contrib.auth import login, logout
from django.http import HttpResponseForbidden
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _

from ipware.ip import get_ip, get_real_ip
from ipware import get_client_ip

from .models import AuthLink


class DefaultAuthLinkAdapter(object):
class DefaultAuthLinkAdapter:
"""
Most application logic should live here, such that it becomes
easily overridable.
Expand All @@ -35,15 +35,15 @@ def create(self, **kwargs):
return authlink

def calculate_expiry(self, created):
return created + datetime.timedelta(
seconds=getattr(settings, "AUTHLINK_TTL_SECONDS", 60)
)
return created + datetime.timedelta(seconds=getattr(settings, "AUTHLINK_TTL_SECONDS", 60))

def extract_ipaddress(self, request):
ipaddress = get_real_ip(request)
if not ipaddress and settings.DEBUG:
ipaddress = get_ip(request)
return ipaddress
# only trust non-routable addresses when in DEBUG, mirroring the
# behaviour of the legacy ipware get_real_ip/get_ip functions
client_ip, is_routable = get_client_ip(request)
if is_routable or settings.DEBUG:
return client_ip
return None

def add_message(self, request, level, message):
messages.add_message(request, level, message)
Expand Down Expand Up @@ -98,7 +98,7 @@ def in_url_whitelist(self, url):

def get_whitelist_failure_response(self, request):
return HttpResponseForbidden(
_("That URL is not whitelisted for your " "authentication method.")
_("That URL is not whitelisted for your authentication method.")
)


Expand Down
2 changes: 1 addition & 1 deletion authlink/api/rest_framework/serializers.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _

from rest_framework import serializers

Expand Down
3 changes: 2 additions & 1 deletion authlink/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@

from authlink.adapter import get_adapter


adapter = get_adapter()


class AuthLinkWhitelistMiddleware(object):
class AuthLinkWhitelistMiddleware:
"""
Only allow access to whitelisted URLs for sessions that are
established using the authlink authentication mechanism.
Expand Down
5 changes: 1 addition & 4 deletions authlink/migrations/0001_initial.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@


class Migration(migrations.Migration):

dependencies = [migrations.swappable_dependency(settings.AUTH_USER_MODEL)]

operations = [
Expand All @@ -32,9 +31,7 @@ class Migration(migrations.Migration):
("used", models.DateTimeField(null=True, blank=True)),
(
"user",
models.ForeignKey(
to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE
),
models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE),
),
],
)
Expand Down
4 changes: 1 addition & 3 deletions authlink/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,4 @@ def get_timezone_now():


def generate_authlink_key():
return get_random_string(
getattr(settings, "AUTHLINK_KEY_LENGTH", 64), VALID_KEY_CHARS
)
return get_random_string(getattr(settings, "AUTHLINK_KEY_LENGTH", 64), VALID_KEY_CHARS)
2 changes: 1 addition & 1 deletion authlink/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from django.db import transaction
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from django.views.generic import View

from .adapter import get_adapter
Expand Down
Loading
Loading