|
|
|
'use strict';
|
|
|
|
|
|
|
|
var obsidian = require('obsidian');
|
|
|
|
|
|
|
|
/*! *****************************************************************************
|
|
|
|
Copyright (c) Microsoft Corporation.
|
|
|
|
|
|
|
|
Permission to use, copy, modify, and/or distribute this software for any
|
|
|
|
purpose with or without fee is hereby granted.
|
|
|
|
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
|
|
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
|
|
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
|
|
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
|
|
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
|
|
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
|
|
PERFORMANCE OF THIS SOFTWARE.
|
|
|
|
***************************************************************************** */
|
|
|
|
|
|
|
|
function __awaiter(thisArg, _arguments, P, generator) {
|
|
|
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
|
|
return new (P || (P = Promise))(function (resolve, reject) {
|
|
|
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
|
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
|
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
|
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
class DecryptModal extends obsidian.Modal {
|
|
|
|
constructor(app, title, text = '') {
|
|
|
|
super(app);
|
|
|
|
this.decryptInPlace = false;
|
|
|
|
this.text = text;
|
|
|
|
this.titleEl.innerText = title;
|
|
|
|
}
|
|
|
|
onOpen() {
|
|
|
|
let { contentEl } = this;
|
|
|
|
const textEl = contentEl.createDiv().createEl('textarea', { text: this.text });
|
|
|
|
textEl.style.width = '100%';
|
|
|
|
textEl.style.height = '100%';
|
|
|
|
textEl.rows = 10;
|
|
|
|
textEl.readOnly = true;
|
|
|
|
//textEl.focus(); // Doesn't seem to work here...
|
|
|
|
setTimeout(() => { textEl.focus(); }, 100); //... but this does
|
|
|
|
const btnContainerEl = contentEl.createDiv('');
|
|
|
|
const decryptInPlaceBtnEl = btnContainerEl.createEl('button', { text: 'Decrypt in-place' });
|
|
|
|
decryptInPlaceBtnEl.addEventListener('click', () => {
|
|
|
|
this.decryptInPlace = true;
|
|
|
|
this.close();
|
|
|
|
});
|
|
|
|
const cancelBtnEl = btnContainerEl.createEl('button', { text: 'Close' });
|
|
|
|
cancelBtnEl.addEventListener('click', () => {
|
|
|
|
this.close();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class PasswordModal extends obsidian.Modal {
|
|
|
|
constructor(app, confirmPassword, defaultPassword = null) {
|
|
|
|
super(app);
|
|
|
|
this.password = null;
|
|
|
|
this.defaultPassword = null;
|
|
|
|
this.defaultPassword = defaultPassword;
|
|
|
|
this.confirmPassword = confirmPassword;
|
|
|
|
}
|
|
|
|
onOpen() {
|
|
|
|
var _a, _b;
|
|
|
|
let { contentEl } = this;
|
|
|
|
contentEl.empty();
|
|
|
|
contentEl.addClass('meld-e-password');
|
|
|
|
if (obsidian.Platform.isMobile) {
|
|
|
|
contentEl.addClass('meld-e-platform-mobile');
|
|
|
|
}
|
|
|
|
else if (obsidian.Platform.isDesktop) {
|
|
|
|
contentEl.addClass('meld-e-platform-desktop');
|
|
|
|
}
|
|
|
|
/* Main password input row */
|
|
|
|
const inputPwContainerEl = contentEl.createDiv({ cls: 'meld-e-row' });
|
|
|
|
inputPwContainerEl.createSpan({ cls: 'meld-e-icon', text: '🔑' });
|
|
|
|
const pwInputEl = inputPwContainerEl.createEl('input', { type: 'password', value: (_a = this.defaultPassword) !== null && _a !== void 0 ? _a : '' });
|
|
|
|
pwInputEl.placeholder = 'Enter your password';
|
|
|
|
pwInputEl.focus();
|
|
|
|
if (obsidian.Platform.isMobile) {
|
|
|
|
// Add 'Next' button for mobile
|
|
|
|
const inputInputNextBtnEl = inputPwContainerEl.createEl('button', {
|
|
|
|
text: '→',
|
|
|
|
cls: 'meld-e-button-next'
|
|
|
|
});
|
|
|
|
inputInputNextBtnEl.addEventListener('click', (ev) => {
|
|
|
|
inputPasswordHandler();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
/* End Main password input row */
|
|
|
|
/* Confirm password input row */
|
|
|
|
const confirmPwContainerEl = contentEl.createDiv({ cls: 'meld-e-row' });
|
|
|
|
confirmPwContainerEl.createSpan({ cls: 'meld-e-icon', text: '🔑' });
|
|
|
|
const pwConfirmInputEl = confirmPwContainerEl.createEl('input', {
|
|
|
|
type: 'password',
|
|
|
|
value: (_b = this.defaultPassword) !== null && _b !== void 0 ? _b : ''
|
|
|
|
});
|
|
|
|
pwConfirmInputEl.placeholder = 'Confirm your password';
|
|
|
|
const messageEl = contentEl.createDiv({ cls: 'meld-e-message' });
|
|
|
|
messageEl.hide();
|
|
|
|
if (obsidian.Platform.isMobile) {
|
|
|
|
// Add 'Next' button for mobile
|
|
|
|
const confirmInputNextBtnEl = confirmPwContainerEl.createEl('button', {
|
|
|
|
text: '→',
|
|
|
|
cls: 'meld-e-button-next'
|
|
|
|
});
|
|
|
|
confirmInputNextBtnEl.addEventListener('click', (ev) => {
|
|
|
|
confirmPasswordHandler();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
/* End Confirm password input row */
|
|
|
|
const confirmPwButtonEl = contentEl.createEl('button', {
|
|
|
|
text: 'Confirm',
|
|
|
|
cls: 'meld-e-button-confirm'
|
|
|
|
});
|
|
|
|
confirmPwButtonEl.addEventListener('click', (ev) => {
|
|
|
|
if (this.confirmPassword) {
|
|
|
|
if (pwInputEl.value == pwConfirmInputEl.value) {
|
|
|
|
this.password = pwConfirmInputEl.value;
|
|
|
|
this.close();
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// passwords don't match
|
|
|
|
messageEl.setText('Passwords don\'t match');
|
|
|
|
messageEl.show();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
this.password = pwInputEl.value;
|
|
|
|
this.close();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
const inputPasswordHandler = () => {
|
|
|
|
if (this.confirmPassword) {
|
|
|
|
// confim password
|
|
|
|
pwConfirmInputEl.focus();
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
this.password = pwInputEl.value;
|
|
|
|
this.close();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
const confirmPasswordHandler = () => {
|
|
|
|
if (pwInputEl.value == pwConfirmInputEl.value) {
|
|
|
|
this.password = pwConfirmInputEl.value;
|
|
|
|
this.close();
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// passwords don't match
|
|
|
|
messageEl.setText('Passwords don\'t match');
|
|
|
|
messageEl.show();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
pwConfirmInputEl.addEventListener('keypress', (ev) => {
|
|
|
|
if ((ev.code === 'Enter' || ev.code === 'NumpadEnter')
|
|
|
|
&& pwConfirmInputEl.value.length > 0) {
|
|
|
|
ev.preventDefault();
|
|
|
|
confirmPasswordHandler();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
if (!this.confirmPassword) {
|
|
|
|
confirmPwContainerEl.hide();
|
|
|
|
}
|
|
|
|
pwInputEl.addEventListener('keypress', (ev) => {
|
|
|
|
if ((ev.code === 'Enter' || ev.code === 'NumpadEnter')
|
|
|
|
&& pwInputEl.value.length > 0) {
|
|
|
|
ev.preventDefault();
|
|
|
|
inputPasswordHandler();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const vectorSize = 16;
|
|
|
|
const utf8Encoder = new TextEncoder();
|
|
|
|
const utf8Decoder = new TextDecoder();
|
|
|
|
const iterations = 1000;
|
|
|
|
const salt = utf8Encoder.encode('XHWnDAT6ehMVY2zD');
|
|
|
|
class CryptoHelperV2 {
|
|
|
|
deriveKey(password) {
|
|
|
|
return __awaiter(this, void 0, void 0, function* () {
|
|
|
|
const buffer = utf8Encoder.encode(password);
|
|
|
|
const key = yield crypto.subtle.importKey('raw', buffer, { name: 'PBKDF2' }, false, ['deriveKey']);
|
|
|
|
const privateKey = crypto.subtle.deriveKey({
|
|
|
|
name: 'PBKDF2',
|
|
|
|
hash: { name: 'SHA-256' },
|
|
|
|
iterations,
|
|
|
|
salt
|
|
|
|
}, key, {
|
|
|
|
name: 'AES-GCM',
|
|
|
|
length: 256
|
|
|
|
}, false, ['encrypt', 'decrypt']);
|
|
|
|
return privateKey;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
encryptToBase64(text, password) {
|
|
|
|
return __awaiter(this, void 0, void 0, function* () {
|
|
|
|
const key = yield this.deriveKey(password);
|
|
|
|
const textBytesToEncrypt = utf8Encoder.encode(text);
|
|
|
|
const vector = crypto.getRandomValues(new Uint8Array(vectorSize));
|
|
|
|
// encrypt into bytes
|
|
|
|
const encryptedBytes = new Uint8Array(yield crypto.subtle.encrypt({ name: 'AES-GCM', iv: vector }, key, textBytesToEncrypt));
|
|
|
|
const finalBytes = new Uint8Array(vector.byteLength + encryptedBytes.byteLength);
|
|
|
|
finalBytes.set(vector, 0);
|
|
|
|
finalBytes.set(encryptedBytes, vector.byteLength);
|
|
|
|
//convert array to base64
|
|
|
|
const base64Text = btoa(String.fromCharCode(...finalBytes));
|
|
|
|
return base64Text;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
stringToArray(str) {
|
|
|
|
var result = [];
|
|
|
|
for (var i = 0; i < str.length; i++) {
|
|
|
|
result.push(str.charCodeAt(i));
|
|
|
|
}
|
|
|
|
return new Uint8Array(result);
|
|
|
|
}
|
|
|
|
decryptFromBase64(base64Encoded, password) {
|
|
|
|
return __awaiter(this, void 0, void 0, function* () {
|
|
|
|
try {
|
|
|
|
let bytesToDecode = this.stringToArray(atob(base64Encoded));
|
|
|
|
// extract iv
|
|
|
|
const vector = bytesToDecode.slice(0, vectorSize);
|
|
|
|
// extract encrypted text
|
|
|
|
const encryptedTextBytes = bytesToDecode.slice(vectorSize);
|
|
|
|
const key = yield this.deriveKey(password);
|
|
|
|
// decrypt into bytes
|
|
|
|
let decryptedBytes = yield crypto.subtle.decrypt({ name: 'AES-GCM', iv: vector }, key, encryptedTextBytes);
|
|
|
|
// convert bytes to text
|
|
|
|
let decryptedText = utf8Decoder.decode(decryptedBytes);
|
|
|
|
return decryptedText;
|
|
|
|
}
|
|
|
|
catch (e) {
|
|
|
|
//console.error(e);
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const algorithmObsolete = {
|
|
|
|
name: 'AES-GCM',
|
|
|
|
iv: new Uint8Array([196, 190, 240, 190, 188, 78, 41, 132, 15, 220, 84, 211]),
|
|
|
|
tagLength: 128
|
|
|
|
};
|
|
|
|
class CryptoHelperObsolete {
|
|
|
|
buildKey(password) {
|
|
|
|
return __awaiter(this, void 0, void 0, function* () {
|
|
|
|
let utf8Encode = new TextEncoder();
|
|
|
|
let passwordBytes = utf8Encode.encode(password);
|
|
|
|
let passwordDigest = yield crypto.subtle.digest({ name: 'SHA-256' }, passwordBytes);
|
|
|
|
let key = yield crypto.subtle.importKey('raw', passwordDigest, algorithmObsolete, false, ['encrypt', 'decrypt']);
|
|
|
|
return key;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
encryptToBase64(text, password) {
|
|
|
|
return __awaiter(this, void 0, void 0, function* () {
|
|
|
|
let key = yield this.buildKey(password);
|
|
|
|
let utf8Encode = new TextEncoder();
|
|
|
|
let bytesToEncrypt = utf8Encode.encode(text);
|
|
|
|
// encrypt into bytes
|
|
|
|
let encryptedBytes = new Uint8Array(yield crypto.subtle.encrypt(algorithmObsolete, key, bytesToEncrypt));
|
|
|
|
//convert array to base64
|
|
|
|
let base64Text = btoa(String.fromCharCode(...encryptedBytes));
|
|
|
|
return base64Text;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
stringToArray(str) {
|
|
|
|
var result = [];
|
|
|
|
for (var i = 0; i < str.length; i++) {
|
|
|
|
result.push(str.charCodeAt(i));
|
|
|
|
}
|
|
|
|
return new Uint8Array(result);
|
|
|
|
}
|
|
|
|
decryptFromBase64(base64Encoded, password) {
|
|
|
|
return __awaiter(this, void 0, void 0, function* () {
|
|
|
|
try {
|
|
|
|
// convert base 64 to array
|
|
|
|
let bytesToDecrypt = this.stringToArray(atob(base64Encoded));
|
|
|
|
let key = yield this.buildKey(password);
|
|
|
|
// decrypt into bytes
|
|
|
|
let decryptedBytes = yield crypto.subtle.decrypt(algorithmObsolete, key, bytesToDecrypt);
|
|
|
|
// convert bytes to text
|
|
|
|
let utf8Decode = new TextDecoder();
|
|
|
|
let decryptedText = utf8Decode.decode(decryptedBytes);
|
|
|
|
return decryptedText;
|
|
|
|
}
|
|
|
|
catch (e) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class MeldEncryptSettingsTab extends obsidian.PluginSettingTab {
|
|
|
|
constructor(app, plugin) {
|
|
|
|
super(app, plugin);
|
|
|
|
this.plugin = plugin;
|
|
|
|
}
|
|
|
|
display() {
|
|
|
|
let { containerEl } = this;
|
|
|
|
containerEl.empty();
|
|
|
|
containerEl.createEl('h2', { text: 'Settings for Meld Encrypt' });
|
|
|
|
new obsidian.Setting(containerEl)
|
|
|
|
.setName('Expand selection to whole line?')
|
|
|
|
.setDesc('Partial selections will get expanded to the whole line.')
|
|
|
|
.addToggle(toggle => {
|
|
|
|
toggle
|
|
|
|
.setValue(this.plugin.settings.expandToWholeLines)
|
|
|
|
.onChange((value) => __awaiter(this, void 0, void 0, function* () {
|
|
|
|
this.plugin.settings.expandToWholeLines = value;
|
|
|
|
yield this.plugin.saveSettings();
|
|
|
|
//this.updateSettingsUi();
|
|
|
|
}));
|
|
|
|
});
|
|
|
|
new obsidian.Setting(containerEl)
|
|
|
|
.setName('Confirm password?')
|
|
|
|
.setDesc('Confirm password when encrypting.')
|
|
|
|
.addToggle(toggle => {
|
|
|
|
toggle
|
|
|
|
.setValue(this.plugin.settings.confirmPassword)
|
|
|
|
.onChange((value) => __awaiter(this, void 0, void 0, function* () {
|
|
|
|
this.plugin.settings.confirmPassword = value;
|
|
|
|
yield this.plugin.saveSettings();
|
|
|
|
this.updateSettingsUi();
|
|
|
|
}));
|
|
|
|
});
|
|
|
|
new obsidian.Setting(containerEl)
|
|
|
|
.setName('Remember password?')
|
|
|
|
.setDesc('Remember the last used password for this session.')
|
|
|
|
.addToggle(toggle => {
|
|
|
|
toggle
|
|
|
|
.setValue(this.plugin.settings.rememberPassword)
|
|
|
|
.onChange((value) => __awaiter(this, void 0, void 0, function* () {
|
|
|
|
this.plugin.settings.rememberPassword = value;
|
|
|
|
yield this.plugin.saveSettings();
|
|
|
|
this.updateSettingsUi();
|
|
|
|
}));
|
|
|
|
});
|
|
|
|
this.pwTimeoutSetting = new obsidian.Setting(containerEl)
|
|
|
|
.setName(this.buildPasswordTimeoutSettingName())
|
|
|
|
.setDesc('The number of minutes to remember the last used password.')
|
|
|
|
.addSlider(slider => {
|
|
|
|
slider
|
|
|
|
.setLimits(0, 120, 5)
|
|
|
|
.setValue(this.plugin.settings.rememberPasswordTimeout)
|
|
|
|
.onChange((value) => __awaiter(this, void 0, void 0, function* () {
|
|
|
|
this.plugin.settings.rememberPasswordTimeout = value;
|
|
|
|
yield this.plugin.saveSettings();
|
|
|
|
this.updateSettingsUi();
|
|
|
|
}));
|
|
|
|
});
|
|
|
|
this.updateSettingsUi();
|
|
|
|
}
|
|
|
|
updateSettingsUi() {
|
|
|
|
this.pwTimeoutSetting.setName(this.buildPasswordTimeoutSettingName());
|
|
|
|
if (this.plugin.settings.rememberPassword) {
|
|
|
|
this.pwTimeoutSetting.settingEl.show();
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
this.pwTimeoutSetting.settingEl.hide();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
buildPasswordTimeoutSettingName() {
|
|
|
|
const value = this.plugin.settings.rememberPasswordTimeout;
|
|
|
|
let timeoutString = `${value} minutes`;
|
|
|
|
if (value == 0) {
|
|
|
|
timeoutString = 'Never forget';
|
|
|
|
}
|
|
|
|
return `Remember Password Timeout (${timeoutString})`;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const _PREFIX_OBSOLETE = '%%🔐 ';
|
|
|
|
const _PREFIX_A = '%%🔐α ';
|
|
|
|
const _SUFFIX = ' 🔐%%';
|
|
|
|
const DEFAULT_SETTINGS = {
|
|
|
|
expandToWholeLines: true,
|
|
|
|
confirmPassword: true,
|
|
|
|
rememberPassword: true,
|
|
|
|
rememberPasswordTimeout: 30
|
|
|
|
};
|
|
|
|
class MeldEncrypt extends obsidian.Plugin {
|
|
|
|
onload() {
|
|
|
|
return __awaiter(this, void 0, void 0, function* () {
|
|
|
|
yield this.loadSettings();
|
|
|
|
this.addSettingTab(new MeldEncryptSettingsTab(this.app, this));
|
|
|
|
this.addCommand({
|
|
|
|
id: 'meld-encrypt',
|
|
|
|
name: 'Encrypt/Decrypt',
|
|
|
|
editorCheckCallback: (checking, editor, view) => this.processEncryptDecryptCommand(checking, editor, view, false)
|
|
|
|
});
|
|
|
|
this.addCommand({
|
|
|
|
id: 'meld-encrypt-in-place',
|
|
|
|
name: 'Encrypt/Decrypt In-place',
|
|
|
|
editorCheckCallback: (checking, editor, view) => this.processEncryptDecryptCommand(checking, editor, view, true)
|
|
|
|
});
|
|
|
|
this.addCommand({
|
|
|
|
id: 'meld-encrypt-note',
|
|
|
|
name: 'Encrypt/Decrypt Whole Note',
|
|
|
|
editorCheckCallback: (checking, editor, view) => this.processEncryptDecryptWholeNoteCommand(checking, editor, view)
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
loadSettings() {
|
|
|
|
return __awaiter(this, void 0, void 0, function* () {
|
|
|
|
this.settings = Object.assign({}, DEFAULT_SETTINGS, yield this.loadData());
|
|
|
|
});
|
|
|
|
}
|
|
|
|
saveSettings() {
|
|
|
|
return __awaiter(this, void 0, void 0, function* () {
|
|
|
|
yield this.saveData(this.settings);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
isSettingsModalOpen() {
|
|
|
|
return document.querySelector('.mod-settings') !== null;
|
|
|
|
}
|
|
|
|
processEncryptDecryptWholeNoteCommand(checking, editor, view) {
|
|
|
|
if (checking && this.isSettingsModalOpen()) {
|
|
|
|
// Settings is open, ensures this command can show up in other
|
|
|
|
// plugins which list commands e.g. customizable-sidebar
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
const startPos = editor.offsetToPos(0);
|
|
|
|
const endPos = { line: editor.lastLine(), ch: editor.getLine(editor.lastLine()).length };
|
|
|
|
const selectionText = editor.getRange(startPos, endPos).trim();
|
|
|
|
return this.processSelection(checking, editor, selectionText, startPos, endPos, true);
|
|
|
|
}
|
|
|
|
processEncryptDecryptCommand(checking, editor, view, decryptInPlace) {
|
|
|
|
if (checking && this.isSettingsModalOpen()) {
|
|
|
|
// Settings is open, ensures this command can show up in other
|
|
|
|
// plugins which list commands e.g. customizable-sidebar
|
|
|
|
console.log('Settings screen is open');
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
let startPos = editor.getCursor('from');
|
|
|
|
let endPos = editor.getCursor('to');
|
|
|
|
if (this.settings.expandToWholeLines) {
|
|
|
|
const startLine = startPos.line;
|
|
|
|
startPos = { line: startLine, ch: 0 }; // want the start of the first line
|
|
|
|
const endLine = endPos.line;
|
|
|
|
const endLineText = editor.getLine(endLine);
|
|
|
|
endPos = { line: endLine, ch: endLineText.length }; // want the end of last line
|
|
|
|
}
|
|
|
|
const selectionText = editor.getRange(startPos, endPos);
|
|
|
|
return this.processSelection(checking, editor, selectionText, startPos, endPos, decryptInPlace);
|
|
|
|
}
|
|
|
|
analyseSelection(selectionText) {
|
|
|
|
const result = new SelectionAnalysis();
|
|
|
|
result.isEmpty = selectionText.length === 0;
|
|
|
|
result.hasObsoleteEncryptedPrefix = selectionText.startsWith(_PREFIX_OBSOLETE);
|
|
|
|
result.hasEncryptedPrefix = result.hasObsoleteEncryptedPrefix || selectionText.startsWith(_PREFIX_A);
|
|
|
|
result.hasDecryptSuffix = selectionText.endsWith(_SUFFIX);
|
|
|
|
result.containsEncryptedMarkers =
|
|
|
|
selectionText.contains(_PREFIX_OBSOLETE)
|
|
|
|
|| selectionText.contains(_PREFIX_A)
|
|
|
|
|| selectionText.contains(_SUFFIX);
|
|
|
|
result.canDecrypt = result.hasEncryptedPrefix && result.hasDecryptSuffix;
|
|
|
|
result.canEncrypt = !result.hasEncryptedPrefix && !result.containsEncryptedMarkers;
|
|
|
|
//console.debug(result);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
processSelection(checking, editor, selectionText, finalSelectionStart, finalSelectionEnd, decryptInPlace) {
|
|
|
|
const selectionAnalysis = this.analyseSelection(selectionText);
|
|
|
|
if (selectionAnalysis.isEmpty) {
|
|
|
|
if (!checking) {
|
|
|
|
new obsidian.Notice('Nothing to Encrypt.');
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (!selectionAnalysis.canDecrypt && !selectionAnalysis.canEncrypt) {
|
|
|
|
if (!checking) {
|
|
|
|
new obsidian.Notice('Unable to Encrypt or Decrypt that.');
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (checking) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
// Fetch password from user
|
|
|
|
// determine default password
|
|
|
|
const isRememberPasswordExpired = !this.settings.rememberPassword
|
|
|
|
|| (this.passwordLastUsedExpiry != null
|
|
|
|
&& Date.now() > this.passwordLastUsedExpiry);
|
|
|
|
const confirmPassword = selectionAnalysis.canEncrypt && this.settings.confirmPassword;
|
|
|
|
if (isRememberPasswordExpired || confirmPassword) {
|
|
|
|
// forget password
|
|
|
|
this.passwordLastUsed = '';
|
|
|
|
}
|
|
|
|
const pwModal = new PasswordModal(this.app, confirmPassword, this.passwordLastUsed);
|
|
|
|
pwModal.onClose = () => {
|
|
|
|
var _a;
|
|
|
|
const pw = (_a = pwModal.password) !== null && _a !== void 0 ? _a : '';
|
|
|
|
if (pw.length == 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// remember password?
|
|
|
|
if (this.settings.rememberPassword) {
|
|
|
|
this.passwordLastUsed = pw;
|
|
|
|
this.passwordLastUsedExpiry =
|
|
|
|
this.settings.rememberPasswordTimeout == 0
|
|
|
|
? null
|
|
|
|
: Date.now() + this.settings.rememberPasswordTimeout * 1000 * 60 // new expiry
|
|
|
|
;
|
|
|
|
}
|
|
|
|
if (selectionAnalysis.canEncrypt) {
|
|
|
|
this.encryptSelection(editor, selectionText, pw, finalSelectionStart, finalSelectionEnd);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if (!selectionAnalysis.hasObsoleteEncryptedPrefix) {
|
|
|
|
this.decryptSelection_a(editor, selectionText, pw, finalSelectionStart, finalSelectionEnd, decryptInPlace);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
this.decryptSelectionObsolete(editor, selectionText, pw, finalSelectionStart, finalSelectionEnd, decryptInPlace);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
pwModal.open();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
encryptSelection(editor, selectionText, password, finalSelectionStart, finalSelectionEnd) {
|
|
|
|
return __awaiter(this, void 0, void 0, function* () {
|
|
|
|
//encrypt
|
|
|
|
const crypto = new CryptoHelperV2();
|
|
|
|
const base64EncryptedText = this.addMarkers(yield crypto.encryptToBase64(selectionText, password));
|
|
|
|
editor.setSelection(finalSelectionStart, finalSelectionEnd);
|
|
|
|
editor.replaceSelection(base64EncryptedText);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
decryptSelection_a(editor, selectionText, password, selectionStart, selectionEnd, decryptInPlace) {
|
|
|
|
return __awaiter(this, void 0, void 0, function* () {
|
|
|
|
//console.log('decryptSelection_a');
|
|
|
|
// decrypt
|
|
|
|
const base64CipherText = this.removeMarkers(selectionText);
|
|
|
|
const crypto = new CryptoHelperV2();
|
|
|
|
const decryptedText = yield crypto.decryptFromBase64(base64CipherText, password);
|
|
|
|
if (decryptedText === null) {
|
|
|
|
new obsidian.Notice('❌ Decryption failed!');
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if (decryptInPlace) {
|
|
|
|
editor.setSelection(selectionStart, selectionEnd);
|
|
|
|
editor.replaceSelection(decryptedText);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
const decryptModal = new DecryptModal(this.app, '🔓', decryptedText);
|
|
|
|
decryptModal.onClose = () => {
|
|
|
|
editor.focus();
|
|
|
|
if (decryptModal.decryptInPlace) {
|
|
|
|
editor.setSelection(selectionStart, selectionEnd);
|
|
|
|
editor.replaceSelection(decryptedText);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
decryptModal.open();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
decryptSelectionObsolete(editor, selectionText, password, selectionStart, selectionEnd, decryptInPlace) {
|
|
|
|
return __awaiter(this, void 0, void 0, function* () {
|
|
|
|
//console.log('decryptSelectionObsolete');
|
|
|
|
// decrypt
|
|
|
|
const base64CipherText = this.removeMarkers(selectionText);
|
|
|
|
const crypto = new CryptoHelperObsolete();
|
|
|
|
const decryptedText = yield crypto.decryptFromBase64(base64CipherText, password);
|
|
|
|
if (decryptedText === null) {
|
|
|
|
new obsidian.Notice('❌ Decryption failed!');
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if (decryptInPlace) {
|
|
|
|
editor.setSelection(selectionStart, selectionEnd);
|
|
|
|
editor.replaceSelection(decryptedText);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
const decryptModal = new DecryptModal(this.app, '🔓', decryptedText);
|
|
|
|
decryptModal.onClose = () => {
|
|
|
|
editor.focus();
|
|
|
|
if (decryptModal.decryptInPlace) {
|
|
|
|
editor.setSelection(selectionStart, selectionEnd);
|
|
|
|
editor.replaceSelection(decryptedText);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
decryptModal.open();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
removeMarkers(text) {
|
|
|
|
if (text.startsWith(_PREFIX_A) && text.endsWith(_SUFFIX)) {
|
|
|
|
return text.replace(_PREFIX_A, '').replace(_SUFFIX, '');
|
|
|
|
}
|
|
|
|
if (text.startsWith(_PREFIX_OBSOLETE) && text.endsWith(_SUFFIX)) {
|
|
|
|
return text.replace(_PREFIX_OBSOLETE, '').replace(_SUFFIX, '');
|
|
|
|
}
|
|
|
|
return text;
|
|
|
|
}
|
|
|
|
addMarkers(text) {
|
|
|
|
if (!text.contains(_PREFIX_OBSOLETE) && !text.contains(_PREFIX_A) && !text.contains(_SUFFIX)) {
|
|
|
|
return _PREFIX_A.concat(text, _SUFFIX);
|
|
|
|
}
|
|
|
|
return text;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
class SelectionAnalysis {
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = MeldEncrypt;
|
|
|
|
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWFpbi5qcyIsInNvdXJjZXMiOlsiLi4vbm9kZV9tb2R1bGVzL3RzbGliL3RzbGliLmVzNi5qcyIsIi4uL3NyYy9EZWNyeXB0TW9kYWwudHMiLCIuLi9zcmMvUGFzc3dvcmRNb2RhbC50cyIsIi4uL3NyYy9DcnlwdG9IZWxwZXIudHMiLCIuLi9zcmMvTWVsZEVuY3J5cHRTZXR0aW5nc1RhYi50cyIsIi4uL3NyYy9tYWluLnRzIl0sInNvdXJjZXNDb250ZW50IjpbIi8qISAqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKlxyXG5Db3B5cmlnaHQgKGMpIE1pY3Jvc29mdCBDb3Jwb3JhdGlvbi5cclxuXHJcblBlcm1pc3Npb24gdG8gdXNlLCBjb3B5LCBtb2RpZnksIGFuZC9vciBkaXN0cmlidXRlIHRoaXMgc29mdHdhcmUgZm9yIGFueVxyXG5wdXJwb3NlIHdpdGggb3Igd2l0aG91dCBmZWUgaXMgaGVyZWJ5IGdyYW50ZWQuXHJcblxyXG5USEUgU09GVFdBUkUgSVMgUFJPVklERUQgXCJBUyBJU1wiIEFORCBUSEUgQVVUSE9SIERJU0NMQUlNUyBBTEwgV0FSUkFOVElFUyBXSVRIXHJcblJFR0FSRCBUTyBUSElTIFNPRlRXQVJFIElOQ0xVRElORyBBTEwgSU1QTElFRCBXQVJSQU5USUVTIE9GIE1FUkNIQU5UQUJJTElUWVxyXG5BTkQgRklUTkVTUy4gSU4gTk8gRVZFTlQgU0hBTEwgVEhFIEFVVEhPUiBCRSBMSUFCTEUgRk9SIEFOWSBTUEVDSUFMLCBESVJFQ1QsXHJcbklORElSRUNULCBPUiBDT05TRVFVRU5USUFMIERBTUFHRVMgT1IgQU5ZIERBTUFHRVMgV0hBVFNPRVZFUiBSRVNVTFRJTkcgRlJPTVxyXG5MT1NTIE9GIFVTRSwgREFUQSBPUiBQUk9GSVRTLCBXSEVUSEVSIElOIEFOIEFDVElPTiBPRiBDT05UUkFDVCwgTkVHTElHRU5DRSBPUlxyXG5PVEhFUiBUT1JUSU9VUyBBQ1RJT04sIEFSSVNJTkcgT1VUIE9GIE9SIElOIENPTk5FQ1RJT04gV0lUSCBUSEUgVVNFIE9SXHJcblBFUkZPUk1BTkNFIE9GIFRISVMgU09GVFdBUkUuXHJcbioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqICovXHJcbi8qIGdsb2JhbCBSZWZsZWN0LCBQcm9taXNlICovXHJcblxyXG52YXIgZXh0ZW5kU3RhdGljcyA9IGZ1bmN0aW9uKGQsIGIpIHtcclxuICAgIGV4dGVuZFN0YXRpY3MgPSBPYmplY3Quc2V0UHJvdG90eXBlT2YgfHxcclxuICAgICAgICAoeyBfX3Byb3RvX186IFtdIH0gaW5zdGFuY2VvZiBBcnJheSAmJiBmdW5jdGlvbiAoZCwgYikgeyBkLl9fcHJvdG9fXyA9IGI7IH0pIHx8XHJcbiAgICAgICAgZnVuY3Rpb24gKGQsIGIpIHsgZm9yICh2YXIgcCBpbiBiKSBpZiAoT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsKGIsIHApKSBkW3BdID0gYltwXTsgfTtcclxuICAgIHJldHVybiBleHRlbmRTdGF0aWNzKGQsIGIpO1xyXG59O1xyXG5cclxuZXhwb3J0IGZ1bmN0aW9uIF9fZXh0ZW5kcyhkLCBiKSB7XHJcbiAgICBpZiAodHlwZW9mIGIgIT09IFwiZnVuY3Rpb25cIiAmJiBiICE9PSBudWxsKVxyXG4gICAgICAgIHRocm93IG5ldyBUeXBlRXJyb3IoXCJDbGFzcyBleHRlbmRzIHZhbHVlIFwiICsgU3RyaW5nKGIpICsgXCIgaXMgbm90IGEgY29uc3RydWN0b3Igb3IgbnVsbFwiKTtcclxuICAgIGV4dGVuZFN0YXRpY3MoZCwgYik7XHJcbiAgICBmdW5jdGlvbiBfXygpIHsgdGhpcy5jb25zdHJ1Y3RvciA9IGQ7IH1cclxuICAgIGQucHJvdG90eXBlID0gYiA9PT0gbnVsbCA/IE9iamVjdC5jcmVhdGUoYikgOiAoX18ucHJvdG90eXBlID0gYi5wcm90b3R5cGUsIG5ldyBfXygpKTtcclxufVxyXG5cclxuZXhwb3J0IHZhciBfX2Fzc2lnbiA9IGZ1bmN0aW9uKCkge1xyXG4gICAgX19hc3NpZ24gPSBPYmplY3QuYXNzaWduIHx8IGZ1bmN0aW9uIF9fYXNzaWduKHQpIHtcclxuICAgICAgICBmb3IgKHZhciBzLCBpID0gMSwgbiA9IGFyZ3VtZW50cy5sZW5ndGg7IGkgPCBuOyBpKyspIHtcclxuICAgICAgICAgICAgcyA9IGFyZ3VtZW50c1tpXTtcclxuICAgICAgICAgICAgZm9yICh2YXIgcCBpbiBzKSBpZiAoT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsKHMsIHApKSB0W3BdID0gc1twXTtcclxuICAgICAgICB9XHJcbiAgICAgICAgcmV0dXJuIHQ7XHJcbiAgICB9XHJcbiAgICByZXR1cm4gX19hc3NpZ24uYXBwbHkodGhpcywgYXJndW1lbnRzKTtcclxufVxyXG5cclxuZXhwb3J0IGZ1bmN0aW9uIF9fcmVzdChzLCBlKSB7XHJcbiAgICB2YXIgdCA9IHt9O1xyXG4gICAgZm9yICh2YXIgcCBpbiBzKSBpZiAoT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsKHMsIHApICYmIGUuaW5kZXhPZihwKSA8IDApXHJcbiAgICAgICAgdFtwXSA9IHNbcF07XHJcbiAgICBpZiAocyAhPSBudWxsICYmIHR5cGVvZiBPYmplY3QuZ2V0T3duUHJvcGVydHlTeW1ib2xzID09PSBcImZ1bmN0aW9uXCIpXHJcbiAgICAgICAgZm9yICh2YXIgaSA9IDAsIHAgPSBPYmplY3QuZ2V0T3duUHJvcGVydHlTeW1ib2xzKHMpOyBpIDwgcC5sZW5ndGg7IGkrKykge1xyXG4gICAgICAgICAgICBpZiAoZS5pbmRleE9mKHBbaV0pIDwgMCAmJiBPYmplY3QucHJvdG90eXBlLnByb3BlcnR5SXNFbnVtZXJhYmxlLmNhbGwocywgcFtpXSkpXHJcbiAgICAgICAgICAgICAgICB0W3BbaV1dID0gc1twW2ldXTtcclxuICAgICAgICB9XHJcbiAgICByZXR1cm4gdDtcclxufVxyXG5cclxuZXhwb3J0IGZ1bmN0aW9uIF9fZGVjb3JhdGUoZGVjb3JhdG9ycywgdGFyZ2V0LCBrZXksIGRlc2MpIHtcclxuICAgIHZhciBjID0gYXJndW1lbnRzLmxlbmd0aCwgciA9IGMgPCAzID8gdGFyZ2V0IDogZGVzYyA9PT0gbnVsbCA/IGRlc2MgPSBPYmplY3QuZ2V0T3duUHJvcGVydHlEZXNjcmlwdG9yKHRhcmdldCwga2V5KSA6IGRlc2MsIGQ7XHJcbiAgICBpZiAodHlwZW9mIFJlZmxlY3QgPT09IFwib2JqZWN0XCIgJiYgdHlwZW9mIFJlZmxlY3QuZGVjb3JhdGUgPT09IFwiZnVuY3Rpb25cIikgciA9IFJlZmxlY3QuZGVjb3JhdGUoZGVjb3JhdG9ycyw
|