Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 16 additions & 9 deletions api/src/utils/market-app.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,23 @@ import {client} from '@contentstack/marketplace-sdk';
import { DEVURLS } from '../constants/index.js';



const buildMarketplaceClient = ({
authtoken,
region,
}: {
authtoken?: string;
region?: string;
}) => {
const host = DEVURLS?.[region as keyof typeof DEVURLS] ?? DEVURLS?.NA;
if (typeof authtoken === 'string' && authtoken.startsWith('Bearer ')) {
return client({ authorization: authtoken, host } as any);
}
return client({ authtoken, host } as any);
};

export const getAllApps = async ({ organizationUid, authtoken, region }: any) => {
try {
const contentstackclient = client({ authtoken, host: DEVURLS?.[region] ?? DEVURLS?.NA });
const contentstackclient = buildMarketplaceClient({ authtoken, region });
const data = await contentstackclient.marketplace(organizationUid).findAllApps();
return data?.items;
} catch (err) {
Expand All @@ -16,7 +28,7 @@ export const getAllApps = async ({ organizationUid, authtoken, region }: any) =>

export const getAppManifestAndAppConfig = async ({ organizationUid, authtoken, region, manifestUid }: any) => {
try {
const contentstackclient = client({ authtoken, host: DEVURLS?.[region] ?? DEVURLS?.NA });
const contentstackclient = buildMarketplaceClient({ authtoken, region });
const data = await contentstackclient.marketplace(organizationUid).app(manifestUid).fetch();
return data;
} catch (err: any) {
Expand Down Expand Up @@ -56,13 +68,8 @@ export const fetchMarketplaceInstallationsForStack = async ({
authtoken: string;
region: string;
}) => {
const host = DEVURLS?.[region as keyof typeof DEVURLS] ?? DEVURLS.NA;

try {
const contentstackclient = client({
authtoken,
host,
});
const contentstackclient = buildMarketplaceClient({ authtoken, region });
const instApi = contentstackclient
.marketplace(organizationUid)
.installation();
Expand Down
64 changes: 64 additions & 0 deletions api/tests/unit/utils/market-app.utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,31 @@ describe('market-app.utils', () => {
});
});

it('routes SSO Bearer tokens to the SDK `authorization` option (not `authtoken`)', async () => {
// SSO callers historically forward a `Bearer <access_token>` string in
// the `authtoken` arg. Routing that into the SDK's `authtoken` option
// puts a Bearer value into the wrong HTTP header and Developer Hub
// rejects it, which is what caused marketplace-app custom fields to
// silently disappear from SSO migrations.
mockClient.marketplace.mockReturnValue({
findAllApps: vi.fn().mockResolvedValue({ items: [] }),
});

await getAllApps({
organizationUid: 'org-123',
authtoken: 'Bearer sso-access-token',
region: 'NA',
});

expect(marketplaceClient).toHaveBeenCalledWith({
authorization: 'Bearer sso-access-token',
host: 'developerhub-api.contentstack.com',
});
expect(marketplaceClient).not.toHaveBeenCalledWith(
expect.objectContaining({ authtoken: 'Bearer sso-access-token' }),
);
});

it('should return undefined and log when error occurs', async () => {
const consoleSpy = vi.spyOn(console, 'info').mockImplementation(() => {});
mockClient.marketplace.mockReturnValue({
Expand Down Expand Up @@ -132,6 +157,26 @@ describe('market-app.utils', () => {
expect(consoleSpy).toHaveBeenCalled();
consoleSpy.mockRestore();
});

it('routes SSO Bearer tokens to the SDK `authorization` option (not `authtoken`)', async () => {
mockClient.marketplace.mockReturnValue({
app: vi.fn().mockReturnValue({
fetch: vi.fn().mockResolvedValue({ uid: 'manifest-1' }),
}),
});

await getAppManifestAndAppConfig({
organizationUid: 'org-123',
authtoken: 'Bearer sso-access-token',
region: 'NA',
manifestUid: 'manifest-1',
});

expect(marketplaceClient).toHaveBeenCalledWith({
authorization: 'Bearer sso-access-token',
host: 'developerhub-api.contentstack.com',
});
});
});

describe('fetchMarketplaceInstallationsForStack', () => {
Expand Down Expand Up @@ -181,6 +226,25 @@ describe('market-app.utils', () => {
expect(result).toEqual([matching]);
});

it('routes SSO Bearer tokens to the SDK `authorization` option (not `authtoken`)', async () => {
const fetchAll = vi.fn().mockResolvedValue({ items: [] });
mockClient.marketplace.mockReturnValue({
findAllApps: vi.fn(),
app: vi.fn(),
installation: vi.fn(() => ({ fetchAll })),
});

await fetchMarketplaceInstallationsForStack({
...baseParams,
authtoken: 'Bearer sso-access-token',
});

expect(marketplaceClient).toHaveBeenCalledWith({
authorization: 'Bearer sso-access-token',
host: 'developerhub-api.contentstack.com',
});
});

it('uses fallback fetchAll() when paginated fetchAll rejects', async () => {
const item = {
uid: 'ins-fb',
Expand Down
9 changes: 9 additions & 0 deletions upload-api/src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ export default {
plan: {
dropdown: { optionLimit: 100 }
},
// CMS type configuration
cmsType: process.env.CMS_TYPE || 'cmsType',
isLocalPath: true,

// AWS data configuration
awsData: {
awsRegion: 'us-east-2',
awsAccessKeyId: '',
Expand All @@ -12,16 +15,22 @@ export default {
bucketName: '',
bucketKey: ''
},

// Drupal database configuration
mysql: {
host: process.env.MYSQL_HOST || 'host_name',
user: process.env.MYSQL_USER || 'user_name',
password: process.env.MYSQL_PASSWORD || '',
database: process.env.MYSQL_DATABASE || 'database_name',
port: process.env.MYSQL_PORT || 'port_number'
},

// Drupal assets configuration
assetsConfig: {
base_url: process.env.DRUPAL_ASSETS_BASE_URL || 'drupal_assets_base_url',
public_path: process.env.DRUPAL_ASSETS_PUBLIC_PATH || 'drupal_assets_public_path'
},

// Local path for the CMS data
localPath: process.env.CMS_LOCAL_PATH || process.env.CONTAINER_PATH || 'your_local_cms_data_path',
};
Loading