Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 724a4ef3df | |||
| 4f2c4bfaf9 | |||
| dacf705329 | |||
| 8296738347 | |||
| 04be495351 | |||
| 43185605eb | |||
| cf3fb208e2 | |||
| 4ca230eb12 | |||
| 4b5e0cb2a3 | |||
| 45f374610f | |||
| b419a2bfd2 | |||
| ee8d96bee7 | |||
| c65ff2d75d | |||
| ea1c90d8ca | |||
| 3e963e3e6d | |||
| 82769128a1 | |||
| 05f4ce6b2e | |||
| 508984ecfa | |||
| a05507f73d | |||
| a27def119c | |||
| fce94f38ce | |||
| 83bd9c5ce3 | |||
| bfbfade99a | |||
| 16daf7b8d3 | |||
| fcad79d662 |
@@ -48,12 +48,12 @@ jobs:
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_HUB_USER }}
|
||||
password: ${{ secrets.DOCKER_HUB_PASSWORD }}
|
||||
-
|
||||
name: Run trivy scan
|
||||
uses: numerique-gouv/action-trivy-cache@main
|
||||
with:
|
||||
docker-build-args: '--target backend-production -f Dockerfile'
|
||||
docker-image-name: '${{ env.DOCKER_CONTAINER_REGISTRY_HOSTNAME }}/${{ env.DOCKER_CONTAINER_REGISTRY_NAMESPACE }}/meet-backend:${{ github.sha }}'
|
||||
# -
|
||||
# name: Run trivy scan
|
||||
# uses: numerique-gouv/action-trivy-cache@main
|
||||
# with:
|
||||
# docker-build-args: '--target backend-production -f Dockerfile'
|
||||
# docker-image-name: '${{ env.DOCKER_CONTAINER_REGISTRY_HOSTNAME }}/${{ env.DOCKER_CONTAINER_REGISTRY_NAMESPACE }}/meet-backend:${{ github.sha }}'
|
||||
-
|
||||
name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
@@ -93,12 +93,12 @@ jobs:
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_HUB_USER }}
|
||||
password: ${{ secrets.DOCKER_HUB_PASSWORD }}
|
||||
-
|
||||
name: Run trivy scan
|
||||
uses: numerique-gouv/action-trivy-cache@main
|
||||
with:
|
||||
docker-build-args: '-f src/frontend/Dockerfile --target frontend-production'
|
||||
docker-image-name: '${{ env.DOCKER_CONTAINER_REGISTRY_HOSTNAME }}/${{ env.DOCKER_CONTAINER_REGISTRY_NAMESPACE }}/meet-frontend:${{ github.sha }}'
|
||||
# -
|
||||
# name: Run trivy scan
|
||||
# uses: numerique-gouv/action-trivy-cache@main
|
||||
# with:
|
||||
# docker-build-args: '-f src/frontend/Dockerfile --target frontend-production'
|
||||
# docker-image-name: '${{ env.DOCKER_CONTAINER_REGISTRY_HOSTNAME }}/${{ env.DOCKER_CONTAINER_REGISTRY_NAMESPACE }}/meet-frontend:${{ github.sha }}'
|
||||
-
|
||||
name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
@@ -139,12 +139,12 @@ jobs:
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_HUB_USER }}
|
||||
password: ${{ secrets.DOCKER_HUB_PASSWORD }}
|
||||
-
|
||||
name: Run trivy scan
|
||||
uses: numerique-gouv/action-trivy-cache@main
|
||||
with:
|
||||
docker-build-args: '-f docker/dinum-frontend/Dockerfile --target frontend-production'
|
||||
docker-image-name: '${{ env.DOCKER_CONTAINER_REGISTRY_HOSTNAME }}/${{ env.DOCKER_CONTAINER_REGISTRY_NAMESPACE }}/meet-frontend-dinum:${{ github.sha }}'
|
||||
# -
|
||||
# name: Run trivy scan
|
||||
# uses: numerique-gouv/action-trivy-cache@main
|
||||
# with:
|
||||
# docker-build-args: '-f docker/dinum-frontend/Dockerfile --target frontend-production'
|
||||
# docker-image-name: '${{ env.DOCKER_CONTAINER_REGISTRY_HOSTNAME }}/${{ env.DOCKER_CONTAINER_REGISTRY_NAMESPACE }}/meet-frontend-dinum:${{ github.sha }}'
|
||||
-
|
||||
name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
@@ -185,13 +185,13 @@ jobs:
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_HUB_USER }}
|
||||
password: ${{ secrets.DOCKER_HUB_PASSWORD }}
|
||||
-
|
||||
name: Run trivy scan
|
||||
uses: numerique-gouv/action-trivy-cache@main
|
||||
continue-on-error: true
|
||||
with:
|
||||
docker-build-args: '-f src/summary/Dockerfile --target production'
|
||||
docker-image-name: '${{ env.DOCKER_CONTAINER_REGISTRY_HOSTNAME }}/${{ env.DOCKER_CONTAINER_REGISTRY_NAMESPACE }}/meet-summary:${{ github.sha }}'
|
||||
# -
|
||||
# name: Run trivy scan
|
||||
# uses: numerique-gouv/action-trivy-cache@main
|
||||
# continue-on-error: true
|
||||
# with:
|
||||
# docker-build-args: '-f src/summary/Dockerfile --target production'
|
||||
# docker-image-name: '${{ env.DOCKER_CONTAINER_REGISTRY_HOSTNAME }}/${{ env.DOCKER_CONTAINER_REGISTRY_NAMESPACE }}/meet-summary:${{ github.sha }}'
|
||||
docker-context: './src/summary'
|
||||
-
|
||||
name: Build and push
|
||||
@@ -233,14 +233,14 @@ jobs:
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_HUB_USER }}
|
||||
password: ${{ secrets.DOCKER_HUB_PASSWORD }}
|
||||
-
|
||||
name: Run trivy scan
|
||||
uses: numerique-gouv/action-trivy-cache@main
|
||||
continue-on-error: true
|
||||
with:
|
||||
docker-build-args: '-f src/agents/Dockerfile --target production'
|
||||
docker-image-name: '${{ env.DOCKER_CONTAINER_REGISTRY_HOSTNAME }}/${{ env.DOCKER_CONTAINER_REGISTRY_NAMESPACE }}/meet-agents:${{ github.sha }}'
|
||||
docker-context: './src/agents'
|
||||
# -
|
||||
# name: Run trivy scan
|
||||
# uses: numerique-gouv/action-trivy-cache@main
|
||||
# continue-on-error: true
|
||||
# with:
|
||||
# docker-build-args: '-f src/agents/Dockerfile --target production'
|
||||
# docker-image-name: '${{ env.DOCKER_CONTAINER_REGISTRY_HOSTNAME }}/${{ env.DOCKER_CONTAINER_REGISTRY_NAMESPACE }}/meet-agents:${{ github.sha }}'
|
||||
# docker-context: './src/agents'
|
||||
-
|
||||
name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
|
||||
@@ -29,7 +29,6 @@ and this project adheres to
|
||||
- 🔐(backend) avoids revealing the inactive status of an application #1135
|
||||
- ⚡️(helm) reduce initialDelaySeconds and add periods seconds #1139
|
||||
- 🔒️(backend) avoid information exposure through exception messages #1144
|
||||
- ⬆️(dependencies) update PyJWT to v2.12.0 [SECURITY] #1151
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -37,8 +36,6 @@ and this project adheres to
|
||||
- 🩹(backend) add page_size to pagination for room endpoints #1131
|
||||
- 🐛(backend) refactor lobby throttling to use participant id #1129
|
||||
- 🩹(backend) ignore non-recording uploads in storage webhook handler #1142
|
||||
- 🐛(frontend) fix dimension mismatch in BackgroundCustomProcessor #1116
|
||||
|
||||
|
||||
## [1.10.0] - 2026-03-05
|
||||
|
||||
|
||||
@@ -61,10 +61,11 @@ services:
|
||||
`docker compose up -d`
|
||||
```
|
||||
|
||||
Your keycloak instance is now available on https://id.yourdomain.tld
|
||||
Your keycloak instance is now available on https://doc.yourdomain.tld
|
||||
|
||||
> [!CAUTION]
|
||||
> Version of the images are set to latest, you should pin it to the desired version to avoid unwanted upgrades when pulling latest image. You can find available versions on [Keycloak registry](https://quay.io/repository/keycloak/keycloak?tab=tags).
|
||||
```
|
||||
|
||||
## Creating an OIDC Client for Meet Application
|
||||
|
||||
@@ -75,7 +76,7 @@ Your keycloak instance is now available on https://id.yourdomain.tld
|
||||
3. Enter the name of the realm - `meet`.
|
||||
4. Click "Create".
|
||||
|
||||
### Step 2: Create a New Client
|
||||
#### Step 2: Create a New Client
|
||||
|
||||
1. Navigate to the "Clients" tab.
|
||||
2. Click on the "Create client" button.
|
||||
@@ -85,7 +86,7 @@ Your keycloak instance is now available on https://id.yourdomain.tld
|
||||
1. Set the "Web Origins" to the URL of your meet application - e.g. `https://meet.example.com`.
|
||||
1. Click "Save".
|
||||
|
||||
### Step 3: Get Client Credentials
|
||||
#### Step 3: Get Client Credentials
|
||||
|
||||
1. Go to the "Credentials" tab.
|
||||
2. Copy the client ID (`meet` in this example) and the client secret.
|
||||
|
||||
@@ -73,3 +73,9 @@ class CreationCallbackAnonRateThrottle(MonitoredAnonRateThrottle):
|
||||
"""Throttle Anonymous user requesting room generation callback"""
|
||||
|
||||
scope = "creation_callback"
|
||||
|
||||
|
||||
class SessionExchangeAnonRateThrottle(MonitoredAnonRateThrottle):
|
||||
"""Throttle anonymous requests to the session exchange endpoint."""
|
||||
|
||||
scope = "session_exchange"
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
"""API endpoint for exchanging a one-time code for a session ID."""
|
||||
|
||||
import logging
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.cache import cache
|
||||
|
||||
from rest_framework import serializers, status
|
||||
from rest_framework.decorators import api_view, permission_classes, throttle_classes
|
||||
from rest_framework.permissions import AllowAny
|
||||
from rest_framework.response import Response
|
||||
|
||||
from core.api.throttling import SessionExchangeAnonRateThrottle
|
||||
|
||||
from .views import EXCHANGE_CODE_PREFIX
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SessionExchangeSerializer(serializers.Serializer):
|
||||
"""Validates the exchange code request."""
|
||||
|
||||
code = serializers.CharField(max_length=64, min_length=16)
|
||||
|
||||
|
||||
@api_view(["POST"])
|
||||
@permission_classes([AllowAny])
|
||||
@throttle_classes([SessionExchangeAnonRateThrottle])
|
||||
def session_exchange(
|
||||
request,
|
||||
): # NOSONAR (S3752) POST-only, AllowAny is intentional: single-use code with 30s TTL and rate limiting
|
||||
"""Exchange a one-time code for a session ID.
|
||||
|
||||
The code was generated during the OIDC callback and stored in cache
|
||||
with a short TTL. This endpoint retrieves the session ID, deletes the
|
||||
code (single-use), and returns the session ID to the native app.
|
||||
"""
|
||||
serializer = SessionExchangeSerializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
code = serializer.validated_data["code"]
|
||||
cache_key = f"{EXCHANGE_CODE_PREFIX}{code}"
|
||||
|
||||
session_key = cache.get(cache_key)
|
||||
if session_key is None:
|
||||
logger.warning(
|
||||
"Session exchange failed: invalid or expired code from %s",
|
||||
request.META.get("REMOTE_ADDR"),
|
||||
)
|
||||
return Response(
|
||||
{"detail": "Invalid or expired code."},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
# Delete immediately — single use
|
||||
cache.delete(cache_key)
|
||||
|
||||
cookie_name = getattr(settings, "SESSION_COOKIE_NAME", "sessionid")
|
||||
logger.info("Session exchange successful from %s", request.META.get("REMOTE_ADDR"))
|
||||
|
||||
return Response({cookie_name: session_key})
|
||||
@@ -0,0 +1,103 @@
|
||||
"""Custom OIDC authentication views for native app support.
|
||||
|
||||
When a native app (iOS, Android, Desktop) initiates OIDC login, it sets
|
||||
`returnTo` to a custom URL scheme (e.g. `visio://auth-callback`). After
|
||||
the OIDC flow completes, Django sets the session cookie and redirects to
|
||||
that URL. However, native apps cannot read browser cookies — they need
|
||||
the session ID passed explicitly.
|
||||
|
||||
Instead of exposing the session ID directly in the redirect URL, this
|
||||
module generates a short-lived, single-use exchange code. The native app
|
||||
then exchanges this code for the session ID via a dedicated API endpoint.
|
||||
"""
|
||||
|
||||
import uuid
|
||||
from urllib.parse import urlencode, urlparse
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.cache import cache
|
||||
from django.http import HttpResponseRedirect
|
||||
|
||||
from lasuite.oidc_login.views import (
|
||||
OIDCAuthenticationCallbackView as BaseCallbackView,
|
||||
OIDCAuthenticationRequestView as BaseRequestView,
|
||||
)
|
||||
|
||||
# Cache key prefix and TTL for exchange codes
|
||||
EXCHANGE_CODE_PREFIX = "auth_exchange:"
|
||||
EXCHANGE_CODE_TTL = 30 # seconds
|
||||
|
||||
|
||||
class NativeAppRedirect(HttpResponseRedirect):
|
||||
"""HttpResponseRedirect subclass that allows native app custom URL schemes.
|
||||
|
||||
Django's HttpResponseRedirect only allows http, https, and ftp schemes.
|
||||
Native apps use custom schemes (e.g. visio://) for deep links, which
|
||||
Django rejects with DisallowedRedirect. This subclass extends
|
||||
allowed_schemes with the configured native app schemes.
|
||||
"""
|
||||
|
||||
allowed_schemes = HttpResponseRedirect.allowed_schemes + list(
|
||||
getattr(settings, "NATIVE_APP_REDIRECT_SCHEMES", [])
|
||||
)
|
||||
|
||||
|
||||
class OIDCAuthenticationRequestView(BaseRequestView):
|
||||
"""Custom authenticate view that preserves native app returnTo in session.
|
||||
|
||||
mozilla-django-oidc's get_next_url() rejects custom URL schemes
|
||||
(e.g. visio://auth-callback) because url_has_allowed_host_and_scheme()
|
||||
only allows http/https. We intercept the returnTo parameter and store
|
||||
it directly in the session for whitelisted schemes, bypassing the
|
||||
safety check (which is not relevant for native app deep links).
|
||||
"""
|
||||
|
||||
def get(self, request):
|
||||
redirect_field = getattr(settings, "OIDC_REDIRECT_FIELD_NAME", "returnTo")
|
||||
return_to = request.GET.get(redirect_field, "")
|
||||
parsed = urlparse(return_to)
|
||||
allowed_schemes = getattr(settings, "NATIVE_APP_REDIRECT_SCHEMES", [])
|
||||
|
||||
response = super().get(request)
|
||||
|
||||
# Override oidc_login_next AFTER super() which set it to None
|
||||
# via get_next_url() rejecting the custom scheme.
|
||||
if parsed.scheme in allowed_schemes:
|
||||
request.session["oidc_login_next"] = return_to
|
||||
request.session.save()
|
||||
|
||||
return response
|
||||
|
||||
|
||||
class OIDCAuthenticationCallbackView(BaseCallbackView):
|
||||
"""Callback view that generates an exchange code for native app deep links."""
|
||||
|
||||
def login_success(self):
|
||||
"""After successful login, append exchange code for whitelisted scheme redirects."""
|
||||
# Temporarily remove native redirect from session to prevent
|
||||
# super().login_success() from raising DisallowedRedirect when
|
||||
# it tries HttpResponseRedirect with a custom scheme.
|
||||
native_redirect = self.request.session.pop("oidc_login_next", None)
|
||||
allowed_schemes = getattr(settings, "NATIVE_APP_REDIRECT_SCHEMES", [])
|
||||
parsed = urlparse(native_redirect or "")
|
||||
|
||||
if native_redirect and parsed.scheme in allowed_schemes:
|
||||
# Let super() redirect to the default URL (homepage)
|
||||
super().login_success()
|
||||
|
||||
# Generate a short-lived, single-use exchange code
|
||||
exchange_code = uuid.uuid4().hex
|
||||
session_key = self.request.session.session_key
|
||||
cache.set(
|
||||
f"{EXCHANGE_CODE_PREFIX}{exchange_code}",
|
||||
session_key,
|
||||
EXCHANGE_CODE_TTL,
|
||||
)
|
||||
|
||||
separator = "&" if parsed.query else "?"
|
||||
new_url = (
|
||||
f"{native_redirect}{separator}{urlencode({'code': exchange_code})}"
|
||||
)
|
||||
return NativeAppRedirect(new_url)
|
||||
|
||||
return super().login_success()
|
||||
@@ -0,0 +1,143 @@
|
||||
"""
|
||||
Tests for the session exchange API endpoint and OIDC callback view.
|
||||
"""
|
||||
|
||||
import uuid
|
||||
|
||||
from django.core.cache import cache
|
||||
|
||||
import pytest
|
||||
from rest_framework.test import APIClient
|
||||
|
||||
from core.authentication.views import EXCHANGE_CODE_PREFIX, EXCHANGE_CODE_TTL
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _clear_throttle_cache():
|
||||
"""Clear cache before each test to reset throttle counters."""
|
||||
cache.clear()
|
||||
|
||||
|
||||
def test_session_exchange_valid_code():
|
||||
"""A valid exchange code should return the session ID and be consumed."""
|
||||
code = uuid.uuid4().hex
|
||||
cache_key = f"{EXCHANGE_CODE_PREFIX}{code}"
|
||||
cache.set(cache_key, "test-session-key-123", EXCHANGE_CODE_TTL)
|
||||
|
||||
client = APIClient()
|
||||
response = client.post(
|
||||
"/api/v1.0/auth/session-exchange/",
|
||||
{"code": code},
|
||||
format="json",
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert "test-session-key-123" in data.values()
|
||||
|
||||
# Code should be consumed (single-use)
|
||||
assert cache.get(cache_key) is None
|
||||
|
||||
|
||||
def test_session_exchange_invalid_code():
|
||||
"""An invalid/unknown code should return 400."""
|
||||
client = APIClient()
|
||||
response = client.post(
|
||||
"/api/v1.0/auth/session-exchange/",
|
||||
{"code": uuid.uuid4().hex},
|
||||
format="json",
|
||||
)
|
||||
|
||||
assert response.status_code == 400
|
||||
assert response.json()["detail"] == "Invalid or expired code."
|
||||
|
||||
|
||||
def test_session_exchange_expired_code():
|
||||
"""An expired code should return 400."""
|
||||
code = uuid.uuid4().hex
|
||||
cache_key = f"{EXCHANGE_CODE_PREFIX}{code}"
|
||||
# Set with 0 TTL to simulate expiration
|
||||
cache.set(cache_key, "expired-session", 0)
|
||||
|
||||
client = APIClient()
|
||||
response = client.post(
|
||||
"/api/v1.0/auth/session-exchange/",
|
||||
{"code": code},
|
||||
format="json",
|
||||
)
|
||||
|
||||
assert response.status_code == 400
|
||||
|
||||
|
||||
def test_session_exchange_code_too_short():
|
||||
"""A code that's too short should be rejected by validation."""
|
||||
client = APIClient()
|
||||
response = client.post(
|
||||
"/api/v1.0/auth/session-exchange/",
|
||||
{"code": "short"},
|
||||
format="json",
|
||||
)
|
||||
|
||||
assert response.status_code == 400
|
||||
|
||||
|
||||
def test_session_exchange_missing_code():
|
||||
"""Missing code field should be rejected."""
|
||||
client = APIClient()
|
||||
response = client.post(
|
||||
"/api/v1.0/auth/session-exchange/",
|
||||
{},
|
||||
format="json",
|
||||
)
|
||||
|
||||
assert response.status_code == 400
|
||||
|
||||
|
||||
def test_session_exchange_replay_attack():
|
||||
"""Using the same code twice should fail the second time."""
|
||||
code = uuid.uuid4().hex
|
||||
cache.set(f"{EXCHANGE_CODE_PREFIX}{code}", "session-123", EXCHANGE_CODE_TTL)
|
||||
|
||||
client = APIClient()
|
||||
|
||||
# First use succeeds
|
||||
response = client.post(
|
||||
"/api/v1.0/auth/session-exchange/",
|
||||
{"code": code},
|
||||
format="json",
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
# Second use fails
|
||||
response = client.post(
|
||||
"/api/v1.0/auth/session-exchange/",
|
||||
{"code": code},
|
||||
format="json",
|
||||
)
|
||||
assert response.status_code == 400
|
||||
|
||||
|
||||
def test_session_exchange_returns_correct_cookie_name(settings):
|
||||
"""The response key should match SESSION_COOKIE_NAME from settings."""
|
||||
settings.SESSION_COOKIE_NAME = "meet_sessionid"
|
||||
code = uuid.uuid4().hex
|
||||
cache.set(f"{EXCHANGE_CODE_PREFIX}{code}", "my-session", EXCHANGE_CODE_TTL)
|
||||
|
||||
client = APIClient()
|
||||
response = client.post(
|
||||
"/api/v1.0/auth/session-exchange/",
|
||||
{"code": code},
|
||||
format="json",
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"meet_sessionid": "my-session"}
|
||||
|
||||
|
||||
def test_session_exchange_get_not_allowed():
|
||||
"""GET method should not be allowed on the exchange endpoint."""
|
||||
client = APIClient()
|
||||
response = client.get("/api/v1.0/auth/session-exchange/")
|
||||
assert response.status_code == 405
|
||||
@@ -7,6 +7,7 @@ from lasuite.oidc_login.urls import urlpatterns as oidc_urls
|
||||
from rest_framework.routers import DefaultRouter
|
||||
|
||||
from core.api import get_frontend_configuration, viewsets
|
||||
from core.authentication.api import session_exchange
|
||||
from core.external_api import viewsets as external_viewsets
|
||||
|
||||
# - Main endpoints
|
||||
@@ -40,6 +41,9 @@ urlpatterns = [
|
||||
[
|
||||
*router.urls,
|
||||
*oidc_urls,
|
||||
path(
|
||||
"auth/session-exchange/", session_exchange, name="session_exchange"
|
||||
),
|
||||
path("config/", get_frontend_configuration, name="config"),
|
||||
]
|
||||
),
|
||||
|
||||
@@ -344,6 +344,11 @@ class Base(Configuration):
|
||||
environ_name="CREATION_CALLBACK_THROTTLE_RATES",
|
||||
environ_prefix=None,
|
||||
),
|
||||
"session_exchange": values.Value(
|
||||
default="5/minute",
|
||||
environ_name="SESSION_EXCHANGE_THROTTLE_RATES",
|
||||
environ_prefix=None,
|
||||
),
|
||||
},
|
||||
}
|
||||
MONITORED_THROTTLE_FAILURE_CALLBACK = (
|
||||
@@ -459,8 +464,16 @@ class Base(Configuration):
|
||||
)
|
||||
|
||||
# OIDC - Authorization Code Flow
|
||||
OIDC_AUTHENTICATE_CLASS = "lasuite.oidc_login.views.OIDCAuthenticationRequestView"
|
||||
OIDC_CALLBACK_CLASS = "lasuite.oidc_login.views.OIDCAuthenticationCallbackView"
|
||||
OIDC_AUTHENTICATE_CLASS = "core.authentication.views.OIDCAuthenticationRequestView"
|
||||
OIDC_CALLBACK_CLASS = "core.authentication.views.OIDCAuthenticationCallbackView"
|
||||
|
||||
# Custom URL schemes allowed for native app OIDC redirects.
|
||||
# Only these schemes will receive an exchange code in the callback.
|
||||
NATIVE_APP_REDIRECT_SCHEMES = values.ListValue(
|
||||
default=["visio"],
|
||||
environ_name="NATIVE_APP_REDIRECT_SCHEMES",
|
||||
environ_prefix=None,
|
||||
)
|
||||
OIDC_CREATE_USER = values.BooleanValue(
|
||||
default=True, environ_name="OIDC_CREATE_USER", environ_prefix=None
|
||||
)
|
||||
|
||||
@@ -52,7 +52,7 @@ dependencies = [
|
||||
"nested-multipart-parser==1.6.0",
|
||||
"psycopg[binary]==3.3.2",
|
||||
"pydantic==2.12.4",
|
||||
"PyJWT==2.12.0",
|
||||
"PyJWT==2.11.0",
|
||||
"python-frontmatter==1.1.0",
|
||||
"python-magic==0.4.27",
|
||||
"requests==2.32.5",
|
||||
|
||||
Generated
+4
-4
@@ -1251,7 +1251,7 @@ requires-dist = [
|
||||
{ name = "nested-multipart-parser", specifier = "==1.6.0" },
|
||||
{ name = "psycopg", extras = ["binary"], specifier = "==3.3.2" },
|
||||
{ name = "pydantic", specifier = "==2.12.4" },
|
||||
{ name = "pyjwt", specifier = "==2.12.0" },
|
||||
{ name = "pyjwt", specifier = "==2.11.0" },
|
||||
{ name = "python-frontmatter", specifier = "==1.1.0" },
|
||||
{ name = "python-magic", specifier = "==0.4.27" },
|
||||
{ name = "redis", specifier = "==5.2.1" },
|
||||
@@ -1754,11 +1754,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "pyjwt"
|
||||
version = "2.12.0"
|
||||
version = "2.11.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a8/10/e8192be5f38f3e8e7e046716de4cae33d56fd5ae08927a823bb916be36c1/pyjwt-2.12.0.tar.gz", hash = "sha256:2f62390b667cd8257de560b850bb5a883102a388829274147f1d724453f8fb02", size = 102511, upload-time = "2026-03-12T17:15:30.831Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5c/5a/b46fa56bf322901eee5b0454a34343cdbdae202cd421775a8ee4e42fd519/pyjwt-2.11.0.tar.gz", hash = "sha256:35f95c1f0fbe5d5ba6e43f00271c275f7a1a4db1dab27bf708073b75318ea623", size = 98019, upload-time = "2026-01-30T19:59:55.694Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/15/70/70f895f404d363d291dcf62c12c85fdd47619ad9674ac0f53364d035925a/pyjwt-2.12.0-py3-none-any.whl", hash = "sha256:9bb459d1bdd0387967d287f5656bf7ec2b9a26645d1961628cda1764e087fd6e", size = 29700, upload-time = "2026-03-12T17:15:29.257Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6f/01/c26ce75ba460d5cd503da9e13b21a33804d38c2165dec7b716d06b13010c/pyjwt-2.11.0-py3-none-any.whl", hash = "sha256:94a6bde30eb5c8e04fee991062b534071fd1439ef58d2adc9ccb823e7bcd0469", size = 28224, upload-time = "2026-01-30T19:59:54.539Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
+1
-1
@@ -187,7 +187,7 @@ export class BackgroundCustomProcessor implements BackgroundProcessorInterface {
|
||||
0,
|
||||
0,
|
||||
PROCESSING_WIDTH,
|
||||
PROCESSING_HEIGHT
|
||||
PROCESSING_WIDTH
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user