mirror of https://github.com/keeweb/keeweb
disabled desktop update archives
parent
bee96ebf15
commit
75fa636366
|
@ -95,11 +95,6 @@ jobs:
|
|||
with:
|
||||
name: KeeWeb-${{ steps.get_tag.outputs.tag }}.linux.x86_64.rpm
|
||||
path: dist/desktop/KeeWeb-${{ steps.get_tag.outputs.tag }}.linux.x86_64.rpm
|
||||
- name: Upload update artifact
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: UpdateDesktop.zip
|
||||
path: dist/desktop/UpdateDesktop.zip
|
||||
|
||||
darwin:
|
||||
runs-on: macos-latest
|
||||
|
@ -124,9 +119,6 @@ jobs:
|
|||
path: dist
|
||||
- name: Install npm modules
|
||||
run: npm ci
|
||||
- name: Install desktop npm modules
|
||||
working-directory: desktop
|
||||
run: npm ci
|
||||
- name: Install grunt
|
||||
run: sudo npm i -g grunt-cli
|
||||
- name: Write secrets
|
||||
|
@ -179,9 +171,6 @@ jobs:
|
|||
path: dist
|
||||
- name: Install npm modules
|
||||
run: npm ci
|
||||
- name: Install desktop npm modules
|
||||
working-directory: desktop
|
||||
run: npm ci
|
||||
- name: Install grunt
|
||||
run: npm i -g grunt-cli
|
||||
- name: Write secrets
|
||||
|
@ -328,11 +317,6 @@ jobs:
|
|||
with:
|
||||
name: KeeWeb-${{ steps.get_tag.outputs.tag }}.win.arm64.zip
|
||||
path: assets
|
||||
- name: Download update artifact
|
||||
uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: UpdateDesktop.zip
|
||||
path: assets
|
||||
- name: Zip html
|
||||
working-directory: html
|
||||
run: zip -vr ../assets/KeeWeb-${{ steps.get_tag.outputs.tag }}.html.zip .
|
||||
|
@ -505,15 +489,6 @@ jobs:
|
|||
asset_path: assets/KeeWeb-${{ steps.get_tag.outputs.tag }}.win.arm64.zip
|
||||
asset_name: KeeWeb-${{ steps.get_tag.outputs.tag }}.win.arm64.zip
|
||||
asset_content_type: application/octet-stream
|
||||
- name: Upload update asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: assets/UpdateDesktop.zip
|
||||
asset_name: UpdateDesktop.zip
|
||||
asset_content_type: application/octet-stream
|
||||
- name: Upload verify.sign asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
|
|
53
Gruntfile.js
53
Gruntfile.js
|
@ -138,18 +138,6 @@ module.exports = function (grunt) {
|
|||
expand: true,
|
||||
nonull: true
|
||||
},
|
||||
'desktop-update': {
|
||||
cwd: 'tmp/desktop/keeweb-linux-x64/resources/',
|
||||
src: 'app.asar',
|
||||
dest: 'tmp/desktop/update/',
|
||||
expand: true,
|
||||
nonull: true
|
||||
},
|
||||
'desktop-update-helper': {
|
||||
src: ['helper/darwin/KeeWebHelper', 'helper/win32/KeeWebHelper.exe'],
|
||||
dest: 'tmp/desktop/update/',
|
||||
nonull: true
|
||||
},
|
||||
'desktop-darwin-helper-x64': {
|
||||
src: 'helper/darwin/KeeWebHelper',
|
||||
dest: 'tmp/desktop/KeeWeb-darwin-x64/KeeWeb.app/Contents/Resources/',
|
||||
|
@ -461,13 +449,6 @@ module.exports = function (grunt) {
|
|||
options: {
|
||||
level: 6
|
||||
},
|
||||
'desktop-update': {
|
||||
options: {
|
||||
archive: 'dist/desktop/UpdateDesktop.zip',
|
||||
comment: zipCommentPlaceholder
|
||||
},
|
||||
files: [{ cwd: 'tmp/desktop/update', src: '**', expand: true, nonull: true }]
|
||||
},
|
||||
'win32-x64': {
|
||||
options: { archive: `dist/desktop/KeeWeb-${pkg.version}.win.x64.zip` },
|
||||
files: [{ cwd: 'tmp/desktop/KeeWeb-win32-x64', src: '**', expand: true }]
|
||||
|
@ -599,35 +580,6 @@ module.exports = function (grunt) {
|
|||
]
|
||||
}
|
||||
},
|
||||
'sign-archive': {
|
||||
'desktop-update': {
|
||||
options: {
|
||||
file: 'dist/desktop/UpdateDesktop.zip',
|
||||
signature: zipCommentPlaceholder
|
||||
}
|
||||
}
|
||||
},
|
||||
'sign-desktop-files': {
|
||||
'desktop-update': {
|
||||
options: {
|
||||
path: 'tmp/desktop/update'
|
||||
}
|
||||
}
|
||||
},
|
||||
'validate-desktop-update': {
|
||||
desktop: {
|
||||
options: {
|
||||
file: 'dist/desktop/UpdateDesktop.zip',
|
||||
expected: [
|
||||
'app.asar',
|
||||
'helper/darwin/KeeWebHelper',
|
||||
'helper/win32/KeeWebHelper.exe'
|
||||
],
|
||||
expectedCount: 7,
|
||||
publicKey: 'app/resources/public-key.pem'
|
||||
}
|
||||
}
|
||||
},
|
||||
'osx-sign': {
|
||||
options: {
|
||||
get identity() {
|
||||
|
@ -758,10 +710,7 @@ module.exports = function (grunt) {
|
|||
sign: 'dist/desktop/Verify.sign.sha256'
|
||||
},
|
||||
files: {
|
||||
'dist/desktop/Verify.sha256': [
|
||||
'dist/desktop/KeeWeb-*',
|
||||
'dist/desktop/UpdateDesktop.zip'
|
||||
]
|
||||
'dist/desktop/Verify.sha256': ['dist/desktop/KeeWeb-*']
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import kdbxweb from 'kdbxweb';
|
||||
import { Events } from 'framework/events';
|
||||
import { RuntimeInfo } from 'const/runtime-info';
|
||||
import { Transport } from 'comp/browser/transport';
|
||||
|
@ -15,7 +16,6 @@ const Updater = {
|
|||
UpdateInterval: 1000 * 60 * 60 * 24,
|
||||
MinUpdateTimeout: 500,
|
||||
MinUpdateSize: 10000,
|
||||
UpdateCheckFiles: ['app.asar'],
|
||||
nextCheckTimeout: null,
|
||||
updateCheckDate: new Date(0),
|
||||
enabled: Launcher && Launcher.updaterEnabled(),
|
||||
|
@ -34,7 +34,7 @@ const Updater = {
|
|||
updateInProgress() {
|
||||
return (
|
||||
UpdateModel.status === 'checking' ||
|
||||
['downloading', 'extracting'].indexOf(UpdateModel.updateStatus) >= 0
|
||||
['downloading', 'extracting', 'updating'].indexOf(UpdateModel.updateStatus) >= 0
|
||||
);
|
||||
},
|
||||
|
||||
|
@ -180,28 +180,61 @@ const Updater = {
|
|||
}
|
||||
UpdateModel.set({ updateStatus: 'downloading', updateError: null });
|
||||
logger.info('Downloading update', ver);
|
||||
const updateAssetName = this.getUpdateAssetName(ver);
|
||||
if (!updateAssetName) {
|
||||
logger.error('Empty updater asset name for', Launcher.platform(), Launcher.arch());
|
||||
return;
|
||||
}
|
||||
const updateUrlBasePath = Links.UpdateBasePath.replace('{ver}', ver);
|
||||
const updateAssetUrl = updateUrlBasePath + updateAssetName;
|
||||
const useCache = !startedByUser;
|
||||
Transport.httpGet({
|
||||
url: Links.UpdateDesktop.replace('{ver}', ver),
|
||||
file: 'KeeWeb-' + ver + '.zip',
|
||||
cache: !startedByUser,
|
||||
success: (filePath) => {
|
||||
UpdateModel.set({ updateStatus: 'extracting' });
|
||||
logger.info('Extracting update file', this.UpdateCheckFiles, filePath);
|
||||
this.extractAppUpdate(filePath, (err) => {
|
||||
if (err) {
|
||||
logger.error('Error extracting update', err);
|
||||
url: updateAssetUrl,
|
||||
file: updateAssetName,
|
||||
cleanupOldFiles: true,
|
||||
cache: useCache,
|
||||
success: (assetFilePath) => {
|
||||
logger.info('Downloading update signatures');
|
||||
Transport.httpGet({
|
||||
url: updateUrlBasePath + 'Verify.sign.sha256',
|
||||
text: true,
|
||||
file: updateAssetName + '.sign',
|
||||
cleanupOldFiles: true,
|
||||
cache: useCache,
|
||||
success: (assetFileSignaturePath) => {
|
||||
this.verifySignature(assetFilePath, updateAssetName, (err, valid) => {
|
||||
if (err) {
|
||||
UpdateModel.set({
|
||||
updateStatus: 'error',
|
||||
updateError: 'Error verifying update signature'
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!valid) {
|
||||
UpdateModel.set({
|
||||
updateStatus: 'error',
|
||||
updateError: 'Invalid update signature'
|
||||
});
|
||||
Launcher.deleteFile(assetFilePath);
|
||||
Launcher.deleteFile(assetFileSignaturePath);
|
||||
return;
|
||||
}
|
||||
logger.info('Update is ready', assetFilePath);
|
||||
UpdateModel.set({ updateStatus: 'ready', updateError: null });
|
||||
if (!startedByUser) {
|
||||
Events.emit('update-app');
|
||||
}
|
||||
if (typeof successCallback === 'function') {
|
||||
successCallback();
|
||||
}
|
||||
});
|
||||
},
|
||||
error(e) {
|
||||
logger.error('Error downloading update signatures', e);
|
||||
UpdateModel.set({
|
||||
updateStatus: 'error',
|
||||
updateError: 'Error extracting update'
|
||||
updateError: 'Error downloading update signatures'
|
||||
});
|
||||
} else {
|
||||
UpdateModel.set({ updateStatus: 'ready', updateError: null });
|
||||
if (!startedByUser) {
|
||||
Events.emit('update-app');
|
||||
}
|
||||
if (typeof successCallback === 'function') {
|
||||
successCallback();
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
@ -215,61 +248,64 @@ const Updater = {
|
|||
});
|
||||
},
|
||||
|
||||
extractAppUpdate(updateFile, cb) {
|
||||
const expectedFiles = this.UpdateCheckFiles;
|
||||
const appPath = Launcher.getUserDataPath();
|
||||
const StreamZip = Launcher.req('node-stream-zip');
|
||||
StreamZip.setFs(Launcher.req('original-fs'));
|
||||
const zip = new StreamZip({ file: updateFile, storeEntries: true });
|
||||
zip.on('error', cb);
|
||||
zip.on('ready', () => {
|
||||
const containsAll = expectedFiles.every((expFile) => {
|
||||
const entry = zip.entry(expFile);
|
||||
return entry && entry.isFile;
|
||||
verifySignature(assetFilePath, assetName, callback) {
|
||||
logger.info('Verifying update signature', assetName);
|
||||
const fs = Launcher.req('fs');
|
||||
const signaturesTxt = fs.readFileSync(assetFilePath + '.sign', 'utf8');
|
||||
const assetSignatureLine = signaturesTxt
|
||||
.split('\n')
|
||||
.find((line) => line.endsWith(assetName));
|
||||
if (!assetSignatureLine) {
|
||||
logger.error('Signature not found for asset', assetName);
|
||||
callback('Asset signature not found');
|
||||
return;
|
||||
}
|
||||
const signature = kdbxweb.ByteUtils.hexToBytes(assetSignatureLine.split(' ')[0]);
|
||||
const fileBytes = fs.readFileSync(assetFilePath);
|
||||
SignatureVerifier.verify(fileBytes, signature)
|
||||
.catch((e) => {
|
||||
logger.error('Error verifying signature', e);
|
||||
callback('Error verifying signature');
|
||||
})
|
||||
.then((valid) => {
|
||||
logger.info(`Update asset signature is ${valid ? 'valid' : 'invalid'}`);
|
||||
callback(undefined, valid);
|
||||
});
|
||||
if (!containsAll) {
|
||||
return cb('Bad archive');
|
||||
}
|
||||
this.validateArchiveSignature(updateFile, zip)
|
||||
.then(() => {
|
||||
zip.extract(null, appPath, (err) => {
|
||||
zip.close();
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
Launcher.deleteFile(updateFile);
|
||||
cb();
|
||||
});
|
||||
})
|
||||
.catch((e) => {
|
||||
return cb('Invalid archive: ' + e);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
validateArchiveSignature(archivePath, zip) {
|
||||
if (!zip.comment) {
|
||||
return Promise.reject('No comment in ZIP');
|
||||
getUpdateAssetName(ver) {
|
||||
const platform = Launcher.platform();
|
||||
const arch = Launcher.arch();
|
||||
switch (platform) {
|
||||
case 'win32':
|
||||
switch (arch) {
|
||||
case 'x64':
|
||||
return `KeeWeb-${ver}.win.x64.exe`;
|
||||
case 'ia32':
|
||||
return `KeeWeb-${ver}.win.ia32.exe`;
|
||||
case 'arm64':
|
||||
return `KeeWeb-${ver}.win.arm64.exe`;
|
||||
}
|
||||
break;
|
||||
case 'darwin':
|
||||
switch (arch) {
|
||||
case 'x64':
|
||||
return `KeeWeb-${ver}.mac.x64.dmg`;
|
||||
case 'arm64':
|
||||
return `KeeWeb-${ver}.mac.arm64.dmg`;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (zip.comment.length !== 512) {
|
||||
return Promise.reject('Bad comment length in ZIP: ' + zip.comment.length);
|
||||
}
|
||||
try {
|
||||
const zipFileData = Launcher.req('fs').readFileSync(archivePath);
|
||||
const dataToVerify = zipFileData.slice(0, zip.centralDirectory.headerOffset + 22);
|
||||
const signature = window.Buffer.from(zip.comment, 'hex');
|
||||
return SignatureVerifier.verify(dataToVerify, signature)
|
||||
.catch(() => {
|
||||
throw new Error('Error verifying signature');
|
||||
})
|
||||
.then((isValid) => {
|
||||
if (!isValid) {
|
||||
throw new Error('Invalid signature');
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
return Promise.reject(err.toString());
|
||||
return undefined;
|
||||
},
|
||||
|
||||
installAndRestart() {
|
||||
if (!Launcher) {
|
||||
return;
|
||||
}
|
||||
const updateAssetName = this.getUpdateAssetName(UpdateModel.lastVersion);
|
||||
const updateFilePath = Transport.cacheFilePath(updateAssetName);
|
||||
Launcher.requestRestartAndUpdate(updateFilePath);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -1,15 +1,33 @@
|
|||
import { Launcher } from 'comp/launcher';
|
||||
import { Logger } from 'util/logger';
|
||||
import { noop } from 'util/fn';
|
||||
import { StringFormat } from 'util/formatting/string-format';
|
||||
|
||||
const logger = new Logger('transport');
|
||||
|
||||
const Transport = {
|
||||
cacheFilePath(fileName) {
|
||||
return Launcher.getTempPath(fileName);
|
||||
},
|
||||
|
||||
httpGet(config) {
|
||||
let tmpFile;
|
||||
const fs = Launcher.req('fs');
|
||||
if (config.file) {
|
||||
tmpFile = Launcher.getTempPath(config.file);
|
||||
const baseTempPath = Launcher.getTempPath();
|
||||
if (config.cleanupOldFiles) {
|
||||
const allFiles = fs.readdirSync(baseTempPath);
|
||||
for (const file of allFiles) {
|
||||
if (
|
||||
file !== config.file &&
|
||||
StringFormat.replaceVersion(file, '0') ===
|
||||
StringFormat.replaceVersion(config.file, '0')
|
||||
) {
|
||||
fs.unlinkSync(Launcher.joinPath(baseTempPath, file));
|
||||
}
|
||||
}
|
||||
}
|
||||
tmpFile = Launcher.joinPath(baseTempPath, config.file);
|
||||
if (fs.existsSync(tmpFile)) {
|
||||
try {
|
||||
if (config.cache && fs.statSync(tmpFile).size > 0) {
|
||||
|
@ -62,8 +80,10 @@ const Transport = {
|
|||
});
|
||||
res.on('end', () => {
|
||||
data = window.Buffer.concat(data);
|
||||
if (config.json) {
|
||||
if (config.text || config.json) {
|
||||
data = data.toString('utf8');
|
||||
}
|
||||
if (config.json) {
|
||||
try {
|
||||
data = JSON.parse(data);
|
||||
} catch (e) {
|
||||
|
|
|
@ -17,6 +17,9 @@ const Launcher = {
|
|||
platform() {
|
||||
return process.platform;
|
||||
},
|
||||
arch() {
|
||||
return process.arch;
|
||||
},
|
||||
electron() {
|
||||
return this.req('electron');
|
||||
},
|
||||
|
@ -55,7 +58,15 @@ const Launcher = {
|
|||
return this.joinPath(this.userDataPath, fileName || '');
|
||||
},
|
||||
getTempPath(fileName) {
|
||||
return this.joinPath(this.remoteApp().getPath('temp'), fileName || '');
|
||||
let tempPath = this.joinPath(this.remoteApp().getPath('temp'), 'KeeWeb');
|
||||
const fs = this.req('fs');
|
||||
if (!fs.existsSync(tempPath)) {
|
||||
fs.mkdirSync(tempPath);
|
||||
}
|
||||
if (fileName) {
|
||||
tempPath = this.joinPath(tempPath, fileName);
|
||||
}
|
||||
return tempPath;
|
||||
},
|
||||
getDocumentsPath(fileName) {
|
||||
return this.joinPath(this.remoteApp().getPath('documents'), fileName || '');
|
||||
|
@ -164,18 +175,18 @@ const Launcher = {
|
|||
requestExit() {
|
||||
const app = this.remoteApp();
|
||||
app.setHookBeforeQuitEvent(false);
|
||||
if (this.restartPending) {
|
||||
app.restartApp();
|
||||
if (this.pendingUpdateFile) {
|
||||
app.restartAndUpdate(this.pendingUpdateFile);
|
||||
} else {
|
||||
app.quit();
|
||||
}
|
||||
},
|
||||
requestRestart() {
|
||||
this.restartPending = true;
|
||||
requestRestartAndUpdate(updateFilePath) {
|
||||
this.pendingUpdateFile = updateFilePath;
|
||||
this.requestExit();
|
||||
},
|
||||
cancelRestart() {
|
||||
this.restartPending = false;
|
||||
this.pendingUpdateFile = undefined;
|
||||
},
|
||||
setClipboardText(text) {
|
||||
return this.electron().clipboard.writeText(text);
|
||||
|
|
|
@ -7,7 +7,7 @@ const Links = {
|
|||
License: 'https://github.com/keeweb/keeweb/blob/master/LICENSE',
|
||||
LicenseApache: 'https://opensource.org/licenses/Apache-2.0',
|
||||
LicenseLinkCCBY40: 'https://creativecommons.org/licenses/by/4.0/',
|
||||
UpdateDesktop: 'https://github.com/keeweb/keeweb/releases/download/v{ver}/UpdateDesktop.zip',
|
||||
UpdateBasePath: 'https://github.com/keeweb/keeweb/releases/download/v{ver}/',
|
||||
ReleaseNotes: 'https://github.com/keeweb/keeweb/blob/master/release-notes.md#release-notes',
|
||||
SelfHostedDropbox: 'https://github.com/keeweb/keeweb#self-hosting',
|
||||
UpdateJson: 'https://app.keeweb.info/update.json',
|
||||
|
|
|
@ -373,7 +373,7 @@
|
|||
"setGenExtractingUpdate": "Extracting update...",
|
||||
"setGenCheckErr": "There was an error downloading new version",
|
||||
"setGenNeverChecked": "Never checked for updates",
|
||||
"setGenRestartToUpdate": "Restart the app to update",
|
||||
"setGenRestartToUpdate": "Restart KeeWeb to update",
|
||||
"setGenDownloadAndRestart": "Download update and restart",
|
||||
"setGenAppearance": "Appearance",
|
||||
"setGenTheme": "Theme",
|
||||
|
|
|
@ -29,6 +29,10 @@ const StringFormat = {
|
|||
|
||||
pascalCase(str) {
|
||||
return this.capFirst(str.replace(this.camelCaseRegex, (match) => match[1].toUpperCase()));
|
||||
},
|
||||
|
||||
replaceVersion(str, replacement) {
|
||||
return str.replace(/\d+\.\d+\.\d+/g, replacement);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -55,7 +55,7 @@ class SettingsGeneralView extends View {
|
|||
'changeFieldLabelDblClickAutoType',
|
||||
'change .settings__general-titlebar-style': 'changeTitlebarStyle',
|
||||
'click .settings__general-update-btn': 'checkUpdate',
|
||||
'click .settings__general-restart-btn': 'restartApp',
|
||||
'click .settings__general-restart-btn': 'installUpdateAndRestart',
|
||||
'click .settings__general-download-update-btn': 'downloadUpdate',
|
||||
'click .settings__general-update-found-btn': 'installFoundUpdate',
|
||||
'change .settings__general-prv-check': 'changeStorageEnabled',
|
||||
|
@ -421,9 +421,9 @@ class SettingsGeneralView extends View {
|
|||
Events.emit('refresh');
|
||||
}
|
||||
|
||||
restartApp() {
|
||||
installUpdateAndRestart() {
|
||||
if (Launcher) {
|
||||
Launcher.requestRestart();
|
||||
Updater.installAndRestart();
|
||||
} else {
|
||||
window.location.reload();
|
||||
}
|
||||
|
@ -435,7 +435,7 @@ class SettingsGeneralView extends View {
|
|||
|
||||
installFoundUpdate() {
|
||||
Updater.update(true, () => {
|
||||
Launcher.requestRestart();
|
||||
Updater.installAndRestart();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -37,7 +37,6 @@
|
|||
{{#if isDesktop}}
|
||||
<h3>Desktop modules</h3>
|
||||
<ul>
|
||||
<li><a href="https://github.com/antelle/node-stream-zip" target="_blank">node-stream-zip</a><span class="muted-color">, node.js library for fast reading of large ZIPs, © 2015 Antelle</span></li>
|
||||
<li><a href="https://github.com/ranisalt/node-argon2" target="_blank">node-argon2</a><span class="muted-color">, node.js bindings for Argon2 hashing algorithm, © 2015 Ranieri Althoff</span></li>
|
||||
<li><a href="https://github.com/tessel/node-usb" target="_blank">node-usb</a><span class="muted-color">, improved USB library for Node.js, © 2012 Nonolith Labs, LLC</span></li>
|
||||
<li><a href="https://github.com/atom/node-keytar" target="_blank">node-keytar</a><span class="muted-color">, native password node module, © 2013 GitHub Inc.</span></li>
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
module.exports = function (grunt) {
|
||||
grunt.registerMultiTask('sign-archive', 'Signs archive with a private key', function () {
|
||||
const done = this.async();
|
||||
const fs = require('fs');
|
||||
const sign = require('../util/sign');
|
||||
const file = fs.readFileSync(this.options().file);
|
||||
const ix = file.toString('binary').lastIndexOf(this.options().signature);
|
||||
if (ix < 0) {
|
||||
grunt.warn('Signature placeholder not found');
|
||||
return;
|
||||
}
|
||||
const data = file.slice(0, ix);
|
||||
sign(grunt, data).then((signature) => {
|
||||
signature = Buffer.from(signature.toString('hex'), 'binary');
|
||||
if (
|
||||
signature.byteLength !== Buffer.from(this.options().signature, 'binary').byteLength
|
||||
) {
|
||||
grunt.warn('Bad signature length');
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < signature.byteLength; i++) {
|
||||
file[ix + i] = signature[i];
|
||||
}
|
||||
fs.writeFileSync(this.options().file, file);
|
||||
done();
|
||||
});
|
||||
});
|
||||
};
|
|
@ -1,63 +0,0 @@
|
|||
module.exports = function (grunt) {
|
||||
grunt.registerMultiTask(
|
||||
'validate-desktop-update',
|
||||
'Validates desktop update package',
|
||||
function () {
|
||||
const path = require('path');
|
||||
const crypto = require('crypto');
|
||||
const fs = require('fs');
|
||||
const done = this.async();
|
||||
const StreamZip = require(path.resolve(
|
||||
__dirname,
|
||||
'../../desktop/node_modules/node-stream-zip'
|
||||
));
|
||||
const zip = new StreamZip({ file: this.options().file, storeEntries: true });
|
||||
const expFiles = this.options().expected;
|
||||
const expFilesCount = this.options().expectedCount;
|
||||
const publicKey = fs.readFileSync(this.options().publicKey, 'binary');
|
||||
const zipFileData = fs.readFileSync(this.options().file);
|
||||
zip.on('error', (err) => {
|
||||
grunt.warn(err);
|
||||
});
|
||||
zip.on('ready', () => {
|
||||
let valid = true;
|
||||
if (!zip.comment) {
|
||||
grunt.warn('No comment in ZIP');
|
||||
return;
|
||||
}
|
||||
if (zip.comment.length !== 512) {
|
||||
grunt.warn('Bad comment length in ZIP');
|
||||
return;
|
||||
}
|
||||
const verify = crypto.createVerify('RSA-SHA256');
|
||||
verify.write(zipFileData.slice(0, zip.centralDirectory.headerOffset + 22));
|
||||
verify.end();
|
||||
const signature = Buffer.from(zip.comment, 'hex');
|
||||
if (!verify.verify(publicKey, signature)) {
|
||||
grunt.warn('Invalid ZIP signature');
|
||||
return;
|
||||
}
|
||||
if (zip.entriesCount !== expFilesCount) {
|
||||
grunt.warn(
|
||||
`ZIP contains ${zip.entriesCount} entries, expected ${expFilesCount}`
|
||||
);
|
||||
valid = false;
|
||||
}
|
||||
expFiles.forEach((entry) => {
|
||||
try {
|
||||
if (!zip.entryDataSync(entry)) {
|
||||
grunt.warn('Corrupted entry in desktop update archive: ' + entry);
|
||||
valid = false;
|
||||
}
|
||||
} catch (e) {
|
||||
grunt.warn('Entry not found in desktop update archive: ' + entry);
|
||||
valid = false;
|
||||
}
|
||||
});
|
||||
if (valid) {
|
||||
done();
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
|
@ -13,7 +13,7 @@ let mainWindow = null;
|
|||
let appIcon = null;
|
||||
let ready = false;
|
||||
let appReady = false;
|
||||
let restartPending = false;
|
||||
let pendingUpdateFilePath;
|
||||
let mainWindowPosition = {};
|
||||
let updateMainWindowPositionTimeout = null;
|
||||
let mainWindowMaximized = false;
|
||||
|
@ -95,9 +95,8 @@ const settingsPromise = loadSettingsEncryptionKey().then((key) => {
|
|||
});
|
||||
|
||||
main.on('window-all-closed', () => {
|
||||
if (restartPending) {
|
||||
main.relaunch();
|
||||
main.exit(0);
|
||||
if (pendingUpdateFilePath) {
|
||||
exitAndStartUpdate();
|
||||
} else {
|
||||
if (process.platform !== 'darwin') {
|
||||
main.quit();
|
||||
|
@ -160,11 +159,11 @@ main.on('web-contents-created', (event, contents) => {
|
|||
}
|
||||
});
|
||||
});
|
||||
main.restartApp = function () {
|
||||
restartPending = true;
|
||||
main.restartAndUpdate = function (updateFilePath) {
|
||||
pendingUpdateFilePath = updateFilePath;
|
||||
mainWindow.close();
|
||||
setTimeout(() => {
|
||||
restartPending = false;
|
||||
pendingUpdateFilePath = undefined;
|
||||
}, 1000);
|
||||
};
|
||||
main.minimizeApp = function (menuItemLabels) {
|
||||
|
@ -947,3 +946,10 @@ function httpRequest(config, log, onLoad) {
|
|||
}
|
||||
req.end();
|
||||
}
|
||||
|
||||
function exitAndStartUpdate() {
|
||||
if (pendingUpdateFilePath) {
|
||||
// TODO: install the update
|
||||
main.exit(0);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
{
|
||||
"name": "KeeWeb",
|
||||
"version": "1.16.7",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"node-stream-zip": {
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/node-stream-zip/-/node-stream-zip-1.4.2.tgz",
|
||||
"integrity": "sha512-9WbviH8qi0HgHmHbQleydUZToH3vNlN83CyOrhrwGaPzmBPH1SUYUkFyHjWl6GWMMODlM81qgycZEfWm267gKA=="
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,9 +14,5 @@
|
|||
"url": "http://antelle.net"
|
||||
},
|
||||
"license": "MIT",
|
||||
"readme": "../README.md",
|
||||
"dependencies": {
|
||||
"node-stream-zip": "^1.4.2"
|
||||
},
|
||||
"devDependencies": {}
|
||||
"readme": "../README.md"
|
||||
}
|
||||
|
|
|
@ -24,7 +24,6 @@ module.exports = function(grunt) {
|
|||
'clean:desktop',
|
||||
'build-desktop-app-content',
|
||||
'build-desktop-executables-linux',
|
||||
'build-desktop-update',
|
||||
'build-desktop-archives-linux',
|
||||
'build-desktop-dist-linux'
|
||||
]);
|
||||
|
|
|
@ -24,15 +24,6 @@ module.exports = function (grunt) {
|
|||
'string-replace:desktop-public-key'
|
||||
]);
|
||||
|
||||
grunt.registerTask('build-desktop-update', [
|
||||
'copy:desktop-update',
|
||||
'copy:desktop-update-helper',
|
||||
'sign-desktop-files:desktop-update',
|
||||
'compress:desktop-update',
|
||||
'sign-archive:desktop-update',
|
||||
'validate-desktop-update'
|
||||
]);
|
||||
|
||||
grunt.registerTask('build-desktop-executables-linux', [
|
||||
'electron:linux',
|
||||
'chmod:linux-desktop-x64',
|
||||
|
@ -126,7 +117,6 @@ module.exports = function (grunt) {
|
|||
'clean:desktop',
|
||||
'build-desktop-app-content',
|
||||
'build-desktop-executables',
|
||||
'build-desktop-update',
|
||||
'build-desktop-archives',
|
||||
'build-desktop-dist',
|
||||
'sign-dist'
|
||||
|
|
|
@ -11412,11 +11412,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node-stream-zip": {
|
||||
"version": "1.12.0",
|
||||
"resolved": "https://registry.npmjs.org/node-stream-zip/-/node-stream-zip-1.12.0.tgz",
|
||||
"integrity": "sha512-HZ3XehqShTFj9gHauRJ3Bri9eiCTOII7/crtXzURtT14NdnOFs9Ia5E82W7z3izVBNx760tqwddxrBJVG52Y1Q=="
|
||||
},
|
||||
"noop-logger": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/noop-logger/-/noop-logger-0.1.1.tgz",
|
||||
|
|
|
@ -76,7 +76,6 @@
|
|||
"morphdom": "^2.6.1",
|
||||
"node-fetch": "^2.6.1",
|
||||
"node-sass": "^5.0.0",
|
||||
"node-stream-zip": "1.12.0",
|
||||
"normalize.css": "8.0.1",
|
||||
"optimize-css-assets-webpack-plugin": "^5.0.4",
|
||||
"pikaday": "1.8.2",
|
||||
|
@ -109,7 +108,6 @@
|
|||
"scripts": {
|
||||
"start": "grunt",
|
||||
"test": "grunt test",
|
||||
"postinstall": "cd desktop && npm install",
|
||||
"build-beta": "grunt --beta && cp dist/index.html ../keeweb-beta/index.html && cd ../keeweb-beta && git add index.html && git commit -a -m 'beta' && git push origin master",
|
||||
"electron": "cross-env KEEWEB_IS_PORTABLE=0 ELECTRON_DISABLE_SECURITY_WARNINGS=1 KEEWEB_HTML_PATH=http://localhost:8085 electron desktop --no-sandbox",
|
||||
"dev": "grunt dev",
|
||||
|
|
|
@ -29,4 +29,10 @@ describe('StringFormat', () => {
|
|||
it('should convert kebab case to pascal case', () => {
|
||||
expect(StringFormat.pascalCase('aa-bbb-c')).to.eql('AaBbbC');
|
||||
});
|
||||
|
||||
it('should replace version', () => {
|
||||
expect(StringFormat.replaceVersion('KeeWeb-1.11.123.x64.dmg', 'ver')).to.eql(
|
||||
'KeeWeb-ver.x64.dmg'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue