Compare commits

..

1 Commits

Author SHA1 Message Date
Martin Guitteny c24c1729ef 🐛(summary) fix feature flag on summary job
Sadly, we used user db id as the posthog distinct id
of identified user, and not the sub.

Before this commit, we were only passing sub to the
summary microservice.

Add the owner's id. Please note we introduce a different
naming behavir, by prefixing the id with "owner". We didn't
for the sub and the email.

We cannot align sub and email with this new naming approach,
because external contributors have already started building
their own microservice.
2025-09-30 15:20:58 +02:00
21 changed files with 13 additions and 712 deletions
+8 -8
View File
@@ -30,17 +30,17 @@ jobs:
images: lasuite/meet-backend
-
name: Login to DockerHub
# if: github.event_name != 'pull_request'
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
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: 'docker.io/lasuite/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: 'docker.io/lasuite/meet-backend:${{ github.sha }}'
-
name: Build and push
uses: docker/build-push-action@v6
@@ -48,7 +48,7 @@ jobs:
context: .
target: backend-production
build-args: DOCKER_USER=${{ env.DOCKER_USER }}:-1000
push: ${{ github.event_name == 'pull_request' }}
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
+1 -1
View File
@@ -42,7 +42,7 @@ COPY ./docker/dinum-frontend/fonts/ \
FROM nginxinc/nginx-unprivileged:alpine3.21 AS frontend-production
USER root
RUN apk update && apk upgrade libssl3 libcrypto3 libxml2>=2.12.7-r2 libxslt>=1.1.39-r2 libexpat>=2.7.2-r0
RUN apk update && apk upgrade libssl3 libcrypto3 libxml2>=2.12.7-r2 libxslt>=1.1.39-r2
USER nginx
-5
View File
@@ -62,11 +62,6 @@ SCREEN_RECORDING_BASE_URL=http://localhost:3000/recordings
# Telephony
ROOM_TELEPHONY_ENABLED=True
ROOM_TELEPHONY_DEFAULT_COUNTRY: 'FR'
ROOM_TELEPHONY_PHONE_NUMBER: '+33901020304'
FRONTEND_USE_FRENCH_GOV_FOOTER=False
FRONTEND_USE_PROCONNECT_BUTTON=False
INTEGRATIONS_JWT_SECRET_KEY=devKey
INTEGRATIONS_APP_BASE_URL: http://localhost:3000
-53
View File
@@ -1,12 +1,9 @@
"""Admin classes and registrations for core app."""
from django import forms
from django.contrib import admin
from django.contrib.auth import admin as auth_admin
from django.utils.translation import gettext_lazy as _
from rest_framework_api_key.admin import APIKeyModelAdmin
from . import models
@@ -153,53 +150,3 @@ class RecordingAdmin(admin.ModelAdmin):
return _("Multiple owners")
return str(owners[0].user)
class ServiceAccountDomainInline(admin.TabularInline):
"""Inline admin for managing allowed domains per service account."""
model = models.ServiceAccountDomain
extra = 0
class ServiceAccountAdminForm(forms.ModelForm):
"""Custom form for ServiceAccount admin with multi-select scopes."""
scopes = forms.MultipleChoiceField(
choices=models.ServiceAccountScope.choices,
widget=forms.CheckboxSelectMultiple,
required=False,
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.instance.pk and self.instance.scopes:
self.fields["scopes"].initial = self.instance.scopes
@admin.register(models.ServiceAccount)
class ServiceAccountAdmin(admin.ModelAdmin):
"""Admin interface for managing service accounts and their permissions."""
form = ServiceAccountAdminForm
list_display = ("id", "name", "client_id", "get_scopes_display")
fields = ["name", "id", "created_at", "updated_at", "scopes", "client_id"]
readonly_fields = ["id", "created_at", "updated_at", "client_id"]
inlines = [ServiceAccountDomainInline]
def get_scopes_display(self, obj):
"""Display scopes in list view."""
if obj.scopes:
return ", ".join(obj.scopes)
return _("No scopes")
get_scopes_display.short_description = _("Scopes")
@admin.register(models.ServiceAccountSecret)
class ServiceAccountSecretModelAdmin(APIKeyModelAdmin):
"""Admin interface for managing service account API keys."""
list_display = [*APIKeyModelAdmin.list_display, "service_account__name"]
search_fields = [*APIKeyModelAdmin.search_fields, "service_account__name"]
@@ -1 +0,0 @@
"""Meet core external API endpoints"""
@@ -1,81 +0,0 @@
"""Authentication Backends for service accounts integrated to the Meet core app."""
import logging
from django.conf import settings
from django.contrib.auth import get_user_model
import jwt
from rest_framework import authentication, exceptions
User = get_user_model()
logger = logging.getLogger(__name__)
class ServiceAccountJWTAuthentication(authentication.BaseAuthentication):
"""JWT authentication for external API endpoints."""
def authenticate(self, request):
"""Extract and validate JWT from Authorization header."""
auth_header = authentication.get_authorization_header(request).split()
if not auth_header or auth_header[0].lower() != b"bearer":
return None
if len(auth_header) != 2:
logger.warning("Invalid token header")
raise exceptions.AuthenticationFailed("Invalid token header.")
try:
token = auth_header[1].decode("utf-8")
except UnicodeError as e:
logger.warning("Invalid: %s", e)
raise exceptions.AuthenticationFailed("Invalid token.") from e
return self.authenticate_credentials(token)
def authenticate_credentials(self, token):
"""Authenticate and validate JWT token credentials."""
try:
payload = jwt.decode(
token,
settings.INTEGRATIONS_JWT_SECRET_KEY,
algorithms=[settings.INTEGRATIONS_JWT_ALG],
issuer=settings.INTEGRATIONS_JWT_ISSUER,
audience=settings.INTEGRATIONS_JWT_AUDIENCE,
)
except jwt.ExpiredSignatureError as e:
logger.warning("Token expired")
raise exceptions.AuthenticationFailed("Token expired.") from e
except jwt.InvalidIssuerError as e:
logger.warning("Invalid JWT issuer: %s", e)
raise exceptions.AuthenticationFailed("Invalid token.") from e
except jwt.InvalidTokenError as e:
logger.warning("Invalid JWT token: %s", e)
raise exceptions.AuthenticationFailed("Invalid token.") from e
user_id = payload.get("user_id")
if not user_id:
logger.warning("Missing 'user_id' in JWT payload")
raise exceptions.AuthenticationFailed("Invalid token.")
try:
user = User.objects.get(id=user_id)
except User.DoesNotExist as e:
logger.warning("User not found: %s", user_id)
raise exceptions.AuthenticationFailed("Invalid token.") from e
if not user.is_active:
logger.warning("Inactive user attempted authentication: %s", user_id)
raise exceptions.AuthenticationFailed("Account disabled.")
# Attach token metadata to user
user.token_scopes = payload.get("scope", [])
user.is_impersonated = payload.get("impersonated", False)
user.client_id = payload.get("client_id")
return (user, token)
@@ -1,44 +0,0 @@
"""Permission handlers for the external API of the Meet core app."""
from rest_framework import exceptions, permissions
from .. import models
class HasRequiredScope(permissions.BasePermission):
"""Enforces scope-based access control on ViewSet actions.
Provides fine-grained permission management by mapping ViewSet actions
to required scopes. JWT tokens must contain the appropriate scope to
perform specific operations, enabling precise control over what external
services can access (principle of least privilege).
"""
scope_map = {
"list": models.ServiceAccountScope.ROOMS_LIST,
"retrieve": models.ServiceAccountScope.ROOMS_RETRIEVE,
"create": models.ServiceAccountScope.ROOMS_CREATE,
"update": models.ServiceAccountScope.ROOMS_UPDATE,
"partial_update": models.ServiceAccountScope.ROOMS_UPDATE,
"destroy": models.ServiceAccountScope.ROOMS_DELETE,
}
def has_permission(self, request, view):
"""Check if JWT token contains required scope for this action."""
action = getattr(view, "action", None)
if not action:
return True
required_scope = self.scope_map.get(action)
if not required_scope:
return True
token_scopes = getattr(request.user, "token_scopes", [])
# todo - parse properly scope
if required_scope not in token_scopes:
raise exceptions.PermissionDenied("Insufficient permissions")
return True
@@ -1,68 +0,0 @@
"""Client serializers for the external API of the Meet core app."""
# pylint: disable=abstract-method
from django.conf import settings
from rest_framework import serializers
from core import models, utils
from core.api.serializers import BaseValidationOnlySerializer
class JwtSerializer(BaseValidationOnlySerializer):
"""Validate OAuth2 JWT token request data."""
client_id = serializers.UUIDField()
client_secret = serializers.CharField(write_only=True)
grant_type = serializers.ChoiceField(choices=["client_credentials"])
scope = serializers.EmailField()
class RoomSerializer(serializers.ModelSerializer):
"""External API serializer with limited, safe room information.
Designed for external integrations with:
- Read-only access to most fields for security
- Additional computed fields (url, telephony info)
- Filtered data appropriate for external consumption
- Automatic room creation with secure defaults
Intentionally limits exposed information compared to internal APIs,
providing only what external services need while protecting sensitive details.
"""
class Meta:
model = models.Room
fields = ["id", "name", "slug", "pin_code", "access_level"]
read_only_fields = ["id", "name", "slug", "pin_code", "access_level"]
def to_representation(self, instance):
"""Add external-specific fields and filter sensitive data."""
output = super().to_representation(instance)
request = self.context.get("request")
if not request:
return output
output["url"] = f"{settings.INTEGRATIONS_APP_BASE_URL}/{output['slug']}"
if settings.ROOM_TELEPHONY_ENABLED:
output["telephony"] = {
"enabled": settings.ROOM_TELEPHONY_ENABLED,
"pin_code": output["pin_code"],
"phone_number": settings.ROOM_TELEPHONY_PHONE_NUMBER,
"default_country": settings.ROOM_TELEPHONY_DEFAULT_COUNTRY,
}
del output["pin_code"]
del output["name"]
return output
def create(self, validated_data):
"""Create room with secure defaults for external API."""
# todo - track source of creation
validated_data["name"] = utils.generate_slug()
validated_data["access_level"] = models.RoomAccessLevel.TRUSTED
return super().create(validated_data)
-186
View File
@@ -1,186 +0,0 @@
"""External API endpoints"""
from datetime import datetime, timedelta
from logging import getLogger
from django.conf import settings
import jwt
from rest_framework import decorators, mixins, viewsets
from rest_framework import (
exceptions as drf_exceptions,
)
from rest_framework import (
response as drf_response,
)
from rest_framework import (
status as drf_status,
)
from core import api, models
from . import authentication, permissions, serializers
logger = getLogger(__name__)
class ServiceAccountViewSet(viewsets.GenericViewSet):
"""Service account management for external integrations."""
@decorators.action(
detail=False,
methods=["post"],
url_path="token",
url_name="token",
)
def generate_token(self, request, *args, **kwargs):
"""Generate JWT token for user impersonation.
Token generation endpoint for service-to-service authentication.
Exchanges service account client_id / client_secret for a scoped JWT token
that can impersonate a specific user. The service account must have permission
to impersonate the user's email domain.
"""
serializer = serializers.JwtSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
client_id = serializer.validated_data["client_id"]
client_secret = serializer.validated_data["client_secret"]
try:
service_account = models.ServiceAccount.objects.get(client_id=client_id)
except models.ServiceAccount.DoesNotExist as e:
raise drf_exceptions.AuthenticationFailed("Invalid client_id") from e
try:
service_account_secret = models.ServiceAccountSecret.objects.get_from_key(
client_secret
)
except models.ServiceAccountSecret.DoesNotExist as e:
raise drf_exceptions.AuthenticationFailed("Invalid client_secret") from e
if (
service_account.client_id
!= service_account_secret.service_account.client_id
):
raise drf_exceptions.AuthenticationFailed("Invalid client_secret")
# todo - explain why scope is an email here
email = serializer.validated_data["scope"]
if not service_account.can_impersonate_email(email):
logger.warning(
"Service account %s denied impersonation of %s",
service_account.id,
email,
)
return drf_response.Response(
{
"error": "Access denied",
"detail": "Cannot impersonate this user. The user's domain may not be authorized in the domain delegation settings for this service account.",
},
status=drf_status.HTTP_403_FORBIDDEN,
)
try:
user = models.User.objects.get(email=email)
except models.User.DoesNotExist as e:
# todo - create unknown user
raise drf_exceptions.NotFound(
{
"error": "User not found",
"detail": f"No user with email '{email}' exists.",
}
) from e
now = datetime.utcnow()
scopes = service_account.scopes or []
payload = {
# Standard JWT claims
"iss": settings.INTEGRATIONS_JWT_ISSUER,
"aud": settings.INTEGRATIONS_JWT_AUDIENCE,
"iat": now,
"exp": now
+ timedelta(seconds=settings.INTEGRATIONS_JWT_EXPIRATION_SECONDS),
# Application claims
"client_id": str(client_id),
"scope": " ".join(scopes),
# Minimal user info
"user_id": str(user.id),
"impersonated": True,
}
try:
token = jwt.encode(
payload,
settings.INTEGRATIONS_JWT_SECRET_KEY,
algorithm=settings.INTEGRATIONS_JWT_ALG,
)
except Exception: # noqa: BLE001
logger.exception("Unexpected error during JWT token generation")
return drf_response.Response(
{
"error": "Internal server error",
"detail": "An unexpected error occurred during JWT token generation. Please contact support.",
},
status=drf_status.HTTP_500_INTERNAL_SERVER_ERROR,
)
return drf_response.Response(
{
"access_token": token,
"token_type": settings.INTEGRATIONS_JWT_TOKEN_TYPE,
"expires_in": settings.INTEGRATIONS_JWT_EXPIRATION_SECONDS,
"scope": " ".join(scopes),
},
status=drf_status.HTTP_200_OK,
)
class RoomViewSet(
mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.ListModelMixin,
viewsets.GenericViewSet,
):
"""External API for room management with scope-based permissions.
Provides JWT-authenticated access to room operations for external services.
All operations are scoped and filtered to authenticated user's accessible rooms.
"""
authentication_classes = [authentication.ServiceAccountJWTAuthentication]
permission_classes = [api.permissions.IsAuthenticated, permissions.HasRequiredScope]
queryset = models.Room.objects.all()
serializer_class = serializers.RoomSerializer
def list(self, request, *args, **kwargs):
"""Limit listed rooms to the ones related to the authenticated user."""
user = self.request.user
if user.is_authenticated:
queryset = (
self.filter_queryset(self.get_queryset()).filter(users=user).distinct()
)
else:
queryset = self.get_queryset().none()
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return drf_response.Response(serializer.data)
def perform_create(self, serializer):
"""Set the current user as owner of the newly created room."""
room = serializer.save()
models.ResourceAccess.objects.create(
resource=room,
user=self.request.user,
role=models.RoleChoices.OWNER,
)
@@ -1,72 +0,0 @@
# Generated by Django 5.2.6 on 2025-10-02 13:15
import django.contrib.postgres.fields
import django.core.validators
import django.db.models.deletion
import uuid
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0014_room_pin_code'),
]
operations = [
migrations.CreateModel(
name='ServiceAccount',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, help_text='primary key for the record as UUID', primary_key=True, serialize=False, verbose_name='id')),
('created_at', models.DateTimeField(auto_now_add=True, help_text='date and time at which a record was created', verbose_name='created on')),
('updated_at', models.DateTimeField(auto_now=True, help_text='date and time at which a record was last updated', verbose_name='updated on')),
('name', models.CharField(help_text='Descriptive name for this service account.', max_length=255, verbose_name='Service account name')),
('active', models.BooleanField(default=True)),
('client_id', models.UUIDField(default=uuid.uuid4, help_text='Unique public identifier for the client, auto-generated UUID used for external integration', verbose_name='client_id')),
('scopes', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(choices=[('rooms:create', 'Create rooms'), ('rooms:list', 'List rooms'), ('rooms:retrieve', 'Retrieve room details'), ('rooms:update', 'Update rooms'), ('rooms:delete', 'Delete rooms')], max_length=50), blank=True, default=list, null=True, size=None)),
],
options={
'verbose_name': 'Service account',
'verbose_name_plural': 'Service accounts',
'db_table': 'meet_service_account',
'ordering': ('-created_at',),
},
),
migrations.CreateModel(
name='ServiceAccountSecret',
fields=[
('id', models.CharField(editable=False, max_length=150, primary_key=True, serialize=False, unique=True)),
('prefix', models.CharField(editable=False, max_length=8, unique=True)),
('hashed_key', models.CharField(editable=False, max_length=150)),
('created', models.DateTimeField(auto_now_add=True, db_index=True)),
('name', models.CharField(default=None, help_text='A free-form name for the API key. Need not be unique. 50 characters max.', max_length=50)),
('revoked', models.BooleanField(blank=True, default=False, help_text='If the API key is revoked, clients cannot use it anymore. (This cannot be undone.)')),
('expiry_date', models.DateTimeField(blank=True, help_text='Once API key expires, clients cannot use it anymore.', null=True, verbose_name='Expires')),
('service_account', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='client_secret', to='core.serviceaccount')),
],
options={
'verbose_name': 'API key',
'verbose_name_plural': 'API keys',
'ordering': ('-created',),
'abstract': False,
},
),
migrations.CreateModel(
name='ServiceAccountDomain',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, help_text='primary key for the record as UUID', primary_key=True, serialize=False, verbose_name='id')),
('created_at', models.DateTimeField(auto_now_add=True, help_text='date and time at which a record was created', verbose_name='created on')),
('updated_at', models.DateTimeField(auto_now=True, help_text='date and time at which a record was last updated', verbose_name='updated on')),
('domain', models.CharField(help_text='Email domain that can be impersonated.', max_length=253, validators=[django.core.validators.DomainNameValidator(accept_idna=False, message='Enter a valid domain')], verbose_name='Domain')),
('service_account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='allowed_domains', to='core.serviceaccount')),
],
options={
'verbose_name': 'Service account domain',
'verbose_name_plural': 'Service account domains',
'db_table': 'meet_service_account_domain',
'ordering': ('domain',),
'unique_together': {('service_account', 'domain')},
},
),
]
-102
View File
@@ -11,7 +11,6 @@ from typing import List, Optional
from django.conf import settings
from django.contrib.auth import models as auth_models
from django.contrib.auth.base_user import AbstractBaseUser
from django.contrib.postgres.fields import ArrayField
from django.core import mail, validators
from django.core.exceptions import PermissionDenied, ValidationError
from django.db import models
@@ -19,7 +18,6 @@ from django.utils import timezone
from django.utils.text import capfirst, slugify
from django.utils.translation import gettext_lazy as _
from rest_framework_api_key.models import AbstractAPIKey
from timezone_field import TimeZoneField
from .recording.enums import FileExtension
@@ -719,103 +717,3 @@ class RecordingAccess(BaseAccess):
Compute and return abilities for a given user on the recording access.
"""
return self._get_abilities(self.recording, user)
class ServiceAccountScope(models.TextChoices):
"""Available permission scopes for service account operations."""
ROOMS_CREATE = "rooms:create", _("Create rooms")
ROOMS_LIST = "rooms:list", _("List rooms")
ROOMS_RETRIEVE = "rooms:retrieve", _("Retrieve room details")
ROOMS_UPDATE = "rooms:update", _("Update rooms")
ROOMS_DELETE = "rooms:delete", _("Delete rooms")
class ServiceAccount(BaseModel):
"""Service account for external API authentication and authorization."""
name = models.CharField(
max_length=255,
verbose_name=_("Service account name"),
help_text=_("Descriptive name for this service account."),
)
active = models.BooleanField(default=True)
client_id = models.UUIDField(
verbose_name=_("client_id"),
help_text=_(
"Unique public identifier for the client, auto-generated UUID used for external integration"
),
default=uuid.uuid4,
)
scopes = ArrayField(
models.CharField(max_length=50, choices=ServiceAccountScope.choices),
default=list,
blank=True,
null=True,
)
class Meta:
db_table = "meet_service_account"
ordering = ("-created_at",)
verbose_name = _("Service account")
verbose_name_plural = _("Service accounts")
def __str__(self):
return f"{self.name!s}"
def can_impersonate_email(self, email):
"""Check if this service account can impersonate the given email."""
if not self.allowed_domains.exists():
return True # No domain restrictions
domain = email.split("@")[-1]
return self.allowed_domains.filter(domain__iexact=domain).exists()
class ServiceAccountDomain(BaseModel):
"""Domain allowed for service account impersonation."""
domain = models.CharField(
max_length=253, # Max domain length per RFC
validators=[
validators.DomainNameValidator(
accept_idna=False,
message=_("Enter a valid domain"),
)
],
verbose_name=_("Domain"),
help_text=_("Email domain that can be impersonated."),
)
service_account = models.ForeignKey(
"ServiceAccount",
on_delete=models.CASCADE,
related_name="allowed_domains",
)
class Meta:
db_table = "meet_service_account_domain"
ordering = ("domain",)
verbose_name = _("Service account domain")
verbose_name_plural = _("Service account domains")
unique_together = [("service_account", "domain")]
def __str__(self):
return self.domain
def save(self, *args, **kwargs):
# Normalize domain to lowercase
self.domain = self.domain.lower().strip()
super().save(*args, **kwargs)
class ServiceAccountSecret(AbstractAPIKey):
"""API key for service account authentication."""
service_account = models.OneToOneField(
ServiceAccount,
on_delete=models.CASCADE,
related_name="client_secret",
)
-19
View File
@@ -7,7 +7,6 @@ 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.external_api import viewsets as external_viewsets
# - Main endpoints
router = DefaultRouter()
@@ -18,16 +17,6 @@ router.register(
"resource-accesses", viewsets.ResourceAccessViewSet, basename="resource_accesses"
)
external_router = DefaultRouter()
external_router.register(
"rooms", external_viewsets.RoomViewSet, basename="external_rooms"
)
external_router.register(
"service-account",
external_viewsets.ServiceAccountViewSet,
basename="external_service_account",
)
urlpatterns = [
path(
f"api/{settings.API_VERSION}/",
@@ -39,12 +28,4 @@ urlpatterns = [
]
),
),
path(
f"external-api/{settings.EXTERNAL_API_VERSION}/",
include(
[
*external_router.urls,
]
),
),
]
-13
View File
@@ -8,8 +8,6 @@ Utils functions used in the core app
import hashlib
import json
import random
import secrets
import string
from typing import List, Optional
from uuid import uuid4
@@ -29,17 +27,6 @@ from livekit.api import ( # pylint: disable=E0611
)
def generate_slug():
"""Generate a random room slug in the format 'xxx-xxxx-xxx'."""
sizes = [3, 4, 3]
parts = [
"".join(secrets.choice(string.ascii_lowercase) for _ in range(size))
for size in sizes
]
return "-".join(parts)
def generate_color(identity: str) -> str:
"""Generates a consistent HSL color based on a given identity string.
-37
View File
@@ -69,7 +69,6 @@ class Base(Configuration):
USE_SWAGGER = False
API_VERSION = "v1.0"
EXTERNAL_API_VERSION = "v1.0"
DATA_DIR = values.Value(path.join("/", "data"), environ_name="DATA_DIR")
@@ -227,7 +226,6 @@ class Base(Configuration):
"corsheaders",
"dockerflow.django",
"rest_framework",
"rest_framework_api_key",
"parler",
"easy_thumbnails",
# Django
@@ -666,41 +664,6 @@ class Base(Configuration):
environ_prefix=None,
)
# Integrations settings
INTEGRATIONS_JWT_SECRET_KEY = SecretFileValue(
None, environ_name="INTEGRATIONS_JWT_SECRET_KEY", environ_prefix=None
)
INTEGRATIONS_JWT_ALG = values.Value(
"HS256",
environ_name="INTEGRATIONS_JWT_ALG",
environ_prefix=None,
)
INTEGRATIONS_JWT_ISSUER = values.Value(
"lasuite-meet",
environ_name="INTEGRATIONS_JWT_ISSUER",
environ_prefix=None,
)
INTEGRATIONS_JWT_AUDIENCE = values.Value(
"http://localhost:8071/external-api/v1.0/",
environ_name="INTEGRATIONS_JWT_AUDIENCE",
environ_prefix=None,
)
INTEGRATIONS_JWT_EXPIRATION_SECONDS = values.PositiveIntegerValue(
3600,
environ_name="INTEGRATIONS_JWT_EXPIRATION_SECONDS",
environ_prefix=None,
)
INTEGRATIONS_JWT_TOKEN_TYPE = values.Value(
"Bearer",
environ_name="INTEGRATIONS_JWT_TOKEN_TYPE",
environ_prefix=None,
)
INTEGRATIONS_APP_BASE_URL = values.Value(
None,
environ_name="INTEGRATIONS_APP_BASE_URL",
environ_prefix=None,
)
# pylint: disable=invalid-name
@property
def ENVIRONMENT(self):
-1
View File
@@ -40,7 +40,6 @@ dependencies = [
"django-timezone-field>=5.1",
"django==5.2.6",
"djangorestframework==3.16.0",
"djangorestframework-api-key==3.1.0",
"drf_spectacular==0.28.0",
"dockerflow==2024.4.2",
"easy_thumbnails==2.10",
+1 -1
View File
@@ -38,7 +38,7 @@ RUN npm run build
FROM nginxinc/nginx-unprivileged:alpine3.21 AS frontend-production
USER root
RUN apk update && apk upgrade libssl3 libcrypto3 libxml2>=2.12.7-r2 libxslt>=1.1.39-r2 libexpat>=2.7.2-r0
RUN apk update && apk upgrade libssl3 libcrypto3 libxml2>=2.12.7-r2 libxslt>=1.1.39-r2
USER nginx
@@ -73,8 +73,6 @@ backend:
ROOM_TELEPHONY_PHONE_NUMBER: '+33901020304'
SSL_CERT_FILE: /usr/local/lib/python3.13/site-packages/certifi/cacert.pem
ROOM_SUBTITLE_ENABLED: True
INTEGRATIONS_JWT_SECRET_KEY: {{ .Values.integrationJwtSecretKey }}
INTEGRATIONS_APP_BASE_URL: https://meet.127.0.0.1.nip.io
migrate:
@@ -1,5 +1,4 @@
djangoSecretKey: 7K9mQ2xR8pL3vN6tY1sW4jH5cE0zF9bM2qA7uI3oP6rT1wErt12te12
integrationJwtSecretKey: devKey
livekit:
keys:
devkey: secret
+1 -1
View File
@@ -1,4 +1,4 @@
apiVersion: v2
type: application
name: meet
version: 0.0.11-debug
version: 0.0.12
-14
View File
@@ -74,20 +74,6 @@ spec:
serviceName: {{ include "meet.backend.fullname" . }}
servicePort: {{ .Values.backend.service.port }}
{{- end }}
- path: /external-api/
{{- 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 }}
+2 -2
View File
@@ -118,9 +118,9 @@ class MetadataManager:
"retries": 0,
}
_required_args_count = 8
_required_args_count = 7
if len(task_args) != _required_args_count:
logger.error("Invalid number of arguments to enable metadata manager.")
logger.error("Invalid number of arguments.")
return
filename, email, _, received_at, *_ = task_args