mirror of
https://github.com/sissbruecker/linkding.git
synced 2025-09-01 14:56:48 +02:00
Compare commits
9 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
cfe4ff113d | ||
![]() |
757dc56277 | ||
![]() |
dfbb367857 | ||
![]() |
2276832465 | ||
![]() |
9d61bdce52 | ||
![]() |
1274a9ae0a | ||
![]() |
5e7172d17e | ||
![]() |
78608135d9 | ||
![]() |
51acd1da3f |
26
.github/workflows/build.yaml
vendored
Normal file
26
.github/workflows/build.yaml
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
name: build
|
||||||
|
|
||||||
|
on: workflow_dispatch
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
docker:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
-
|
||||||
|
name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
-
|
||||||
|
name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
-
|
||||||
|
name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
-
|
||||||
|
name: Build and push
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
file: ./docker/default.Dockerfile
|
||||||
|
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||||
|
push: false
|
||||||
|
tags: sissbruecker/linkding:test
|
||||||
|
target: linkding
|
18
CHANGELOG.md
18
CHANGELOG.md
@@ -1,5 +1,23 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## v1.38.0 (09/02/2025)
|
||||||
|
|
||||||
|
### What's Changed
|
||||||
|
* Fix nav menu closing on mousedown in Safari by @sissbruecker in https://github.com/sissbruecker/linkding/pull/965
|
||||||
|
* Allow customizing username when creating user through OIDC by @kyuuk in https://github.com/sissbruecker/linkding/pull/971
|
||||||
|
* Improve accessibility of modal dialogs by @sissbruecker in https://github.com/sissbruecker/linkding/pull/974
|
||||||
|
* Add option to collapse side panel by @sissbruecker in https://github.com/sissbruecker/linkding/pull/975
|
||||||
|
* Convert tag modal into drawer by @sissbruecker in https://github.com/sissbruecker/linkding/pull/977
|
||||||
|
* Add RSS link to shared bookmarks page by @sissbruecker in https://github.com/sissbruecker/linkding/pull/984
|
||||||
|
* Add Additional iOS Shortcut to community section by @joshdick in https://github.com/sissbruecker/linkding/pull/968
|
||||||
|
|
||||||
|
### New Contributors
|
||||||
|
* @kyuuk made their first contribution in https://github.com/sissbruecker/linkding/pull/971
|
||||||
|
|
||||||
|
**Full Changelog**: https://github.com/sissbruecker/linkding/compare/v1.37.0...v1.38.0
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## v1.37.0 (26/01/2025)
|
## v1.37.0 (26/01/2025)
|
||||||
|
|
||||||
### What's Changed
|
### What's Changed
|
||||||
|
@@ -10,6 +10,7 @@ from bookmarks.services.bookmarks import (
|
|||||||
enhance_with_website_metadata,
|
enhance_with_website_metadata,
|
||||||
)
|
)
|
||||||
from bookmarks.services.tags import get_or_create_tag
|
from bookmarks.services.tags import get_or_create_tag
|
||||||
|
from bookmarks.services.wayback import generate_fallback_webarchive_url
|
||||||
|
|
||||||
|
|
||||||
class TagListField(serializers.ListField):
|
class TagListField(serializers.ListField):
|
||||||
@@ -59,9 +60,10 @@ class BookmarkSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
# Custom tag_names field to allow passing a list of tag names to create/update
|
# Custom tag_names field to allow passing a list of tag names to create/update
|
||||||
tag_names = TagListField(required=False)
|
tag_names = TagListField(required=False)
|
||||||
# Custom fields to return URLs for favicon and preview image
|
# Custom fields to generate URLs for favicon, preview image, and web archive snapshot
|
||||||
favicon_url = serializers.SerializerMethodField()
|
favicon_url = serializers.SerializerMethodField()
|
||||||
preview_image_url = serializers.SerializerMethodField()
|
preview_image_url = serializers.SerializerMethodField()
|
||||||
|
web_archive_snapshot_url = serializers.SerializerMethodField()
|
||||||
# Add dummy website title and description fields for backwards compatibility but keep them empty
|
# Add dummy website title and description fields for backwards compatibility but keep them empty
|
||||||
website_title = serializers.SerializerMethodField()
|
website_title = serializers.SerializerMethodField()
|
||||||
website_description = serializers.SerializerMethodField()
|
website_description = serializers.SerializerMethodField()
|
||||||
@@ -82,6 +84,12 @@ class BookmarkSerializer(serializers.ModelSerializer):
|
|||||||
preview_image_url = request.build_absolute_uri(preview_image_file_path)
|
preview_image_url = request.build_absolute_uri(preview_image_file_path)
|
||||||
return preview_image_url
|
return preview_image_url
|
||||||
|
|
||||||
|
def get_web_archive_snapshot_url(self, obj: Bookmark):
|
||||||
|
if obj.web_archive_snapshot_url:
|
||||||
|
return obj.web_archive_snapshot_url
|
||||||
|
|
||||||
|
return generate_fallback_webarchive_url(obj.url, obj.date_added)
|
||||||
|
|
||||||
def get_website_title(self, obj: Bookmark):
|
def get_website_title(self, obj: Bookmark):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@@ -93,6 +93,19 @@ class Bookmark(models.Model):
|
|||||||
return self.resolved_title + " (" + self.url[:30] + "...)"
|
return self.resolved_title + " (" + self.url[:30] + "...)"
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(post_delete, sender=Bookmark)
|
||||||
|
def bookmark_deleted(sender, instance, **kwargs):
|
||||||
|
if instance.preview_image_file:
|
||||||
|
filepath = os.path.join(settings.LD_PREVIEW_FOLDER, instance.preview_image_file)
|
||||||
|
if os.path.isfile(filepath):
|
||||||
|
try:
|
||||||
|
os.remove(filepath)
|
||||||
|
except Exception as error:
|
||||||
|
logger.error(
|
||||||
|
f"Failed to delete preview image: {filepath}", exc_info=error
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class BookmarkAsset(models.Model):
|
class BookmarkAsset(models.Model):
|
||||||
TYPE_SNAPSHOT = "snapshot"
|
TYPE_SNAPSHOT = "snapshot"
|
||||||
TYPE_UPLOAD = "upload"
|
TYPE_UPLOAD = "upload"
|
||||||
|
@@ -28,7 +28,7 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<a href="{% url 'bookmarks:settings.index' %}" class="btn btn-link">Settings</a>
|
<a href="{% url 'bookmarks:settings.index' %}" class="btn btn-link">Settings</a>
|
||||||
<form class="d-inline" action="{% url 'logout' %}" method="post">
|
<form class="d-inline" action="{% url 'logout' %}" method="post" data-turbo="false">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<button type="submit" class="btn btn-link">Logout</button>
|
<button type="submit" class="btn btn-link">Logout</button>
|
||||||
</form>
|
</form>
|
||||||
@@ -72,7 +72,7 @@
|
|||||||
<a href="{% url 'bookmarks:settings.index' %}" class="menu-link">Settings</a>
|
<a href="{% url 'bookmarks:settings.index' %}" class="menu-link">Settings</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="menu-item">
|
<li class="menu-item">
|
||||||
<form class="d-inline" action="{% url 'logout' %}" method="post">
|
<form class="d-inline" action="{% url 'logout' %}" method="post" data-turbo="false">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<button type="submit" class="btn btn-link menu-link">Logout</button>
|
<button type="submit" class="btn btn-link menu-link">Logout</button>
|
||||||
</form>
|
</form>
|
||||||
|
@@ -1,25 +1,25 @@
|
|||||||
import os
|
import os
|
||||||
|
import shutil
|
||||||
|
import tempfile
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.test import TestCase
|
from django.test import TestCase, override_settings
|
||||||
|
|
||||||
from bookmarks.tests.helpers import (
|
|
||||||
BookmarkFactoryMixin,
|
|
||||||
)
|
|
||||||
from bookmarks.services import bookmarks
|
from bookmarks.services import bookmarks
|
||||||
|
from bookmarks.tests.helpers import BookmarkFactoryMixin
|
||||||
|
|
||||||
|
|
||||||
class BookmarkAssetsTestCase(TestCase, BookmarkFactoryMixin):
|
class BookmarkAssetsTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
|
def setUp(self):
|
||||||
|
self.temp_dir = tempfile.mkdtemp()
|
||||||
|
self.override = override_settings(LD_ASSET_FOLDER=self.temp_dir)
|
||||||
|
self.override.enable()
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
temp_files = [
|
self.override.disable()
|
||||||
f for f in os.listdir(settings.LD_ASSET_FOLDER) if f.startswith("temp")
|
shutil.rmtree(self.temp_dir)
|
||||||
]
|
|
||||||
for temp_file in temp_files:
|
|
||||||
os.remove(os.path.join(settings.LD_ASSET_FOLDER, temp_file))
|
|
||||||
|
|
||||||
def setup_asset_file(self, filename):
|
def setup_asset_file(self, filename):
|
||||||
if not os.path.exists(settings.LD_ASSET_FOLDER):
|
|
||||||
os.makedirs(settings.LD_ASSET_FOLDER)
|
|
||||||
filepath = os.path.join(settings.LD_ASSET_FOLDER, filename)
|
filepath = os.path.join(settings.LD_ASSET_FOLDER, filename)
|
||||||
with open(filepath, "w") as f:
|
with open(filepath, "w") as f:
|
||||||
f.write("test")
|
f.write("test")
|
||||||
|
70
bookmarks/tests/test_bookmark_previews.py
Normal file
70
bookmarks/tests/test_bookmark_previews.py
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.test import TestCase, override_settings
|
||||||
|
|
||||||
|
from bookmarks.services import bookmarks
|
||||||
|
from bookmarks.tests.helpers import BookmarkFactoryMixin
|
||||||
|
|
||||||
|
|
||||||
|
class BookmarkPreviewsTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
|
def setUp(self):
|
||||||
|
self.temp_dir = tempfile.mkdtemp()
|
||||||
|
self.override = override_settings(LD_PREVIEW_FOLDER=self.temp_dir)
|
||||||
|
self.override.enable()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
self.override.disable()
|
||||||
|
shutil.rmtree(self.temp_dir)
|
||||||
|
|
||||||
|
def setup_preview_file(self, filename):
|
||||||
|
filepath = os.path.join(settings.LD_PREVIEW_FOLDER, filename)
|
||||||
|
with open(filepath, "w") as f:
|
||||||
|
f.write("test")
|
||||||
|
|
||||||
|
def setup_bookmark_with_preview(self):
|
||||||
|
bookmark = self.setup_bookmark()
|
||||||
|
bookmark.preview_image_file = f"preview_{bookmark.id}.jpg"
|
||||||
|
bookmark.save()
|
||||||
|
self.setup_preview_file(bookmark.preview_image_file)
|
||||||
|
return bookmark
|
||||||
|
|
||||||
|
def assertPreviewImageExists(self, bookmark):
|
||||||
|
self.assertTrue(
|
||||||
|
os.path.exists(
|
||||||
|
os.path.join(settings.LD_PREVIEW_FOLDER, bookmark.preview_image_file)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def assertPreviewImageDoesNotExist(self, bookmark):
|
||||||
|
self.assertFalse(
|
||||||
|
os.path.exists(
|
||||||
|
os.path.join(settings.LD_PREVIEW_FOLDER, bookmark.preview_image_file)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_delete_bookmark_deletes_preview_image(self):
|
||||||
|
bookmark = self.setup_bookmark_with_preview()
|
||||||
|
self.assertPreviewImageExists(bookmark)
|
||||||
|
|
||||||
|
bookmark.delete()
|
||||||
|
self.assertPreviewImageDoesNotExist(bookmark)
|
||||||
|
|
||||||
|
def test_bulk_delete_bookmarks_deletes_preview_images(self):
|
||||||
|
bookmark1 = self.setup_bookmark_with_preview()
|
||||||
|
bookmark2 = self.setup_bookmark_with_preview()
|
||||||
|
bookmark3 = self.setup_bookmark_with_preview()
|
||||||
|
|
||||||
|
self.assertPreviewImageExists(bookmark1)
|
||||||
|
self.assertPreviewImageExists(bookmark2)
|
||||||
|
self.assertPreviewImageExists(bookmark3)
|
||||||
|
|
||||||
|
bookmarks.delete_bookmarks(
|
||||||
|
[bookmark1.id, bookmark2.id, bookmark3.id], self.get_or_create_test_user()
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertPreviewImageDoesNotExist(bookmark1)
|
||||||
|
self.assertPreviewImageDoesNotExist(bookmark2)
|
||||||
|
self.assertPreviewImageDoesNotExist(bookmark3)
|
@@ -1,15 +1,18 @@
|
|||||||
|
import datetime
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
from django.utils import timezone
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.authtoken.models import Token
|
from rest_framework.authtoken.models import Token
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from bookmarks.models import Bookmark, BookmarkSearch, UserProfile
|
from bookmarks.models import Bookmark, BookmarkSearch, UserProfile
|
||||||
from bookmarks.services import website_loader
|
from bookmarks.services import website_loader
|
||||||
|
from bookmarks.services.wayback import generate_fallback_webarchive_url
|
||||||
from bookmarks.services.website_loader import WebsiteMetadata
|
from bookmarks.services.website_loader import WebsiteMetadata
|
||||||
from bookmarks.tests.helpers import LinkdingApiTestCase, BookmarkFactoryMixin
|
from bookmarks.tests.helpers import LinkdingApiTestCase, BookmarkFactoryMixin
|
||||||
|
|
||||||
@@ -33,7 +36,10 @@ class BookmarksApiTestCase(LinkdingApiTestCase, BookmarkFactoryMixin):
|
|||||||
expectation["title"] = bookmark.title
|
expectation["title"] = bookmark.title
|
||||||
expectation["description"] = bookmark.description
|
expectation["description"] = bookmark.description
|
||||||
expectation["notes"] = bookmark.notes
|
expectation["notes"] = bookmark.notes
|
||||||
expectation["web_archive_snapshot_url"] = bookmark.web_archive_snapshot_url
|
expectation["web_archive_snapshot_url"] = (
|
||||||
|
bookmark.web_archive_snapshot_url
|
||||||
|
or generate_fallback_webarchive_url(bookmark.url, bookmark.date_added)
|
||||||
|
)
|
||||||
expectation["favicon_url"] = (
|
expectation["favicon_url"] = (
|
||||||
f"http://testserver/static/{bookmark.favicon_file}"
|
f"http://testserver/static/{bookmark.favicon_file}"
|
||||||
if bookmark.favicon_file
|
if bookmark.favicon_file
|
||||||
@@ -590,6 +596,23 @@ class BookmarksApiTestCase(LinkdingApiTestCase, BookmarkFactoryMixin):
|
|||||||
response = self.get(url, expected_status_code=status.HTTP_200_OK)
|
response = self.get(url, expected_status_code=status.HTTP_200_OK)
|
||||||
self.assertBookmarkListEqual([response.data], [bookmark])
|
self.assertBookmarkListEqual([response.data], [bookmark])
|
||||||
|
|
||||||
|
def test_get_bookmark_returns_fallback_webarchive_url(self):
|
||||||
|
self.authenticate()
|
||||||
|
bookmark = self.setup_bookmark(
|
||||||
|
web_archive_snapshot_url="",
|
||||||
|
url="https://example.com/",
|
||||||
|
added=timezone.datetime(
|
||||||
|
2023, 8, 11, 21, 45, 11, tzinfo=datetime.timezone.utc
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
url = reverse("bookmarks:bookmark-detail", args=[bookmark.id])
|
||||||
|
response = self.get(url, expected_status_code=status.HTTP_200_OK)
|
||||||
|
self.assertEqual(
|
||||||
|
response.data["web_archive_snapshot_url"],
|
||||||
|
"https://web.archive.org/web/20230811214511/https://example.com/",
|
||||||
|
)
|
||||||
|
|
||||||
def test_update_bookmark(self):
|
def test_update_bookmark(self):
|
||||||
self.authenticate()
|
self.authenticate()
|
||||||
bookmark = self.setup_bookmark()
|
bookmark = self.setup_bookmark()
|
||||||
|
@@ -10,7 +10,7 @@ COPY bookmarks/styles ./bookmarks/styles
|
|||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
|
|
||||||
FROM python:3.12.8-alpine3.21 AS build-deps
|
FROM python:3.12.9-alpine3.21 AS build-deps
|
||||||
# Add required packages
|
# Add required packages
|
||||||
# alpine-sdk linux-headers pkgconfig: build Python packages from source
|
# alpine-sdk linux-headers pkgconfig: build Python packages from source
|
||||||
# libpq-dev: build Postgres client from source
|
# libpq-dev: build Postgres client from source
|
||||||
@@ -49,7 +49,7 @@ RUN wget https://www.sqlite.org/${SQLITE_RELEASE_YEAR}/sqlite-amalgamation-${SQL
|
|||||||
gcc -fPIC -shared icu.c `pkg-config --libs --cflags icu-uc icu-io` -o libicu.so
|
gcc -fPIC -shared icu.c `pkg-config --libs --cflags icu-uc icu-io` -o libicu.so
|
||||||
|
|
||||||
|
|
||||||
FROM python:3.12.8-alpine3.21 AS linkding
|
FROM python:3.12.9-alpine3.21 AS linkding
|
||||||
LABEL org.opencontainers.image.source="https://github.com/sissbruecker/linkding"
|
LABEL org.opencontainers.image.source="https://github.com/sissbruecker/linkding"
|
||||||
# install runtime dependencies
|
# install runtime dependencies
|
||||||
RUN apk update && apk add bash curl icu libpq mailcap libssl3
|
RUN apk update && apk add bash curl icu libpq mailcap libssl3
|
||||||
@@ -73,6 +73,8 @@ ENV PATH=/opt/venv/bin:$PATH
|
|||||||
RUN mkdir data && \
|
RUN mkdir data && \
|
||||||
python manage.py collectstatic
|
python manage.py collectstatic
|
||||||
|
|
||||||
|
# Limit file descriptors used by uwsgi, see https://github.com/sissbruecker/linkding/issues/453
|
||||||
|
ENV UWSGI_MAX_FD=4096
|
||||||
# Expose uwsgi server at port 9090
|
# Expose uwsgi server at port 9090
|
||||||
EXPOSE 9090
|
EXPOSE 9090
|
||||||
# Allow running containers as an an arbitrary user in the root group, to support deployment scenarios like OpenShift, Podman
|
# Allow running containers as an an arbitrary user in the root group, to support deployment scenarios like OpenShift, Podman
|
||||||
|
@@ -10,7 +10,7 @@ COPY bookmarks/styles ./bookmarks/styles
|
|||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
|
|
||||||
FROM python:3.12.8-slim-bookworm AS build-deps
|
FROM python:3.12.9-slim-bookworm AS build-deps
|
||||||
# Add required packages
|
# Add required packages
|
||||||
# build-essential pkg-config: build Python packages from source
|
# build-essential pkg-config: build Python packages from source
|
||||||
# libpq-dev: build Postgres client from source
|
# libpq-dev: build Postgres client from source
|
||||||
@@ -51,7 +51,7 @@ RUN wget https://www.sqlite.org/${SQLITE_RELEASE_YEAR}/sqlite-amalgamation-${SQL
|
|||||||
gcc -fPIC -shared icu.c `pkg-config --libs --cflags icu-uc icu-io` -o libicu.so
|
gcc -fPIC -shared icu.c `pkg-config --libs --cflags icu-uc icu-io` -o libicu.so
|
||||||
|
|
||||||
|
|
||||||
FROM python:3.12.8-slim-bookworm AS linkding
|
FROM python:3.12.9-slim-bookworm AS linkding
|
||||||
LABEL org.opencontainers.image.source="https://github.com/sissbruecker/linkding"
|
LABEL org.opencontainers.image.source="https://github.com/sissbruecker/linkding"
|
||||||
# install runtime dependencies
|
# install runtime dependencies
|
||||||
RUN apt-get update && apt-get -y install mime-support libpq-dev libicu-dev libssl3 curl
|
RUN apt-get update && apt-get -y install mime-support libpq-dev libicu-dev libssl3 curl
|
||||||
@@ -71,6 +71,8 @@ ENV PATH=/opt/venv/bin:$PATH
|
|||||||
RUN mkdir data && \
|
RUN mkdir data && \
|
||||||
python manage.py collectstatic
|
python manage.py collectstatic
|
||||||
|
|
||||||
|
# Limit file descriptors used by uwsgi, see https://github.com/sissbruecker/linkding/issues/453
|
||||||
|
ENV UWSGI_MAX_FD=4096
|
||||||
# Expose uwsgi server at port 9090
|
# Expose uwsgi server at port 9090
|
||||||
EXPOSE 9090
|
EXPOSE 9090
|
||||||
# Allow running containers as an an arbitrary user in the root group, to support deployment scenarios like OpenShift, Podman
|
# Allow running containers as an an arbitrary user in the root group, to support deployment scenarios like OpenShift, Podman
|
||||||
|
@@ -19,7 +19,7 @@ For multiple options, use one `-e` argument per option.
|
|||||||
|
|
||||||
### Docker-compose
|
### Docker-compose
|
||||||
|
|
||||||
For docker-compose options are configured using an `.env` file.
|
For docker-compose options are configured using an `.env` file.
|
||||||
Follow the docker-compose setup in the README and copy `.env.sample` to `.env`. Then modify the options in `.env`.
|
Follow the docker-compose setup in the README and copy `.env.sample` to `.env`. Then modify the options in `.env`.
|
||||||
|
|
||||||
## List of options
|
## List of options
|
||||||
@@ -106,10 +106,10 @@ Values: `True`, `False` | Default = `False`
|
|||||||
Enables support for OpenID Connect (OIDC) authentication, allowing to use single sign-on (SSO) with OIDC providers.
|
Enables support for OpenID Connect (OIDC) authentication, allowing to use single sign-on (SSO) with OIDC providers.
|
||||||
When enabled, this shows a button on the login page that allows users to authenticate using an OIDC provider.
|
When enabled, this shows a button on the login page that allows users to authenticate using an OIDC provider.
|
||||||
Users are associated by the email address provided from the OIDC provider, which is by default also used as username in linkding. You can configure a custom claim to be used as username with `OIDC_USERNAME_CLAIM`.
|
Users are associated by the email address provided from the OIDC provider, which is by default also used as username in linkding. You can configure a custom claim to be used as username with `OIDC_USERNAME_CLAIM`.
|
||||||
If there is no user with that email address as username, a new user is created automatically.
|
If there is no user with that email address as username, a new user is created automatically.
|
||||||
|
|
||||||
This requires configuring a number of options, which of those you need depends on which OIDC provider you use and how it is configured.
|
This requires configuring a number of options, which of those you need depends on which OIDC provider you use and how it is configured.
|
||||||
In general, you should find the required information in the UI of your OIDC provider, or its documentation.
|
In general, you should find the required information in the UI of your OIDC provider, or its documentation.
|
||||||
|
|
||||||
The options are adopted from the [mozilla-django-oidc](https://mozilla-django-oidc.readthedocs.io/en/stable/) library, which is used by linkding for OIDC support.
|
The options are adopted from the [mozilla-django-oidc](https://mozilla-django-oidc.readthedocs.io/en/stable/) library, which is used by linkding for OIDC support.
|
||||||
Please check their documentation for more information on the options.
|
Please check their documentation for more information on the options.
|
||||||
@@ -127,6 +127,13 @@ The following options can be configured:
|
|||||||
- `OIDC_RP_SCOPES` - Scopes asked for on the authorization flow. Default is `oidc email profile`.
|
- `OIDC_RP_SCOPES` - Scopes asked for on the authorization flow. Default is `oidc email profile`.
|
||||||
- `OIDC_USERNAME_CLAIM` - A custom claim to used as username for new accounts, for example `preferred_username`. If the configured claim does not exist or is empty, the email claim is used as fallback. Default is `email`.
|
- `OIDC_USERNAME_CLAIM` - A custom claim to used as username for new accounts, for example `preferred_username`. If the configured claim does not exist or is empty, the email claim is used as fallback. Default is `email`.
|
||||||
|
|
||||||
|
#### `OIDC` and `LD_SUPERUSER_NAME`
|
||||||
|
|
||||||
|
As noted above, OIDC matches users by email address, but `LD_SUPERUSER_NAME` will only set the username.
|
||||||
|
Instead of setting `LD_SUPERUSER_NAME` it is recommended that you use the method described in [User setup](/installation#user-setup) to configure a superuser with both username and email address.
|
||||||
|
This way when OIDC searches for a matching user it will find the superuser account you created.
|
||||||
|
Note that you should create the superuser **before** logging in with OIDC for the first time.
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
|
|
||||||
<summary>Authelia Example</summary>
|
<summary>Authelia Example</summary>
|
||||||
@@ -200,7 +207,7 @@ All the other database variables below are only required for configured Postgres
|
|||||||
|
|
||||||
Values: `String` | Default = `linkding`
|
Values: `String` | Default = `linkding`
|
||||||
|
|
||||||
The name of the database.
|
The name of the database.
|
||||||
|
|
||||||
### `LD_DB_USER`
|
### `LD_DB_USER`
|
||||||
|
|
||||||
@@ -260,7 +267,7 @@ Alternative favicon providers:
|
|||||||
Values: `Float` | Default = 60.0
|
Values: `Float` | Default = 60.0
|
||||||
|
|
||||||
When creating HTML archive snapshots, control the timeout for how long to wait for the snapshot to complete, in `seconds`.
|
When creating HTML archive snapshots, control the timeout for how long to wait for the snapshot to complete, in `seconds`.
|
||||||
Defaults to 60 seconds; on lower-powered hardware you may need to increase this value.
|
Defaults to 60 seconds; on lower-powered hardware you may need to increase this value.
|
||||||
|
|
||||||
### `LD_SINGLEFILE_OPTIONS`
|
### `LD_SINGLEFILE_OPTIONS`
|
||||||
|
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "linkding",
|
"name": "linkding",
|
||||||
"version": "1.38.0",
|
"version": "1.38.1",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@@ -1 +1 @@
|
|||||||
1.38.0
|
1.38.1
|
||||||
|
Reference in New Issue
Block a user