Access token never expires APIv2 1.36.35

Discussions related to the 1.36.x series of ZoneMinder
Post Reply
alextuning
Posts: 7
Joined: Tue Sep 17, 2024 4:48 am

Access token never expires APIv2 1.36.35

Post by alextuning »

Hi @iconnor and happy NY!

I recently faced an issue that on Ubuntu 22.04.5 ZM 1.36.35 access token received with v2.0 API is not getting expired.

Using same token I can access a video stream days later. I mean that every time a new connection is initiated with same access token (not a case viewtopic.php?t=30611) I find the token is not expired :D

Code: Select all

OPT_USE_AUTH      enabled
AUTH_HASH_SECRET  defined
OPT_USE_AP        enabled
OPT_USE_LEGACY_API_AUTH  enabled (tried to disable as well - same)
AUTH_HASH_TTL     2
Can you please reproduce the issue on your environment and confirm?
User avatar
iconnor
Posts: 3286
Joined: Fri Oct 29, 2010 1:43 am
Location: Toronto
Contact:

Re: Access token never expires APIv2 1.36.35

Post by iconnor »

This is interesting, because one issue I face is I have a customer who for whatever reason is using an old zmNinja which waits for the token to expire before getting a new one, which spams the logs. So in newer zmNinja I get a new token BEFORE the current one expires.
alextuning
Posts: 7
Joined: Tue Sep 17, 2024 4:48 am

Re: Access token never expires APIv2 1.36.35

Post by alextuning »

@iconnor, I was able to reproduce the issue on my fresh test environment Ubuntu 22.04.5 with ZM 1.36.35.
Might probably, the issue lays on service level as path /zm/api/host/monitors.json returns 401 Unauthorized "Expired token" as expected but camera stream still accessible. "Revoke all tokens" via web UI works fine - can not get video stream with token. Log file contains not any meaningful information.

Can you give me some steps how to fix this?

Code: Select all

root@zm:~# date
Sun Jan  5 08:09:20 AM UTC 2025
root@zm:~# curl -sk -d 'token=eyJ0eXAiOiJKV1QiLCJhbGci<...>sdwU' http://localhost/zm/api/host/monitors.json | jq .
{
  "success": false,
  "data": {
    "name": "Expired token",
    "message": "Expired token",
    "url": "/zm/api/host/monitors.json",
    "exception": {
      "class": "UnauthorizedException",
      "code": 401,
      "message": "Expired token"
    }
  }
}
root@zm:~# curl -vk 'http://localhost/zm/cgi-bin/nph-zms?mode=jpeg&monitor=1&token=eyJ0eXAiOiJKV1QiLCJhbGci<...>sdwU'
*   Trying 127.0.0.1:80...
* Connected to localhost (127.0.0.1) port 80 (#0)
> GET /zm/cgi-bin/nph-zms?mode=jpeg&monitor=1&token=eyJ0eXAiOiJKV1QiLCJhbGci<...>sdwU HTTP/1.1
> Host: localhost
> User-Agent: curl/7.81.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
< Server: ZoneMinder Video Server/1.36.35
< Last-Modified: Sun, 05 Jan 2025 08:09:23 GMT
< Expires: Mon, 26 Jul 1997 05:00:00 GMT
< Cache-Control: no-store, no-cache, must-revalidate
< Cache-Control: post-check=0, pre-check=0
< Pragma: no-cache
< Content-Type: multipart/x-mixed-replace; boundary=ZoneMinderFrame
<
Warning: Binary output can mess up your terminal. Use "--output -" to tell
Warning: curl to output it to your terminal anyway, or consider "--output
Warning: <FILE>" to save to a file.
* Failure writing output to destination
* Closing connection 0
User avatar
iconnor
Posts: 3286
Joined: Fri Oct 29, 2010 1:43 am
Location: Toronto
Contact:

Re: Access token never expires APIv2 1.36.35

Post by iconnor »

I wonder is your system time zone and php/zm timezone synced up?

Otherwise I'll have to dig in see if I can figure it out.
alextuning
Posts: 7
Joined: Tue Sep 17, 2024 4:48 am

Re: Access token never expires APIv2 1.36.35

Post by alextuning »

Just made sure php/system/mysql timezones are the same:

Code: Select all

root@zm:~ # timedatectl | egrep "Time zone|synchronized"
                Time zone: Asia/Novosibirsk (+07, +0700)
System clock synchronized: yes

mysql> SELECT @@system_time_zone;
+--------------------+
| @@system_time_zone |
+--------------------+
| +07                |
+--------------------+
1 row in set (0.00 sec)

mysql> SELECT TIMEDIFF(NOW(), UTC_TIMESTAMP);
+--------------------------------+
| TIMEDIFF(NOW(), UTC_TIMESTAMP) |
+--------------------------------+
| 07:00:00                       |
+--------------------------------+
1 row in set (0.00 sec)

root@zm:~ # grep "timezone" /etc/php/8.1/apache2/php.ini
date.timezone = 'Asia/Novosibirsk'
alextuning
Posts: 7
Joined: Tue Sep 17, 2024 4:48 am

Re: Access token never expires APIv2 1.36.35

Post by alextuning »

iconnor wrote: Mon Jan 06, 2025 9:21 pm I wonder is your system time zone and php/zm timezone synced up?

Otherwise I'll have to dig in see if I can figure it out.
Dear @iconnor,

I hope this message finds you well.
I wanted to kindly inquire if there have been any updates regarding this matter?
alextuning
Posts: 7
Joined: Tue Sep 17, 2024 4:48 am

Re: Access token never expires APIv2 1.36.35

Post by alextuning »

iconnor wrote: Mon Jan 06, 2025 9:21 pm I wonder is your system time zone and php/zm timezone synced up?

Otherwise I'll have to dig in see if I can figure it out.
Hi @iconnor,
Is there any way to call "Revoke all tokens" (or specific user) periodically as workaround?
User avatar
iconnor
Posts: 3286
Joined: Fri Oct 29, 2010 1:43 am
Location: Toronto
Contact:

Re: Access token never expires APIv2 1.36.35

Post by iconnor »

Yes there is a way.. it's in the user options.. it isn't ideal but if you look in the code you should see how to do it.

A revamp of all this is on the todo list, but has been for like 2 years..
alextuning
Posts: 7
Joined: Tue Sep 17, 2024 4:48 am

Re: Access token never expires APIv2 1.36.35

Post by alextuning »

iconnor wrote: Tue Jan 21, 2025 11:42 pm Yes there is a way.. it's in the user options.. it isn't ideal but if you look in the code you should see how to do it.

A revamp of all this is on the todo list, but has been for like 2 years..
Hope someday this is got properly fixed :)
To whom it may concern as workaround a simple python script to revoke all tokens bellow:

Code: Select all

import mysql.connector
from mysql.connector import Error
import os
import time

class DatabaseManager:
    def __init__(self, config_file_path):
        self.config = self.load_env(config_file_path)

    def load_env(self, file_path):
        if not os.path.exists(file_path):
            raise FileNotFoundError(f"Configuration file not found: {file_path}")

        config = {}
        with open(file_path, 'r') as file:
            for line in file:
                line = line.strip()
                if not line or line.startswith('#'): 
                    continue
                key, value = line.split('=', 1)
                config[key.strip()] = value.strip()

        return {
            'ZM_DB_HOST': config.get('ZM_DB_HOST', 'localhost'),
            'ZM_DB_NAME': config.get('ZM_DB_NAME', ''),
            'ZM_DB_USER': config.get('ZM_DB_USER', ''),
            'ZM_DB_PASS': config.get('ZM_DB_PASS', '')
        }

    def db_connect(self):
        try:
            conn = mysql.connector.connect(
                host=self.config['ZM_DB_HOST'],
                database=self.config['ZM_DB_NAME'],
                user=self.config['ZM_DB_USER'],
                password=self.config['ZM_DB_PASS']
            )
            return conn
        except Error as e:
            print(f"Error connecting to MySQL database: {str(e)}")
            return None

    def db_query(self, query, params=None):
        conn = self.db_connect()
        if conn is None:
            return None

        cursor = conn.cursor()
        try:
            if params:
                cursor.execute(query, params)
            else:
                cursor.execute(query)

            conn.commit()
            return cursor
        except Error as e:
            print(f"SQL Error: {str(e)}")
            return None
        finally:
            cursor.close()
            conn.close()

    def revoke_all_tokens(self):
        min_token_time = int(time.time())
        result = self.db_query('UPDATE `Users` SET `TokenMinExpiry` = %s', (min_token_time,))
        if result is not None:
            print('All tokens have been revoked.')
        else:
            print('Failed to revoke tokens.')

if __name__ == '__main__':
    config_file_path = '/etc/zm/zm.conf'
    db_manager = DatabaseManager(config_file_path)
    db_manager.revoke_all_tokens()
Post Reply