@unito/integration-cli 0.58.0 → 0.58.2

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 (35) hide show
  1. package/dist/boilerplate/.dockerignore +1 -1
  2. package/dist/boilerplate/Dockerfile +1 -1
  3. package/dist/boilerplate/eslint.config.js +7 -0
  4. package/dist/boilerplate/package.json +8 -15
  5. package/dist/boilerplate/src/handlers/me.ts +10 -0
  6. package/dist/boilerplate/src/handlers/root.ts +8 -0
  7. package/dist/boilerplate/src/index.ts +7 -88
  8. package/dist/boilerplate/tsconfig.json +5 -13
  9. package/dist/src/commands/oauth2.js +14 -13
  10. package/dist/src/resources/configuration.d.ts +1 -1
  11. package/dist/src/resources/configuration.js +4 -4
  12. package/dist/src/resources/{oauth2Helper.d.ts → oauth2.d.ts} +2 -2
  13. package/dist/src/resources/{oauth2Helper.js → oauth2.js} +4 -6
  14. package/dist/src/resources/template.d.ts +3 -0
  15. package/dist/src/resources/template.js +40 -0
  16. package/dist/src/services/{oauth2Helper.d.ts → oauth2.d.ts} +4 -3
  17. package/dist/src/services/{oauth2Helper.js → oauth2.js} +54 -34
  18. package/dist/test/commands/oauth2.test.js +19 -2
  19. package/dist/test/resources/{oauth2Helper.test.js → oauth2.test.js} +8 -8
  20. package/dist/test/resources/template.test.js +40 -0
  21. package/dist/test/services/oauth2.test.d.ts +1 -0
  22. package/dist/test/{oauth2Helper/oauth2Helper.test.js → services/oauth2.test.js} +33 -8
  23. package/oclif.manifest.json +1 -1
  24. package/package.json +1 -1
  25. package/dist/boilerplate/.eslintrc.js +0 -74
  26. package/dist/boilerplate/src/logger.ts +0 -55
  27. package/dist/boilerplate/src/middlewares/additionalLoggingContext.ts +0 -22
  28. package/dist/boilerplate/src/middlewares/correlationId.ts +0 -13
  29. package/dist/boilerplate/src/middlewares/credentials.ts +0 -38
  30. package/dist/boilerplate/src/request.ts +0 -59
  31. package/dist/boilerplate/src/routes/index.ts +0 -11
  32. package/dist/boilerplate/src/routes/me.ts +0 -15
  33. package/dist/boilerplate/src/routes/root.ts +0 -12
  34. /package/dist/test/{oauth2Helper/oauth2Helper.test.d.ts → resources/oauth2.test.d.ts} +0 -0
  35. /package/dist/test/resources/{oauth2Helper.test.d.ts → template.test.d.ts} +0 -0
@@ -1,3 +1,3 @@
1
1
  node_modules
2
2
  dist
3
- .eslintrc.js
3
+ eslint.config.js
@@ -35,4 +35,4 @@ RUN --mount=type=secret,id=npmrc,target=.npmrc npm ci --omit=dev
35
35
  RUN npm install --omit=dev
36
36
 
37
37
  ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
38
- CMD ["node", "./src/index.js"]
38
+ CMD ["node", "./index.js"]
@@ -0,0 +1,7 @@
1
+ import eslint from '@eslint/js';
2
+ import tseslint from 'typescript-eslint';
3
+
4
+ export default tseslint.config(
5
+ eslint.configs.recommended,
6
+ ...tseslint.configs.recommended,
7
+ );
@@ -1,13 +1,15 @@
1
1
  {
2
2
  "name": "integration-boilerplate",
3
+ "type": "module",
3
4
  "version": "0.0.1",
4
5
  "description": "Integration Boilerplate",
6
+ "license": "LicenseRef-LICENSE",
5
7
  "scripts": {
6
8
  "compile": "tsc",
7
- "dev": "nodemon --watch \"src/**\" --ext ts --exec \"node --inspect --no-lazy -r ts-node/register/transpile-only src/index.ts\"",
8
- "lint": "eslint --resolve-plugins-relative-to . --fix src --ext .ts && prettier --write src",
9
+ "dev": "tsx watch --no-warnings src/index.ts",
10
+ "lint": "eslint --fix src && prettier --write src",
9
11
  "ci:audit": "check-audit",
10
- "ci:eslint": "eslint --resolve-plugins-relative-to . src --ext .ts",
12
+ "ci:eslint": "eslint src",
11
13
  "ci:lint": "npm run ci:prettier && npm run ci:eslint || (echo \"Please run eslint and/or prettier and commit the changes\" && exit 1)",
12
14
  "ci:prettier": "prettier --cache --ignore-unknown --check src",
13
15
  "ci:test": "NODE_ENV=test mocha"
@@ -19,25 +21,16 @@
19
21
  "engines": {
20
22
  "node": ">=20.0.0"
21
23
  },
22
- "license": "LicenseRef-LICENSE",
23
24
  "dependencies": {
24
- "@unito/integration-api": "^0.x",
25
- "express": "^5.0.0-beta.1",
26
- "uuid": "9.x"
25
+ "@unito/integration-sdk": "^0.x"
27
26
  },
28
27
  "devDependencies": {
29
- "@types/express": "4.x",
30
- "@types/mocha": "10.x",
31
28
  "@types/node": "20.x",
32
- "@types/uuid": "9.x",
33
- "@typescript-eslint/eslint-plugin": "5.x",
34
- "@typescript-eslint/parser": "5.x",
29
+ "typescript-eslint": "7.x",
35
30
  "eslint": "8.x",
36
- "mocha": "10.x",
37
- "nodemon": "2.x",
38
31
  "npm-audit-resolver": "^3.0.0-RC.0",
39
32
  "prettier": "2.x",
40
- "ts-node": "10.x",
33
+ "tsx": "4.x",
41
34
  "typescript": "5.x"
42
35
  }
43
36
  }
@@ -0,0 +1,10 @@
1
+ import { GetCredentialAccountHandler } from '@unito/integration-sdk';
2
+
3
+ export const getCredentialAccount: GetCredentialAccountHandler = async () => {
4
+ return {
5
+ id: 'me',
6
+ displayName: 'Me',
7
+ emails: [],
8
+ partition: undefined,
9
+ };
10
+ };
@@ -0,0 +1,8 @@
1
+ import { GetItemHandler } from '@unito/integration-sdk';
2
+
3
+ export const getItem: GetItemHandler = async () => {
4
+ return {
5
+ fields: {},
6
+ relations: [],
7
+ };
8
+ };
@@ -1,92 +1,11 @@
1
- import express from 'express';
2
- import { Error as APIError } from '@unito/integration-api';
1
+ import { Integration } from '@unito/integration-sdk';
3
2
 
4
- import indexRouter from './routes/index';
5
- import { extractCredentials } from './middlewares/credentials';
6
- import { extractCorrelationId } from './middlewares/correlationId';
7
- import { extractAdditionalLoggingContext } from './middlewares/additionalLoggingContext';
8
- import { logger } from './logger';
3
+ import * as meHandler from './handlers/me.js';
4
+ import * as rootHandler from './handlers/root.js';
9
5
 
10
- // Express Server initialization
11
- const app: express.Application = express();
6
+ const integration = new Integration();
12
7
 
13
- // Parse query strings with https://github.com/ljharb/qs.
14
- app.set('query parser', 'extended');
8
+ integration.addHandler('/', rootHandler);
9
+ integration.addHandler('/me', meHandler);
15
10
 
16
- app.use(express.json());
17
-
18
- // Must be one of the first handlers (to catch all the errors).
19
- app.use((req: express.Request, res: express.Response, next: express.NextFunction) => {
20
- if (req.originalUrl !== '/health') {
21
- res.on('finish', function () {
22
- const loggerLevel = res.statusCode >= 500 ? 'error' : 'info';
23
-
24
- // eslint-disable-next-line
25
- logger[loggerLevel](`${req.method} ${req.originalUrl} ${res.statusCode}`);
26
- });
27
- }
28
-
29
- next();
30
- });
31
-
32
- // Extract and validate the credentials.
33
- app.use(extractCredentials);
34
-
35
- // Extract the correlation id.
36
- app.use(extractCorrelationId);
37
-
38
- // Load the routes.
39
- app.use('/', indexRouter);
40
-
41
- // Extract the additional logging context.
42
- app.use(extractAdditionalLoggingContext);
43
-
44
- // Must be the (last - 1) handler.
45
- app.use((err: Error, _req: express.Request, res: express.Response, next: express.NextFunction) => {
46
- if (res.headersSent) {
47
- return next(err);
48
- }
49
-
50
- const originalError: APIError = {
51
- code: err.name,
52
- message: err.message,
53
- };
54
-
55
- res.status(500).json({
56
- code: '500',
57
- message: 'Oops! Something went wrong',
58
- originalError: originalError,
59
- } as APIError);
60
- });
61
-
62
- // Must be the last handler.
63
- app.use((req: express.Request, res: express.Response, _next: express.NextFunction) => {
64
- const error: APIError = {
65
- code: '404',
66
- message: `Path ${req.path} not found.`,
67
- };
68
-
69
- res.status(404).json(error);
70
- });
71
-
72
- // eslint-disable-next-line
73
- const instance = app.listen(process.env.PORT || 9200, () => console.log(`Server started on port ${process.env.PORT || 9200}.`));
74
-
75
- // Trap exit signals.
76
- ['SIGTERM', 'SIGINT', 'SIGUSR2'].forEach(signalType => {
77
- process.once(signalType, async () => {
78
- // eslint-disable-next-line
79
- console.log(`Received termination signal ${signalType}. Exiting.`);
80
-
81
- try {
82
- if (instance) {
83
- instance.close();
84
- }
85
- } catch (e) {
86
- // eslint-disable-next-line
87
- console.error('Failed to gracefully exit', e);
88
- }
89
-
90
- process.exit();
91
- });
92
- });
11
+ integration.start();
@@ -1,10 +1,5 @@
1
1
  {
2
- "ts-node": {
3
- "logError": true
4
- },
5
2
  "compilerOptions": {
6
- "allowJs": true,
7
- "allowSyntheticDefaultImports": true,
8
3
  "baseUrl": ".",
9
4
  "declaration": true,
10
5
  "declarationMap": true,
@@ -15,23 +10,20 @@
15
10
  "incremental": true,
16
11
  "isolatedModules": false,
17
12
  "lib": ["dom", "ES2022"],
18
- "module": "commonjs",
19
- "moduleResolution": "node",
13
+ "module": "NodeNext",
20
14
  "noImplicitAny": true,
21
15
  "noFallthroughCasesInSwitch": true,
22
16
  "noUnusedLocals": true,
23
17
  "outDir": "dist",
24
18
  "pretty": true,
25
- "resolveJsonModule": true,
26
- "rootDir": ".",
19
+ "moduleResolution": "NodeNext",
20
+ "rootDir": "./src",
27
21
  "skipLibCheck": true,
28
22
  "sourceMap": true,
29
23
  "strict": true,
30
24
  "strictFunctionTypes": true,
31
25
  "strictNullChecks": true,
32
26
  "strictPropertyInitialization": false,
33
- "target": "ES2022"
34
- },
35
- "include": ["src/**/*"],
36
- "exclude": ["node_modules"]
27
+ "target": "esnext"
28
+ }
37
29
  }
@@ -4,7 +4,7 @@ const tslib_1 = require("tslib");
4
4
  const core_1 = require("@oclif/core");
5
5
  const chalk_1 = tslib_1.__importDefault(require("chalk"));
6
6
  const configuration_1 = require("../resources/configuration");
7
- const Oauth2Helper = tslib_1.__importStar(require("../resources/oauth2Helper"));
7
+ const Oauth2Resource = tslib_1.__importStar(require("../resources/oauth2"));
8
8
  const IntegrationsPlatform = tslib_1.__importStar(require("../services/integrationsPlatform"));
9
9
  const configurationTypes_1 = require("../configurationTypes");
10
10
  const errors_1 = require("../errors");
@@ -56,9 +56,9 @@ class Oauth2 extends core_1.Command {
56
56
  }
57
57
  async run() {
58
58
  const { flags } = await this.parse(Oauth2);
59
- const testAccount = flags['test-account'] ?? 'development';
60
59
  const environment = flags.environment ?? globalConfiguration_1.Environment.Production;
61
- const configuration = await (0, configuration_1.getConfiguration)(environment, flags['config-path']);
60
+ const customConfigPath = flags['config-path'];
61
+ const configuration = await (0, configuration_1.getConfiguration)(environment, customConfigPath);
62
62
  // First check for development oauth2 authorization scheme
63
63
  let oauth2 = configuration.authorizations?.find(authorization => authorization.method === configurationTypes_1.Method.OAUTH2 && authorization.development)?.oauth2;
64
64
  // Try for any oauth2 authorization scheme if there was no development one
@@ -70,22 +70,23 @@ class Oauth2 extends core_1.Command {
70
70
  }
71
71
  // Decrypt any encrypted oauth2 fields (mainly clientSecret)
72
72
  oauth2 = (await (0, decryption_1.decryptEntries)(configuration.name, environment, this.config.configDir, oauth2)).entries;
73
- const account = testAccount === 'development' ? configuration.testAccounts?.development : configuration.testAccounts?.compliance;
73
+ const testAccount = flags['test-account'];
74
+ const testAccountCredentials = configuration.testAccounts?.[testAccount];
74
75
  let credentials;
75
- if (!account || Object.entries(account).length === 0 || flags.reauth) {
76
- core_1.ux.action.start(`Starting Oauth2 flow for account: ${testAccount}`, undefined, { stdout: true });
77
- credentials = await Oauth2Helper.performOAuth2Flow(oauth2, environment);
78
- core_1.ux.action.stop();
76
+ if (!testAccountCredentials?.accessToken || flags.reauth) {
77
+ core_1.ux.log(`Starting Oauth2 flow for test account: ${testAccount}`);
78
+ credentials = await Oauth2Resource.performOAuth2Flow(oauth2, environment, testAccountCredentials);
79
+ core_1.ux.log(`Oauth2 flow... Done`);
79
80
  }
80
81
  else {
81
- if (!account?.refreshToken) {
82
+ if (!testAccountCredentials?.refreshToken) {
82
83
  core_1.ux.log(chalk_1.default.redBright('No refresh token found, nothing to do.'));
83
84
  return;
84
85
  }
85
- const decryptionResult = await (0, decryption_1.decryptEntries)(configuration.name, environment, this.config.configDir, account);
86
+ const decryptionResult = await (0, decryption_1.decryptEntries)(configuration.name, environment, this.config.configDir, testAccountCredentials);
86
87
  const refreshToken = decryptionResult.entries?.refreshToken;
87
- core_1.ux.action.start(`Refreshing test account ${testAccount}`, undefined, { stdout: true });
88
- credentials = await Oauth2Helper.updateToken(oauth2, refreshToken);
88
+ core_1.ux.action.start(`Refreshing test account ${testAccountCredentials}`, undefined, { stdout: true });
89
+ credentials = await Oauth2Resource.updateToken(oauth2, refreshToken);
89
90
  // If provider response doesn't contain a new refresh token, use the one we already have
90
91
  if (!credentials.refreshToken) {
91
92
  credentials.refreshToken = refreshToken;
@@ -99,7 +100,7 @@ class Oauth2 extends core_1.Command {
99
100
  core_1.ux.action.stop();
100
101
  }
101
102
  core_1.ux.action.start(`Saving credentials for account: ${testAccount}`, undefined, { stdout: true });
102
- await (0, configuration_1.writeTestAccount)(configuration, credentials, testAccount);
103
+ await (0, configuration_1.writeTestAccount)(configuration, environment, customConfigPath, credentials, testAccount);
103
104
  core_1.ux.action.stop();
104
105
  return;
105
106
  }
@@ -24,7 +24,7 @@ export declare function getConfiguration(environment: Environment, customConfigP
24
24
  * Write the configuration to the default configuration file.
25
25
  */
26
26
  export declare function writeConfiguration(configuration: Configuration, environment?: Environment, customConfigPath?: string): Promise<void>;
27
- export declare function writeTestAccount(configuration: Configuration, account: {
27
+ export declare function writeTestAccount(configuration: Configuration, environment: Environment | undefined, customConfigPath: string | undefined, account: {
28
28
  accessToken: string;
29
29
  refreshToken?: string;
30
30
  }, accountName: string): Promise<void>;
@@ -95,14 +95,14 @@ async function writeConfiguration(configuration, environment = globalConfigurati
95
95
  await fs.promises.writeFile(configurationPath, JSON.stringify(configuration, null, 2));
96
96
  }
97
97
  exports.writeConfiguration = writeConfiguration;
98
- async function writeTestAccount(configuration, account, accountName) {
98
+ async function writeTestAccount(configuration, environment = globalConfiguration_1.Environment.Production, customConfigPath, account, accountName) {
99
99
  // istanbul ignore next
100
100
  if (accountName !== 'development' && accountName !== 'compliance') {
101
101
  throw new Error('Invalid account name');
102
102
  }
103
- configuration.testAccounts = configuration.testAccounts ?? {};
104
- configuration.testAccounts[accountName] = account;
105
- await writeConfiguration(configuration);
103
+ configuration.testAccounts ??= {};
104
+ configuration.testAccounts[accountName] = { ...configuration.testAccounts[accountName], ...account };
105
+ await writeConfiguration(configuration, environment, customConfigPath);
106
106
  }
107
107
  exports.writeTestAccount = writeTestAccount;
108
108
  async function validateConfiguration(configuration) {
@@ -1,4 +1,4 @@
1
- import { Oauth2Response, Oauth2Payload } from '../services/oauth2Helper';
1
+ import { Oauth2Response, Oauth2Payload } from '../services/oauth2';
2
2
  import { Environment } from './globalConfiguration';
3
- export declare function performOAuth2Flow(applicationCredentials: Oauth2Payload, environment?: Environment): Promise<Oauth2Response>;
3
+ export declare function performOAuth2Flow(applicationCredentials: Oauth2Payload, environment?: Environment, credentialPayload?: Record<string, unknown>): Promise<Oauth2Response>;
4
4
  export declare function updateToken(applicationCredentials: Oauth2Payload, refreshToken: string): Promise<Oauth2Response>;
@@ -2,11 +2,11 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.updateToken = exports.performOAuth2Flow = void 0;
4
4
  const tslib_1 = require("tslib");
5
- const oauth2Helper_1 = tslib_1.__importDefault(require("../services/oauth2Helper"));
5
+ const oauth2_1 = tslib_1.__importDefault(require("../services/oauth2"));
6
6
  const errors_1 = require("../errors");
7
7
  const globalConfiguration_1 = require("./globalConfiguration");
8
- async function performOAuth2Flow(applicationCredentials, environment = globalConfiguration_1.Environment.Production) {
9
- const oauthHelper = new oauth2Helper_1.default(applicationCredentials, environment);
8
+ async function performOAuth2Flow(applicationCredentials, environment = globalConfiguration_1.Environment.Production, credentialPayload) {
9
+ const oauthHelper = new oauth2_1.default(applicationCredentials, environment, credentialPayload);
10
10
  const serverUrl = await oauthHelper.startServer();
11
11
  const healthCheck = await fetch(`${serverUrl}/health`);
12
12
  if (healthCheck.status !== 200) {
@@ -21,10 +21,8 @@ async function performOAuth2Flow(applicationCredentials, environment = globalCon
21
21
  return oauth2Response;
22
22
  }
23
23
  exports.performOAuth2Flow = performOAuth2Flow;
24
- /* istanbul ignore next */
25
- // No point to test this function as it is just a wrapper around OAuth2Helper
26
24
  async function updateToken(applicationCredentials, refreshToken) {
27
- const oauthHelper = new oauth2Helper_1.default(applicationCredentials);
25
+ const oauthHelper = new oauth2_1.default(applicationCredentials);
28
26
  return oauthHelper.updateToken(refreshToken);
29
27
  }
30
28
  exports.updateToken = updateToken;
@@ -0,0 +1,3 @@
1
+ export declare function parseTemplate(template: string): {
2
+ expand: (context: Record<string, unknown>) => string;
3
+ };
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseTemplate = void 0;
4
+ const operators = Object.freeze(['+']);
5
+ function encode(value) {
6
+ return value
7
+ ? value
8
+ .toString()
9
+ .split(/(%[0-9A-Fa-f]{2})/g)
10
+ .map(function (part) {
11
+ if (!/%[0-9A-Fa-f]/.test(part)) {
12
+ part = encodeURI(part).replace(/%5B/g, '[').replace(/%5D/g, ']');
13
+ }
14
+ return part;
15
+ })
16
+ .join('')
17
+ : '';
18
+ }
19
+ function parseTemplate(template) {
20
+ return {
21
+ expand: function (context) {
22
+ return template.replace(/\{([^{}]+)\}|([^{}]+)/g, function (_, expression, literal) {
23
+ if (expression) {
24
+ if (operators.indexOf(expression.charAt(0)) !== -1) {
25
+ expression = expression.substr(1);
26
+ return encode(context[expression]);
27
+ }
28
+ else {
29
+ // For now, if no '+' operator is present, do not expand
30
+ return `{${expression}}`;
31
+ }
32
+ }
33
+ else {
34
+ return encode(literal);
35
+ }
36
+ });
37
+ },
38
+ };
39
+ }
40
+ exports.parseTemplate = parseTemplate;
@@ -25,7 +25,7 @@ export interface TokenPayload extends Oauth2Credentials {
25
25
  code: string;
26
26
  }
27
27
  export type Oauth2Payload = Oauth2;
28
- declare class OAuth2Helper {
28
+ declare class OAuth2Service {
29
29
  private environment;
30
30
  private server;
31
31
  private clientId;
@@ -39,6 +39,7 @@ declare class OAuth2Helper {
39
39
  private serverUrl;
40
40
  private tokenRequestParameters;
41
41
  private refreshRequestParameters;
42
+ private credentialPayload;
42
43
  /**
43
44
  * Constructs an instance of OAuthHelper.
44
45
  * @param clientId The client ID for your OAuth application.
@@ -47,7 +48,7 @@ declare class OAuth2Helper {
47
48
  * @param scopes The scopes required for the OAuth authorization.
48
49
  * @param providerTokenUrl The URL for the token endpoint of the provider.
49
50
  */
50
- constructor(authorizationInfo: Oauth2Payload, environment?: Environment);
51
+ constructor(authorizationInfo: Oauth2Payload, environment?: Environment, credentialPayload?: Record<string, unknown>);
51
52
  /**
52
53
  * Initiate the authorization flow and redirects the user to the provider's authorization page.
53
54
  */
@@ -75,4 +76,4 @@ declare class OAuth2Helper {
75
76
  */
76
77
  stopServer(): Promise<void>;
77
78
  }
78
- export default OAuth2Helper;
79
+ export default OAuth2Service;
@@ -8,13 +8,15 @@ const openUrl = tslib_1.__importStar(require("openurl"));
8
8
  const ngrok_1 = tslib_1.__importDefault(require("ngrok"));
9
9
  const IntegrationsPlatformClient = tslib_1.__importStar(require("./integrationsPlatform"));
10
10
  const configurationTypes_1 = require("../configurationTypes");
11
+ const template_1 = require("../resources/template");
11
12
  const errors_1 = require("../errors");
12
13
  const globalConfiguration_1 = require("../resources/globalConfiguration");
13
14
  // It allows to stub openUrl library in the test
14
15
  exports.open = openUrl;
15
16
  exports.HTML_ERROR_MSG = `<!doctype html><head><title>Unito</title></head><body style="text-align:center"> An unknown error occured </body>`;
16
17
  exports.HTML_SUCCESS_MSG = `<!doctype html><head><title>Unito</title></head><body style="text-align:center"> Redirected to the CLI successfully </body>`;
17
- class OAuth2Helper {
18
+ const AUTHORIZATION_RESPONSE_QUERY_PARAMS = ['code', 'state', 'error', 'error_description', 'error_uri'];
19
+ class OAuth2Service {
18
20
  environment;
19
21
  server = null;
20
22
  clientId;
@@ -28,6 +30,7 @@ class OAuth2Helper {
28
30
  serverUrl = '';
29
31
  tokenRequestParameters;
30
32
  refreshRequestParameters;
33
+ credentialPayload;
31
34
  /**
32
35
  * Constructs an instance of OAuthHelper.
33
36
  * @param clientId The client ID for your OAuth application.
@@ -36,7 +39,7 @@ class OAuth2Helper {
36
39
  * @param scopes The scopes required for the OAuth authorization.
37
40
  * @param providerTokenUrl The URL for the token endpoint of the provider.
38
41
  */
39
- constructor(authorizationInfo, environment = globalConfiguration_1.Environment.Production) {
42
+ constructor(authorizationInfo, environment = globalConfiguration_1.Environment.Production, credentialPayload) {
40
43
  const { clientId, clientSecret, authorizationUrl, scopes, tokenUrl, grantType, requestContentType, refreshRequestParameters, tokenRequestParameters, } = authorizationInfo;
41
44
  this.startServer = this.startServer.bind(this);
42
45
  this.stopServer = this.stopServer.bind(this);
@@ -51,6 +54,7 @@ class OAuth2Helper {
51
54
  this.tokenRequestParameters = tokenRequestParameters;
52
55
  this.refreshRequestParameters = refreshRequestParameters;
53
56
  this.environment = environment;
57
+ this.credentialPayload = credentialPayload;
54
58
  if (!Object.values(configurationTypes_1.RequestContentType).includes(this.requestContentType)) {
55
59
  throw new errors_1.UnsupportedContentTypeError(`Request content type not supported: ${this.requestContentType}`);
56
60
  }
@@ -62,22 +66,27 @@ class OAuth2Helper {
62
66
  if (!this.providerAuthorizationUrl) {
63
67
  throw new errors_1.InvalidRequestContentTypeError('authorizationUrl must be defined in .unito.json');
64
68
  }
65
- const authUrl = new URL(this.providerAuthorizationUrl);
66
- const params = new URLSearchParams(authUrl.search);
69
+ const authorizationParams = new URLSearchParams();
67
70
  if (this.clientId) {
68
- params.set('client_id', this.clientId);
71
+ authorizationParams.set('client_id', this.clientId);
72
+ }
73
+ if (this.scopes) {
74
+ authorizationParams.set('scope', this.scopes.join(' '));
69
75
  }
70
- params.set('redirect_uri', `${IntegrationsPlatformClient.Servers[this.environment]}/credentials/new/oauth2/callback-cli`);
71
76
  const state = Buffer.from(JSON.stringify({
72
77
  cliCallbackUrl: `${this.serverUrl}/oauth2/callback`,
73
78
  })).toString('base64');
74
- params.set('state', state);
75
- if (this.scopes) {
76
- params.set('scope', this.scopes.join(' '));
77
- }
78
- params.set('response_type', 'code');
79
- authUrl.search = params.toString();
80
- exports.open.open(authUrl.toString());
79
+ authorizationParams.set('state', state);
80
+ authorizationParams.set('response_type', 'code');
81
+ authorizationParams.set('redirect_uri', `${IntegrationsPlatformClient.Servers[this.environment]}/credentials/new/oauth2/callback-cli`);
82
+ const delimiter = this.providerAuthorizationUrl.includes('?') ? '&' : '?';
83
+ const authorizationUrlTemplate = `${this.providerAuthorizationUrl}${delimiter}${authorizationParams.toString()}`;
84
+ const authorizationUrl = (0, template_1.parseTemplate)(authorizationUrlTemplate).expand({
85
+ ...(this.credentialPayload ?? {}),
86
+ ...Object.fromEntries(authorizationParams.entries()),
87
+ });
88
+ console.log(' Calling the following authorization URL: \n ', authorizationUrl);
89
+ exports.open.open(authorizationUrl);
81
90
  }
82
91
  /**
83
92
  * Handles the callback request from the provider and stores the authorization code.
@@ -93,28 +102,39 @@ class OAuth2Helper {
93
102
  res.setHeader('Content-Type', 'text/html').send(exports.HTML_ERROR_MSG);
94
103
  return;
95
104
  }
96
- const code = req.query.code;
97
- const bodyData = {
98
- code,
105
+ // We keep all the non-standard query parameters of the authorization response
106
+ // so they can be used later on for the access token request (see tokenRequestParameters).
107
+ const authorizationResponseVariables = {};
108
+ for (const [key, value] of Object.entries(req.query)) {
109
+ if (!AUTHORIZATION_RESPONSE_QUERY_PARAMS.includes(key)) {
110
+ authorizationResponseVariables[`authorizationResponse.${key}`] = value?.toString() ?? '';
111
+ }
112
+ }
113
+ const templateVariables = { ...(this.credentialPayload ?? {}), ...authorizationResponseVariables };
114
+ const tokenRequestPayload = {
115
+ ...Object.fromEntries(Object.entries(this.tokenRequestParameters?.body ?? {}).map(([key, value]) => [
116
+ key,
117
+ typeof value === 'string' ? decodeURIComponent((0, template_1.parseTemplate)(value).expand(templateVariables)) : value,
118
+ ])),
99
119
  grant_type: this.grantType,
120
+ code: req.query.code,
100
121
  redirect_uri: `${IntegrationsPlatformClient.Servers[this.environment]}/credentials/new/oauth2/callback-cli`,
122
+ ...(this.clientId && { client_id: this.clientId }),
123
+ ...(this.clientSecret && { client_secret: this.clientSecret }),
101
124
  };
102
- if (this.clientId) {
103
- bodyData.client_id = this.clientId;
104
- }
105
- if (this.clientSecret) {
106
- bodyData.client_secret = this.clientSecret;
107
- }
108
- const fetchOptions = {
109
- headers: {
110
- 'Content-type': this.requestContentType,
111
- ...(this.tokenRequestParameters?.header ?? {}),
112
- },
113
- body: this.encodeBody(bodyData, this.requestContentType),
114
- method: 'POST',
125
+ const tokenRequestHeaders = {
126
+ 'Content-Type': this.requestContentType,
127
+ ...Object.fromEntries(Object.entries(this.tokenRequestParameters?.header ?? {}).map(([key, value]) => [
128
+ key,
129
+ decodeURIComponent((0, template_1.parseTemplate)(String(value)).expand(templateVariables)),
130
+ ])),
115
131
  };
116
132
  try {
117
- const fetchResult = await fetch(this.tokenUrl, fetchOptions);
133
+ const fetchResult = await fetch((0, template_1.parseTemplate)(this.tokenUrl).expand(templateVariables), {
134
+ headers: tokenRequestHeaders,
135
+ body: this.encodeBody(tokenRequestPayload, this.requestContentType),
136
+ method: 'POST',
137
+ });
118
138
  if (fetchResult.status !== 200) {
119
139
  res.setHeader('Content-Type', 'text/html');
120
140
  res.send(exports.HTML_ERROR_MSG);
@@ -176,7 +196,7 @@ class OAuth2Helper {
176
196
  }
177
197
  const fetchOptions = {
178
198
  headers: {
179
- 'Content-type': this.requestContentType,
199
+ 'Content-Type': this.requestContentType,
180
200
  ...(this.refreshRequestParameters?.header ?? {}),
181
201
  },
182
202
  body: this.encodeBody(bodyData, this.requestContentType),
@@ -214,7 +234,7 @@ class OAuth2Helper {
214
234
  });
215
235
  app.get('/oauth2/callback', this.handleCallback);
216
236
  this.server = app.listen(PORT, () => {
217
- console.log(`Listening at port ${PORT}`);
237
+ console.log(` Listening at port ${PORT}`);
218
238
  });
219
239
  return this.serverUrl;
220
240
  }
@@ -226,9 +246,9 @@ class OAuth2Helper {
226
246
  if (this.server) {
227
247
  await ngrok_1.default.kill();
228
248
  this.server.close(() => {
229
- console.log('Server has stopped');
249
+ console.log(' Server has stopped');
230
250
  });
231
251
  }
232
252
  }
233
253
  }
234
- exports.default = OAuth2Helper;
254
+ exports.default = OAuth2Service;
@@ -6,7 +6,7 @@ const core_1 = require("@oclif/core");
6
6
  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
- const oauth2Service = tslib_1.__importStar(require("../../src/resources/oauth2Helper"));
9
+ const oauth2Service = tslib_1.__importStar(require("../../src/resources/oauth2"));
10
10
  const IntegrationsPlatform = tslib_1.__importStar(require("../../src/services/integrationsPlatform"));
11
11
  const decryptionResource = tslib_1.__importStar(require("../../src/resources/decryption"));
12
12
  const configurationTypes_1 = require("../../src/configurationTypes");
@@ -87,6 +87,7 @@ describe('oauth2', () => {
87
87
  (0, test_1.expect)(performOAuth2FlowStub.getCall(0).args).to.deep.equal([
88
88
  { ...oauth2Information, clientId: 'devClientID' },
89
89
  'production',
90
+ undefined,
90
91
  ]);
91
92
  (0, test_1.expect)(updateTokenStub.getCalls().length).to.equal(0);
92
93
  (0, test_1.expect)(writeTestAccountStub.getCall(0).args).to.deep.equal([
@@ -102,6 +103,8 @@ describe('oauth2', () => {
102
103
  },
103
104
  ],
104
105
  },
106
+ 'production',
107
+ undefined,
105
108
  credentials,
106
109
  'development',
107
110
  ]);
@@ -112,7 +115,13 @@ describe('oauth2', () => {
112
115
  .it('performs the oauth flow when there is no test accounts and the auth information are setup', () => {
113
116
  (0, test_1.expect)(performOAuth2FlowStub.getCalls().length).to.equal(1);
114
117
  (0, test_1.expect)(updateTokenStub.getCalls().length).to.equal(0);
115
- (0, test_1.expect)(writeTestAccountStub.getCall(0).args).to.deep.equal([baseConfiguration, credentials, 'development']);
118
+ (0, test_1.expect)(writeTestAccountStub.getCall(0).args).to.deep.equal([
119
+ baseConfiguration,
120
+ 'production',
121
+ undefined,
122
+ credentials,
123
+ 'development',
124
+ ]);
116
125
  });
117
126
  test_1.test
118
127
  .stdout()
@@ -133,6 +142,8 @@ describe('oauth2', () => {
133
142
  development: credentials,
134
143
  },
135
144
  },
145
+ 'production',
146
+ undefined,
136
147
  credentials,
137
148
  'development',
138
149
  ]);
@@ -156,6 +167,8 @@ describe('oauth2', () => {
156
167
  development: { something: 'something' },
157
168
  },
158
169
  },
170
+ 'production',
171
+ undefined,
159
172
  credentials,
160
173
  'compliance',
161
174
  ]);
@@ -179,6 +192,8 @@ describe('oauth2', () => {
179
192
  development: credentials,
180
193
  },
181
194
  },
195
+ 'production',
196
+ undefined,
182
197
  credentials,
183
198
  'development',
184
199
  ]);
@@ -242,6 +257,8 @@ describe('oauth2', () => {
242
257
  },
243
258
  },
244
259
  },
260
+ 'production',
261
+ undefined,
245
262
  {
246
263
  accessToken: `${Configuration.ENCRYPTION_PREFIX}encryptedAccessToken`,
247
264
  refreshToken: `${Configuration.ENCRYPTION_PREFIX}encryptedRefreshToken`,
@@ -3,9 +3,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const tslib_1 = require("tslib");
4
4
  const strict_1 = tslib_1.__importDefault(require("node:assert/strict"));
5
5
  const sinon_1 = tslib_1.__importDefault(require("sinon"));
6
- const oauth2Helper_1 = tslib_1.__importDefault(require("../../src/services/oauth2Helper"));
6
+ const oauth2_1 = tslib_1.__importDefault(require("../../src/services/oauth2"));
7
7
  const configurationTypes_1 = require("../../src/configurationTypes");
8
- const oauth2HelperResource = tslib_1.__importStar(require("../../src/resources/oauth2Helper"));
8
+ const oauth2HelperResource = tslib_1.__importStar(require("../../src/resources/oauth2"));
9
9
  const errors_1 = require("../../src/errors");
10
10
  const inquirer_1 = tslib_1.__importDefault(require("inquirer"));
11
11
  describe('OAuth2Helper', () => {
@@ -21,9 +21,9 @@ describe('OAuth2Helper', () => {
21
21
  responseContentType: configurationTypes_1.RequestContentType.JSON,
22
22
  };
23
23
  beforeEach(() => {
24
- sinon_1.default.stub(oauth2Helper_1.default.prototype, 'startServer').resolves('http://localhost:5050');
25
- sinon_1.default.stub(oauth2Helper_1.default.prototype, 'stopServer');
26
- sinon_1.default.stub(oauth2Helper_1.default.prototype, 'authorize');
24
+ sinon_1.default.stub(oauth2_1.default.prototype, 'startServer').resolves('http://localhost:5050');
25
+ sinon_1.default.stub(oauth2_1.default.prototype, 'stopServer');
26
+ sinon_1.default.stub(oauth2_1.default.prototype, 'authorize');
27
27
  sinon_1.default.stub(inquirer_1.default, 'prompt');
28
28
  });
29
29
  afterEach(() => {
@@ -33,21 +33,21 @@ describe('OAuth2Helper', () => {
33
33
  it('should perform the oauth flow', async () => {
34
34
  fetchStub = sinon_1.default.stub().onFirstCall().resolves({ status: 200 }).onSecondCall().resolves({ status: 200 });
35
35
  sinon_1.default.replace(global, 'fetch', fetchStub);
36
- sinon_1.default.stub(oauth2Helper_1.default.prototype, 'callbackIsDone').resolves({ accessToken: 'token' });
36
+ sinon_1.default.stub(oauth2_1.default.prototype, 'callbackIsDone').resolves({ accessToken: 'token' });
37
37
  await oauth2HelperResource.performOAuth2Flow(authorizationInfo);
38
38
  strict_1.default.equal(fetchStub.getCall(0).args.at(0), 'http://localhost:5050/health');
39
39
  });
40
40
  it('raises a FailedToRetrieveAccessTokenError', async () => {
41
41
  fetchStub = sinon_1.default.stub().onFirstCall().resolves({ status: 200 }).onSecondCall().resolves({ status: 200 });
42
42
  sinon_1.default.replace(global, 'fetch', fetchStub);
43
- sinon_1.default.stub(oauth2Helper_1.default.prototype, 'callbackIsDone').resolves({});
43
+ sinon_1.default.stub(oauth2_1.default.prototype, 'callbackIsDone').resolves({});
44
44
  const response = oauth2HelperResource.performOAuth2Flow(authorizationInfo);
45
45
  await strict_1.default.rejects(response, errors_1.FailedToRetrieveAccessTokenError);
46
46
  });
47
47
  it('raises a FailedToRetrieveAccessTokenError when the accessToken is not returned', async () => {
48
48
  fetchStub = sinon_1.default.stub().onFirstCall().resolves({ status: 200 }).onSecondCall().resolves({ status: 200 });
49
49
  sinon_1.default.replace(global, 'fetch', fetchStub);
50
- sinon_1.default.stub(oauth2Helper_1.default.prototype, 'callbackIsDone').resolves({});
50
+ sinon_1.default.stub(oauth2_1.default.prototype, 'callbackIsDone').resolves({});
51
51
  const response = oauth2HelperResource.performOAuth2Flow(authorizationInfo);
52
52
  await strict_1.default.rejects(response, errors_1.FailedToRetrieveAccessTokenError);
53
53
  });
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ const strict_1 = tslib_1.__importDefault(require("node:assert/strict"));
5
+ const node_test_1 = require("node:test");
6
+ const template_1 = require("../../src/resources/template");
7
+ (0, node_test_1.describe)('template', function () {
8
+ const context = {
9
+ var: 'value',
10
+ 'some.value': 'some',
11
+ some_value: 'value',
12
+ 'Some%20Thing': 'Some%20Thing',
13
+ foo: 'bar',
14
+ hello: 'Hello World!',
15
+ bool: 'false',
16
+ toString: 'string',
17
+ chars: 'šö䟜ñꀣ¥‡ÑÒÓÔÕÖרÙÚàáâãäåæçÿü',
18
+ path: '/foo/bar',
19
+ surrogatepairs: '\uD834\uDF06',
20
+ };
21
+ (0, node_test_1.it)('empty string', function () {
22
+ strict_1.default.equal((0, template_1.parseTemplate)('').expand(context), '');
23
+ });
24
+ (0, node_test_1.it)('does not encode non expressions', function () {
25
+ strict_1.default.equal((0, template_1.parseTemplate)('hello/world').expand(context), 'hello/world');
26
+ strict_1.default.equal((0, template_1.parseTemplate)('Hello World!/{foo}').expand(context), 'Hello%20World!/{foo}');
27
+ });
28
+ (0, node_test_1.it)('expand non-ASCII strings', function () {
29
+ strict_1.default.equal((0, template_1.parseTemplate)('{+chars}').expand(context), '%C5%A1%C3%B6%C3%A4%C5%B8%C5%93%C3%B1%C3%AA%E2%82%AC%C2%A3%C2%A5%E2%80%A1%C3%91%C3%92%C3%93%C3%94%C3%95%C3%96%C3%97%C3%98%C3%99%C3%9A%C3%A0%C3%A1%C3%A2%C3%A3%C3%A4%C3%A5%C3%A6%C3%A7%C3%BF%C3%BC');
30
+ });
31
+ (0, node_test_1.it)('reserved expansion of basic strings', function () {
32
+ strict_1.default.equal((0, template_1.parseTemplate)('{+var}').expand(context), 'value');
33
+ strict_1.default.equal((0, template_1.parseTemplate)('{+hello}').expand(context), 'Hello%20World!');
34
+ strict_1.default.equal((0, template_1.parseTemplate)('{+Some%20Thing}').expand(context), 'Some%20Thing');
35
+ });
36
+ (0, node_test_1.it)('preserves paths', function () {
37
+ strict_1.default.equal((0, template_1.parseTemplate)('{+path}/here').expand(context), '/foo/bar/here');
38
+ strict_1.default.equal((0, template_1.parseTemplate)('here?ref={+path}').expand(context), 'here?ref=/foo/bar');
39
+ });
40
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -4,9 +4,10 @@ const tslib_1 = require("tslib");
4
4
  /* eslint-disable @typescript-eslint/no-explicit-any */
5
5
  const strict_1 = tslib_1.__importDefault(require("node:assert/strict"));
6
6
  const sinon_1 = tslib_1.__importDefault(require("sinon"));
7
- const oauth2Helper_1 = tslib_1.__importStar(require("../../src/services/oauth2Helper")), oauth2Namespace = oauth2Helper_1;
7
+ const oauth2_1 = tslib_1.__importStar(require("../../src/services/oauth2")), oauth2Namespace = oauth2_1;
8
8
  const configurationTypes_1 = require("../../src/configurationTypes");
9
9
  const errors_1 = require("../../src/errors");
10
+ const globalConfiguration_1 = require("../../src/resources/globalConfiguration");
10
11
  describe('OAuth2Helper', () => {
11
12
  let oauth2Helper;
12
13
  let openSpy;
@@ -14,14 +15,14 @@ describe('OAuth2Helper', () => {
14
15
  describe('constructor', () => {
15
16
  it('should throw an error if the request content type is not supported', async () => {
16
17
  try {
17
- new oauth2Helper_1.default({ scopes: [], requestContentType: 'random' });
18
+ new oauth2_1.default({ scopes: [], requestContentType: 'random' });
18
19
  }
19
20
  catch (err) {
20
21
  (0, strict_1.default)(err instanceof errors_1.UnsupportedContentTypeError);
21
22
  }
22
23
  });
23
24
  });
24
- describe('endpoints', () => {
25
+ describe('method', () => {
25
26
  beforeEach(() => {
26
27
  const authorizationInfo = {
27
28
  clientId: 'your-client-id',
@@ -38,7 +39,7 @@ describe('OAuth2Helper', () => {
38
39
  },
39
40
  },
40
41
  };
41
- oauth2Helper = new oauth2Helper_1.default(authorizationInfo);
42
+ oauth2Helper = new oauth2_1.default(authorizationInfo, globalConfiguration_1.Environment.Production, { foo: 'fooValue', bar: 'barValue' });
42
43
  fetchStub = sinon_1.default.stub().resolves({ json: sinon_1.default.stub().resolves({}), status: 200 });
43
44
  sinon_1.default.stub(oauth2Helper, 'startServer').resolves('http://localhost:5050');
44
45
  sinon_1.default.stub(oauth2Helper, 'stopServer');
@@ -49,17 +50,23 @@ describe('OAuth2Helper', () => {
49
50
  afterEach(() => {
50
51
  sinon_1.default.restore();
51
52
  });
52
- describe('handleAuthorize', () => {
53
+ describe('authorize', () => {
53
54
  it('should open the authorization URL', async () => {
54
55
  await oauth2Helper.authorize();
55
56
  sinon_1.default.assert.calledOnce(openSpy);
56
- sinon_1.default.assert.calledWith(openSpy, 'https://provider.com/oauth/authorize?client_id=your-client-id&redirect_uri=https%3A%2F%2Fintegrations-platform.unito.io%2Fcredentials%2Fnew%2Foauth2%2Fcallback-cli&state=eyJjbGlDYWxsYmFja1VybCI6Ii9vYXV0aDIvY2FsbGJhY2sifQ%3D%3D&scope=scope1+scope2&response_type=code');
57
+ sinon_1.default.assert.calledWith(openSpy, 'https://provider.com/oauth/authorize?client_id=your-client-id&scope=scope1+scope2&state=eyJjbGlDYWxsYmFja1VybCI6Ii9vYXV0aDIvY2FsbGJhY2sifQ%3D%3D&response_type=code&redirect_uri=https%3A%2F%2Fintegrations-platform.unito.io%2Fcredentials%2Fnew%2Foauth2%2Fcallback-cli');
57
58
  });
58
59
  it('should maintain the pre-existing query parameters in authorization URL', async () => {
59
60
  oauth2Helper['providerAuthorizationUrl'] = 'https://provider.com/oauth/authorize?query1=value1&query2=value2';
60
61
  await oauth2Helper.authorize();
61
62
  sinon_1.default.assert.calledOnce(openSpy);
62
- sinon_1.default.assert.calledWith(openSpy, 'https://provider.com/oauth/authorize?query1=value1&query2=value2&client_id=your-client-id&redirect_uri=https%3A%2F%2Fintegrations-platform.unito.io%2Fcredentials%2Fnew%2Foauth2%2Fcallback-cli&state=eyJjbGlDYWxsYmFja1VybCI6Ii9vYXV0aDIvY2FsbGJhY2sifQ%3D%3D&scope=scope1+scope2&response_type=code');
63
+ sinon_1.default.assert.calledWith(openSpy, 'https://provider.com/oauth/authorize?query1=value1&query2=value2&client_id=your-client-id&scope=scope1+scope2&state=eyJjbGlDYWxsYmFja1VybCI6Ii9vYXV0aDIvY2FsbGJhY2sifQ%3D%3D&response_type=code&redirect_uri=https%3A%2F%2Fintegrations-platform.unito.io%2Fcredentials%2Fnew%2Foauth2%2Fcallback-cli');
64
+ });
65
+ it('should support dynamic params', async () => {
66
+ oauth2Helper['providerAuthorizationUrl'] = 'https://pro-{+foo}-der.com/oauth/authorize?query1={+bar}';
67
+ await oauth2Helper.authorize();
68
+ sinon_1.default.assert.calledOnce(openSpy);
69
+ sinon_1.default.assert.calledWith(openSpy, 'https://pro-fooValue-der.com/oauth/authorize?query1=barValue&client_id=your-client-id&scope=scope1+scope2&state=eyJjbGlDYWxsYmFja1VybCI6Ii9vYXV0aDIvY2FsbGJhY2sifQ%3D%3D&response_type=code&redirect_uri=https%3A%2F%2Fintegrations-platform.unito.io%2Fcredentials%2Fnew%2Foauth2%2Fcallback-cli');
63
70
  });
64
71
  });
65
72
  describe('handleCallback', () => {
@@ -71,7 +78,25 @@ describe('OAuth2Helper', () => {
71
78
  sinon_1.default.assert.calledOnce(fetchStub);
72
79
  sinon_1.default.assert.calledWith(fetchStub, 'https://provider.com/oauth/token', {
73
80
  headers: {
74
- 'Content-type': 'application/x-www-form-urlencoded',
81
+ 'Content-Type': 'application/x-www-form-urlencoded',
82
+ Authorization: 'custom',
83
+ },
84
+ body: sinon_1.default.match.string,
85
+ method: 'POST',
86
+ });
87
+ sinon_1.default.assert.calledOnce(res.send);
88
+ sinon_1.default.assert.calledWith(res.send, oauth2Namespace.HTML_SUCCESS_MSG);
89
+ });
90
+ it('should handle dynamic variables with values from provider response and credential payload', async () => {
91
+ oauth2Helper['tokenUrl'] = 'https://{+foo}.com/oauth/token?specialParam={+authorizationResponse.responseValue}';
92
+ const code = 'test-code';
93
+ const req = { query: { code, responseValue: 'DynamicValue' } };
94
+ const res = { send: sinon_1.default.stub(), setHeader: sinon_1.default.stub() };
95
+ await oauth2Helper['handleCallback'](req, res);
96
+ sinon_1.default.assert.calledOnce(fetchStub);
97
+ sinon_1.default.assert.calledWith(fetchStub, 'https://fooValue.com/oauth/token?specialParam=DynamicValue', {
98
+ headers: {
99
+ 'Content-Type': 'application/x-www-form-urlencoded',
75
100
  Authorization: 'custom',
76
101
  },
77
102
  body: sinon_1.default.match.string,
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.58.0",
2
+ "version": "0.58.2",
3
3
  "commands": {
4
4
  "activity": {
5
5
  "id": "activity",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unito/integration-cli",
3
- "version": "0.58.0",
3
+ "version": "0.58.2",
4
4
  "description": "Integration CLI",
5
5
  "bin": {
6
6
  "integration-cli": "./bin/run"
@@ -1,74 +0,0 @@
1
- module.exports = {
2
- 'env': {
3
- 'browser': true,
4
- 'es6': true,
5
- 'node': true
6
- },
7
- 'parser': '@typescript-eslint/parser',
8
- 'parserOptions': {
9
- 'project': './tsconfig.json',
10
- },
11
- 'plugins': [
12
- '@typescript-eslint'
13
- ],
14
- extends: [
15
- 'eslint:recommended',
16
- 'plugin:@typescript-eslint/recommended',
17
- ],
18
- ignorePatterns: [
19
- 'node_modules',
20
- 'dist',
21
- '.eslintrc.js'
22
- ],
23
- 'rules': {
24
- '@typescript-eslint/no-loss-of-precision': 0,
25
- '@typescript-eslint/no-explicit-any': 0,
26
- '@typescript-eslint/ban-ts-comment': 0,
27
- '@typescript-eslint/ban-ts-ignore': 0,
28
- '@typescript-eslint/explicit-module-boundary-types': 0,
29
- '@typescript-eslint/no-var-requires': 0,
30
- '@typescript-eslint/no-floating-promises': 2,
31
- '@typescript-eslint/no-unused-vars': 0,
32
- // We need null in connectors because in some API, adding null to a field makes the API remove the value to the field
33
- '@typescript-eslint/no-non-null-assertion': 0,
34
- '@typescript-eslint/prefer-namespace-keyword': 0,
35
- '@typescript-eslint/no-namespace': 0,
36
- '@typescript-eslint/no-inferrable-types': 0,
37
- '@typescript-eslint/naming-convention': [
38
- 1,
39
- {
40
- selector: [
41
- 'classProperty',
42
- 'objectLiteralProperty',
43
- 'typeProperty',
44
- 'classMethod',
45
- 'objectLiteralMethod',
46
- 'typeMethod',
47
- 'accessor',
48
- 'enumMember'
49
- ],
50
- format: null,
51
- modifiers: ['requiresQuotes']
52
- }
53
- ],
54
- 'no-whitespace-before-property': 2,
55
- 'no-trailing-spaces': 2,
56
- 'no-extra-boolean-cast': 0,
57
- 'no-inner-declarations': 0,
58
- 'no-useless-escape': 0,
59
- 'no-case-declarations': 0,
60
- 'space-unary-ops': [2,
61
- {
62
- 'words': true,
63
- 'nonwords': false,
64
- },
65
- ],
66
- 'space-before-function-paren': [2, {
67
- 'anonymous': 'always',
68
- 'named': 'never',
69
- 'asyncArrow': 'always'
70
- }],
71
- 'object-curly-spacing': [2, 'always'],
72
- 'no-console': 2,
73
- }
74
- };
@@ -1,55 +0,0 @@
1
- enum LogLevel {
2
- ERROR = 'error',
3
- WARN = 'warn',
4
- INFO = 'info',
5
- LOG = 'log',
6
- DEBUG = 'debug',
7
- }
8
-
9
- export class Logger {
10
- private metadata: { [key: string]: string } = {};
11
-
12
- private send(logLevel: LogLevel, message: string) {
13
- console[logLevel](
14
- // Datadog automatically parses JSON-formatted logs
15
- JSON.stringify({
16
- message: message,
17
- ...this.metadata,
18
- }),
19
- );
20
- }
21
-
22
- public log(message: string) {
23
- this.send(LogLevel.LOG, message);
24
- }
25
-
26
- public error(message: string) {
27
- this.send(LogLevel.ERROR, message);
28
- }
29
-
30
- public warn(message: string) {
31
- this.send(LogLevel.WARN, message);
32
- }
33
-
34
- public info(message: string) {
35
- this.send(LogLevel.INFO, message);
36
- }
37
-
38
- public debug(message: string) {
39
- this.send(LogLevel.DEBUG, message);
40
- }
41
-
42
- public decorate(metadata: { [key: string]: string }) {
43
- this.metadata = metadata;
44
- }
45
-
46
- public setMeta(key: string, value: string) {
47
- this.metadata[key] = value;
48
- }
49
-
50
- public clearMeta() {
51
- this.metadata = {};
52
- }
53
- }
54
-
55
- export const logger = new Logger();
@@ -1,22 +0,0 @@
1
- import express from 'express';
2
- import { logger } from '../logger';
3
-
4
- export const extractAdditionalLoggingContext = (req: express.Request, res: express.Response, next: express.NextFunction) => {
5
- const additionalLoggingContextHeader = req.header('X-Unito-Additional-Logging-Context');
6
-
7
- let additionalLoggingContext;
8
-
9
- if (typeof additionalLoggingContextHeader === 'string') {
10
- try {
11
- additionalLoggingContext = JSON.parse(additionalLoggingContextHeader);
12
- } catch (error) {
13
- logger.warn(`Failed parsing header X-Unito-Additional-Logging-Context: ${additionalLoggingContextHeader}`);
14
- }
15
- }
16
-
17
- for (const [key, value] of Object.entries(additionalLoggingContext || {})) {
18
- logger.setMeta(key, String(value));
19
- }
20
-
21
- next();
22
- };
@@ -1,13 +0,0 @@
1
- import express from 'express';
2
- import * as uuid from 'uuid';
3
- import { logger } from '../logger';
4
-
5
- export const extractCorrelationId = (req: express.Request, res: express.Response, next: express.NextFunction) => {
6
- const correlationIdHeader = req.header('X-Unito-Correlation-Id');
7
-
8
- const correlationId = correlationIdHeader ?? uuid.v4();
9
-
10
- logger.setMeta('correlation_id', correlationId);
11
-
12
- next();
13
- };
@@ -1,38 +0,0 @@
1
- import { Request, Response, NextFunction } from 'express';
2
-
3
- // This interface contains the different variables defined in the authorization methods
4
- // of your integration (in the Integrations Platform registry).
5
- //
6
- // For example, if you add a "custom" authorization method in the registry with the variable "apiKey",
7
- // you must add "apiKey: string" to this interface.
8
- //
9
- // For your convenience, this interface is initialized for an integration that can either have:
10
- //
11
- // * No authentication.
12
- // * API key authentication (Bearer <accessToken>).
13
- // * OAuth 2 authentication (Bearer <accessToken>).
14
- //
15
- export interface Credentials {
16
- accessToken?: string;
17
- }
18
-
19
- export const extractCredentials = (req: Request, res: Response, next: NextFunction) => {
20
- const credentialsHeader = req.header('X-Unito-Credentials');
21
-
22
- let credentials: Credentials | null = null;
23
-
24
- if (credentialsHeader) {
25
- try {
26
- credentials = JSON.parse(Buffer.from(credentialsHeader, 'base64').toString('utf8'));
27
- } catch {
28
- return res.status(400).json({ code: 400, message: 'Error parsing credentials' });
29
- }
30
- }
31
-
32
- // You can add additional verifications here to make sure you received
33
- // all the necessary information to authenticate a user.
34
-
35
- res.locals.credentials = credentials;
36
-
37
- next();
38
- };
@@ -1,59 +0,0 @@
1
- import { Credentials } from './middlewares/credentials';
2
-
3
- export interface RequestOptions {
4
- queryParams?: { [key: string]: string };
5
- method?: 'POST' | 'GET' | 'PATCH';
6
- body?: Record<string, unknown>;
7
- credentials?: Credentials;
8
- }
9
-
10
- const apiUrl = 'https://path_to_your_api';
11
-
12
- export async function get<T>(endpoint: string, options: RequestOptions): Promise<T> {
13
- return fetchWrapper(endpoint, options);
14
- }
15
-
16
- export async function post<T>(endpoint: string, options: RequestOptions): Promise<T> {
17
- return fetchWrapper<T>(endpoint, {
18
- ...options,
19
- method: 'POST',
20
- });
21
- }
22
-
23
- export async function patch<T>(endpoint: string, options: RequestOptions): Promise<T> {
24
- return fetchWrapper<T>(endpoint, {
25
- ...options,
26
- method: 'PATCH',
27
- });
28
- }
29
-
30
- async function fetchWrapper<T>(endpoint: string, options: RequestOptions): Promise<T> {
31
- let absoluteUrl = [apiUrl, endpoint.charAt(0) === '/' ? endpoint.substring(1) : endpoint].join('/');
32
-
33
- if (options.queryParams) {
34
- absoluteUrl = `${absoluteUrl}?${new URLSearchParams(options.queryParams)}`;
35
- }
36
-
37
- const headers: { [key: string]: string } = {
38
- 'Content-Type': 'application/json',
39
- Accept: 'application/json',
40
- };
41
-
42
- if (options.credentials?.accessToken) {
43
- headers['Authorization'] = `Bearer ${options.credentials?.accessToken}`;
44
- }
45
-
46
- const response = await fetch(absoluteUrl, {
47
- method: options.method,
48
- headers,
49
- body: options.body ? JSON.stringify(options.body) : undefined,
50
- });
51
-
52
- if (response.status === 200) {
53
- return (await response.json()) as T;
54
- }
55
-
56
- const textResult = await response.text();
57
-
58
- throw new Error(textResult);
59
- }
@@ -1,11 +0,0 @@
1
- import { Router } from 'express';
2
-
3
- import { router as rootRouter } from './root';
4
- import { router as meRouter } from './me';
5
-
6
- const router = Router();
7
-
8
- router.use('/', rootRouter);
9
- router.use('/me', meRouter);
10
-
11
- export default router;
@@ -1,15 +0,0 @@
1
- import { Request, Response, Router } from 'express';
2
-
3
- import { CredentialAccount } from '@unito/integration-api';
4
-
5
- export const router = Router();
6
-
7
- router.get('/', async (_req: Request, res: Response<CredentialAccount>) => {
8
- const account: CredentialAccount = {
9
- id: 'me',
10
- displayName: 'Me',
11
- emails: [],
12
- };
13
-
14
- res.json(account);
15
- });
@@ -1,12 +0,0 @@
1
- import { Request, Response, Router } from 'express';
2
-
3
- import { Item } from '@unito/integration-api';
4
-
5
- export const router = Router();
6
-
7
- router.get('/', (_req: Request, res: Response<Item>) => {
8
- return res.send({
9
- fields: {},
10
- relations: [],
11
- });
12
- });