Skip to content
This repository was archived by the owner on Feb 2, 2021. It is now read-only.

Commit 6b724be

Browse files
Merge pull request #1033 from telerik/vladimirov/proxy
Proxy improvements
2 parents 5e24712 + 6cfc46b commit 6b724be

12 files changed

+102
-267
lines changed

appbuilder/services/npm-service.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import * as constants from "../../constants";
44
import { fromWindowsRelativePathToUnix, toBoolean } from "../../helpers";
55
import { exported } from "../../decorators";
66
import * as url from "url";
7+
const { getCredentialsFromAuth } = require("proxy-lib/lib/utils");
78

89
export class NpmService implements INpmService {
910
private static TYPES_DIRECTORY = "@types/";
@@ -347,10 +348,14 @@ export class NpmService implements INpmService {
347348
if (npmProxy && npmProxy !== "null") {
348349
const strictSslString = (await this.$childProcess.exec("npm config get strict-ssl") || "").toString().trim();
349350
const uri = url.parse(npmProxy);
351+
const { username, password } = getCredentialsFromAuth(uri.auth || "");
352+
350353
this._proxySettings = {
351354
hostname: uri.hostname,
352355
port: uri.port,
353-
rejectUnauthorized: toBoolean(strictSslString)
356+
rejectUnauthorized: toBoolean(strictSslString),
357+
username,
358+
password
354359
};
355360
}
356361
} catch (err) {

bootstrap.ts

-1
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,6 @@ $injector.require("devicePlatformsConstants", "./mobile/device-platforms-constan
9595
$injector.require("helpService", "./services/help-service");
9696
$injector.require("messageContractGenerator", "./services/message-contract-generator");
9797
$injector.require("proxyService", "./services/proxy-service");
98-
$injector.require("credentialsService", "./services/credentials-service");
9998
$injector.requireCommand("dev-preuninstall", "./commands/preuninstall");
10099
$injector.requireCommand("dev-generate-messages", "./commands/generate-messages");
101100
$injector.requireCommand("doctor", "./commands/doctor");

commands/proxy/proxy-clear.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export class ProxyClearCommand extends ProxyCommandBase {
99
}
1010

1111
public async execute(args: string[]): Promise<void> {
12-
this.$proxyService.clearCache();
12+
await this.$proxyService.clearCache();
1313
this.$logger.out("Successfully cleared proxy.");
1414
await this.tryTrackUsage();
1515
}

commands/proxy/proxy-set.ts

+8-26
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { ProxyCommandBase } from "./proxy-base";
44
import { HttpProtocolToPort } from "../../constants";
55
import { parse } from "url";
66
import { platform, EOL } from "os";
7+
const { getCredentialsFromAuth } = require("proxy-lib/lib/utils");
78

89
const proxySetCommandName = "proxy|set";
910

@@ -53,7 +54,7 @@ export class ProxySetCommand extends ProxyCommandBase {
5354

5455
let port = urlObj.port && +urlObj.port || HttpProtocolToPort[urlObj.protocol];
5556
const noPort = !port || !this.isValidPort(port);
56-
const authCredentials = this.getCredentialsFromAuth(urlObj.auth || "");
57+
const authCredentials = getCredentialsFromAuth(urlObj.auth || "");
5758
if ((username && authCredentials.username && username !== authCredentials.username) ||
5859
password && authCredentials.password && password !== authCredentials.password) {
5960
this.$errors.fail("The credentials you have provided in the url address mismatch those passed as command line arguments.");
@@ -86,18 +87,11 @@ export class ProxySetCommand extends ProxyCommandBase {
8687
password = await this.$prompter.getPassword("Password");
8788
}
8889

89-
if (username && password) {
90-
await this.$proxyService.setCredentials({
91-
password,
92-
username
93-
});
94-
}
95-
96-
const proxyCache: IProxyCache = {
97-
PROXY_HOSTNAME: urlObj.hostname,
98-
PROXY_PORT: port,
99-
PROXY_PROTOCOL: urlObj.protocol,
100-
ALLOW_INSECURE: this.$options.insecure
90+
const settings: IProxyLibSettings = {
91+
proxyUrl: urlString,
92+
username,
93+
password,
94+
rejectUnauthorized: !this.$options.insecure
10195
};
10296

10397
if (!this.$hostInfo.isWindows) {
@@ -111,24 +105,12 @@ export class ProxySetCommand extends ProxyCommandBase {
111105

112106
this.$logger.warn(`${messageNote}Run '${clientName} proxy set --help' for more information.`);
113107

114-
this.$proxyService.setCache(proxyCache);
108+
await this.$proxyService.setCache(settings);
115109
this.$logger.out(`Successfully setup proxy.${EOL}`);
116110
this.$logger.out(await this.$proxyService.getInfo());
117111
await this.tryTrackUsage();
118112
}
119113

120-
private getCredentialsFromAuth(auth: string): ICredentials {
121-
const colonIndex = auth.indexOf(":");
122-
let username = "";
123-
let password = "";
124-
if (colonIndex > -1) {
125-
username = auth.substring(0, colonIndex);
126-
password = auth.substring(colonIndex + 1);
127-
}
128-
129-
return { username, password };
130-
}
131-
132114
private isPasswordRequired(username: string, password: string): boolean {
133115
return !!(username && !password);
134116
}

declarations.d.ts

+44-85
Original file line numberDiff line numberDiff line change
@@ -550,13 +550,24 @@ interface IOpener {
550550

551551
interface IErrors {
552552
fail(formatStr: string, ...args: any[]): never;
553-
fail(opts: { formatStr?: string; errorCode?: number; suppressCommandHelp?: boolean }, ...args: any[]): never;
553+
fail(opts: { formatStr?: string; errorCode?: number; suppressCommandHelp?: boolean, proxyAuthenticationRequired?: boolean }, ...args: any[]): never;
554554
failWithoutHelp(message: string, ...args: any[]): never;
555555
beginCommand(action: () => Promise<boolean>, printCommandHelp: () => Promise<void>): Promise<boolean>;
556556
verifyHeap(message: string): void;
557557
printCallStack: boolean;
558558
}
559559

560+
/**
561+
* Describes error raised when making http requests.
562+
*/
563+
interface IHttpRequestError extends Error {
564+
565+
/**
566+
* Defines if the error is caused by the proxy requiring authentication.
567+
*/
568+
proxyAuthenticationRequired: boolean;
569+
}
570+
560571
interface ICommandOptions {
561572
disableAnalytics?: boolean;
562573
enableHooks?: boolean;
@@ -843,61 +854,47 @@ interface IDynamicHelpService {
843854
}
844855

845856
/**
846-
* Describes cache information about proxy settings.
857+
* Describes standard username/password type credentials.
847858
*/
848-
interface IProxyCache {
859+
interface ICredentials {
860+
username: string;
861+
password: string;
862+
}
863+
864+
interface IRejectUnauthorized {
849865
/**
850-
* Hostname of the proxy
866+
* Defines if NODE_TLS_REJECT_UNAUTHORIZED should be set to true or false. Default value is true.
851867
*/
868+
rejectUnauthorized: boolean;
869+
}
852870

853-
PROXY_HOSTNAME: string;
871+
/**
872+
* Proxy settings required for http request.
873+
*/
874+
interface IProxySettings extends IRejectUnauthorized, ICredentials {
854875
/**
855-
* Port of the proxy
876+
* Hostname of the machine used for proxy.
856877
*/
857-
PROXY_PORT: number;
878+
hostname: string;
858879

859880
/**
860-
* Protocol of the proxy - http or https
881+
* Port of the machine used for proxy that allows connections.
861882
*/
862-
PROXY_PROTOCOL: string;
883+
port: string;
863884

864885
/**
865886
* Protocol of the proxy - http or https
866887
*/
867-
ALLOW_INSECURE: boolean;
868-
}
888+
protocol?: string;
869889

870-
/**
871-
* Describes standard username/password type credentials.
872-
*/
873-
interface ICredentials {
874-
username: string;
875-
password: string;
890+
891+
proxy?: string;
876892
}
877893

878-
/**
879-
* Describes Service used for interaction with the OS' secure storage (Windows Credentials manager for example).
880-
*/
881-
interface ICredentialsService {
882-
/**
883-
* Sets the provided credentials in the OS' secure storage.
884-
* @param key {string} A key which can later be used to retrieve the credentials.
885-
* @param credentials {ICredentials} Credentials to be stored.
886-
* @returns {Promise<ICredentials>} The stored credentials.
887-
*/
888-
setCredentials(key: string, credentials: ICredentials): Promise<ICredentials>;
889-
/**
890-
* Retrieves credentials from the OS' secure storage with the given key.
891-
* @param key {string} A key based on which to retrieve the credentials.
892-
* @returns {Promise<ICredentials>} The stored credentials.
893-
*/
894-
getCredentials(key: string): Promise<ICredentials>;
895-
/**
896-
* Clears credentials from the OS' secure storage with the given key.
897-
* @param key {string} A key based on which to clear the credentials.
898-
* @returns {Promise<void>}
899-
*/
900-
clearCredentials(key: string): Promise<void>;
894+
interface IProxyLibSettings extends IRejectUnauthorized, ICredentials {
895+
proxyUrl: string;
896+
credentialsKey?: string;
897+
userSpecifiedSettingsFilePath?: string;
901898
}
902899

903900
/**
@@ -906,35 +903,22 @@ interface ICredentialsService {
906903
interface IProxyService {
907904
/**
908905
* Caches proxy data.
909-
* @param cacheData {IProxyCache} Data to be cached.
910-
* @returns {Promise<ICredentials>} The cache.
906+
* @param cacheData {IProxyLibSettings} Data to be cached.
907+
* @returns {Promise<void>} The cache.
911908
*/
912-
setCache(cacheData: IProxyCache): IProxyCache;
909+
setCache(settings: IProxyLibSettings): Promise<void>;
913910

914911
/**
915912
* Retrieves proxy cache data.
916-
* @returns {IProxyCache} The cache.
913+
* @returns {Promise<IProxySettings>} Proxy data.
917914
*/
918-
getCache(): IProxyCache;
915+
getCache(): Promise<IProxySettings>;
919916

920917
/**
921918
* Clears proxy cache data.
922-
* @returns {void}
923-
*/
924-
clearCache(): void;
925-
926-
/**
927-
* Sets the provided proxy credentials in the OS' secure storage.
928-
* @param credentials {ICredentials} Proxy credentials to be stored.
929-
* @returns {Promise<ICredentials>} The stored proxy credentials.
930-
*/
931-
setCredentials(credentials: ICredentials): Promise<ICredentials>;
932-
933-
/**
934-
* Retrieves proxy credentials from the OS' secure storage with the given key.
935-
* @returns {Promise<ICredentials>} The stored proxy credentials.
919+
* @returns {Promise<void>}
936920
*/
937-
getCredentials(): Promise<ICredentials>;
921+
clearCache(): Promise<void>;
938922

939923
/**
940924
* Gets info about the proxy that can be printed and shown to the user.
@@ -1918,31 +1902,6 @@ interface ITypeScriptTranspileOptions {
19181902
useLocalTypeScriptCompiler?: boolean;
19191903
}
19201904

1921-
/**
1922-
* Proxy settings required for http request.
1923-
*/
1924-
interface IProxySettings {
1925-
/**
1926-
* Hostname of the machine used for proxy.
1927-
*/
1928-
hostname: string;
1929-
1930-
/**
1931-
* Port of the machine used for proxy that allows connections.
1932-
*/
1933-
port: string;
1934-
1935-
/**
1936-
* Protocol of the proxy - http or https
1937-
*/
1938-
protocol?: string;
1939-
1940-
/**
1941-
* Defines if NODE_TLS_REJECT_UNAUTHORIZED should be set to true or false. Default value is true.
1942-
*/
1943-
rejectUnauthorized: boolean;
1944-
}
1945-
19461905
/**
19471906
* Describes operating system-related utility methods
19481907
*/

errors.ts

+1
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ export class Errors implements IErrors {
142142
exception.stack = (new Error(exception.message)).stack;
143143
exception.errorCode = opts.errorCode || ErrorCodes.UNKNOWN;
144144
exception.suppressCommandHelp = opts.suppressCommandHelp;
145+
exception.proxyAuthenticationRequired = !!opts.proxyAuthenticationRequired;
145146
this.$injector.resolve("logger").trace(opts.formatStr);
146147
throw exception;
147148
}

http-client.ts

+17-16
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,12 @@ export class HttpClient implements Server.IHttpClient {
4343
let pipeTo = options.pipeTo;
4444
delete options.pipeTo;
4545

46-
const proxyCache = this.$proxyService.getCache();
46+
const cliProxySettings = await this.$proxyService.getCache();
4747

4848
options.headers = options.headers || {};
4949
const headers = options.headers;
5050

51-
await this.useProxySettings(proxySettings, proxyCache, options, headers, requestProto);
51+
await this.useProxySettings(proxySettings, cliProxySettings, options, headers, requestProto);
5252

5353
if (!headers.Accept || headers.Accept.indexOf("application/json") < 0) {
5454
if (headers.Accept) {
@@ -98,7 +98,7 @@ export class HttpClient implements Server.IHttpClient {
9898
const requestObj = request(options);
9999

100100
requestObj
101-
.on("error", (err: Error) => {
101+
.on("error", (err: IHttpRequestError) => {
102102
this.$logger.trace("An error occurred while sending the request:", err);
103103
// In case we get a 4xx error code there seems to be no better way than this regex to get the error code
104104
// the tunnel-agent module that request is using is obscuring the response and hence the statusCode by throwing an error message
@@ -107,6 +107,7 @@ export class HttpClient implements Server.IHttpClient {
107107
const errorMessageMatch = err.message.match(HttpClient.STATUS_CODE_REGEX);
108108
const errorMessageStatusCode = errorMessageMatch && errorMessageMatch[1] && +errorMessageMatch[1];
109109
const errorMessage = this.getErrorMessage(errorMessageStatusCode, null);
110+
err.proxyAuthenticationRequired = errorMessageStatusCode === HttpStatusCodes.PROXY_AUTHENTICATION_REQUIRED;
110111
err.message = errorMessage || err.message;
111112
this.setResponseResult(promiseActions, timerId, { err });
112113
})
@@ -239,7 +240,8 @@ export class HttpClient implements Server.IHttpClient {
239240
private getErrorMessage(statusCode: number, body: string): string {
240241
if (statusCode === HttpStatusCodes.PROXY_AUTHENTICATION_REQUIRED) {
241242
const clientNameLowerCase = this.$staticConfig.CLIENT_NAME.toLowerCase();
242-
return `Your proxy requires authentication. You can run ${EOL}\t${clientNameLowerCase} proxy set <url> <username> <password>.${EOL}In order to supply ${clientNameLowerCase} with the credentials needed.`;
243+
this.$logger.error(`You can run ${EOL}\t${clientNameLowerCase} proxy set <url> <username> <password>.${EOL}In order to supply ${clientNameLowerCase} with the credentials needed.`);
244+
return "Your proxy requires authentication.";
243245
} else if (statusCode === HttpStatusCodes.PAYMENT_REQUIRED) {
244246
const subscriptionUrl = util.format("%s://%s/appbuilder/account/subscription", this.$config.AB_SERVER_PROTO, this.$config.AB_SERVER);
245247
return util.format("Your subscription has expired. Go to %s to manage your subscription. Note: After you renew your subscription, " +
@@ -253,10 +255,10 @@ export class HttpClient implements Server.IHttpClient {
253255
return err;
254256
}
255257

256-
if (err.ExceptionMessage) {
258+
if (err && err.ExceptionMessage) {
257259
return err.ExceptionMessage;
258260
}
259-
if (err.Message) {
261+
if (err && err.Message) {
260262
return err.Message;
261263
}
262264
} catch (parsingFailed) {
@@ -270,25 +272,24 @@ export class HttpClient implements Server.IHttpClient {
270272
/**
271273
* This method respects the proxySettings (or proxyCache) by modifying headers and options passed to http(s) module.
272274
* @param {IProxySettings} proxySettings The settings passed for this specific call.
273-
* @param {IProxyCache} proxyCache The globally set proxy for this CLI.
275+
* @param {IProxySettings} cliProxySettings The globally set proxy for this CLI.
274276
* @param {any}options The object that will be passed to http(s) module.
275277
* @param {any} headers Headers of the current request.
276278
* @param {string} requestProto The protocol used for the current request - http or https.
277279
*/
278-
private async useProxySettings(proxySettings: IProxySettings, proxyCache: IProxyCache, options: any, headers: any, requestProto: string): Promise<void> {
279-
if (proxySettings || proxyCache) {
280-
const proto = (proxySettings && proxySettings.protocol) || proxyCache.PROXY_PROTOCOL || "http:";
281-
const host = (proxySettings && proxySettings.hostname) || proxyCache.PROXY_HOSTNAME;
282-
const port = (proxySettings && proxySettings.port) || proxyCache.PROXY_PORT;
280+
private async useProxySettings(proxySettings: IProxySettings, cliProxySettings: IProxySettings, options: any, headers: any, requestProto: string): Promise<void> {
281+
if (proxySettings || cliProxySettings) {
282+
const proto = (proxySettings && proxySettings.protocol) || cliProxySettings.protocol || "http:";
283+
const host = (proxySettings && proxySettings.hostname) || cliProxySettings.hostname;
284+
const port = (proxySettings && proxySettings.port) || cliProxySettings.port;
283285
let credentialsPart = "";
284-
const proxyCredentials = await this.$proxyService.getCredentials();
285-
if (proxyCredentials && proxyCredentials.username && proxyCredentials.password) {
286-
credentialsPart = `${proxyCredentials.username}:${proxyCredentials.password}@`;
286+
if (cliProxySettings.username && cliProxySettings.password) {
287+
credentialsPart = `${cliProxySettings.username}:${cliProxySettings.password}@`;
287288
}
288289

289290
// Note that proto ends with :
290291
options.proxy = `${proto}//${credentialsPart}${host}:${port}`;
291-
options.rejectUnauthorized = proxySettings ? proxySettings.rejectUnauthorized : (proxyCache ? !proxyCache.ALLOW_INSECURE : true);
292+
options.rejectUnauthorized = proxySettings ? proxySettings.rejectUnauthorized : cliProxySettings.rejectUnauthorized;
292293

293294
this.$logger.trace("Using proxy: %s", options.proxy);
294295
}

0 commit comments

Comments
 (0)