@unito/integration-cli 0.58.0 → 0.58.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.
- package/dist/.eslintrc.d.ts +4 -4
- package/dist/.eslintrc.js +8 -0
- package/dist/src/commands/oauth2.js +14 -13
- package/dist/src/resources/configuration.d.ts +1 -1
- package/dist/src/resources/configuration.js +4 -4
- package/dist/src/resources/{oauth2Helper.d.ts → oauth2.d.ts} +2 -2
- package/dist/src/resources/{oauth2Helper.js → oauth2.js} +4 -6
- package/dist/src/resources/template.d.ts +3 -0
- package/dist/src/resources/template.js +40 -0
- package/dist/src/services/{oauth2Helper.d.ts → oauth2.d.ts} +4 -3
- package/dist/src/services/{oauth2Helper.js → oauth2.js} +54 -34
- package/dist/test/commands/oauth2.test.js +19 -2
- package/dist/test/resources/{oauth2Helper.test.js → oauth2.test.js} +8 -8
- package/dist/test/resources/template.test.js +40 -0
- package/dist/test/services/oauth2.test.d.ts +1 -0
- package/dist/test/{oauth2Helper/oauth2Helper.test.js → services/oauth2.test.js} +33 -8
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
- /package/dist/test/{oauth2Helper/oauth2Helper.test.d.ts → resources/oauth2.test.d.ts} +0 -0
- /package/dist/test/resources/{oauth2Helper.test.d.ts → template.test.d.ts} +0 -0
package/dist/.eslintrc.d.ts
CHANGED
|
@@ -4,13 +4,13 @@ export declare namespace env {
|
|
|
4
4
|
export declare let plugins: string[];
|
|
5
5
|
declare let _extends: string[];
|
|
6
6
|
export { _extends as extends };
|
|
7
|
-
export declare let rules: {
|
|
8
|
-
'no-console': string;
|
|
9
|
-
};
|
|
10
|
-
export declare let ignorePatterns: string[];
|
|
11
7
|
export declare let overrides: {
|
|
12
8
|
files: string[];
|
|
13
9
|
rules: {
|
|
14
10
|
'@typescript-eslint/no-floating-promises': string;
|
|
15
11
|
};
|
|
16
12
|
}[];
|
|
13
|
+
export declare let rules: {
|
|
14
|
+
'no-console': string;
|
|
15
|
+
};
|
|
16
|
+
export declare let ignorePatterns: string[];
|
package/dist/.eslintrc.js
CHANGED
|
@@ -10,6 +10,14 @@ module.exports = {
|
|
|
10
10
|
'eslint:recommended',
|
|
11
11
|
'plugin:@typescript-eslint/recommended',
|
|
12
12
|
],
|
|
13
|
+
overrides: [
|
|
14
|
+
{
|
|
15
|
+
'files': ['test/**/*.test.ts'],
|
|
16
|
+
'rules': {
|
|
17
|
+
'@typescript-eslint/no-floating-promises': 'off'
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
],
|
|
13
21
|
rules: {
|
|
14
22
|
'no-console': 'off',
|
|
15
23
|
},
|
|
@@ -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
|
|
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
|
|
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
|
|
73
|
+
const testAccount = flags['test-account'];
|
|
74
|
+
const testAccountCredentials = configuration.testAccounts?.[testAccount];
|
|
74
75
|
let credentials;
|
|
75
|
-
if (!
|
|
76
|
-
core_1.ux.
|
|
77
|
-
credentials = await
|
|
78
|
-
core_1.ux.
|
|
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 (!
|
|
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,
|
|
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 ${
|
|
88
|
-
credentials = await
|
|
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
|
|
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/
|
|
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
|
|
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
|
|
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
|
|
25
|
+
const oauthHelper = new oauth2_1.default(applicationCredentials);
|
|
28
26
|
return oauthHelper.updateToken(refreshToken);
|
|
29
27
|
}
|
|
30
28
|
exports.updateToken = updateToken;
|
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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
|
|
66
|
-
const params = new URLSearchParams(authUrl.search);
|
|
69
|
+
const authorizationParams = new URLSearchParams();
|
|
67
70
|
if (this.clientId) {
|
|
68
|
-
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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,
|
|
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-
|
|
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 =
|
|
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/
|
|
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([
|
|
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
|
|
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/
|
|
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(
|
|
25
|
-
sinon_1.default.stub(
|
|
26
|
-
sinon_1.default.stub(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
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('
|
|
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
|
|
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('
|
|
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
|
|
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
|
|
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-
|
|
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,
|
package/oclif.manifest.json
CHANGED
package/package.json
CHANGED
|
File without changes
|
|
File without changes
|