question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Electron Updater error: Only one auth mechanism allowed

See original GitHub issue
  • Electron Builder Version: 22.9.1

  • Electron Version: 9.1.0

  • Electron Type (current, beta, nightly): current

  • Electron Updater Version: 4.3.5

  • Target: mac - dmg and zip

Since yesterday, I’ve not been able to use the updater on my app. Anytime the app checks for an update, I get the below error message.

I’m aware something like this has happened before, per #1370, however this suddenly popped up on old and new versions of Electron Builder/Updater.

I was using Electron Updater v4.2.2 and Electron Builder v22.7.0, before updating to the latest versions listed above, but regardless, the issue is the same.

I haven’t changed anything about my config for weeks, so struggling to figure out what the issue might be. Any help would be appreciated!

ERROR MESSAGE

Error in auto-updater. HttpError: 400 
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n
<Error>
<Code>InvalidArgument</Code>
<Message>Only one auth mechanism allowed; only the X-Amz-Algorithm query parameter, Signature query string parameter or the Authorization header should be specified</Message>
<ArgumentName>Authorization</ArgumentName>
<ArgumentValue>token __redacted__</ArgumentValue>
<RequestId>__redacted__</RequestId>
<HostId>__redacted__</HostId>
</Error>"
Headers: { 
  "status": "400", 
  "x-amz-request-id": "51FCEE6BE4B9B7ED", 
  "x-amz-id-2": "TwopT1BDNyVCWzhApPt7ZNju/FLIsmlTkkI7XaYpOkBc90vYGFpLjIJ3oLV45kDet1TIfZ4UGHs=", 
  "content-type": "application/xml", 
  "server": "AmazonS3", 
  "accept-ranges": "bytes", 
  "via": "1.1 varnish, 1.1 varnish", 
  "date": "Fri, 29 Jan 2021 08:16:35 GMT", 
  "x-served-by": "cache-dca17729-DCA, cache-lon4226-LON",
  "x-cache": "MISS, MISS", 
  "x-cache-hits": "0, 0", 
  "x-fastly-request-id": "43468af043a1d5472820e0dbf9403e6912b6de68" 
}

package.json config:

"build": {
    "afterSign": "deploy/notarize.js",
    "appId": "com.electron._redacted_",
    "buildDependenciesFromSource": true,
    "generateUpdatesFilesForAllChannels": true,
    "icon": "build/assets/icon.png",
    "productName": "_redacted_",
    "directories": {
      "buildResources": "build",
      "output": "dist"
    },
    "mac": {
      "hardenedRuntime": true,
      "gatekeeperAssess": false,
      "entitlements": "build/entitlements.mac.plist",
      "entitlementsInherit": "build/entitlements.mac.plist",
      "category": "public.app-category._redacted_",
      "target": [
        "dmg",
        "zip"
      ]
    },
    "dmg": {
      "sign": false
    },
    "asarUnpack": [
      "build/pydist"
    ],
    "publish": [
      {
        "provider": "github",
        "owner": "_redacted_",
        "repo": "_redacted_",
        "token": "_redacted_",
        "private": true,
        "releaseType": "release"
      }
    ]
  }

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Reactions:44
  • Comments:67 (1 by maintainers)

github_iconTop GitHub Comments

22reactions
NoahAndrewscommented, Feb 18, 2021

Great news! I talked to GitHub support, and they made a server-side change so that applications using the old version of electron-updater can successfully check for updates again!

I’ve only tested this for an application updating from a private GitHub repository, so someone else will have to see if it’s fixed for public repositories too.

16reactions
antim-streamcommented, Feb 12, 2021

I figured out the solution for this and this will worked for other users also who already downloaded the app(added Screenshot) these changes you can do in your pipeline

  1. create httpExecutor.js inside src/utils/httpExecutorUtil.js

//////////////////////////////////////////////////////////////////// “use strict”;

Object.defineProperty(exports, “__esModule”, { value: true }); exports.createHttpError = createHttpError; exports.parseJson = parseJson; exports.configureRequestOptionsFromUrl = configureRequestOptionsFromUrl; exports.configureRequestUrl = configureRequestUrl; exports.safeGetHeader = safeGetHeader; exports.configureRequestOptions = configureRequestOptions; exports.safeStringifyJson = safeStringifyJson; exports.DigestTransform = exports.HttpExecutor = exports.HttpError = void 0;

function _crypto() { const data = require(“crypto”);

_crypto = function () { return data; };

return data; }

var _debug2 = _interopRequireDefault(require(“debug”));

var _fs = require(“fs”);

function _stream() { const data = require(“stream”);

_stream = function () { return data; };

return data; }

function _url() { const data = require(“url”);

_url = function () { return data; };

return data; }

function _CancellationToken() { const data = require(“./CancellationToken”);

_CancellationToken = function () { return data; };

return data; }

function _index() { const data = require(“./index”);

_index = function () { return data; };

return data; }

function _ProgressCallbackTransform() { const data = require(“./ProgressCallbackTransform”);

_ProgressCallbackTransform = function () { return data; };

return data; }

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

const debug = (0, _debug2.default)(“electron-builder”);

function createHttpError(response, description = null) { return new HttpError(response.statusCode || -1, ${response.statusCode} ${response.statusMessage} + (description == null ? “” : “\n” + JSON.stringify(description, null, " ")) + "\nHeaders: " + safeStringifyJson(response.headers), description); }

const HTTP_STATUS_CODES = new Map([[429, “Too many requests”], [400, “Bad request”], [403, “Forbidden”], [404, “Not found”], [405, “Method not allowed”], [406, “Not acceptable”], [408, “Request timeout”], [413, “Request entity too large”], [500, “Internal server error”], [502, “Bad gateway”], [503, “Service unavailable”], [504, “Gateway timeout”], [505, “HTTP version not supported”]]);

class HttpError extends Error { constructor(statusCode, message = HTTP error: ${HTTP_STATUS_CODES.get(statusCode) || statusCode}, description = null) { super(message); this.statusCode = statusCode; this.description = description; this.name = “HttpError”; this.code = HTTP_ERROR_${statusCode}; }

}

exports.HttpError = HttpError;

function parseJson(result) { return result.then(it => it == null || it.length === 0 ? null : JSON.parse(it)); }

class HttpExecutor { constructor() { this.maxRedirects = 10; }

request(options, cancellationToken = new (_CancellationToken().CancellationToken)(), data) { configureRequestOptions(options); const encodedData = data == null ? undefined : Buffer.from(JSON.stringify(data));

if (encodedData != null) {
  options.method = "post";
  options.headers["Content-Type"] = "application/json";
  options.headers["Content-Length"] = encodedData.length;
}

return this.doApiRequest(options, cancellationToken, it => {
  it.end(encodedData);
});

}

doApiRequest(options, cancellationToken, requestProcessor, redirectCount = 0) { if (debug.enabled) { debug(Request: ${safeStringifyJson(options)}); }

return cancellationToken.createPromise((resolve, reject, onCancel) => {
  const request = this.createRequest(options, response => {
    try {
      this.handleResponse(response, options, cancellationToken, resolve, reject, redirectCount, requestProcessor);
    } catch (e) {
      reject(e);
    }
  });
  this.addErrorAndTimeoutHandlers(request, reject);
  this.addRedirectHandlers(request, options, reject, redirectCount, options => {
    this.doApiRequest(options, cancellationToken, requestProcessor, redirectCount).then(resolve).catch(reject);
  });
  requestProcessor(request, reject);
  onCancel(() => request.abort());
});

} // noinspection JSUnusedLocalSymbols // eslint-disable-next-line

addRedirectHandlers(request, options, reject, redirectCount, handler) {// not required for NodeJS }

addErrorAndTimeoutHandlers(request, reject) { this.addTimeOutHandler(request, reject); request.on(“error”, reject); request.on(“aborted”, () => { reject(new Error(“Request has been aborted by the server”)); }); }

handleResponse(response, options, cancellationToken, resolve, reject, redirectCount, requestProcessor) { if (debug.enabled) { debug(Response: ${response.statusCode} ${response.statusMessage}, request options: ${safeStringifyJson(options)}); } // we handle any other >= 400 error on request end (read detailed message in the response body)

if (response.statusCode === 404) {
  // error is clear, we don't need to read detailed error description
  reject(createHttpError(response, `method: ${options.method || "GET"} url: ${options.protocol || "https:"}//${options.hostname}${options.port ? `:${options.port}` : ""}${options.path}

Please double check that your authentication token is correct. Due to security reasons actual status maybe not reported, but 404. `)); return; } else if (response.statusCode === 204) { // on DELETE request resolve(); return; }

const redirectUrl = safeGetHeader(response, "location");

if (redirectUrl != null) {
  if (redirectCount > this.maxRedirects) {
    reject(this.createMaxRedirectError());
    return;
  }

  this.doApiRequest(HttpExecutor.prepareRedirectUrlOptions(redirectUrl, options), cancellationToken, requestProcessor, redirectCount).then(resolve).catch(reject);
  return;
}

response.setEncoding("utf8");
let data = "";
response.on("error", reject);
response.on("data", chunk => data += chunk);
response.on("end", () => {
  try {
    if (response.statusCode != null && response.statusCode >= 400) {
      const contentType = safeGetHeader(response, "content-type");
      const isJson = contentType != null && (Array.isArray(contentType) ? contentType.find(it => it.includes("json")) != null : contentType.includes("json"));
      reject(createHttpError(response, isJson ? JSON.parse(data) : data));
    } else {
      resolve(data.length === 0 ? null : data);
    }
  } catch (e) {
    reject(e);
  }
});

}

async downloadToBuffer(url, options) { return await options.cancellationToken.createPromise((resolve, reject, onCancel) => { let result = null; const requestOptions = { headers: options.headers || undefined, // because PrivateGitHubProvider requires HttpExecutor.prepareRedirectUrlOptions logic, so, we need to redirect manually redirect: “manual” }; configureRequestUrl(url, requestOptions); configureRequestOptions(requestOptions); this.doDownload(requestOptions, { destination: null, options, onCancel, callback: error => { if (error == null) { resolve(result); } else { reject(error); } }, responseHandler: (response, callback) => { const contentLength = safeGetHeader(response, “content-length”); let position = -1;

      if (contentLength != null) {
        const size = parseInt(contentLength, 10);

        if (size > 0) {
          if (size > 52428800) {
            callback(new Error("Maximum allowed size is 50 MB"));
            return;
          }

          result = Buffer.alloc(size);
          position = 0;
        }
      }

      response.on("data", chunk => {
        if (position !== -1) {
          chunk.copy(result, position);
          position += chunk.length;
        } else if (result == null) {
          result = chunk;
        } else {
          if (result.length > 52428800) {
            callback(new Error("Maximum allowed size is 50 MB"));
            return;
          }

          result = Buffer.concat([result, chunk]);
        }
      });
      response.on("end", () => {
        if (result != null && position !== -1 && position !== result.length) {
          callback(new Error(`Received data length ${position} is not equal to expected ${result.length}`));
        } else {
          callback(null);
        }
      });
    }
  }, 0);
});

}

doDownload(requestOptions, options, redirectCount) { const request = this.createRequest(requestOptions, response => { if (response.statusCode >= 400) { options.callback(new Error(Cannot download "${requestOptions.protocol || "https:"}//${requestOptions.hostname}${requestOptions.path}", status ${response.statusCode}: ${response.statusMessage})); return; } // It is possible for the response stream to fail, e.g. when a network is lost while // response stream is in progress. Stop waiting and reject so consumer can catch the error.

  response.on("error", options.callback); // this code not relevant for Electron (redirect event instead handled)

  const redirectUrl = safeGetHeader(response, "location");

  if (redirectUrl != null) {
    if (redirectCount < this.maxRedirects) {
      this.doDownload(HttpExecutor.prepareRedirectUrlOptions(redirectUrl, requestOptions), options, redirectCount++);
    } else {
      options.callback(this.createMaxRedirectError());
    }

    return;
  }

  if (options.responseHandler == null) {
    configurePipes(options, response);
  } else {
    options.responseHandler(response, options.callback);
  }
});
this.addErrorAndTimeoutHandlers(request, options.callback);
this.addRedirectHandlers(request, requestOptions, options.callback, redirectCount, requestOptions => {
  this.doDownload(requestOptions, options, redirectCount++);
});
request.end();

}

createMaxRedirectError() { return new Error(Too many redirects (> ${this.maxRedirects})); }

addTimeOutHandler(request, callback) { request.on(“socket”, socket => { socket.setTimeout(60 * 1000, () => { request.abort(); callback(new Error(“Request timed out”)); }); }); }

static prepareRedirectUrlOptions(redirectUrl, options) { const newOptions = configureRequestOptionsFromUrl(redirectUrl, { …options }); const headers = newOptions.headers;

if (headers != null && headers.authorization != null && headers.authorization.startsWith("token")) {
  const parsedNewUrl = new (_url().URL)(redirectUrl);
  
  // if (parsedNewUrl.hostname.endsWith(".amazonaws.com")) {
  if (parsedNewUrl.hostname.endsWith(".amazonaws.com") || parsedNewUrl.searchParams.has("X-Amz-Credential")) {
    delete headers.authorization;
  }
}

return newOptions;

}

}

exports.HttpExecutor = HttpExecutor;

function configureRequestOptionsFromUrl(url, options) { const result = configureRequestOptions(options); configureRequestUrl(new (_url().URL)(url), result); return result; }

function configureRequestUrl(url, options) { options.protocol = url.protocol; options.hostname = url.hostname;

if (url.port) { options.port = url.port; } else if (options.port) { delete options.port; }

options.path = url.pathname + url.search; }

class DigestTransform extends _stream().Transform { constructor(expected, algorithm = “sha512”, encoding = “base64”) { super(); this.expected = expected; this.algorithm = algorithm; this.encoding = encoding; this._actual = null; this.isValidateOnEnd = true; this.digester = (0, _crypto().createHash)(algorithm); } // noinspection JSUnusedGlobalSymbols

get actual() { return this._actual; } // noinspection JSUnusedGlobalSymbols

_transform(chunk, encoding, callback) { this.digester.update(chunk); callback(null, chunk); } // noinspection JSUnusedGlobalSymbols

_flush(callback) { this._actual = this.digester.digest(this.encoding);

if (this.isValidateOnEnd) {
  try {
    this.validate();
  } catch (e) {
    callback(e);
    return;
  }
}

callback(null);

}

validate() { if (this._actual == null) { throw (0, _index().newError)(“Not finished yet”, “ERR_STREAM_NOT_FINISHED”); }

if (this._actual !== this.expected) {
  throw (0, _index().newError)(`${this.algorithm} checksum mismatch, expected ${this.expected}, got ${this._actual}`, "ERR_CHECKSUM_MISMATCH");
}

return null;

}

}

exports.DigestTransform = DigestTransform;

function checkSha2(sha2Header, sha2, callback) { if (sha2Header != null && sha2 != null && sha2Header !== sha2) { callback(new Error(checksum mismatch: expected ${sha2} but got ${sha2Header} (X-Checksum-Sha2 header))); return false; }

return true; }

function safeGetHeader(response, headerKey) { const value = response.headers[headerKey];

if (value == null) { return null; } else if (Array.isArray(value)) { // electron API return value.length === 0 ? null : value[value.length - 1]; } else { return value; } }

function configurePipes(options, response) { if (!checkSha2(safeGetHeader(response, “X-Checksum-Sha2”), options.options.sha2, options.callback)) { return; }

const streams = [];

if (options.options.onProgress != null) { const contentLength = safeGetHeader(response, “content-length”);

if (contentLength != null) {
  streams.push(new (_ProgressCallbackTransform().ProgressCallbackTransform)(parseInt(contentLength, 10), options.options.cancellationToken, options.options.onProgress));
}

}

const sha512 = options.options.sha512;

if (sha512 != null) { streams.push(new DigestTransform(sha512, “sha512”, sha512.length === 128 && !sha512.includes(“+”) && !sha512.includes(“Z”) && !sha512.includes(“=”) ? “hex” : “base64”)); } else if (options.options.sha2 != null) { streams.push(new DigestTransform(options.options.sha2, “sha256”, “hex”)); }

const fileOut = (0, _fs.createWriteStream)(options.destination); streams.push(fileOut); let lastStream = response;

for (const stream of streams) { stream.on(“error”, error => { if (!options.options.cancellationToken.cancelled) { options.callback(error); } }); lastStream = lastStream.pipe(stream); }

fileOut.on(“finish”, () => { fileOut.close(options.callback); }); }

function configureRequestOptions(options, token, method) { if (method != null) { options.method = method; }

options.headers = { …options.headers }; const headers = options.headers;

if (token != null) { headers.authorization = token.startsWith(“Basic”) ? token : token ${token}; }

if (headers[“User-Agent”] == null) { headers[“User-Agent”] = “electron-builder”; }

if (method == null || method === “GET” || headers[“Cache-Control”] == null) { headers[“Cache-Control”] = “no-cache”; } // do not specify for node (in any case we use https module)

if (options.protocol == null && process.versions.electron != null) { options.protocol = “https:”; }

return options; }

function safeStringifyJson(data, skippedNames) { return JSON.stringify(data, (name, value) => { if (name.endsWith(“authorization”) || name.endsWith(“Password”) || name.endsWith(“PASSWORD”) || name.endsWith(“Token”) || name.includes(“password”) || name.includes(“token”) || skippedNames != null && skippedNames.has(name)) { return “<stripped sensitive data>”; }

return value;

}, 2); } // __ts-babel@6.0.4 //# sourceMappingURL=httpExecutor.js.map ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

  1. npm install removing node_modules/builder-util-runtime/out/httpExecutor.js from node modules
  2. rm -rf node_modules/builder-util-runtime/out/httpExecutor.js coping httpExecutor.js from src/utils/httpExecutor.js to node_modules/builder-util-runtime/out/
  3. cp src/utils/httpExecutor.js node_modules/builder-util-runtime/out/ build your app
  4. yarn run build publish your app
  5. yarn publish-app Screenshot (725)
Read more comments on GitHub >

github_iconTop Results From Across the Web

Auto Update - electron-builder
Mac, otherwise latest-mac.yml cannot be created, which causes autoUpdater error. Default target for macOS is dmg + zip , so there is no...
Read more >
Updating Applications | Electron
There are several ways to provide automatic updates to your Electron application. The easiest and officially supported one is taking advantage of the...
Read more >
Build a Desktop App with Electron and Authentication
In this article, I'll show you how to create an Electron app with TypeScript, AppAuth-JS, and OpenID Connect (OIDC). You'll learn how to...
Read more >
electron auto updater not installing in OSx - Stack Overflow
On OSx however, the new version gets downloaded, but is never installed. The event fired when on update is downloaded is this one...
Read more >
FDA Mpox Response
Colorized scanning electron micrograph of monkeypox virus (orange) on the ... November 15, 2022: The FDA issued an Emergency Use Authorization (EUA) to ......
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found