mirror of https://github.com/keeweb/keeweb.git
disabled desktop update archives
This commit is contained in:
parent
bee96ebf15
commit
75fa636366
|
@ -95,11 +95,6 @@ jobs:
|
||||||
with:
|
with:
|
||||||
name: KeeWeb-${{ steps.get_tag.outputs.tag }}.linux.x86_64.rpm
|
name: KeeWeb-${{ steps.get_tag.outputs.tag }}.linux.x86_64.rpm
|
||||||
path: dist/desktop/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:
|
darwin:
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
|
@ -124,9 +119,6 @@ jobs:
|
||||||
path: dist
|
path: dist
|
||||||
- name: Install npm modules
|
- name: Install npm modules
|
||||||
run: npm ci
|
run: npm ci
|
||||||
- name: Install desktop npm modules
|
|
||||||
working-directory: desktop
|
|
||||||
run: npm ci
|
|
||||||
- name: Install grunt
|
- name: Install grunt
|
||||||
run: sudo npm i -g grunt-cli
|
run: sudo npm i -g grunt-cli
|
||||||
- name: Write secrets
|
- name: Write secrets
|
||||||
|
@ -179,9 +171,6 @@ jobs:
|
||||||
path: dist
|
path: dist
|
||||||
- name: Install npm modules
|
- name: Install npm modules
|
||||||
run: npm ci
|
run: npm ci
|
||||||
- name: Install desktop npm modules
|
|
||||||
working-directory: desktop
|
|
||||||
run: npm ci
|
|
||||||
- name: Install grunt
|
- name: Install grunt
|
||||||
run: npm i -g grunt-cli
|
run: npm i -g grunt-cli
|
||||||
- name: Write secrets
|
- name: Write secrets
|
||||||
|
@ -328,11 +317,6 @@ jobs:
|
||||||
with:
|
with:
|
||||||
name: KeeWeb-${{ steps.get_tag.outputs.tag }}.win.arm64.zip
|
name: KeeWeb-${{ steps.get_tag.outputs.tag }}.win.arm64.zip
|
||||||
path: assets
|
path: assets
|
||||||
- name: Download update artifact
|
|
||||||
uses: actions/download-artifact@v1
|
|
||||||
with:
|
|
||||||
name: UpdateDesktop.zip
|
|
||||||
path: assets
|
|
||||||
- name: Zip html
|
- name: Zip html
|
||||||
working-directory: html
|
working-directory: html
|
||||||
run: zip -vr ../assets/KeeWeb-${{ steps.get_tag.outputs.tag }}.html.zip .
|
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_path: assets/KeeWeb-${{ steps.get_tag.outputs.tag }}.win.arm64.zip
|
||||||
asset_name: 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
|
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
|
- name: Upload verify.sign asset
|
||||||
uses: actions/upload-release-asset@v1
|
uses: actions/upload-release-asset@v1
|
||||||
env:
|
env:
|
||||||
|
|
53
Gruntfile.js
53
Gruntfile.js
|
@ -138,18 +138,6 @@ module.exports = function (grunt) {
|
||||||
expand: true,
|
expand: true,
|
||||||
nonull: 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': {
|
'desktop-darwin-helper-x64': {
|
||||||
src: 'helper/darwin/KeeWebHelper',
|
src: 'helper/darwin/KeeWebHelper',
|
||||||
dest: 'tmp/desktop/KeeWeb-darwin-x64/KeeWeb.app/Contents/Resources/',
|
dest: 'tmp/desktop/KeeWeb-darwin-x64/KeeWeb.app/Contents/Resources/',
|
||||||
|
@ -461,13 +449,6 @@ module.exports = function (grunt) {
|
||||||
options: {
|
options: {
|
||||||
level: 6
|
level: 6
|
||||||
},
|
},
|
||||||
'desktop-update': {
|
|
||||||
options: {
|
|
||||||
archive: 'dist/desktop/UpdateDesktop.zip',
|
|
||||||
comment: zipCommentPlaceholder
|
|
||||||
},
|
|
||||||
files: [{ cwd: 'tmp/desktop/update', src: '**', expand: true, nonull: true }]
|
|
||||||
},
|
|
||||||
'win32-x64': {
|
'win32-x64': {
|
||||||
options: { archive: `dist/desktop/KeeWeb-${pkg.version}.win.x64.zip` },
|
options: { archive: `dist/desktop/KeeWeb-${pkg.version}.win.x64.zip` },
|
||||||
files: [{ cwd: 'tmp/desktop/KeeWeb-win32-x64', src: '**', expand: true }]
|
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': {
|
'osx-sign': {
|
||||||
options: {
|
options: {
|
||||||
get identity() {
|
get identity() {
|
||||||
|
@ -758,10 +710,7 @@ module.exports = function (grunt) {
|
||||||
sign: 'dist/desktop/Verify.sign.sha256'
|
sign: 'dist/desktop/Verify.sign.sha256'
|
||||||
},
|
},
|
||||||
files: {
|
files: {
|
||||||
'dist/desktop/Verify.sha256': [
|
'dist/desktop/Verify.sha256': ['dist/desktop/KeeWeb-*']
|
||||||
'dist/desktop/KeeWeb-*',
|
|
||||||
'dist/desktop/UpdateDesktop.zip'
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import kdbxweb from 'kdbxweb';
|
||||||
import { Events } from 'framework/events';
|
import { Events } from 'framework/events';
|
||||||
import { RuntimeInfo } from 'const/runtime-info';
|
import { RuntimeInfo } from 'const/runtime-info';
|
||||||
import { Transport } from 'comp/browser/transport';
|
import { Transport } from 'comp/browser/transport';
|
||||||
|
@ -15,7 +16,6 @@ const Updater = {
|
||||||
UpdateInterval: 1000 * 60 * 60 * 24,
|
UpdateInterval: 1000 * 60 * 60 * 24,
|
||||||
MinUpdateTimeout: 500,
|
MinUpdateTimeout: 500,
|
||||||
MinUpdateSize: 10000,
|
MinUpdateSize: 10000,
|
||||||
UpdateCheckFiles: ['app.asar'],
|
|
||||||
nextCheckTimeout: null,
|
nextCheckTimeout: null,
|
||||||
updateCheckDate: new Date(0),
|
updateCheckDate: new Date(0),
|
||||||
enabled: Launcher && Launcher.updaterEnabled(),
|
enabled: Launcher && Launcher.updaterEnabled(),
|
||||||
|
@ -34,7 +34,7 @@ const Updater = {
|
||||||
updateInProgress() {
|
updateInProgress() {
|
||||||
return (
|
return (
|
||||||
UpdateModel.status === 'checking' ||
|
UpdateModel.status === 'checking' ||
|
||||||
['downloading', 'extracting'].indexOf(UpdateModel.updateStatus) >= 0
|
['downloading', 'extracting', 'updating'].indexOf(UpdateModel.updateStatus) >= 0
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -180,21 +180,46 @@ const Updater = {
|
||||||
}
|
}
|
||||||
UpdateModel.set({ updateStatus: 'downloading', updateError: null });
|
UpdateModel.set({ updateStatus: 'downloading', updateError: null });
|
||||||
logger.info('Downloading update', ver);
|
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({
|
Transport.httpGet({
|
||||||
url: Links.UpdateDesktop.replace('{ver}', ver),
|
url: updateAssetUrl,
|
||||||
file: 'KeeWeb-' + ver + '.zip',
|
file: updateAssetName,
|
||||||
cache: !startedByUser,
|
cleanupOldFiles: true,
|
||||||
success: (filePath) => {
|
cache: useCache,
|
||||||
UpdateModel.set({ updateStatus: 'extracting' });
|
success: (assetFilePath) => {
|
||||||
logger.info('Extracting update file', this.UpdateCheckFiles, filePath);
|
logger.info('Downloading update signatures');
|
||||||
this.extractAppUpdate(filePath, (err) => {
|
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) {
|
if (err) {
|
||||||
logger.error('Error extracting update', err);
|
|
||||||
UpdateModel.set({
|
UpdateModel.set({
|
||||||
updateStatus: 'error',
|
updateStatus: 'error',
|
||||||
updateError: 'Error extracting update'
|
updateError: 'Error verifying update signature'
|
||||||
});
|
});
|
||||||
} else {
|
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 });
|
UpdateModel.set({ updateStatus: 'ready', updateError: null });
|
||||||
if (!startedByUser) {
|
if (!startedByUser) {
|
||||||
Events.emit('update-app');
|
Events.emit('update-app');
|
||||||
|
@ -202,6 +227,14 @@ const Updater = {
|
||||||
if (typeof successCallback === 'function') {
|
if (typeof successCallback === 'function') {
|
||||||
successCallback();
|
successCallback();
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
error(e) {
|
||||||
|
logger.error('Error downloading update signatures', e);
|
||||||
|
UpdateModel.set({
|
||||||
|
updateStatus: 'error',
|
||||||
|
updateError: 'Error downloading update signatures'
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -215,61 +248,64 @@ const Updater = {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
extractAppUpdate(updateFile, cb) {
|
verifySignature(assetFilePath, assetName, callback) {
|
||||||
const expectedFiles = this.UpdateCheckFiles;
|
logger.info('Verifying update signature', assetName);
|
||||||
const appPath = Launcher.getUserDataPath();
|
const fs = Launcher.req('fs');
|
||||||
const StreamZip = Launcher.req('node-stream-zip');
|
const signaturesTxt = fs.readFileSync(assetFilePath + '.sign', 'utf8');
|
||||||
StreamZip.setFs(Launcher.req('original-fs'));
|
const assetSignatureLine = signaturesTxt
|
||||||
const zip = new StreamZip({ file: updateFile, storeEntries: true });
|
.split('\n')
|
||||||
zip.on('error', cb);
|
.find((line) => line.endsWith(assetName));
|
||||||
zip.on('ready', () => {
|
if (!assetSignatureLine) {
|
||||||
const containsAll = expectedFiles.every((expFile) => {
|
logger.error('Signature not found for asset', assetName);
|
||||||
const entry = zip.entry(expFile);
|
callback('Asset signature not found');
|
||||||
return entry && entry.isFile;
|
return;
|
||||||
});
|
|
||||||
if (!containsAll) {
|
|
||||||
return cb('Bad archive');
|
|
||||||
}
|
}
|
||||||
this.validateArchiveSignature(updateFile, zip)
|
const signature = kdbxweb.ByteUtils.hexToBytes(assetSignatureLine.split(' ')[0]);
|
||||||
.then(() => {
|
const fileBytes = fs.readFileSync(assetFilePath);
|
||||||
zip.extract(null, appPath, (err) => {
|
SignatureVerifier.verify(fileBytes, signature)
|
||||||
zip.close();
|
|
||||||
if (err) {
|
|
||||||
return cb(err);
|
|
||||||
}
|
|
||||||
Launcher.deleteFile(updateFile);
|
|
||||||
cb();
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
return cb('Invalid archive: ' + 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);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
validateArchiveSignature(archivePath, zip) {
|
getUpdateAssetName(ver) {
|
||||||
if (!zip.comment) {
|
const platform = Launcher.platform();
|
||||||
return Promise.reject('No comment in ZIP');
|
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`;
|
||||||
}
|
}
|
||||||
if (zip.comment.length !== 512) {
|
break;
|
||||||
return Promise.reject('Bad comment length in ZIP: ' + zip.comment.length);
|
case 'darwin':
|
||||||
|
switch (arch) {
|
||||||
|
case 'x64':
|
||||||
|
return `KeeWeb-${ver}.mac.x64.dmg`;
|
||||||
|
case 'arm64':
|
||||||
|
return `KeeWeb-${ver}.mac.arm64.dmg`;
|
||||||
}
|
}
|
||||||
try {
|
break;
|
||||||
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');
|
|
||||||
}
|
}
|
||||||
});
|
return undefined;
|
||||||
} catch (err) {
|
},
|
||||||
return Promise.reject(err.toString());
|
|
||||||
|
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 { Launcher } from 'comp/launcher';
|
||||||
import { Logger } from 'util/logger';
|
import { Logger } from 'util/logger';
|
||||||
import { noop } from 'util/fn';
|
import { noop } from 'util/fn';
|
||||||
|
import { StringFormat } from 'util/formatting/string-format';
|
||||||
|
|
||||||
const logger = new Logger('transport');
|
const logger = new Logger('transport');
|
||||||
|
|
||||||
const Transport = {
|
const Transport = {
|
||||||
|
cacheFilePath(fileName) {
|
||||||
|
return Launcher.getTempPath(fileName);
|
||||||
|
},
|
||||||
|
|
||||||
httpGet(config) {
|
httpGet(config) {
|
||||||
let tmpFile;
|
let tmpFile;
|
||||||
const fs = Launcher.req('fs');
|
const fs = Launcher.req('fs');
|
||||||
if (config.file) {
|
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)) {
|
if (fs.existsSync(tmpFile)) {
|
||||||
try {
|
try {
|
||||||
if (config.cache && fs.statSync(tmpFile).size > 0) {
|
if (config.cache && fs.statSync(tmpFile).size > 0) {
|
||||||
|
@ -62,8 +80,10 @@ const Transport = {
|
||||||
});
|
});
|
||||||
res.on('end', () => {
|
res.on('end', () => {
|
||||||
data = window.Buffer.concat(data);
|
data = window.Buffer.concat(data);
|
||||||
if (config.json) {
|
if (config.text || config.json) {
|
||||||
data = data.toString('utf8');
|
data = data.toString('utf8');
|
||||||
|
}
|
||||||
|
if (config.json) {
|
||||||
try {
|
try {
|
||||||
data = JSON.parse(data);
|
data = JSON.parse(data);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
@ -17,6 +17,9 @@ const Launcher = {
|
||||||
platform() {
|
platform() {
|
||||||
return process.platform;
|
return process.platform;
|
||||||
},
|
},
|
||||||
|
arch() {
|
||||||
|
return process.arch;
|
||||||
|
},
|
||||||
electron() {
|
electron() {
|
||||||
return this.req('electron');
|
return this.req('electron');
|
||||||
},
|
},
|
||||||
|
@ -55,7 +58,15 @@ const Launcher = {
|
||||||
return this.joinPath(this.userDataPath, fileName || '');
|
return this.joinPath(this.userDataPath, fileName || '');
|
||||||
},
|
},
|
||||||
getTempPath(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) {
|
getDocumentsPath(fileName) {
|
||||||
return this.joinPath(this.remoteApp().getPath('documents'), fileName || '');
|
return this.joinPath(this.remoteApp().getPath('documents'), fileName || '');
|
||||||
|
@ -164,18 +175,18 @@ const Launcher = {
|
||||||
requestExit() {
|
requestExit() {
|
||||||
const app = this.remoteApp();
|
const app = this.remoteApp();
|
||||||
app.setHookBeforeQuitEvent(false);
|
app.setHookBeforeQuitEvent(false);
|
||||||
if (this.restartPending) {
|
if (this.pendingUpdateFile) {
|
||||||
app.restartApp();
|
app.restartAndUpdate(this.pendingUpdateFile);
|
||||||
} else {
|
} else {
|
||||||
app.quit();
|
app.quit();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
requestRestart() {
|
requestRestartAndUpdate(updateFilePath) {
|
||||||
this.restartPending = true;
|
this.pendingUpdateFile = updateFilePath;
|
||||||
this.requestExit();
|
this.requestExit();
|
||||||
},
|
},
|
||||||
cancelRestart() {
|
cancelRestart() {
|
||||||
this.restartPending = false;
|
this.pendingUpdateFile = undefined;
|
||||||
},
|
},
|
||||||
setClipboardText(text) {
|
setClipboardText(text) {
|
||||||
return this.electron().clipboard.writeText(text);
|
return this.electron().clipboard.writeText(text);
|
||||||
|
|
|
@ -7,7 +7,7 @@ const Links = {
|
||||||
License: 'https://github.com/keeweb/keeweb/blob/master/LICENSE',
|
License: 'https://github.com/keeweb/keeweb/blob/master/LICENSE',
|
||||||
LicenseApache: 'https://opensource.org/licenses/Apache-2.0',
|
LicenseApache: 'https://opensource.org/licenses/Apache-2.0',
|
||||||
LicenseLinkCCBY40: 'https://creativecommons.org/licenses/by/4.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',
|
ReleaseNotes: 'https://github.com/keeweb/keeweb/blob/master/release-notes.md#release-notes',
|
||||||
SelfHostedDropbox: 'https://github.com/keeweb/keeweb#self-hosting',
|
SelfHostedDropbox: 'https://github.com/keeweb/keeweb#self-hosting',
|
||||||
UpdateJson: 'https://app.keeweb.info/update.json',
|
UpdateJson: 'https://app.keeweb.info/update.json',
|
||||||
|
|
|
@ -373,7 +373,7 @@
|
||||||
"setGenExtractingUpdate": "Extracting update...",
|
"setGenExtractingUpdate": "Extracting update...",
|
||||||
"setGenCheckErr": "There was an error downloading new version",
|
"setGenCheckErr": "There was an error downloading new version",
|
||||||
"setGenNeverChecked": "Never checked for updates",
|
"setGenNeverChecked": "Never checked for updates",
|
||||||
"setGenRestartToUpdate": "Restart the app to update",
|
"setGenRestartToUpdate": "Restart KeeWeb to update",
|
||||||
"setGenDownloadAndRestart": "Download update and restart",
|
"setGenDownloadAndRestart": "Download update and restart",
|
||||||
"setGenAppearance": "Appearance",
|
"setGenAppearance": "Appearance",
|
||||||
"setGenTheme": "Theme",
|
"setGenTheme": "Theme",
|
||||||
|
|
|
@ -29,6 +29,10 @@ const StringFormat = {
|
||||||
|
|
||||||
pascalCase(str) {
|
pascalCase(str) {
|
||||||
return this.capFirst(str.replace(this.camelCaseRegex, (match) => match[1].toUpperCase()));
|
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',
|
'changeFieldLabelDblClickAutoType',
|
||||||
'change .settings__general-titlebar-style': 'changeTitlebarStyle',
|
'change .settings__general-titlebar-style': 'changeTitlebarStyle',
|
||||||
'click .settings__general-update-btn': 'checkUpdate',
|
'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-download-update-btn': 'downloadUpdate',
|
||||||
'click .settings__general-update-found-btn': 'installFoundUpdate',
|
'click .settings__general-update-found-btn': 'installFoundUpdate',
|
||||||
'change .settings__general-prv-check': 'changeStorageEnabled',
|
'change .settings__general-prv-check': 'changeStorageEnabled',
|
||||||
|
@ -421,9 +421,9 @@ class SettingsGeneralView extends View {
|
||||||
Events.emit('refresh');
|
Events.emit('refresh');
|
||||||
}
|
}
|
||||||
|
|
||||||
restartApp() {
|
installUpdateAndRestart() {
|
||||||
if (Launcher) {
|
if (Launcher) {
|
||||||
Launcher.requestRestart();
|
Updater.installAndRestart();
|
||||||
} else {
|
} else {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
}
|
}
|
||||||
|
@ -435,7 +435,7 @@ class SettingsGeneralView extends View {
|
||||||
|
|
||||||
installFoundUpdate() {
|
installFoundUpdate() {
|
||||||
Updater.update(true, () => {
|
Updater.update(true, () => {
|
||||||
Launcher.requestRestart();
|
Updater.installAndRestart();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,6 @@
|
||||||
{{#if isDesktop}}
|
{{#if isDesktop}}
|
||||||
<h3>Desktop modules</h3>
|
<h3>Desktop modules</h3>
|
||||||
<ul>
|
<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/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/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>
|
<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 appIcon = null;
|
||||||
let ready = false;
|
let ready = false;
|
||||||
let appReady = false;
|
let appReady = false;
|
||||||
let restartPending = false;
|
let pendingUpdateFilePath;
|
||||||
let mainWindowPosition = {};
|
let mainWindowPosition = {};
|
||||||
let updateMainWindowPositionTimeout = null;
|
let updateMainWindowPositionTimeout = null;
|
||||||
let mainWindowMaximized = false;
|
let mainWindowMaximized = false;
|
||||||
|
@ -95,9 +95,8 @@ const settingsPromise = loadSettingsEncryptionKey().then((key) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
main.on('window-all-closed', () => {
|
main.on('window-all-closed', () => {
|
||||||
if (restartPending) {
|
if (pendingUpdateFilePath) {
|
||||||
main.relaunch();
|
exitAndStartUpdate();
|
||||||
main.exit(0);
|
|
||||||
} else {
|
} else {
|
||||||
if (process.platform !== 'darwin') {
|
if (process.platform !== 'darwin') {
|
||||||
main.quit();
|
main.quit();
|
||||||
|
@ -160,11 +159,11 @@ main.on('web-contents-created', (event, contents) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
main.restartApp = function () {
|
main.restartAndUpdate = function (updateFilePath) {
|
||||||
restartPending = true;
|
pendingUpdateFilePath = updateFilePath;
|
||||||
mainWindow.close();
|
mainWindow.close();
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
restartPending = false;
|
pendingUpdateFilePath = undefined;
|
||||||
}, 1000);
|
}, 1000);
|
||||||
};
|
};
|
||||||
main.minimizeApp = function (menuItemLabels) {
|
main.minimizeApp = function (menuItemLabels) {
|
||||||
|
@ -947,3 +946,10 @@ function httpRequest(config, log, onLoad) {
|
||||||
}
|
}
|
||||||
req.end();
|
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"
|
"url": "http://antelle.net"
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"readme": "../README.md",
|
"readme": "../README.md"
|
||||||
"dependencies": {
|
|
||||||
"node-stream-zip": "^1.4.2"
|
|
||||||
},
|
|
||||||
"devDependencies": {}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,6 @@ module.exports = function(grunt) {
|
||||||
'clean:desktop',
|
'clean:desktop',
|
||||||
'build-desktop-app-content',
|
'build-desktop-app-content',
|
||||||
'build-desktop-executables-linux',
|
'build-desktop-executables-linux',
|
||||||
'build-desktop-update',
|
|
||||||
'build-desktop-archives-linux',
|
'build-desktop-archives-linux',
|
||||||
'build-desktop-dist-linux'
|
'build-desktop-dist-linux'
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -24,15 +24,6 @@ module.exports = function (grunt) {
|
||||||
'string-replace:desktop-public-key'
|
'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', [
|
grunt.registerTask('build-desktop-executables-linux', [
|
||||||
'electron:linux',
|
'electron:linux',
|
||||||
'chmod:linux-desktop-x64',
|
'chmod:linux-desktop-x64',
|
||||||
|
@ -126,7 +117,6 @@ module.exports = function (grunt) {
|
||||||
'clean:desktop',
|
'clean:desktop',
|
||||||
'build-desktop-app-content',
|
'build-desktop-app-content',
|
||||||
'build-desktop-executables',
|
'build-desktop-executables',
|
||||||
'build-desktop-update',
|
|
||||||
'build-desktop-archives',
|
'build-desktop-archives',
|
||||||
'build-desktop-dist',
|
'build-desktop-dist',
|
||||||
'sign-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": {
|
"noop-logger": {
|
||||||
"version": "0.1.1",
|
"version": "0.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/noop-logger/-/noop-logger-0.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/noop-logger/-/noop-logger-0.1.1.tgz",
|
||||||
|
|
|
@ -76,7 +76,6 @@
|
||||||
"morphdom": "^2.6.1",
|
"morphdom": "^2.6.1",
|
||||||
"node-fetch": "^2.6.1",
|
"node-fetch": "^2.6.1",
|
||||||
"node-sass": "^5.0.0",
|
"node-sass": "^5.0.0",
|
||||||
"node-stream-zip": "1.12.0",
|
|
||||||
"normalize.css": "8.0.1",
|
"normalize.css": "8.0.1",
|
||||||
"optimize-css-assets-webpack-plugin": "^5.0.4",
|
"optimize-css-assets-webpack-plugin": "^5.0.4",
|
||||||
"pikaday": "1.8.2",
|
"pikaday": "1.8.2",
|
||||||
|
@ -109,7 +108,6 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "grunt",
|
"start": "grunt",
|
||||||
"test": "grunt test",
|
"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",
|
"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",
|
"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",
|
"dev": "grunt dev",
|
||||||
|
|
|
@ -29,4 +29,10 @@ describe('StringFormat', () => {
|
||||||
it('should convert kebab case to pascal case', () => {
|
it('should convert kebab case to pascal case', () => {
|
||||||
expect(StringFormat.pascalCase('aa-bbb-c')).to.eql('AaBbbC');
|
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