diff --git a/apps/backend/src/services/admin/platform-config/cloudtentacles.js b/apps/backend/src/services/admin/platform-config/cloudtentacles.js index 8b65709d..5f085bd6 100644 --- a/apps/backend/src/services/admin/platform-config/cloudtentacles.js +++ b/apps/backend/src/services/admin/platform-config/cloudtentacles.js @@ -3,6 +3,7 @@ import { getCloudtentaclesSourceConfig } from '../../platforms/cloudtentacles/source-config-service.js' import { getCloudtentaclesSessionState } from '../../platforms/cloudtentacles/session-state-service.js' import { pickFirstNonEmpty, resolveCloudtentaclesAdminContext } from './context.js' +import { maskPhone, maskSecret } from './mappers.js' const DEFAULT_CLOUDTENTACLES_BASE_URL = 'https://123.207.217.176' @@ -42,3 +43,62 @@ export function resolveAdminCloudtentaclesSessionPayload( defaultBaseUrl: options.defaultBaseUrl || DEFAULT_CLOUDTENTACLES_BASE_URL, }) } + +export function buildAdminCloudtentaclesPersistedSessionPayload( + session = {}, + options = {}, +) { + return { + token: String(session.token || '').trim(), + baseUrl: String(session.baseUrl || '').trim(), + username: pickFirstNonEmpty([options.username, session.username]), + phone: pickFirstNonEmpty([options.phone, session.phone]), + loggedInAt: String(session.loggedInAt || '').trim(), + deviceId: pickFirstNonEmpty([options.deviceId, session.deviceId, '-']), + deviceType: options.deviceType ?? session.deviceType, + } +} + +export function buildAdminCloudtentaclesSessionSummary( + session = {}, + savedSession = {}, +) { + const permissions = Array.isArray(session.permissions) ? session.permissions : [] + + return { + tokenMasked: maskSecret(session.token), + permissionCount: permissions.length, + permissions, + persisted: Boolean(savedSession.token), + } +} + +export function buildAdminCloudtentaclesLoginResult( + session = {}, + savedSession = {}, +) { + return { + baseUrl: String(session.baseUrl || '').trim(), + username: String(session.username || '').trim(), + phoneMasked: maskPhone(session.phone), + loggedInAt: String(session.loggedInAt || '').trim(), + responseMessage: String(session.responseMessage || '').trim(), + token: String(session.token || '').trim(), + session: buildAdminCloudtentaclesSessionSummary(session, savedSession), + userInfo: session.userInfo, + asset: session.asset, + } +} + +export function buildAdminCloudtentaclesValidateResult( + session = {}, + savedSession = {}, +) { + return { + baseUrl: String(session.baseUrl || '').trim(), + loggedInAt: String(session.loggedInAt || '').trim(), + session: buildAdminCloudtentaclesSessionSummary(session, savedSession), + userInfo: session.userInfo, + asset: session.asset, + } +} diff --git a/apps/backend/src/services/admin/platform-config/cloudtentacles.test.js b/apps/backend/src/services/admin/platform-config/cloudtentacles.test.js index 779f2298..3e95c19b 100644 --- a/apps/backend/src/services/admin/platform-config/cloudtentacles.test.js +++ b/apps/backend/src/services/admin/platform-config/cloudtentacles.test.js @@ -2,6 +2,10 @@ import test from 'node:test' import assert from 'node:assert/strict' import { + buildAdminCloudtentaclesLoginResult, + buildAdminCloudtentaclesPersistedSessionPayload, + buildAdminCloudtentaclesSessionSummary, + buildAdminCloudtentaclesValidateResult, resolveAdminCloudtentaclesCredentialPayload, resolveAdminCloudtentaclesSessionPayload, } from './cloudtentacles.js' @@ -63,3 +67,119 @@ test('resolveAdminCloudtentaclesSessionPayload merges payload source and persist }, ) }) + +test('buildAdminCloudtentaclesPersistedSessionPayload prefers explicit overrides', () => { + assert.deepEqual( + buildAdminCloudtentaclesPersistedSessionPayload( + { + token: ' token-1 ', + baseUrl: ' https://cloud.example.com ', + username: ' session-user ', + phone: ' 13900000000 ', + loggedInAt: ' 2026-05-04T12:00:00.000Z ', + deviceId: ' session-device ', + deviceType: 9, + }, + { + username: 'saved-user', + phone: '13800138000', + deviceId: 'saved-device', + deviceType: 3, + }, + ), + { + token: 'token-1', + baseUrl: 'https://cloud.example.com', + username: 'saved-user', + phone: '13800138000', + loggedInAt: '2026-05-04T12:00:00.000Z', + deviceId: 'saved-device', + deviceType: 3, + }, + ) +}) + +test('buildAdminCloudtentaclesSessionSummary masks token and reports permission count', () => { + assert.deepEqual( + buildAdminCloudtentaclesSessionSummary( + { + token: 'abcdef1234567890', + permissions: ['asset', 'sku'], + }, + { + token: 'persisted-token', + }, + ), + { + tokenMasked: 'abcdef****567890', + permissionCount: 2, + permissions: ['asset', 'sku'], + persisted: true, + }, + ) +}) + +test('buildAdminCloudtentaclesLoginResult assembles login response payload', () => { + assert.deepEqual( + buildAdminCloudtentaclesLoginResult( + { + token: 'abcdef1234567890', + baseUrl: ' https://cloud.example.com ', + username: ' test-user ', + phone: '13812345678', + loggedInAt: ' 2026-05-04T12:00:00.000Z ', + responseMessage: ' success ', + permissions: ['asset'], + userInfo: { id: 1 }, + asset: { balance: 10 }, + }, + { + token: 'persisted-token', + }, + ), + { + baseUrl: 'https://cloud.example.com', + username: 'test-user', + phoneMasked: '138****5678', + loggedInAt: '2026-05-04T12:00:00.000Z', + responseMessage: 'success', + token: 'abcdef1234567890', + session: { + tokenMasked: 'abcdef****567890', + permissionCount: 1, + permissions: ['asset'], + persisted: true, + }, + userInfo: { id: 1 }, + asset: { balance: 10 }, + }, + ) +}) + +test('buildAdminCloudtentaclesValidateResult assembles validate response payload', () => { + assert.deepEqual( + buildAdminCloudtentaclesValidateResult( + { + token: 'abcdef1234567890', + baseUrl: ' https://cloud.example.com ', + loggedInAt: ' 2026-05-04T12:00:00.000Z ', + permissions: ['asset', 'sku'], + userInfo: { id: 2 }, + asset: { balance: 20 }, + }, + {}, + ), + { + baseUrl: 'https://cloud.example.com', + loggedInAt: '2026-05-04T12:00:00.000Z', + session: { + tokenMasked: 'abcdef****567890', + permissionCount: 2, + permissions: ['asset', 'sku'], + persisted: false, + }, + userInfo: { id: 2 }, + asset: { balance: 20 }, + }, + ) +}) diff --git a/apps/backend/src/services/admin/platform-config/service.js b/apps/backend/src/services/admin/platform-config/service.js index 4c8b1db9..da992f9e 100644 --- a/apps/backend/src/services/admin/platform-config/service.js +++ b/apps/backend/src/services/admin/platform-config/service.js @@ -88,6 +88,9 @@ import { resolveFulfillmentLookupDetail, } from './fulfillment.js' import { + buildAdminCloudtentaclesLoginResult, + buildAdminCloudtentaclesPersistedSessionPayload, + buildAdminCloudtentaclesValidateResult, resolveAdminCloudtentaclesCredentialPayload, resolveAdminCloudtentaclesSessionPayload, } from './cloudtentacles.js' @@ -479,32 +482,12 @@ export async function testAdminCloudtentaclesLogin(payload = /** @type {AdminClo ...credentialContext, code: String(payload.code || '').trim(), }) - const savedSession = saveCloudtentaclesSessionState({ - token: session.token, - baseUrl: session.baseUrl, - username: session.username, - phone: session.phone, - loggedInAt: session.loggedInAt, + const savedSession = saveCloudtentaclesSessionState(buildAdminCloudtentaclesPersistedSessionPayload(session, { deviceId: credentialContext.deviceId, deviceType: credentialContext.deviceType, - }) + })) - return { - baseUrl: session.baseUrl, - username: session.username, - phoneMasked: maskPhone(session.phone), - loggedInAt: session.loggedInAt, - responseMessage: session.responseMessage, - token: session.token, - session: { - tokenMasked: maskSecret(session.token), - permissionCount: session.permissions.length, - permissions: session.permissions, - persisted: Boolean(savedSession.token), - }, - userInfo: session.userInfo, - asset: session.asset, - } + return buildAdminCloudtentaclesLoginResult(session, savedSession) } /** @param {AdminCloudtentaclesValidateSessionInput} [payload] */ @@ -513,28 +496,14 @@ export async function validateAdminCloudtentaclesSession(payload = /** @type {Ad const persistedSession = getCloudtentaclesSessionState() const sessionContext = resolveAdminCloudtentaclesSessionPayload(payload, { savedSource, persistedSession }) const session = await validateCloudtentaclesSession(sessionContext) - const savedSession = saveCloudtentaclesSessionState({ - token: session.token, - baseUrl: session.baseUrl, + const savedSession = saveCloudtentaclesSessionState(buildAdminCloudtentaclesPersistedSessionPayload(session, { username: pickFirstNonEmpty([persistedSession.username, savedSource.username]), phone: pickFirstNonEmpty([persistedSession.phone, savedSource.phone]), - loggedInAt: session.loggedInAt, deviceId: sessionContext.deviceId, deviceType: sessionContext.deviceType, - }) + })) - return { - baseUrl: session.baseUrl, - loggedInAt: session.loggedInAt, - session: { - tokenMasked: maskSecret(session.token), - permissionCount: session.permissions.length, - permissions: session.permissions, - persisted: Boolean(savedSession.token), - }, - userInfo: session.userInfo, - asset: session.asset, - } + return buildAdminCloudtentaclesValidateResult(session, savedSession) } /** @param {AdminCloudtentaclesCatalogQueryInput} [payload] */