diff --git a/src/backend/core/addons/__init__.py b/src/backend/core/addons/__init__.py
new file mode 100644
index 00000000..f784ffb1
--- /dev/null
+++ b/src/backend/core/addons/__init__.py
@@ -0,0 +1 @@
+"""Meet core add-ons module."""
diff --git a/src/backend/core/addons/service.py b/src/backend/core/addons/service.py
new file mode 100644
index 00000000..a3008888
--- /dev/null
+++ b/src/backend/core/addons/service.py
@@ -0,0 +1,124 @@
+"""Authentication session management for add-ons using temporary cache-based sessions."""
+
+import secrets
+from datetime import datetime, timedelta, timezone
+from enum import Enum
+from logging import getLogger
+
+from django.conf import settings
+from django.core.cache import cache
+from django.core.exceptions import SuspiciousOperation
+
+from core.models import User
+from core.services.jwt_token import JwtTokenService
+
+logger = getLogger(__name__)
+
+
+class SessionState(str, Enum):
+ """Add-on authentication session states."""
+
+ PENDING = "pending"
+ AUTHENTICATED = "authenticated"
+
+
+class TokenExchangeService:
+ """Manage temporary authentication sessions for add-on JWT token exchange."""
+
+ def __init__(self):
+ """Initialize the service with the configured token service."""
+
+ self._token_service = JwtTokenService(
+ secret_key=settings.ADDONS_JWT_SECRET_KEY,
+ algorithm=settings.ADDONS_JWT_ALG,
+ issuer=settings.ADDONS_JWT_ISSUER,
+ audience=settings.ADDONS_JWT_AUDIENCE,
+ expiration_seconds=settings.ADDONS_JWT_EXPIRATION_SECONDS,
+ token_type=settings.ADDONS_JWT_TOKEN_TYPE,
+ )
+
+ def _get_cache_key(self, session_id: str) -> str:
+ """Generate cache key for a session ID."""
+ return f"{settings.ADDONS_SESSION_KEY_PREFIX}_{session_id}"
+
+ def init_session(self) -> str:
+ """Create a new pending authentication session and return its ID."""
+
+ session_id = secrets.token_urlsafe(settings.ADDONS_SESSION_ID_LENGTH)
+ expires_at = datetime.now(timezone.utc) + timedelta(
+ seconds=settings.ADDONS_SESSION_TIMEOUT
+ )
+
+ session_data = {
+ "state": SessionState.PENDING,
+ "expires_at": expires_at.isoformat(),
+ }
+
+ cache_key = self._get_cache_key(session_id)
+ cache.set(
+ cache_key,
+ session_data,
+ timeout=settings.ADDONS_SESSION_TIMEOUT,
+ )
+
+ return session_id
+
+ def get_session(self, session_id: str) -> dict:
+ """Retrieve session data and clear it if authenticated."""
+
+ cache_key = self._get_cache_key(session_id)
+ data = cache.get(cache_key)
+
+ if not data:
+ return {}
+
+ if data.get("state") == SessionState.AUTHENTICATED:
+ self.clear_session(session_id)
+
+ # Return copy without internal fields
+ internal_fields = {"expires_at"}
+ return {k: v for k, v in data.items() if k not in internal_fields}
+
+ def clear_session(self, session_id: str) -> None:
+ """Remove session data from cache."""
+
+ cache_key = self._get_cache_key(session_id)
+ cache.delete(cache_key)
+
+ def set_access_token(self, user: User, session_id: str):
+ """Generate and store access token for an authenticated user session."""
+
+ cache_key = self._get_cache_key(session_id)
+ existing_data = cache.get(cache_key)
+
+ if not existing_data:
+ raise SuspiciousOperation("Session not found.")
+
+ expires_at = existing_data.get("expires_at", None)
+
+ if not expires_at:
+ self.clear_session(session_id)
+ raise SuspiciousOperation("Invalid session data.")
+
+ remaining_seconds = int(
+ (
+ datetime.fromisoformat(expires_at) - datetime.now(timezone.utc)
+ ).total_seconds()
+ )
+
+ if remaining_seconds <= 0:
+ self.clear_session(session_id)
+ raise SuspiciousOperation("Session expired.")
+
+ if existing_data.get("state") != SessionState.PENDING:
+ self.clear_session(session_id)
+ raise SuspiciousOperation("Access token already set.")
+
+ response = self._token_service.generate_jwt(user, settings.ADDONS_SCOPES)
+ new_data = {
+ **existing_data,
+ **response,
+ "state": SessionState.AUTHENTICATED,
+ }
+
+ cache.set(cache_key, new_data, timeout=remaining_seconds)
diff --git a/src/backend/core/addons/views.py b/src/backend/core/addons/views.py
new file mode 100644
index 00000000..7e594301
--- /dev/null
+++ b/src/backend/core/addons/views.py
@@ -0,0 +1,57 @@
+"""Add-ons views."""
+
+from django.conf import settings
+from django.core.exceptions import SuspiciousOperation
+from django.shortcuts import redirect, render
+from django.utils.translation import gettext_lazy as _
+from django.views.decorators.http import require_http_methods
+
+from core.addons.service import SessionState, TokenExchangeService
+
+
+def render_error(request, message, status=400):
+ """Render simple error page."""
+ return render(request, "addons/error.html", {"message": message}, status=status)
+
+
+@require_http_methods(["GET"])
+def transit_page(request):
+ """Initialize authentication flow for add-on session."""
+
+ session_id = request.GET.get("session_id")
+
+ if not session_id:
+ return render_error(request, _("Session ID is required."), status=400)
+
+ data = TokenExchangeService().get_session(session_id)
+
+ if not data:
+ return render_error(request, _("Session not found or expired."), status=404)
+
+ if data.get("state") != SessionState.PENDING:
+ return render_error(request, _("Invalid session state."), status=400)
+
+ request.session[settings.ADDONS_SESSION_KEY_AUTH] = session_id
+
+ return_to = request.build_absolute_uri("/addons/redirect")
+ return redirect(f"/api/{settings.API_VERSION}/authenticate/?returnTo={return_to}")
+
+
+@require_http_methods(["GET"])
+def redirect_page(request):
+ """Complete authentication and close the popup window."""
+
+ if not request.user.is_authenticated:
+ return render_error(request, _("Authentication required."), status=401)
+
+ session_id = request.session.pop(settings.ADDONS_SESSION_KEY_AUTH, None)
+
+ if not session_id:
+ return render_error(request, _("No active session found."), status=404)
+
+ try:
+ TokenExchangeService().set_access_token(request.user, session_id)
+ except SuspiciousOperation:
+ return render_error(request, _("Invalid or expired session."), status=400)
+
+ return render(request, "addons/redirect_success.html")
diff --git a/src/backend/core/addons/viewsets.py b/src/backend/core/addons/viewsets.py
new file mode 100644
index 00000000..9a04463e
--- /dev/null
+++ b/src/backend/core/addons/viewsets.py
@@ -0,0 +1,47 @@
+"""Add-ons API endpoints"""
+
+from logging import getLogger
+
+from rest_framework import (
+ response as drf_response,
+)
+from rest_framework import status as drf_status
+from rest_framework import viewsets
+
+from core.addons.service import TokenExchangeService
+
+logger = getLogger(__name__)
+
+
+class AuthSessionViewSet(viewsets.ViewSet):
+ """ViewSet for managing add-on authentication sessions via token exchange."""
+
+ authentication_classes = []
+ permission_classes = []
+ throttle_classes = []
+
+ def create(self, request):
+ """Create a new pending authentication session."""
+ session_id = TokenExchangeService().init_session()
+ return drf_response.Response(
+ {"session_id": session_id}, status=drf_status.HTTP_201_CREATED
+ )
+
+ def retrieve(self, request, pk=None):
+ """Retrieve authentication session data by session ID."""
+ data = TokenExchangeService().get_session(pk)
+
+ if not data:
+ return drf_response.Response(
+ {"detail": "Session not found or expired."},
+ status=drf_status.HTTP_404_NOT_FOUND,
+ )
+
+ return drf_response.Response(data, status=drf_status.HTTP_200_OK)
+
+ def destroy(self, request, pk=None):
+ """Delete an authentication session by session ID."""
+ TokenExchangeService().clear_session(pk)
+ return drf_response.Response(
+ {"status": "ok"}, status=drf_status.HTTP_204_NO_CONTENT
+ )
diff --git a/src/backend/core/external_api/authentication.py b/src/backend/core/external_api/authentication.py
index c16f6573..0309e860 100644
--- a/src/backend/core/external_api/authentication.py
+++ b/src/backend/core/external_api/authentication.py
@@ -214,6 +214,19 @@ class ApplicationJWTAuthentication(BaseJWTAuthentication):
raise exceptions.AuthenticationFailed("Invalid token type.")
+class AddonsJWTAuthentication(BaseJWTAuthentication):
+ """JWT authentication for addons API access.
+
+ Validates JWT tokens issued by addons for authenticating users.
+ Tokens must include user_id to identify the authenticated user.
+ """
+
+ secret_key = settings.ADDONS_JWT_SECRET_KEY
+ algorithm = settings.ADDONS_JWT_ALG
+ issuer = settings.ADDONS_JWT_ISSUER
+ audience = settings.ADDONS_JWT_AUDIENCE
+
+
class ResourceServerBackend(LaSuiteBackend):
"""OIDC Resource Server backend for user creation and retrieval."""
diff --git a/src/backend/core/external_api/viewsets.py b/src/backend/core/external_api/viewsets.py
index 5a0e550c..bba2564f 100644
--- a/src/backend/core/external_api/viewsets.py
+++ b/src/backend/core/external_api/viewsets.py
@@ -173,6 +173,7 @@ class RoomViewSet(
authentication_classes = [
authentication.ApplicationJWTAuthentication,
+ authentication.AddonsJWTAuthentication,
ResourceServerAuthentication,
]
permission_classes = [
diff --git a/src/backend/core/templates/addons/error.html b/src/backend/core/templates/addons/error.html
new file mode 100644
index 00000000..f749ae43
--- /dev/null
+++ b/src/backend/core/templates/addons/error.html
@@ -0,0 +1,17 @@
+{% load i18n %}
+{% get_current_language as LANGUAGE %}
+
+
+
+
+ {% trans "Error" %}
+
+
+
+
{{ title|default:_("Error") }}
+
{{ message|default:_("Something went wrong.") }}
+
+
+
+
+
diff --git a/src/backend/core/templates/addons/redirect_success.html b/src/backend/core/templates/addons/redirect_success.html
new file mode 100644
index 00000000..769e73cc
--- /dev/null
+++ b/src/backend/core/templates/addons/redirect_success.html
@@ -0,0 +1,17 @@
+{% load i18n %}
+{% get_current_language as LANGUAGE %}
+
+
+
+
+ {% trans "Authentication Success" %}
+
+
+
+ {% trans "Session stored successfully. This window will close automatically." %}
+ {% trans "If it doesn't close" %}, {% trans "click here" %}.
+
+
+
diff --git a/src/backend/core/urls.py b/src/backend/core/urls.py
index d8493e5a..c81c4229 100644
--- a/src/backend/core/urls.py
+++ b/src/backend/core/urls.py
@@ -6,6 +6,8 @@ from django.urls import include, path
from lasuite.oidc_login.urls import urlpatterns as oidc_urls
from rest_framework.routers import DefaultRouter
+from core.addons import views as addons_views
+from core.addons import viewsets as addons_viewsets
from core.api import get_frontend_configuration, viewsets
from core.external_api import viewsets as external_viewsets
@@ -26,12 +28,24 @@ external_router.register(
basename="external_application",
)
+# - Addons API
+addons_router = DefaultRouter()
+addons_router.register(
+ "addons/sessions",
+ addons_viewsets.AuthSessionViewSet,
+ basename="addons_auth_sessions",
+)
+
external_router.register(
"rooms",
external_viewsets.RoomViewSet,
basename="external_room",
)
+
+addons_urls = addons_router.urls if settings.ADDONS_ENABLED else []
+
+
urlpatterns = [
path(
f"api/{settings.API_VERSION}/",
@@ -39,12 +53,26 @@ urlpatterns = [
[
*router.urls,
*oidc_urls,
+ *addons_urls,
path("config/", get_frontend_configuration, name="config"),
]
),
),
]
+if settings.ADDONS_ENABLED:
+ urlpatterns.append(
+ path(
+ "addons/",
+ include(
+ [
+ path("transit/", addons_views.transit_page, name="transit_page"),
+ path("redirect/", addons_views.redirect_page, name="redirect_page"),
+ ]
+ ),
+ ),
+ )
+
if settings.EXTERNAL_API_ENABLED:
urlpatterns.append(
path(
diff --git a/src/backend/locale/de_DE/LC_MESSAGES/django.mo b/src/backend/locale/de_DE/LC_MESSAGES/django.mo
index fd9e4b59..22522cd6 100644
Binary files a/src/backend/locale/de_DE/LC_MESSAGES/django.mo and b/src/backend/locale/de_DE/LC_MESSAGES/django.mo differ
diff --git a/src/backend/locale/de_DE/LC_MESSAGES/django.po b/src/backend/locale/de_DE/LC_MESSAGES/django.po
index 051db135..0b668f15 100644
--- a/src/backend/locale/de_DE/LC_MESSAGES/django.po
+++ b/src/backend/locale/de_DE/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2025-12-29 15:15+0000\n"
+"POT-Creation-Date: 2026-01-26 15:40+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
@@ -17,6 +17,30 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
+#: core/addons/views.py:24
+msgid "Session ID is required."
+msgstr "Sitzungs-ID ist erforderlich."
+
+#: core/addons/views.py:29
+msgid "Session not found or expired."
+msgstr "Sitzung nicht gefunden oder abgelaufen."
+
+#: core/addons/views.py:32
+msgid "Invalid session state."
+msgstr "Ungültiger Sitzungsstatus."
+
+#: core/addons/views.py:45
+msgid "Authentication required."
+msgstr "Authentifizierung erforderlich."
+
+#: core/addons/views.py:50
+msgid "No active session found."
+msgstr "Keine aktive Sitzung gefunden."
+
+#: core/addons/views.py:55
+msgid "Invalid or expired session."
+msgstr "Ungültige oder abgelaufene Sitzung."
+
#: core/admin.py:29
msgid "Personal info"
msgstr "Persönliche Informationen"
@@ -408,7 +432,7 @@ msgstr "Anwendungsdomain"
msgid "Application domains"
msgstr "Anwendungsdomains"
-#: core/recording/event/notification.py:94
+#: core/recording/event/notification.py:116
msgid "Your recording is ready"
msgstr "Ihre Aufzeichnung ist bereit"
@@ -417,6 +441,30 @@ msgstr "Ihre Aufzeichnung ist bereit"
msgid "Video call in progress: {sender.email} is waiting for you to connect"
msgstr "Videoanruf läuft: {sender.email} wartet auf Ihre Teilnahme"
+#: core/templates/addons/error.html:7 core/templates/addons/error.html:11
+msgid "Error"
+msgstr "Fehler"
+
+#: core/templates/addons/error.html:12
+msgid "Something went wrong."
+msgstr "Etwas ist schiefgelaufen."
+
+#: core/templates/addons/error.html:13
+msgid "Close"
+msgstr "Schließen"
+
+#: core/templates/addons/redirect_success.html:7
+msgid "Authentication Success"
+msgstr "Authentifizierung erfolgreich"
+
+#: core/templates/addons/redirect_success.html:13
+msgid "Session stored successfully. This window will close automatically."
+msgstr "Sitzung erfolgreich gespeichert. Dieses Fenster wird automatisch geschlossen."
+
+#: core/templates/addons/redirect_success.html:14
+msgid "If it doesn't close"
+msgstr "Falls es sich nicht schließt"
+
#: core/templates/mail/html/invitation.html:159
#: core/templates/mail/html/screen_recording.html:159
#: core/templates/mail/text/invitation.txt:3
diff --git a/src/backend/locale/en_US/LC_MESSAGES/django.mo b/src/backend/locale/en_US/LC_MESSAGES/django.mo
index cdd88d17..fb982e7b 100644
Binary files a/src/backend/locale/en_US/LC_MESSAGES/django.mo and b/src/backend/locale/en_US/LC_MESSAGES/django.mo differ
diff --git a/src/backend/locale/en_US/LC_MESSAGES/django.po b/src/backend/locale/en_US/LC_MESSAGES/django.po
index 313f2352..9d3fcd50 100644
--- a/src/backend/locale/en_US/LC_MESSAGES/django.po
+++ b/src/backend/locale/en_US/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2025-12-29 15:15+0000\n"
+"POT-Creation-Date: 2026-01-26 15:40+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
@@ -17,6 +17,30 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
+#: core/addons/views.py:24
+msgid "Session ID is required."
+msgstr "Session ID is required."
+
+#: core/addons/views.py:29
+msgid "Session not found or expired."
+msgstr "Session not found or expired."
+
+#: core/addons/views.py:32
+msgid "Invalid session state."
+msgstr "Invalid session state."
+
+#: core/addons/views.py:45
+msgid "Authentication required."
+msgstr "Authentication required."
+
+#: core/addons/views.py:50
+msgid "No active session found."
+msgstr "No active session found."
+
+#: core/addons/views.py:55
+msgid "Invalid or expired session."
+msgstr "Invalid or expired session."
+
#: core/admin.py:29
msgid "Personal info"
msgstr "Personal info"
@@ -405,7 +429,7 @@ msgstr "Application domain"
msgid "Application domains"
msgstr "Application domains"
-#: core/recording/event/notification.py:94
+#: core/recording/event/notification.py:116
msgid "Your recording is ready"
msgstr "Your recording is ready"
@@ -414,6 +438,30 @@ msgstr "Your recording is ready"
msgid "Video call in progress: {sender.email} is waiting for you to connect"
msgstr "Video call in progress: {sender.email} is waiting for you to connect"
+#: core/templates/addons/error.html:7 core/templates/addons/error.html:11
+msgid "Error"
+msgstr "Error"
+
+#: core/templates/addons/error.html:12
+msgid "Something went wrong."
+msgstr "Something went wrong."
+
+#: core/templates/addons/error.html:13
+msgid "Close"
+msgstr "Close"
+
+#: core/templates/addons/redirect_success.html:7
+msgid "Authentication Success"
+msgstr "Authentication Success"
+
+#: core/templates/addons/redirect_success.html:13
+msgid "Session stored successfully. This window will close automatically."
+msgstr "Session stored successfully. This window will close automatically."
+
+#: core/templates/addons/redirect_success.html:14
+msgid "If it doesn't close"
+msgstr "If it doesn't close"
+
#: core/templates/mail/html/invitation.html:159
#: core/templates/mail/html/screen_recording.html:159
#: core/templates/mail/text/invitation.txt:3
diff --git a/src/backend/locale/fr_FR/LC_MESSAGES/django.mo b/src/backend/locale/fr_FR/LC_MESSAGES/django.mo
index c76470df..11899a94 100644
Binary files a/src/backend/locale/fr_FR/LC_MESSAGES/django.mo and b/src/backend/locale/fr_FR/LC_MESSAGES/django.mo differ
diff --git a/src/backend/locale/fr_FR/LC_MESSAGES/django.po b/src/backend/locale/fr_FR/LC_MESSAGES/django.po
index 5f8c16b2..f61a3fab 100644
--- a/src/backend/locale/fr_FR/LC_MESSAGES/django.po
+++ b/src/backend/locale/fr_FR/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2025-12-29 15:15+0000\n"
+"POT-Creation-Date: 2026-01-26 15:40+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: antoine.lebaud@mail.numerique.gouv.fr\n"
"Language-Team: LANGUAGE \n"
@@ -17,6 +17,30 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
+#: core/addons/views.py:24
+msgid "Session ID is required."
+msgstr "L'identifiant de session est requis."
+
+#: core/addons/views.py:29
+msgid "Session not found or expired."
+msgstr "Session introuvable ou expirée."
+
+#: core/addons/views.py:32
+msgid "Invalid session state."
+msgstr "État de session invalide."
+
+#: core/addons/views.py:45
+msgid "Authentication required."
+msgstr "Authentification requise."
+
+#: core/addons/views.py:50
+msgid "No active session found."
+msgstr "Aucune session active trouvée."
+
+#: core/addons/views.py:55
+msgid "Invalid or expired session."
+msgstr "Session invalide ou expirée."
+
#: core/admin.py:29
msgid "Personal info"
msgstr "Informations personnelles"
@@ -409,7 +433,7 @@ msgstr "Domaine d’application"
msgid "Application domains"
msgstr "Domaines d’application"
-#: core/recording/event/notification.py:94
+#: core/recording/event/notification.py:116
msgid "Your recording is ready"
msgstr "Votre enregistrement est prêt"
@@ -418,6 +442,30 @@ msgstr "Votre enregistrement est prêt"
msgid "Video call in progress: {sender.email} is waiting for you to connect"
msgstr "Appel vidéo en cours : {sender.email} attend que vous vous connectiez"
+#: core/templates/addons/error.html:7 core/templates/addons/error.html:11
+msgid "Error"
+msgstr "Erreur"
+
+#: core/templates/addons/error.html:12
+msgid "Something went wrong."
+msgstr "Une erreur s'est produite."
+
+#: core/templates/addons/error.html:13
+msgid "Close"
+msgstr "Fermer"
+
+#: core/templates/addons/redirect_success.html:7
+msgid "Authentication Success"
+msgstr "Authentification réussie"
+
+#: core/templates/addons/redirect_success.html:13
+msgid "Session stored successfully. This window will close automatically."
+msgstr "Session enregistrée avec succès. Cette fenêtre se fermera automatiquement."
+
+#: core/templates/addons/redirect_success.html:14
+msgid "If it doesn't close"
+msgstr "Si elle ne se ferme pas"
+
#: core/templates/mail/html/invitation.html:159
#: core/templates/mail/html/screen_recording.html:159
#: core/templates/mail/text/invitation.txt:3
diff --git a/src/backend/locale/nl_NL/LC_MESSAGES/django.mo b/src/backend/locale/nl_NL/LC_MESSAGES/django.mo
index afea428a..e26acf92 100644
Binary files a/src/backend/locale/nl_NL/LC_MESSAGES/django.mo and b/src/backend/locale/nl_NL/LC_MESSAGES/django.mo differ
diff --git a/src/backend/locale/nl_NL/LC_MESSAGES/django.po b/src/backend/locale/nl_NL/LC_MESSAGES/django.po
index 160e2240..4951037c 100644
--- a/src/backend/locale/nl_NL/LC_MESSAGES/django.po
+++ b/src/backend/locale/nl_NL/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2025-12-29 15:15+0000\n"
+"POT-Creation-Date: 2026-01-26 15:40+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
@@ -17,6 +17,30 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
+#: core/addons/views.py:24
+msgid "Session ID is required."
+msgstr "Sessie-ID is vereist."
+
+#: core/addons/views.py:29
+msgid "Session not found or expired."
+msgstr "Sessie niet gevonden of verlopen."
+
+#: core/addons/views.py:32
+msgid "Invalid session state."
+msgstr "Ongeldige sessiestatus."
+
+#: core/addons/views.py:45
+msgid "Authentication required."
+msgstr "Authenticatie vereist."
+
+#: core/addons/views.py:50
+msgid "No active session found."
+msgstr "Geen actieve sessie gevonden."
+
+#: core/addons/views.py:55
+msgid "Invalid or expired session."
+msgstr "Ongeldige of verlopen sessie."
+
#: core/admin.py:29
msgid "Personal info"
msgstr "Persoonlijke informatie"
@@ -404,7 +428,7 @@ msgstr "Applicatiedomein"
msgid "Application domains"
msgstr "Applicatiedomeinen"
-#: core/recording/event/notification.py:94
+#: core/recording/event/notification.py:116
msgid "Your recording is ready"
msgstr "Je opname is klaar"
@@ -413,6 +437,30 @@ msgstr "Je opname is klaar"
msgid "Video call in progress: {sender.email} is waiting for you to connect"
msgstr "Video-oproep bezig: {sender.email} wacht op je verbinding"
+#: core/templates/addons/error.html:7 core/templates/addons/error.html:11
+msgid "Error"
+msgstr "Fout"
+
+#: core/templates/addons/error.html:12
+msgid "Something went wrong."
+msgstr "Er is iets misgegaan."
+
+#: core/templates/addons/error.html:13
+msgid "Close"
+msgstr "Sluiten"
+
+#: core/templates/addons/redirect_success.html:7
+msgid "Authentication Success"
+msgstr "Authenticatie geslaagd"
+
+#: core/templates/addons/redirect_success.html:13
+msgid "Session stored successfully. This window will close automatically."
+msgstr "Sessie succesvol opgeslagen. Dit venster wordt automatisch gesloten."
+
+#: core/templates/addons/redirect_success.html:14
+msgid "If it doesn't close"
+msgstr "Als het niet sluit"
+
#: core/templates/mail/html/invitation.html:159
#: core/templates/mail/html/screen_recording.html:159
#: core/templates/mail/text/invitation.txt:3
diff --git a/src/backend/meet/settings.py b/src/backend/meet/settings.py
index eda28a76..f2b0279d 100755
--- a/src/backend/meet/settings.py
+++ b/src/backend/meet/settings.py
@@ -797,6 +797,66 @@ class Base(Configuration):
environ_prefix=None,
)
+ # Addons
+ ADDONS_ENABLED = values.BooleanValue(
+ False,
+ environ_name="ADDONS_ENABLED",
+ environ_prefix=None,
+ )
+ ADDONS_SESSION_ID_LENGTH = values.PositiveIntegerValue(
+ 32,
+ environ_name="ADDONS_SESSION_ID_LENGTH",
+ environ_prefix=None,
+ )
+ # Used in cache key generation
+ ADDONS_SESSION_KEY_PREFIX = values.Value(
+ "addons_session_id",
+ environ_name="ADDONS_SESSION_KEY_PREFIX",
+ environ_prefix=None,
+ )
+ # Used as the Django session key in transit page
+ ADDONS_SESSION_KEY_AUTH = values.Value(
+ "addons_session_id",
+ environ_name="ADDONS_SESSION_KEY_AUTH",
+ environ_prefix=None,
+ )
+ ADDONS_SESSION_TIMEOUT = values.PositiveIntegerValue(
+ 600, environ_name="ADDONS_SESSION_TIMEOUT", environ_prefix=None
+ )
+ ADDONS_JWT_SECRET_KEY = SecretFileValue(
+ None, environ_name="ADDONS_JWT_SECRET_KEY", environ_prefix=None
+ )
+ ADDONS_JWT_ALG = values.Value(
+ "HS256",
+ environ_name="ADDONS_JWT_ALG",
+ environ_prefix=None,
+ )
+ ADDONS_SCOPES = values.Value(
+ "rooms:create rooms:list",
+ environ_name="ADDONS_SCOPES",
+ environ_prefix=None,
+ )
+ ADDONS_JWT_ISSUER = values.Value(
+ "lasuite-meet",
+ environ_name="ADDONS_JWT_ISSUER",
+ environ_prefix=None,
+ )
+ ADDONS_JWT_AUDIENCE = values.Value(
+ None,
+ environ_name="ADDONS_JWT_AUDIENCE",
+ environ_prefix=None,
+ )
+ ADDONS_JWT_EXPIRATION_SECONDS = values.PositiveIntegerValue(
+ 3600,
+ environ_name="ADDONS_JWT_EXPIRATION_SECONDS",
+ environ_prefix=None,
+ )
+ ADDONS_JWT_TOKEN_TYPE = values.Value(
+ "Bearer",
+ environ_name="ADDONS_JWT_TOKEN_TYPE",
+ environ_prefix=None,
+ )
+
# pylint: disable=invalid-name
@property
def ENVIRONMENT(self):
diff --git a/src/helm/env.d/dev-keycloak/values.meet.yaml.gotmpl b/src/helm/env.d/dev-keycloak/values.meet.yaml.gotmpl
index 04c2f170..e34a3075 100644
--- a/src/helm/env.d/dev-keycloak/values.meet.yaml.gotmpl
+++ b/src/helm/env.d/dev-keycloak/values.meet.yaml.gotmpl
@@ -79,6 +79,7 @@ backend:
APPLICATION_JWT_AUDIENCE: https://meet.127.0.0.1.nip.io/external-api/v1.0/
APPLICATION_JWT_SECRET_KEY: devKeyApplication
APPLICATION_BASE_URL: https://meet.127.0.0.1.nip.io
+ ADDONS_JWT_SECRET_KEY: devKeyApplicationAddons
migrate:
diff --git a/src/helm/meet/templates/ingress.yaml b/src/helm/meet/templates/ingress.yaml
index 8f15f761..e83459cf 100644
--- a/src/helm/meet/templates/ingress.yaml
+++ b/src/helm/meet/templates/ingress.yaml
@@ -88,6 +88,20 @@ spec:
serviceName: {{ include "meet.backend.fullname" . }}
servicePort: {{ .Values.backend.service.port }}
{{- end }}
+ - path: /addons/
+ {{- if semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion }}
+ pathType: Prefix
+ {{- end }}
+ backend:
+ {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }}
+ service:
+ name: {{ include "meet.backend.fullname" . }}
+ port:
+ number: {{ .Values.backend.service.port }}
+ {{- else }}
+ serviceName: {{ include "meet.backend.fullname" . }}
+ servicePort: {{ .Values.backend.service.port }}
+ {{- end }}
{{- with .Values.ingress.customBackends }}
{{- toYaml . | nindent 10 }}
{{- end }}
@@ -138,6 +152,20 @@ spec:
serviceName: {{ include "meet.backend.fullname" $ }}
servicePort: {{ $.Values.backend.service.port }}
{{- end }}
+ - path: /addons/
+ {{- if semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion }}
+ pathType: Prefix
+ {{- end }}
+ backend:
+ {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }}
+ service:
+ name: {{ include "meet.backend.fullname" $ }}
+ port:
+ number: {{ $.Values.backend.service.port }}
+ {{- else }}
+ serviceName: {{ include "meet.backend.fullname" $ }}
+ servicePort: {{ $.Values.backend.service.port }}
+ {{- end }}
{{- with $.Values.ingress.customBackends }}
{{- toYaml . | nindent 10 }}
{{- end }}