Skip to content
Open
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
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@gofynd/fdk-cli",
"version": "8.0.7",
"version": "8.0.8",
"main": "index.js",
"license": "MIT",
"bin": {
Expand Down
81 changes: 81 additions & 0 deletions src/__tests__/api.interceptor.spec.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
/// <reference types="jest" />
import { addSignatureFn } from '../lib/api/helper/interceptors';
import ConfigStore, { CONFIG_KEYS } from '../lib/Config';

const packageJSON = require('../../package.json');

describe('API request interceptor', () => {
afterEach(() => {
jest.restoreAllMocks();
});

it('adds fdk cli version header before service requests are signed', async () => {
const interceptor = addSignatureFn({});
const config: any = {
Expand All @@ -18,4 +23,80 @@ describe('API request interceptor', () => {
expect(signedConfig.headers['x-fp-date']).toBeDefined();
expect(signedConfig.headers['x-fp-signature']).toBeDefined();
});

it('adds stored region header before fynd service requests are signed', async () => {
jest.spyOn(ConfigStore, 'get').mockImplementation((key: string) => {
if (key === CONFIG_KEYS.REGION) return 'asia-south1';
return undefined;
});
const interceptor = addSignatureFn({});
const config: any = {
url: 'https://api.fyndx1.de/service/panel/authentication/v1.0/oauth/token',
method: 'get',
headers: {},
};

const signedConfig = await interceptor(config);

expect(signedConfig.headers['x-region']).toBe('asia-south1');
expect(signedConfig.headers['x-fp-signature']).toBeDefined();
});

it('prefers request region header over stored region header', async () => {
jest.spyOn(ConfigStore, 'get').mockImplementation((key: string) => {
if (key === CONFIG_KEYS.REGION) return 'chinmay';
return undefined;
});
const interceptor = addSignatureFn({});
const config: any = {
url: 'https://api.fyndx1.de/service/panel/authentication/v1.0/oauth/token',
method: 'get',
headers: {
'x-region': 'asia-south2',
},
};

const signedConfig = await interceptor(config);

expect(signedConfig.headers['x-region']).toBe('asia-south2');
expect(signedConfig.headers['x-fp-signature']).toBeDefined();
});

it('does not add stored region header when request opts out of region', async () => {
jest.spyOn(ConfigStore, 'get').mockImplementation((key: string) => {
if (key === CONFIG_KEYS.REGION) return 'chinmay';
return undefined;
});
const interceptor = addSignatureFn({});
const config: any = {
url: 'https://api.fyndx1.de/service/panel/authentication/v1.0/oauth/token',
method: 'get',
headers: {
'x-region': null,
},
};

const signedConfig = await interceptor(config);

expect(signedConfig.headers['x-region']).toBeUndefined();
expect(signedConfig.headers['x-fp-signature']).toBeDefined();
});

it('does not add stored region header to third-party requests', async () => {
jest.spyOn(ConfigStore, 'get').mockImplementation((key: string) => {
if (key === CONFIG_KEYS.REGION) return 'asia-south1';
return undefined;
});
const interceptor = addSignatureFn({});
const config: any = {
url: 'https://storage.googleapis.com/fdk-upload/signed-url',
method: 'put',
headers: {},
};

const signedConfig = await interceptor(config);

expect(signedConfig.headers['x-region']).toBeUndefined();
expect(signedConfig.headers['x-fp-signature']).toBeUndefined();
});
});
46 changes: 45 additions & 1 deletion src/__tests__/auth.device.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ jest.mock('open', () => ({
default: (...args) => openMock(...args),
}));

jest.mock('../helper/extension_utils', () => ({
getRandomFreePort: jest.fn().mockResolvedValue(43123),
}));

jest.mock('../helper/formatter', () => ({
OutputFormatter: {
link: (value: string) => value,
Expand Down Expand Up @@ -127,6 +131,7 @@ describe('Auth device flow', () => {
expect.objectContaining({
headers: {
'Content-Type': 'application/json',
'x-region': 'asia-south1/development',
},
}),
expect.objectContaining({
Expand All @@ -137,6 +142,9 @@ describe('Auth device flow', () => {
1,
'https://api.fyndx1.de/region/asia-south1/development/service/panel/authentication/v1.0/oauth/device_authorization',
expect.objectContaining({
headers: expect.objectContaining({
'x-region': 'asia-south1/development',
}),
data: expect.objectContaining({
requested_region: 'asia-south1/development',
}),
Expand All @@ -146,6 +154,9 @@ describe('Auth device flow', () => {
2,
'https://api.fyndx1.de/region/asia-south1/development/service/panel/authentication/v1.0/oauth/token',
expect.objectContaining({
headers: expect.objectContaining({
'x-region': 'asia-south1/development',
}),
data: expect.objectContaining({
device_code: 'device-code-region',
}),
Expand All @@ -155,6 +166,7 @@ describe('Auth device flow', () => {
'https://partners.fyndx1.de/partners/organizations/?device_id=device-code-region&region=asia-south1%2Fdevelopment',
);
expect(configStore.get(CONFIG_KEYS.AUTH_TOKEN).access_token).toBe('region-token');
expect(configStore.get(CONFIG_KEYS.REGION)).toBe('asia-south1/development');
});

it('uses device flow and opens verification URL unchanged', async () => {
Expand Down Expand Up @@ -186,14 +198,46 @@ describe('Auth device flow', () => {
},
} as any);

configStore.set(CONFIG_KEYS.REGION, 'asia-south1');

await Auth.login({ host: 'api.fyndx1.de' });

expect(getSpy).toHaveBeenCalled();
expect(getSpy).toHaveBeenCalledWith(
'https://api.fyndx1.de/service/panel/authentication/v1.0/oauth/client-config',
expect.objectContaining({
headers: {
'Content-Type': 'application/json',
'x-region': null,
},
}),
expect.objectContaining({
validateStatus: expect.any(Function),
}),
);
expect(postSpy).toHaveBeenCalledTimes(2);
expect(postSpy).toHaveBeenNthCalledWith(
1,
'https://api.fyndx1.de/service/panel/authentication/v1.0/oauth/device_authorization',
expect.objectContaining({
headers: expect.objectContaining({
'x-region': null,
}),
}),
);
expect(postSpy).toHaveBeenNthCalledWith(
2,
'https://api.fyndx1.de/service/panel/authentication/v1.0/oauth/token',
expect.objectContaining({
headers: expect.objectContaining({
'x-region': null,
}),
}),
);
expect(openMock).toHaveBeenCalledTimes(1);
const openedUrl = openMock.mock.calls[0][0] as string;
expect(openedUrl).toBe('https://partners.fyndx1.de/partners/organizations/?device_id=device-code-basic');
expect(configStore.get(CONFIG_KEYS.AUTH_TOKEN).access_token).toBe('token-1');
expect(configStore.get(CONFIG_KEYS.REGION)).toBeUndefined();
});

it('maps expired_token polling response to CommandError', async () => {
Expand Down
38 changes: 33 additions & 5 deletions src/__tests__/auth.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ import {
jest.mock('inquirer');
let program;

jest.mock('../helper/extension_utils', () => ({
getRandomFreePort: jest.fn().mockResolvedValue(43123),
}));

jest.mock('configstore', () => {
const Store =
jest.requireActual('configstore');
Expand All @@ -45,15 +49,14 @@ jest.mock('configstore', () => {
jest.mock('open', () => {
return () => {}
})
export async function login(domain?: string) {
export async function login(domain?: string, region?: string) {
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; // Disable SSL verification
const port = await getRandomFreePort([]);
const app = await startServer(port);
const req = request(app);
if(domain)
await program.parseAsync(['ts-node', './src/fdk.ts', 'login', '--host', domain]);
else
await program.parseAsync(['ts-node', './src/fdk.ts', 'login', '--host', 'api.fyndx1.de']);
const args = ['ts-node', './src/fdk.ts', 'login', '--host', domain || 'api.fyndx1.de'];
if (region) args.push('--region', region);
await program.parseAsync(args);
return await req.post('/token').send(tokenData);
}

Expand Down Expand Up @@ -132,11 +135,36 @@ describe('Auth Commands', () => {
});
it('Should successfully login with and env should updated', async () => {
configStore.delete(CONFIG_KEYS.AUTH_TOKEN);
configStore.set(CONFIG_KEYS.REGION, 'asia-south1');
await login('api.fynd.com');
expect(configStore.get(CONFIG_KEYS.CURRENT_ENV_VALUE)).toBe('api.fynd.com');
expect(configStore.get(CONFIG_KEYS.AUTH_TOKEN).access_token).toBe(
'pr-4fb094006ed3a6d749b69875be0418b83238d078',
);
expect(configStore.get(CONFIG_KEYS.REGION)).toBeUndefined();
});
it('Should store region after regional partner panel login', async () => {
configStore.delete(CONFIG_KEYS.AUTH_TOKEN);
configStore.delete(CONFIG_KEYS.REGION);
await login('api.fyndx1.de', 'asia-south1');
expect(configStore.get(CONFIG_KEYS.REGION)).toBe('asia-south1');
});
it('Should pass command region while validating host before login', async () => {
configStore.delete(CONFIG_KEYS.AUTH_TOKEN);
configStore.set(CONFIG_KEYS.REGION, 'chinmay');
const getSpy = jest.spyOn(axios, 'get');

await login('api.fyndx1.de', 'asia-south2');

expect(getSpy).toHaveBeenCalledWith(
'https://api.fyndx1.de/service/application/content/_healthz',
{
headers: {
'x-region': 'asia-south2',
},
},
);
expect(configStore.get(CONFIG_KEYS.REGION)).toBe('asia-south2');
});
it('should console active user', async () => {
let consoleWarnSpy: jest.SpyInstance;
Expand Down
40 changes: 27 additions & 13 deletions src/lib/Auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,22 @@ function getRegionFromOptions(options: any) {
return options?.region?.trim();
}

function updateRegionConfig(region?: string) {
if (region) {
ConfigStore.set(CONFIG_KEYS.REGION, region);
} else {
ConfigStore.delete(CONFIG_KEYS.REGION);
}
}

function getAuthHeaders(region?: string) {
const headers = {
'Content-Type': 'application/json',
};
headers['x-region'] = region || null;
return headers;
}

export const getApp = async () => {
const app = express();
let isLoading = false;
Expand Down Expand Up @@ -65,11 +81,12 @@ export const getApp = async () => {
Env.setEnv(Auth.newDomainToUpdate);
}
else {
await Env.setNewEnvs(Auth.newDomainToUpdate);
await Env.setNewEnvs(Auth.newDomainToUpdate, Auth.regionToUpdate);
}
}
ConfigStore.set(CONFIG_KEYS.AUTH_TOKEN, req.body.auth_token);
ConfigStore.set(CONFIG_KEYS.ORGANIZATION, req.body.organization);
updateRegionConfig(Auth.regionToUpdate);
const organization_detail =
await OrganizationService.getOrganizationDetails();
ConfigStore.set(
Expand Down Expand Up @@ -153,15 +170,14 @@ export default class Auth {
static timer_id;
static wantToChangeOrganization = false;
static newDomainToUpdate = null;
static regionToUpdate = null;
constructor() { }

private static async getAuthFlowConfig(env: string, region?: string) {
try {
const url = URLS.OAUTH_CLIENT_CONFIG({ env, region });
const response = await ApiClient.get(url, {
headers: {
'Content-Type': 'application/json',
},
headers: getAuthHeaders(region),
}, {
validateStatus: (status) =>
(status >= 200 && status < 300) || status === 404,
Expand All @@ -186,9 +202,7 @@ export default class Auth {
const region = getRegionFromOptions(options);
const urlOptions = { env, region };
const response = await ApiClient.post(URLS.OAUTH_DEVICE_AUTHORIZATION(urlOptions), {
headers: {
'Content-Type': 'application/json',
},
headers: getAuthHeaders(region),
data: {
client_id: FDK_CLI_CLIENT_ID,
scope: DEVICE_AUTH_SCOPES,
Expand Down Expand Up @@ -217,9 +231,7 @@ export default class Auth {
await sleep(interval * 1000);
try {
const tokenRes = await ApiClient.post(URLS.OAUTH_DEVICE_TOKEN(urlOptions), {
headers: {
'Content-Type': 'application/json',
},
headers: getAuthHeaders(region),
data: {
grant_type: DEVICE_CODE_GRANT_TYPE,
client_id: FDK_CLI_CLIENT_ID,
Expand All @@ -240,11 +252,12 @@ export default class Auth {
Env.setEnv(Auth.newDomainToUpdate);
}
else {
await Env.setNewEnvs(Auth.newDomainToUpdate);
await Env.setNewEnvs(Auth.newDomainToUpdate, region);
}
}
ConfigStore.set(CONFIG_KEYS.AUTH_TOKEN, authToken);
ConfigStore.set(CONFIG_KEYS.ORGANIZATION, organization);
updateRegionConfig(region);
const organization_detail =
await OrganizationService.getOrganizationDetails();
ConfigStore.set(
Expand Down Expand Up @@ -272,9 +285,11 @@ export default class Auth {
public static async login(options) {

let env: string;
const region = getRegionFromOptions(options);
Auth.regionToUpdate = region || null;
const port = await getRandomFreePort([]);
if (options.host) {
env = await Env.verifyAndSanitizeEnvValue(options.host);
env = await Env.verifyAndSanitizeEnvValue(options.host, region);
}
else {
env = 'api.fynd.com';
Expand Down Expand Up @@ -317,7 +332,6 @@ export default class Auth {
}
}
try {
const region = getRegionFromOptions(options);
const authFlowConfig = await Auth.getAuthFlowConfig(env, region);
if (Auth.shouldUseDeviceFlow(authFlowConfig)) {
await Auth.runDeviceLogin(env, options);
Expand Down
Loading
Loading