@unito/integration-cli 0.56.0 → 0.56.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/dist/integrationGenerator/integrationBoilerplate/.nvmrc +1 -1
  2. package/dist/integrationGenerator/integrationBoilerplate/Dockerfile +2 -2
  3. package/dist/integrationGenerator/integrationBoilerplate/integrationBoilerplate/.nvmrc +1 -1
  4. package/dist/integrationGenerator/integrationBoilerplate/integrationBoilerplate/Dockerfile +2 -2
  5. package/dist/integrationGenerator/integrationBoilerplate/integrationBoilerplate/package.json +2 -2
  6. package/dist/integrationGenerator/integrationBoilerplate/integrationBoilerplate/src/middlewares/additionalLoggingContext.ts +1 -1
  7. package/dist/integrationGenerator/integrationBoilerplate/package.json +2 -2
  8. package/dist/integrationGenerator/integrationBoilerplate/src/middlewares/additionalLoggingContext.ts +1 -1
  9. package/dist/src/commands/activity.js +13 -4
  10. package/dist/src/commands/dev.js +9 -3
  11. package/dist/src/commands/invite.js +13 -4
  12. package/dist/src/commands/oauth2.d.ts +6 -1
  13. package/dist/src/commands/oauth2.js +58 -55
  14. package/dist/src/commands/publish.js +13 -2
  15. package/dist/src/commands/test.js +9 -3
  16. package/dist/src/errors.d.ts +2 -0
  17. package/dist/src/errors.js +11 -1
  18. package/dist/src/resources/configuration.d.ts +1 -1
  19. package/dist/src/resources/configuration.js +5 -2
  20. package/dist/src/resources/decryption.d.ts +10 -4
  21. package/dist/src/resources/decryption.js +13 -40
  22. package/dist/src/services/integrationsPlatform.d.ts +1 -1
  23. package/dist/src/services/integrationsPlatform.js +1 -8
  24. package/dist/test/commands/activity.test.js +3 -1
  25. package/dist/test/commands/invite.test.js +3 -1
  26. package/dist/test/commands/oauth2.test.js +155 -11
  27. package/dist/test/commands/publish.test.js +23 -23
  28. package/dist/test/resources/decryption.test.js +28 -19
  29. package/dist/test/services/integrationsPlatform.test.js +0 -4
  30. package/oclif.manifest.json +36 -5
  31. package/package.json +3 -3
@@ -1 +1 @@
1
- lts/hydrogen
1
+ lts/iron
@@ -4,7 +4,7 @@
4
4
  # Build
5
5
  #
6
6
 
7
- FROM --platform=$BUILDPLATFORM public.ecr.aws/docker/library/node:18-alpine as build
7
+ FROM --platform=$BUILDPLATFORM public.ecr.aws/docker/library/node:20-alpine as build
8
8
 
9
9
  WORKDIR /build
10
10
 
@@ -19,7 +19,7 @@ RUN npm run compile
19
19
  # Runtime
20
20
  #
21
21
 
22
- FROM --platform=$TARGETPLATFORM public.ecr.aws/docker/library/node:18-alpine as runtime
22
+ FROM --platform=$TARGETPLATFORM public.ecr.aws/docker/library/node:20-alpine as runtime
23
23
 
24
24
  WORKDIR /app
25
25
 
@@ -1 +1 @@
1
- lts/hydrogen
1
+ lts/iron
@@ -4,7 +4,7 @@
4
4
  # Build
5
5
  #
6
6
 
7
- FROM --platform=$BUILDPLATFORM public.ecr.aws/docker/library/node:18-alpine as build
7
+ FROM --platform=$BUILDPLATFORM public.ecr.aws/docker/library/node:20-alpine as build
8
8
 
9
9
  WORKDIR /build
10
10
 
@@ -19,7 +19,7 @@ RUN npm run compile
19
19
  # Runtime
20
20
  #
21
21
 
22
- FROM --platform=$TARGETPLATFORM public.ecr.aws/docker/library/node:18-alpine as runtime
22
+ FROM --platform=$TARGETPLATFORM public.ecr.aws/docker/library/node:20-alpine as runtime
23
23
 
24
24
  WORKDIR /app
25
25
 
@@ -17,7 +17,7 @@
17
17
  "email": "hello@unito.io"
18
18
  },
19
19
  "engines": {
20
- "node": ">=18.0.0"
20
+ "node": ">=20.0.0"
21
21
  },
22
22
  "license": "LicenseRef-LICENSE",
23
23
  "dependencies": {
@@ -28,7 +28,7 @@
28
28
  "devDependencies": {
29
29
  "@types/express": "4.x",
30
30
  "@types/mocha": "10.x",
31
- "@types/node": "18.x",
31
+ "@types/node": "20.x",
32
32
  "@types/uuid": "9.x",
33
33
  "@typescript-eslint/eslint-plugin": "5.x",
34
34
  "@typescript-eslint/parser": "5.x",
@@ -10,7 +10,7 @@ export const extractAdditionalLoggingContext = (req: express.Request, res: expre
10
10
  try {
11
11
  additionalLoggingContext = JSON.parse(additionalLoggingContextHeader);
12
12
  } catch (error) {
13
- logger.warn(`Failed parsing header X-Unito-Additional-Logging-Context: ${additionalLoggingContext}`);
13
+ logger.warn(`Failed parsing header X-Unito-Additional-Logging-Context: ${additionalLoggingContextHeader}`);
14
14
  }
15
15
  }
16
16
 
@@ -17,7 +17,7 @@
17
17
  "email": "hello@unito.io"
18
18
  },
19
19
  "engines": {
20
- "node": ">=18.0.0"
20
+ "node": ">=20.0.0"
21
21
  },
22
22
  "license": "LicenseRef-LICENSE",
23
23
  "dependencies": {
@@ -28,7 +28,7 @@
28
28
  "devDependencies": {
29
29
  "@types/express": "4.x",
30
30
  "@types/mocha": "10.x",
31
- "@types/node": "18.x",
31
+ "@types/node": "20.x",
32
32
  "@types/uuid": "9.x",
33
33
  "@typescript-eslint/eslint-plugin": "5.x",
34
34
  "@typescript-eslint/parser": "5.x",
@@ -10,7 +10,7 @@ export const extractAdditionalLoggingContext = (req: express.Request, res: expre
10
10
  try {
11
11
  additionalLoggingContext = JSON.parse(additionalLoggingContextHeader);
12
12
  } catch (error) {
13
- logger.warn(`Failed parsing header X-Unito-Additional-Logging-Context: ${additionalLoggingContext}`);
13
+ logger.warn(`Failed parsing header X-Unito-Additional-Logging-Context: ${additionalLoggingContextHeader}`);
14
14
  }
15
15
  }
16
16
 
@@ -55,10 +55,19 @@ class Activity extends baseCommand_1.BaseCommand {
55
55
  IntegrationsPlatform.setApiKey(apiKey);
56
56
  IntegrationsPlatform.setEnvironment(environment);
57
57
  core_1.ux.action.start('Finding the integration');
58
- const integration = await IntegrationsPlatform.getIntegrationByName(integrationConfiguration.name);
59
- if (!integration) {
60
- core_1.ux.log(chalk_1.default.redBright(`Integration not found! You can only retrieve activity for existing integrations.`));
61
- this.exit(-1);
58
+ let integration;
59
+ try {
60
+ integration = await IntegrationsPlatform.getIntegrationByName(integrationConfiguration.name);
61
+ }
62
+ catch (error) {
63
+ if (error instanceof IntegrationsPlatform.HttpError && error.status === 403) {
64
+ core_1.ux.log(chalk_1.default.redBright(`Access Denied! You do not have access to this integration.`));
65
+ this.exit(-1);
66
+ }
67
+ else {
68
+ core_1.ux.log(chalk_1.default.redBright(`Integration not found! You can only retrieve activity for existing integrations.`));
69
+ this.exit(-1);
70
+ }
62
71
  }
63
72
  core_1.ux.action.stop();
64
73
  core_1.ux.action.start('Retrieving activity');
@@ -63,15 +63,21 @@ class Dev extends baseCommand_1.BaseCommand {
63
63
  // Resolve the configuration.
64
64
  const environment = flags.environment ?? GlobalConfiguration.Environment.Production;
65
65
  const configuration = await (0, configuration_1.getConfiguration)(environment, flags['config-path']);
66
- const credentialPayload = await (0, decryption_1.decryptTestAccountCredentials)(configuration, environment, configuration_1.CredentialScope.DEVELOPMENT, this.config.configDir);
66
+ let credentials = configuration.testAccounts?.[configuration_1.CredentialScope.DEVELOPMENT];
67
+ if (credentials) {
68
+ ({ entries: credentials } = await (0, decryption_1.decryptEntries)(configuration.name, environment, this.config.configDir, credentials));
69
+ }
70
+ let secrets = {};
67
71
  // Decrypt secrets, if necessary.
68
- const secrets = await (0, decryption_1.decryptSecrets)(configuration, environment, this.config.configDir);
72
+ if (configuration.secrets) {
73
+ secrets = await (0, decryption_1.decryptEntries)(configuration.name, environment, this.config.configDir, configuration.secrets);
74
+ }
69
75
  // Launch the debugger.
70
76
  const commandArguments = [
71
77
  `${process.env.NODE_MODULES_FOLDER}/@unito/integration-debugger/dist/src/index.js`,
72
78
  '--integration-url=http://localhost:9200',
73
79
  `--spawn-process=npm run dev`,
74
- `--credential-payload=${JSON.stringify(credentialPayload)}`,
80
+ `--credential-payload=${JSON.stringify(credentials)}`,
75
81
  `--secrets-payload=${JSON.stringify(secrets)}`,
76
82
  ];
77
83
  if (configuration.graphRelativeUrl) {
@@ -56,10 +56,19 @@ class Invite extends baseCommand_1.BaseCommand {
56
56
  },
57
57
  ]);
58
58
  core_1.ux.action.start('Finding the integration');
59
- const integration = await IntegrationsPlatform.getIntegrationByName(integrationConfiguration.name);
60
- if (!integration) {
61
- core_1.ux.log(chalk_1.default.redBright(`Integration not found! You can only invite users to a published integration.`));
62
- this.exit(-1);
59
+ let integration;
60
+ try {
61
+ integration = await IntegrationsPlatform.getIntegrationByName(integrationConfiguration.name);
62
+ }
63
+ catch (error) {
64
+ if (error instanceof IntegrationsPlatform.HttpError && error.status === 403) {
65
+ core_1.ux.log(chalk_1.default.redBright(`Access Denied! You do not have access to this integration.`));
66
+ this.exit(-1);
67
+ }
68
+ else {
69
+ core_1.ux.log(chalk_1.default.redBright(`Integration not found! You can only invite users to a published integration.`));
70
+ this.exit(-1);
71
+ }
63
72
  }
64
73
  core_1.ux.action.stop();
65
74
  core_1.ux.action.start('Inviting user');
@@ -1,9 +1,14 @@
1
1
  import { Command } from '@oclif/core';
2
+ import { Environment } from '../resources/globalConfiguration';
2
3
  export default class Oauth2 extends Command {
4
+ static summary: string;
3
5
  static description: string;
4
6
  static examples: string[];
5
7
  static flags: {
6
- accountName: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
8
+ 'test-account': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
9
+ environment: import("@oclif/core/lib/interfaces").OptionFlag<Environment | undefined, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
10
+ reauth: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
11
+ 'config-path': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
7
12
  };
8
13
  catch(error: Error): Promise<void>;
9
14
  run(): Promise<void>;
@@ -2,18 +2,43 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const tslib_1 = require("tslib");
4
4
  const core_1 = require("@oclif/core");
5
- const inquirer_1 = tslib_1.__importDefault(require("inquirer"));
6
5
  const chalk_1 = tslib_1.__importDefault(require("chalk"));
7
6
  const configuration_1 = require("../resources/configuration");
8
7
  const Oauth2Helper = tslib_1.__importStar(require("../services/oauth2Helper"));
8
+ const IntegrationsPlatform = tslib_1.__importStar(require("../services/integrationsPlatform"));
9
9
  const configurationTypes_1 = require("../configurationTypes");
10
10
  const errors_1 = require("../errors");
11
11
  const globalConfiguration_1 = require("../resources/globalConfiguration");
12
+ const decryption_1 = require("../resources/decryption");
12
13
  class Oauth2 extends core_1.Command {
13
- static description = 'Perform an OAuth2 workflow locally';
14
+ static summary = 'Perform an OAuth2 workflow locally';
15
+ static description = 'The Oauth2 command allows you to perform an OAuth2 workflow locally, either to create a new test account or to refresh an existing one.';
14
16
  static examples = ['<%= config.bin %> <%= command.id %>'];
15
17
  static flags = {
16
- accountName: core_1.Flags.string({ description: 'Name of the account used to perfom oauth2 actions' }),
18
+ 'test-account': core_1.Flags.string({
19
+ description: 'Test account to use.',
20
+ options: Object.values(configuration_1.CredentialScope),
21
+ default: configuration_1.CredentialScope.DEVELOPMENT,
22
+ }),
23
+ environment: core_1.Flags.custom({
24
+ description: 'the environment of the platform',
25
+ options: Object.values(globalConfiguration_1.Environment),
26
+ default: globalConfiguration_1.Environment.Production,
27
+ })(),
28
+ reauth: core_1.Flags.boolean({
29
+ description: 'triggers a new oauth2 flow to collect credentials and overwrite the current one',
30
+ default: false,
31
+ }),
32
+ 'config-path': core_1.Flags.string({
33
+ summary: 'relative path to a custom ".unito.json" file',
34
+ description: `Use a custom configuration file instead of the default '.unito.json' or other environment specific
35
+ ones.
36
+
37
+ If you want to force the CLI to use a specific configuration file, you can use this flag to specify the relative
38
+ path from your integration's root folder (with a leading '/').
39
+
40
+ Usage: <%= config.bin %> <%= command.id %> --config-path=/myCustomConfig.json`,
41
+ }),
17
42
  };
18
43
  async catch(error) {
19
44
  /* istanbul ignore if */
@@ -24,74 +49,52 @@ class Oauth2 extends core_1.Command {
24
49
  }
25
50
  async run() {
26
51
  const { flags } = await this.parse(Oauth2);
27
- const accountName = flags.accountName ?? 'development';
28
- // Might want to expose Environment flag, but for now we'll default to Production
29
- const configuration = await (0, configuration_1.getConfiguration)(globalConfiguration_1.Environment.Production);
30
- let oauth2 = configuration.authorizations?.find(authorization => authorization.method === configurationTypes_1.Method.OAUTH2 && authorization.name === accountName && authorization.development)?.oauth2;
52
+ const testAccount = flags['test-account'] ?? 'development';
53
+ const environment = flags.environment ?? globalConfiguration_1.Environment.Production;
54
+ const configuration = await (0, configuration_1.getConfiguration)(environment, flags['config-path']);
55
+ // First check for development oauth2 authorization scheme
56
+ let oauth2 = configuration.authorizations?.find(authorization => authorization.method === configurationTypes_1.Method.OAUTH2 && authorization.development)?.oauth2;
57
+ // Try for any oauth2 authorization scheme if there was no development one
31
58
  if (!oauth2) {
32
- const { oauth2Information } = await inquirer_1.default.prompt([
33
- {
34
- name: 'oauth2Information',
35
- type: 'editor',
36
- message: `${chalk_1.default.yellowBright('OAuth2 credentials')} that will be used to authenticate to the API`,
37
- validate: (value) => {
38
- try {
39
- JSON.parse(value);
40
- }
41
- catch (e) {
42
- return false;
43
- }
44
- return true;
45
- },
46
- default: JSON.stringify({
47
- clientId: '',
48
- clientSecret: '',
49
- tokenUrl: '',
50
- authorizationUrl: '',
51
- scopes: [{ name: '', help: '' }],
52
- requestContentType: 'application/x-www-form-urlencoded',
53
- responseContentType: 'application/json',
54
- grantType: 'authorization_code',
55
- }, null, 2),
56
- },
57
- ]);
58
- oauth2 = JSON.parse(oauth2Information);
59
- if (!configuration.authorizations) {
60
- configuration.authorizations = [];
61
- }
62
- configuration.authorizations.push({
63
- name: 'development',
64
- method: configurationTypes_1.Method.OAUTH2,
65
- development: true,
66
- oauth2,
67
- });
68
- core_1.ux.action.start('Saving configuration');
69
- await (0, configuration_1.writeConfiguration)(configuration);
70
- core_1.ux.action.stop();
59
+ oauth2 = configuration.authorizations?.find(authorization => authorization.method === configurationTypes_1.Method.OAUTH2)?.oauth2;
71
60
  }
72
61
  if (!oauth2) {
73
- throw new Error('No oauth2 information found');
62
+ throw new errors_1.MissingAuth2AuthorizationError();
74
63
  }
75
- const account = accountName === 'development' ? configuration.testAccounts?.development : configuration.testAccounts?.compliance;
64
+ // Decrypt any encrypted oauth2 fields (mainly clientSecret)
65
+ oauth2 = (await (0, decryption_1.decryptEntries)(configuration.name, environment, this.config.configDir, oauth2)).entries;
66
+ const account = testAccount === 'development' ? configuration.testAccounts?.development : configuration.testAccounts?.compliance;
76
67
  let credentials;
77
- if (Object.keys(account || {}).length === 0) {
78
- core_1.ux.action.start(`Starting Oauth2 flow for account: ${accountName}`);
68
+ if (!account || Object.entries(account).length === 0 || flags.reauth) {
69
+ core_1.ux.action.start(`Starting Oauth2 flow for account: ${testAccount}`);
79
70
  credentials = await Oauth2Helper.performOAuth2Flow(oauth2);
80
71
  core_1.ux.action.stop();
81
72
  }
82
73
  else {
83
74
  if (!account?.refreshToken) {
84
- core_1.ux.log('No refresh token found, aborting refresh token flow');
75
+ core_1.ux.log(chalk_1.default.redBright('No refresh token found, nothing to do.'));
76
+ return;
85
77
  }
86
- core_1.ux.action.start(`Refreshing test account ${accountName}`);
87
- credentials = await Oauth2Helper.updateToken({ ...oauth2, refreshToken: account?.refreshToken });
78
+ const decryptionResult = await (0, decryption_1.decryptEntries)(configuration.name, environment, this.config.configDir, account);
79
+ core_1.ux.action.start(`Refreshing test account ${testAccount}`);
80
+ credentials = await Oauth2Helper.updateToken({
81
+ ...oauth2,
82
+ refreshToken: decryptionResult.entries?.refreshToken,
83
+ });
84
+ // If provider response doesn't contain a new refresh token, use the one we already have
88
85
  if (!credentials.refreshToken) {
89
86
  credentials.refreshToken = account?.refreshToken;
90
87
  }
88
+ if (decryptionResult.decryptedKeys.includes('accessToken')) {
89
+ credentials.accessToken = (await IntegrationsPlatform.encryptData(configuration.name, credentials.accessToken)).encryptedData;
90
+ }
91
+ if (decryptionResult.decryptedKeys.includes('refreshToken')) {
92
+ credentials.refreshToken = (await IntegrationsPlatform.encryptData(configuration.name, credentials.refreshToken)).encryptedData;
93
+ }
91
94
  core_1.ux.action.stop();
92
95
  }
93
- core_1.ux.action.start(`Saving credentials for account: ${accountName}`);
94
- await (0, configuration_1.writeTestAccount)(configuration, credentials, accountName);
96
+ core_1.ux.action.start(`Saving credentials for account: ${testAccount}`);
97
+ await (0, configuration_1.writeTestAccount)(configuration, credentials, testAccount);
95
98
  core_1.ux.action.stop();
96
99
  return;
97
100
  }
@@ -144,8 +144,19 @@ class Publish extends baseCommand_1.BaseCommand {
144
144
  });
145
145
  }
146
146
  async updateRegistry(integrationConfiguration) {
147
- const integrations = await IntegrationsPlatform.getIntegrations();
148
- const existing = integrations.find(integration => integration.name === integrationConfiguration.name);
147
+ let existing;
148
+ try {
149
+ existing = await IntegrationsPlatform.getIntegrationByName(integrationConfiguration.name);
150
+ }
151
+ catch (error) {
152
+ if (error instanceof IntegrationsPlatform.HttpError && error.status === 403) {
153
+ core_1.ux.log(chalk_1.default.redBright(`Access Denied! You do not have access to this integration.`));
154
+ this.exit(-1);
155
+ }
156
+ else {
157
+ existing = undefined;
158
+ }
159
+ }
149
160
  // Create or update the integration.
150
161
  let updated;
151
162
  for (const authorization of integrationConfiguration.authorizations ?? []) {
@@ -92,14 +92,20 @@ class Test extends baseCommand_1.BaseCommand {
92
92
  let credentialPayload = flags['credential-payload'];
93
93
  // Default to test account's credentials if none are specifically provided
94
94
  if (credentialPayload === null || credentialPayload === undefined) {
95
- const decryptedCredentials = await (0, decryption_1.decryptTestAccountCredentials)(configuration, environment, flags['test-account'], this.config.configDir);
96
- credentialPayload = JSON.stringify(decryptedCredentials);
95
+ let credentials = configuration.testAccounts?.[flags['test-account']];
96
+ if (credentials) {
97
+ ({ entries: credentials } = await (0, decryption_1.decryptEntries)(configuration.name, environment, this.config.configDir, credentials));
98
+ }
99
+ credentialPayload = JSON.stringify(credentials);
97
100
  }
98
101
  if (!credentialPayload || credentialPayload === '{}') {
99
102
  throw new errors_1.MissingCredentialsError();
100
103
  }
104
+ let secrets = {};
101
105
  // Decrypt secrets, if necessary.
102
- const secrets = await (0, decryption_1.decryptSecrets)(configuration, environment, this.config.configDir);
106
+ if (configuration.secrets) {
107
+ secrets = await (0, decryption_1.decryptEntries)(configuration.name, environment, this.config.configDir, configuration.secrets);
108
+ }
103
109
  // Launch the tests.
104
110
  const commandArguments = [
105
111
  `${process.env.NODE_MODULES_FOLDER}/@unito/integration-debugger/dist/src/index.js`,
@@ -15,6 +15,8 @@ export declare class InvalidRequestContentTypeError extends Error {
15
15
  }
16
16
  export declare class MissingCredentialsError extends Error {
17
17
  }
18
+ export declare class MissingAuth2AuthorizationError extends Error {
19
+ }
18
20
  export declare class FileSizeExceeded extends Error {
19
21
  }
20
22
  export declare class MissingApiKey extends Error {
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.handleError = exports.ConfigurationInvalid = exports.EntryDecryptionError = exports.DecryptionAuthenticationError = exports.AuthenticationFailed = exports.MissingApiKey = exports.FileSizeExceeded = exports.MissingCredentialsError = exports.InvalidRequestContentTypeError = exports.FailedToRetrieveAccessTokenError = exports.NoRefreshTokenError = exports.MissingEnvironmentVariableError = exports.ConfigurationMalformed = exports.NoConfigurationFileError = exports.NoIntegrationFoundError = void 0;
3
+ exports.handleError = exports.ConfigurationInvalid = exports.EntryDecryptionError = exports.DecryptionAuthenticationError = exports.AuthenticationFailed = exports.MissingApiKey = exports.FileSizeExceeded = exports.MissingAuth2AuthorizationError = exports.MissingCredentialsError = exports.InvalidRequestContentTypeError = exports.FailedToRetrieveAccessTokenError = exports.NoRefreshTokenError = exports.MissingEnvironmentVariableError = exports.ConfigurationMalformed = exports.NoConfigurationFileError = exports.NoIntegrationFoundError = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const core_1 = require("@oclif/core");
6
6
  const chalk_1 = tslib_1.__importDefault(require("chalk"));
@@ -32,6 +32,9 @@ exports.InvalidRequestContentTypeError = InvalidRequestContentTypeError;
32
32
  class MissingCredentialsError extends Error {
33
33
  }
34
34
  exports.MissingCredentialsError = MissingCredentialsError;
35
+ class MissingAuth2AuthorizationError extends Error {
36
+ }
37
+ exports.MissingAuth2AuthorizationError = MissingAuth2AuthorizationError;
35
38
  class FileSizeExceeded extends Error {
36
39
  }
37
40
  exports.FileSizeExceeded = FileSizeExceeded;
@@ -154,6 +157,13 @@ function handleError(command, error) {
154
157
  command.logToStderr(`Make sure they were encrypted by the same environement (Currently targeting: ${error.environment}).`);
155
158
  handled = true;
156
159
  }
160
+ else if (error instanceof MissingAuth2AuthorizationError) {
161
+ command.logToStderr();
162
+ command.logToStderr(chalk_1.default.redBright(`No Oauth2 authorization found in your configuration file`));
163
+ // TODO: Update link once the documentation is made available publicly
164
+ command.logToStderr('See https://staging-dev.unito.io/docs/integrations/configuration/authorizations#oauth-2 for more information on how to configure Oauth2');
165
+ handled = true;
166
+ }
157
167
  return handled;
158
168
  }
159
169
  exports.handleError = handleError;
@@ -23,7 +23,7 @@ export declare function getConfiguration(environment: Environment, customConfigP
23
23
  /**
24
24
  * Write the configuration to the default configuration file.
25
25
  */
26
- export declare function writeConfiguration(configuration: Configuration): Promise<void>;
26
+ export declare function writeConfiguration(configuration: Configuration, environment?: Environment, customConfigPath?: string): Promise<void>;
27
27
  export declare function writeTestAccount(configuration: Configuration, account: {
28
28
  accessToken: string;
29
29
  refreshToken?: string;
@@ -88,10 +88,13 @@ exports.getConfiguration = getConfiguration;
88
88
  /**
89
89
  * Write the configuration to the default configuration file.
90
90
  */
91
- async function writeConfiguration(configuration) {
91
+ async function writeConfiguration(configuration, environment = globalConfiguration_1.Environment.Production, customConfigPath) {
92
92
  (0, integrations_1.validateIsIntegrationDirectory)();
93
93
  await validateConfiguration(configuration);
94
- await fs.promises.writeFile(getConfigurationPath(globalConfiguration_1.Environment.Production), JSON.stringify(configuration, null, 2));
94
+ const configurationPath = customConfigPath
95
+ ? `${process.cwd()}${customConfigPath.startsWith('/') ? customConfigPath : `/${customConfigPath}`}`
96
+ : getConfigurationPath(environment);
97
+ await fs.promises.writeFile(configurationPath, JSON.stringify(configuration, null, 2));
95
98
  }
96
99
  exports.writeConfiguration = writeConfiguration;
97
100
  async function writeTestAccount(configuration, account, accountName) {
@@ -1,5 +1,11 @@
1
- import { Configuration } from '../configurationTypes';
2
1
  import * as GlobalConfiguration from './globalConfiguration';
3
- import { CredentialScope } from './configuration';
4
- export declare function decryptTestAccountCredentials(configuration: Configuration, environment: GlobalConfiguration.Environment, testAccount: CredentialScope, configDir: string): Promise<Record<string, unknown>>;
5
- export declare function decryptSecrets(configuration: Configuration, environment: GlobalConfiguration.Environment, configDir: string): Promise<Record<string, unknown>>;
2
+ /**
3
+ * Object returned when decrypting
4
+ *
5
+ * Allows the caller to know which keys were decrypted to reencrypt them later as needed
6
+ */
7
+ export type DecryptionResult = {
8
+ decryptedKeys: string[];
9
+ entries: Record<string, unknown>;
10
+ };
11
+ export declare function decryptEntries(configurationName: string, environment: GlobalConfiguration.Environment, configDir: string, entries: Record<string, unknown>, fieldName?: string): Promise<DecryptionResult>;
@@ -1,62 +1,35 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.decryptSecrets = exports.decryptTestAccountCredentials = void 0;
3
+ exports.decryptEntries = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const errors_1 = require("../errors");
6
6
  const GlobalConfiguration = tslib_1.__importStar(require("./globalConfiguration"));
7
7
  const integrationsPlatform_1 = require("./integrationsPlatform");
8
8
  const configuration_1 = require("./configuration");
9
9
  const integrationsPlatform_2 = require("../services/integrationsPlatform");
10
- async function decryptTestAccountCredentials(configuration, environment, testAccount, configDir) {
11
- const credentials = configuration.testAccounts?.[testAccount];
12
- if (!credentials) {
13
- return {};
14
- }
15
- // Get encrypted credential entries
16
- const encryptedEntries = Object.entries(credentials).filter((entry) => typeof entry[1] === 'string' && entry[1].startsWith(configuration_1.ENCRYPTION_PREFIX));
10
+ async function decryptEntries(configurationName, environment, configDir, entries, fieldName = 'entries') {
11
+ // Get encrypted entries
12
+ const encryptedEntries = Object.entries(entries).filter((entry) => typeof entry[1] === 'string' && entry[1].startsWith(configuration_1.ENCRYPTION_PREFIX));
17
13
  if (!encryptedEntries.length) {
18
- return credentials;
19
- }
20
- // Copy credentials to avoid mutating the configuration.
21
- const decryptedCredentials = structuredClone(credentials);
22
- const globalConfiguration = await GlobalConfiguration.read(configDir);
23
- try {
24
- await (0, integrationsPlatform_1.validateAuthenticated)(globalConfiguration, environment);
25
- }
26
- catch (err) {
27
- throw new errors_1.DecryptionAuthenticationError('credentials');
28
- }
29
- for (const [key, encryptedCredential] of encryptedEntries) {
30
- try {
31
- decryptedCredentials[key] = (await (0, integrationsPlatform_2.decryptData)(configuration.name, encryptedCredential)).decryptedData;
32
- }
33
- catch (err) {
34
- throw new errors_1.EntryDecryptionError(key, encryptedCredential, environment);
35
- }
36
- }
37
- return decryptedCredentials;
38
- }
39
- exports.decryptTestAccountCredentials = decryptTestAccountCredentials;
40
- async function decryptSecrets(configuration, environment, configDir) {
41
- if (!Object.entries(configuration.secrets ?? {}).length) {
42
- return {};
14
+ return { decryptedKeys: [], entries };
43
15
  }
44
16
  const globalConfiguration = await GlobalConfiguration.read(configDir);
45
17
  try {
46
18
  await (0, integrationsPlatform_1.validateAuthenticated)(globalConfiguration, environment);
47
19
  }
48
20
  catch (err) {
49
- throw new errors_1.DecryptionAuthenticationError('secrets');
21
+ throw new errors_1.DecryptionAuthenticationError(fieldName);
50
22
  }
51
- const decryptedSecrets = {};
52
- for (const [key, encryptedSecret] of Object.entries(configuration.secrets ?? {})) {
23
+ // Copy credentials to avoid mutating the configuration.
24
+ const decryptedEntries = structuredClone(entries);
25
+ for (const [key, encryptedEntry] of encryptedEntries) {
53
26
  try {
54
- decryptedSecrets[key] = (await (0, integrationsPlatform_2.decryptData)(configuration.name, encryptedSecret)).decryptedData;
27
+ decryptedEntries[key] = (await (0, integrationsPlatform_2.decryptData)(configurationName, encryptedEntry)).decryptedData;
55
28
  }
56
29
  catch (err) {
57
- throw new errors_1.EntryDecryptionError(key, encryptedSecret, environment);
30
+ throw new errors_1.EntryDecryptionError(key, encryptedEntry, environment);
58
31
  }
59
32
  }
60
- return decryptedSecrets;
33
+ return { decryptedKeys: encryptedEntries.map(([key]) => key), entries: decryptedEntries };
61
34
  }
62
- exports.decryptSecrets = decryptSecrets;
35
+ exports.decryptEntries = decryptEntries;
@@ -27,7 +27,7 @@ export declare function reencryptData(integrationName: string, encryptedData: st
27
27
  encryptedData: string;
28
28
  }>;
29
29
  export declare function getIntegration(integrationId: number): Promise<Integration>;
30
- export declare function getIntegrationByName(integrationName: string): Promise<Integration | null>;
30
+ export declare function getIntegrationByName(integrationName: string): Promise<Integration>;
31
31
  export declare function getIntegrations(): Promise<IntegrationSummary[]>;
32
32
  export declare function publishIntegration(archivePath: string): Promise<Integration>;
33
33
  export declare function createIntegration(configuration: Configuration): Promise<Integration>;
@@ -62,14 +62,7 @@ async function getIntegration(integrationId) {
62
62
  }
63
63
  exports.getIntegration = getIntegration;
64
64
  async function getIntegrationByName(integrationName) {
65
- let integration;
66
- try {
67
- integration = await integrations_platform_client_1.default.getIntegrationByName(integrationName);
68
- }
69
- catch {
70
- return null;
71
- }
72
- return integration;
65
+ return integrations_platform_client_1.default.getIntegrationByName(integrationName);
73
66
  }
74
67
  exports.getIntegrationByName = getIntegrationByName;
75
68
  async function getIntegrations() {
@@ -53,7 +53,9 @@ describe('activity', () => {
53
53
  });
54
54
  test_1.test
55
55
  .stdout()
56
- .stub(IntegrationsPlatform, 'getIntegrationByName', () => undefined)
56
+ .stub(IntegrationsPlatform, 'getIntegrationByName', () => {
57
+ throw new Error();
58
+ })
57
59
  .command(['activity'])
58
60
  .catch(error => {
59
61
  (0, test_1.expect)(error.message).to.equal('EEXIT: -1');
@@ -47,7 +47,9 @@ describe('invite', () => {
47
47
  test_1.test
48
48
  .stdout()
49
49
  .stub(GlobalConfiguration, 'read', () => ({ email: 'foo@bar.com' }))
50
- .stub(IntegrationsPlatform, 'getIntegrationByName', () => undefined)
50
+ .stub(IntegrationsPlatform, 'getIntegrationByName', () => {
51
+ throw new Error();
52
+ })
51
53
  .command(['invite'])
52
54
  .catch(error => {
53
55
  (0, test_1.expect)(error.message).to.equal('EEXIT: -1');
@@ -7,12 +7,13 @@ const sinon = tslib_1.__importStar(require("sinon"));
7
7
  const inquirer_1 = tslib_1.__importDefault(require("inquirer"));
8
8
  const Configuration = tslib_1.__importStar(require("../../src/resources/configuration"));
9
9
  const oauth2Service = tslib_1.__importStar(require("../../src/services/oauth2Helper"));
10
+ const IntegrationsPlatform = tslib_1.__importStar(require("../../src/services/integrationsPlatform"));
11
+ const decryptionResource = tslib_1.__importStar(require("../../src/resources/decryption"));
10
12
  const configurationTypes_1 = require("../../src/configurationTypes");
11
13
  describe('oauth2', () => {
12
14
  const INTEGRATION_NAME = 'myintegration';
13
15
  const sandbox = sinon.createSandbox();
14
16
  let getConfigurationsStub;
15
- let writeConfigurationStub;
16
17
  let writeTestAccountStub;
17
18
  let performOAuth2FlowStub;
18
19
  let updateTokenStub;
@@ -30,7 +31,7 @@ describe('oauth2', () => {
30
31
  name: INTEGRATION_NAME,
31
32
  authorizations: [
32
33
  {
33
- name: 'development',
34
+ name: 'MyAuthorization',
34
35
  method: configurationTypes_1.Method.OAUTH2,
35
36
  oauth2: oauth2Information,
36
37
  },
@@ -48,7 +49,6 @@ describe('oauth2', () => {
48
49
  sandbox.stub(inquirer_1.default, 'prompt').resolves({
49
50
  oauth2Information: JSON.stringify(oauth2Information),
50
51
  });
51
- writeConfigurationStub = sandbox.stub(Configuration, 'writeConfiguration');
52
52
  writeTestAccountStub = sandbox.stub(Configuration, 'writeTestAccount');
53
53
  performOAuth2FlowStub = sandbox.stub(oauth2Service, 'performOAuth2Flow');
54
54
  performOAuth2FlowStub.resolves(credentials);
@@ -63,16 +63,48 @@ describe('oauth2', () => {
63
63
  ...baseConfiguration,
64
64
  authorizations: [],
65
65
  }))
66
- .command(['oauth2', '--accountName', 'development'])
67
- .it("asks for authentication information when it's not setup in the configuration file", () => {
68
- (0, test_1.expect)(writeConfigurationStub.getCall(0)?.firstArg).to.deep.equal({
69
- ...baseConfiguration,
70
- authorizations: [{ oauth2: oauth2Information, name: 'development', method: 'oauth2', development: true }],
71
- });
66
+ .command(['oauth2', '--test-account', 'development'])
67
+ .exit(-1)
68
+ .it("Errors out if no oauth2 authorization scheme is found in the integration's configuration");
69
+ test_1.test
70
+ .stdout()
71
+ .do(() => getConfigurationsStub.returns({
72
+ ...baseConfiguration,
73
+ authorizations: [
74
+ ...(baseConfiguration.authorizations ?? []),
75
+ {
76
+ name: 'Development Authorization',
77
+ method: configurationTypes_1.Method.OAUTH2,
78
+ oauth2: { ...oauth2Information, clientId: 'devClientID' },
79
+ development: true,
80
+ },
81
+ ],
82
+ }))
83
+ .command(['oauth2', '--test-account', 'development'])
84
+ .it('prioritize development authorization', () => {
85
+ (0, test_1.expect)(performOAuth2FlowStub.getCalls().length).to.equal(1);
86
+ (0, test_1.expect)(performOAuth2FlowStub.getCall(0).args).to.deep.equal([{ ...oauth2Information, clientId: 'devClientID' }]);
87
+ (0, test_1.expect)(updateTokenStub.getCalls().length).to.equal(0);
88
+ (0, test_1.expect)(writeTestAccountStub.getCall(0).args).to.deep.equal([
89
+ {
90
+ ...baseConfiguration,
91
+ authorizations: [
92
+ ...(baseConfiguration.authorizations ?? []),
93
+ {
94
+ name: 'Development Authorization',
95
+ method: configurationTypes_1.Method.OAUTH2,
96
+ oauth2: { ...oauth2Information, clientId: 'devClientID' },
97
+ development: true,
98
+ },
99
+ ],
100
+ },
101
+ credentials,
102
+ 'development',
103
+ ]);
72
104
  });
73
105
  test_1.test
74
106
  .stdout()
75
- .command(['oauth2', '--accountName', 'development'])
107
+ .command(['oauth2', '--test-account', 'development'])
76
108
  .it('performs the oauth flow when there is no test accounts and the auth information are setup', () => {
77
109
  (0, test_1.expect)(performOAuth2FlowStub.getCalls().length).to.equal(1);
78
110
  (0, test_1.expect)(updateTokenStub.getCalls().length).to.equal(0);
@@ -86,7 +118,53 @@ describe('oauth2', () => {
86
118
  development: credentials,
87
119
  },
88
120
  }))
89
- .command(['oauth2', '--accountName', 'development'])
121
+ .command(['oauth2', '--test-account', 'development', '--reauth'])
122
+ .it('performs the oauth flow when there is a test accounts, the auth information are setup and --reauth flag is present', () => {
123
+ (0, test_1.expect)(performOAuth2FlowStub.getCalls().length).to.equal(1);
124
+ (0, test_1.expect)(updateTokenStub.getCalls().length).to.equal(0);
125
+ (0, test_1.expect)(writeTestAccountStub.getCall(0).args).to.deep.equal([
126
+ {
127
+ ...baseConfiguration,
128
+ testAccounts: {
129
+ development: credentials,
130
+ },
131
+ },
132
+ credentials,
133
+ 'development',
134
+ ]);
135
+ });
136
+ test_1.test
137
+ .stdout()
138
+ .do(() => getConfigurationsStub.returns({
139
+ ...baseConfiguration,
140
+ testAccounts: {
141
+ development: { something: 'something' },
142
+ },
143
+ }))
144
+ .command(['oauth2', '--test-account', 'compliance'])
145
+ .it('performs the oauth flow when the requested test account is not setup', () => {
146
+ (0, test_1.expect)(performOAuth2FlowStub.getCalls().length).to.equal(1);
147
+ (0, test_1.expect)(updateTokenStub.getCalls().length).to.equal(0);
148
+ (0, test_1.expect)(writeTestAccountStub.getCall(0).args).to.deep.equal([
149
+ {
150
+ ...baseConfiguration,
151
+ testAccounts: {
152
+ development: { something: 'something' },
153
+ },
154
+ },
155
+ credentials,
156
+ 'compliance',
157
+ ]);
158
+ });
159
+ test_1.test
160
+ .stdout()
161
+ .do(() => getConfigurationsStub.returns({
162
+ ...baseConfiguration,
163
+ testAccounts: {
164
+ development: credentials,
165
+ },
166
+ }))
167
+ .command(['oauth2', '--test-account', 'development'])
90
168
  .it('refresh the token when there is an existing test accounts and the auth information are setup', () => {
91
169
  (0, test_1.expect)(performOAuth2FlowStub.getCalls().length).to.equal(0);
92
170
  (0, test_1.expect)(updateTokenStub.getCalls().length).to.equal(1);
@@ -101,4 +179,70 @@ describe('oauth2', () => {
101
179
  'development',
102
180
  ]);
103
181
  });
182
+ test_1.test
183
+ .stdout()
184
+ .do(() => {
185
+ getConfigurationsStub.returns({
186
+ ...baseConfiguration,
187
+ authorizations: [
188
+ {
189
+ name: 'Authorization',
190
+ method: configurationTypes_1.Method.OAUTH2,
191
+ oauth2: { ...oauth2Information, clientSecret: `${Configuration.ENCRYPTION_PREFIX}devClientID` },
192
+ },
193
+ ],
194
+ testAccounts: {
195
+ development: {
196
+ accessToken: `${Configuration.ENCRYPTION_PREFIX}devAccessToken`,
197
+ refreshToken: `${Configuration.ENCRYPTION_PREFIX}devRefreshToken`,
198
+ },
199
+ },
200
+ });
201
+ sandbox
202
+ .stub(decryptionResource, 'decryptEntries')
203
+ .onFirstCall()
204
+ .resolves({ decryptedKeys: ['clientSecret'], entries: { ...oauth2Information, clientSecret: 'devClientID' } })
205
+ .onSecondCall()
206
+ .resolves({
207
+ decryptedKeys: ['accessToken', 'refreshToken'],
208
+ entries: {
209
+ accessToken: 'devAccessToken',
210
+ refreshToken: 'devRefreshToken',
211
+ },
212
+ });
213
+ sandbox
214
+ .stub(IntegrationsPlatform, 'encryptData')
215
+ .onFirstCall()
216
+ .resolves({ encryptedData: `${Configuration.ENCRYPTION_PREFIX}encryptedAccessToken` })
217
+ .onSecondCall()
218
+ .resolves({ encryptedData: `${Configuration.ENCRYPTION_PREFIX}encryptedRefreshToken` });
219
+ })
220
+ .command(['oauth2', '--test-account', 'development'])
221
+ .it('decrypt oauth2 authorization entries and test-account', () => {
222
+ (0, test_1.expect)(performOAuth2FlowStub.getCalls().length).to.equal(0);
223
+ (0, test_1.expect)(updateTokenStub.getCalls().length).to.equal(1);
224
+ (0, test_1.expect)(writeTestAccountStub.getCall(0).args).to.deep.equal([
225
+ {
226
+ ...baseConfiguration,
227
+ authorizations: [
228
+ {
229
+ name: 'Authorization',
230
+ method: configurationTypes_1.Method.OAUTH2,
231
+ oauth2: { ...oauth2Information, clientSecret: `${Configuration.ENCRYPTION_PREFIX}devClientID` },
232
+ },
233
+ ],
234
+ testAccounts: {
235
+ development: {
236
+ accessToken: `${Configuration.ENCRYPTION_PREFIX}devAccessToken`,
237
+ refreshToken: `${Configuration.ENCRYPTION_PREFIX}devRefreshToken`,
238
+ },
239
+ },
240
+ },
241
+ {
242
+ accessToken: `${Configuration.ENCRYPTION_PREFIX}encryptedAccessToken`,
243
+ refreshToken: `${Configuration.ENCRYPTION_PREFIX}encryptedRefreshToken`,
244
+ },
245
+ 'development',
246
+ ]);
247
+ });
104
248
  });
@@ -55,7 +55,7 @@ describe('Publish', () => {
55
55
  (0, test_1.expect)(customPath).to.be.undefined;
56
56
  return { name: 'a' };
57
57
  })
58
- .stub(IntegrationsPlatform, 'getIntegrations', () => [])
58
+ .stub(IntegrationsPlatform, 'getIntegrationByName', () => undefined)
59
59
  .stub(FileSystem, 'getFileBuffer', () => Promise.resolve(Buffer.from('')))
60
60
  .command(['publish'])
61
61
  .it('publish the integration - default', ctx => {
@@ -72,7 +72,7 @@ describe('Publish', () => {
72
72
  (0, test_1.expect)(customPath).to.equal('/custom-config.json');
73
73
  return { name: 'a' };
74
74
  })
75
- .stub(IntegrationsPlatform, 'getIntegrations', () => [])
75
+ .stub(IntegrationsPlatform, 'getIntegrationByName', () => undefined)
76
76
  .stub(FileSystem, 'getFileBuffer', () => Promise.resolve(Buffer.from('')))
77
77
  .command(['publish', '--config-path', '/custom-config.json'])
78
78
  .it('publish the integration with custom config - default', ctx => {
@@ -89,7 +89,7 @@ describe('Publish', () => {
89
89
  (0, test_1.expect)(customPath).to.be.undefined;
90
90
  return { name: 'a' };
91
91
  })
92
- .stub(IntegrationsPlatform, 'getIntegrations', () => [])
92
+ .stub(IntegrationsPlatform, 'getIntegrationByName', () => undefined)
93
93
  .stub(FileSystem, 'getFileBuffer', () => Promise.resolve(Buffer.from('')))
94
94
  .command(['publish', '--environment', 'local'])
95
95
  .it('publish the integration - local', ctx => {
@@ -108,7 +108,7 @@ describe('Publish', () => {
108
108
  return { name: 'a' };
109
109
  })
110
110
  .stub(IntegrationConfiguration, 'writeConfiguration', () => Promise)
111
- .stub(IntegrationsPlatform, 'getIntegrations', () => [])
111
+ .stub(IntegrationsPlatform, 'getIntegrationByName', () => undefined)
112
112
  .stub(FileSystem, 'getFileBuffer', () => Promise.resolve(Buffer.from('')))
113
113
  .command(['publish', '--environment', 'local'])
114
114
  .it('publish the integration using .unito.local.json - local', ctx => {
@@ -126,7 +126,7 @@ describe('Publish', () => {
126
126
  (0, test_1.expect)(customPath).to.be.undefined;
127
127
  return { name: 'a' };
128
128
  })
129
- .stub(IntegrationsPlatform, 'getIntegrations', () => [])
129
+ .stub(IntegrationsPlatform, 'getIntegrationByName', () => undefined)
130
130
  .stub(FileSystem, 'getFileBuffer', () => Promise.resolve(Buffer.from('')))
131
131
  .command(['publish', '--environment', 'staging'])
132
132
  .it('publish the integration - staging', ctx => {
@@ -145,7 +145,7 @@ describe('Publish', () => {
145
145
  return {};
146
146
  })
147
147
  .stub(IntegrationConfiguration, 'writeConfiguration', () => Promise)
148
- .stub(IntegrationsPlatform, 'getIntegrations', () => [])
148
+ .stub(IntegrationsPlatform, 'getIntegrationByName', () => undefined)
149
149
  .stub(FileSystem, 'getFileBuffer', () => Promise.resolve(Buffer.from('')))
150
150
  .command(['publish', '--environment', 'staging'])
151
151
  .it('publish the integration using .unito.staging.json - staging', ctx => {
@@ -163,7 +163,7 @@ describe('Publish', () => {
163
163
  (0, test_1.expect)(customPath).to.equal('/my-awesome-config.json');
164
164
  return { name: 'a' };
165
165
  })
166
- .stub(IntegrationsPlatform, 'getIntegrations', () => [])
166
+ .stub(IntegrationsPlatform, 'getIntegrationByName', () => undefined)
167
167
  .stub(FileSystem, 'getFileBuffer', () => Promise.resolve(Buffer.from('')))
168
168
  .command(['publish', '--config-path', '/my-awesome-config.json'])
169
169
  .it('publish the integration using custom configuration', ctx => {
@@ -173,7 +173,7 @@ describe('Publish', () => {
173
173
  .stdout()
174
174
  .stub(GlobalConfiguration, 'read', () => ({ apiKey: 'foo' }))
175
175
  .stub(IntegrationConfiguration, 'getConfiguration', () => ({}))
176
- .stub(IntegrationsPlatform, 'getIntegrations', () => [])
176
+ .stub(IntegrationsPlatform, 'getIntegrationByName', () => undefined)
177
177
  .stub(FileSystem, 'getFileBuffer', () => Promise.resolve(Buffer.from('')))
178
178
  .stub(inquirer_1.default, 'prompt', () => Promise.resolve({ proceed: false }))
179
179
  .command(['publish', '--registry-only'])
@@ -186,7 +186,7 @@ describe('Publish', () => {
186
186
  .stdout()
187
187
  .stub(GlobalConfiguration, 'read', () => ({ apiKey: 'foo' }))
188
188
  .stub(IntegrationConfiguration, 'getConfiguration', () => ({}))
189
- .stub(IntegrationsPlatform, 'getIntegrations', () => [])
189
+ .stub(IntegrationsPlatform, 'getIntegration', () => undefined)
190
190
  .stub(FileSystem, 'getFileBuffer', () => Promise.resolve(Buffer.from('')))
191
191
  .stub(inquirer_1.default, 'prompt', () => Promise.resolve({ proceed: false }))
192
192
  .command(['publish', '--registry-only', '--force'])
@@ -198,7 +198,7 @@ describe('Publish', () => {
198
198
  .stdout()
199
199
  .stub(GlobalConfiguration, 'read', () => ({ apiKey: 'foo' }))
200
200
  .stub(IntegrationConfiguration, 'getConfiguration', () => ({}))
201
- .stub(IntegrationsPlatform, 'getIntegrations', () => [])
201
+ .stub(IntegrationsPlatform, 'getIntegration', () => undefined)
202
202
  .stub(FileSystem, 'getFileBuffer', () => Promise.resolve(Buffer.from('')))
203
203
  .stub(inquirer_1.default, 'prompt', () => Promise.resolve({ proceed: true }))
204
204
  .command(['publish', '--registry-only'])
@@ -210,7 +210,7 @@ describe('Publish', () => {
210
210
  .stdout()
211
211
  .stub(GlobalConfiguration, 'read', () => ({ apiKey: 'foo' }))
212
212
  .stub(IntegrationConfiguration, 'getConfiguration', () => ({ name: 'a' }))
213
- .stub(IntegrationsPlatform, 'getIntegrations', () => [{ name: 'a' }])
213
+ .stub(IntegrationsPlatform, 'getIntegrationByName', () => ({ name: 'a' }))
214
214
  .stub(FileSystem, 'getFileBuffer', () => Promise.resolve(Buffer.from('')))
215
215
  .stub(inquirer_1.default, 'prompt', () => Promise.resolve({ proceed: true }))
216
216
  .command(['publish', '--registry-only'])
@@ -225,7 +225,7 @@ describe('Publish', () => {
225
225
  .stub(IntegrationConfiguration, 'getConfiguration', () => ({
226
226
  authorizations: [{ type: 'custom', development: true }, { type: 'oauth2' }],
227
227
  }))
228
- .stub(IntegrationsPlatform, 'getIntegrations', () => [])
228
+ .stub(IntegrationsPlatform, 'getIntegrationByName', () => undefined)
229
229
  // eslint-disable-next-line
230
230
  // @ts-ignore typing doesn't work here, for some reason.
231
231
  .stub(IntegrationsPlatform, 'createIntegration', (config) => config)
@@ -240,7 +240,7 @@ describe('Publish', () => {
240
240
  .stdout()
241
241
  .stub(GlobalConfiguration, 'read', () => ({ apiKey: 'foo' }))
242
242
  .stub(IntegrationConfiguration, 'getConfiguration', () => ({ name: 'foo' }))
243
- .stub(IntegrationsPlatform, 'getIntegrations', () => [])
243
+ .stub(IntegrationsPlatform, 'getIntegrationByName', () => undefined)
244
244
  .stub(FileSystem, 'getFileBuffer', () => Promise.resolve(Buffer.from('')))
245
245
  .command(['publish', '--preview'])
246
246
  .it('preview the integration', ctx => {
@@ -250,7 +250,7 @@ describe('Publish', () => {
250
250
  .stdout()
251
251
  .stub(GlobalConfiguration, 'read', () => ({ apiKey: 'foo' }))
252
252
  .stub(IntegrationConfiguration, 'getConfiguration', () => ({ name: 'foo', ui: { displayName: 'Foo' } }))
253
- .stub(IntegrationsPlatform, 'getIntegrations', () => [])
253
+ .stub(IntegrationsPlatform, 'getIntegrationByName', () => undefined)
254
254
  // eslint-disable-next-line
255
255
  // @ts-ignore typing doesn't work here, for some reason.
256
256
  .stub(IntegrationConfiguration, 'writeConfiguration', configuration => {
@@ -281,7 +281,7 @@ describe('Publish', () => {
281
281
  .stdout()
282
282
  .stub(GlobalConfiguration, 'read', () => ({ apiKey: 'foo' }))
283
283
  .stub(IntegrationConfiguration, 'getConfiguration', () => ({}))
284
- .stub(IntegrationsPlatform, 'getIntegrations', () => [])
284
+ .stub(IntegrationsPlatform, 'getIntegrationByName', () => undefined)
285
285
  .stub(FileSystem, 'getFileBuffer', () => Promise.resolve(Buffer.from('')))
286
286
  .command(['publish', '--live-preview'])
287
287
  .it('live-preview the integration', ctx => {
@@ -291,7 +291,7 @@ describe('Publish', () => {
291
291
  .stdout()
292
292
  .stub(GlobalConfiguration, 'read', () => ({ apiKey: 'foo' }))
293
293
  .stub(IntegrationConfiguration, 'getConfiguration', () => ({ ui: { displayName: 'Foo' } }))
294
- .stub(IntegrationsPlatform, 'getIntegrations', () => [])
294
+ .stub(IntegrationsPlatform, 'getIntegrationByName', () => undefined)
295
295
  // eslint-disable-next-line
296
296
  // @ts-ignore typing doesn't work here, for some reason.
297
297
  .stub(IntegrationsPlatform, 'createIntegration', (config) => config)
@@ -304,7 +304,7 @@ describe('Publish', () => {
304
304
  .stderr()
305
305
  .stub(GlobalConfiguration, 'read', () => ({}))
306
306
  .stub(IntegrationConfiguration, 'getConfiguration', () => ({}))
307
- .stub(IntegrationsPlatform, 'getIntegrations', () => [])
307
+ .stub(IntegrationsPlatform, 'getIntegrationByName', () => undefined)
308
308
  .stub(FileSystem, 'getFileBuffer', () => Promise.resolve(Buffer.from('')))
309
309
  .command(['publish'])
310
310
  .exit(-1)
@@ -333,7 +333,7 @@ describe('Publish', () => {
333
333
  authorizations: [{ name: 'oauth2', oauth2: { clientSecret: 'unito-secret-' } }],
334
334
  secrets: { secret1: 'unito-secret-', invalidSecret: 'Should never happen anyway' },
335
335
  }))
336
- .stub(IntegrationsPlatform, 'getIntegrations', () => [])
336
+ .stub(IntegrationsPlatform, 'getIntegrationByName', () => undefined)
337
337
  .stub(FileSystem, 'getFileBuffer', () => Promise.resolve(Buffer.from('')))
338
338
  .command(['publish', '--preview'])
339
339
  .it('reencrypts the clientSecret on preview', ctx => {
@@ -349,7 +349,7 @@ describe('Publish', () => {
349
349
  authorizations: [{ name: 'oauth2', oauth2: { clientSecret: 'unito-secret-' } }],
350
350
  secrets: { secret1: 'unito-secret-', invalidSecret: 'Should never happen anyway' },
351
351
  }))
352
- .stub(IntegrationsPlatform, 'getIntegrations', () => [])
352
+ .stub(IntegrationsPlatform, 'getIntegrationByName', () => undefined)
353
353
  .stub(FileSystem, 'getFileBuffer', () => Promise.resolve(Buffer.from('')))
354
354
  .command(['publish', '--live-preview'])
355
355
  .it('reencrypts the clientSecret on live-preview', ctx => {
@@ -364,7 +364,7 @@ describe('Publish', () => {
364
364
  name: 'myIntegration',
365
365
  authorizations: [{ name: 'oauth2', oauth2: { clientSecret: 'toto' } }],
366
366
  }))
367
- .stub(IntegrationsPlatform, 'getIntegrations', () => [])
367
+ .stub(IntegrationsPlatform, 'getIntegrationByName', () => undefined)
368
368
  .stub(FileSystem, 'getFileBuffer', () => Promise.resolve(Buffer.from('')))
369
369
  .command(['publish', '--live-preview'])
370
370
  .it('skips reencryption', ctx => {
@@ -381,7 +381,7 @@ describe('Publish', () => {
381
381
  },
382
382
  ],
383
383
  }))
384
- .stub(IntegrationsPlatform, 'getIntegrations', () => [])
384
+ .stub(IntegrationsPlatform, 'getIntegrationByName', () => undefined)
385
385
  .stub(FileSystem, 'getFileBuffer', () => Promise.resolve(Buffer.from('foo')))
386
386
  .stub(inquirer_1.default, 'prompt', () => Promise.resolve({ proceed: true }))
387
387
  .command(['publish', '--registry-only'])
@@ -400,7 +400,7 @@ describe('Publish', () => {
400
400
  },
401
401
  ],
402
402
  }))
403
- .stub(IntegrationsPlatform, 'getIntegrations', () => [])
403
+ .stub(IntegrationsPlatform, 'getIntegrationByName', () => undefined)
404
404
  .stub(FileSystem, 'getFileBuffer', () => Promise.reject())
405
405
  .stub(inquirer_1.default, 'prompt', () => Promise.resolve({ proceed: true }))
406
406
  .command(['publish', '--registry-only'])
@@ -418,7 +418,7 @@ describe('Publish', () => {
418
418
  },
419
419
  ],
420
420
  }))
421
- .stub(IntegrationsPlatform, 'getIntegrations', () => [])
421
+ .stub(IntegrationsPlatform, 'getIntegrationByName', () => undefined)
422
422
  .stub(FileSystem, 'getFileBuffer', () => null)
423
423
  .stub(inquirer_1.default, 'prompt', () => Promise.resolve({ proceed: true }))
424
424
  .command(['publish', '--registry-only'])
@@ -8,7 +8,7 @@ const IntegrationsPlatform = tslib_1.__importStar(require("../../src/services/in
8
8
  const GlobalConfiguration = tslib_1.__importStar(require("../../src/resources/globalConfiguration"));
9
9
  const configuration_1 = require("../../src/resources/configuration");
10
10
  const decryptionHelper = tslib_1.__importStar(require("../../src/resources/decryption"));
11
- describe('decryptionHelper', () => {
11
+ describe('decryption helper', () => {
12
12
  const configuration = {
13
13
  name: 'a',
14
14
  baseUrl: 'b',
@@ -34,35 +34,44 @@ describe('decryptionHelper', () => {
34
34
  afterEach(() => {
35
35
  sinon_1.default.restore();
36
36
  });
37
- describe('decryptCredentials', () => {
38
- it('support unencrypted credentials', async () => {
39
- const decryptedCredentials = await decryptionHelper.decryptTestAccountCredentials(configuration, GlobalConfiguration.Environment.Local, configuration_1.CredentialScope.DEVELOPMENT, '/path/to/config');
40
- (0, chai_1.expect)(decryptedCredentials).to.deep.equal({
37
+ describe('decryptEntries', () => {
38
+ it('support unencrypted entries', async () => {
39
+ const decryptionResult = await decryptionHelper.decryptEntries(configuration.name, GlobalConfiguration.Environment.Local, '/path/to/config', {
40
+ accessToken: 'developmentToken',
41
+ accessToken1: 'developmentToken1',
42
+ });
43
+ (0, chai_1.expect)(decryptionResult.decryptedKeys).to.deep.equal([]);
44
+ (0, chai_1.expect)(decryptionResult.entries).to.deep.equal({
45
+ accessToken1: 'developmentToken1',
41
46
  accessToken: 'developmentToken',
42
47
  });
43
48
  });
44
- it('support encrypted credentials', async () => {
45
- const decryptedCredentials = await decryptionHelper.decryptTestAccountCredentials(configuration, GlobalConfiguration.Environment.Local, configuration_1.CredentialScope.COMPLIANCE, '/path/to/config');
46
- (0, chai_1.expect)(decryptedCredentials).to.deep.equal({
49
+ it('support mixed encrypted and not encrypted entries', async () => {
50
+ const decryptionResult = await decryptionHelper.decryptEntries(configuration.name, GlobalConfiguration.Environment.Local, '/path/to/config', {
51
+ accessToken: 'token',
52
+ encryptedToken: `${configuration_1.ENCRYPTION_PREFIX}decrypt-me`,
53
+ });
54
+ (0, chai_1.expect)(decryptionResult.decryptedKeys).to.deep.equal(['encryptedToken']);
55
+ (0, chai_1.expect)(decryptionResult.entries).to.deep.equal({
47
56
  accessToken: 'token',
48
57
  encryptedToken: `decrypted-me`,
49
58
  });
50
59
  });
51
- });
52
- describe('decryptSecrets', () => {
53
- it('support encrypted secrets', async () => {
54
- const decryptedSecrets = await decryptionHelper.decryptSecrets(configuration, GlobalConfiguration.Environment.Local, '/path/to/config');
55
- (0, chai_1.expect)(decryptedSecrets).to.deep.equal({
60
+ it('support multiple encrypted entries', async () => {
61
+ const decryptionResult = await decryptionHelper.decryptEntries(configuration.name, GlobalConfiguration.Environment.Local, '/path/to/config', {
62
+ encryptedSecret1: `${configuration_1.ENCRYPTION_PREFIX}decrypt-me`,
63
+ encryptedSecret2: `${configuration_1.ENCRYPTION_PREFIX}decrypt-me`,
64
+ });
65
+ (0, chai_1.expect)(decryptionResult.decryptedKeys).to.deep.equal(['encryptedSecret1', 'encryptedSecret2']);
66
+ (0, chai_1.expect)(decryptionResult.entries).to.deep.equal({
56
67
  encryptedSecret1: 'decrypted-me',
57
68
  encryptedSecret2: 'decrypted-me',
58
69
  });
59
70
  });
60
- it('support empty secrets', async () => {
61
- const decryptedSecrets = await decryptionHelper.decryptSecrets({
62
- ...configuration,
63
- secrets: {},
64
- }, GlobalConfiguration.Environment.Local, '/path/to/config');
65
- (0, chai_1.expect)(decryptedSecrets).to.deep.equal({});
71
+ it('support empty object', async () => {
72
+ const decryptionResult = await decryptionHelper.decryptEntries(configuration.name, GlobalConfiguration.Environment.Local, '/path/to/config', {});
73
+ (0, chai_1.expect)(decryptionResult.decryptedKeys).to.deep.equal([]);
74
+ (0, chai_1.expect)(decryptionResult.entries).to.deep.equal({});
66
75
  });
67
76
  });
68
77
  });
@@ -70,10 +70,6 @@ describe('integrations platform', function () {
70
70
  sinon.stub(integrations_platform_client_1.default, 'getIntegrationByName').resolves(integration);
71
71
  (0, chai_1.expect)(await IntegrationsPlatform.getIntegrationByName('foo')).to.deep.equal(integration);
72
72
  });
73
- it('getIntegrationByName - error', async function () {
74
- sinon.stub(integrations_platform_client_1.default, 'getIntegrationByName').throws();
75
- (0, chai_1.expect)(await IntegrationsPlatform.getIntegrationByName('foo')).to.deep.equal(null);
76
- });
77
73
  it('getIntegrations', async function () {
78
74
  sinon.stub(integrations_platform_client_1.default, 'getIntegrations').resolves({ total: 1, data: [integrationSummary] });
79
75
  (0, chai_1.expect)(await IntegrationsPlatform.getIntegrations()).to.deep.equal([integrationSummary]);
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.56.0",
2
+ "version": "0.56.1",
3
3
  "commands": {
4
4
  "activity": {
5
5
  "id": "activity",
@@ -201,7 +201,8 @@
201
201
  },
202
202
  "oauth2": {
203
203
  "id": "oauth2",
204
- "description": "Perform an OAuth2 workflow locally",
204
+ "summary": "Perform an OAuth2 workflow locally",
205
+ "description": "The Oauth2 command allows you to perform an OAuth2 workflow locally, either to create a new test account or to refresh an existing one.",
205
206
  "strict": true,
206
207
  "pluginName": "@unito/integration-cli",
207
208
  "pluginAlias": "@unito/integration-cli",
@@ -211,10 +212,40 @@
211
212
  "<%= config.bin %> <%= command.id %>"
212
213
  ],
213
214
  "flags": {
214
- "accountName": {
215
- "name": "accountName",
215
+ "test-account": {
216
+ "name": "test-account",
217
+ "type": "option",
218
+ "description": "Test account to use.",
219
+ "multiple": false,
220
+ "options": [
221
+ "development",
222
+ "compliance"
223
+ ],
224
+ "default": "development"
225
+ },
226
+ "environment": {
227
+ "name": "environment",
228
+ "type": "option",
229
+ "description": "the environment of the platform",
230
+ "multiple": false,
231
+ "options": [
232
+ "local",
233
+ "staging",
234
+ "production"
235
+ ],
236
+ "default": "production"
237
+ },
238
+ "reauth": {
239
+ "name": "reauth",
240
+ "type": "boolean",
241
+ "description": "triggers a new oauth2 flow to collect credentials and overwrite the current one",
242
+ "allowNo": false
243
+ },
244
+ "config-path": {
245
+ "name": "config-path",
216
246
  "type": "option",
217
- "description": "Name of the account used to perfom oauth2 actions",
247
+ "summary": "relative path to a custom \".unito.json\" file",
248
+ "description": "Use a custom configuration file instead of the default '.unito.json' or other environment specific\n ones.\n\n If you want to force the CLI to use a specific configuration file, you can use this flag to specify the relative\n path from your integration's root folder (with a leading '/').\n\n Usage: <%= config.bin %> <%= command.id %> --config-path=/myCustomConfig.json",
218
249
  "multiple": false
219
250
  }
220
251
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unito/integration-cli",
3
- "version": "0.56.0",
3
+ "version": "0.56.1",
4
4
  "description": "Integration CLI",
5
5
  "bin": {
6
6
  "integration-cli": "./bin/run"
@@ -25,7 +25,7 @@
25
25
  "compile:watch": "tsc --build -w",
26
26
  "test": "mocha $(find test -name '*.test.ts') $(find integrationGenerator/test -name '*.test.ts')",
27
27
  "test:debug": "mocha test/**/*.test.ts integrationGenerator/test/**/*.test.ts --inspect-brk",
28
- "ci:test": "nyc npm run test"
28
+ "ci:test": "c8 npm run test"
29
29
  },
30
30
  "files": [
31
31
  "/bin",
@@ -67,11 +67,11 @@
67
67
  "@types/node": "18.x",
68
68
  "@types/openurl": "1.x",
69
69
  "@types/tmp": "0.x",
70
+ "c8": "9.x",
70
71
  "chai": "4.x",
71
72
  "chai-as-promised": "7.x",
72
73
  "json-schema-to-typescript": "13.x",
73
74
  "mocha": "10.x",
74
- "nyc": "15.x",
75
75
  "oclif": "3.x",
76
76
  "shx": "0.x",
77
77
  "sinon": "16.x",