Hello Sam, I've made some more testing and fixes and I think v2 is ready to use. I haven't updated docs yet, so it's not published in the main repository, but if you try it and works for you, let me know. By the way, have you tried the fixed suggested above?
/***
|Name |EncryptionPlugin|
|Description |Adds AES encryption and password protection and several macros to work with them|
|Version |2.0.3|
Unlike v1.x, v2.x only requires installing as usual (import/copy the plugin with the {{{systemConfig}}} tag, save and reload).
!Upgrading from 1.x
Although it works smoothly, it is recommended to create a backup before upgrading from 1.x.
After upgrading, manual removal of the old vault is recommended: open TW with a text editor, find {{{<!--POST-SHADOWAREA-->}}}, and remove the 2 lines after it (one starting with {{{<div id="vaultArea">}}} and the other being {{{<!--POST-VAULTAREA-->}}}).
!Migration from ~EncryptedVaultPlugin
TODO: describe how to and backward compatibility
{{PoG{test thoroughly (including, DefaultTiddlers, first run of the plugin, macros appearence and usage, data format compatibility, including unencrypted, ...; .oO MVP for ...) and update metadata}}}
Compared to ~EncryptedVaultPlugin, this plugin:
* Uses a stonger encryption (AES);
* Fixes various major issues, uses decorators instead of overridings of several core functions;
* Uses a customizable StyleSheetVault shadow for styles;
* Improves the UI for password (doesn't show the typed value, adds autofocus, supports pressing enter to apply).
!Usage
* Use <<unlock>><<setPassword>> button (available by default in SideBarOptions)
* Use a blank password to save unencrypted (disable vault usage)
* Use {{{unencrypted}}} tag to avoid encryption for some tiddler
* Use {{{forceEncryption}}} tag to force some shadow tiddler to be encrypted
* TODO: describe other macros
!Description
* Create an ''encrypted vault'' where all tiddlers are ''password protected''.
* By default, only the system tiddlers (shadow ones and those tagged {{{systemConfig}}}) aren't encrypted. Also, if you create [[ListUnencrypted]] and put a [[filter|classic.tiddlywiki.com#Filters]] there, the filtered tiddlers won't be encrypted, too.
* Even shadow tiddlers (MainMenu, SiteTitle, PageTemplate, StyleSheet, ...) ''can be encrypted''. The shadow version is used until unlocking.
!Configuration
The following macros are available:
* {{{<<unlock ButtonTitle ButtonTooltip OpenTiddlersWhenUnlock CloseTiddlersWhenUnlock>>}}} creates a button to unlock the encrypted vault (all parameters are optional)
* {{{<<setPassword ButtonTitle ButtonTooltip>>}}} if unlocked, creates a button to set the current password (all parameters are optional)
* {{{<<purge ButtonTitle ButtonTooltip>>}}} if locked, creates a button to purge a locked vault, useful for lost password (encrypted content is the deleted)
* {{{<<ifLocked tiddlyText>>}}} displays tiddlyText (wikified) if the vault is locked
* {{{<<ifUnlocked tiddlyText>>}}} displays tiddlyText (wikified) if the vault is unlocked
<<ifLocked "!!!!Lost password ?">><<ifLocked "Click on">> <<purge>><<ifLocked "to delete any content locked in the encrypted vault.">>
return res === null ? src : res
},
isEncrypted: function(src) {
return src.substr(0, this.prefix.length) == this.prefix
},
// these should not be found by indexOf() of this source;
// as < is encoded as <, this won't happen anyway
vaultAreaId: 'vaultArea',
startSaveVaultArea: '<div id="vaultArea">',
endSaveVaultArea: '</div>',
postVaultAreaMarker: '<!--POST-VAULTAREA-->',
// adapted from locateStoreArea
locateVaultArea: function(original) {
if(!original) return null
// the vaultArea div should be just before the storeArea div
var posOpeningDiv = original.indexOf(this.startSaveVaultArea)
var limitClosingDiv = original.indexOf(this.postVaultAreaMarker)
// startSaveArea is globally available (should be deprecated though)
if(limitClosingDiv == -1)
limitClosingDiv = original.indexOf(startSaveArea)
var posClosingDiv = original.lastIndexOf(this.endSaveVaultArea, limitClosingDiv)
return (posOpeningDiv == -1 || posClosingDiv == -1) ? null :
[posOpeningDiv, posClosingDiv];
},
// implementing in-tiddler vault
singleVaultTiddlerName: "EncryptedVault",
updateVaultTiddler: function(encryptedContentOrNull) {
if(encryptedContentOrNull === null) {
// TODO: ask for confirmation? make undo-able?
store.deleteTiddler(this.singleVaultTiddlerName)
return
}
var encryptedContent = encryptedContentOrNull
var vaultTiddler = store.fetchTiddler(this.singleVaultTiddlerName) // fetch existing or create
if(!vaultTiddler) {
vaultTiddler = new Tiddler(this.singleVaultTiddlerName)
vaultTiddler.tags.push(this.tagDontEncrypt)
store.addTiddler(vaultTiddler) // doesn't setDirty, unlike createTiddler
}
vaultTiddler.text = encryptedContent
vaultTiddler.modified = new Date()
// TODO: update modifier? (see core methods that update the fields) something else?
},
getVaultTiddlerContent: function() {
var vaultTiddler = store.fetchTiddler(this.singleVaultTiddlerName)
return vaultTiddler ? vaultTiddler.text : null
// same, but returning null is implicit: return store.getTiddlerText(this.singleVaultTiddlerName)
},
tagDontEncrypt: "unencrypted",
tagForceEncrypt: "forceEncryption",
getUnencryptedArray: function() {
var unencryptedList = store.fetchTiddler("ListUnencrypted")
return !unencryptedList ? [] :
store.filterTiddlers(unencryptedList.text)
},
// this is used to avoid quadratic complexity (filtering N tiddlers per each N tiddlers)
unencryptedCacheMap: null,
populateUnencryptedCacheMap: function() {
var unencryptedArray = this.getUnencryptedArray()
this.unencryptedCacheMap = {}
for(var i = 0; i < unencryptedArray.length; i++) {
var title = unencryptedArray[i].title
this.unencryptedCacheMap[title] = true
}
},
clearnUnencryptedCacheMap: function() {
this.unencryptedCacheMap = null
},
shouldEncryptTiddler: function(tiddler) {
if(tiddler.isTagged(this.tagForceEncrypt)) return true
if(store.isShadowTiddler([tiddler.title])
|| tiddler.title === "ListUnencrypted"
|| tiddler.isTagged("systemConfig")
|| tiddler.isTagged(this.tagDontEncrypt)
) return false
if(this.unencryptedCacheMap) {
return !this.unencryptedCacheMap[tiddler.title]
} else {
var unencryptedArray = this.getUnencryptedArray()
if(unencryptedArray.indexOf(tiddler) !== -1) return false
/*var unencryptedList = store.fetchTiddler("ListUnencrypted")
if(unencryptedList) {
var filter = unencryptedList.text
var tids = store.filterTiddlers(filter)
if(tids.indexOf(tiddler) !== -1) return false
}*/
}
return true
},
// TODO: review terms (locked, loaded, ..) – should be consistent
getVaultContent: function() {
var inTiddlerContent = this.getVaultTiddlerContent()
if(inTiddlerContent) return inTiddlerContent
// backward compatibility with 1.x
var el = document.getElementById(this.vaultAreaId)
return el ? el.innerHTML : null
},
// TODO: remove state; may be move some sections above to ~submodules instead
// loaded: falsy by default (we don't set it so that installing twice won't hurt)
isLocked: function() {
if(this.loaded) return false
var vaultContent = this.getVaultContent()
return vaultContent === null || this.isEncrypted(vaultContent)
},
existsAndIsLocked: function() {
return this.getVaultContent() !== null &&
this.isLocked()
},
// TODO: fix lingo (missing, prompt); review
// returns a boolean indicating success
load: function() {
if (!this.existsAndIsLocked()) {
// decorate
Tiddler.prototype.doNotSave = function() {
var shouldEncrypt = config.extensions.vault.shouldEncryptTiddler(this)
var mode = config.extensions.vault.doNotSaveMode
if((shouldEncrypt && mode == "encrypted" || !shouldEncrypt && mode == "unencrypted") &&
config.extensions.vault.password) return true
return config.extensions.vault.originals.Tiddler_doNotSave.apply(this, arguments)
}
TiddlyWiki.prototype.allEncryptedTiddlersAsHtml = function() {
var prevMode = config.extensions.vault.doNotSaveMode
config.extensions.vault.doNotSaveMode = "unencrypted"
var result = this.allTiddlersAsHtml()
config.extensions.vault.doNotSaveMode = prevMode
return result
}
// TODO: review all decorations; may be move (encapsulate) some bits to ceVault methods
// decorate
window.updateOriginal = function(original, posDiv) {
var ceVault = config.extensions.vault
var vaultIsUpdatable = !ceVault.locateVaultArea(original) ||
!ceVault.existsAndIsLocked(true) || ceVault.shouldPurge
ceVault.populateUnencryptedCacheMap()
var password = ceVault.password
ceVault.updateVaultTiddler(!password ? null : ceVault.encrypt(store.allEncryptedTiddlersAsHtml(), password))
var revised = ceVault.originals.updateOriginal.apply(this, arguments)
//# move reporting results outside updateOriginal?
if(vaultIsUpdatable) {
displayMessage(config.messages[password ? 'vaultEncryptedInfo' : 'emptyVaultInfo'])
} else
displayMessage(config.messages.vaultUnchangedInfo)
if(config.extensions.vault.existsAndIsLocked()) return
createTiddlyButton(form, params.label, params.tooltip, this.onClick)
},
onClick: function(event) {
var form = event.target.parentElement
jQuery(form).empty()
var refreshForm = function() { config.macros.setPassword.refresh(form) }
var input = createTiddlyElement(form, 'input', null, null, null, {
type: 'password'
})
input.focus()
var ceVault = config.extensions.vault
jQuery(input).on('keydown', function(event) {
if(event.key === 'Escape') return refreshForm()
if(event.key !== 'Enter') return
ceVault.password = input.value
refreshForm()
displayMessage(ceVault.password ?
config.messages.passwordSet :
config.messages.passwordUnset)
return false
})
.on('blur', refreshForm)
// TODO: explain they have to type and press "enter" and/or add a button for that
// TODO: add also way to cancel (→ refresh form)
return false;
}
}
config.macros.purge = {
handler: function(place, macroName, params, wikifier, paramString, tiddler) {
var label = params[0] || "purge vault";
var tooltip = params[1] || "Delete locked vault";
var ceVault = config.extensions.vault
if (ceVault.existsAndIsLocked())
createTiddlyButton(place, label, tooltip, this.onClick);
},
onClick: function() {
var ceVault = config.extensions.vault
if (!ceVault.existsAndIsLocked())
alert(config.messages.noLockedVaultNoPurge);
else
if(confirm(config.messages.purgeConfirm)) {
ceVault.shouldPurge = true;
alert(config.messages.vaultPurgedInfo);
}
return false;
}
}
config.macros.ifLocked = {
handler: function(place, macroName, params, wikifier, paramString, tiddler) {
if(config.extensions.vault.existsAndIsLocked())
wikify(params[0], place, null, tiddler);
}
}
config.macros.ifUnlocked = {
handler: function(place, macroName, params, wikifier, paramString, tiddler) {
if(!config.extensions.vault.existsAndIsLocked())
wikify(params[0], place, null, tiddler);
}
}
//}}}
пятница, 7 марта 2025 г. в 20:35:43 UTC+3, Yakov: