mirror of
				https://github.com/Jermolene/TiddlyWiki5
				synced 2025-10-30 15:13:00 +00:00 
			
		
		
		
	Add support for importing encrypted TiddlyWiki documents
This commit is contained in:
		| @@ -58,3 +58,7 @@ Error message and password prompt | |||||||
| 	line-height: 20px; | 	line-height: 20px; | ||||||
| 	padding-bottom: 16px; | 	padding-bottom: 16px; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | .tw-password-wrapper input { | ||||||
|  | 	width: 100%; | ||||||
|  | } | ||||||
|   | |||||||
							
								
								
									
										37
									
								
								boot/boot.js
									
									
									
									
									
								
							
							
						
						
									
										37
									
								
								boot/boot.js
									
									
									
									
									
								
							| @@ -448,11 +448,13 @@ Adds a new password prompt. Options are: | |||||||
| submitText: text to use for submit button (defaults to "Login") | submitText: text to use for submit button (defaults to "Login") | ||||||
| serviceName: text of the human readable service name | serviceName: text of the human readable service name | ||||||
| noUserName: set true to disable username prompt | noUserName: set true to disable username prompt | ||||||
|  | canCancel: set true to enable a cancel button (callback called with null) | ||||||
| callback: function to be called on submission with parameter of object {username:,password:}. Callback must return `true` to remove the password prompt | callback: function to be called on submission with parameter of object {username:,password:}. Callback must return `true` to remove the password prompt | ||||||
| */ | */ | ||||||
| $tw.utils.PasswordPrompt.prototype.createPrompt = function(options) { | $tw.utils.PasswordPrompt.prototype.createPrompt = function(options) { | ||||||
| 	// Create and add the prompt to the DOM | 	// Create and add the prompt to the DOM | ||||||
| 	var submitText = options.submitText || "Login", | 	var self = this, | ||||||
|  | 		submitText = options.submitText || "Login", | ||||||
| 		dm = $tw.utils.domMaker, | 		dm = $tw.utils.domMaker, | ||||||
| 		children = [dm("h1",{text: options.serviceName})]; | 		children = [dm("h1",{text: options.serviceName})]; | ||||||
| 	if(!options.noUserName) { | 	if(!options.noUserName) { | ||||||
| @@ -465,6 +467,19 @@ $tw.utils.PasswordPrompt.prototype.createPrompt = function(options) { | |||||||
| 		attributes: {type: "password", name: "password", placeholder: "Password"}, | 		attributes: {type: "password", name: "password", placeholder: "Password"}, | ||||||
| 		"class": "input-small" | 		"class": "input-small" | ||||||
| 	})); | 	})); | ||||||
|  | 	if(options.canCancel) { | ||||||
|  | 		children.push(dm("button",{ | ||||||
|  | 			text: "Cancel", | ||||||
|  | 			"class": "btn", | ||||||
|  | 			eventListeners: [{ | ||||||
|  | 					name: "click", | ||||||
|  | 					handlerFunction: function(event) { | ||||||
|  | 						self.removePrompt(promptInfo); | ||||||
|  | 						options.callback(null); | ||||||
|  | 					} | ||||||
|  | 				}] | ||||||
|  | 		})); | ||||||
|  | 	} | ||||||
| 	children.push(dm("button",{ | 	children.push(dm("button",{ | ||||||
| 		attributes: {type: "submit"}, | 		attributes: {type: "submit"}, | ||||||
| 		text: submitText, | 		text: submitText, | ||||||
| @@ -492,12 +507,7 @@ $tw.utils.PasswordPrompt.prototype.createPrompt = function(options) { | |||||||
| 		// Call the callback | 		// Call the callback | ||||||
| 		if(options.callback(data)) { | 		if(options.callback(data)) { | ||||||
| 			// Remove the prompt if the callback returned true | 			// Remove the prompt if the callback returned true | ||||||
| 			var i = self.passwordPrompts.indexOf(promptInfo); | 			self.removePrompt(promptInfo); | ||||||
| 			if(i !== -1) { |  | ||||||
| 				self.passwordPrompts.splice(i,1); |  | ||||||
| 				promptInfo.form.parentNode.removeChild(promptInfo.form); |  | ||||||
| 				self.setWrapperDisplay(); |  | ||||||
| 			} |  | ||||||
| 		} else { | 		} else { | ||||||
| 			// Clear the password if the callback returned false | 			// Clear the password if the callback returned false | ||||||
| 			$tw.utils.each(form.elements,function(element) { | 			$tw.utils.each(form.elements,function(element) { | ||||||
| @@ -520,6 +530,15 @@ $tw.utils.PasswordPrompt.prototype.createPrompt = function(options) { | |||||||
| 	this.setWrapperDisplay(); | 	this.setWrapperDisplay(); | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | $tw.utils.PasswordPrompt.prototype.removePrompt = function(promptInfo) { | ||||||
|  | 	var i = this.passwordPrompts.indexOf(promptInfo); | ||||||
|  | 	if(i !== -1) { | ||||||
|  | 		this.passwordPrompts.splice(i,1); | ||||||
|  | 		promptInfo.form.parentNode.removeChild(promptInfo.form); | ||||||
|  | 		this.setWrapperDisplay(); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
| /* | /* | ||||||
| Crypto helper object for encrypted content. It maintains the password text in a closure, and provides methods to change | Crypto helper object for encrypted content. It maintains the password text in a closure, and provides methods to change | ||||||
| the password, and to encrypt/decrypt a block of text | the password, and to encrypt/decrypt a block of text | ||||||
| @@ -530,7 +549,9 @@ $tw.utils.Crypto = function() { | |||||||
| 		callSjcl = function(method,inputText) { | 		callSjcl = function(method,inputText) { | ||||||
| 			var outputText; | 			var outputText; | ||||||
| 			try { | 			try { | ||||||
| 				outputText = sjcl[method](password,inputText); | 				if(password) { | ||||||
|  | 					outputText = sjcl[method](password,inputText); | ||||||
|  | 				} | ||||||
| 			} catch(ex) { | 			} catch(ex) { | ||||||
| 				console.log("Crypto error:" + ex); | 				console.log("Crypto error:" + ex); | ||||||
| 				outputText = null;	 | 				outputText = null;	 | ||||||
|   | |||||||
| @@ -1071,17 +1071,70 @@ exports.readFile = function(file,callback) { | |||||||
| 	// Onload | 	// Onload | ||||||
| 	reader.onload = function(event) { | 	reader.onload = function(event) { | ||||||
| 		// Deserialise the file contents | 		// Deserialise the file contents | ||||||
| 		var tiddlerFields = {title: file.name || "Untitled", type: type}; | 		var text = event.target.result, | ||||||
|  | 			tiddlerFields = {title: file.name || "Untitled", type: type}; | ||||||
| 		// Are we binary? | 		// Are we binary? | ||||||
| 		if(isBinary) { | 		if(isBinary) { | ||||||
| 			// The base64 section starts after the first comma in the data URI | 			// The base64 section starts after the first comma in the data URI | ||||||
| 			var commaPos = event.target.result.indexOf(","); | 			var commaPos = text.indexOf(","); | ||||||
| 			if(commaPos !== -1) { | 			if(commaPos !== -1) { | ||||||
| 				tiddlerFields.text = event.target.result.substr(commaPos+1); | 				tiddlerFields.text = text.substr(commaPos+1); | ||||||
| 				callback([tiddlerFields]); | 				callback([tiddlerFields]); | ||||||
| 			} | 			} | ||||||
| 		} else { | 		} else { | ||||||
| 			callback(self.deserializeTiddlers(type,event.target.result,tiddlerFields)); | 			// Check whether this is an encrypted TiddlyWiki file | ||||||
|  | 			var encryptedStoreAreaStartMarker = "<pre id=\"encryptedStoreArea\" type=\"text/plain\" style=\"display:none;\">", | ||||||
|  | 				encryptedStoreAreaStart = text.indexOf(encryptedStoreAreaStartMarker), | ||||||
|  | 				encryptedStoreAreaEnd = encryptedStoreAreaStart !== -1 ? text.indexOf("</pre>",encryptedStoreAreaStart) : -1; | ||||||
|  | 			if(encryptedStoreAreaStart !== -1 && encryptedStoreAreaEnd !== -1) { | ||||||
|  | 				var encryptedJson = $tw.utils.htmlDecode(text.substring(encryptedStoreAreaStart + encryptedStoreAreaStartMarker.length,encryptedStoreAreaEnd-1)), | ||||||
|  | 					attemptDecryption = function() { | ||||||
|  | 						var decryptedText = $tw.crypto.decrypt(encryptedJson); | ||||||
|  | 						if(decryptedText) { | ||||||
|  | 							var json = JSON.parse(decryptedText), | ||||||
|  | 								tiddlers = []; | ||||||
|  | 							for(var title in json) { | ||||||
|  | 								tiddlers.push(json[title]); | ||||||
|  | 							} | ||||||
|  | 							return tiddlers; | ||||||
|  | 						} else { | ||||||
|  | 							return null; | ||||||
|  | 						} | ||||||
|  | 					}; | ||||||
|  | 				// Try to decrypt with the current password | ||||||
|  | 				var tiddlers = attemptDecryption(); | ||||||
|  | 				if(tiddlers) { | ||||||
|  | 					callback(tiddlers); | ||||||
|  | 				} else { | ||||||
|  | 					// Prompt for a new password and keep trying | ||||||
|  | 					$tw.passwordPrompt.createPrompt({ | ||||||
|  | 						serviceName: "Enter a password to decrypt the imported TiddlyWiki", | ||||||
|  | 						noUserName: true, | ||||||
|  | 						canCancel: true, | ||||||
|  | 						submitText: "Decrypt", | ||||||
|  | 						callback: function(data) { | ||||||
|  | 							// Exit if the user cancelled | ||||||
|  | 							if(!data) { | ||||||
|  | 								return false; | ||||||
|  | 							} | ||||||
|  | 							// Attempt to decrypt the tiddlers | ||||||
|  | 							$tw.crypto.setPassword(data.password); | ||||||
|  | 							var tiddlers = attemptDecryption(); | ||||||
|  | 							if(tiddlers) { | ||||||
|  | 								callback(tiddlers); | ||||||
|  | 								// Exit and remove the password prompt | ||||||
|  | 								return true; | ||||||
|  | 							} else { | ||||||
|  | 								// We didn't decrypt everything, so continue to prompt for password | ||||||
|  | 								return false; | ||||||
|  | 							} | ||||||
|  | 						} | ||||||
|  | 					}); | ||||||
|  | 				} | ||||||
|  | 			} else { | ||||||
|  | 				// Try to deserialise any tiddlers in the file | ||||||
|  | 				callback(self.deserializeTiddlers(type,text,tiddlerFields)); | ||||||
|  | 			} | ||||||
| 		} | 		} | ||||||
| 	}; | 	}; | ||||||
| 	// Kick off the read | 	// Kick off the read | ||||||
|   | |||||||
| @@ -16,6 +16,7 @@ type: text/vnd.tiddlywiki | |||||||
|  |  | ||||||
| !! Improvements | !! Improvements | ||||||
|  |  | ||||||
|  | * Added support for importing encrypted TiddlyWiki documents | ||||||
| * Added the [[highlight.js|http://highlightjs.org/]] syntax highlighting plugin: http://tiddlywiki.com/highlightdemo.html (thanks to João Bolila, @jbolila on GitHub) | * Added the [[highlight.js|http://highlightjs.org/]] syntax highlighting plugin: http://tiddlywiki.com/highlightdemo.html (thanks to João Bolila, @jbolila on GitHub) | ||||||
| * Added the first export option to the ''Tools'' tab of the [[control panel|$:/ControlPanel]] | * Added the first export option to the ''Tools'' tab of the [[control panel|$:/ControlPanel]] | ||||||
| * [[Added|https://github.com/Jermolene/TiddlyWiki5/commit/ffcc215e8f8896be96093579abc5bcfb76335e66]] an ellipsis for [[advanced search|$:/AdvancedSearch]] next to the search box in the sidebar | * [[Added|https://github.com/Jermolene/TiddlyWiki5/commit/ffcc215e8f8896be96093579abc5bcfb76335e66]] an ellipsis for [[advanced search|$:/AdvancedSearch]] next to the search box in the sidebar | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Jermolene
					Jermolene