fix(channels): resolve type and channel references safely
This commit is contained in:
@@ -5,6 +5,19 @@ import { AuditService } from '../../../shared/services/audit.service.js';
|
||||
import { ApiError } from '../../../utils/helpers/ApiError.utils.js';
|
||||
import { generateUniqueCode } from '../../../utils/helpers/code.utils.js';
|
||||
|
||||
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
||||
|
||||
async function resolveChannelTypeId(channelType) {
|
||||
if (!channelType) return null;
|
||||
if (UUID_PATTERN.test(channelType)) return channelType;
|
||||
|
||||
const type = await models.ChannelType.findOne({ where: { code: channelType } });
|
||||
if (!type) {
|
||||
throw new ApiError(400, `Unknown channel type: ${channelType}`);
|
||||
}
|
||||
return type.id;
|
||||
}
|
||||
|
||||
export class ChannelService {
|
||||
async getAll(query = {}, context = {}) {
|
||||
// Add business logic filtering, pagination, etc.
|
||||
@@ -22,7 +35,7 @@ export class ChannelService {
|
||||
async create(data, userContext = {}) {
|
||||
const payload = { ...data };
|
||||
if (payload.channelType) {
|
||||
payload.type_id = payload.channelType;
|
||||
payload.type_id = await resolveChannelTypeId(payload.channelType);
|
||||
delete payload.channelType;
|
||||
}
|
||||
if (payload.allowPublishing !== undefined) {
|
||||
@@ -52,7 +65,7 @@ export class ChannelService {
|
||||
async update(id, data, userContext = {}) {
|
||||
const payload = { ...data };
|
||||
if (payload.channelType) {
|
||||
payload.type_id = payload.channelType;
|
||||
payload.type_id = await resolveChannelTypeId(payload.channelType);
|
||||
delete payload.channelType;
|
||||
}
|
||||
if (payload.allowPublishing !== undefined) {
|
||||
|
||||
@@ -12,6 +12,8 @@ import { executeGenericWebhook } from '../channels/syndication/genericWebhookCon
|
||||
import { models } from '../../shared/database/models.js';
|
||||
import { normalizeShopDomain, testShopifyConnection } from '../channels/syndication/shopifyConnector.service.js';
|
||||
|
||||
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
||||
|
||||
const RESERVED_FIELDS = new Set([
|
||||
'id', 'tenant_id', 'created_at', 'updated_at', 'deleted_at', 'name',
|
||||
'description', 'channel', 'integrationType', 'integration_type', 'environment',
|
||||
@@ -77,10 +79,15 @@ function baseRecord(payload, context) {
|
||||
};
|
||||
}
|
||||
|
||||
async function assertTenantChannel(channelId, context) {
|
||||
if (!channelId) return;
|
||||
const channel = await models.Channel.findOne({ where: { id: channelId, tenant_id: context.tenantId } });
|
||||
async function resolveTenantChannel(channelReference, context) {
|
||||
if (!channelReference) return null;
|
||||
const channel = await models.Channel.findOne({
|
||||
where: UUID_PATTERN.test(channelReference)
|
||||
? { id: channelReference, tenant_id: context.tenantId }
|
||||
: { code: channelReference, tenant_id: context.tenantId }
|
||||
});
|
||||
if (!channel) throw new ApiError(400, 'Selected Channel does not belong to this tenant');
|
||||
return channel;
|
||||
}
|
||||
|
||||
function normalizeGenericRestPayload(payload) {
|
||||
@@ -113,7 +120,8 @@ export class IntegrationService {
|
||||
|
||||
async create(payload, context) {
|
||||
if (!payload.name) throw new ApiError(400, 'Integration name is required');
|
||||
await assertTenantChannel(payload.channel, context);
|
||||
const channel = await resolveTenantChannel(payload.channel, context);
|
||||
payload = { ...payload, channel: channel?.id || null };
|
||||
if ((payload.integrationType || payload.integration_type) === 'shopify') {
|
||||
payload = { ...payload, storeUrl: `https://${normalizeShopDomain(payload.storeUrl)}`, apiVersion: '2026-07' };
|
||||
if (!payload.clientId || !payload.clientSecret) throw new ApiError(400, 'Shopify Client ID and Client Secret are required');
|
||||
@@ -132,7 +140,10 @@ export class IntegrationService {
|
||||
async update(id, payload, context) {
|
||||
const record = await repository.findById(id, context);
|
||||
if (!record) throw new ApiError(404, 'Integration not found');
|
||||
if (payload.channel !== undefined) await assertTenantChannel(payload.channel, context);
|
||||
if (payload.channel !== undefined) {
|
||||
const channel = await resolveTenantChannel(payload.channel, context);
|
||||
payload = { ...payload, channel: channel?.id || null };
|
||||
}
|
||||
if ((payload.integrationType || record.integration_type) === 'shopify') {
|
||||
const storeUrl = payload.storeUrl ?? record.config?.storeUrl;
|
||||
payload = { ...payload, storeUrl: `https://${normalizeShopDomain(storeUrl)}`, apiVersion: '2026-07' };
|
||||
|
||||
Reference in New Issue
Block a user