fix: include username in auth relay and fix stale auth in stream restart

- Add user= parameter to get_auth_relay() so zms can use the indexed
  Username column instead of iterating all users to validate the hash
- Apply the same fix to Event.php getStreamSrc() and getThumbnailSrc()
- Tighten Monitor.php from isset() to !empty() for consistency
- In MonitorStream.js start(), check if the auth hash in the img src
  matches the current auth_hash before resuming via CMD_PLAY. If stale,
  fall through to rebuild the URL with fresh auth_relay. This prevents
  long-running montage pages from spawning zms with expired credentials.
- Downgrade zms auth failure from Error to Warning

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
pull/4707/merge
Isaac Connor 2026-03-16 10:01:47 -04:00
parent dcb12e6f2a
commit 5561829450
5 changed files with 26 additions and 8 deletions

View File

@ -259,7 +259,7 @@ int main(int argc, const char *argv[], char **envp) {
fputs("HTTP/1.0 403 Forbidden\r\n\r\n", stdout);
const char *referer = getenv("HTTP_REFERER");
Error("Unable to authenticate user from %s", referer);
Warning("Unable to authenticate user from %s", referer);
return exit_zm(0);
}
if ( !ValidateAccess(user, monitor_id) ) {

View File

@ -291,6 +291,9 @@ class Event extends ZM_Object {
if ( ZM_OPT_USE_AUTH ) {
if ( ZM_AUTH_RELAY == 'hashed' ) {
$args['auth'] = generateAuthHash(ZM_AUTH_HASH_IPS);
// Include username so zms can filter by indexed Username column
// instead of iterating all users to validate the auth hash
if (!empty($_SESSION['username'])) $args['user'] = $_SESSION['username'];
} else if ( ZM_AUTH_RELAY == 'plain' ) {
$args['user'] = $_SESSION['username'];
$args['pass'] = $_SESSION['password'];
@ -406,6 +409,9 @@ class Event extends ZM_Object {
if ( ZM_OPT_USE_AUTH ) {
if ( ZM_AUTH_RELAY == 'hashed' ) {
$args['auth'] = generateAuthHash(ZM_AUTH_HASH_IPS);
// Include username so zms can filter by indexed Username column
// instead of iterating all users to validate the auth hash
if (!empty($_SESSION['username'])) $args['user'] = $_SESSION['username'];
} else if ( ZM_AUTH_RELAY == 'plain' ) {
$args['user'] = $_SESSION['username'];
$args['pass'] = $_SESSION['password'];

View File

@ -553,8 +553,9 @@ class Monitor extends ZM_Object {
if (ZM_OPT_USE_AUTH) {
if (ZM_AUTH_RELAY == 'hashed') {
$args['auth'] = generateAuthHash(ZM_AUTH_HASH_IPS);
# Include user so that db lookups can be more efficient
$args['user'] = isset($_SESSION['username']) ? $_SESSION['username'] : '';
// Include username so zms can filter by indexed Username column
// instead of iterating all users to validate the auth hash
if (!empty($_SESSION['username'])) $args['user'] = $_SESSION['username'];
} elseif ( ZM_AUTH_RELAY == 'plain' ) {
$args['user'] = isset($_SESSION['username']) ? $_SESSION['username'] : '';
$args['pass'] = isset($_SESSION['password']) ? $_SESSION['password'] : '';

View File

@ -504,7 +504,13 @@ function userFromSession() {
function get_auth_relay() {
if (ZM_OPT_USE_AUTH) {
if (ZM_AUTH_RELAY == 'hashed') {
return 'auth='.generateAuthHash(ZM_AUTH_HASH_IPS);
$relay = 'auth='.generateAuthHash(ZM_AUTH_HASH_IPS);
// Include username so zms can filter by indexed Username column
// instead of iterating all users to validate the auth hash
if (!empty($_SESSION['username'])) {
$relay .= '&user='.$_SESSION['username'];
}
return $relay;
} else if (ZM_AUTH_RELAY == 'plain') {
// password probably needs to be escaped
return 'username='.(isset($_SESSION['username'])?$_SESSION['username']:'').'&password='.urlencode(isset($_SESSION['password']) ? $_SESSION['password'] : '');

View File

@ -590,12 +590,17 @@ function MonitorStream(monitorData) {
}
stream.onerror = this.img_onerror.bind(this);
stream.onload = this.img_onload.bind(this);
if (this.activePlayer == 'zms') {
// Only if we were already zms streaming
// Check if the auth hash in the current img src is still valid.
// On long-running pages the hash from page load may have expired.
const srcAuthMatch = stream.src ? stream.src.match(/auth=(\w+)/i) : null;
const srcAuthCurrent = srcAuthMatch && srcAuthMatch[1] === auth_hash;
if (srcAuthCurrent && this.activePlayer == 'zms') {
// Auth is current and zms was already the active player — just resume
this.streamCmdTimer = setInterval(this.streamCmdQuery.bind(this), statusRefreshTimeout);
this.streamCommand(CMD_PLAY);
} else if (-1 != stream.src.indexOf('mode=paused')) {
// Initial page load has zms with mode=paused
} else if (srcAuthCurrent && (-1 != stream.src.indexOf('mode=paused'))) {
// Initial page load has zms with mode=paused, auth is still valid
this.streamCmdTimer = setInterval(this.streamCmdQuery.bind(this), statusRefreshTimeout);
this.streamCommand(CMD_PLAY);
} else {