From 7e0e5c9266de6ea4ad529ed5b102191f59528c2a Mon Sep 17 00:00:00 2001 From: Samuel Hassine Date: Sun, 22 Feb 2026 12:47:33 +0100 Subject: [PATCH 1/7] feat: XTM One --- .../opencti-graphql/config/default.json | 3 +- .../opencti-graphql/src/manager/index.ts | 1 + .../src/manager/xtmOneRegistrationManager.ts | 44 ++++++++++ .../src/modules/xtm/one/xtm-one-client.ts | 58 +++++++++++++ .../src/modules/xtm/one/xtm-one.ts | 84 +++++++++++++++++++ 5 files changed, 189 insertions(+), 1 deletion(-) create mode 100644 opencti-platform/opencti-graphql/src/manager/xtmOneRegistrationManager.ts create mode 100644 opencti-platform/opencti-graphql/src/modules/xtm/one/xtm-one-client.ts create mode 100644 opencti-platform/opencti-graphql/src/modules/xtm/one/xtm-one.ts diff --git a/opencti-platform/opencti-graphql/config/default.json b/opencti-platform/opencti-graphql/config/default.json index 5acb65f7ec36..9da39a73ce53 100644 --- a/opencti-platform/opencti-graphql/config/default.json +++ b/opencti-platform/opencti-graphql/config/default.json @@ -213,7 +213,8 @@ "timeout": 60000 }, "xtm": { - "xtm_one_url": "https://common.xtm1.filigran.io", + "xtm_one_url": "", + "xtm_one_token": "", "openaev_url": "", "openaev_token": "", "openaev_reject_unauthorized": false, diff --git a/opencti-platform/opencti-graphql/src/manager/index.ts b/opencti-platform/opencti-graphql/src/manager/index.ts index 1741cfd3bf1f..12a77e19b518 100644 --- a/opencti-platform/opencti-graphql/src/manager/index.ts +++ b/opencti-platform/opencti-graphql/src/manager/index.ts @@ -1,6 +1,7 @@ import './indicatorDecayManager'; import './garbageCollectionManager'; import './hubRegistrationManager'; +import './xtmOneRegistrationManager'; import './telemetryManager'; import './retentionManager'; import './exclusionListCacheBuildManager'; diff --git a/opencti-platform/opencti-graphql/src/manager/xtmOneRegistrationManager.ts b/opencti-platform/opencti-graphql/src/manager/xtmOneRegistrationManager.ts new file mode 100644 index 000000000000..0723369d6507 --- /dev/null +++ b/opencti-platform/opencti-graphql/src/manager/xtmOneRegistrationManager.ts @@ -0,0 +1,44 @@ +import conf from '../config/conf'; +import { logApp } from '../config/conf'; +import { executionContext } from '../utils/access'; +import { SYSTEM_USER } from '../utils/access'; +import { registerWithXtmOne } from '../modules/xtm/one/xtm-one'; +import type { ManagerDefinition } from './managerModule'; +import { registerManager } from './managerModule'; + +const XTM_ONE_URL = conf.get('xtm:xtm_one_url'); +const XTM_ONE_TOKEN = conf.get('xtm:xtm_one_token'); +const XTM_ONE_ENABLED = !!(XTM_ONE_URL && XTM_ONE_TOKEN); +const SCHEDULE_TIME = conf.get('hub_registration_manager:interval') || 60 * 60 * 1000; // 1 hour + +export const xtmOneRegistrationManager = async () => { + const context = executionContext('xtm_one_registration_manager'); + try { + await registerWithXtmOne(context, SYSTEM_USER); + } catch (error: any) { + logApp.error('[XTM One] Registration manager error', { error: error.message }); + } +}; + +const XTM_ONE_REGISTRATION_MANAGER_DEFINITION: ManagerDefinition = { + id: 'XTM_ONE_REGISTRATION_MANAGER', + label: 'XTM One registration manager', + executionContext: 'xtm_one_registration_manager', + cronSchedulerHandler: { + handler: xtmOneRegistrationManager, + interval: SCHEDULE_TIME, + lockKey: 'xtm_one_registration_manager_lock', + }, + enabledByConfig: XTM_ONE_ENABLED, + enabledToStart(): boolean { + return this.enabledByConfig; + }, + enabled(): boolean { + return this.enabledByConfig; + }, + warning(): boolean { + return XTM_ONE_ENABLED && (!XTM_ONE_URL || !XTM_ONE_TOKEN); + }, +}; + +registerManager(XTM_ONE_REGISTRATION_MANAGER_DEFINITION); diff --git a/opencti-platform/opencti-graphql/src/modules/xtm/one/xtm-one-client.ts b/opencti-platform/opencti-graphql/src/modules/xtm/one/xtm-one-client.ts new file mode 100644 index 000000000000..988a61971217 --- /dev/null +++ b/opencti-platform/opencti-graphql/src/modules/xtm/one/xtm-one-client.ts @@ -0,0 +1,58 @@ +import axios from 'axios'; +import conf from '../../../config/conf'; +import { logApp } from '../../../config/conf'; + +const XTM_ONE_URL = conf.get('xtm:xtm_one_url'); +const XTM_ONE_TOKEN = conf.get('xtm:xtm_one_token'); + +export interface XtmOneUserEntry { + email: string; + display_name: string; + api_key: string; +} + +export interface XtmOneRegistrationInput { + platform_identifier: string; + platform_url: string; + platform_title: string; + platform_version: string; + platform_id: string; + enterprise_license_pem: string | undefined; + users: XtmOneUserEntry[]; +} + +export interface XtmOneRegistrationResponse { + status: string; + platform_identifier: string; + ee_enabled: boolean; + user_integrations: number; +} + +const xtmOneClient = { + isConfigured: (): boolean => { + return !!(XTM_ONE_URL && XTM_ONE_TOKEN); + }, + + register: async (input: XtmOneRegistrationInput): Promise => { + if (!XTM_ONE_URL || !XTM_ONE_TOKEN) { + return null; + } + try { + const url = `${XTM_ONE_URL}/api/v1/platform/register`; + const response = await axios.post(url, input, { + headers: { + Authorization: `Bearer ${XTM_ONE_TOKEN}`, + 'Content-Type': 'application/json', + }, + timeout: 15000, + }); + return response.data; + } catch (error: any) { + logApp.error('[XTM One] Registration failed', { error: error.message }); + return null; + } + }, + +}; + +export default xtmOneClient; diff --git a/opencti-platform/opencti-graphql/src/modules/xtm/one/xtm-one.ts b/opencti-platform/opencti-graphql/src/modules/xtm/one/xtm-one.ts new file mode 100644 index 000000000000..cb6485e9bf21 --- /dev/null +++ b/opencti-platform/opencti-graphql/src/modules/xtm/one/xtm-one.ts @@ -0,0 +1,84 @@ +import { PLATFORM_VERSION } from '../../../config/conf'; +import { logApp } from '../../../config/conf'; +import type { AuthContext, AuthUser } from '../../../types/user'; +import type { BasicStoreSettings } from '../../../types/settings'; +import { getEntityFromCache, getEntitiesListFromCache } from '../../../database/cache'; +import { ENTITY_TYPE_SETTINGS } from '../../../schema/internalObject'; +import { ENTITY_TYPE_USER } from '../../../schema/internalObject'; +import { getEnterpriseEditionActivePem } from '../../settings/licensing'; +import { addUserTokenByAdmin } from '../../user/user-domain'; +import xtmOneClient from './xtm-one-client'; +import type { XtmOneUserEntry } from './xtm-one-client'; + +const XTM_ONE_TOKEN_NAME = 'XTM One'; + +/** + * Register this OpenCTI instance with XTM One. + * + * Called on every tick by the XTM One registration manager. The /register + * endpoint is an upsert so repeated calls are safe and serve as both + * initial registration and periodic heartbeat. + * + * For each user, we check if an "XTM One" named token already exists. + * If not, we create one and include the plaintext in the registration. + * Users whose token was already created (and plaintext is no longer + * available) are still included — XTM One will upsert based on email. + */ +export const registerWithXtmOne = async (context: AuthContext, user: AuthUser): Promise => { + if (!xtmOneClient.isConfigured()) { + return; + } + + const settings = await getEntityFromCache(context, user, ENTITY_TYPE_SETTINGS); + if (!settings) { + logApp.warn('[XTM One] Cannot register: settings not available'); + return; + } + + const { pem } = getEnterpriseEditionActivePem(settings); + + const users: XtmOneUserEntry[] = []; + try { + const allUsers = await getEntitiesListFromCache(context, user, ENTITY_TYPE_USER); + for (const u of allUsers) { + if (!u.user_email) continue; + const apiTokens = (u as any).api_tokens ?? []; + const existingXtmToken = apiTokens.find((t: any) => t.name === XTM_ONE_TOKEN_NAME); + if (existingXtmToken) { + continue; + } + try { + const newToken = await addUserTokenByAdmin(context, user, u.id, { name: XTM_ONE_TOKEN_NAME }); + users.push({ + email: u.user_email, + display_name: u.name || u.user_email, + api_key: newToken.plaintext_token, + }); + } catch (tokenErr: any) { + logApp.warn('[XTM One] Failed to create token for user', { email: u.user_email, error: tokenErr.message }); + } + } + } catch (err: any) { + logApp.warn('[XTM One] Failed to collect users', { error: err.message }); + } + + const result = await xtmOneClient.register({ + platform_identifier: 'opencti', + platform_url: settings.platform_url || '', + platform_title: settings.platform_title || 'OpenCTI Platform', + platform_version: PLATFORM_VERSION, + platform_id: settings.internal_id || settings.id, + enterprise_license_pem: pem, + users, + }); + + if (result) { + logApp.info('[XTM One] Registration successful', { + status: result.status, + ee_enabled: result.ee_enabled, + user_integrations: result.user_integrations, + }); + } else { + logApp.warn('[XTM One] Registration failed, will retry on next tick'); + } +}; From 3a82e3821b4a7ce7f5696d544aa3b4cb255e6029 Mon Sep 17 00:00:00 2001 From: Samuel Hassine Date: Sun, 22 Feb 2026 15:20:12 +0100 Subject: [PATCH 2/7] Fix --- .../src/manager/xtmOneRegistrationManager.ts | 21 +++++++++---- .../src/modules/xtm/one/xtm-one-client.ts | 4 +-- .../src/modules/xtm/one/xtm-one.ts | 30 ++++++++++--------- 3 files changed, 32 insertions(+), 23 deletions(-) diff --git a/opencti-platform/opencti-graphql/src/manager/xtmOneRegistrationManager.ts b/opencti-platform/opencti-graphql/src/manager/xtmOneRegistrationManager.ts index 0723369d6507..92e6a93ca90e 100644 --- a/opencti-platform/opencti-graphql/src/manager/xtmOneRegistrationManager.ts +++ b/opencti-platform/opencti-graphql/src/manager/xtmOneRegistrationManager.ts @@ -1,7 +1,5 @@ -import conf from '../config/conf'; -import { logApp } from '../config/conf'; -import { executionContext } from '../utils/access'; -import { SYSTEM_USER } from '../utils/access'; +import conf, { logApp } from '../config/conf'; +import { executionContext, SYSTEM_USER } from '../utils/access'; import { registerWithXtmOne } from '../modules/xtm/one/xtm-one'; import type { ManagerDefinition } from './managerModule'; import { registerManager } from './managerModule'; @@ -9,7 +7,8 @@ import { registerManager } from './managerModule'; const XTM_ONE_URL = conf.get('xtm:xtm_one_url'); const XTM_ONE_TOKEN = conf.get('xtm:xtm_one_token'); const XTM_ONE_ENABLED = !!(XTM_ONE_URL && XTM_ONE_TOKEN); -const SCHEDULE_TIME = conf.get('hub_registration_manager:interval') || 60 * 60 * 1000; // 1 hour +const SCHEDULE_TIME = 5 * 60 * 1000; // 5 minutes +const BOOT_DELAY = 30_000; // 30 seconds — let the platform finish init export const xtmOneRegistrationManager = async () => { const context = executionContext('xtm_one_registration_manager'); @@ -37,8 +36,18 @@ const XTM_ONE_REGISTRATION_MANAGER_DEFINITION: ManagerDefinition = { return this.enabledByConfig; }, warning(): boolean { - return XTM_ONE_ENABLED && (!XTM_ONE_URL || !XTM_ONE_TOKEN); + return false; }, }; registerManager(XTM_ONE_REGISTRATION_MANAGER_DEFINITION); + +// Fire once at boot so the platform appears in XTM One immediately +// instead of waiting for the first interval tick. +if (XTM_ONE_ENABLED) { + setTimeout(() => { + xtmOneRegistrationManager().catch((err: any) => { + logApp.warn('[XTM One] Boot registration failed, will retry on next tick', { error: err.message }); + }); + }, BOOT_DELAY); +} diff --git a/opencti-platform/opencti-graphql/src/modules/xtm/one/xtm-one-client.ts b/opencti-platform/opencti-graphql/src/modules/xtm/one/xtm-one-client.ts index 988a61971217..41406647b8a6 100644 --- a/opencti-platform/opencti-graphql/src/modules/xtm/one/xtm-one-client.ts +++ b/opencti-platform/opencti-graphql/src/modules/xtm/one/xtm-one-client.ts @@ -1,6 +1,5 @@ import axios from 'axios'; -import conf from '../../../config/conf'; -import { logApp } from '../../../config/conf'; +import conf, { logApp } from '../../../config/conf'; const XTM_ONE_URL = conf.get('xtm:xtm_one_url'); const XTM_ONE_TOKEN = conf.get('xtm:xtm_one_token'); @@ -52,7 +51,6 @@ const xtmOneClient = { return null; } }, - }; export default xtmOneClient; diff --git a/opencti-platform/opencti-graphql/src/modules/xtm/one/xtm-one.ts b/opencti-platform/opencti-graphql/src/modules/xtm/one/xtm-one.ts index cb6485e9bf21..76582220fec1 100644 --- a/opencti-platform/opencti-graphql/src/modules/xtm/one/xtm-one.ts +++ b/opencti-platform/opencti-graphql/src/modules/xtm/one/xtm-one.ts @@ -1,10 +1,9 @@ -import { PLATFORM_VERSION } from '../../../config/conf'; -import { logApp } from '../../../config/conf'; +import { PLATFORM_VERSION, logApp } from '../../../config/conf'; import type { AuthContext, AuthUser } from '../../../types/user'; import type { BasicStoreSettings } from '../../../types/settings'; import { getEntityFromCache, getEntitiesListFromCache } from '../../../database/cache'; -import { ENTITY_TYPE_SETTINGS } from '../../../schema/internalObject'; -import { ENTITY_TYPE_USER } from '../../../schema/internalObject'; +import { internalLoadById } from '../../../database/middleware-loader'; +import { ENTITY_TYPE_SETTINGS, ENTITY_TYPE_USER } from '../../../schema/internalObject'; import { getEnterpriseEditionActivePem } from '../../settings/licensing'; import { addUserTokenByAdmin } from '../../user/user-domain'; import xtmOneClient from './xtm-one-client'; @@ -15,14 +14,14 @@ const XTM_ONE_TOKEN_NAME = 'XTM One'; /** * Register this OpenCTI instance with XTM One. * - * Called on every tick by the XTM One registration manager. The /register + * Called on every tick by the XTM One registration manager. The /register * endpoint is an upsert so repeated calls are safe and serve as both * initial registration and periodic heartbeat. * - * For each user, we check if an "XTM One" named token already exists. - * If not, we create one and include the plaintext in the registration. - * Users whose token was already created (and plaintext is no longer - * available) are still included — XTM One will upsert based on email. + * For each user we load from the DB (not cache) to reliably check whether + * an "XTM One" named token already exists. Only users who receive a + * newly-created token are included in the payload — their plaintext key + * is only available at creation time. */ export const registerWithXtmOne = async (context: AuthContext, user: AuthUser): Promise => { if (!xtmOneClient.isConfigured()) { @@ -42,12 +41,15 @@ export const registerWithXtmOne = async (context: AuthContext, user: AuthUser): const allUsers = await getEntitiesListFromCache(context, user, ENTITY_TYPE_USER); for (const u of allUsers) { if (!u.user_email) continue; - const apiTokens = (u as any).api_tokens ?? []; - const existingXtmToken = apiTokens.find((t: any) => t.name === XTM_ONE_TOKEN_NAME); - if (existingXtmToken) { - continue; - } try { + // Load from DB (not cache) so the api_tokens check is reliable + // and we never accidentally create duplicate "XTM One" tokens. + const freshUser = await internalLoadById(context, user, u.id); + if (!freshUser) continue; + const existingTokens = (freshUser as any).api_tokens ?? []; + if (existingTokens.some((t: any) => t.name === XTM_ONE_TOKEN_NAME)) { + continue; + } const newToken = await addUserTokenByAdmin(context, user, u.id, { name: XTM_ONE_TOKEN_NAME }); users.push({ email: u.user_email, From 0a9b5129f79ac6bd33892f3d094cdde6ccdb0fb7 Mon Sep 17 00:00:00 2001 From: Samuel Hassine Date: Mon, 23 Feb 2026 11:35:53 +0100 Subject: [PATCH 3/7] Fix --- opencti-platform/opencti-graphql/src/domain/user.js | 10 +++++++++- .../src/modules/xtm/one/xtm-one-client.ts | 1 + .../opencti-graphql/src/modules/xtm/one/xtm-one.ts | 5 ++++- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/opencti-platform/opencti-graphql/src/domain/user.js b/opencti-platform/opencti-graphql/src/domain/user.js index 41b18e62f99d..69a3bd3fcae2 100644 --- a/opencti-platform/opencti-graphql/src/domain/user.js +++ b/opencti-platform/opencti-graphql/src/domain/user.js @@ -1930,7 +1930,15 @@ export const authenticateUserFromRequest = async (context, req) => { try { return await authenticateUserByToken(context, req, bearerToken); } catch (err) { - logApp.warn('Error resolving user by token', { cause: err }); + const tokenPrefix = bearerToken.substring(0, 20); + const userAgent = req.headers['user-agent'] || 'unknown'; + const origin = req.headers.origin || req.headers.referer || 'unknown'; + logApp.warn('Error resolving user by token', { + cause: err, + token_prefix: `${tokenPrefix}...`, + user_agent: userAgent, + origin, + }); return undefined; } } diff --git a/opencti-platform/opencti-graphql/src/modules/xtm/one/xtm-one-client.ts b/opencti-platform/opencti-graphql/src/modules/xtm/one/xtm-one-client.ts index 41406647b8a6..85354463abe2 100644 --- a/opencti-platform/opencti-graphql/src/modules/xtm/one/xtm-one-client.ts +++ b/opencti-platform/opencti-graphql/src/modules/xtm/one/xtm-one-client.ts @@ -17,6 +17,7 @@ export interface XtmOneRegistrationInput { platform_version: string; platform_id: string; enterprise_license_pem: string | undefined; + admin_api_key: string; users: XtmOneUserEntry[]; } diff --git a/opencti-platform/opencti-graphql/src/modules/xtm/one/xtm-one.ts b/opencti-platform/opencti-graphql/src/modules/xtm/one/xtm-one.ts index 76582220fec1..df119d8d50d9 100644 --- a/opencti-platform/opencti-graphql/src/modules/xtm/one/xtm-one.ts +++ b/opencti-platform/opencti-graphql/src/modules/xtm/one/xtm-one.ts @@ -1,4 +1,4 @@ -import { PLATFORM_VERSION, logApp } from '../../../config/conf'; +import conf, { PLATFORM_VERSION, logApp } from '../../../config/conf'; import type { AuthContext, AuthUser } from '../../../types/user'; import type { BasicStoreSettings } from '../../../types/settings'; import { getEntityFromCache, getEntitiesListFromCache } from '../../../database/cache'; @@ -64,6 +64,8 @@ export const registerWithXtmOne = async (context: AuthContext, user: AuthUser): logApp.warn('[XTM One] Failed to collect users', { error: err.message }); } + const adminToken = conf.get('app:admin:token') || ''; + const result = await xtmOneClient.register({ platform_identifier: 'opencti', platform_url: settings.platform_url || '', @@ -71,6 +73,7 @@ export const registerWithXtmOne = async (context: AuthContext, user: AuthUser): platform_version: PLATFORM_VERSION, platform_id: settings.internal_id || settings.id, enterprise_license_pem: pem, + admin_api_key: adminToken, users, }); From ba8ba28a72c557aa8ba33e039398dd451e692470 Mon Sep 17 00:00:00 2001 From: Samuel Hassine Date: Mon, 23 Feb 2026 14:39:32 +0100 Subject: [PATCH 4/7] Fix --- .../src/modules/xtm/one/xtm-one-client.ts | 2 ++ .../src/modules/xtm/one/xtm-one.ts | 21 ++++++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/opencti-platform/opencti-graphql/src/modules/xtm/one/xtm-one-client.ts b/opencti-platform/opencti-graphql/src/modules/xtm/one/xtm-one-client.ts index 85354463abe2..ff93afea2089 100644 --- a/opencti-platform/opencti-graphql/src/modules/xtm/one/xtm-one-client.ts +++ b/opencti-platform/opencti-graphql/src/modules/xtm/one/xtm-one-client.ts @@ -17,6 +17,7 @@ export interface XtmOneRegistrationInput { platform_version: string; platform_id: string; enterprise_license_pem: string | undefined; + license_type: string | undefined; admin_api_key: string; users: XtmOneUserEntry[]; } @@ -26,6 +27,7 @@ export interface XtmOneRegistrationResponse { platform_identifier: string; ee_enabled: boolean; user_integrations: number; + chat_web_token: string | null; } const xtmOneClient = { diff --git a/opencti-platform/opencti-graphql/src/modules/xtm/one/xtm-one.ts b/opencti-platform/opencti-graphql/src/modules/xtm/one/xtm-one.ts index df119d8d50d9..0c23622e6009 100644 --- a/opencti-platform/opencti-graphql/src/modules/xtm/one/xtm-one.ts +++ b/opencti-platform/opencti-graphql/src/modules/xtm/one/xtm-one.ts @@ -4,13 +4,17 @@ import type { BasicStoreSettings } from '../../../types/settings'; import { getEntityFromCache, getEntitiesListFromCache } from '../../../database/cache'; import { internalLoadById } from '../../../database/middleware-loader'; import { ENTITY_TYPE_SETTINGS, ENTITY_TYPE_USER } from '../../../schema/internalObject'; -import { getEnterpriseEditionActivePem } from '../../settings/licensing'; +import { decodeLicensePem, getEnterpriseEditionActivePem } from '../../settings/licensing'; import { addUserTokenByAdmin } from '../../user/user-domain'; import xtmOneClient from './xtm-one-client'; import type { XtmOneUserEntry } from './xtm-one-client'; const XTM_ONE_TOKEN_NAME = 'XTM One'; +let discoveredChatWebToken: string | null = null; + +export const getDiscoveredChatWebToken = (): string | null => discoveredChatWebToken; + /** * Register this OpenCTI instance with XTM One. * @@ -36,6 +40,16 @@ export const registerWithXtmOne = async (context: AuthContext, user: AuthUser): const { pem } = getEnterpriseEditionActivePem(settings); + let licenseType: string | undefined; + try { + const licenseInfo = decodeLicensePem(settings); + if (licenseInfo.license_validated && licenseInfo.license_type) { + licenseType = licenseInfo.license_type; + } + } catch { + // license info not available — CE or invalid PEM + } + const users: XtmOneUserEntry[] = []; try { const allUsers = await getEntitiesListFromCache(context, user, ENTITY_TYPE_USER); @@ -73,11 +87,16 @@ export const registerWithXtmOne = async (context: AuthContext, user: AuthUser): platform_version: PLATFORM_VERSION, platform_id: settings.internal_id || settings.id, enterprise_license_pem: pem, + license_type: licenseType, admin_api_key: adminToken, users, }); if (result) { + if (result.chat_web_token) { + discoveredChatWebToken = result.chat_web_token; + logApp.info('[XTM One] Chat web token discovered from registration'); + } logApp.info('[XTM One] Registration successful', { status: result.status, ee_enabled: result.ee_enabled, From a452f32b3c2b9613cf5ef71c4e70a101f766f210 Mon Sep 17 00:00:00 2001 From: Samuel Hassine Date: Mon, 23 Feb 2026 14:39:59 +0100 Subject: [PATCH 5/7] Fix --- .../opencti-graphql/src/modules/xtm/one/xtm-one.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/opencti-platform/opencti-graphql/src/modules/xtm/one/xtm-one.ts b/opencti-platform/opencti-graphql/src/modules/xtm/one/xtm-one.ts index 0c23622e6009..3db2660b5233 100644 --- a/opencti-platform/opencti-graphql/src/modules/xtm/one/xtm-one.ts +++ b/opencti-platform/opencti-graphql/src/modules/xtm/one/xtm-one.ts @@ -4,6 +4,7 @@ import type { BasicStoreSettings } from '../../../types/settings'; import { getEntityFromCache, getEntitiesListFromCache } from '../../../database/cache'; import { internalLoadById } from '../../../database/middleware-loader'; import { ENTITY_TYPE_SETTINGS, ENTITY_TYPE_USER } from '../../../schema/internalObject'; +import { TokenDuration } from '../../../generated/graphql'; import { decodeLicensePem, getEnterpriseEditionActivePem } from '../../settings/licensing'; import { addUserTokenByAdmin } from '../../user/user-domain'; import xtmOneClient from './xtm-one-client'; @@ -58,13 +59,13 @@ export const registerWithXtmOne = async (context: AuthContext, user: AuthUser): try { // Load from DB (not cache) so the api_tokens check is reliable // and we never accidentally create duplicate "XTM One" tokens. - const freshUser = await internalLoadById(context, user, u.id); + const freshUser = await internalLoadById(context, user, u.id) as unknown as AuthUser; if (!freshUser) continue; const existingTokens = (freshUser as any).api_tokens ?? []; if (existingTokens.some((t: any) => t.name === XTM_ONE_TOKEN_NAME)) { continue; } - const newToken = await addUserTokenByAdmin(context, user, u.id, { name: XTM_ONE_TOKEN_NAME }); + const newToken = await addUserTokenByAdmin(context, user, u.id, { name: XTM_ONE_TOKEN_NAME, duration: TokenDuration.Unlimited }); users.push({ email: u.user_email, display_name: u.name || u.user_email, From 7bb99fcfde71434f1e0ecbbc32d6c20e614fa7e4 Mon Sep 17 00:00:00 2001 From: Samuel Hassine Date: Mon, 23 Feb 2026 14:49:00 +0100 Subject: [PATCH 6/7] Fix --- opencti-platform/opencti-front/lang/back/de.json | 1 + opencti-platform/opencti-front/lang/back/en.json | 1 + opencti-platform/opencti-front/lang/back/es.json | 1 + opencti-platform/opencti-front/lang/back/fr.json | 1 + opencti-platform/opencti-front/lang/back/it.json | 1 + opencti-platform/opencti-front/lang/back/ja.json | 1 + opencti-platform/opencti-front/lang/back/ko.json | 1 + opencti-platform/opencti-front/lang/back/ru.json | 1 + opencti-platform/opencti-front/lang/back/zh.json | 1 + 9 files changed, 9 insertions(+) diff --git a/opencti-platform/opencti-front/lang/back/de.json b/opencti-platform/opencti-front/lang/back/de.json index 6d2d0a7dc792..419260cd4b20 100644 --- a/opencti-platform/opencti-front/lang/back/de.json +++ b/opencti-platform/opencti-front/lang/back/de.json @@ -1066,5 +1066,6 @@ "XTM Hub registration User name": "XTM-Hub-Registrierung Benutzername", "XTM Hub should send connectivity email": "XTM Hub sollte Konnektivitäts-E-Mails senden", "XTM Hub Token": "XTM-Hub-Token", + "XTM One registration manager": "XTM One Registrierungsmanager", "XTM1 CGU acceptance status": "XTM1 CGU-Annahmestatus" } \ No newline at end of file diff --git a/opencti-platform/opencti-front/lang/back/en.json b/opencti-platform/opencti-front/lang/back/en.json index 428aebdc653c..afcec0e5d6b5 100644 --- a/opencti-platform/opencti-front/lang/back/en.json +++ b/opencti-platform/opencti-front/lang/back/en.json @@ -1066,5 +1066,6 @@ "XTM Hub registration User name": "XTM Hub registration User name", "XTM Hub should send connectivity email": "XTM Hub should send connectivity email", "XTM Hub Token": "XTM Hub Token", + "XTM One registration manager": "XTM One registration manager", "XTM1 CGU acceptance status": "XTM1 CGU acceptance status" } \ No newline at end of file diff --git a/opencti-platform/opencti-front/lang/back/es.json b/opencti-platform/opencti-front/lang/back/es.json index 910aeb0c569c..cd04b17fbc82 100644 --- a/opencti-platform/opencti-front/lang/back/es.json +++ b/opencti-platform/opencti-front/lang/back/es.json @@ -1066,5 +1066,6 @@ "XTM Hub registration User name": "XTM Hub registro Nombre de usuario", "XTM Hub should send connectivity email": "El eje de XTM debe enviar el email de la conectividad", "XTM Hub Token": "Token del concentrador XTM", + "XTM One registration manager": "Gestor de inscripciones XTM One", "XTM1 CGU acceptance status": "XTM1 Estado de aceptación de la UGC" } \ No newline at end of file diff --git a/opencti-platform/opencti-front/lang/back/fr.json b/opencti-platform/opencti-front/lang/back/fr.json index a10db199094e..1dcfb98f3ac7 100644 --- a/opencti-platform/opencti-front/lang/back/fr.json +++ b/opencti-platform/opencti-front/lang/back/fr.json @@ -1066,5 +1066,6 @@ "XTM Hub registration User name": "Nom de l'utilisateur pour l'enregistrement du XTM Hub", "XTM Hub should send connectivity email": "XTM Hub doit envoyer un e-mail de connectivité", "XTM Hub Token": "Token XTM Hub", + "XTM One registration manager": "Gestionnaire d'enregistrement XTM One", "XTM1 CGU acceptance status": "XTM1 Statut d'acceptation des CGU" } \ No newline at end of file diff --git a/opencti-platform/opencti-front/lang/back/it.json b/opencti-platform/opencti-front/lang/back/it.json index 88005cb76015..fe928ee470eb 100644 --- a/opencti-platform/opencti-front/lang/back/it.json +++ b/opencti-platform/opencti-front/lang/back/it.json @@ -1066,5 +1066,6 @@ "XTM Hub registration User name": "Registrazione XTM Hub Nome utente", "XTM Hub should send connectivity email": "XTM Hub dovrebbe inviare un'e-mail di connettività", "XTM Hub Token": "Token XTM Hub", + "XTM One registration manager": "Gestore delle registrazioni XTM One", "XTM1 CGU acceptance status": "XTM1 Stato di accettazione della CGU" } \ No newline at end of file diff --git a/opencti-platform/opencti-front/lang/back/ja.json b/opencti-platform/opencti-front/lang/back/ja.json index c61d0ff4068c..c91ea3b71fb3 100644 --- a/opencti-platform/opencti-front/lang/back/ja.json +++ b/opencti-platform/opencti-front/lang/back/ja.json @@ -1066,5 +1066,6 @@ "XTM Hub registration User name": "XTMハブ登録ユーザー名", "XTM Hub should send connectivity email": "XTM Hubは接続性の電子メールを送信する必要があります。", "XTM Hub Token": "XTM ハブ・トークン", + "XTM One registration manager": "XTM One登録マネージャー", "XTM1 CGU acceptance status": "XTM1 CGU受入状況" } \ No newline at end of file diff --git a/opencti-platform/opencti-front/lang/back/ko.json b/opencti-platform/opencti-front/lang/back/ko.json index 5086dc7d8ba2..ffcb17351ccc 100644 --- a/opencti-platform/opencti-front/lang/back/ko.json +++ b/opencti-platform/opencti-front/lang/back/ko.json @@ -1066,5 +1066,6 @@ "XTM Hub registration User name": "XTM Hub 등록 사용자 이름", "XTM Hub should send connectivity email": "XTM 허브가 연결 이메일을 보내야 합니다", "XTM Hub Token": "XTM 허브 토큰", + "XTM One registration manager": "XTM One 등록 관리자", "XTM1 CGU acceptance status": "XTM1 CGU 승인 상태" } \ No newline at end of file diff --git a/opencti-platform/opencti-front/lang/back/ru.json b/opencti-platform/opencti-front/lang/back/ru.json index 50afa3108d3d..74c31d587a9b 100644 --- a/opencti-platform/opencti-front/lang/back/ru.json +++ b/opencti-platform/opencti-front/lang/back/ru.json @@ -1066,5 +1066,6 @@ "XTM Hub registration User name": "Регистрация концентратора XTM Имя пользователя", "XTM Hub should send connectivity email": "Концентратор XTM должен отправить электронное сообщение о возможности подключения", "XTM Hub Token": "Токен концентратора XTM", + "XTM One registration manager": "Менеджер регистрации XTM One", "XTM1 CGU acceptance status": "Статус принятия XTM1 CGU" } \ No newline at end of file diff --git a/opencti-platform/opencti-front/lang/back/zh.json b/opencti-platform/opencti-front/lang/back/zh.json index 71586764a3e7..336e96609c6b 100644 --- a/opencti-platform/opencti-front/lang/back/zh.json +++ b/opencti-platform/opencti-front/lang/back/zh.json @@ -1066,5 +1066,6 @@ "XTM Hub registration User name": "XTM Hub 注册用户名", "XTM Hub should send connectivity email": "XTM 中枢应发送连接电子邮件", "XTM Hub Token": "XTM 中枢令牌", + "XTM One registration manager": "XTM One 注册管理器", "XTM1 CGU acceptance status": "XTM1 CGU 验收状态" } \ No newline at end of file From b7219baab0e1901e8c89ad31ae152b833dc4bcec Mon Sep 17 00:00:00 2001 From: Samuel Hassine Date: Thu, 5 Mar 2026 09:15:56 -0500 Subject: [PATCH 7/7] fix: release --- .../src/manager/xtmOneRegistrationManager.ts | 4 +++ .../src/modules/xtm/one/xtm-one.ts | 26 +++++++++++-------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/opencti-platform/opencti-graphql/src/manager/xtmOneRegistrationManager.ts b/opencti-platform/opencti-graphql/src/manager/xtmOneRegistrationManager.ts index 92e6a93ca90e..c71ec4a3e172 100644 --- a/opencti-platform/opencti-graphql/src/manager/xtmOneRegistrationManager.ts +++ b/opencti-platform/opencti-graphql/src/manager/xtmOneRegistrationManager.ts @@ -6,6 +6,10 @@ import { registerManager } from './managerModule'; const XTM_ONE_URL = conf.get('xtm:xtm_one_url'); const XTM_ONE_TOKEN = conf.get('xtm:xtm_one_token'); +// Both URL *and* token are required. xtm_one_url alone is used by the +// chatbot proxy (httpChatbotProxy) and does NOT mean the registration +// manager should run — the token is the signal that XTM One registration +// is intentionally configured. const XTM_ONE_ENABLED = !!(XTM_ONE_URL && XTM_ONE_TOKEN); const SCHEDULE_TIME = 5 * 60 * 1000; // 5 minutes const BOOT_DELAY = 30_000; // 30 seconds — let the platform finish init diff --git a/opencti-platform/opencti-graphql/src/modules/xtm/one/xtm-one.ts b/opencti-platform/opencti-graphql/src/modules/xtm/one/xtm-one.ts index 3db2660b5233..03fbc7aa372c 100644 --- a/opencti-platform/opencti-graphql/src/modules/xtm/one/xtm-one.ts +++ b/opencti-platform/opencti-graphql/src/modules/xtm/one/xtm-one.ts @@ -23,10 +23,11 @@ export const getDiscoveredChatWebToken = (): string | null => discoveredChatWebT * endpoint is an upsert so repeated calls are safe and serve as both * initial registration and periodic heartbeat. * - * For each user we load from the DB (not cache) to reliably check whether - * an "XTM One" named token already exists. Only users who receive a - * newly-created token are included in the payload — their plaintext key - * is only available at creation time. + * Every user with an email is sent on every tick so that XTM One can + * create/update them. An "XTM One" API token is provisioned once per + * user; the plaintext key is only available at creation time, so + * subsequent pings send the user with an empty api_key (XTM One keeps + * the previously stored credentials). */ export const registerWithXtmOne = async (context: AuthContext, user: AuthUser): Promise => { if (!xtmOneClient.isConfigured()) { @@ -57,22 +58,25 @@ export const registerWithXtmOne = async (context: AuthContext, user: AuthUser): for (const u of allUsers) { if (!u.user_email) continue; try { - // Load from DB (not cache) so the api_tokens check is reliable - // and we never accidentally create duplicate "XTM One" tokens. const freshUser = await internalLoadById(context, user, u.id) as unknown as AuthUser; if (!freshUser) continue; const existingTokens = (freshUser as any).api_tokens ?? []; - if (existingTokens.some((t: any) => t.name === XTM_ONE_TOKEN_NAME)) { - continue; + const hasXtmOneToken = existingTokens.some((t: any) => t.name === XTM_ONE_TOKEN_NAME); + let apiKey = ''; + if (!hasXtmOneToken) { + const newToken = await addUserTokenByAdmin(context, user, u.id, { name: XTM_ONE_TOKEN_NAME, duration: TokenDuration.Unlimited }); + apiKey = newToken.plaintext_token; } - const newToken = await addUserTokenByAdmin(context, user, u.id, { name: XTM_ONE_TOKEN_NAME, duration: TokenDuration.Unlimited }); + // Always include the user so XTM One can create/update them on + // every ping. api_key is only available at token-creation time; + // XTM One treats an empty key as "keep existing credentials". users.push({ email: u.user_email, display_name: u.name || u.user_email, - api_key: newToken.plaintext_token, + api_key: apiKey, }); } catch (tokenErr: any) { - logApp.warn('[XTM One] Failed to create token for user', { email: u.user_email, error: tokenErr.message }); + logApp.warn('[XTM One] Failed to process token for user', { email: u.user_email, error: tokenErr.message }); } } } catch (err: any) {