Skip to content

Native app clients get HTTP 404 on POST /api/v4/call/{token} β€” ParticipantService actorCache returns session-less participant for app-password authΒ #17350

@Frisch12

Description

@Frisch12

How to use GitHub


Steps to reproduce

  1. Authenticate a native Talk client (iOS v23.0.1) via app-password/token authentication (no PHP session cookie).
  2. Join or receive a call in any conversation room.
  3. The client calls POST /api/v4/call/{token} to join the call.

Expected behaviour

The call should be joined successfully (HTTP 200), just as it works in the browser for the same user, room, and Talk version.

Actual behaviour

The API returns HTTP 404. The iOS app displays:

"Anruf kann nicht angenommen werden / Unterhaltung nicht gefunden oder nicht beigetreten"
("Call cannot be accepted / Conversation not found or not joined")

This occurs 100% of the time on native clients. Browser-based calls work fine.

Root cause: Manager::getRoomForUserByToken() caches a Participant without a session in ParticipantService::$actorCache when $sessionId is null. For native app clients, TalkSession::getSessionForRoom() returns null because there is no PHP session cookie β€” the OCP\ISession is a stateless in-memory session that doesn't persist between requests.

Detailed trace
  1. InjectionMiddleware::getLoggedInOrGuest() calls Manager::getRoomForUserByToken($token, $userId, null).

  2. In [getRoomForUserByToken()](https://github.com/nextcloud/spreed/blob/v23.0.1/lib/Manager.php#L700), the session LEFT JOIN is skipped because $sessionId is null:

if ($sessionId !== null) {  // null for app-password auth β†’ SKIPPED
    $helper->selectSessionsTable($query);
    $query->leftJoin('a', 'talk_sessions', 's', ...);
}
  1. The participant is created without session data and cached:
$participant = $this->createParticipantObject($room, $row);
$this->participantService->cacheParticipant($room, $participant);  // cached WITHOUT session
  1. Back in getLoggedInOrGuest(), getParticipant($room, $userId) is called with default $sessionId = null.

  2. In [ParticipantService::getParticipant()](https://github.com/nextcloud/spreed/blob/v23.0.1/lib/Service/ParticipantService.php#L2233), the !$sessionId shortcut returns the cached null-session participant β€” the DB LEFT JOIN to talk_sessions never executes:

if (isset($this->actorCache[...][...][...])) {
    $participant = $this->actorCache[...];
    if (!$sessionId  // !null β†’ true β†’ RETURNS CACHED WITHOUT SESSION
        || ($participant->getSession() instanceof Session
            && $participant->getSession()->getSessionId() === $sessionId)) {
        return $participant;
    }
}
  1. CallController::joinCall() checks $this->participant->getSession() β†’ null β†’ returns HTTP 404.
Proof

Adding debug instrumentation to joinCall() to query talk_sessions directly at the moment of the 404 shows 10 active sessions (state=1) for the user in the database. The sessions exist β€” they are just never loaded.

Proposed fix

In ParticipantService::getParticipant(), change the actorCache hit condition so that a null-session cached participant is not returned when the caller requests a session ($sessionId = null):

// Before:
if (!$sessionId
    || ($participant->getSession() instanceof Session
        && $participant->getSession()->getSessionId() === $sessionId)) {
    return $participant;
}

// After:
if ($sessionId === false
    || ($participant->getSession() instanceof Session
        && (!$sessionId || $participant->getSession()->getSessionId() === $sessionId))) {
    return $participant;
}

This ensures:

  • $sessionId === false (caller doesn't need session) β†’ return cached, preserving performance.
  • $sessionId === null (caller wants any session) β†’ only return cached if it actually has a session; otherwise fall through to DB query.
  • $sessionId === "specific" β†’ only return cached if it matches.

Related issues:

Note: The // FIXME PROBLEM comment at the null-$sessionId LEFT JOIN branch in getParticipant() suggests this area was already known to be problematic.

Talk app

Talk app version: v23.0.1

**Custom Signaling server configured: ** strukturag/nextcloud-spreed-signaling:latest

Browser

Not applicable β€” this bug affects native app clients only. Browser-based calls work correctly.

Microphone available: n/a

Camera available: n/a

Operating system: iOS (Talk iOS app v23.0.1)

Browser name: n/a

Browser version: n/a

Browser log

Details
n/a β€” native app issue, not browser-related.

Server configuration

Operating system: Docker FPM Alpine 33.0.0

Web server: Nginx (behind nginx ingress, 2 Nextcloud replicas)

Database: MariaDB Galera (3-node cluster)

Nextcloud Version: 33.0.0

List of activated apps:

Details
Enabled:
  - activity: 6.0.0-dev.0
  - admin_audit: 1.23.0
  - app_api: 33.0.0
  - approval: 3.1.0
  - bruteforcesettings: 6.0.0-dev.0
  - calendar: 6.2.1
  - circles: 33.0.0
  - cloud_federation_api: 1.17.0
  - collectives: 4.0.0
  - comments: 1.23.0
  - contacts: 8.4.1
  - contactsinteraction: 1.14.1
  - dashboard: 7.13.0
  - dav: 1.36.0
  - deck: 1.17.0
  - federatedfilesharing: 1.23.0
  - federation: 1.23.0
  - files: 2.5.0
  - files_downloadlimit: 5.1.0-dev.0
  - files_pdfviewer: 6.0.0-dev.0
  - files_reminders: 1.6.0
  - files_sharing: 1.25.2
  - files_trashbin: 1.23.0
  - files_versions: 1.26.0
  - firstrunwizard: 6.0.0-dev.0
  - forms: 5.2.5
  - groupfolders: 21.0.6
  - logreader: 6.0.0
  - lookup_server_connector: 1.21.0
  - mail: 5.7.2
  - nextcloud_announcements: 5.0.0
  - notes: 4.13.0
  - notifications: 6.0.0
  - notify_push: 1.3.0
  - oauth2: 1.21.0
  - password_policy: 5.0.0-dev.0
  - photos: 6.0.0-dev.0
  - privacy: 5.0.0-dev.0
  - profile: 1.2.0
  - provisioning_api: 1.23.0
  - recommendations: 6.0.0-dev.0
  - related_resources: 4.0.0-dev.0
  - richdocuments: 10.1.0
  - serverinfo: 5.0.0-dev.0
  - settings: 1.16.0
  - sharebymail: 1.23.0
  - spreed: 23.0.1
  - support: 5.0.0
  - survey_client: 5.0.0-dev.0
  - suspicious_login: 11.0.0-dev.0
  - systemtags: 1.23.0
  - tasks: 0.17.1
  - text: 7.0.0-dev.3
  - theming: 2.8.0
  - twofactor_backupcodes: 1.22.0
  - twofactor_totp: 15.0.0-dev.0
  - updatenotification: 1.23.0
  - user_ldap: 1.24.0
  - user_status: 1.13.0
  - viewer: 6.0.0-dev.0
  - weather_status: 1.13.0
  - webhook_listeners: 1.5.0
  - whiteboard: 1.5.7
  - workflowengine: 2.15.0
Disabled:
  - encryption: 2.21.0
  - files_external: 1.25.1
  - twofactor_nextcloud_notification: 7.0.0

Nextcloud configuration:

Details
{
    "system": {
        "passwordsalt": "***REMOVED SENSITIVE VALUE***",
        "secret": "***REMOVED SENSITIVE VALUE***",
        "trusted_domains": [
            "localhost",
            "nextcloud.nextcloud-test.example.org",
            "nextcloud-chart.nextcloud.svc.cluster.local"
        ],
        "datadirectory": "***REMOVED SENSITIVE VALUE***",
        "dbtype": "mysql",
        "version": "33.0.0.16",
        "overwrite.cli.url": "https:\/\/nextcloud.nextcloud-test.example.org",
        "dbname": "***REMOVED SENSITIVE VALUE***",
        "dbhost": "***REMOVED SENSITIVE VALUE***",
        "dbport": "",
        "dbtableprefix": "oc_",
        "mysql.utf8mb4": true,
        "dbuser": "***REMOVED SENSITIVE VALUE***",
        "dbpassword": "***REMOVED SENSITIVE VALUE***",
        "installed": true,
        "instanceid": "***REMOVED SENSITIVE VALUE***",
        "htaccess.RewriteBase": "\/",
        "memcache.local": "\\OC\\Memcache\\APCu",
        "apps_paths": [
            {
                "path": "\/var\/www\/html\/apps",
                "url": "\/apps",
                "writable": false
            },
            {
                "path": "\/var\/www\/html\/custom_apps",
                "url": "\/custom_apps",
                "writable": true
            }
        ],
        "memcache.distributed": "\\OC\\Memcache\\Redis",
        "memcache.locking": "\\OC\\Memcache\\Redis",
        "redis": {
            "host": "***REMOVED SENSITIVE VALUE***",
            "password": "***REMOVED SENSITIVE VALUE***",
            "port": 26379,
            "masterName": "nextcloud-redis",
            "sentinels": [
                {
                    "host": "nextcloud-chart-redis.nextcloud.svc.cluster.local",
                    "port": 26379
                }
            ],
            "timeout": 1.5,
            "read_timeout": 1.5
        },
        "overwriteprotocol": "https",
        "upgrade.disable-web": true,
        "app_install_overwrite": [
            "spreed",
            "mail",
            "calendar",
            "contacts",
            "tasks",
            "deck",
            "notes",
            "forms",
            "collectives",
            "whiteboard",
            "richdocuments",
            "groupfolders",
            "user_ldap",
            "twofactor_totp",
            "admin_audit",
            "suspicious_login",
            "terms_of_service",
            "approval",
            "notify_push"
        ],
        "ldapProviderFactory": "OCA\\User_LDAP\\LDAPProviderFactory",
        "trusted_proxies": "***REMOVED SENSITIVE VALUE***",
        "appstoreenabled": false,
        "updatechecker": false,
        "loglevel": 2,
        "maintenance": false,
        "log_type": "file",
        "logfile": "\/var\/www\/html\/data\/nextcloud.log",
        "default_phone_region": "DE",
        "skeletondirectory": "",
        "default_locale": "de_DE",
        "maintenance_window_start": "1"
    }
}

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions